Skip to content

[heft-lint-plugin] Lint files selected by ESLint flat config - #6006

Draft
Ian Clanton-Thuon (iclanton) wants to merge 5 commits into
microsoft:mainfrom
iclanton:fix/heft-lint-flat-config-files
Draft

Ian Clanton-Thuon (iclanton) wants to merge 5 commits into
microsoft:mainfrom
iclanton:fix/heft-lint-flat-config-files

Conversation

@iclanton

@iclanton Ian Clanton-Thuon (iclanton) commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

The Heft lint plugin currently sends ESLint only the files from the TypeScript program. As a result, files selected exclusively by the ESLint flat config — for example configuration files, tests excluded from tsconfig.json, or custom extensions such as Markdown or JSON — are never linted.

This change uses ESLint's native flat-config enumeration to discover those files and lints them through the plugin's existing cache, fix, diagnostic, and SARIF pipeline with a single ESLint instance.

Note: The @rushstack/eslint-config changes this feature builds on (grouping the type-aware rules and adding the flat/without-type-information helper, plus the TypeScript conversion of that package) landed separately in #6026 and are already published. This PR now depends on that published version.

Details

For ESLint 9, the plugin creates a discovery-only ESLint instance and calls lintFiles() with rules disabled to enumerate the files the flat config selects. TypeScript program roots and primary TypeScript emit directories are excluded from discovery. Extensions introduced by the flat config (Markdown, JSON, JSX, custom languages, .ts files outside the program, etc.) remain discoverable.

A single ESLint instance lints both the program files and the discovered additional files. The injected TypeScript Program is scoped (via a files-limited override) to the program's own files, so additional files fall through to the flat config's own parser instead of failing to resolve against the program. Additional files are run through the existing per-file content/config cache and result reporting, and SARIF metadata is collected from that instance.

When a type-aware rule would apply to a file that is not part of the TypeScript program (typescript-eslint cannot produce type information for it), the plugin reports an actionable error telling the user to either exclude the file from ESLint or lint it with a configuration that does not enable type-aware rules.

ESLint 8 and TSLint behavior is unchanged.

Adopting the pattern in this repo

