diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json
index 151c2001186bf..75da43f6c1d13 100644
--- a/packages/eslint-plugin-next/package.json
+++ b/packages/eslint-plugin-next/package.json
@@ -15,6 +15,7 @@
"glob": "7.1.7"
},
"devDependencies": {
+ "@types/glob": "7.1.1",
"eslint": "7.24.0"
},
"scripts": {
diff --git a/packages/eslint-plugin-next/src/rules/google-font-display.js b/packages/eslint-plugin-next/src/rules/google-font-display.ts
similarity index 88%
rename from packages/eslint-plugin-next/src/rules/google-font-display.js
rename to packages/eslint-plugin-next/src/rules/google-font-display.ts
index 19a7096eca907..5913d4a2025ac 100644
--- a/packages/eslint-plugin-next/src/rules/google-font-display.js
+++ b/packages/eslint-plugin-next/src/rules/google-font-display.ts
@@ -1,8 +1,9 @@
-const NodeAttributes = require('../utils/node-attributes.js')
+import { defineRule } from '../utils/define-rule'
+import NodeAttributes from '../utils/node-attributes'
const url = 'https://nextjs.org/docs/messages/google-font-display'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description: 'Enforce font-display behavior with Google Fonts.',
@@ -12,10 +13,10 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
return {
JSXOpeningElement(node) {
- let message
+ let message: string | undefined
if (node.name.name !== 'link') {
return
@@ -58,4 +59,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/google-font-preconnect.js b/packages/eslint-plugin-next/src/rules/google-font-preconnect.ts
similarity index 87%
rename from packages/eslint-plugin-next/src/rules/google-font-preconnect.js
rename to packages/eslint-plugin-next/src/rules/google-font-preconnect.ts
index ae5491f964c69..05da834b22b32 100644
--- a/packages/eslint-plugin-next/src/rules/google-font-preconnect.js
+++ b/packages/eslint-plugin-next/src/rules/google-font-preconnect.ts
@@ -1,8 +1,9 @@
-const NodeAttributes = require('../utils/node-attributes.js')
+import { defineRule } from '../utils/define-rule'
+import NodeAttributes from '../utils/node-attributes'
const url = 'https://nextjs.org/docs/messages/google-font-preconnect'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description: 'Ensure `preconnect` is used with Google Fonts.',
@@ -12,7 +13,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
return {
JSXOpeningElement(node) {
if (node.name.name !== 'link') {
@@ -43,4 +44,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/inline-script-id.js b/packages/eslint-plugin-next/src/rules/inline-script-id.ts
similarity index 95%
rename from packages/eslint-plugin-next/src/rules/inline-script-id.js
rename to packages/eslint-plugin-next/src/rules/inline-script-id.ts
index e7cd994bd1899..e73a38dd836a4 100644
--- a/packages/eslint-plugin-next/src/rules/inline-script-id.js
+++ b/packages/eslint-plugin-next/src/rules/inline-script-id.ts
@@ -1,6 +1,8 @@
+import { defineRule } from '../utils/define-rule'
+
const url = 'https://nextjs.org/docs/messages/inline-script-id'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description:
@@ -11,7 +13,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
let nextScriptImportName = null
return {
@@ -70,4 +72,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/next-script-for-ga.js b/packages/eslint-plugin-next/src/rules/next-script-for-ga.ts
similarity index 94%
rename from packages/eslint-plugin-next/src/rules/next-script-for-ga.js
rename to packages/eslint-plugin-next/src/rules/next-script-for-ga.ts
index 4dfd6bea6889b..e928e4219a1b0 100644
--- a/packages/eslint-plugin-next/src/rules/next-script-for-ga.js
+++ b/packages/eslint-plugin-next/src/rules/next-script-for-ga.ts
@@ -1,4 +1,5 @@
-const NodeAttributes = require('../utils/node-attributes.js')
+import { defineRule } from '../utils/define-rule'
+import NodeAttributes from '../utils/node-attributes'
const SUPPORTED_SRCS = [
'www.google-analytics.com/analytics.js',
@@ -18,7 +19,7 @@ const containsStr = (str, strList) => {
return strList.some((s) => str.includes(s))
}
-module.exports = {
+export = defineRule({
meta: {
docs: {
description,
@@ -28,7 +29,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
return {
JSXOpeningElement(node) {
if (node.name.name !== 'script') {
@@ -76,4 +77,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-assign-module-variable.js b/packages/eslint-plugin-next/src/rules/no-assign-module-variable.ts
similarity index 67%
rename from packages/eslint-plugin-next/src/rules/no-assign-module-variable.js
rename to packages/eslint-plugin-next/src/rules/no-assign-module-variable.ts
index 459546741ca85..3fa7690810123 100644
--- a/packages/eslint-plugin-next/src/rules/no-assign-module-variable.js
+++ b/packages/eslint-plugin-next/src/rules/no-assign-module-variable.ts
@@ -1,6 +1,7 @@
+import { defineRule } from '../utils/define-rule'
const url = 'https://nextjs.org/docs/messages/no-assign-module-variable'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description: 'Prevent assignment to the `module` variable.',
@@ -11,13 +12,16 @@ module.exports = {
schema: [],
},
- create: function (context) {
+ create(context) {
return {
VariableDeclaration(node) {
// Checks node.declarations array for variable with id.name of `module`
- const moduleVariableFound = node.declarations.some(
- (declaration) => declaration.id.name === 'module'
- )
+ const moduleVariableFound = node.declarations.some((declaration) => {
+ if ('name' in declaration.id) {
+ return declaration.id.name === 'module'
+ }
+ return false
+ })
// Return early if no `module` variable is found
if (!moduleVariableFound) {
@@ -31,4 +35,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.js b/packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts
similarity index 91%
rename from packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.js
rename to packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts
index 9125fa0b2491d..26b81c626cf15 100644
--- a/packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.js
+++ b/packages/eslint-plugin-next/src/rules/no-before-interactive-script-outside-document.ts
@@ -1,9 +1,10 @@
-const path = require('path')
+import { defineRule } from '../utils/define-rule'
+import * as path from 'path'
const url =
'https://nextjs.org/docs/messages/no-before-interactive-script-outside-document'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description:
@@ -14,7 +15,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
let scriptImportName = null
return {
@@ -56,4 +57,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-css-tags.js b/packages/eslint-plugin-next/src/rules/no-css-tags.ts
similarity index 91%
rename from packages/eslint-plugin-next/src/rules/no-css-tags.js
rename to packages/eslint-plugin-next/src/rules/no-css-tags.ts
index 520ae3a5f99d4..a206d28c75875 100644
--- a/packages/eslint-plugin-next/src/rules/no-css-tags.js
+++ b/packages/eslint-plugin-next/src/rules/no-css-tags.ts
@@ -1,6 +1,7 @@
+import { defineRule } from '../utils/define-rule'
const url = 'https://nextjs.org/docs/messages/no-css-tags'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description: 'Prevent manual stylesheet tags.',
@@ -10,7 +11,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
return {
JSXOpeningElement(node) {
if (node.name.name !== 'link') {
@@ -43,4 +44,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-document-import-in-page.js b/packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts
similarity index 87%
rename from packages/eslint-plugin-next/src/rules/no-document-import-in-page.js
rename to packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts
index b801650e34e04..c5317f9960d94 100644
--- a/packages/eslint-plugin-next/src/rules/no-document-import-in-page.js
+++ b/packages/eslint-plugin-next/src/rules/no-document-import-in-page.ts
@@ -1,8 +1,9 @@
-const path = require('path')
+import { defineRule } from '../utils/define-rule'
+import * as path from 'path'
const url = 'https://nextjs.org/docs/messages/no-document-import-in-page'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description:
@@ -13,7 +14,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
return {
ImportDeclaration(node) {
if (node.source.value !== 'next/document') {
@@ -38,4 +39,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-duplicate-head.js b/packages/eslint-plugin-next/src/rules/no-duplicate-head.ts
similarity index 77%
rename from packages/eslint-plugin-next/src/rules/no-duplicate-head.js
rename to packages/eslint-plugin-next/src/rules/no-duplicate-head.ts
index 8b574fd73492b..0dcfdbe4140fa 100644
--- a/packages/eslint-plugin-next/src/rules/no-duplicate-head.js
+++ b/packages/eslint-plugin-next/src/rules/no-duplicate-head.ts
@@ -1,6 +1,7 @@
+import { defineRule } from '../utils/define-rule'
const url = 'https://nextjs.org/docs/messages/no-duplicate-head'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description:
@@ -11,7 +12,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
let documentImportName
return {
ImportDeclaration(node) {
@@ -30,6 +31,7 @@ module.exports = {
(ancestorNode) =>
ancestorNode.type === 'ClassDeclaration' &&
ancestorNode.superClass &&
+ 'name' in ancestorNode.superClass &&
ancestorNode.superClass.name === documentImportName
)
@@ -37,7 +39,13 @@ module.exports = {
return
}
- if (node.argument && node.argument.children) {
+ // @ts-expect-error - `node.argument` could be a `JSXElement` which has property `children`
+ if (
+ node.argument &&
+ 'children' in node.argument &&
+ node.argument.children
+ ) {
+ // @ts-expect-error - `node.argument` could be a `JSXElement` which has property `children`
const headComponents = node.argument.children.filter(
(childrenNode) =>
childrenNode.openingElement &&
@@ -57,4 +65,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-head-element.js b/packages/eslint-plugin-next/src/rules/no-head-element.ts
similarity index 88%
rename from packages/eslint-plugin-next/src/rules/no-head-element.js
rename to packages/eslint-plugin-next/src/rules/no-head-element.ts
index c1668b2cf6fca..1bcd4874cf4c5 100644
--- a/packages/eslint-plugin-next/src/rules/no-head-element.js
+++ b/packages/eslint-plugin-next/src/rules/no-head-element.ts
@@ -1,6 +1,8 @@
+import { defineRule } from '../utils/define-rule'
+
const url = 'https://nextjs.org/docs/messages/no-head-element'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description: 'Prevent usage of `
` element.',
@@ -11,7 +13,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
return {
JSXOpeningElement(node) {
const paths = context.getFilename()
@@ -29,4 +31,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-head-import-in-document.js b/packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts
similarity index 87%
rename from packages/eslint-plugin-next/src/rules/no-head-import-in-document.js
rename to packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts
index 9a852f40558ae..2d480c7c6648b 100644
--- a/packages/eslint-plugin-next/src/rules/no-head-import-in-document.js
+++ b/packages/eslint-plugin-next/src/rules/no-head-import-in-document.ts
@@ -1,8 +1,9 @@
-const path = require('path')
+import { defineRule } from '../utils/define-rule'
+import * as path from 'path'
const url = 'https://nextjs.org/docs/messages/no-head-import-in-document'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description: 'Prevent usage of `next/head` in `pages/_document.js`.',
@@ -12,7 +13,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
return {
ImportDeclaration(node) {
if (node.source.value !== 'next/head') {
@@ -38,4 +39,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.js b/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts
similarity index 86%
rename from packages/eslint-plugin-next/src/rules/no-html-link-for-pages.js
rename to packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts
index df4dfc58c0b7b..b5b06727f53c5 100644
--- a/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.js
+++ b/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts
@@ -1,12 +1,13 @@
-// @ts-check
-const path = require('path')
-const fs = require('fs')
-const getRootDir = require('../utils/get-root-dirs')
-const {
+import { defineRule } from '../utils/define-rule'
+import * as path from 'path'
+import * as fs from 'fs'
+import { getRootDirs } from '../utils/get-root-dirs'
+
+import {
getUrlFromPagesDirectories,
normalizeURL,
execOnce,
-} = require('../utils/url')
+} from '../utils/url'
const pagesDirWarning = execOnce((pagesDirs) => {
console.warn(
@@ -21,7 +22,7 @@ const fsExistsSyncCache = {}
const url = 'https://nextjs.org/docs/messages/no-html-link-for-pages'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description:
@@ -51,16 +52,12 @@ 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) {
- /** @type {(string|string[])[]} */
- const ruleOptions = context.options
+ create(context) {
+ const ruleOptions: (string | string[])[] = context.options
const [customPagesDirectory] = ruleOptions
- const rootDirs = getRootDir(context)
+ const rootDirs = getRootDirs(context)
const pagesDirs = (
customPagesDirectory
@@ -135,4 +132,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-img-element.ts b/packages/eslint-plugin-next/src/rules/no-img-element.ts
index baea84b669b93..0a5e5e4dcd32d 100644
--- a/packages/eslint-plugin-next/src/rules/no-img-element.ts
+++ b/packages/eslint-plugin-next/src/rules/no-img-element.ts
@@ -1,37 +1,38 @@
-import type { Rule } from 'eslint'
+import { defineRule } from '../utils/define-rule'
const url = 'https://nextjs.org/docs/messages/no-img-element'
-export const meta: Rule.RuleMetaData = {
- docs: {
- description: 'Prevent usage of `
` element to prevent layout shift.',
- category: 'HTML',
- recommended: true,
- url,
+export = defineRule({
+ meta: {
+ docs: {
+ description: 'Prevent usage of `
` element to prevent layout shift.',
+ category: 'HTML',
+ recommended: true,
+ url,
+ },
+ type: 'problem',
+ schema: [],
},
- type: 'problem',
- schema: [],
-}
-
-export const create: Rule.RuleModule['create'] = (context) => {
- return {
- JSXOpeningElement(node) {
- if (node.name.name !== 'img') {
- return
- }
+ create(context) {
+ return {
+ JSXOpeningElement(node) {
+ if (node.name.name !== 'img') {
+ return
+ }
- if (node.attributes.length === 0) {
- return
- }
+ if (node.attributes.length === 0) {
+ return
+ }
- if (node.parent?.parent?.openingElement?.name?.name === 'picture') {
- return
- }
+ if (node.parent?.parent?.openingElement?.name?.name === 'picture') {
+ return
+ }
- context.report({
- node,
- message: `Do not use \`
\` element. Use \`\` from \`next/image\` instead. See: ${url}`,
- })
- },
- }
-}
+ context.report({
+ node,
+ message: `Do not use \`
\` element. Use \`\` from \`next/image\` instead. See: ${url}`,
+ })
+ },
+ }
+ },
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-page-custom-font.js b/packages/eslint-plugin-next/src/rules/no-page-custom-font.ts
similarity index 82%
rename from packages/eslint-plugin-next/src/rules/no-page-custom-font.js
rename to packages/eslint-plugin-next/src/rules/no-page-custom-font.ts
index 6bd5e7872aba2..6cbe4f7243a62 100644
--- a/packages/eslint-plugin-next/src/rules/no-page-custom-font.js
+++ b/packages/eslint-plugin-next/src/rules/no-page-custom-font.ts
@@ -1,9 +1,15 @@
-const NodeAttributes = require('../utils/node-attributes.js')
-const { sep, posix } = require('path')
+import { defineRule } from '../utils/define-rule'
+import NodeAttributes from '../utils/node-attributes'
+import { sep, posix } from 'path'
+import type { AST } from 'eslint'
const url = 'https://nextjs.org/docs/messages/no-page-custom-font'
-module.exports = {
+function isIdentifierMatch(id1, id2) {
+ return (id1 === null && id2 === null) || (id1 && id2 && id1.name === id2.name)
+}
+
+export = defineRule({
meta: {
docs: {
description: 'Prevent page-only custom fonts.',
@@ -13,7 +19,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
const paths = context.getFilename().split('pages')
const page = paths[paths.length - 1]
@@ -53,6 +59,7 @@ module.exports = {
if (
node.declaration.type === 'ClassDeclaration' &&
node.declaration.superClass &&
+ 'name' in node.declaration.superClass &&
node.declaration.superClass.name === documentImportName
) {
localDefaultExportId = node.declaration.id
@@ -73,7 +80,7 @@ module.exports = {
// find the top level of the module
const program = ancestors.find(
(ancestor) => ancestor.type === 'Program'
- )
+ ) as AST.Program
// go over each token to find the combination of `export default `
for (let i = 0; i <= program.tokens.length - 1; i++) {
@@ -106,22 +113,28 @@ module.exports = {
if (exportDeclarationType === 'ClassDeclaration') {
return (
ancestor.type === exportDeclarationType &&
+ 'superClass' in ancestor &&
ancestor.superClass &&
+ 'name' in ancestor.superClass &&
ancestor.superClass.name === documentImportName
)
}
- // export default function ...
- if (exportDeclarationType === 'FunctionDeclaration') {
- return (
- ancestor.type === exportDeclarationType &&
- isIdentifierMatch(ancestor.id, localDefaultExportId)
- )
+ if ('id' in ancestor) {
+ // export default function ...
+ if (exportDeclarationType === 'FunctionDeclaration') {
+ return (
+ ancestor.type === exportDeclarationType &&
+ isIdentifierMatch(ancestor.id, localDefaultExportId)
+ )
+ }
+
+ // function ...() {} export default ...
+ // class ... extends ...; export default ...
+ return isIdentifierMatch(ancestor.id, localDefaultExportId)
}
- // function ...() {} export default ...
- // class ... extends ...; export default ...
- return isIdentifierMatch(ancestor.id, localDefaultExportId)
+ return false
})
// file starts with _document and this is within the default export
@@ -154,8 +167,4 @@ module.exports = {
},
}
},
-}
-
-function isIdentifierMatch(id1, id2) {
- return (id1 === null && id2 === null) || (id1 && id2 && id1.name === id2.name)
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-script-component-in-head.js b/packages/eslint-plugin-next/src/rules/no-script-component-in-head.ts
similarity index 92%
rename from packages/eslint-plugin-next/src/rules/no-script-component-in-head.js
rename to packages/eslint-plugin-next/src/rules/no-script-component-in-head.ts
index 9e342e738f42f..68b063ef8abc5 100644
--- a/packages/eslint-plugin-next/src/rules/no-script-component-in-head.js
+++ b/packages/eslint-plugin-next/src/rules/no-script-component-in-head.ts
@@ -1,6 +1,7 @@
+import { defineRule } from '../utils/define-rule'
const url = 'https://nextjs.org/docs/messages/no-script-component-in-head'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description: 'Prevent usage of `next/script` in `next/head` component.',
@@ -10,7 +11,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
let isNextHead = null
return {
@@ -52,4 +53,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.js b/packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.ts
similarity index 89%
rename from packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.js
rename to packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.ts
index 342f93eb1a066..42f4a4b899bb9 100644
--- a/packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.js
+++ b/packages/eslint-plugin-next/src/rules/no-styled-jsx-in-document.ts
@@ -1,8 +1,9 @@
-const path = require('path')
+import { defineRule } from '../utils/define-rule'
+import * as path from 'path'
const url = 'https://nextjs.org/docs/messages/no-styled-jsx-in-document'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description: 'Prevent usage of `styled-jsx` in `pages/_document.js`.',
@@ -12,7 +13,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
return {
JSXOpeningElement(node) {
const document = context.getFilename().split('pages')[1]
@@ -44,4 +45,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-sync-scripts.js b/packages/eslint-plugin-next/src/rules/no-sync-scripts.ts
similarity index 90%
rename from packages/eslint-plugin-next/src/rules/no-sync-scripts.js
rename to packages/eslint-plugin-next/src/rules/no-sync-scripts.ts
index 335c429fa5632..e786d2a52d5d1 100644
--- a/packages/eslint-plugin-next/src/rules/no-sync-scripts.js
+++ b/packages/eslint-plugin-next/src/rules/no-sync-scripts.ts
@@ -1,6 +1,8 @@
+import { defineRule } from '../utils/define-rule'
+
const url = 'https://nextjs.org/docs/messages/no-sync-scripts'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description: 'Prevent synchronous scripts.',
@@ -10,7 +12,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
return {
JSXOpeningElement(node) {
if (node.name.name !== 'script') {
@@ -35,4 +37,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-title-in-document-head.js b/packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts
similarity index 93%
rename from packages/eslint-plugin-next/src/rules/no-title-in-document-head.js
rename to packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts
index 5c6346ee84cc8..f164498e0e00d 100644
--- a/packages/eslint-plugin-next/src/rules/no-title-in-document-head.js
+++ b/packages/eslint-plugin-next/src/rules/no-title-in-document-head.ts
@@ -1,6 +1,7 @@
+import { defineRule } from '../utils/define-rule'
const url = 'https://nextjs.org/docs/messages/no-title-in-document-head'
-module.exports = {
+export = defineRule({
meta: {
docs: {
description:
@@ -11,7 +12,7 @@ module.exports = {
type: 'problem',
schema: [],
},
- create: function (context) {
+ create(context) {
let headFromNextDocument = false
return {
ImportDeclaration(node) {
@@ -51,4 +52,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-typos.js b/packages/eslint-plugin-next/src/rules/no-typos.ts
similarity index 95%
rename from packages/eslint-plugin-next/src/rules/no-typos.js
rename to packages/eslint-plugin-next/src/rules/no-typos.ts
index 09ca6f3128998..9882746d0b601 100644
--- a/packages/eslint-plugin-next/src/rules/no-typos.js
+++ b/packages/eslint-plugin-next/src/rules/no-typos.ts
@@ -1,4 +1,5 @@
-const path = require('path')
+import { defineRule } from '../utils/define-rule'
+import * as path from 'path'
const NEXT_EXPORT_FUNCTIONS = [
'getStaticProps',
@@ -40,7 +41,7 @@ function minDistance(a, b) {
}
/* eslint-disable eslint-plugin/require-meta-docs-url */
-module.exports = {
+export = defineRule({
meta: {
docs: {
description: 'Prevent common typos in Next.js data fetching functions.',
@@ -50,7 +51,7 @@ module.exports = {
schema: [],
},
- create: function (context) {
+ create(context) {
function checkTypos(node, name) {
if (NEXT_EXPORT_FUNCTIONS.includes(name)) {
return
@@ -105,4 +106,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.js b/packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts
similarity index 97%
rename from packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.js
rename to packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts
index 7fc24d756e927..b1ac5f2780d45 100644
--- a/packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.js
+++ b/packages/eslint-plugin-next/src/rules/no-unwanted-polyfillio.ts
@@ -1,3 +1,5 @@
+import { defineRule } from '../utils/define-rule'
+
// Keep in sync with next.js polyfills file : https://github.com/vercel/next.js/blob/master/packages/next-polyfill-nomodule/src/index.js
const NEXT_POLYFILLED_FEATURES = [
'Array.prototype.@@iterator',
@@ -69,7 +71,7 @@ const url = 'https://nextjs.org/docs/messages/no-unwanted-polyfillio'
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
-module.exports = {
+export = defineRule({
meta: {
docs: {
description: 'Prevent duplicate polyfills from Polyfill.io.',
@@ -81,7 +83,7 @@ module.exports = {
schema: [],
},
- create: function (context) {
+ create(context) {
let scriptImport = null
return {
@@ -132,4 +134,4 @@ module.exports = {
},
}
},
-}
+})
diff --git a/packages/eslint-plugin-next/src/utils/define-rule.ts b/packages/eslint-plugin-next/src/utils/define-rule.ts
new file mode 100644
index 0000000000000..22220254a7ee5
--- /dev/null
+++ b/packages/eslint-plugin-next/src/utils/define-rule.ts
@@ -0,0 +1,3 @@
+import type { Rule } from 'eslint'
+
+export const defineRule = (rule: Rule.RuleModule) => rule
diff --git a/packages/eslint-plugin-next/src/utils/get-root-dirs.js b/packages/eslint-plugin-next/src/utils/get-root-dirs.js
deleted file mode 100644
index 9a65aa99201b8..0000000000000
--- a/packages/eslint-plugin-next/src/utils/get-root-dirs.js
+++ /dev/null
@@ -1,40 +0,0 @@
-// @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
diff --git a/packages/eslint-plugin-next/src/utils/get-root-dirs.ts b/packages/eslint-plugin-next/src/utils/get-root-dirs.ts
new file mode 100644
index 0000000000000..db8b6b712d5eb
--- /dev/null
+++ b/packages/eslint-plugin-next/src/utils/get-root-dirs.ts
@@ -0,0 +1,32 @@
+import * as glob from 'glob'
+import type { Rule } from 'eslint'
+
+/**
+ * Process a Next.js root directory glob.
+ */
+const processRootDir = (rootDir: string): string[] => {
+ // Ensures we only match folders.
+ if (!rootDir.endsWith('/')) rootDir += '/'
+ return glob.sync(rootDir)
+}
+
+/**
+ * Gets one or more Root, returns an array of root directories.
+ */
+export const getRootDirs = (context: Rule.RuleContext) => {
+ let rootDirs = [context.getCwd()]
+
+ const nextSettings: { rootDir?: string | string[] } =
+ 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
+}
diff --git a/packages/eslint-plugin-next/src/utils/node-attributes.js b/packages/eslint-plugin-next/src/utils/node-attributes.js
deleted file mode 100644
index 2e1e5792c5c92..0000000000000
--- a/packages/eslint-plugin-next/src/utils/node-attributes.js
+++ /dev/null
@@ -1,54 +0,0 @@
-// Return attributes and values of a node in a convenient way:
-/* example:
-
- { attr1: {
- hasValue: true,
- value: 15
- },
- attr2: {
- hasValue: false
- }
-Inclusion of hasValue is in case an eslint rule cares about boolean values
-explicitly assigned to attribute vs the attribute being used as a flag
-*/
-class NodeAttributes {
- constructor(ASTnode) {
- this.attributes = {}
- ASTnode.attributes.forEach((attribute) => {
- if (!attribute.type || attribute.type !== 'JSXAttribute') {
- return
- }
- this.attributes[attribute.name.name] = {
- hasValue: !!attribute.value,
- }
- if (attribute.value) {
- if (attribute.value.value) {
- this.attributes[attribute.name.name].value = attribute.value.value
- } else if (attribute.value.expression) {
- this.attributes[attribute.name.name].value =
- typeof attribute.value.expression.value !== 'undefined'
- ? attribute.value.expression.value
- : attribute.value.expression.properties
- }
- }
- })
- }
- hasAny() {
- return !!Object.keys(this.attributes).length
- }
- has(attrName) {
- return !!this.attributes[attrName]
- }
- hasValue(attrName) {
- return !!this.attributes[attrName].hasValue
- }
- value(attrName) {
- if (!this.attributes[attrName]) {
- return true
- }
-
- return this.attributes[attrName].value
- }
-}
-
-module.exports = NodeAttributes
diff --git a/packages/eslint-plugin-next/src/utils/node-attributes.ts b/packages/eslint-plugin-next/src/utils/node-attributes.ts
new file mode 100644
index 0000000000000..4ad4667fa8b74
--- /dev/null
+++ b/packages/eslint-plugin-next/src/utils/node-attributes.ts
@@ -0,0 +1,74 @@
+// Return attributes and values of a node in a convenient way:
+/* example:
+
+ { attr1: {
+ hasValue: true,
+ value: 15
+ },
+ attr2: {
+ hasValue: false
+ }
+Inclusion of hasValue is in case an eslint rule cares about boolean values
+explicitly assigned to attribute vs the attribute being used as a flag
+*/
+export default class NodeAttributes {
+ attributes: Record<
+ string,
+ | {
+ hasValue?: false
+ }
+ | {
+ hasValue: true
+ value: any
+ }
+ >
+
+ constructor(ASTnode) {
+ this.attributes = {}
+ ASTnode.attributes.forEach((attribute) => {
+ if (!attribute.type || attribute.type !== 'JSXAttribute') {
+ return
+ }
+
+ if (!!attribute.value) {
+ // hasValue
+ const value = attribute.value.value
+ ? attribute.value.value
+ : typeof attribute.value.expression.value !== 'undefined'
+ ? attribute.value.expression.value
+ : attribute.value.expression.properties
+
+ this.attributes[attribute.name.name] = {
+ hasValue: true,
+ value,
+ }
+ } else {
+ this.attributes[attribute.name.name] = {
+ hasValue: false,
+ }
+ }
+ })
+ }
+ hasAny() {
+ return !!Object.keys(this.attributes).length
+ }
+ has(attrName: string) {
+ return !!this.attributes[attrName]
+ }
+ hasValue(attrName: string) {
+ return !!this.attributes[attrName].hasValue
+ }
+ value(attrName: string) {
+ const attr = this.attributes[attrName]
+
+ if (!attr) {
+ return true
+ }
+
+ if (attr.hasValue) {
+ return attr.value
+ }
+
+ return undefined
+ }
+}
diff --git a/packages/eslint-plugin-next/src/utils/url.js b/packages/eslint-plugin-next/src/utils/url.ts
similarity index 81%
rename from packages/eslint-plugin-next/src/utils/url.js
rename to packages/eslint-plugin-next/src/utils/url.ts
index 7f4823ac325bb..a49f00471040d 100644
--- a/packages/eslint-plugin-next/src/utils/url.js
+++ b/packages/eslint-plugin-next/src/utils/url.ts
@@ -1,5 +1,5 @@
-const fs = require('fs')
-const path = require('path')
+import * as path from 'path'
+import * as fs from 'fs'
// Cache for fs.lstatSync lookup.
// Prevent multiple blocking IO requests that have already been calculated.
@@ -11,53 +11,26 @@ const fsLstatSync = (source) => {
/**
* Checks if the source is a directory.
- * @param {string} source
*/
-function isDirectory(source) {
+function isDirectory(source: string) {
return fsLstatSync(source).isDirectory()
}
/**
* Checks if the source is a directory.
- * @param {string} source
*/
-function isSymlink(source) {
+function isSymlink(source: string) {
return fsLstatSync(source).isSymbolicLink()
}
-/**
- * Gets the possible URLs from a directory.
- * @param {string} urlprefix
- * @param {string[]} directories
- */
-function getUrlFromPagesDirectories(urlPrefix, directories) {
- return Array.from(
- // De-duplicate similar pages across multiple directories.
- new Set(
- directories
- .map((directory) => parseUrlForPages(urlPrefix, directory))
- .flat()
- .map(
- // Since the URLs are normalized we add `^` and `$` to the RegExp to make sure they match exactly.
- (url) => `^${normalizeURL(url)}$`
- )
- )
- ).map((urlReg) => {
- urlReg = urlReg.replace(/\[.*\]/g, '((?!.+?\\..+?).*?)')
- return new RegExp(urlReg)
- })
-}
-
// Cache for fs.readdirSync lookup.
// Prevent multiple blocking IO requests that have already been calculated.
const fsReadDirSyncCache = {}
/**
* Recursively parse directory for page URLs.
- * @param {string} urlprefix
- * @param {string} directory
*/
-function parseUrlForPages(urlprefix, directory) {
+function parseUrlForPages(urlprefix: string, directory: string) {
fsReadDirSyncCache[directory] =
fsReadDirSyncCache[directory] || fs.readdirSync(directory)
const res = []
@@ -84,9 +57,8 @@ function parseUrlForPages(urlprefix, directory) {
* - Replaces `index.html` with `/`
* - Makes sure all URLs are have a trailing `/`
* - Removes query string
- * @param {string} url
*/
-function normalizeURL(url) {
+export function normalizeURL(url: string) {
if (!url) {
return
}
@@ -101,11 +73,37 @@ function normalizeURL(url) {
return url
}
-function execOnce(fn) {
+/**
+ * Gets the possible URLs from a directory.
+ */
+export function getUrlFromPagesDirectories(
+ urlPrefix: string,
+ directories: string[]
+) {
+ return Array.from(
+ // De-duplicate similar pages across multiple directories.
+ new Set(
+ directories
+ .map((directory) => parseUrlForPages(urlPrefix, directory))
+ .flat()
+ .map(
+ // Since the URLs are normalized we add `^` and `$` to the RegExp to make sure they match exactly.
+ (url) => `^${normalizeURL(url)}$`
+ )
+ )
+ ).map((urlReg) => {
+ urlReg = urlReg.replace(/\[.*\]/g, '((?!.+?\\..+?).*?)')
+ return new RegExp(urlReg)
+ })
+}
+
+export function execOnce(
+ fn: (...args: TArgs) => TResult
+): (...args: TArgs) => TResult {
let used = false
- let result
+ let result: TResult
- return (...args) => {
+ return (...args: TArgs) => {
if (!used) {
used = true
result = fn(...args)
@@ -113,9 +111,3 @@ function execOnce(fn) {
return result
}
}
-
-module.exports = {
- getUrlFromPagesDirectories,
- normalizeURL,
- execOnce,
-}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5a3bb5809f4bb..d8a2c15109ebd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -419,11 +419,13 @@ importers:
packages/eslint-plugin-next:
specifiers:
+ '@types/glob': 7.1.1
eslint: 7.24.0
glob: 7.1.7
dependencies:
glob: 7.1.7
devDependencies:
+ '@types/glob': 7.1.1
eslint: 7.24.0
packages/font:
@@ -594,8 +596,8 @@ importers:
source-map: 0.6.1
stream-browserify: 3.0.0
stream-http: 3.1.1
- string_decoder: 1.3.0
string-hash: 1.1.3
+ string_decoder: 1.3.0
strip-ansi: 6.0.0
styled-jsx: 5.0.7
tar: 6.1.11
@@ -784,8 +786,8 @@ importers:
source-map: 0.6.1
stream-browserify: 3.0.0
stream-http: 3.1.1
- string_decoder: 1.3.0
string-hash: 1.1.3
+ string_decoder: 1.3.0
strip-ansi: 6.0.0
tar: 6.1.11
taskr: 1.1.0
@@ -11618,8 +11620,8 @@ packages:
engines: { node: '>=10' }
hasBin: true
dependencies:
- is-text-path: 1.0.1
JSONStream: 1.3.5
+ is-text-path: 1.0.1
lodash: 4.17.21
meow: 8.1.2
split2: 2.2.0