-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Description
Before You File a Proposal Please Confirm You Have Done The Following...
- I have searched for related issues and found none that match my proposal.
- I have searched the current rule list and found no rules that match my proposal.
- I have read the FAQ and my problem is not listed.
Relevant Package
typescript-estree
My proposal is suitable for this project
- I believe my proposal would be useful to the broader TypeScript community (meaning it is not a niche proposal).
Description
Typed linting currently fails for virtual child files produced by processors (e.g. docs/example.md/0.ts, file.mdc/0_0.ts) when using parserOptions.projectService: true, because those virtual paths are not recognized as eligible for the TypeScript default project.
This issue proposes enhancing @typescript-eslint/typescript-estree so that such virtual children nested under a real file can be type-checked via the default project without extra configuration or temp files.
Context & Background
- When using processors such as
@eslint/markdownor similar, TypeScript code blocks are extracted into virtual child paths that do not exist on disk and cannot be added totsconfig.json(e.g.path/docs/example.md/0.ts,path/rules/Example.mdc/0_0.ts). - With
parserOptions.projectService: true, the TypeScript project service currently only understands real filesystem paths; it does not know how to associate these virtual children with the default project. - As a result, typed rules do not run on those code blocks unless users:
- Add fragile
allowDefaultProjectglobs that attempt to match all possible virtual child paths, or - Implement workarounds that materialize temporary
.tsfiles on disk (as explored in eslint/eslint#20378 and related discussions).
- Add fragile
- That behavior pushes complexity into each processor and into end-user configuration, instead of handling the virtual-child concept centrally in
typescript-estree’s project-service integration.
Environment
@typescript-eslint/parser: current releases withparserOptions.projectService: true(exact versions not critical to the behavior)@typescript-eslint/typescript-estree: current releases andmain- Processors: tools that emit virtual TS children, e.g.
@eslint/markdownor equivalent Markdown/MDX/.mdc processors - TypeScript: Not determined; the behavior stems from project service handling of non-existent paths rather than a specific TS version
- OS/Platform: Not determined; behavior appears OS-agnostic (depends on filesystem existence checks and project service)
Affected Versions / Branch
- Affected: Current
@typescript-eslint/parser/typescript-estreewhenparserOptions.projectService: trueis enabled and virtual child paths are produced by a processor - Introduced in: Not determined (likely since project-service integration began handling file eligibility strictly based on real filesystem paths and explicit
allowDefaultProjectglobs)
Steps to Reproduce
- Create a TypeScript project with a standard
tsconfig.jsonthat includes your.tssource files (but not virtual child paths like*.md/0.ts). - Configure ESLint to use
@typescript-eslint/parserwithparserOptions.projectService: true. - Add and configure a processor such as
@eslint/markdown(or similar) that:- Extracts fenced TS code blocks from
.md/.mdx/.mdcfiles, and - Emits virtual filenames such as
docs/example.md/0.tsorfile.mdc/0_0.ts.
- Extracts fenced TS code blocks from
- Run ESLint on a Markdown/MDX/.mdc file containing a fenced
tsortypescriptcode block. - Observe that:
- Typed rules do not run on the extracted code block, and
- The project service reports that the virtual child path was not found.
Expected Behavior
- Virtual processor child paths that are nested under an existing real file on disk (e.g.
docs/example.md/0.tsunderdocs/example.md) should be implicitly eligible to use the TypeScript default project whenprojectServiceis enabled. - Users should not need to:
- Add brittle
allowDefaultProjectglob patterns for every possible virtual child naming scheme, or - Materialize temporary
.tsfiles on disk in processors just to make typed linting work.
- Add brittle
- Real files on disk should continue to respect existing
allowDefaultProjectbehavior and other project-service constraints exactly as today.
Actual Behavior
- Virtual child paths are treated as if they were normal filesystem paths that happen not to exist:
- They cannot be added to
tsconfig.jsonbecause they are not real files. - They are not considered eligible for the default project unless explicitly matched by
allowDefaultProjectglobs.
- They cannot be added to
- As a result, type-aware linting for code extracted from Markdown/MDX/.mdc files fails with errors such as:
<path>/file.mdc/0_0.ts was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProject.
- This blocks or discourages typed linting for documentation code samples and other processor-driven virtual files.
Impact & Severity
- Severity: Major
- Impact:
- Projects that rely on processors for documentation or MDX content cannot easily get type-aware linting on those code blocks with
projectService. - Workarounds (temp files, broad globs) increase configuration complexity and are error-prone (e.g., cleanup, missed patterns).
- The issue affects any ecosystem tooling that emits similar virtual child paths, not just a single processor.
- Projects that rely on processors for documentation or MDX content cannot easily get type-aware linting on those code blocks with
Logs & Evidence
<project>/file.mdc/0_0.ts was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProject.
(Representative error when running ESLint with parserOptions.projectService: true on a Markdown/MDX/.mdc file containing TypeScript code blocks.)
Code References
packages/typescript-estree/src/useProgramFromProjectService.ts- Contains the logic that:
- Resolves
filePathAbsolute/filePathRelative - Checks
allowDefaultProjectglobs - Decides whether the default project is allowed for a given file
- Resolves
- Today, this logic only considers real filesystem paths and explicit allow-listing; it does not have a concept of “virtual child” paths nested under an existing file.
- Contains the logic that:
Proposed Direction / Next Steps
-
Teach
typescript-estree’s project-service integration to recognize virtual child paths and treat them as default-project-eligible when appropriate, for example:- If the exact
filePathAbsoluteexists on disk → treat it as a normal file (current behavior). - If it does not exist, walk up the path toward the filesystem root and:
- If an ancestor path is a real file (e.g.
docs/example.md), treat the original path as a virtual child of that file and implicitly allow the default project. - If no file ancestor is found, keep current behavior (no implicit default-project allowance).
- If an ancestor path is a real file (e.g.
- If the exact
-
Preserve existing semantics for:
- Real files and
allowDefaultProjectglobs, - Non-standard extensions /
extraFileExtensions, - Default-project matching limits and error messaging,
- Reload /
singleRunbehavior.
- Real files and
-
If virtual-path detection fails unexpectedly (e.g. filesystem error), fall back to current non-virtual behavior to stay robust.
I’m very open to alternative heuristics or a different API shape here; the goal is to solve the underlying problem (typed linting for processor-generated virtual children) in a way that fits well with the existing projectService design.
While testing this locally with my fork, I noticed that I had to increase maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING. When a single Markdown file contains several TypeScript code blocks, the current default limit of 8 is reached quite quickly, so this limit (or its recommended configuration) may need special consideration for virtual-children scenarios.
Acceptance Criteria
- A minimal reproduction using a processor that emits virtual TS children and
parserOptions.projectService: trueis documented and confirmed. - Virtual child paths nested under a real file on disk can be type-checked via the default project without additional
allowDefaultProjectconfiguration. - Existing behavior for real files and
allowDefaultProjectremains unchanged and is covered by tests. - Unit tests cover:
- Virtual child paths whose parent exists as a file,
- Non-virtual paths and error-path behavior (including filesystem errors),
- Interaction with
allowDefaultProjectwhen virtual detection is not applicable or fails.
Additional Links
- Related ESLint RFC and discussion around physical filenames and temp files:
- Prior draft implementation for reference (happy to adjust based on discussion):
Suggested Labels (non-binding)
type: enhancementpkg: typescript-estreearea: typed-linting
Additional Info
No response