Skip to content

Commit

Permalink
Add rootDir setting to eslint-plugin-next (#27918)
Browse files Browse the repository at this point in the history
## 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
  • Loading branch information
mrmckeb committed Aug 11, 2021
1 parent e61ea6f commit 4cd45aa
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 17 deletions.
4 changes: 4 additions & 0 deletions packages/eslint-plugin-next/README.md
@@ -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 changes: 7 additions & 0 deletions packages/eslint-plugin-next/jsconfig.json
@@ -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: {
Expand Down
@@ -1,4 +1,4 @@
const NodeAttributes = require('../utils/nodeAttributes.js')
const NodeAttributes = require('../utils/node-attributes.js')

module.exports = {
meta: {
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin-next/lib/rules/link-passhref.js
@@ -1,4 +1,4 @@
const NodeAttributes = require('../utils/nodeAttributes.js')
const NodeAttributes = require('../utils/node-attributes.js')

module.exports = {
meta: {
Expand Down
31 changes: 23 additions & 8 deletions packages/eslint-plugin-next/lib/rules/no-html-link-for-pages.js
@@ -1,5 +1,7 @@
// @ts-check
const path = require('path')
const fs = require('fs')
const getRootDir = require('../utils/get-root-dirs')
const {
getUrlFromPagesDirectories,
normalizeURL,
Expand All @@ -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,
},
Expand All @@ -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)
Expand Down
@@ -1,4 +1,4 @@
const NodeAttributes = require('../utils/nodeAttributes.js')
const NodeAttributes = require('../utils/node-attributes.js')

module.exports = {
meta: {
Expand Down
40 changes: 40 additions & 0 deletions packages/eslint-plugin-next/lib/utils/get-root-dirs.js
@@ -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
9 changes: 9 additions & 0 deletions packages/eslint-plugin-next/package.json
Expand Up @@ -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 changes: 17 additions & 5 deletions yarn.lock
Expand Up @@ -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" "*"
Expand Down Expand Up @@ -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==
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 4cd45aa

Please sign in to comment.