Skip to content
This repository has been archived by the owner on Jun 21, 2023. It is now read-only.

Commit

Permalink
ESLint: More updates and bug fixes (vercel#25952)
Browse files Browse the repository at this point in the history
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
housseindjirdeh and kodiakhq[bot] committed Jun 10, 2021
1 parent fe4b8aa commit 89caf1b
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 28 deletions.
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"
}

0 comments on commit 89caf1b

Please sign in to comment.