Repository navigation
Allow content mapper registrations to filter their files with an include glob, inherit default extension from manifest - #65047
Open
Andrew Branch (andrewbranch) wants to merge 8 commits into
Conversation
This was referenced Nov 3, 2026
Use manifest extensions when a registration omits them, while preserving explicit overrides. Carry these defaults through extension contributions, validation, and config schemas. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Narrow mapper claims with tsconfig-style include patterns. Use the winning mapper consistently for discovery, resolution, language services, and output naming, and reject incompatible output mappings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Andrew Branch (andrewbranch)
force-pushed
the
content-mapper-include-glob
branch
from
November 9, 2026 21:23
89e69b4 to
d389ac6
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> # Conflicts: # packages/typescript/src/vscode/extensionApi.ts # packages/vscode-typescript/src/contentMapperContributions.ts # packages/vscode-typescript/test/contentMapperContributions.test.ts # tools/scripts/lsp/generate.mts # tools/scripts/tsc/options-schema.ts # tools/scripts/tsc/options.test.ts # tsc/internal/diagnostics/diagnosticMessages.json
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Declaration emit and resolution use different suffixes for overlapping filtered extensions, preventing declaration-only consumers from resolving imports even when the emitted declaration exists.
1 open issue
4 resolved since last review
2 files not reviewed
tsc/internal/diagnostics/diagnostics_generated.go: Generated filetsc/internal/lsp/lsproto/lsp_generated.go: Generated file
Review effort: Balanced
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Human assessment is needed because mapper inclusion and extension-fallback interactions can break module resolution, while relative-pattern registrations introduce a separate client compatibility risk.
No open issues 🎉
1 resolved since last review
2 files not reviewed
tsc/internal/diagnostics/diagnostics_generated.go: Generated filetsc/internal/lsp/lsproto/lsp_generated.go: Generated file
Review effort: Balanced
Andrew Branch (andrewbranch)
marked this pull request as ready for review
November 10, 2026 00:31
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Fixes #64362
Limitations
Normally content mappers that specify
outputExtensionsparticipate fully inrewriteRelativeImportExtensions. As an example, the Ember content mapper registers"outputExtensions": { ".gts": ".js" }because an external build tool compiles.gtsfiles to.jsoutputs. This controls two behaviors by TypeScript: (1) the declaration file output for aComponent.gtsfile will beComponent.d.ts, to correspond with theComponent.jsfile produced by the external tool; and (2) the build will require--rewriteRelativeImportExtensionsto be enabled so an import ofcan be rewritten as
import Component from "./Component.js"in TypeScript's output. Dynamic imports likealso get routed through a helper that replaces the trailing
.gtswith.js.This logic gets more complicated when a content mapper that uses
outputExtensionsalso uses anincludefilter, because it's harder to tell, in a single-file emit context, whether the file resolved by a given import is governed by a content mapper registration that contributes a module specifier rewrite.Restrictions on imports affected by
rootDirsandmoduleSuffixesThese two options can cause false negative or false positives on whether a module specifier should be rewritten that can only be resolved by probing module resolution, which breaks the rule that module specifiers must be safely rewritable by looking only at configuration and the single file being processed. An single example exercising the ambiguity created by each:
{ "compilerOptions": { "rootDirs": ["src", "types"], "moduleSuffixes": [".ios", ".android", ""], }, "contentMappers": [{ "package": "example-mapper", "extensions": [".foo"], "include": ["src", "**/*.android.foo"], "outputExtensions": { ".foo": ".js" }, }] }To decide whether
"./Component.foo"should be rewritten to"./Component.js", you have to know what file it actually resolved to:src, it should be rewrittentypes, it shouldn't,types/Component.ios.foodoes not exist ANDtypes/Component.android.foodoes existSince this is undecidable in a single-file context, we use the following process to decide whether to issue a diagnostic:
"./Component.foo"fromsrcto besrc/Component.fooinclude—it passes, so assume that rewriting should happenincludeThis process means you can use
moduleSuffixesandrootDirsin combination with an extension-rewriting content mapper, just not in a way where the presence or absence of other files on disk can change the answer of whether a given import resolves to a content-mapped file or not.Restrictions on dynamic imports
Non-literal import calls and require calls get a runtime helper with
rewriteRelativeImportExtensionssuch thatawait import(dir + "./Component.foo")can have its.foosuffix rewritten to.jsat runtime. If a content mapper uses bothoutputExtensionsand aninclude, the include glob would have to be evaluated at runtime inside that helper to determine whether the rewriting should happen. That would be more complex and expensive than what it's worth, so using a dynamic import under these circumstances requires setting--disableDynamicImportRewriting(#65289) to make it explicit that you are opting out of getting the runtime rewriting helper.