@rushstack/eslint-config (published via #6026) exposes a flat/without-type-information helper that disables type-aware parsing and rules for a given set of files. This PR wires that helper through the repo's node rigs (decoupled-local-node-rig and local-node-rig, including the rig's own localTypeAwareRules group), and uses it in @rushstack/playwright-browser-tunnel to lint its playwright.config.ts and tests/** files — which are excluded from the TypeScript program — with only the non-type-aware rules. This also surfaces and fixes several previously-unlinted issues in those files (including a broken import in the test fixture).

How it was tested

  • heft build in heft-plugins/heft-lint-plugin (TypeScript + self-lint + API Extractor): passed
  • heft test --clean in build-tests/eslint-9-test: 1 passed, including a non-TypeScript custom-extension SARIF result
  • heft build --clean in apps/playwright-browser-tunnel: passes, linting the Playwright config and test files with only the non-type-aware rules via the rig helper
  • rush rebuild for a decoupled-rig consumer (@rushstack/eslint-plugin): no lint regressions from the rig's type-aware rule split
  • rush change --verify

@github-project-automation github-project-automation Bot moved this to Needs triage in Bug Triage Sep 2, 2026
@iclanton
Ian Clanton-Thuon (iclanton) force-pushed the fix/heft-lint-flat-config-files branch 2 times, most recently from bca4d92 to 54eb97a Compare September 5, 2026 00:05
Comment thread heft-plugins/heft-lint-plugin/package.json Outdated
Comment thread apps/playwright-browser-tunnel/eslint.config.js
Comment thread eslint/eslint-config/flat/profile/_common.js Outdated
Comment thread eslint/eslint-config/flat/without-type-information.js Outdated
Comment thread heft-plugins/heft-lint-plugin/src/Eslint.ts Outdated
Comment thread heft-plugins/heft-lint-plugin/src/Eslint.ts Outdated
Comment thread heft-plugins/heft-lint-plugin/src/Eslint.ts Outdated
Comment thread heft-plugins/heft-lint-plugin/src/LinterBase.ts Outdated
Comment thread heft-plugins/heft-lint-plugin/src/LintPlugin.ts Outdated

const nodeImportResolverPath = require.resolve('eslint-import-resolver-node');

// These localCommonConfig rules require type information (i.e. the TypeScript program). They are grouped

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes can probably all go away when @rushstack/eslint-config is published and pulled back in, right? I'm thinking we just make that its own PR and plan on doing this after the dependency of this project gets bumped.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(COPILOT) Agreed — reverted the localTypeAwareRules split and the rig without-type-information wrappers out of this PR. I'll do them in a follow-up once @rushstack/eslint-config is published and this project's dependency is bumped, so the rig can consume the published typeAwareRules/helper directly. For now @rushstack/playwright-browser-tunnel is fixed inline in its own eslint.config.js.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this cleanup?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(COPILOT) The rig wiring is back now that @rushstack/eslint-config is published: the localTypeAwareRules split plus without-type-information wrappers in decoupled-local-node-rig/local-node-rig. Re: "Can we do this cleanup?" — could you confirm which cleanup you mean (e.g. the // TODO: Put back ... typescriptEslintPlugin at the top of this file)? Happy to take it on.

Comment thread heft-plugins/heft-lint-plugin/src/Eslint.ts Outdated
Use ESLint's native flat-config enumeration to find files outside the TypeScript
program and lint them through the existing cache and reporting pipeline with a
single ESLint instance. When a type-aware rule is applied to a file that is not
part of the TypeScript program, emit actionable guidance to either exclude the
file or lint it with a configuration that does not enable type-aware rules.

Also fix the lint issues this surfaces in @rushstack/playwright-browser-tunnel's
Playwright config and test fixture files.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Now that @rushstack/eslint-config is published with the `flat/without-type-information`
helper, group the rig's own type-aware rules (localCommonConfig) into a
`localTypeAwareRules` set and expose a `without-type-information` helper from
`decoupled-local-node-rig` and `local-node-rig` that disables type-aware parsing
plus both the base profile's and the rig's type-aware rules. Use it in
@rushstack/playwright-browser-tunnel to lint the Playwright config and test files,
which are excluded from the TypeScript program.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread heft-plugins/heft-lint-plugin/src/Eslint.ts Outdated
Comment thread heft-plugins/heft-lint-plugin/src/Eslint.ts Outdated
Comment thread heft-plugins/heft-lint-plugin/src/LinterBase.ts Outdated
Comment thread heft-plugins/heft-lint-plugin/src/LintPlugin.ts Outdated
Simplify the additional-file sort to a default lexicographic sort, note that the
enumerator lints relative to `buildFolderPath` (its `cwd`), convert the type-info
error helper to a loose function that takes the TypeScript file set and build
folder as parameters, and empty the playwright-browser-tunnel change comment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address review feedback:
- Remove the `additionalFiles` option and the extra-file generic from LinterBase;
  the base now exposes a neutral `getExtraSourceFilesToLintAsync` hook (default
  empty) that Eslint overrides to enumerate the files selected by the ESLint
  configuration. The base only deals with a generic `ISourceFileToLint` shape.
- Thread the (project-folder-resolved) TypeScript file set into the enumeration
  as a parameter instead of reading it from a field, resolving the paths at the
  LintPlugin call site.
- Make `ISourceFileToLint.version` optional (omit it for enumerated files) so the
  base computes the version from file contents, instead of passing an empty string.
- Drop the redundant TypeScript-output-folder ignore patterns; emitted JavaScript
  is already excluded by the default-JavaScript-extension filter (and emit folders
  such as `lib-esm` cannot be derived from the compiler options anyway).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of blanket-ignoring ESLint's default JavaScript extensions (which also
prevented authored `.js` files from being linted), discover the TypeScript output
folders and ignore only those when enumerating additional files.

- heft-typescript-plugin: expose `emitFolderPaths` on `IChangedFilesHookOptions`
  (the `outDir`/`declarationDir` plus any `additionalModuleKindsToEmit` folders,
  such as `lib-esm`, which are not part of the compiler options).
- heft-lint-plugin: aggregate those folders from the accessor and ignore them in
  the additional-file enumerator, and remove the default-JavaScript-extension
  filter so that authored `.js` files selected by the ESLint configuration are
  linted. Update the eslint-9-test snapshot, which now lints `eslint.config.js`
  while still excluding the emitted `lib-commonjs`/`lib-esm` output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs triage

Development

Successfully merging this pull request may close these issues.

1 participant