[ESLint] Adds --format
flag to next lint
(#27052)
Adds `--format` support to `next lint` to allow defining of additional formatters. Fixes #26387.
This commit is contained in:
parent
fac083bcfa
commit
078cfb59f9
4 changed files with 86 additions and 38 deletions
|
@ -63,9 +63,11 @@ const nextLint: cliCommand = (argv) => {
|
|||
'--cache': Boolean,
|
||||
'--cache-location': String,
|
||||
'--error-on-unmatched-pattern': Boolean,
|
||||
'--format': String,
|
||||
|
||||
// Aliases
|
||||
'-c': '--config',
|
||||
'-f': '--format',
|
||||
}
|
||||
|
||||
let args: arg.Result<arg.Spec>
|
||||
|
@ -112,6 +114,9 @@ const nextLint: cliCommand = (argv) => {
|
|||
Handling warnings:
|
||||
--quiet Report errors only - default: false
|
||||
--max-warnings Int Number of warnings to trigger nonzero exit code - default: -1
|
||||
|
||||
Output:
|
||||
-f, --format String Use a specific output format - default: Next.js custom formatter
|
||||
|
||||
Inline configuration comments:
|
||||
--no-inline-config Prevent comments from changing config or rules
|
||||
|
@ -148,6 +153,7 @@ const nextLint: cliCommand = (argv) => {
|
|||
|
||||
const reportErrorsOnly = Boolean(args['--quiet'])
|
||||
const maxWarnings = args['--max-warnings'] ?? -1
|
||||
const formatter = args['--format'] || null
|
||||
|
||||
runLintCheck(
|
||||
baseDir,
|
||||
|
@ -155,7 +161,8 @@ const nextLint: cliCommand = (argv) => {
|
|||
false,
|
||||
eslintOptions(args),
|
||||
reportErrorsOnly,
|
||||
maxWarnings
|
||||
maxWarnings,
|
||||
formatter
|
||||
)
|
||||
.then(async (lintResults) => {
|
||||
const lintOutput =
|
||||
|
|
|
@ -24,15 +24,35 @@ export interface LintResult {
|
|||
source?: string
|
||||
}
|
||||
|
||||
function pluginCount(
|
||||
messages: LintMessage[]
|
||||
): { nextPluginErrorCount: number; nextPluginWarningCount: number } {
|
||||
let nextPluginWarningCount = 0
|
||||
let nextPluginErrorCount = 0
|
||||
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const { severity, ruleId } = messages[i]
|
||||
|
||||
if (ruleId?.includes('@next/next')) {
|
||||
if (severity === MessageSeverity.Warning) {
|
||||
nextPluginWarningCount += 1
|
||||
} else {
|
||||
nextPluginErrorCount += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nextPluginErrorCount,
|
||||
nextPluginWarningCount,
|
||||
}
|
||||
}
|
||||
|
||||
function formatMessage(
|
||||
dir: string,
|
||||
messages: LintMessage[],
|
||||
filePath: string
|
||||
): {
|
||||
output: string
|
||||
nextPluginErrorCount: number
|
||||
nextPluginWarningCount: number
|
||||
} {
|
||||
): string {
|
||||
let fileName = path.posix.normalize(
|
||||
path.relative(dir, filePath).replace(/\\/g, '/')
|
||||
)
|
||||
|
@ -42,8 +62,6 @@ 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]
|
||||
|
@ -59,14 +77,6 @@ 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 {
|
||||
|
@ -80,16 +90,13 @@ function formatMessage(
|
|||
}
|
||||
}
|
||||
|
||||
return {
|
||||
output,
|
||||
nextPluginErrorCount,
|
||||
nextPluginWarningCount,
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
export function formatResults(
|
||||
baseDir: string,
|
||||
results: LintResult[]
|
||||
results: LintResult[],
|
||||
format: (r: LintResult[]) => string
|
||||
): {
|
||||
output: string
|
||||
totalNextPluginErrorCount: number
|
||||
|
@ -97,21 +104,28 @@ export function formatResults(
|
|||
} {
|
||||
let totalNextPluginErrorCount = 0
|
||||
let totalNextPluginWarningCount = 0
|
||||
let resultsWithMessages = results.filter(({ messages }) => messages?.length)
|
||||
|
||||
const formattedResults = results
|
||||
.filter(({ messages }) => messages?.length)
|
||||
.map(({ messages, filePath }) => {
|
||||
const res = formatMessage(baseDir, messages, filePath)
|
||||
totalNextPluginErrorCount += res.nextPluginErrorCount
|
||||
totalNextPluginWarningCount += res.nextPluginWarningCount
|
||||
return res.output
|
||||
})
|
||||
.join('\n')
|
||||
// Track number of Next.js plugin errors and warnings
|
||||
resultsWithMessages.forEach(({ messages }) => {
|
||||
const res = pluginCount(messages)
|
||||
totalNextPluginErrorCount += res.nextPluginErrorCount
|
||||
totalNextPluginWarningCount += res.nextPluginWarningCount
|
||||
})
|
||||
|
||||
// Use user defined formatter or Next.js's built-in custom formatter
|
||||
const output = format
|
||||
? format(resultsWithMessages)
|
||||
: resultsWithMessages
|
||||
.map(({ messages, filePath }) =>
|
||||
formatMessage(baseDir, messages, filePath)
|
||||
)
|
||||
.join('\n')
|
||||
|
||||
return {
|
||||
output:
|
||||
formattedResults.length > 0
|
||||
? formattedResults +
|
||||
resultsWithMessages.length > 0
|
||||
? output +
|
||||
`\n\n${chalk.bold(
|
||||
'Need to disable some ESLint rules? Learn more here:'
|
||||
)} https://nextjs.org/docs/basic-features/eslint#disabling-rules\n`
|
||||
|
|
|
@ -30,7 +30,8 @@ async function lint(
|
|||
pkgJsonPath: string | null,
|
||||
eslintOptions: any = null,
|
||||
reportErrorsOnly: boolean = false,
|
||||
maxWarnings: number = -1
|
||||
maxWarnings: number = -1,
|
||||
formatter: string | null = null
|
||||
): Promise<
|
||||
| string
|
||||
| null
|
||||
|
@ -111,12 +112,18 @@ async function lint(
|
|||
}
|
||||
}
|
||||
const lintStart = process.hrtime()
|
||||
|
||||
let results = await eslint.lintFiles(lintDirs)
|
||||
let selectedFormatter = null
|
||||
|
||||
if (options.fix) await ESLint.outputFixes(results)
|
||||
if (reportErrorsOnly) results = await ESLint.getErrorResults(results) // Only return errors if --quiet flag is used
|
||||
|
||||
const formattedResult = formatResults(baseDir, results)
|
||||
if (formatter) selectedFormatter = await eslint.loadFormatter(formatter)
|
||||
const formattedResult = formatResults(
|
||||
baseDir,
|
||||
results,
|
||||
selectedFormatter?.format
|
||||
)
|
||||
const lintEnd = process.hrtime(lintStart)
|
||||
const totalWarnings = results.reduce(
|
||||
(sum: number, file: LintResult) => sum + file.warningCount,
|
||||
|
@ -152,7 +159,8 @@ export async function runLintCheck(
|
|||
lintDuringBuild: boolean = false,
|
||||
eslintOptions: any = null,
|
||||
reportErrorsOnly: boolean = false,
|
||||
maxWarnings: number = -1
|
||||
maxWarnings: number = -1,
|
||||
formatter: string | null = null
|
||||
): ReturnType<typeof lint> {
|
||||
try {
|
||||
// Find user's .eslintrc file
|
||||
|
@ -215,7 +223,8 @@ export async function runLintCheck(
|
|||
pkgJsonPath,
|
||||
eslintOptions,
|
||||
reportErrorsOnly,
|
||||
maxWarnings
|
||||
maxWarnings,
|
||||
formatter
|
||||
)
|
||||
} catch (err) {
|
||||
throw err
|
||||
|
|
|
@ -305,5 +305,23 @@ describe('ESLint', () => {
|
|||
'Warning: External synchronous scripts are forbidden'
|
||||
)
|
||||
})
|
||||
|
||||
test('format flag supports additional user-defined formats', async () => {
|
||||
const { stdout, stderr } = await nextLint(
|
||||
dirMaxWarnings,
|
||||
['-f', 'codeframe'],
|
||||
{
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
}
|
||||
)
|
||||
|
||||
const output = stdout + stderr
|
||||
expect(output).toContain(
|
||||
'warning: External synchronous scripts are forbidden'
|
||||
)
|
||||
expect(stdout).toContain('<script src="https://example.com" />')
|
||||
expect(stdout).toContain('2 warnings found')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue