7a1c9eb17e
This PR introduces an improved developer experience when `next lint` is run for the first time. ### Current behavior `eslint-config-next` is a required package that must be installed before proceeding with `next lint` or `next build`: ![image](https://user-images.githubusercontent.com/12476932/123468791-43088100-d5c0-11eb-9ad0-5beb80b6c968.png) Although this has helped many developers start using the new ESLint config, this has also resulted in a few issues: - Users are required to install the full config (`eslint-config-next`) even if they do not use it or use the Next.js plugin directly (`eslint-plugin-next`). - #26348 - There's some confusion on why `eslint-config-next` needs to be installed or how it should be used instead of `eslint-plugin-next`. - #26574 - #26475 - #26438 ### New behavior Instead of enforcing `eslint-config-next` as a required package, this PR prompts the user by asking what config they would like to start. This happens when `next lint` is run for the first time **and** if no ESLint configuration is detected in the application. <img src="https://user-images.githubusercontent.com/12476932/124331177-e1668a80-db5c-11eb-8915-38d3dc20f5d4.gif" width="800" /> - The CLI will take care of installing `eslint` or `eslint-config-next` if either is not already installed - Users now have the option to choose between a strict configuration (`next/core-web-vitals`) or just the base configuration (`next`) - For users that decide to create their own ESLint configuration, or already have an existing one, **installing `eslint-config-next` will not be a requirement for `next lint` or `next build` to run**. A warning message will just show if the Next.js ESLint plugin is not detected in an ESLint config. <img width="682" alt="Screen Shot 2021-06-25 at 3 02 12 PM" src="https://user-images.githubusercontent.com/12476932/123473329-6cc4a680-d5c6-11eb-9a57-d5c0b89a2732.png"> --- In addition, this PR also: - Fixes #26348 - Updates documentation to make it more clear what approach to take for new and existing ESLint configurations
136 lines
3.1 KiB
TypeScript
136 lines
3.1 KiB
TypeScript
import chalk from 'chalk'
|
|
import path from 'path'
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
export enum MessageSeverity {
|
|
Warning = 1,
|
|
Error = 2,
|
|
}
|
|
|
|
interface LintMessage {
|
|
ruleId: string | null
|
|
severity: 1 | 2
|
|
message: string
|
|
line: number
|
|
column: number
|
|
}
|
|
|
|
export interface LintResult {
|
|
filePath: string
|
|
messages: LintMessage[]
|
|
errorCount: number
|
|
warningCount: number
|
|
output?: string
|
|
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
|
|
): string {
|
|
let fileName = path.posix.normalize(
|
|
path.relative(dir, filePath).replace(/\\/g, '/')
|
|
)
|
|
|
|
if (!fileName.startsWith('.')) {
|
|
fileName = './' + fileName
|
|
}
|
|
|
|
let output = '\n' + chalk.cyan(fileName)
|
|
|
|
for (let i = 0; i < messages.length; i++) {
|
|
const { message, severity, line, column, ruleId } = messages[i]
|
|
|
|
output = output + '\n'
|
|
|
|
if (line && column) {
|
|
output =
|
|
output +
|
|
chalk.yellow(line.toString()) +
|
|
':' +
|
|
chalk.yellow(column.toString()) +
|
|
' '
|
|
}
|
|
|
|
if (severity === MessageSeverity.Warning) {
|
|
output += chalk.yellow.bold('Warning') + ': '
|
|
} else {
|
|
output += chalk.red.bold('Error') + ': '
|
|
}
|
|
|
|
output += message
|
|
|
|
if (ruleId) {
|
|
output += ' ' + chalk.gray.bold(ruleId)
|
|
}
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
export function formatResults(
|
|
baseDir: string,
|
|
results: LintResult[],
|
|
format: (r: LintResult[]) => string
|
|
): {
|
|
output: string
|
|
totalNextPluginErrorCount: number
|
|
totalNextPluginWarningCount: number
|
|
} {
|
|
let totalNextPluginErrorCount = 0
|
|
let totalNextPluginWarningCount = 0
|
|
let resultsWithMessages = results.filter(({ messages }) => messages?.length)
|
|
|
|
// 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:
|
|
resultsWithMessages.length > 0
|
|
? output +
|
|
`\n\n${chalk.cyan(
|
|
'info'
|
|
)} - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rules`
|
|
: '',
|
|
totalNextPluginErrorCount,
|
|
totalNextPluginWarningCount,
|
|
}
|
|
}
|