2021-06-03 14:01:24 +02:00
|
|
|
import { promises as fs } from 'fs'
|
|
|
|
import chalk from 'chalk'
|
2021-04-30 13:09:07 +02:00
|
|
|
|
|
|
|
import findUp from 'next/dist/compiled/find-up'
|
|
|
|
import semver from 'next/dist/compiled/semver'
|
2021-06-03 14:01:24 +02:00
|
|
|
import * as CommentJson from 'next/dist/compiled/comment-json'
|
2021-04-30 13:09:07 +02:00
|
|
|
|
|
|
|
import { formatResults } from './customFormatter'
|
|
|
|
import { writeDefaultConfig } from './writeDefaultConfig'
|
2021-06-03 14:01:24 +02:00
|
|
|
import { findPagesDir } from '../find-pages-dir'
|
2021-04-30 13:09:07 +02:00
|
|
|
|
|
|
|
import { CompileError } from '../compile-error'
|
|
|
|
import {
|
|
|
|
hasNecessaryDependencies,
|
|
|
|
NecessaryDependencies,
|
|
|
|
} from '../has-necessary-dependencies'
|
|
|
|
|
|
|
|
import * as Log from '../../build/output/log'
|
|
|
|
|
|
|
|
type Config = {
|
|
|
|
plugins: string[]
|
|
|
|
rules: { [key: string]: Array<number | string> }
|
|
|
|
}
|
|
|
|
|
2021-06-03 14:01:24 +02:00
|
|
|
const linteableFiles = (dir: string) => {
|
|
|
|
return `${dir}/**/*.{${['jsx', 'js', 'ts', 'tsx'].join(',')}}`
|
|
|
|
}
|
2021-04-30 13:09:07 +02:00
|
|
|
|
|
|
|
async function lint(
|
|
|
|
deps: NecessaryDependencies,
|
|
|
|
baseDir: string,
|
2021-06-03 14:01:24 +02:00
|
|
|
lintDirs: string[] | null,
|
2021-04-30 13:09:07 +02:00
|
|
|
eslintrcFile: string | null,
|
|
|
|
pkgJsonPath: string | null
|
|
|
|
): Promise<string | null> {
|
|
|
|
// Load ESLint after we're sure it exists:
|
2021-06-08 23:29:34 +02:00
|
|
|
const mod = await import(deps.resolved)
|
|
|
|
|
|
|
|
const { ESLint } = mod
|
2021-04-30 13:09:07 +02:00
|
|
|
|
|
|
|
if (!ESLint) {
|
2021-06-08 23:29:34 +02:00
|
|
|
const eslintVersion: string | undefined = mod?.CLIEngine?.version
|
2021-04-30 13:09:07 +02:00
|
|
|
|
2021-06-08 23:29:34 +02:00
|
|
|
if (!eslintVersion || semver.lt(eslintVersion, '7.0.0')) {
|
2021-06-03 14:01:24 +02:00
|
|
|
Log.error(
|
2021-06-08 23:29:34 +02:00
|
|
|
`Your project has an older version of ESLint installed${
|
|
|
|
eslintVersion ? ' (' + eslintVersion + ')' : ''
|
|
|
|
}. Please upgrade to ESLint version 7 or later`
|
2021-04-30 13:09:07 +02:00
|
|
|
)
|
2021-06-08 23:29:34 +02:00
|
|
|
return null
|
2021-04-30 13:09:07 +02:00
|
|
|
}
|
2021-06-08 23:29:34 +02:00
|
|
|
|
|
|
|
Log.error(
|
|
|
|
`ESLint class not found. Please upgrade to ESLint version 7 or later`
|
|
|
|
)
|
2021-04-30 13:09:07 +02:00
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
let options: any = {
|
|
|
|
useEslintrc: true,
|
|
|
|
baseConfig: {},
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-03 14:01:24 +02:00
|
|
|
const pagesDir = findPagesDir(baseDir)
|
|
|
|
|
2021-04-30 13:09:07 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-03 14:01:24 +02:00
|
|
|
// If no directories to lint are provided, only the pages directory will be linted
|
|
|
|
const filesToLint = lintDirs
|
|
|
|
? lintDirs.map(linteableFiles)
|
|
|
|
: linteableFiles(pagesDir)
|
|
|
|
|
|
|
|
const results = await eslint.lintFiles(filesToLint)
|
2021-04-30 13:09:07 +02:00
|
|
|
|
|
|
|
if (ESLint.getErrorResults(results)?.length > 0) {
|
|
|
|
throw new CompileError(await formatResults(baseDir, results))
|
|
|
|
}
|
|
|
|
return results?.length > 0 ? formatResults(baseDir, results) : null
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function runLintCheck(
|
|
|
|
baseDir: string,
|
2021-06-03 14:01:24 +02:00
|
|
|
lintDirs: string[] | null,
|
|
|
|
lintDuringBuild: boolean = false
|
2021-04-30 13:09:07 +02:00
|
|
|
): Promise<string | null> {
|
|
|
|
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
|
2021-06-03 14:01:24 +02:00
|
|
|
let packageJsonConfig = null
|
|
|
|
if (pkgJsonPath) {
|
|
|
|
const pkgJsonContent = await fs.readFile(pkgJsonPath, {
|
|
|
|
encoding: 'utf8',
|
|
|
|
})
|
|
|
|
packageJsonConfig = CommentJson.parse(pkgJsonContent)
|
|
|
|
}
|
2021-04-30 13:09:07 +02:00
|
|
|
|
2021-06-03 14:01:24 +02:00
|
|
|
// 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`
|
|
|
|
)
|
2021-04-30 13:09:07 +02:00
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure ESLint and necessary plugins and configs are installed:
|
|
|
|
const deps: NecessaryDependencies = await hasNecessaryDependencies(
|
|
|
|
baseDir,
|
|
|
|
false,
|
2021-06-03 14:01:24 +02:00
|
|
|
true,
|
|
|
|
eslintrcFile ?? '',
|
|
|
|
!!packageJsonConfig.eslintConfig,
|
|
|
|
lintDuringBuild
|
2021-04-30 13:09:07 +02:00
|
|
|
)
|
|
|
|
|
2021-06-03 14:01:24 +02:00
|
|
|
// Write default ESLint config if none is present
|
|
|
|
await writeDefaultConfig(eslintrcFile, pkgJsonPath, packageJsonConfig)
|
2021-04-30 13:09:07 +02:00
|
|
|
|
|
|
|
// Run ESLint
|
2021-06-03 14:01:24 +02:00
|
|
|
return await lint(deps, baseDir, lintDirs, eslintrcFile, pkgJsonPath)
|
2021-04-30 13:09:07 +02:00
|
|
|
} catch (err) {
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|