Skip to content

fix(linter): improve convert-to-flat-config output fidelity#35330

Open
leosvelperez wants to merge 6 commits intomasterfrom
eslint-convert-to-flat-config-fixes
Open

fix(linter): improve convert-to-flat-config output fidelity#35330
leosvelperez wants to merge 6 commits intomasterfrom
eslint-convert-to-flat-config-fixes

Conversation

@leosvelperez
Copy link
Copy Markdown
Member

Current Behavior

Running @nx/eslint:convert-to-flat-config against a workspace with legacy .eslintrc configs produces flat configs that don't faithfully reflect the source, and leaves stale references to the deleted files behind:

  • Every converted config — root and leaves — gets an unconditional { ignores: ['**/dist', '**/out-tsc'] } block prepended, even when the legacy config never ignored those paths.
  • Leaves whose only compat-looking field was a custom parser (e.g. jsonc-eslint-parser for package.json) get a full @eslint/eslintrc FlatCompat scaffold — FlatCompat import, dirname, fileURLToPath, js, the const compat = new FlatCompat({...}) block — that's never referenced.
  • Rule option values that embedded legacy filenames (most notably @nx/dependency-checks's ignoredFiles) keep pointing at .eslintrc.json / .eslintrc.base.json / .eslintignore after those files are deleted.
  • nx.json gets the new eslint.config.<fmt> entry added to the lint target and production named input, but legacy .eslintrc.json / .eslintignore entries stay in place. Other targetDefaults inputs and namedInputs are never rewritten.
  • project.json files aren't touched at all — every targets[*].inputs or namedInputs[*] that referenced the deleted files stays stale.
  • Configs with extends: '../../.eslintrc' (extensionless — ESLint's JSON-by-convention form) aren't supported: the source file isn't converted, and if a leaf extends one that was converted elsewhere the leaf ends up with ...compat.extends('../../.eslintrc') pointing at nothing.
  • Legacy ignorePatterns entries that started with ! were being dropped wholesale, destroying real un-ignores like ['dist/**', '!dist/keep.js'] that flat config still honors.
  • files / excludedFiles arrays with source-side duplicates (e.g. package.json, ./generators.json, ./executors.json repeated in the same override) are emitted with the duplicates intact.

Expected Behavior

Conversion preserves the semantics and intent of the legacy config, rewrites everything that referred to the deleted files, and drops noise that serves no purpose in flat config:

  • The implicit **/dist / **/out-tsc ignore block is no longer added. If the legacy config didn't ignore those paths, the converted one doesn't either — migration stays faithful to the source. (lint-project still adds them for fresh scaffolding, where there's no source intent to preserve.)
  • Parser-only overrides emit a clean flat entry with a hoisted static parser import and no unused FlatCompat scaffold. Leaf configs shed the dead boilerplate.
  • Rule option values that embed .eslintrc[.base].json or .eslintignore are rewritten to the flat-config equivalent. Accidentally collapsed duplicates inside string arrays are deduped.
  • nx.json is swept generically — every targetDefaults[*].inputs and namedInputs[*] gets legacy filenames rewritten, with dedup so the rewrite doesn't collide with freshly-added entries. { fileset } shapes are handled; non-path shapes (runtime, env, externalDependencies, dependentTasksOutputFiles, named-input refs) are left untouched.
  • Every project's project.json receives the same sweep across targets[*].inputs and namedInputs[*].
  • Extensionless .eslintrc is now a convertible source, and extends paths that point at ../../.eslintrc (or .eslintrc.base) are rewritten to the generated base config and imported as baseConfig.
  • Real negated ignorePatterns like !dist/keep.js survive the conversion; only the legacy **/* / !**/* / node_modules catch-alls are dropped.
  • files / excludedFiles arrays are deduped after glob mapping, so source-side duplicates and glob-normalization collisions collapse.

- Stop emitting the `**/dist`/`**/out-tsc` ignores block when the
  source config didn't include those patterns.
- Rewrite stale `.eslintrc(.base)?.json` refs inside rule option
  values (e.g. `@nx/dependency-checks`'s `ignoredFiles`) to their
  flat-config counterparts so references don't point at deleted files.
- Drop dead `FlatCompat` scaffold emitted for overrides whose only
  compat-looking field was a parser; parsers now use hoisted static
  imports and don't need compat.
- Accept `.eslintrc` (no extension) as a convertible source.
- Dedupe `files`/`excludedFiles` arrays after glob mapping.
- Extract `renameLegacyEslintrcFile` shared by `addExtends` and the
  stale-ref rewriter to collapse duplicated regex logic.
- Extract `preprocessRules` helper — the rename + rewrite pipeline ran
  identically on top-level and override rules.
- Merge `ignorePatterns` filter into a single predicate.
- Collapse `mapFilePaths` `files`/`excludedFiles` arms into a local
  `normalize` helper.
- Use `basename(source) === '.eslintrc'` instead of inline regex.
- Drop dead `if (!rules || typeof rules !== 'object')` guard in
  `renameLegacyWorkspaceRules`; tighten signature.
- Drop `as unknown as T` casts from `rewriteStaleEslintrcRefs` by
  typing it as `unknown -> unknown` and casting once at the boundary.
The converter left references to the deleted `.eslintrc[.base].json`
and `.eslintignore` files scattered across nx.json (targetDefaults
inputs, namedInputs) and project.json (targets inputs, namedInputs),
as well as inside the generated flat config itself (`ignoredFiles`
in rule options referencing `.eslintignore`).

Generalize the rewrite:
- Extend `renameLegacyEslintrcFile` to also handle `.eslintignore`.
- Replace the hardcoded triple in `updateNxJsonConfig` with a sweep
  over every `targetDefaults[*].inputs` and `namedInputs[*]`, still
  ensuring the lint target / production named input carry the new
  `eslint.config.<fmt>` entry.
- Walk every project via `getProjects`, rewriting `targets[*].inputs`
  and `namedInputs[*]`.
- Handle both plain-string inputs and `{ fileset }` shapes; leave
  runtime/env/dependentTasksOutputFiles/externalDependencies alone.
- Dedupe after rewriting so newly-added flat-config entries don't
  collide with legacy entries they replaced.
- Preserve the original `{ fileset }` entry reference in
  `rewriteLegacyInputs` when the fileset doesn't change, so
  `inputsEqual` no longer reports spurious mutations and skips the
  needless `updateProjectConfiguration` write.
- Update `rewriteStaleEslintrcRefs` doc comment to mention `.eslintignore`
  — the helper has been handling it since the input-sweep change.
…ionless .eslintrc support

- Restore master's narrow ignorePatterns filter. The previous version
  dropped every pattern starting with `!`, losing the un-ignore half of
  pairs like `['dist/**', '!dist/keep.js']` that flat config still
  honors. Only the `!**/*` sentinel is now filtered out.
- Extend the legacy-filename regex in `renameLegacyEslintrcFile` and the
  local-extends match in `addExtends` to accept extensionless `.eslintrc`
  (ESLint treats it as JSON by convention). Without this, leaves
  `extends: ['../../.eslintrc']` were rewritten as
  `...compat.extends('../../.eslintrc')` — pointing at a file the
  converter just deleted.
- Replace the "sentinel" terminology with a concrete explanation of
  which `ignorePatterns` entries are dropped and why.
- Tighten `inputsEqual` into a one-liner using `Array.prototype.every`.
- Rename the new test to describe the behavior it guards against
  ("paired with broader ignores") rather than the internals.
@leosvelperez leosvelperez self-assigned this Apr 17, 2026
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 17, 2026

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit eaf432d
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/69e258df79471900076ad5f2
😎 Deploy Preview https://deploy-preview-35330--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 17, 2026

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit eaf432d
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/69e258dffcd029000828a637
😎 Deploy Preview https://deploy-preview-35330--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented Apr 17, 2026

View your CI Pipeline Execution ↗ for commit eaf432d

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 48m 42s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 3s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 17s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 23s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 15s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-17 16:52:29 UTC

@leosvelperez leosvelperez marked this pull request as ready for review April 21, 2026 12:35
@leosvelperez leosvelperez requested a review from a team as a code owner April 21, 2026 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant