diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index d689da2dbb798..2fdb93dac4ccd 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -21,6 +21,7 @@ }, "scripts": { "build": "swc -d dist src", + "dev": "tsc -w", "prepublishOnly": "cd ../../ && turbo run build" } } diff --git a/packages/eslint-plugin-next/src/rules/date-hydration.ts b/packages/eslint-plugin-next/src/rules/date-hydration.ts index 0eeb25d60a221..aefb0402afc98 100644 --- a/packages/eslint-plugin-next/src/rules/date-hydration.ts +++ b/packages/eslint-plugin-next/src/rules/date-hydration.ts @@ -1,5 +1,6 @@ -import type { JSXElement, JSXAttribute } from 'estree-jsx' +import type { JSXElement, JSXExpressionContainer } from 'estree-jsx' import { defineRule } from '../utils/define-rule' +import NodeAttributes from '../utils/node-attributes' const url = 'https://nextjs.org/docs/messages/date-hydration' @@ -15,26 +16,25 @@ export = defineRule({ }, create(context) { return { - /** @param {import("estree-jsx").JSXExpressionContainer} node */ JSXExpressionContainer(node) { - if (node.expression.type !== 'CallExpression') return - if (node.expression.callee.type !== 'MemberExpression') return - if (node.expression.callee.object.type !== 'NewExpression') return - if (node.expression.callee.object.callee.type !== 'Identifier') return - if (node.expression.callee.object.callee.name !== 'Date') return + const n = node as JSXExpressionContainer + if (n.expression.type !== 'CallExpression') return + else if (n.expression.callee.type !== 'MemberExpression') return + else if (n.expression.callee.object.type !== 'NewExpression') return + else if (n.expression.callee.object.callee.type !== 'Identifier') return + else if (n.expression.callee.object.callee.name !== 'Date') return + const parent = context .getAncestors() .reverse()[0] as unknown as JSXElement - const suppressed = parent.openingElement.attributes.some( - (a) => (a as JSXAttribute).name.name === 'suppressHydrationWarning' - ) + + const attributes = new NodeAttributes(parent.openingElement) + const suppressed = attributes.has('suppressHydrationWarning') + if (suppressed) return context.report({ node, - suggest: [ - { desc: ` is not a valid React element. See: ${url}` }, - ], message: `Rendering \`Date\` directly can cause a hydration mismatch. See ${url}`, }) }, diff --git a/packages/eslint-plugin-next/tsconfig.json b/packages/eslint-plugin-next/tsconfig.json index ed9206798a9e8..890c0a35ef26b 100644 --- a/packages/eslint-plugin-next/tsconfig.json +++ b/packages/eslint-plugin-next/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2019" + "target": "es2019", + "outDir": "dist" }, "include": ["src/**/*"], "exclude": ["node_modules"] diff --git a/test/unit/eslint-plugin-next/date-hydration.test.ts b/test/unit/eslint-plugin-next/date-hydration.test.ts index 596da876db019..ef04673f2b96e 100644 --- a/test/unit/eslint-plugin-next/date-hydration.test.ts +++ b/test/unit/eslint-plugin-next/date-hydration.test.ts @@ -1,13 +1,7 @@ -import rule from '@next/eslint-plugin-next/lib/rules/date-hydration' -import { RuleTester } from 'eslint' +import rule from '@next/eslint-plugin-next/dist/rules/date-hydration' +import { ruleTester } from './utils' -new RuleTester({ - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - ecmaFeatures: { modules: true, jsx: true }, - }, -}).run('date-hydration', rule, { +ruleTester.run('date-hydration', rule, { valid: [ `export default function Page() { return

{new Date().toLocaleString()}

diff --git a/test/unit/eslint-plugin-next/utils.ts b/test/unit/eslint-plugin-next/utils.ts new file mode 100644 index 0000000000000..458d0a9790452 --- /dev/null +++ b/test/unit/eslint-plugin-next/utils.ts @@ -0,0 +1,9 @@ +import { RuleTester } from 'eslint' + +export const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { modules: true, jsx: true }, + }, +})