Skip to content

Commit 4cd45aa

Browse files
authored
Add rootDir setting to eslint-plugin-next (#27918)
## Introduction This PR enables setting a `rootDir` for a Next.js project, and follows the same pattern used by [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#parseroptionsproject). ## Details Previously, users had to pass paths to the rule itself. ```js module.exports = { rules: { "@next/next/no-html-link-for-pages": [ "error", // This could be a string, or array of strings. "/packages/my-app/pages", ], }, }; ``` With this PR, this has been simplified (the previous implementation still works as expected). ```js module.exports = { settings: { next: { rootDir: "/packages/my-app", }, }, rules: { "@next/next/no-html-link-for-pages": "error", }, }; ``` Further, this rule allows the use of globs, again aligning with `@typescript-eslint/parser`. ```js module.exports = { settings: { next: { // Globs rootDir: "/packages/*", rootDir: "/packages/{app-a,app-b}", // Arrays rootDir: ["/app-a", "/app-b"], // Arrays with globs rootDir: ["/main-app", "/other-apps/*"], }, }; ``` This enables users to either provide per-workspace configuration with overrides, or to use globs for situations like monorepos where the apps share a domain (micro-frontends). This doesn't solve, but improves #26330. ## Feature - [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [x] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [x] Make sure the linting passes
1 parent e61ea6f commit 4cd45aa

11 files changed

+104
-17
lines changed

packages/eslint-plugin-next/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# `@next/eslint-plugin-next`
2+
3+
Documentation for `@next/eslint-plugin-next` can be found at:
4+
https://nextjs.org/docs/basic-features/eslint#eslint-plugin
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"module": "commonjs",
4+
"target": "es2019"
5+
},
6+
"exclude": ["node_modules"]
7+
}

packages/eslint-plugin-next/lib/rules/google-font-display.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const NodeAttributes = require('../utils/nodeAttributes.js')
1+
const NodeAttributes = require('../utils/node-attributes.js')
22

