Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,19 @@ The `next/core-web-vitals` rule set is enabled when `next lint` is run for the f

> The `next/core-web-vitals` entry point is automatically included for new applications built with [Create Next App](/docs/app/api-reference/create-next-app).

### TypeScript

In addition to the Next.js ESLint rules, `create-next-app --typescript` will also add TypeScript-specific lint rules with `next/typescript` to your config:

```json filename=".eslintrc.json"
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
```

Those rules are based on [`plugin:@typescript-eslint/recommended`](https://typescript-eslint.io/linting/configs#recommended).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoshuaKGoldberg It'd be also nice as a follow-up to document how one would opt-into type-aware ESLint rules.

Copy link
Copy Markdown
Contributor Author

@JoshuaKGoldberg JoshuaKGoldberg Jun 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah! Strong +1.

Since typescript-eslint v8 with the stabilized projectService is coming in a few months, my suggestion would be to procrastinate until then. That way we can use the nice:

{
  "extends": ["next/core-web-vitals", "next/typescript",
+  "plugin:@typescript-eslint/recommended-type-checked" // (or similar)
  ],
+ "parserOptions": {
+   "projectService": true
+ }
}

See [typescript-eslint > Configs](https://typescript-eslint.io/linting/configs) for more details.

## Usage With Other Tools

### Prettier
Expand Down
4 changes: 2 additions & 2 deletions examples/with-temporal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"@types/node-fetch": "^3.0.3",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.1",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"cross-env": "^7.0.3",
"nodemon": "^2.0.12",
"ts-node": "^10.2.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/create-next-app/templates/app-tw/ts/eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals", "next/typescript"]
}
2 changes: 1 addition & 1 deletion packages/create-next-app/templates/app/ts/eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals", "next/typescript"]
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals", "next/typescript"]
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals", "next/typescript"]
}
15 changes: 6 additions & 9 deletions packages/eslint-config-next/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ sortedPaths.push(...keptPaths)

const hookPropertyMap = new Map(
[
['eslint-plugin-import', 'eslint-plugin-import'],
['eslint-plugin-react', 'eslint-plugin-react'],
['eslint-plugin-jsx-a11y', 'eslint-plugin-jsx-a11y'],
].map(([request, replacement]) => [
'@typescript-eslint/eslint-plugin',
'eslint-plugin-import',
'eslint-plugin-react',
'eslint-plugin-jsx-a11y',
].map((request) => [
request,
require.resolve(replacement, { paths: sortedPaths }),
require.resolve(request, { paths: sortedPaths }),
])
)

Expand Down Expand Up @@ -96,10 +97,6 @@ module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
jsx: true,
Comment thread
eps1lon marked this conversation as resolved.
Outdated
},
warnOnUnsupportedTypeScriptVersion: true,
},
},
],
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-config-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"@next/eslint-plugin-next": "15.0.0-canary.44",
"@rushstack/eslint-patch": "^1.3.3",
"@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0",
"@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
Expand Down
3 changes: 3 additions & 0 deletions packages/eslint-config-next/typescript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ['plugin:@typescript-eslint/recommended'],
}
20 changes: 0 additions & 20 deletions packages/next/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,26 +79,6 @@ export const SSG_FALLBACK_EXPORT_ERROR = `Pages with \`fallback\` enabled in \`g

export const ESLINT_DEFAULT_DIRS = ['app', 'pages', 'components', 'lib', 'src']

export const ESLINT_PROMPT_VALUES = [
{
title: 'Strict',
recommended: true,
config: {
extends: 'next/core-web-vitals',
},
},
{
title: 'Base',
config: {
extends: 'next',
},
},
{
title: 'Cancel',
config: null,
},
]

export const SERVER_RUNTIME: Record<string, ServerRuntime> = {
edge: 'edge',
experimentalEdge: 'experimental-edge',
Expand Down
32 changes: 32 additions & 0 deletions packages/next/src/lib/eslint/getESLintPromptValues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import findUp from 'next/dist/compiled/find-up'

export const getESLintStrictValue = async (cwd: string) => {
const tsConfigLocation = await findUp('tsconfig.json', { cwd })
const hasTSConfig = tsConfigLocation !== undefined

return {
title: 'Strict',
recommended: true,
config: {
extends: hasTSConfig
? ['next/core-web-vitals', 'next/typescript']
Comment thread
eps1lon marked this conversation as resolved.
Outdated
: 'next/core-web-vitals',
},
}
}

export const getESLintPromptValues = async (cwd: string) => {
return [
await getESLintStrictValue(cwd),
{
title: 'Base',
config: {
extends: 'next',
},
},
{
title: 'Cancel',
config: null,
},
]
}
15 changes: 8 additions & 7 deletions packages/next/src/lib/eslint/runLintCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { writeDefaultConfig } from './writeDefaultConfig'
import { hasEslintConfiguration } from './hasEslintConfiguration'
import { writeOutputFile } from './writeOutputFile'

import { ESLINT_PROMPT_VALUES } from '../constants'
import { findPagesDir } from '../find-pages-dir'
import { installDependencies } from '../install-dependencies'
import { hasNecessaryDependencies } from '../has-necessary-dependencies'
Expand All @@ -21,6 +20,10 @@ import * as Log from '../../build/output/log'
import type { EventLintCheckCompleted } from '../../telemetry/events/build'
import isError, { getProperError } from '../is-error'
import { getPkgManager } from '../helpers/get-pkg-manager'
import {
getESLintStrictValue,
getESLintPromptValues,
} from './getESLintPromptValues'

type Config = {
plugins: string[]
Expand All @@ -44,7 +47,7 @@ const requiredPackages = [
},
]

async function cliPrompt(): Promise<{ config?: any }> {
async function cliPrompt(cwd: string): Promise<{ config?: any }> {
console.log(
bold(
`${cyan(
Expand All @@ -58,7 +61,7 @@ async function cliPrompt(): Promise<{ config?: any }> {
await Promise.resolve(require('next/dist/compiled/cli-select'))
).default
const { value } = await cliSelect({
values: ESLINT_PROMPT_VALUES,
values: await getESLintPromptValues(cwd),
valueRenderer: (
{
title,
Expand Down Expand Up @@ -356,10 +359,8 @@ export async function runLintCheck(
} else {
// Ask user what config they would like to start with for first time "next lint" setup
const { config: selectedConfig } = strict
? ESLINT_PROMPT_VALUES.find(
(opt: { title: string }) => opt.title === 'Strict'
)!
: await cliPrompt()
? await getESLintStrictValue(baseDir)
: await cliPrompt(baseDir)

if (selectedConfig == null) {
// Show a warning if no option is selected in prompt
Expand Down
95 changes: 83 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading