rsnext/packages/next/lib/eslint/runLintCheck.ts

210 lines
5.5 KiB
TypeScript

import { promises as fs } from 'fs'
import chalk from 'chalk'
import path from 'path'
import findUp from 'next/dist/compiled/find-up'
import semver from 'next/dist/compiled/semver'
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 {
hasNecessaryDependencies,
NecessaryDependencies,
} from '../has-necessary-dependencies'
import * as Log from '../../build/output/log'
import { EventLintCheckCompleted } from '../../telemetry/events/build'
type Config = {
plugins: string[]
rules: { [key: string]: Array<number | string> }
}
async function lint(
deps: NecessaryDependencies,
baseDir: string,
lintDirs: string[],
eslintrcFile: string | null,
pkgJsonPath: string | null,
eslintOptions: any = 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.get('eslint')!)
const { ESLint } = mod
let eslintVersion = ESLint?.version
if (!ESLint) {
eslintVersion = mod?.CLIEngine?.version
if (!eslintVersion || semver.lt(eslintVersion, '7.0.0')) {
return `${chalk.red(
'error'
)} - Your project has an older version of ESLint installed${
eslintVersion ? ' (' + eslintVersion + ')' : ''
}. Please upgrade to ESLint version 7 or later`
}
return `${chalk.red(
'error'
)} - ESLint class not found. Please upgrade to ESLint version 7 or later`
}
let options: any = {
useEslintrc: true,
baseConfig: {},
extensions: ['.js', '.jsx', '.ts', '.tsx'],
...eslintOptions,
}
let eslint = new ESLint(options)
let nextEslintPluginIsEnabled = false
const pagesDirRules = ['@next/next/no-html-link-for-pages']
for (const configFile of [eslintrcFile, pkgJsonPath]) {
if (!configFile) continue
const completeConfig: Config = await eslint.calculateConfigForFile(
configFile
)
if (completeConfig.plugins?.includes('@next/next')) {
nextEslintPluginIsEnabled = true
break
}
}
const pagesDir = findPagesDir(baseDir)
if (nextEslintPluginIsEnabled) {
let updatedPagesDir = false
for (const rule of pagesDirRules) {
if (
!options.baseConfig!.rules?.[rule] &&
!options.baseConfig!.rules?.[
rule.replace('@next/next', '@next/babel-plugin-next')
]
) {
if (!options.baseConfig!.rules) {
options.baseConfig!.rules = {}
}
options.baseConfig!.rules[rule] = [1, pagesDir]
updatedPagesDir = true
}
}
if (updatedPagesDir) {
eslint = new ESLint(options)
}
}
const lintStart = process.hrtime()
const results = await eslint.lintFiles(lintDirs)
if (options.fix) await ESLint.outputFixes(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,
},
}
}
export async function runLintCheck(
baseDir: string,
lintDirs: string[],
lintDuringBuild: boolean = false,
eslintOptions: any = null
): ReturnType<typeof lint> {
try {
// Find user's .eslintrc file
const eslintrcFile =
(await findUp(
[
'.eslintrc.js',
'.eslintrc.yaml',
'.eslintrc.yml',
'.eslintrc.json',
'.eslintrc',
],
{
cwd: baseDir,
}
)) ?? null
const pkgJsonPath = (await findUp('package.json', { cwd: baseDir })) ?? null
let packageJsonConfig = null
if (pkgJsonPath) {
const pkgJsonContent = await fs.readFile(pkgJsonPath, {
encoding: 'utf8',
})
packageJsonConfig = CommentJson.parse(pkgJsonContent)
}
// Warning displayed if no ESLint configuration is present during build
if (lintDuringBuild && !eslintrcFile && !packageJsonConfig.eslintConfig) {
Log.warn(
`No ESLint configuration detected. Run ${chalk.bold.cyan(
'next lint'
)} to begin setup`
)
return null
}
// Ensure ESLint and necessary plugins and configs are installed:
const deps: NecessaryDependencies = await hasNecessaryDependencies(
baseDir,
false,
true,
lintDuringBuild
)
// Write default ESLint config if none is present
// Check for /pages and src/pages is to make sure this happens in Next.js folder
if (
existsSync(path.join(baseDir, 'pages')) ||
existsSync(path.join(baseDir, 'src/pages'))
) {
await writeDefaultConfig(eslintrcFile, pkgJsonPath, packageJsonConfig)
}
// Run ESLint
return await lint(
deps,
baseDir,
lintDirs,
eslintrcFile,
pkgJsonPath,
eslintOptions
)
} catch (err) {
throw err
}
}