Skip to content
This repository has been archived by the owner on Jun 21, 2023. It is now read-only.

Commit

Permalink
Add lint checking events (vercel#26089)
Browse files Browse the repository at this point in the history
* Add lint checking events

* remove extra log

* Update check

* Update version check
  • Loading branch information
ijjk committed Jun 15, 2021
1 parent 76034cc commit d128722
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 34 deletions.
3 changes: 2 additions & 1 deletion packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ export default async function build(
dir,
lintDirs,
config.experimental.cpus,
config.experimental.workerThreads
config.experimental.workerThreads,
telemetry
)
})
}
Expand Down
37 changes: 33 additions & 4 deletions packages/next/cli/next-lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { cliCommand } from '../bin/next'
import { ESLINT_DEFAULT_DIRS } from '../lib/constants'
import { runLintCheck } from '../lib/eslint/runLintCheck'
import { printAndExit } from '../server/lib/utils'
import { Telemetry } from '../telemetry/storage'
import loadConfig from '../next-server/server/config'
import { PHASE_PRODUCTION_BUILD } from '../next-server/lib/constants'
import { eventLintCheckCompleted } from '../telemetry/events'
import { CompileError } from '../lib/compile-error'

const eslintOptions = (args: arg.Spec) => ({
overrideConfigFile: args['--config'] || null,
Expand Down Expand Up @@ -135,11 +140,35 @@ const nextLint: cliCommand = (argv) => {
},
[]
)

runLintCheck(baseDir, lintDirs, false, eslintOptions(args))
.then((results) => {
if (results) {
console.log(results)
.then(async (lintResults) => {
const lintOutput =
typeof lintResults === 'string' ? lintResults : lintResults?.output

if (typeof lintResults !== 'string' && lintResults?.eventInfo) {
const conf = await loadConfig(PHASE_PRODUCTION_BUILD, baseDir)
const telemetry = new Telemetry({
distDir: join(baseDir, conf.distDir),
})
telemetry.record(
eventLintCheckCompleted({
...lintResults.eventInfo,
buildLint: false,
})
)
await telemetry.flush()
}

if (
typeof lintResults !== 'string' &&
lintResults?.isError &&
lintOutput
) {
throw new CompileError(lintOutput)
}

if (lintOutput) {
console.log(lintOutput)
} else {
console.log(chalk.green('✔ No ESLint warnings or errors'))
}
Expand Down
60 changes: 49 additions & 11 deletions packages/next/lib/eslint/customFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface LintMessage {
column: number
}

interface LintResult {
export interface LintResult {
filePath: string
messages: LintMessage[]
errorCount: number
Expand All @@ -28,7 +28,11 @@ function formatMessage(
dir: string,
messages: LintMessage[],
filePath: string
): string | void {
): {
output: string
nextPluginErrorCount: number
nextPluginWarningCount: number
} {
let fileName = path.posix.normalize(
path.relative(dir, filePath).replace(/\\/g, '/')
)
Expand All @@ -38,6 +42,8 @@ function formatMessage(
}

let output = '\n' + chalk.cyan(fileName)
let nextPluginWarningCount = 0
let nextPluginErrorCount = 0

for (let i = 0; i < messages.length; i++) {
const { message, severity, line, column, ruleId } = messages[i]
Expand All @@ -53,6 +59,14 @@ function formatMessage(
' '
}

if (ruleId?.includes('@next/next')) {
if (severity === MessageSeverity.Warning) {
nextPluginWarningCount += 1
} else {
nextPluginErrorCount += 1
}
}

if (severity === MessageSeverity.Warning) {
output += chalk.yellow.bold('Warning') + ': '
} else {
Expand All @@ -66,19 +80,43 @@ function formatMessage(
}
}

return output
return {
output,
nextPluginErrorCount,
nextPluginWarningCount,
}
}

export function formatResults(baseDir: string, results: LintResult[]): string {
export function formatResults(
baseDir: string,
results: LintResult[]
): {
output: string
totalNextPluginErrorCount: number
totalNextPluginWarningCount: number
} {
let totalNextPluginErrorCount = 0
let totalNextPluginWarningCount = 0

const formattedResults = results
.filter(({ messages }) => messages?.length)
.map(({ messages, filePath }) => formatMessage(baseDir, messages, filePath))
.map(({ messages, filePath }) => {
const res = formatMessage(baseDir, messages, filePath)
totalNextPluginErrorCount += res.nextPluginErrorCount
totalNextPluginWarningCount += res.nextPluginWarningCount
return res.output
})
.join('\n')

return formattedResults.length > 0
? formattedResults +
`\n\n${chalk.bold(
'Need to disable some ESLint rules? Learn more here:'
)} https://nextjs.org/docs/basic-features/eslint#disabling-rules\n`
: ''
return {
output:
formattedResults.length > 0
? formattedResults +
`\n\n${chalk.bold(
'Need to disable some ESLint rules? Learn more here:'
)} https://nextjs.org/docs/basic-features/eslint#disabling-rules\n`
: '',
totalNextPluginErrorCount,
totalNextPluginWarningCount,
}
}
46 changes: 37 additions & 9 deletions packages/next/lib/eslint/runLintCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import * as CommentJson from 'next/dist/compiled/comment-json'
import { formatResults } from './customFormatter'
import { writeDefaultConfig } from './writeDefaultConfig'
import { existsSync, findPagesDir } from '../find-pages-dir'
import { CompileError } from '../compile-error'
import {
hasNecessaryDependencies,
NecessaryDependencies,
} from '../has-necessary-dependencies'

import * as Log from '../../build/output/log'
import { EventLintCheckCompleted } from '../../telemetry/events/build'

type Config = {
plugins: string[]
Expand All @@ -29,14 +29,23 @@ async function lint(
eslintrcFile: string | null,
pkgJsonPath: string | null,
eslintOptions: any = null
): Promise<string | null> {
): Promise<
| string
| null
| {
output: string | null
isError: boolean
eventInfo: EventLintCheckCompleted
}
> {
// Load ESLint after we're sure it exists:
const mod = await import(deps.resolved)
const mod = await import(deps.resolved.get('eslint')!)

const { ESLint } = mod
let eslintVersion = ESLint.version

if (!ESLint) {
const eslintVersion: string | undefined = mod?.CLIEngine?.version
eslintVersion = mod?.CLIEngine?.version

if (!eslintVersion || semver.lt(eslintVersion, '7.0.0')) {
return `${chalk.red(
Expand Down Expand Up @@ -99,22 +108,41 @@ async function lint(
eslint = new ESLint(options)
}
}
const lintStart = process.hrtime()

const results = await eslint.lintFiles(lintDirs)
if (options.fix) await ESLint.outputFixes(results)

if (ESLint.getErrorResults(results)?.length > 0) {
throw new CompileError(await formatResults(baseDir, results))
const formattedResult = formatResults(baseDir, results)
const lintEnd = process.hrtime(lintStart)

return {
output: formattedResult.output,
isError: ESLint.getErrorResults(results)?.length > 0,
eventInfo: {
durationInSeconds: lintEnd[0],
eslintVersion: eslintVersion,
lintedFilesCount: results.length,
lintFix: !!options.fix,
nextEslintPluginVersion: nextEslintPluginIsEnabled
? require(path.join(
path.dirname(deps.resolved.get('eslint-config-next')!),
'package.json'
)).version
: null,
nextEslintPluginErrorsCount: formattedResult.totalNextPluginErrorCount,
nextEslintPluginWarningsCount:
formattedResult.totalNextPluginWarningCount,
},
}

return results?.length > 0 ? formatResults(baseDir, results) : null
}

export async function runLintCheck(
baseDir: string,
lintDirs: string[],
lintDuringBuild: boolean = false,
eslintOptions: any = null
): Promise<string | null> {
): ReturnType<typeof lint> {
try {
// Find user's .eslintrc file
const eslintrcFile =
Expand Down
6 changes: 2 additions & 4 deletions packages/next/lib/has-necessary-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const requiredLintPackages = [
]

export type NecessaryDependencies = {
resolved: string
resolved: Map<string, string>
}

export async function hasNecessaryDependencies(
Expand Down Expand Up @@ -46,9 +46,7 @@ export async function hasNecessaryDependencies(

if (missingPackages.length < 1) {
return {
resolved: checkESLintDeps
? resolutions.get('eslint')!
: resolutions.get('typescript')!,
resolved: resolutions,
}
}

Expand Down
29 changes: 25 additions & 4 deletions packages/next/lib/verifyAndLint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import { Worker } from 'jest-worker'
import { existsSync } from 'fs'
import { join } from 'path'
import { ESLINT_DEFAULT_DIRS } from './constants'
import { Telemetry } from '../telemetry/storage'
import { eventLintCheckCompleted } from '../telemetry/events'
import { CompileError } from './compile-error'

export async function verifyAndLint(
dir: string,
configLintDirs: string[] | undefined,
numWorkers: number | undefined,
enableWorkerThreads: boolean | undefined
enableWorkerThreads: boolean | undefined,
telemetry: Telemetry
): Promise<void> {
try {
const lintWorkers = new Worker(require.resolve('./eslint/runLintCheck'), {
Expand All @@ -32,13 +36,30 @@ export async function verifyAndLint(
)

const lintResults = await lintWorkers.runLintCheck(dir, lintDirs, true)
if (lintResults) {
console.log(lintResults)
const lintOutput =
typeof lintResults === 'string' ? lintResults : lintResults?.output

if (typeof lintResults !== 'string' && lintResults?.eventInfo) {
telemetry.record(
eventLintCheckCompleted({
...lintResults.eventInfo,
buildLint: true,
})
)
}

if (typeof lintResults !== 'string' && lintResults?.isError && lintOutput) {
await telemetry.flush()
throw new CompileError(lintOutput)
}

if (lintOutput) {
console.log(lintOutput)
}

lintWorkers.end()
} catch (err) {
if (err.type === 'CompileError') {
if (err.type === 'CompileError' || err instanceof CompileError) {
console.error(chalk.red('\nFailed to compile.'))
console.error(err.message)
process.exit(1)
Expand Down
4 changes: 3 additions & 1 deletion packages/next/lib/verifyTypeScriptSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export async function verifyTypeScriptSetup(
)

// Load TypeScript after we're sure it exists:
const ts = (await import(deps.resolved)) as typeof import('typescript')
const ts = (await import(
deps.resolved.get('typescript')!
)) as typeof import('typescript')

if (semver.lt(ts.version, '4.3.2')) {
log.warn(
Expand Down
21 changes: 21 additions & 0 deletions packages/next/telemetry/events/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ export function eventTypeCheckCompleted(
}
}

const EVENT_LINT_CHECK_COMPLETED = 'NEXT_LINT_CHECK_COMPLETED'
export type EventLintCheckCompleted = {
durationInSeconds: number
eslintVersion: string | null
lintedFilesCount?: number
lintFix?: boolean
buildLint?: boolean
nextEslintPluginVersion?: string | null
nextEslintPluginErrorsCount?: number
nextEslintPluginWarningsCount?: number
}

export function eventLintCheckCompleted(
event: EventLintCheckCompleted
): { eventName: string; payload: EventLintCheckCompleted } {
return {
eventName: EVENT_LINT_CHECK_COMPLETED,
payload: event,
}
}

const EVENT_BUILD_COMPLETED = 'NEXT_BUILD_COMPLETED'
type EventBuildCompleted = {
durationInSeconds: number
Expand Down

0 comments on commit d128722

Please sign in to comment.