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 https://github.com/vercel/next.js/issues/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
This commit is contained in:
parent
e61ea6f27b
commit
4cd45aabcf
11 changed files with 104 additions and 17 deletions
4
packages/eslint-plugin-next/README.md
Normal file
4
packages/eslint-plugin-next/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# `@next/eslint-plugin-next`
|
||||
|
||||
Documentation for `@next/eslint-plugin-next` can be found at:
|
||||
https://nextjs.org/docs/basic-features/eslint#eslint-plugin
|
7
packages/eslint-plugin-next/jsconfig.json
Normal file
7
packages/eslint-plugin-next/jsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2019"
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
const NodeAttributes = require('../utils/nodeAttributes.js')
|
||||
const NodeAttributes = require('../utils/node-attributes.js')
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const NodeAttributes = require('../utils/nodeAttributes.js')
|
||||
const NodeAttributes = require('../utils/node-attributes.js')
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const NodeAttributes = require('../utils/nodeAttributes.js')
|
||||
const NodeAttributes = require('../utils/node-attributes.js')
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// @ts-check
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const getRootDir = require('../utils/get-root-dirs')
|
||||
const {
|
||||
getUrlFromPagesDirectories,
|
||||
normalizeURL,
|
||||
|
@ -20,7 +22,7 @@ const fsExistsSyncCache = {}
|
|||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Prohibit full page refresh for nextjs pages',
|
||||
description: 'Prohibit full page refresh for Next.js pages',
|
||||
category: 'HTML',
|
||||
recommended: true,
|
||||
},
|
||||
|
@ -43,14 +45,27 @@ module.exports = {
|
|||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an ESLint rule listener.
|
||||
*
|
||||
* @param {import('eslint').Rule.RuleContext} context - ESLint rule context
|
||||
* @returns {import('eslint').Rule.RuleListener} An ESLint rule listener
|
||||
*/
|
||||
create: function (context) {
|
||||
const [customPagesDirectory] = context.options
|
||||
const pagesDirs = customPagesDirectory
|
||||
? [customPagesDirectory].flat()
|
||||
: [
|
||||
path.join(context.getCwd(), 'pages'),
|
||||
path.join(context.getCwd(), 'src', 'pages'),
|
||||
]
|
||||
/** @type {(string|string[])[]} */
|
||||
const ruleOptions = context.options
|
||||
const [customPagesDirectory] = ruleOptions
|
||||
|
||||
const rootDirs = getRootDir(context)
|
||||
|
||||
const pagesDirs = (customPagesDirectory
|
||||
? [customPagesDirectory]
|
||||
: rootDirs.map((dir) => [
|
||||
path.join(dir, 'pages'),
|
||||
path.join(dir, 'src', 'pages'),
|
||||
])
|
||||
).flat()
|
||||
|
||||
const foundPagesDirs = pagesDirs.filter((dir) => {
|
||||
if (fsExistsSyncCache[dir] === undefined) {
|
||||
fsExistsSyncCache[dir] = fs.existsSync(dir)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const NodeAttributes = require('../utils/nodeAttributes.js')
|
||||
const NodeAttributes = require('../utils/node-attributes.js')
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
|
|
40
packages/eslint-plugin-next/lib/utils/get-root-dirs.js
Normal file
40
packages/eslint-plugin-next/lib/utils/get-root-dirs.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
// @ts-check
|
||||
const glob = require('glob')
|
||||
|
||||
/**
|
||||
* Process a Next.js root directory glob.
|
||||
*
|
||||
* @param {string} rootDir - A Next.js root directory glob.
|
||||
* @returns {string[]} - An array of Root directories.
|
||||
*/
|
||||
const processRootDir = (rootDir) => {
|
||||
// Ensures we only match folders.
|
||||
if (!rootDir.endsWith('/')) rootDir += '/'
|
||||
return glob.sync(rootDir)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets one or more Root
|
||||
*
|
||||
* @param {import('eslint').Rule.RuleContext} context - ESLint rule context
|
||||
* @returns An array of root directories.
|
||||
*/
|
||||
const getRootDirs = (context) => {
|
||||
let rootDirs = [context.getCwd()]
|
||||
|
||||
/** @type {{rootDir?:string|string[]}|undefined} */
|
||||
const nextSettings = context.settings.next || {}
|
||||
let rootDir = nextSettings.rootDir
|
||||
|
||||
if (typeof rootDir === 'string') {
|
||||
rootDirs = processRootDir(rootDir)
|
||||
} else if (Array.isArray(rootDir)) {
|
||||
rootDirs = rootDir
|
||||
.map((dir) => (typeof dir === 'string' ? processRootDir(dir) : []))
|
||||
.flat()
|
||||
}
|
||||
|
||||
return rootDirs
|
||||
}
|
||||
|
||||
module.exports = getRootDirs
|
|
@ -7,5 +7,14 @@
|
|||
"repository": {
|
||||
"url": "vercel/next.js",
|
||||
"directory": "packages/eslint-plugin-next"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"glob": "7.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/eslint": "7.28.0"
|
||||
}
|
||||
}
|
||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -4142,10 +4142,10 @@
|
|||
"@types/eslint" "*"
|
||||
"@types/estree" "*"
|
||||
|
||||
"@types/eslint@*":
|
||||
version "7.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.8.tgz#45cd802380fcc352e5680e1781d43c50916f12ee"
|
||||
integrity sha512-RTKvBsfz0T8CKOGZMfuluDNyMFHnu5lvNr4hWEsQeHXH6FcmIDIozOyWMh36nLGMwVd5UFNXC2xztA8lln22MQ==
|
||||
"@types/eslint@*", "@types/eslint@7.28.0":
|
||||
version "7.28.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.28.0.tgz#7e41f2481d301c68e14f483fe10b017753ce8d5a"
|
||||
integrity sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==
|
||||
dependencies:
|
||||
"@types/estree" "*"
|
||||
"@types/json-schema" "*"
|
||||
|
@ -6325,7 +6325,7 @@ caniuse-api@^3.0.0:
|
|||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
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:
|
||||
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:
|
||||
version "1.0.30001228"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa"
|
||||
integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==
|
||||
|
@ -9826,6 +9826,18 @@ glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glo
|
|||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@7.1.7:
|
||||
version "7.1.7"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
|
||||
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
global-dirs@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d"
|
||||
|
|
Loading…
Reference in a new issue