33
module.exports = {
44
meta: {

packages/eslint-plugin-next/lib/rules/google-font-preconnect.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const NodeAttributes = require('../utils/nodeAttributes.js')
1+
const NodeAttributes = require('../utils/node-attributes.js')
22

33
module.exports = {
44
meta: {

packages/eslint-plugin-next/lib/rules/link-passhref.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const NodeAttributes = require('../utils/nodeAttributes.js')
1+
const NodeAttributes = require('../utils/node-attributes.js')
22

33
module.exports = {
44
meta: {

packages/eslint-plugin-next/lib/rules/no-html-link-for-pages.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
// @ts-check
12
const path = require('path')
23
const fs = require('fs')
4+
const getRootDir = require('../utils/get-root-dirs')
35
const {
46
getUrlFromPagesDirectories,
57
normalizeURL,
@@ -20,7 +22,7 @@ const fsExistsSyncCache = {}
2022
module.exports = {
2123
meta: {
2224
docs: {
23-
description: 'Prohibit full page refresh for nextjs pages',
25+
description: 'Prohibit full page refresh for Next.js pages',
2426
category: 'HTML',
2527
recommended: true,
2628
},
@@ -43,14 +45,27 @@ module.exports = {
4345
],
4446
},
4547

48+
/**
49+
* Creates an ESLint rule listener.
50+
*
51+
* @param {import('eslint').Rule.RuleContext} context - ESLint rule context
52+
* @returns {import('eslint').Rule.RuleListener} An ESLint rule listener
53+
*/
4654
create: function (context) {
47-
const [customPagesDirectory] = context.options
48-
const pagesDirs = customPagesDirectory
49-
? [customPagesDirectory].flat()
50-
: [
51-
path.join(context.getCwd(), 'pages'),
52-
path.join(context.getCwd(), 'src', 'pages'),
53-
]
55+
/** @type {(string|string[])[]} */
56+
const ruleOptions = context.options
57+
const [customPagesDirectory] = ruleOptions
58+
59+
const rootDirs = getRootDir(context)
60+
61+
const pagesDirs = (customPagesDirectory
62+
? [customPagesDirectory]
63+
: rootDirs.map((dir) => [
64+
path.join(dir, 'pages'),
65+
path.join(dir, 'src', 'pages'),
66+
])
67+
).flat()
68+
5469
const foundPagesDirs = pagesDirs.filter((dir) => {
5570
if (fsExistsSyncCache[dir] === undefined) {
5671
fsExistsSyncCache[dir] = fs.existsSync(dir)

packages/eslint-plugin-next/lib/rules/no-page-custom-font.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const NodeAttributes = require('../utils/nodeAttributes.js')
1+
const NodeAttributes = require('../utils/node-attributes.js')
22

33
module.exports = {
44
meta: {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// @ts-check
2+
const glob = require('glob')
3+
4+
/**
5+
* Process a Next.js root directory glob.
6+
*
7+
* @param {string} rootDir - A Next.js root directory glob.
8+
* @returns {string[]} - An array of Root directories.
9+
*/
10+
const processRootDir = (rootDir) => {
11+
// Ensures we only match folders.
12+
if (!rootDir.endsWith('/')) rootDir += '/'
13+
return glob.sync(rootDir)
14+
}
15+
16+
/**
17+
* Gets one or more Root
18+
*
19+
* @param {import('eslint').Rule.RuleContext} context - ESLint rule context
20+
* @returns An array of root directories.
21+
*/
22+
const getRootDirs = (context) => {
23+
let rootDirs = [context.getCwd()]
24+
25+
/** @type {{rootDir?:string|string[]}|undefined} */
26+
const nextSettings = context.settings.next || {}
27+
let rootDir = nextSettings.rootDir
28+
29+
if (typeof rootDir === 'string') {
30+
rootDirs = processRootDir(rootDir)
31+
} else if (Array.isArray(rootDir)) {
32+
rootDirs = rootDir
33+
.map((dir) => (typeof dir === 'string' ? processRootDir(dir) : []))
34+
.flat()
35+
}
36+
37+
return rootDirs
38+
}
39+
40+
module.exports = getRootDirs

packages/eslint-plugin-next/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,14 @@
77
"repository": {
88
"url": "vercel/next.js",
99
"directory": "packages/eslint-plugin-next"
10+
},
11+
"files": [
12+
"lib"
13+
],
14+
"dependencies": {
15+
"glob": "7.1.7"
16+
},
17+
"devDependencies": {
18+
"@types/eslint": "7.28.0"
1019
}
1120
}

yarn.lock

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4142,10 +4142,10 @@
41424142
"@types/eslint" "*"
41434143
"@types/estree" "*"
41444144

4145-
"@types/eslint@*":
4146-
version "7.2.8"
4147-
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.8.tgz#45cd802380fcc352e5680e1781d43c50916f12ee"
4148-
integrity sha512-RTKvBsfz0T8CKOGZMfuluDNyMFHnu5lvNr4hWEsQeHXH6FcmIDIozOyWMh36nLGMwVd5UFNXC2xztA8lln22MQ==
4145+
"@types/eslint@*", "@types/[email protected]":
4146+
version "7.28.0"
4147+
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.28.0.tgz#7e41f2481d301c68e14f483fe10b017753ce8d5a"
4148+
integrity sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==
41494149
dependencies:
41504150
"@types/estree" "*"
41514151
"@types/json-schema" "*"
@@ -6325,7 +6325,7 @@ caniuse-api@^3.0.0:
63256325
lodash.memoize "^4.1.2"
63266326
lodash.uniq "^4.5.0"
63276327

6328-
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001165, caniuse-lite@^1.0.30001202, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001228:
6328+
caniuse-lite@1.0.30001228, caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001165, caniuse-lite@^1.0.30001202, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001228:
63296329
version "1.0.30001228"
63306330
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa"
63316331
integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==
@@ -9826,6 +9826,18 @@ [email protected], glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glo
98269826
once "^1.3.0"
98279827
path-is-absolute "^1.0.0"
98289828

9829+
9830+
version "7.1.7"
9831+
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
9832+
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
9833+
dependencies:
9834+
fs.realpath "^1.0.0"
9835+
inflight "^1.0.4"
9836+
inherits "2"
9837+
minimatch "^3.0.4"
9838+
once "^1.3.0"
9839+
path-is-absolute "^1.0.0"
9840+
98299841
global-dirs@^2.0.1:
98309842
version "2.1.0"
98319843
resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d"

0 commit comments

Comments
 (0)