Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESLint: More updates and bug fixes #25952

Merged
merged 2 commits into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 76 additions & 7 deletions packages/next/cli/next-lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,65 @@ import { resolve, join } from 'path'
import chalk from 'chalk'

import { cliCommand } from '../bin/next'
import { ESLINT_DEFAULT_DIRS } from '../lib/constants'
import { runLintCheck } from '../lib/eslint/runLintCheck'
import { printAndExit } from '../server/lib/utils'

const eslintOptions = (args: arg.Spec) => ({
overrideConfigFile: args['--config'] || null,
extensions: args['--ext'] ?? ['.js', '.jsx', '.ts', '.tsx'],
resolvePluginsRelativeTo: args['--resolve-plugins-relative-to'] || null,
rulePaths: args['--rulesdir'] ?? [],
fix: args['--fix'] ?? false,
fixTypes: args['--fix-type'] ?? null,
ignorePath: args['--ignore-path'] || null,
ignore: !Boolean(args['--no-ignore']),
allowInlineConfig: !Boolean(args['--no-inline-config']),
reportUnusedDisableDirectives:
args['--report-unused-disable-directives'] || null,
cache: args['--cache'] ?? false,
cacheLocation: args['--cache-location'] || '.eslintcache',
cacheStrategy: args['--cache-strategy'] || 'metadata',
errorOnUnmatchedPattern: !Boolean(args['--no-error-on-unmatched-pattern']),
})

