From e5ef60fecb142c149e0307376a1996f941b2840d Mon Sep 17 00:00:00 2001 From: Houssein Djirdeh Date: Tue, 23 Mar 2021 17:32:42 -0400 Subject: [PATCH] Add ESLint to Next.js (#22437) For #22228 This PR: - Adds ESLint to toolchain - Included by default for builds (`next build`) - Can be enabled for development (`next dev`) - Custom formatter built for output - Adds appropriate tests - Adds two documentation pages --- .eslintignore | 2 + .../next.config.js/eslint-warnings-errors.md | 70 ++++++ docs/basic-features/eslint.md | 50 +++++ docs/manifest.json | 8 + .../lib/rules/no-html-link-for-pages.js | 22 +- packages/eslint-plugin-next/lib/utils/url.js | 14 ++ packages/next/build/index.ts | 35 +++ packages/next/lib/eslint/customFormatter.ts | 68 ++++++ packages/next/lib/verifyAndLint.ts | 141 ++++++++++++ .../next/next-server/server/config-shared.ts | 4 + packages/next/package.json | 6 +- packages/next/server/hot-reloader.ts | 1 + .../next/server/on-demand-entry-handler.ts | 12 + test-pnp.sh | 3 + test/integration/dist-dir/test/index.test.js | 11 +- .../custom-eslint-config/.eslintrc.json | 16 ++ .../custom-eslint-config/pages/index.js | 13 ++ .../eslint/custom-next-config/.eslintrc.json | 14 ++ .../eslint/custom-next-config/next.config.js | 6 + .../eslint/custom-next-config/pages/index.js | 13 ++ .../pkg-json-eslint-config/package.json | 36 +++ .../pkg-json-eslint-config/pages/index.js | 13 ++ .../eslint/test/custom-eslint-config.test.js | 37 ++++ .../eslint/test/custom-next-config.test.js | 55 +++++ .../test/pkg-json-eslint-config.test.js | 44 ++++ .../index-index/pages/index/index.js | 2 +- .../index-index/pages/index/index/index.js | 2 +- .../index-index/pages/index/project/index.js | 2 +- .../index-index/pages/index/user.js | 2 +- .../production-build-dir/next.config.js | 5 + .../production/pages/error-in-ssr-render.js | 3 +- .../tsconfig-verifier/pages/index.tsx | 4 +- .../webpack-require-hook/next.config.js | 3 + yarn.lock | 208 +++++++++++++++++- 34 files changed, 900 insertions(+), 25 deletions(-) create mode 100644 docs/api-reference/next.config.js/eslint-warnings-errors.md create mode 100644 docs/basic-features/eslint.md create mode 100644 packages/next/lib/eslint/customFormatter.ts create mode 100644 packages/next/lib/verifyAndLint.ts create mode 100644 test/integration/eslint/custom-eslint-config/.eslintrc.json create mode 100644 test/integration/eslint/custom-eslint-config/pages/index.js create mode 100644 test/integration/eslint/custom-next-config/.eslintrc.json create mode 100644 test/integration/eslint/custom-next-config/next.config.js create mode 100644 test/integration/eslint/custom-next-config/pages/index.js create mode 100644 test/integration/eslint/pkg-json-eslint-config/package.json create mode 100644 test/integration/eslint/pkg-json-eslint-config/pages/index.js create mode 100644 test/integration/eslint/test/custom-eslint-config.test.js create mode 100644 test/integration/eslint/test/custom-next-config.test.js create mode 100644 test/integration/eslint/test/pkg-json-eslint-config.test.js create mode 100644 test/integration/production-build-dir/next.config.js diff --git a/.eslintignore b/.eslintignore index 37e0a229afb6..fa46bc2d129c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,6 +2,7 @@ node_modules **/.next/** **/_next/** **/dist/** +e2e-tests/** examples/with-typescript-eslint-jest/** examples/with-kea/** packages/next/bundles/webpack/packages/*.runtime.js @@ -16,4 +17,5 @@ packages/next-codemod/**/*.js packages/next-codemod/**/*.d.ts packages/next-env/**/*.d.ts test/integration/async-modules/** +test/integration/eslint/** test-timings.json \ No newline at end of file diff --git a/docs/api-reference/next.config.js/eslint-warnings-errors.md b/docs/api-reference/next.config.js/eslint-warnings-errors.md new file mode 100644 index 000000000000..d13372413216 --- /dev/null +++ b/docs/api-reference/next.config.js/eslint-warnings-errors.md @@ -0,0 +1,70 @@ +--- +description: Learn how to opt-in and out of ESLint during development mode and production builds. +--- + +# ESLint Warnings and Errors + +## During builds + +Next.js fails your **production build** (`next build`) when ESLint errors are present in your +project. + +If you'd like Next.js to dangerously produce production code even when your application has errors, +you can disable ESLint running during the build process. + +> It's recommended to run ESLint as part of the production build process to ensure your application +> is resilient against runtime issues. + +Open `next.config.js` and disable the `build` option in the `eslint` config: + +```js +module.exports = { + eslint: { + // !! WARN !! + // Dangerously allow production builds to successfully complete even if + // your project has ESLint errors. + // !! WARN !! + build: false, + }, +} +``` + +## During development + +By default, Next.js does not run ESLint during **development** (`next dev`). + +If you would like Next.js to lint files separately in development mode, you can enable it in your +configuration. + +> Enabling ESLint during development mode will slow down how fast pages are compiled. Until this is +> optimized, we recommend that you [integrate ESLint in your code +> editor](https://eslint.org/docs/user-guide/integrations#editors). + +Open `next.config.js` and enable the `dev` option in the `eslint` config: + +```js +module.exports = { + eslint: { + // !! WARN !! + // This can slow down how long pages take to compile during development + // !! WARN !! + dev: true, + }, +} +``` + +## Related + +
+ + Introduction to next.config.js: + Learn more about the configuration file used by Next.js. + +
+ +
+ + ESLint: + Learn more about how to use ESLint in Next.js. + +
diff --git a/docs/basic-features/eslint.md b/docs/basic-features/eslint.md new file mode 100644 index 000000000000..019cc1d7c3e2 --- /dev/null +++ b/docs/basic-features/eslint.md @@ -0,0 +1,50 @@ +--- +description: Next.js uses ESLint to find and resolve issues affecting the user or developer +experience. +--- + +# ESLint + +Next.js uses [ESLint](https://eslint.org/) to find and resolve issues affecting the user or +developer experience. A minimal set of Next.js rules are provided by default, but can be extended by +adding an `.eslintrc` file to your project. + +## Default configuration + +By default, Next.js provides a set of [recommended ESLint +rules](https://github.com/vercel/next.js/blob/canary/packages/eslint-plugin-next/lib/index.js#L10-L18) +that are automatically linted against as part of `next build`. If you would like to control which +ESLint rules are checked during builds, you will need to add an `.eslintrc` file to the root of your +project. + +Here's an example of an `.eslintrc.json` file: + +```json +{ + "extends": ["plugin:@next/next/recommended"], + "parser": "@babel/eslint-parser", + "parserOptions": { + "requireConfigFile": false, + "sourceType": "module", + "babelOptions": { + "presets": ["next/babel"] + } + } +} +``` + +- Extending the original base of rules (`plugin:@next/next/recommended`) is highly recommended to + catch and fix significant Next.js issues in your application +- Including `@babel/eslint-parser` with the `next/babel` preset ensures that all language features + supported by Next.js will also be supported by ESLint. Although `@babel/eslint-parser` can parse + TypeScript, consider using + [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser) + if you have TypeScript enabled in your application to check for type-specific linting rules. + +> If you add an `.eslintrc` file to your application and don't include +> `plugin:@next/next/recommended`in the config, its rules will not be checked during development or +> production builds. This is **not recommended**. + +If you want to enable ESLint to run during development, or disable it for production builds; refer +to the documentation for [ESLint Warnings and +Errors](/docs/api-reference/next.config.js/eslint-warnings-errors.md). diff --git a/docs/manifest.json b/docs/manifest.json index a9dcd38126b8..cd9010eb8bfc 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -33,6 +33,10 @@ "title": "Fast Refresh", "path": "/docs/basic-features/fast-refresh.md" }, + { + "title": "ESLint", + "path": "/docs/basic-features/eslint.md" + }, { "title": "TypeScript", "path": "/docs/basic-features/typescript.md" @@ -336,6 +340,10 @@ "title": "Configuring onDemandEntries", "path": "/docs/api-reference/next.config.js/configuring-onDemandEntries.md" }, + { + "title": "ESLint Warnings and Errors", + "path": "/docs/api-reference/next.config.js/eslint-warnings-errors.md" + }, { "title": "Ignoring TypeScript Errors", "path": "/docs/api-reference/next.config.js/ignoring-typescript-errors.md" diff --git a/packages/eslint-plugin-next/lib/rules/no-html-link-for-pages.js b/packages/eslint-plugin-next/lib/rules/no-html-link-for-pages.js index 947850a6f5af..b816a418b860 100644 --- a/packages/eslint-plugin-next/lib/rules/no-html-link-for-pages.js +++ b/packages/eslint-plugin-next/lib/rules/no-html-link-for-pages.js @@ -1,10 +1,18 @@ const path = require('path') const fs = require('fs') -const { getUrlFromPagesDirectory, normalizeURL } = require('../utils/url') +const { + getUrlFromPagesDirectory, + normalizeURL, + execOnce, +} = require('../utils/url') + +const pagesDirWarning = execOnce((pagesDirs) => { + console.warn( + `Pages directory cannot be found at ${pagesDirs.join(' or ')}. ` + + `If using a custom path, please configure with the no-html-link-for-pages rule in your eslint config file` + ) +}) -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ module.exports = { meta: { docs: { @@ -26,10 +34,8 @@ module.exports = { ] const pagesDir = pagesDirs.find((dir) => fs.existsSync(dir)) if (!pagesDir) { - throw new Error( - `Pages directory cannot be found at ${pagesDirs.join(' or ')}. ` + - `If using a custom path, please configure with the no-html-link-for-pages rule` - ) + pagesDirWarning(pagesDirs) + return {} } const urls = getUrlFromPagesDirectory('/', pagesDir) diff --git a/packages/eslint-plugin-next/lib/utils/url.js b/packages/eslint-plugin-next/lib/utils/url.js index 4b3999b10f45..ae1163b47ff1 100644 --- a/packages/eslint-plugin-next/lib/utils/url.js +++ b/packages/eslint-plugin-next/lib/utils/url.js @@ -76,7 +76,21 @@ function normalizeURL(url) { return url } +function execOnce(fn) { + let used = false + let result + + return (...args) => { + if (!used) { + used = true + result = fn(...args) + } + return result + } +} + module.exports = { getUrlFromPagesDirectory, normalizeURL, + execOnce, } diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 4faaaad434e9..e79555487d23 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -175,6 +175,41 @@ export default async function build( telemetry.record(events) ) + if (config.eslint?.build) { + await nextBuildSpan + .traceChild('verify-and-lint') + .traceAsyncFn(async () => { + const lintWorkers = new Worker( + require.resolve('../lib/verifyAndLint'), + { + numWorkers: config.experimental.cpus, + enableWorkerThreads: config.experimental.workerThreads, + } + ) as Worker & { + verifyAndLint: typeof import('../lib/verifyAndLint').verifyAndLint + } + + lintWorkers.getStdout().pipe(process.stdout) + lintWorkers.getStderr().pipe(process.stderr) + + const lintResults = await lintWorkers.verifyAndLint( + dir, + pagesDir, + null + ) + + if (lintResults.hasErrors) { + console.error(chalk.red('Failed to compile.')) + console.error(lintResults.results) + process.exit(1) + } else if (lintResults.hasMessages) { + console.log(lintResults.results) + } + + lintWorkers.end() + }) + } + const ignoreTypeScriptErrors = Boolean(config.typescript?.ignoreBuildErrors) await nextBuildSpan .traceChild('verify-typescript-setup') diff --git a/packages/next/lib/eslint/customFormatter.ts b/packages/next/lib/eslint/customFormatter.ts new file mode 100644 index 000000000000..6fc55ef09b04 --- /dev/null +++ b/packages/next/lib/eslint/customFormatter.ts @@ -0,0 +1,68 @@ +import { ESLint, Linter } from 'eslint' + +import chalk from 'chalk' +import path from 'path' + +export enum MessageSeverity { + Warning = 1, + Error = 2, +} + +function formatMessage( + dir: string, + messages: Linter.LintMessage[], + filePath: string +): string | void { + 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: ESLint.LintResult[] +): string { + return ( + results + .filter(({ messages }) => messages?.length) + .map(({ messages, filePath }) => + formatMessage(baseDir, messages, filePath) + ) + .join('\n') + '\n' + ) +} diff --git a/packages/next/lib/verifyAndLint.ts b/packages/next/lib/verifyAndLint.ts new file mode 100644 index 000000000000..997b77b94b10 --- /dev/null +++ b/packages/next/lib/verifyAndLint.ts @@ -0,0 +1,141 @@ +import { ESLint } from 'eslint' +import { join } from 'path' + +import { formatResults } from './eslint/customFormatter' +import { fileExists } from './file-exists' +import * as log from '../build/output/log' + +import findUp from 'next/dist/compiled/find-up' + +type Config = { + plugins: string[] + rules: { [key: string]: Array } +} + +export async function verifyAndLint( + baseDir: string, + pagesDir: string, + pagePath: string | null +): Promise<{ + results: string + hasErrors: boolean + hasMessages: boolean +}> { + let options: ESLint.Options + + let pathNotExists = Boolean( + pagePath && !(await fileExists(join(pagesDir, pagePath))) + ) + + if (pathNotExists) + return { results: '', hasErrors: false, hasMessages: false } + + const eslintrcFile = await findUp( + [ + '.eslintrc.js', + '.eslintrc.yaml', + '.eslintrc.yml', + '.eslintrc.json', + '.eslintrc', + ], + { + cwd: baseDir, + } + ) + + const pagesDirRules = ['@next/next/no-html-link-for-pages'] + const pkgJsonPath = await findUp('package.json', { cwd: baseDir }) + const { eslintConfig = null } = !!pkgJsonPath + ? await import(pkgJsonPath!) + : {} + let pluginIsEnabled = false + + if (eslintrcFile) { + options = { + useEslintrc: true, + baseConfig: {}, + } + } else { + if (!eslintConfig) { + console.log() + log.info( + 'No ESLint configuration was detected, but checks from the Next.js ESLint plugin were included automatically (see https://nextjs.org/docs/basic-features/eslint).' + ) + pluginIsEnabled = true + } + + options = { + baseConfig: eslintConfig ?? { + extends: ['plugin:@next/next/recommended'], + parser: require.resolve('@babel/eslint-parser'), + parserOptions: { + requireConfigFile: false, + sourceType: 'module', + babelOptions: { + presets: ['next/babel'], + }, + }, + }, + useEslintrc: false, + } + } + + let eslint = new ESLint(options) + + // check both eslintrc and package.json config since + // eslint will load config from both + for (const configFile of [eslintrcFile, pkgJsonPath]) { + if (!configFile) continue + + const completeConfig: Config = await eslint.calculateConfigForFile( + configFile + ) + + if (completeConfig.plugins?.includes('@next/next')) { + pluginIsEnabled = true + break + } + } + + if (pluginIsEnabled) { + 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) + } + } else { + console.log() + log.warn( + `The Next.js ESLint plugin was not detected in ${ + eslintrcFile || pkgJsonPath + }. We recommend including it to prevent significant issues in your application (see https://nextjs.org/docs/basic-features/eslint).` + ) + } + + const results = await eslint.lintFiles([ + pagePath ? join(pagesDir, pagePath) : `${pagesDir}/**/*.{js,tsx}`, + ]) + + const errors = ESLint.getErrorResults(results) + + return { + results: formatResults(baseDir, results), + hasErrors: errors?.length > 0 && !pagePath, + hasMessages: results?.length > 0, + } +} diff --git a/packages/next/next-server/server/config-shared.ts b/packages/next/next-server/server/config-shared.ts index a677d5db3a25..196973e5709b 100644 --- a/packages/next/next-server/server/config-shared.ts +++ b/packages/next/next-server/server/config-shared.ts @@ -89,6 +89,10 @@ export const defaultConfig: NextConfig = { serverRuntimeConfig: {}, publicRuntimeConfig: {}, reactStrictMode: false, + eslint: { + dev: false, + build: true, + }, } export function normalizeConfig(phase: string, config: any) { diff --git a/packages/next/package.json b/packages/next/package.json index c248c52fb4d8..e2fe18a11b8c 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -60,9 +60,12 @@ ] }, "dependencies": { + "@babel/core": "7.12.10", "@babel/runtime": "7.12.5", + "@babel/eslint-parser": "7.13.4", "@hapi/accept": "5.0.1", "@next/env": "10.0.10-canary.7", + "@next/eslint-plugin-next": "10.0.10-canary.7", "@next/polyfill-module": "10.0.10-canary.7", "@next/react-dev-overlay": "10.0.10-canary.7", "@next/react-refresh-utils": "10.0.10-canary.7", @@ -79,6 +82,7 @@ "crypto-browserify": "3.12.0", "cssnano-simple": "1.2.2", "domain-browser": "4.19.0", + "eslint": "7.9.0", "etag": "1.8.1", "find-cache-dir": "3.3.1", "get-orientation": "1.1.2", @@ -130,7 +134,6 @@ "devDependencies": { "@ampproject/toolbox-optimizer": "2.7.1-alpha.0", "@babel/code-frame": "7.12.11", - "@babel/core": "7.12.10", "@babel/plugin-proposal-class-properties": "7.12.1", "@babel/plugin-proposal-export-namespace-from": "7.12.1", "@babel/plugin-proposal-numeric-separator": "7.12.7", @@ -161,6 +164,7 @@ "@types/cookie": "0.3.3", "@types/cross-spawn": "6.0.0", "@types/debug": "4.1.5", + "@types/eslint": "7.2.5", "@types/etag": "1.8.0", "@types/fresh": "0.5.0", "@types/json5": "0.0.30", diff --git a/packages/next/server/hot-reloader.ts b/packages/next/server/hot-reloader.ts index 15e33caa2a6c..1470bde405eb 100644 --- a/packages/next/server/hot-reloader.ts +++ b/packages/next/server/hot-reloader.ts @@ -512,6 +512,7 @@ export default class HotReloader { this.onDemandEntries = onDemandEntryHandler(this.watcher, multiCompiler, { pagesDir: this.pagesDir, pageExtensions: this.config.pageExtensions, + eslint: this.config.eslint?.dev, ...(this.config.onDemandEntries as { maxInactiveAge: number pagesBufferLength: number diff --git a/packages/next/server/on-demand-entry-handler.ts b/packages/next/server/on-demand-entry-handler.ts index d6bef0477fe5..bd918494f257 100644 --- a/packages/next/server/on-demand-entry-handler.ts +++ b/packages/next/server/on-demand-entry-handler.ts @@ -4,6 +4,7 @@ import { join, posix } from 'path' import { parse } from 'url' import { webpack } from 'next/dist/compiled/webpack/webpack' import * as Log from '../build/output/log' +import { verifyAndLint } from '../lib/verifyAndLint' import { normalizePagePath, normalizePathSep, @@ -32,11 +33,13 @@ export default function onDemandEntryHandler( { pagesDir, pageExtensions, + eslint, maxInactiveAge, pagesBufferLength, }: { pagesDir: string pageExtensions: string[] + eslint: boolean maxInactiveAge: number pagesBufferLength: number } @@ -192,6 +195,15 @@ export default function onDemandEntryHandler( } } + // TODO: Move out of hot-reloader into a separate process + if (eslint) { + verifyAndLint(process.cwd(), pagesDir, pagePath).then( + ({ results, hasMessages }) => { + if (hasMessages) console.log(results) + } + ) + } + Log.event(`build page: ${normalizedPage}`) entries[normalizedPage] = { diff --git a/test-pnp.sh b/test-pnp.sh index 8b5fd93beb57..1bcc4a4016c7 100755 --- a/test-pnp.sh +++ b/test-pnp.sh @@ -29,6 +29,9 @@ do cp -r "./examples/$testCase/." "./e2e-tests/$testCase" cd "./e2e-tests/$testCase" + # Ensure builds do not fail due to lint errors + echo "module.exports = { eslint: { build: false } }" > next.config.js + touch yarn.lock yarn set version berry diff --git a/test/integration/dist-dir/test/index.test.js b/test/integration/dist-dir/test/index.test.js index 8930f21ed046..d9202d8d8d52 100644 --- a/test/integration/dist-dir/test/index.test.js +++ b/test/integration/dist-dir/test/index.test.js @@ -47,7 +47,10 @@ describe('distDir', () => { it('should throw error with invalid distDir', async () => { const origNextConfig = await fs.readFile(nextConfig, 'utf8') - await fs.writeFile(nextConfig, `module.exports = { distDir: '' }`) + await fs.writeFile( + nextConfig, + `module.exports = { distDir: '', eslint: { build: false } }` + ) const { stderr } = await nextBuild(appDir, [], { stderr: true }) await fs.writeFile(nextConfig, origNextConfig) @@ -58,10 +61,12 @@ describe('distDir', () => { it('should handle null/undefined distDir', async () => { const origNextConfig = await fs.readFile(nextConfig, 'utf8') - await fs.writeFile(nextConfig, `module.exports = { distDir: null }`) + await fs.writeFile( + nextConfig, + `module.exports = { distDir: null, eslint: { build: false } }` + ) const { stderr } = await nextBuild(appDir, [], { stderr: true }) await fs.writeFile(nextConfig, origNextConfig) - expect(stderr.length).toBe(0) }) }) diff --git a/test/integration/eslint/custom-eslint-config/.eslintrc.json b/test/integration/eslint/custom-eslint-config/.eslintrc.json new file mode 100644 index 000000000000..f0dc21af7d53 --- /dev/null +++ b/test/integration/eslint/custom-eslint-config/.eslintrc.json @@ -0,0 +1,16 @@ +{ + "extends": ["plugin:@next/next/recommended"], + "rules": { + "@next/next/no-sync-scripts": "off", + "@next/next/no-css-tags": "warn", + "@next/next/no-html-link-for-pages": ["error", "./"] + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true, + "modules": true + } + } +} diff --git a/test/integration/eslint/custom-eslint-config/pages/index.js b/test/integration/eslint/custom-eslint-config/pages/index.js new file mode 100644 index 000000000000..64a516a0323a --- /dev/null +++ b/test/integration/eslint/custom-eslint-config/pages/index.js @@ -0,0 +1,13 @@ +import { Head } from 'next/document' + +export default class Test extends Head { + render() { + return ( +
+

Hello title

+ + +
+ ) + } +} diff --git a/test/integration/eslint/custom-next-config/.eslintrc.json b/test/integration/eslint/custom-next-config/.eslintrc.json new file mode 100644 index 000000000000..444bf63d9ee1 --- /dev/null +++ b/test/integration/eslint/custom-next-config/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "extends": ["plugin:@next/next/recommended"], + "rules": { + "@next/next/no-html-link-for-pages": ["error", "./"] + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true, + "modules": true + } + } +} diff --git a/test/integration/eslint/custom-next-config/next.config.js b/test/integration/eslint/custom-next-config/next.config.js new file mode 100644 index 000000000000..d1567e6d0576 --- /dev/null +++ b/test/integration/eslint/custom-next-config/next.config.js @@ -0,0 +1,6 @@ +module.exports = { + eslint: { + dev: true, + build: false, + }, +} diff --git a/test/integration/eslint/custom-next-config/pages/index.js b/test/integration/eslint/custom-next-config/pages/index.js new file mode 100644 index 000000000000..64a516a0323a --- /dev/null +++ b/test/integration/eslint/custom-next-config/pages/index.js @@ -0,0 +1,13 @@ +import { Head } from 'next/document' + +export default class Test extends Head { + render() { + return ( +
+

Hello title

+ + +
+ ) + } +} diff --git a/test/integration/eslint/pkg-json-eslint-config/package.json b/test/integration/eslint/pkg-json-eslint-config/package.json new file mode 100644 index 000000000000..4d09b24ceb47 --- /dev/null +++ b/test/integration/eslint/pkg-json-eslint-config/package.json @@ -0,0 +1,36 @@ +{ + "name": "hello-world", + "version": "1.0.0", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "latest", + "react": "^16.13.1", + "react-dom": "^16.13.1" + }, + "license": "MIT", + "eslintConfig": { + "extends": [ + "plugin:@next/next/recommended" + ], + "rules": { + "@next/next/no-sync-scripts": "off", + "@next/next/no-css-tags": "warn", + "@next/next/no-html-link-for-pages": [ + "error", + "./" + ] + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true, + "modules": true + } + } + } +} diff --git a/test/integration/eslint/pkg-json-eslint-config/pages/index.js b/test/integration/eslint/pkg-json-eslint-config/pages/index.js new file mode 100644 index 000000000000..64a516a0323a --- /dev/null +++ b/test/integration/eslint/pkg-json-eslint-config/pages/index.js @@ -0,0 +1,13 @@ +import { Head } from 'next/document' + +export default class Test extends Head { + render() { + return ( +
+

Hello title

+ + +
+ ) + } +} diff --git a/test/integration/eslint/test/custom-eslint-config.test.js b/test/integration/eslint/test/custom-eslint-config.test.js new file mode 100644 index 000000000000..bcfce446a5f5 --- /dev/null +++ b/test/integration/eslint/test/custom-eslint-config.test.js @@ -0,0 +1,37 @@ +import { join } from 'path' +import { nextBuild } from 'next-test-utils' +import { remove } from 'fs-extra' + +jest.setTimeout(1000 * 60 * 2) + +const appDir = join(__dirname, '../custom-eslint-config') + +describe('ESLint', () => { + let stdout + let code + + beforeAll(async () => { + await remove(join(appDir, '.next')) + ;({ code, stdout } = await nextBuild(appDir, [], { + stdout: true, + })) + }) + + it('should show warnings and errors based on custom eslint config', async () => { + expect(code).toBe(0) + expect(stdout).not.toContain( + 'No ESLint configuration was detected, but checks from the Next.js ESLint plugin were included automatically' + ) + expect(stdout).toContain('./pages/index.js') + expect(stdout).not.toContain( + "8:9 Warning: A synchronous script tag can impact your webpage's performance @next/next/no-sync-scripts" + ) + expect(stdout).toContain( + '9:9 Warning: In order to use external stylesheets use @import in the root stylesheet compiled with NextJS. This ensures proper priority to CSS when loading a webpage. @next/next/no-css-tags' + ) + expect(stdout).toContain( + '9:9 Warning: Stylesheet does not have an associated preload tag. This could potentially impact First paint. @next/next/missing-preload' + ) + expect(stdout).toContain('Compiled successfully') + }) +}) diff --git a/test/integration/eslint/test/custom-next-config.test.js b/test/integration/eslint/test/custom-next-config.test.js new file mode 100644 index 000000000000..7daf4689de0a --- /dev/null +++ b/test/integration/eslint/test/custom-next-config.test.js @@ -0,0 +1,55 @@ +import { join } from 'path' +import { + launchApp, + renderViaHTTP, + killApp, + findPort, + nextBuild, +} from 'next-test-utils' + +jest.setTimeout(1000 * 60 * 2) + +const appDir = join(__dirname, '../custom-next-config') + +describe('ESLint', () => { + it('should show messages in dev mode as specified in next config', async () => { + let stdout + + const appPort = await findPort() + const app = await launchApp(appDir, appPort, { + onStdout(msg) { + stdout += msg || '' + }, + }) + await renderViaHTTP(appPort, '/') + await killApp(app) + expect(stdout).toContain('pages/index.js') + expect(stdout).toContain( + "8:9 Warning: A synchronous script tag can impact your webpage's performance @next/next/no-sync-scripts" + ) + expect(stdout).toContain( + '9:9 Warning: In order to use external stylesheets use @import in the root stylesheet compiled with NextJS. This ensures proper priority to CSS when loading a webpage. @next/next/no-css-tags' + ) + expect(stdout).toContain( + '9:9 Warning: Stylesheet does not have an associated preload tag. This could potentially impact First paint. @next/next/missing-preload' + ) + }) + + it('should not show messages in build mode as specified in next config', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).not.toContain('pages/index.js') + expect(stdout).not.toContain( + "8:9 Warning: A synchronous script tag can impact your webpage's performance @next/next/no-sync-scripts" + ) + expect(stdout).not.toContain( + '9:9 Warning: In order to use external stylesheets use @import in the root stylesheet compiled with NextJS. This ensures proper priority to CSS when loading a webpage. @next/next/no-css-tags' + ) + expect(stdout).not.toContain( + '9:9 Warning: Stylesheet does not have an associated preload tag. This could potentially impact First paint. @next/next/missing-preload' + ) + expect(stdout).toContain('Compiled successfully') + }) +}) diff --git a/test/integration/eslint/test/pkg-json-eslint-config.test.js b/test/integration/eslint/test/pkg-json-eslint-config.test.js new file mode 100644 index 000000000000..ac0069ee7b74 --- /dev/null +++ b/test/integration/eslint/test/pkg-json-eslint-config.test.js @@ -0,0 +1,44 @@ +import { join } from 'path' +import { nextBuild } from 'next-test-utils' +import { remove } from 'fs-extra' + +jest.setTimeout(1000 * 60 * 2) + +const appDir = join(__dirname, '../pkg-json-eslint-config') + +describe('ESLint', () => { + let code + let output + + beforeAll(async () => { + await remove(join(appDir, '.next')) + let stderr + let stdout + ;({ code, stdout, stderr } = await nextBuild(appDir, [], { + stdout: true, + stderr: true, + })) + output = stderr + stdout + }) + + it('should show warnings and errors based on eslint config in package.json', async () => { + expect(code).toBe(0) + expect(output).not.toContain( + 'The Next.js ESLint plugin was not detected in' + ) + expect(output).not.toContain( + 'No ESLint configuration was detected, but checks from the Next.js ESLint plugin were included automatically' + ) + expect(output).toContain('./pages/index.js') + expect(output).not.toContain( + "8:9 Warning: A synchronous script tag can impact your webpage's performance @next/next/no-sync-scripts" + ) + expect(output).toContain( + '9:9 Warning: In order to use external stylesheets use @import in the root stylesheet compiled with NextJS. This ensures proper priority to CSS when loading a webpage. @next/next/no-css-tags' + ) + expect(output).toContain( + '9:9 Warning: Stylesheet does not have an associated preload tag. This could potentially impact First paint. @next/next/missing-preload' + ) + expect(output).toContain('Compiled successfully') + }) +}) diff --git a/test/integration/index-index/pages/index/index.js b/test/integration/index-index/pages/index/index.js index 626b31302558..6a83652f5ecf 100644 --- a/test/integration/index-index/pages/index/index.js +++ b/test/integration/index-index/pages/index/index.js @@ -1,3 +1,3 @@ export default function Index() { - return
index > index
+ return
index > index
} diff --git a/test/integration/index-index/pages/index/index/index.js b/test/integration/index-index/pages/index/index/index.js index 639914d7c4bb..5dfa7851c114 100644 --- a/test/integration/index-index/pages/index/index/index.js +++ b/test/integration/index-index/pages/index/index/index.js @@ -1,3 +1,3 @@ export default function Index() { - return
index > index > index
+ return
index > index > index
} diff --git a/test/integration/index-index/pages/index/project/index.js b/test/integration/index-index/pages/index/project/index.js index b6b50016bf4e..87892a403390 100644 --- a/test/integration/index-index/pages/index/project/index.js +++ b/test/integration/index-index/pages/index/project/index.js @@ -1,3 +1,3 @@ export default function Index() { - return
index > project
+ return
index > project
} diff --git a/test/integration/index-index/pages/index/user.js b/test/integration/index-index/pages/index/user.js index b317488aaebf..398520816d1d 100644 --- a/test/integration/index-index/pages/index/user.js +++ b/test/integration/index-index/pages/index/user.js @@ -1,3 +1,3 @@ export default function Index() { - return
index > user
+ return
index > user
} diff --git a/test/integration/production-build-dir/next.config.js b/test/integration/production-build-dir/next.config.js new file mode 100644 index 000000000000..397e034c301b --- /dev/null +++ b/test/integration/production-build-dir/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + eslint: { + build: false, + }, +} diff --git a/test/integration/production/pages/error-in-ssr-render.js b/test/integration/production/pages/error-in-ssr-render.js index 863abf80fb1d..c2477a38db1c 100644 --- a/test/integration/production/pages/error-in-ssr-render.js +++ b/test/integration/production/pages/error-in-ssr-render.js @@ -1,10 +1,11 @@ +/* eslint-disable */ + import React from 'react' export default class ErrorInRenderPage extends React.Component { static async getInitialProps() { return {} } - // eslint-disable-next-line react/require-render-return render() { throw new Error('An Expected error occured') } diff --git a/test/integration/tsconfig-verifier/pages/index.tsx b/test/integration/tsconfig-verifier/pages/index.tsx index f8dc264a3716..47bd85b976b0 100644 --- a/test/integration/tsconfig-verifier/pages/index.tsx +++ b/test/integration/tsconfig-verifier/pages/index.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars +/* eslint-disable */ + const blah: boolean = false -// eslint-disable-next-line @typescript-eslint/no-unused-vars const blah2 = import('../value').then((r) => r.default) export default () =>

Hello TypeScript

diff --git a/test/integration/webpack-require-hook/next.config.js b/test/integration/webpack-require-hook/next.config.js index 01cbcf62c6cf..588fbfd05bb5 100644 --- a/test/integration/webpack-require-hook/next.config.js +++ b/test/integration/webpack-require-hook/next.config.js @@ -11,4 +11,7 @@ module.exports = { throw new Error('Webpack require hook not applying') return config }, + eslint: { + build: false, + }, } diff --git a/yarn.lock b/yarn.lock index 464133effdf5..5062f49526fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -93,6 +93,15 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/eslint-parser@7.13.4": + version "7.13.4" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.13.4.tgz#dd9df3c70f44d2fb5a6519e8e10ca06c67dca43a" + integrity sha512-WfFEd89SzqmtYox8crTLJuEXyJolZY6Uu6iJpJmw4aMu50zHbYnxzxwuVkCt2cWygw+gLkUPTtAuox7eSnrL8g== + dependencies: + eslint-scope "5.1.0" + eslint-visitor-keys "^1.3.0" + semver "7.0.0" + "@babel/generator@^7.12.10", "@babel/generator@^7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.11.tgz#98a7df7b8c358c9a37ab07a24056853016aba3af" @@ -1162,6 +1171,22 @@ version "0.7.5" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" +"@eslint/eslintrc@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.1.3.tgz#7d1a2b2358552cc04834c0979bd4275362e37085" + integrity sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + lodash "^4.17.19" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + "@firebase/analytics-types@0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.3.1.tgz#3c5f5d71129c88295e17e914e34b391ffda1723c" @@ -2925,6 +2950,14 @@ version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" +"@types/eslint@7.2.5": + version "7.2.5" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.5.tgz#92172ecf490c2fce4b076739693d75f30376d610" + integrity sha512-Dc6ar9x16BdaR3NSxSF7T4IjL9gxxViJq8RmFd+2UAyA+K6ck2W+gUwfgpG/y9TPyUuBL35109bbULpEynvltA== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + "@types/estree@*": version "0.0.45" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" @@ -3034,6 +3067,11 @@ dependencies: "@types/jest-diff" "*" +"@types/json-schema@*": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" + integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== + "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" @@ -3568,6 +3606,11 @@ acorn-jsx@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" +acorn-jsx@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" + integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== + acorn-walk@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e" @@ -3585,6 +3628,11 @@ acorn@^7.1.0, acorn@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + acorn@^8.0.4: version "8.0.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.4.tgz#7a3ae4191466a6984eee0fe3407a4f3aa9db8354" @@ -3681,6 +3729,11 @@ anser@1.4.9: version "1.4.9" resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760" +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + ansi-escapes@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -5624,7 +5677,7 @@ cross-spawn@^5.0.1: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -6064,7 +6117,7 @@ deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" -deep-is@~0.1.3: +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -6468,6 +6521,13 @@ enhanced-resolve@^4.3.0: memory-fs "^0.5.0" tapable "^1.0.0" +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -6707,6 +6767,14 @@ eslint-plugin-react@7.19.0: string.prototype.matchall "^4.0.2" xregexp "^4.3.0" +eslint-scope@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" + integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -6722,6 +6790,14 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + eslint-utils@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" @@ -6734,10 +6810,22 @@ eslint-utils@^2.0.0: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" +eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + eslint@6.8.0: version "6.8.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" @@ -6780,6 +6868,49 @@ eslint@6.8.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +eslint@7.9.0: + version "7.9.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.9.0.tgz#522aeccc5c3a19017cf0cb46ebfd660a79acf337" + integrity sha512-V6QyhX21+uXp4T+3nrNfI3hQNBDa/P8ga7LoQOenwrlEFXrEnUEE+ok1dMtaS3b6rmLXhT1TkTIsG75HMLbknA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@eslint/eslintrc" "^0.1.3" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.0" + eslint-utils "^2.1.0" + eslint-visitor-keys "^1.3.0" + espree "^7.3.0" + esquery "^1.2.0" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.19" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + espree@^6.1.2: version "6.1.2" resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d" @@ -6788,6 +6919,15 @@ espree@^6.1.2: acorn-jsx "^5.1.0" eslint-visitor-keys "^1.1.0" +espree@^7.3.0: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -6798,17 +6938,31 @@ esquery@^1.0.1: dependencies: estraverse "^4.0.0" +esquery@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== + dependencies: + estraverse "^5.1.0" + esrecurse@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" dependencies: estraverse "^4.1.0" +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" -estraverse@^5.2.0: +estraverse@^5.1.0, estraverse@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== @@ -7116,7 +7270,7 @@ fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" -fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -9858,6 +10012,14 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + libnpmaccess@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-4.0.1.tgz#17e842e03bef759854adf6eb6c2ede32e782639f" @@ -11484,6 +11646,18 @@ optionator@^0.8.1, optionator@^0.8.3: type-check "~0.3.2" word-wrap "~1.2.3" +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + ora@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ora/-/ora-2.0.0.tgz#8ec3a37fa7bffb54a3a0c188a1f6798e7e1827cd" @@ -12826,6 +13000,11 @@ pre-commit@1.2.2: spawn-sync "^1.0.15" which "1.2.x" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -13499,6 +13678,11 @@ regexpp@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.0.0.tgz#dd63982ee3300e67b41c1956f850aa680d9d330e" +regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + regexpu-core@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" @@ -14146,7 +14330,7 @@ semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.3.2, semver@^7.3.4: +semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== @@ -14900,6 +15084,11 @@ strip-json-comments@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -15535,6 +15724,13 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -16240,7 +16436,7 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" -word-wrap@~1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"