Skip to content

Commit

Permalink
feat: support ESLint on terminal log
Browse files Browse the repository at this point in the history
  • Loading branch information
fi3ework committed Jul 17, 2021
1 parent 6b710d7 commit 0819997
Show file tree
Hide file tree
Showing 13 changed files with 191 additions and 19 deletions.
4 changes: 3 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
lib
node_modules
playground
playground/react-ts
playground/vue2-vls
playground/vue3-vue-tsc
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
root: true,
extends: ['alloy', 'alloy/typescript'],
env: {
jest: true,
Expand Down
12 changes: 9 additions & 3 deletions packages/vite-plugin-checker/src/checkers/eslint/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os from 'os'
import path from 'path'
import invariant from 'tiny-invariant'
import { ESLint } from 'eslint'
import { parentPort } from 'worker_threads'
Expand All @@ -8,7 +9,7 @@ import {
diagnosticToTerminalLog,
diagnosticToViteError,
ensureCall,
normalizeTsDiagnostic,
normalizeEslintDiagnostic,
} from '../../logger'

import type { CreateDiagnostic } from '../../types'
Expand All @@ -19,8 +20,13 @@ const createDiagnostic: CreateDiagnostic<'typescript'> = (pluginConfig) => {
let currErr: ErrorPayload['err'] | null = null

return {
config: ({ hmr }) => {
const engine = new ESLint()
config: async ({ hmr }) => {
const eslint = new ESLint()
const diagnostics = await eslint.lintFiles(path.resolve(process.cwd(), 'src/*.ts'))
const normalized = diagnostics.map((p) => normalizeEslintDiagnostic(p)).flat(1)
normalized.forEach((n) => {
console.log(diagnosticToTerminalLog(n))
})
},
configureServer({ root }) {},
}
Expand Down
84 changes: 70 additions & 14 deletions packages/vite-plugin-checker/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ErrorPayload } from 'vite'
import { codeFrameColumns, SourceLocation } from '@babel/code-frame'

import type { Range } from 'vscode-languageclient'
import type { ESLint } from 'eslint'

import type {
Diagnostic as LspDiagnostic,
Expand Down Expand Up @@ -37,26 +38,26 @@ interface NormalizedDiagnostic {
/** error code location */
loc?: SourceLocation
/** error level */
level?: DiagnosticCategory
level?: DiagnosticLevel
}

// copied from TypeScript because we used `import type`.
export enum DiagnosticCategory {
export enum DiagnosticLevel {
Warning = 0,
Error = 1,
Suggestion = 2,
Message = 3,
}

export function diagnosticToTerminalLog(d: NormalizedDiagnostic): string {
const labelMap: Record<DiagnosticCategory, string> = {
[DiagnosticCategory.Error]: chalk.bold.red('ERROR'),
[DiagnosticCategory.Warning]: chalk.bold.yellow('WARNING'),
[DiagnosticCategory.Suggestion]: chalk.bold.blue('SUGGESTION'),
[DiagnosticCategory.Message]: chalk.bold.cyan('MESSAGE'),
const labelMap: Record<DiagnosticLevel, string> = {
[DiagnosticLevel.Error]: chalk.bold.red('ERROR'),
[DiagnosticLevel.Warning]: chalk.bold.yellow('WARNING'),
[DiagnosticLevel.Suggestion]: chalk.bold.blue('SUGGESTION'),
[DiagnosticLevel.Message]: chalk.bold.cyan('MESSAGE'),
}

const levelLabel = labelMap[d.level || DiagnosticCategory.Error]
const levelLabel = labelMap[d.level || DiagnosticLevel.Error]
const fileLabel = chalk.green.bold('FILE') + ' '
const position = d.loc
? chalk.yellow(d.loc.start.line) + ':' + chalk.yellow(d.loc.start.column)
Expand Down Expand Up @@ -161,7 +162,7 @@ export function normalizeTsDiagnostic(d: TsDiagnostic): NormalizedDiagnostic {
id: fileName,
checker: 'TypeScript',
loc,
level: d.category,
level: d.category as any as DiagnosticLevel,
}
}

Expand All @@ -176,22 +177,22 @@ export function normalizeLspDiagnostic({
absFilePath: string
fileText: string
}): NormalizedDiagnostic {
let level = DiagnosticCategory.Error
let level = DiagnosticLevel.Error
const loc = lspRange2Location(diagnostic.range)
const codeFrame = codeFrameColumns(fileText, loc)

switch (diagnostic.severity) {
case 1: // Error
level = DiagnosticCategory.Error
level = DiagnosticLevel.Error
break
case 2: // Warning
level = DiagnosticCategory.Warning
level = DiagnosticLevel.Warning
break
case 3: // Information
level = DiagnosticCategory.Message
level = DiagnosticLevel.Message
break
case 4: // Hint
level = DiagnosticCategory.Suggestion
level = DiagnosticLevel.Suggestion
break
}

Expand Down Expand Up @@ -246,6 +247,61 @@ export function lspRange2Location(range: Range): SourceLocation {

/* --------------------------------- ESLint --------------------------------- */

const isNormalizedDiagnostic = (
d: NormalizedDiagnostic | null | undefined
): d is NormalizedDiagnostic => {
return Boolean(d)
}

export function normalizeEslintDiagnostic(diagnostic: ESLint.LintResult): NormalizedDiagnostic[] {
const firstMessage = diagnostic.messages[0]
if (!firstMessage) return []

return diagnostic.messages
.map((d) => {
let level = DiagnosticLevel.Error
switch (firstMessage.severity) {
case 0: // off, ignore
level = DiagnosticLevel.Error
return null
case 1: // warn
level = DiagnosticLevel.Warning
break
case 2: // error
level = DiagnosticLevel.Error
break
}

const loc: SourceLocation = {
start: {
line: firstMessage.line,
column: firstMessage.column,
},
end: {
line: firstMessage.endLine || 0,
column: firstMessage.endColumn,
},
}

const codeFrame = createFrame({
source: diagnostic.source ?? '',
location: loc,
})

return {
message: firstMessage.message,
conclusion: '',
codeFrame,
stripedCodeFrame: codeFrame && strip(codeFrame),
id: diagnostic.filePath,
checker: 'ESLint',
loc,
level,
} as any as NormalizedDiagnostic
})
.filter(isNormalizedDiagnostic)
}

/* ------------------------------ miscellaneous ----------------------------- */
export function ensureCall(callback: CallableFunction) {
setTimeout(() => {
Expand Down
2 changes: 1 addition & 1 deletion packages/vite-plugin-checker/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export * from './codeFrame'
export * from './worker'

const sharedConfigKeys: (keyof SharedConfig)[] = ['enableBuild', 'overlay']
const buildInCheckerKeys: BuildInCheckerNames[] = ['typescript', 'vueTsc', 'vls']
const buildInCheckerKeys: BuildInCheckerNames[] = ['typescript', 'vueTsc', 'vls', 'eslint']

function createCheckers(userConfig: UserPluginConfig, env: ConfigEnv): ServeAndBuildChecker[] {
const serveAndBuildCheckers: ServeAndBuildChecker[] = []
Expand Down
8 changes: 8 additions & 0 deletions packages/vite-plugin-checker/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ export type VlsConfig =
// TODO: support vls config
}>

/** ESLint checker configuration */
export type EslintConfig =
| boolean
| Partial<{
// TODO: support ESLint config
}>

/** checkers shared configuration */
export interface SharedConfig {
/**
Expand All @@ -46,6 +53,7 @@ export interface BuildInCheckers {
typescript: TscConfig
vueTsc: VueTscConfig
vls: VlsConfig
eslint: EslintConfig
}

export type BuildInCheckerNames = keyof BuildInCheckers
Expand Down
13 changes: 13 additions & 0 deletions playground/vanilla-ts/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
23 changes: 23 additions & 0 deletions playground/vanilla-ts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"private": true,
"name": "vite-react-typescript-starter",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"lint": "eslint --ext .js,.ts ./src/**"
},
"dependencies": {
"react": "^17.0.0",
"react-dom": "^17.0.0"
},
"devDependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@vitejs/plugin-react-refresh": "^1.3.1",
"typescript": "^4.1.2",
"vite": "^2.3.8",
"vite-plugin-checker": "workspace:*"
}
}
8 changes: 8 additions & 0 deletions playground/vanilla-ts/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { text } from './text'

var hello: string = 'Hello'

const rootDom = document.querySelector('#root')!
rootDom.innerHTML = hello + text

export {}
1 change: 1 addition & 0 deletions playground/vanilla-ts/src/text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const text = 'Vanilla JS/TS'
19 changes: 19 additions & 0 deletions playground/vanilla-ts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"types": ["vite/client"],
"allowJs": false,
"skipLibCheck": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": ["./src"]
}
14 changes: 14 additions & 0 deletions playground/vanilla-ts/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from 'vite'
import reactRefresh from '@vitejs/plugin-react-refresh'
import Checker from 'vite-plugin-checker'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
reactRefresh(),
Checker({
typescript: true,
eslint: true,
}),
],
})
21 changes: 21 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0819997

Please sign in to comment.