const nextLint: cliCommand = (argv) => {
const validArgs: arg.Spec = {
// Types
'--help': Boolean,
'--base-dir': String,
'--dir': [String],

// Aliases
'-h': '--help',
'-b': '--base-dir',
'-d': '--dir',
}

const validEslintArgs: arg.Spec = {
// Types
'--config': String,
'--ext': [String],
'--resolve-plugins-relative-to': String,
'--rulesdir': [String],
'--fix': Boolean,
'--fix-type': [String],
'--ignore-path': String,
'--no-ignore': Boolean,
'--no-inline-config': Boolean,
'--report-unused-disable-directives': String,
'--cache': Boolean,
'--cache-location': String,
'--cache-strategy': String,
'--no-error-on-unmatched-pattern': Boolean,

// Aliases
'-c': '--config',
}

let args: arg.Result<arg.Spec>
try {
args = arg(validArgs, { argv })
args = arg({ ...validArgs, ...validEslintArgs }, { argv })
} catch (error) {
if (error.code === 'ARG_UNKNOWN_OPTION') {
return printAndExit(error.message, 1)
Expand All @@ -37,14 +79,41 @@ const nextLint: cliCommand = (argv) => {

Usage
$ next lint <baseDir> [options]

<baseDir> represents the directory of the Next.js application.
If no directory is provided, the current directory will be used.

Options
-h - list this help
-d - set directory, or directories, to run ESLint (defaults to only 'pages')
`,
Basic configuration:
-h, --help List this help
-d, --dir Array Set directory, or directories, to run ESLint - default: 'pages', 'components', and 'lib'
-c, --config path::String Use this configuration file, overriding all other config options
--ext [String] Specify JavaScript file extensions - default: .js, .jsx, .ts, .tsx
--resolve-plugins-relative-to path::String A folder where plugins should be resolved from, CWD by default

Specifying rules:
--rulesdir [path::String] Use additional rules from this directory

Fixing problems:
--fix Automatically fix problems
--fix-type Array Specify the types of fixes to apply (problem, suggestion, layout)

Ignoring files:
--ignore-path path::String Specify path of ignore file
--no-ignore Disable use of ignore files and patterns

Inline configuration comments:
--no-inline-config Prevent comments from changing config or rules
--report-unused-disable-directives Adds reported errors for unused eslint-disable directives ("error" | "warn" | "off")

Caching:
--cache Only check changed files - default: false
--cache-location path::String Path to the cache file or directory - default: .eslintcache
--cache-strategy String Strategy to use for detecting changed files - either: metadata or content - default: metadata

Miscellaneous:
--no-error-on-unmatched-pattern Prevent errors when pattern is unmatched - default: false
`,
0
)
}
Expand All @@ -57,7 +126,7 @@ const nextLint: cliCommand = (argv) => {
}

const dirs: string[] = args['--dir']
const lintDirs = (dirs ?? ['pages', 'components', 'lib']).reduce(
const lintDirs = (dirs ?? ESLINT_DEFAULT_DIRS).reduce(
(res: string[], d: string) => {
const currDir = join(baseDir, d)
if (!existsSync(currDir)) return res
Expand All @@ -67,7 +136,7 @@ const nextLint: cliCommand = (argv) => {
[]
)

runLintCheck(baseDir, lintDirs)
runLintCheck(baseDir, lintDirs, false, eslintOptions(args))
.then((results) => {
if (results) {
console.log(results)
Expand Down
9 changes: 9 additions & 0 deletions packages/next/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,12 @@ export const GSSP_COMPONENT_MEMBER_ERROR = `can not be attached to a page's comp
export const NON_STANDARD_NODE_ENV = `You are using a non-standard "NODE_ENV" value in your environment. This creates inconsistencies in the project and is strongly advised against. Read more: https://nextjs.org/docs/messages/non-standard-node-env`

export const SSG_FALLBACK_EXPORT_ERROR = `Pages with \`fallback\` enabled in \`getStaticPaths\` can not be exported. See more info here: https://nextjs.org/docs/messages/ssg-fallback-true-export`

export const ESLINT_DEFAULT_DIRS = [
'pages',
'components',
'lib',
'src/pages',
'src/components',
'src/lib',
]
54 changes: 34 additions & 20 deletions packages/next/lib/eslint/runLintCheck.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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 { findPagesDir } from '../find-pages-dir'
import { existsSync, findPagesDir } from '../find-pages-dir'
import { CompileError } from '../compile-error'
import {
hasNecessaryDependencies,
Expand All @@ -21,16 +22,13 @@ type Config = {
rules: { [key: string]: Array<number | string> }
}

const linteableFiles = (dir: string) => {
return `${dir}/**/*.{${['jsx', 'js', 'ts', 'tsx'].join(',')}}`
}

async function lint(
deps: NecessaryDependencies,
baseDir: string,
lintDirs: string[],
eslintrcFile: string | null,
pkgJsonPath: string | null
pkgJsonPath: string | null,
eslintOptions: any = null
): Promise<string | null> {
// Load ESLint after we're sure it exists:
const mod = await import(deps.resolved)
Expand All @@ -41,23 +39,23 @@ async function lint(
const eslintVersion: string | undefined = mod?.CLIEngine?.version

if (!eslintVersion || semver.lt(eslintVersion, '7.0.0')) {
Log.error(
`Your project has an older version of ESLint installed${
eslintVersion ? ' (' + eslintVersion + ')' : ''
}. Please upgrade to ESLint version 7 or later`
)
return null
return `${chalk.red(
'error'
)} - Your project has an older version of ESLint installed${
eslintVersion ? ' (' + eslintVersion + ')' : ''
}. Please upgrade to ESLint version 7 or later`
}

Log.error(
`ESLint class not found. Please upgrade to ESLint version 7 or later`
)
return null
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)

Expand Down Expand Up @@ -101,18 +99,21 @@ async function lint(
eslint = new ESLint(options)
}
}
const results = await eslint.lintFiles(lintDirs)
if (options.fix) await ESLint.outputFixes(results)

const results = await eslint.lintFiles(lintDirs.map(linteableFiles))
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,
lintDirs: string[],
lintDuringBuild: boolean = false
lintDuringBuild: boolean = false,
eslintOptions: any = null
): Promise<string | null> {
try {
// Find user's .eslintrc file
Expand Down Expand Up @@ -158,10 +159,23 @@ export async function runLintCheck(
)

// Write default ESLint config if none is present
await writeDefaultConfig(eslintrcFile, pkgJsonPath, packageJsonConfig)
// 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)
return await lint(
deps,
baseDir,
lintDirs,
eslintrcFile,
pkgJsonPath,
eslintOptions
)
} catch (err) {
throw err
}
Expand Down
3 changes: 2 additions & 1 deletion packages/next/lib/verifyAndLint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import chalk from 'chalk'
import { Worker } from 'jest-worker'
import { existsSync } from 'fs'
import { join } from 'path'
import { ESLINT_DEFAULT_DIRS } from './constants'

export async function verifyAndLint(
dir: string,
Expand All @@ -20,7 +21,7 @@ export async function verifyAndLint(
lintWorkers.getStdout().pipe(process.stdout)
lintWorkers.getStderr().pipe(process.stderr)

const lintDirs = (configLintDirs ?? ['pages', 'components', 'lib']).reduce(
const lintDirs = (configLintDirs ?? ESLINT_DEFAULT_DIRS).reduce(
(res: string[], d: string) => {
const currDir = join(dir, d)
if (!existsSync(currDir)) return res
Expand Down
1 change: 1 addition & 0 deletions test/integration/eslint/first-time-setup/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"root": true,
"extends": "next"
}