Skip to content

Commit 5ed92fe

Browse files
wojtekolekijjk
authored andcommitted
[ESLint] Add app dir to default linting directories (vercel#44426)
Fixes vercel#44424 by adding the `app` folder to an `ESLINT_DEFAULT_DIRS` constant which defines all folders where the linter should go through. ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) --------- Co-authored-by: JJ Kasper <[email protected]>
1 parent 77f130e commit 5ed92fe

File tree

8 files changed

+137
-9
lines changed

8 files changed

+137
-9
lines changed

docs/api-reference/cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ npx next start --keepAliveTimeout 70000
120120

121121
## Lint
122122

123-
`next lint` runs ESLint for all files in the `pages`, `components`, and `lib` directories. It also
123+
`next lint` runs ESLint for all files in the `pages/`, `app` (only if the experimental `appDir` feature is enabled), `components/`, `lib/`, and `src/` directories. It also
124124
provides a guided setup to install any required dependencies if ESLint is not already configured in
125125
your application.
126126

docs/basic-features/eslint.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ If you're using `eslint-plugin-next` in a project where Next.js isn't installed
126126

127127
## Linting Custom Directories and Files
128128

129-
By default, Next.js will run ESLint for all files in the `pages/`, `components/`, `lib/`, and `src/` directories. However, you can specify which directories using the `dirs` option in the `eslint` config in `next.config.js` for production builds:
129+
By default, Next.js will run ESLint for all files in the `pages/`, `app` (only if the experimental `appDir` feature is enabled), `components/`, `lib/`, and `src/` directories. However, you can specify which directories using the `dirs` option in the `eslint` config in `next.config.js` for production builds:
130130

131131
```js
132132
module.exports = {

packages/next/src/cli/next-lint.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { join } from 'path'
55
import chalk from 'next/dist/compiled/chalk'
66

77
import { CliCommand } from '../lib/commands'
8-
import { ESLINT_DEFAULT_DIRS } from '../lib/constants'
8+
import {
9+
ESLINT_DEFAULT_DIRS,
10+
ESLINT_DEFAULT_DIRS_WITH_APP,
11+
} from '../lib/constants'
912
import { runLintCheck } from '../lib/eslint/runLintCheck'
1013
import { printAndExit } from '../server/lib/utils'
1114
import { Telemetry } from '../telemetry/storage'
@@ -170,8 +173,13 @@ const nextLint: CliCommand = async (argv) => {
170173
const dirs: string[] = args['--dir'] ?? nextConfig.eslint?.dirs
171174
const filesToLint = [...(dirs ?? []), ...files]
172175

176+
// Remove that when the `appDir` will be stable.
177+
const directoriesToLint = !!nextConfig.experimental.appDir
178+
? ESLINT_DEFAULT_DIRS_WITH_APP
179+
: ESLINT_DEFAULT_DIRS
180+
173181
const pathsToLint = (
174-
filesToLint.length ? filesToLint : ESLINT_DEFAULT_DIRS
182+
filesToLint.length ? filesToLint : directoriesToLint
175183
).reduce((res: string[], d: string) => {
176184
const currDir = join(baseDir, d)
177185
if (!existsSync(currDir)) return res

packages/next/src/lib/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ export const NON_STANDARD_NODE_ENV = `You are using a non-standard "NODE_ENV" va
3939

4040
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`
4141

42+
// Consolidate this consts when the `appDir` will be stable.
4243
export const ESLINT_DEFAULT_DIRS = ['pages', 'components', 'lib', 'src']
44+
export const ESLINT_DEFAULT_DIRS_WITH_APP = ['app', ...ESLINT_DEFAULT_DIRS]
4345

4446
export const ESLINT_PROMPT_VALUES = [
4547
{

packages/next/src/lib/verifyAndLint.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import chalk from 'next/dist/compiled/chalk'
22
import { Worker } from 'next/dist/compiled/jest-worker'
33
import { existsSync } from 'fs'
44
import { join } from 'path'
5-
import { ESLINT_DEFAULT_DIRS } from './constants'
5+
import { ESLINT_DEFAULT_DIRS, ESLINT_DEFAULT_DIRS_WITH_APP } from './constants'
66
import { Telemetry } from '../telemetry/storage'
77
import { eventLintCheckCompleted } from '../telemetry/events'
88
import { CompileError } from './compile-error'
@@ -28,7 +28,12 @@ export async function verifyAndLint(
2828
lintWorkers.getStdout().pipe(process.stdout)
2929
lintWorkers.getStderr().pipe(process.stderr)
3030

31-
const lintDirs = (configLintDirs ?? ESLINT_DEFAULT_DIRS).reduce(
31+
// Remove that when the `appDir` will be stable.
32+
const directoriesToLint = hasAppDir
33+
? ESLINT_DEFAULT_DIRS_WITH_APP
34+
: ESLINT_DEFAULT_DIRS
35+
36+
const lintDirs = (configLintDirs ?? directoriesToLint).reduce(
3237
(res: string[], d: string) => {
3338
const currDir = join(dir, d)
3439
if (!existsSync(currDir)) return res
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const RootLayout = () => (
2+
<div>
3+
<h1>Hello title</h1>
4+
<link href="https://fonts.gstatic.com" />
5+
</div>
6+
)
7+
8+
export default RootLayout
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
experimental: {
3+
appDir: false,
4+
},
5+
}

test/integration/eslint/test/index.test.js

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import fs from 'fs-extra'
22
import os from 'os'
3-
import execa from 'execa'
43

5-
import { dirname, join } from 'path'
4+
import { join } from 'path'
65

76
import findUp from 'next/dist/compiled/find-up'
8-
import { nextBuild, nextLint } from 'next-test-utils'
7+
import { nextBuild, nextLint, File } from 'next-test-utils'
98

109
const dirFirstTimeSetup = join(__dirname, '../first-time-setup')
1110
const dirCustomConfig = join(__dirname, '../custom-config')
@@ -20,6 +19,9 @@ const dirPluginCoreWebVitalsConfig = join(
2019
)
2120
const dirIgnoreDuringBuilds = join(__dirname, '../ignore-during-builds')
2221
const dirBaseDirectories = join(__dirname, '../base-directories')
22+
const dirBaseDirectoriesConfigFile = new File(
23+
join(dirBaseDirectories, '/next.config.js')
24+
)
2325
const dirCustomDirectories = join(__dirname, '../custom-directories')
2426
const dirConfigInPackageJson = join(__dirname, '../config-in-package-json')
2527
const dirInvalidOlderEslintVersion = join(
@@ -81,7 +83,15 @@ describe('ESLint', () => {
8183
)
8284
})
8385

86+
// Consolidate two tests below when the `appDir` is released.
8487
test('base directories are linted by default during builds', async () => {
88+
dirBaseDirectoriesConfigFile.write(`
89+
module.exports = {
90+
experimental: {
91+
appDir: false,
92+
}
93+
}
94+
`)
8595
const { stdout, stderr } = await nextBuild(dirBaseDirectories, [], {
8696
stdout: true,
8797
stderr: true,
@@ -100,12 +110,54 @@ describe('ESLint', () => {
100110
expect(output).toContain(
101111
'Warning: Synchronous scripts should not be used'
102112
)
113+
expect(output).not.toContain(
114+
'Warning: `rel="preconnect"` is missing from Google Font'
115+
)
103116

104117
// Files in pages, components, lib, and src directories are linted
105118
expect(output).toContain('pages/_document.js')
106119
expect(output).toContain('components/bar.js')
107120
expect(output).toContain('lib/foo.js')
108121
expect(output).toContain('src/index.js')
122+
expect(output).not.toContain('app/layout.js')
123+
})
124+
125+
test('base directories with appDir flag are linted by default during builds', async () => {
126+
dirBaseDirectoriesConfigFile.write(`
127+
module.exports = {
128+
experimental: {
129+
appDir: true,
130+
}
131+
}
132+
`)
133+
const { stdout, stderr } = await nextBuild(dirBaseDirectories, [], {
134+
stdout: true,
135+
stderr: true,
136+
})
137+
138+
const output = stdout + stderr
139+
140+
expect(output).toContain('Failed to compile')
141+
expect(output).toContain(
142+
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
143+
)
144+
expect(output).toContain(
145+
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Use `<Image />` from `next/image` instead to utilize Image Optimization.'
146+
)
147+
expect(output).toContain('Warning: Do not include stylesheets manually')
148+
expect(output).toContain(
149+
'Warning: Synchronous scripts should not be used'
150+
)
151+
expect(output).toContain(
152+
'Warning: `rel="preconnect"` is missing from Google Font'
153+
)
154+
155+
// Files in pages, app, components, lib, and src directories are linted
156+
expect(output).toContain('pages/_document.js')
157+
expect(output).toContain('components/bar.js')
158+
expect(output).toContain('lib/foo.js')
159+
expect(output).toContain('src/index.js')
160+
expect(output).toContain('app/layout.js')
109161
})
110162

111163
test('custom directories', async () => {
@@ -316,7 +368,15 @@ describe('ESLint', () => {
316368
)
317369
})
318370

371+
// Consolidate two tests below when the `appDir` is released.
319372
test('base directories are linted by default', async () => {
373+
dirBaseDirectoriesConfigFile.write(`
374+
module.exports = {
375+
experimental: {
376+
appDir: false,
377+
}
378+
}
379+
`)
320380
const { stdout, stderr } = await nextLint(dirBaseDirectories, [], {
321381
stdout: true,
322382
stderr: true,
@@ -333,12 +393,52 @@ describe('ESLint', () => {
333393
expect(output).toContain(
334394
'Warning: Synchronous scripts should not be used'
335395
)
396+
expect(output).not.toContain(
397+
'Warning: `rel="preconnect"` is missing from Google Font'
398+
)
336399

337400
// Files in pages, components, lib, and src directories are linted
338401
expect(output).toContain('pages/_document.js')
339402
expect(output).toContain('components/bar.js')
340403
expect(output).toContain('lib/foo.js')
341404
expect(output).toContain('src/index.js')
405+
expect(output).not.toContain('app/layout.js')
406+
})
407+
408+
test('base directories with appDir flag are linted by default', async () => {
409+
dirBaseDirectoriesConfigFile.write(`
410+
module.exports = {
411+
experimental: {
412+
appDir: true,
413+
}
414+
}
415+
`)
416+
const { stdout, stderr } = await nextLint(dirBaseDirectories, [], {
417+
stdout: true,
418+
stderr: true,
419+
})
420+
421+
const output = stdout + stderr
422+
expect(output).toContain(
423+
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
424+
)
425+
expect(output).toContain(
426+
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Use `<Image />` from `next/image` instead to utilize Image Optimization.'
427+
)
428+
expect(output).toContain('Warning: Do not include stylesheets manually')
429+
expect(output).toContain(
430+
'Warning: Synchronous scripts should not be used'
431+
)
432+
expect(output).toContain(
433+
'Warning: `rel="preconnect"` is missing from Google Font'
434+
)
435+
436+
// Files in pages, app, components, lib, and src directories are linted
437+
expect(output).toContain('pages/_document.js')
438+
expect(output).toContain('components/bar.js')
439+
expect(output).toContain('lib/foo.js')
440+
expect(output).toContain('src/index.js')
441+
expect(output).toContain('app/layout.js')
342442
})
343443

344444
test('shows warnings and errors with next/core-web-vitals config', async () => {

0 commit comments

Comments
 (0)