diff --git a/packages/eslint/src/generators/utils/eslint-file.ts b/packages/eslint/src/generators/utils/eslint-file.ts index c18ea2b8dd900..7f3e7d2d5d935 100644 --- a/packages/eslint/src/generators/utils/eslint-file.ts +++ b/packages/eslint/src/generators/utils/eslint-file.ts @@ -207,7 +207,7 @@ function overrideNeedsCompat( override: Linter.ConfigOverride ) { return ( - !override.env && !override.extends && !override.plugins && !override.parser + override.env || override.extends || override.plugins || override.parser ); } @@ -229,7 +229,12 @@ export function updateOverrideInLintConfig( updateJson(tree, fileName, (json: Linter.Config) => { const index = json.overrides.findIndex(lookup); if (index !== -1) { - json.overrides[index] = update(json.overrides[index]); + const newOverride = update(json.overrides[index]); + if (newOverride) { + json.overrides[index] = newOverride; + } else { + json.overrides.splice(index, 1); + } } return json; }); @@ -301,10 +306,9 @@ export function addExtendsToLintConfig( if (useFlatConfig(tree)) { const fileName = joinPathFragments(root, 'eslint.config.js'); const pluginExtends = generatePluginExtendsElement(plugins); - tree.write( - fileName, - addBlockToFlatConfigExport(tree.read(fileName, 'utf8'), pluginExtends) - ); + let content = tree.read(fileName, 'utf8'); + content = addCompatToFlatConfig(content); + tree.write(fileName, addBlockToFlatConfigExport(content, pluginExtends)); } else { const fileName = joinPathFragments(root, '.eslintrc.json'); updateJson(tree, fileName, (json) => { diff --git a/packages/eslint/src/generators/utils/flat-config/ast-utils.ts b/packages/eslint/src/generators/utils/flat-config/ast-utils.ts index a4a2b0e9b89ef..34f3cb91273ed 100644 --- a/packages/eslint/src/generators/utils/flat-config/ast-utils.ts +++ b/packages/eslint/src/generators/utils/flat-config/ast-utils.ts @@ -125,7 +125,7 @@ export function replaceOverride( content: string, root: string, lookup: (override: Linter.ConfigOverride) => boolean, - update: ( + update?: ( override: Linter.ConfigOverride ) => Linter.ConfigOverride ): string { @@ -167,12 +167,14 @@ export function replaceOverride( length: end - start, }); const updatedData = update(data); - mapFilePaths(updatedData); - changes.push({ - type: ChangeType.Insert, - index: start, - text: JSON.stringify(updatedData, null, 2).slice(2, -2), // remove curly braces and start/end line breaks since we are injecting just properties - }); + if (updatedData) { + mapFilePaths(updatedData); + changes.push({ + type: ChangeType.Insert, + index: start, + text: JSON.stringify(updatedData, null, 2).slice(2, -2), // remove curly braces and start/end line breaks since we are injecting just properties + }); + } } } }); diff --git a/packages/next/migrations.json b/packages/next/migrations.json index e198c0fcbbb41..17a141efc96e4 100644 --- a/packages/next/migrations.json +++ b/packages/next/migrations.json @@ -23,6 +23,12 @@ "version": "16.4.0-beta.3", "description": "Update package.json moving @nx/next from dependency to devDependency", "implementation": "./src/migrations/update-16-4-0/update-nx-next-dependency" + }, + "update-17-2-7": { + "cli": "nx", + "version": "17.2.7", + "description": "Remove patched eslint rule for @next/next/no-html-link-for-pages", + "implementation": "./src/migrations/update-17-2-7/remove-eslint-rules-patch" } }, "packageJsonUpdates": { diff --git a/packages/next/src/generators/application/application.spec.ts b/packages/next/src/generators/application/application.spec.ts index 4f451d4d49559..0d8569d018139 100644 --- a/packages/next/src/generators/application/application.spec.ts +++ b/packages/next/src/generators/application/application.spec.ts @@ -626,14 +626,6 @@ describe('app', () => { ".next/**/*", ], "overrides": [ - { - "files": [ - "*.*", - ], - "rules": { - "@next/next/no-html-link-for-pages": "off", - }, - }, { "files": [ "*.ts", diff --git a/packages/next/src/generators/application/lib/add-linting.spec.ts b/packages/next/src/generators/application/lib/add-linting.spec.ts index 0e452f60391fe..06c15274252b1 100644 --- a/packages/next/src/generators/application/lib/add-linting.spec.ts +++ b/packages/next/src/generators/application/lib/add-linting.spec.ts @@ -62,14 +62,6 @@ describe('updateEslint', () => { ".next/**/*", ], "overrides": [ - { - "files": [ - "*.*", - ], - "rules": { - "@next/next/no-html-link-for-pages": "off", - }, - }, { "files": [ "*.ts", @@ -132,10 +124,6 @@ describe('updateEslint', () => { module.exports = [ - { - files: ["**/*.*"], - rules: { "@next/next/no-html-link-for-pages": "off" } - }, ...baseConfig, { "files": [ diff --git a/packages/next/src/generators/application/lib/add-linting.ts b/packages/next/src/generators/application/lib/add-linting.ts index 0a0267a841938..7cb008901a83c 100644 --- a/packages/next/src/generators/application/lib/add-linting.ts +++ b/packages/next/src/generators/application/lib/add-linting.ts @@ -38,19 +38,6 @@ export async function addLinting( 'next/core-web-vitals', ]); - // Turn off @next/next/no-html-link-for-pages since there is an issue with nextjs throwing linting errors - // TODO(nicholas): remove after Vercel updates nextjs linter to only lint ["*.ts", "*.tsx", "*.js", "*.jsx"] - addOverrideToLintConfig( - host, - options.appProjectRoot, - { - files: ['*.*'], - rules: { - '@next/next/no-html-link-for-pages': 'off', - }, - }, - { insertAtTheEnd: false } - ); updateOverrideInLintConfig( host, options.appProjectRoot, diff --git a/packages/next/src/migrations/update-17-2-7/remove-eslint-rules-patch.spec.ts b/packages/next/src/migrations/update-17-2-7/remove-eslint-rules-patch.spec.ts new file mode 100644 index 0000000000000..cf91c1e1abcbd --- /dev/null +++ b/packages/next/src/migrations/update-17-2-7/remove-eslint-rules-patch.spec.ts @@ -0,0 +1,142 @@ +import { Tree, addProjectConfiguration, writeJson } from '@nx/devkit'; +import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports'; + +import update from './remove-eslint-rules-patch'; +import { readJson } from '@nx/devkit'; + +describe('update-nx-next-dependency', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should remove @next/next/no-html-link-for-pages in json configs', async () => { + tree.write('.eslintrc.json', '{}'); + + addProjectConfiguration(tree, 'my-pkg', { + root: 'packages/my-pkg', + }); + writeJson(tree, `packages/my-pkg/.eslintrc.json`, { + root: true, + ignorePatterns: ['!**/*'], + plugins: ['@nx'], + overrides: [ + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + rules: { + '@next/next/no-html-link-for-pages': ['error', 'apps/lint/pages'], + 'no-console': 'error', + }, + }, + { + files: ['**/*.*'], + rules: { '@next/next/no-html-link-for-pages': 'off' }, + }, + { + files: ['**/*.ts', '**/*.tsx'], + rules: {}, + }, + { + files: ['**/*.js', '**/*.jsx'], + rules: {}, + }, + ], + }); + + await update(tree); + + expect(readJson(tree, `packages/my-pkg/.eslintrc.json`).overrides) + .toMatchInlineSnapshot(` + [ + { + "files": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + ], + "rules": { + "@next/next/no-html-link-for-pages": [ + "error", + "apps/lint/pages", + ], + "no-console": "error", + }, + }, + { + "files": [ + "**/*.ts", + "**/*.tsx", + ], + "rules": {}, + }, + { + "files": [ + "**/*.js", + "**/*.jsx", + ], + "rules": {}, + }, + ] + `); + }); + + it('should remove @next/next/no-html-link-for-pages in flat configs', async () => { + tree.write('eslint.config.js', 'module.exports = []'); + + addProjectConfiguration(tree, 'my-pkg', { + root: 'packages/my-pkg', + }); + tree.write( + `packages/my-pkg/eslint.config.js`, + `const { FlatCompat } = require('@eslint/eslintrc'); + const baseConfig = require('../../eslint.config.js'); + const js = require('@eslint/js'); + + const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + }); + + module.exports = [ + ...baseConfig, + ...compat.extends( + 'plugin:@nx/react-typescript', + 'next', + 'next/core-web-vitals' + ), + { + files: ['**/*.*'], + rules: { '@next/next/no-html-link-for-pages': 'off' }, + }, + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + rules: { + '@next/next/no-html-link-for-pages': ['error', 'apps/lint/pages'], + 'no-console': 'error', + }, + }, + { + files: ['**/*.ts', '**/*.tsx'], + rules: {}, + }, + { + files: ['**/*.js', '**/*.jsx'], + rules: {}, + }, + ...compat.config({ env: { jest: true } }).map((config) => ({ + ...config, + files: ['**/*.spec.ts', '**/*.spec.tsx', '**/*.spec.js', '**/*.spec.jsx'], + })), + { ignores: ['.next/**/*'] }, + ];` + ); + + await update(tree); + + expect( + tree.read(`packages/my-pkg/eslint.config.js`, 'utf-8') + ).not.toContain("'@next/next/no-html-link-for-pages': 'off'"); + }); +}); diff --git a/packages/next/src/migrations/update-17-2-7/remove-eslint-rules-patch.ts b/packages/next/src/migrations/update-17-2-7/remove-eslint-rules-patch.ts new file mode 100644 index 0000000000000..8b9ff201ebd1a --- /dev/null +++ b/packages/next/src/migrations/update-17-2-7/remove-eslint-rules-patch.ts @@ -0,0 +1,18 @@ +import { Tree, formatFiles, getProjects } from '@nx/devkit'; +import { updateOverrideInLintConfig } from '@nx/eslint/src/generators/utils/eslint-file'; + +export default async function update(tree: Tree) { + const projects = getProjects(tree); + projects.forEach((project) => { + updateOverrideInLintConfig( + tree, + project.root, + (o) => + o.rules?.['@next/next/no-html-link-for-pages'] && + o.files?.includes('**/*.*'), + (o) => undefined + ); + }); + + await formatFiles(tree); +}