diff --git a/packages/typescript/src/vscode/extensionApi.ts b/packages/typescript/src/vscode/extensionApi.ts index 2e2db90b84478..897c996495544 100644 --- a/packages/typescript/src/vscode/extensionApi.ts +++ b/packages/typescript/src/vscode/extensionApi.ts @@ -22,6 +22,7 @@ export interface ContentMapperManifest { readonly cwd?: ContentMapperUri | undefined; readonly compilerOptions?: readonly string[] | undefined; readonly dynamicConfig?: boolean | undefined; + readonly outputExtensions?: Readonly> | undefined; } export interface ContentMapperContribution { diff --git a/packages/vscode-typescript/src/contentMapperContributions.ts b/packages/vscode-typescript/src/contentMapperContributions.ts index c8529d11ee8b2..8b9da5241c85c 100644 --- a/packages/vscode-typescript/src/contentMapperContributions.ts +++ b/packages/vscode-typescript/src/contentMapperContributions.ts @@ -13,6 +13,7 @@ export interface SerializedContentMapperContribution { readonly cwd?: string; readonly compilerOptions?: readonly string[]; readonly dynamicConfig?: boolean; + readonly outputExtensions?: Readonly>; }; }; } @@ -33,6 +34,9 @@ export function serializeContentMapperContributions( exec: [...contribution.inferredProjectContribution.manifest.exec], cwd: contribution.inferredProjectContribution.manifest.cwd?.fsPath, compilerOptions: contribution.inferredProjectContribution.manifest.compilerOptions && [...contribution.inferredProjectContribution.manifest.compilerOptions], + ...(contribution.inferredProjectContribution.manifest.outputExtensions && { + outputExtensions: { ...contribution.inferredProjectContribution.manifest.outputExtensions }, + }), }, }, }); @@ -46,8 +50,11 @@ export function validateContentMapperRegistration(contributorId: string, contrib throw new TypeError("Content mapper contributor ID must not be empty."); } for (const contribution of contributions) { - if (contribution.extensions.length === 0 || contribution.extensions.some(extension => !extension.startsWith(".") || extension.length === 1)) { - throw new TypeError("Content mapper contributions require non-empty extensions beginning with '.'."); + if (contribution.extensions.length === 0 || contribution.extensions.some(extension => !isExtension(extension))) { + throw new TypeError("Content mapper contributions require non-empty extensions beginning with '.' and containing no path separators."); + } + if (contribution.extensions.some(extension => /\.(?:[cm]?[jt]s|[jt]sx|json)$/i.test(extension))) { + throw new TypeError("Content mapper extensions must not end in a built-in extension."); } const inferredProjectContribution = contribution.inferredProjectContribution; if (inferredProjectContribution?.options === null || Array.isArray(inferredProjectContribution?.options) || inferredProjectContribution?.options !== undefined && typeof inferredProjectContribution.options !== "object") { @@ -59,9 +66,16 @@ export function validateContentMapperRegistration(contributorId: string, contrib if (inferredProjectContribution?.manifest.cwd && inferredProjectContribution.manifest.cwd.scheme !== "file") { throw new TypeError("Content mapper contribution cwd must be a file URI."); } + if (inferredProjectContribution?.manifest.outputExtensions && Object.entries(inferredProjectContribution.manifest.outputExtensions).some(([source, output]) => !isExtension(source) || !isExtension(output))) { + throw new TypeError("Content mapper output extensions must be non-empty, begin with '.', and contain no path separators."); + } } } +function isExtension(value: string): boolean { + return value.length > 1 && value.startsWith(".") && !/[\\/]/.test(value); +} + export function documentMatchesContentMapperContributions( document: { readonly uri: { readonly path: string; }; }, registrations: ReadonlyMap, diff --git a/packages/vscode-typescript/test/contentMapperContributions.test.ts b/packages/vscode-typescript/test/contentMapperContributions.test.ts index 60db78fb9d403..57e3771d28765 100644 --- a/packages/vscode-typescript/test/contentMapperContributions.test.ts +++ b/packages/vscode-typescript/test/contentMapperContributions.test.ts @@ -4,6 +4,7 @@ import { type ContentMapperContribution, documentMatchesContentMapperContributions, serializeContentMapperContributions, + validateContentMapperRegistration, } from "../src/contentMapperContributions"; const documentedContribution = { @@ -16,11 +17,46 @@ const documentedContribution = { exec: ["node", "mapper.js"], compilerOptions: ["strict"], dynamicConfig: true, + outputExtensions: { ".vue": ".js" }, }, }, } satisfies ContentMapperContribution; describe("content mapper contributions", { concurrency: true }, () => { + test("rejects path separators in source and output extensions", () => { + for (const extension of [".vue/foo", ".vue\\foo"]) { + assert.throws(() => validateContentMapperRegistration("mapper", [{ extensions: [extension] }]), /path separators/); + for (const outputExtensions of [{ [extension]: ".js" }, { ".vue": extension }]) { + assert.throws(() => + validateContentMapperRegistration("mapper", [{ + ...documentedContribution, + inferredProjectContribution: { + manifest: { ...documentedContribution.inferredProjectContribution.manifest, outputExtensions }, + }, + }]), /path separators/); + } + } + }); + + test("accepts compound extensions with non-reserved suffixes", () => { + assert.doesNotThrow(() => + validateContentMapperRegistration("mapper", [{ + extensions: [".y.z", ".component.tsx.vue"], + }]) + ); + }); + + test("rejects builtin extensions and compound extensions ending in them", () => { + for (const extension of [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json", ".d.ts", ".d.mts", ".d.cts"]) { + for (const source of [extension, extension.toUpperCase(), `.component${extension}`, `.component${extension.toUpperCase()}`]) { + assert.throws(() => + validateContentMapperRegistration("mapper", [{ + extensions: [source], + }]), /built-in extension/); + } + } + }); + test("content mapper extensions match document paths case-insensitively", () => { const registrations = new Map([[ "publisher.extension", @@ -49,6 +85,7 @@ describe("content mapper contributions", { concurrency: true }, () => { cwd: undefined, compilerOptions: ["strict"], dynamicConfig: true, + outputExtensions: { ".vue": ".js" }, }, }, }]); diff --git a/tools/scripts/lsp/generate.mts b/tools/scripts/lsp/generate.mts index 1f160c3403f6d..54e8c16362c0a 100644 --- a/tools/scripts/lsp/generate.mts +++ b/tools/scripts/lsp/generate.mts @@ -459,6 +459,7 @@ const customStructures: Structure[] = [ { name: "cwd", type: { kind: "base", name: "string" }, optional: true, documentation: "Absolute working directory for the mapper process." }, { name: "compilerOptions", type: { kind: "array", element: { kind: "base", name: "string" } }, optional: true, documentation: "Compiler option names forwarded to the mapper." }, { name: "dynamicConfig", type: { kind: "base", name: "boolean" }, optional: true, documentation: "Whether the mapper uses project-scoped dynamic configuration." }, + { name: "outputExtensions", type: { kind: "map", key: { kind: "base", name: "string" }, value: { kind: "base", name: "string" } }, optional: true, documentation: "Default source-to-runtime output extension mappings." }, ], documentation: "Inline content mapper manifest supplied by a contributing extension.", }, diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 72f6ca9c30d82..1843e80d62929 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -8449,6 +8449,9 @@ func (c *Checker) checkImportCallExpression(node *ast.Node) *Type { return c.createPromiseReturnType(node, c.anyType) //nolint:customlint // no arguments to check } specifier := args[0] + if c.compilerOptions.RewriteRelativeImportExtensions.IsTrue() && !ast.IsStringLiteralLike(specifier) { + c.checkExternalEmitHelpers(node, ExternalEmitHelpersRewriteRelativeImportExtension) + } specifierType := c.checkExpressionCached(specifier) var optionsType *Type if len(args) > 1 { @@ -8501,6 +8504,11 @@ func (c *Checker) checkImportCallExpression(node *ast.Node) *Type { * @returns On success, the expression's signature's return type. On failure, anyType. */ func (c *Checker) checkCallExpression(node *ast.Node, checkMode CheckMode) *Type { + if c.compilerOptions.RewriteRelativeImportExtensions.IsTrue() && + ast.IsInJSFile(node) && ast.IsRequireCall(node, false /*requireStringLiteralLikeArgument*/) && + !ast.IsStringLiteralLike(node.Arguments()[0]) { + c.checkExternalEmitHelpers(node, ExternalEmitHelpersRewriteRelativeImportExtension) + } c.checkGrammarTypeArguments(node, node.TypeArgumentList()) signature := c.getResolvedSignature(node, nil /*candidatesOutArray*/, checkMode) if signature == c.resolvingSignature { @@ -15507,6 +15515,12 @@ func (c *Checker) resolveExternalModule( } if errorNode != nil { + rewrites := c.program.ContentMapperExtensionRewrites() + caseSensitivity := c.program.CaseSensitivity() + specifier := tspath.ToModuleSpecifier(moduleReference) + _, mappedSpecifier := specifier.RewriteExtension(rewrites, caseSensitivity) + checkDeclarationRewrite := c.compilerOptions.GetEmitDeclarations() && mappedSpecifier && + tspath.PathIsRelative(moduleReference) && !importingSourceFile.IsDeclarationFile if resolvedModule.ResolvedUsingTsExtension && tspath.IsDeclarationFileName(moduleReference) { if ast.FindAncestor(location, ast.IsEmittableImport) != nil { tsExtension := tspath.TryExtractTSExtension(moduleReference) @@ -15543,12 +15557,18 @@ func (c *Checker) resolveExternalModule( ) } } else if c.compilerOptions.RewriteRelativeImportExtensions.IsTrue() && - location.Flags&ast.NodeFlagsAmbient == 0 && + (location.Flags&ast.NodeFlagsAmbient == 0 || checkDeclarationRewrite) && !tspath.IsDeclarationFileName(moduleReference) && - !ast.IsLiteralImportTypeNode(location) && - !ast.IsPartOfTypeOnlyImportOrExportDeclaration(location) { - shouldRewrite := core.ShouldRewriteModuleSpecifier(moduleReference, c.compilerOptions) - if !resolvedModule.ResolvedUsingTsExtension && shouldRewrite { + (!ast.IsLiteralImportTypeNode(location) && !ast.IsPartOfTypeOnlyImportOrExportDeclaration(location) || + checkDeclarationRewrite) { + shouldRewrite := specifier.ShouldRewriteExtension(rewrites, caseSensitivity) + mappedSource := false + if sourceFile.ContentMapper() != "" { + _, mappedSource = sourceFile.FileName().RewriteExtension(rewrites, caseSensitivity) + } + resolvedUsingRewritableExtension := resolvedModule.ResolvedUsingTsExtension || + resolvedModule.ResolvedUsingExtraExtensions && sourceFile.ContentMapper() != "" && mappedSource + if !resolvedUsingRewritableExtension && shouldRewrite { relativeToSourceFile := resolvedModule.ResolvedFileName.AsString() if relativePath, ok := c.program.CaseSensitivity().RelativePathFromFile( importingSourceFile.FileName(), @@ -15562,13 +15582,18 @@ func (c *Checker) resolveExternalModule( diagnostics.This_relative_import_path_is_unsafe_to_rewrite_because_it_looks_like_a_file_name_but_actually_resolves_to_0, relativeToSourceFile, ) - } else if resolvedModule.ResolvedUsingTsExtension && !shouldRewrite && c.program.SourceFileMayBeEmitted(sourceFile, false) { + } else if resolvedUsingRewritableExtension && !shouldRewrite && (mappedSource || c.program.SourceFileMayBeEmitted(sourceFile, false)) { + extension := tspath.GetAnyExtensionFromPath(moduleReference, nil, tspath.CaseSensitive) + if mappedSource { + rewrite, _ := sourceFile.FileName().GetExtensionRewrite(rewrites, caseSensitivity) + extension = rewrite.Source + } c.error( errorNode, diagnostics.This_import_uses_a_0_extension_to_resolve_to_an_input_TypeScript_file_but_will_not_be_rewritten_during_emit_because_it_is_not_a_relative_path, - tspath.GetAnyExtensionFromPath(moduleReference, nil, tspath.CaseSensitive), + extension, ) - } else if resolvedModule.ResolvedUsingTsExtension && shouldRewrite { + } else if resolvedUsingRewritableExtension && shouldRewrite { if redirect := c.program.GetRedirectForResolution(sourceFile); redirect != nil { ownRootDir := c.program.CommonSourceDirectory() otherRootDir := redirect.CommonSourceDirectory() @@ -29106,6 +29131,10 @@ func (c *Checker) checkExternalEmitHelpers(location *ast.Node, helpers ExternalE if !c.hasSignatureWithArityGreaterThan(symbol, 4) { c.error(location, diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 5) } + } else if helper&ExternalEmitHelpersRewriteRelativeImportExtension != 0 && len(c.program.ContentMapperExtensionRewrites()) > 0 { + if !c.hasSignatureWithArityGreaterThan(symbol, 3) { + c.error(location, diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 4) + } } } } diff --git a/tsc/internal/checker/nodebuilderimpl.go b/tsc/internal/checker/nodebuilderimpl.go index fac1f1414c58a..e1938f377cf14 100644 --- a/tsc/internal/checker/nodebuilderimpl.go +++ b/tsc/internal/checker/nodebuilderimpl.go @@ -1342,9 +1342,22 @@ func (b *NodeBuilderImpl) moduleSpecifierResultForSymbol(result moduleSpecifierR if importAttributesType != nil && b.moduleSpecifierResolvesToSymbol(result.specifier, importAttributesType, symbol) { result.importAttributesType = importAttributesType } + result.specifier = b.rewriteContentMappedModuleSpecifier(result.specifier) return result } +func (b *NodeBuilderImpl) rewriteContentMappedModuleSpecifier(specifier tspath.ModuleSpecifier) tspath.ModuleSpecifier { + if b.ctx.internalFlags&nodebuilder.InternalFlagsRewriteModuleSpecifiers != 0 && b.ch.compilerOptions.RewriteRelativeImportExtensions.IsTrue() { + rewrites := b.ctx.host.ContentMapperExtensionRewrites() + caseSensitivity := b.ctx.host.CaseSensitivity() + if specifier.ShouldRewriteExtension(rewrites, caseSensitivity) { + rewritten, _ := specifier.RewriteExtension(rewrites, caseSensitivity) + return rewritten + } + } + return specifier +} + func (b *NodeBuilderImpl) moduleSpecifierResolvesToSymbol(specifier tspath.ModuleSpecifier, importAttributesType *Type, symbol *ast.Symbol) bool { location := b.ctx.enclosingDeclaration if location == nil && b.ctx.enclosingFile != nil { diff --git a/tsc/internal/checker/nodecopy.go b/tsc/internal/checker/nodecopy.go index 5b2a0739f7612..f0f74cbc3040d 100644 --- a/tsc/internal/checker/nodecopy.go +++ b/tsc/internal/checker/nodecopy.go @@ -7,6 +7,7 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/nodebuilder" "github.com/microsoft/TypeScript/tsc/internal/printer" + "github.com/microsoft/TypeScript/tsc/internal/tspath" ) func (b *NodeBuilderImpl) reuseNode(node *ast.Node) *ast.Node { @@ -270,7 +271,10 @@ func (b *NodeBuilderImpl) getModuleSpecifierOverride(parent *ast.Node, lit *ast. func (b *NodeBuilderImpl) rewriteModuleSpecifier(parent *ast.Node, lit *ast.Node) *ast.Node { newName := b.getModuleSpecifierOverride(parent, lit) if len(newName) == 0 { - return lit + newName = b.rewriteContentMappedModuleSpecifier(tspath.ToModuleSpecifier(lit.Text())).AsString() + if newName == lit.Text() { + return lit + } } res := b.f.NewStringLiteral(newName, ast.TokenFlagsNone) b.e.SetOriginal(res, lit) diff --git a/tsc/internal/compiler/emitHost.go b/tsc/internal/compiler/emitHost.go index 1f199f0c9d7a0..d8309e29bc49a 100644 --- a/tsc/internal/compiler/emitHost.go +++ b/tsc/internal/compiler/emitHost.go @@ -118,6 +118,10 @@ func (host *emitHost) ContentMapperExtensions() []string { return host.program.ContentMapperExtensions() } +func (host *emitHost) ContentMapperExtensionRewrites() []tspath.ExtensionRewrite { + return host.program.ContentMapperExtensionRewrites() +} + func (host *emitHost) CaseSensitivity() tspath.CaseSensitivity { return host.program.CaseSensitivity() } diff --git a/tsc/internal/compiler/emitter.go b/tsc/internal/compiler/emitter.go index 4e3b697a1b17e..6bf810fbe9ba3 100644 --- a/tsc/internal/compiler/emitter.go +++ b/tsc/internal/compiler/emitter.go @@ -127,11 +127,13 @@ func getScriptTransformers(emitContext *printer.EmitContext, host printer.EmitHo } opts := transformers.TransformOptions{ - Context: emitContext, - CompilerOptions: options, - Resolver: referenceResolver, - EmitResolver: emitResolver, - GetEmitModuleFormatOfFile: host.GetEmitModuleFormatOfFile, + Context: emitContext, + CompilerOptions: options, + Resolver: referenceResolver, + EmitResolver: emitResolver, + GetEmitModuleFormatOfFile: host.GetEmitModuleFormatOfFile, + ContentMapperExtensionRewrites: host.ContentMapperExtensionRewrites(), + CaseSensitivity: host.CaseSensitivity(), } // transform TypeScript syntax diff --git a/tsc/internal/compiler/program.go b/tsc/internal/compiler/program.go index 31363f95afc88..b25b1c5411277 100644 --- a/tsc/internal/compiler/program.go +++ b/tsc/internal/compiler/program.go @@ -546,7 +546,11 @@ func (p *Program) GetContentMapper(file *ast.SourceFile) *contentmapper.Mapper { return nil } -func (p *Program) ContentMapperExtensions() []string { return p.opts.Config.ContentMapperExtensions() } +func (p *Program) ContentMapperExtensions() []string { return p.opts.Config.ContentMapperExtensions() } + +func (p *Program) ContentMapperExtensionRewrites() []tspath.ExtensionRewrite { + return p.opts.Config.ContentMapperExtensionRewrites() +} func (p *Program) CommandLine() *tsoptions.ParsedCommandLine { return p.opts.Config } func (p *Program) Host() CompilerHost { return p.hosts.Host } func (p *Program) Tracing() *tracing.Tracing { return p.hosts.Tracing } @@ -1395,13 +1399,19 @@ func (p *Program) verifyCompilerOptions() { } } + externalOutputSourceFiles := core.Filter(p.SourceFiles(), func(sourceFile *ast.SourceFile) bool { + return sourceFile.ContentMapper() != "" && + !p.IsSourceFileFromExternalLibrary(sourceFile) && + p.GetProjectReferenceFromSource(sourceFile.PathKey()) == nil + }) outputpaths.ForEachEmittedFile(p, options, func(emitFileNames *outputpaths.OutputPaths, sourceFile *ast.SourceFile) bool { verifyEmitFilePath(emitFileNames.JsFilePath()) verifyEmitFilePath(emitFileNames.SourceMapFilePath()) verifyEmitFilePath(emitFileNames.DeclarationFilePath()) verifyEmitFilePath(emitFileNames.DeclarationMapPath()) + verifyEmitFilePath(emitFileNames.ExternalOutputFilePath()) return false - }, p.getSourceFilesToEmit(nil, false, false), false) + }, p.getSourceFilesToEmit(nil, false, false), externalOutputSourceFiles, false) verifyEmitFilePath(p.opts.Config.GetBuildInfoFileName()) } } diff --git a/tsc/internal/contentmapper/contentmapper.go b/tsc/internal/contentmapper/contentmapper.go index 7e50dc695c98e..1b4cb33bd3c6a 100644 --- a/tsc/internal/contentmapper/contentmapper.go +++ b/tsc/internal/contentmapper/contentmapper.go @@ -13,6 +13,7 @@ package contentmapper import ( "errors" + "maps" "reflect" "slices" "strings" @@ -30,20 +31,23 @@ var ErrProjectUnavailable = errors.New("content mapper project is unavailable") // Definition is a content mapper as declared in a tsconfig's "contentMappers": the npm package that // implements the mapper and the otherwise unsupported file extensions it registers. type Definition struct { - Package string `json:"package"` - Extensions []string `json:"extensions"` - Options json.Value `json:"options,omitempty"` + Package string `json:"package"` + Extensions []string `json:"extensions"` + // OutputExtensions replaces the manifest defaults, including when the map is empty. + OutputExtensions map[string]string `json:"outputExtensions,omitzero"` + Options json.Value `json:"options,omitempty"` } // Manifest is the content-mapper information read from a package's package.json: its name and version // (which form the mapper's identity), the argv used to run it, and the compiler options it declares it // depends on. type Manifest struct { - Name string - Version string - Exec []string - CompilerOptions []string - DynamicConfig bool + Name string + Version string + Exec []string + CompilerOptions []string + DynamicConfig bool + DefaultOutputExtensions map[string]string } // Mapper is a resolved content mapper: its tsconfig Definition combined with the Manifest resolved from @@ -65,6 +69,22 @@ func IsSupportedVirtualExtension(extension string) bool { return supportedVirtualExtensions.Has(extension) } +func IsValidExtension(extension string) bool { + return len(extension) > 1 && extension[0] == '.' && !strings.ContainsAny(extension, "/\\") +} + +func HasReservedSourceExtension(extension string) bool { + for _, extensions := range tspath.AllSupportedExtensionsWithJson { + for _, nativeExtension := range extensions { + if len(extension) >= len(nativeExtension) && + strings.EqualFold(extension[len(extension)-len(nativeExtension):], nativeExtension) { + return true + } + } + } + return false +} + // DiagnosticName returns the best available user-facing name, including when manifest resolution failed. func (m *Mapper) DiagnosticName() string { switch { @@ -108,6 +128,8 @@ func (m *Mapper) Equals(other *Mapper) bool { return m.Package == other.Package && (m.Extensions == nil) == (other.Extensions == nil) && slices.Equal(m.Extensions, other.Extensions) && + (m.Definition.OutputExtensions == nil) == (other.Definition.OutputExtensions == nil) && + maps.Equal(m.Definition.OutputExtensions, other.Definition.OutputExtensions) && (m.Options == nil) == (other.Options == nil) && slices.Equal(m.Options, other.Options) && m.Name == other.Name && @@ -117,25 +139,40 @@ func (m *Mapper) Equals(other *Mapper) bool { (m.CompilerOptions == nil) == (other.CompilerOptions == nil) && slices.Equal(m.CompilerOptions, other.CompilerOptions) && m.DynamicConfig == other.DynamicConfig && + (m.Manifest.DefaultOutputExtensions == nil) == (other.Manifest.DefaultOutputExtensions == nil) && + maps.Equal(m.Manifest.DefaultOutputExtensions, other.Manifest.DefaultOutputExtensions) && m.PackageDirectory == other.PackageDirectory && m.ContributionID == other.ContributionID } -// TransformIdentity returns a fingerprint of everything besides a file's content that determines the -// output of transforming it with this mapper under the given options: the mapper's identity and the -// values of the compiler options it declared it depends on. Folding it into a cache key means a change to -// the mapper version or a relevant compiler option invalidates cached results. It is a pure function of -// the mapper and options — the declared options come from the manifest, so it never starts the mapper -// process. +// EffectiveOutputExtensions returns the project override when present, otherwise the manifest defaults. +func (m *Mapper) EffectiveOutputExtensions() map[string]string { + if m.Definition.OutputExtensions != nil { + return m.Definition.OutputExtensions + } + return m.Manifest.DefaultOutputExtensions +} + +// TransformIdentity fingerprints the mapper's identity, declared compiler options, and runtime output +// contract so both transformed-file caches and incremental emit are invalidated when these change. +// It is a pure function of the mapper and options and never starts the mapper process. func (m *Mapper) TransformIdentity(options *core.CompilerOptions) xxh3.Uint128 { declared, _ := m.MarshalDeclaredOptions(options) optionsJSON, _ := json.Marshal(declared) - buf := make([]byte, 0, len(m.Identity())+2+len(m.Options)+len(optionsJSON)) + var outputExtensionsJSON []byte + if len(m.EffectiveOutputExtensions()) > 0 { + outputExtensionsJSON, _ = json.Marshal(m.EffectiveOutputExtensions(), json.Deterministic(true)) + } + buf := make([]byte, 0, len(m.Identity())+3+len(m.Options)+len(optionsJSON)+len(outputExtensionsJSON)) buf = append(buf, m.Identity()...) buf = append(buf, 0) buf = append(buf, m.Options...) buf = append(buf, 0) buf = append(buf, optionsJSON...) + if len(outputExtensionsJSON) > 0 { + buf = append(buf, 0) + buf = append(buf, outputExtensionsJSON...) + } return xxh3.Hash128(buf) } diff --git a/tsc/internal/contentmapper/host_test.go b/tsc/internal/contentmapper/host_test.go index 2f48ff6af2126..920936bcdae6f 100644 --- a/tsc/internal/contentmapper/host_test.go +++ b/tsc/internal/contentmapper/host_test.go @@ -27,6 +27,19 @@ import ( // fakeMapper is an in-process mapper that transforms content verbatim and reports one diagnostic. type fakeMapper struct{} +func TestEmptyOutputExtensionsOverrideSurvivesSerialization(t *testing.T) { + t.Parallel() + definition := contentmapper.Definition{Package: "mapper", Extensions: []string{".vue"}, OutputExtensions: map[string]string{}} + data, err := json.Marshal(definition) + assert.NilError(t, err) + var parsed contentmapper.Definition + assert.NilError(t, json.Unmarshal(data, &parsed)) + assert.Assert(t, parsed.OutputExtensions != nil) + mapper := &contentmapper.Mapper{Definition: parsed} + mapper.Manifest.DefaultOutputExtensions = map[string]string{".vue": ".js"} + assert.Equal(t, len(mapper.EffectiveOutputExtensions()), 0) +} + type responseMapper struct { response func(contentmapper.TransformParams) any } diff --git a/tsc/internal/core/core.go b/tsc/internal/core/core.go index afb5ec0039188..f5c296b9b5bb0 100644 --- a/tsc/internal/core/core.go +++ b/tsc/internal/core/core.go @@ -720,10 +720,6 @@ func IndexAfter(s string, pattern string, startIndex int) int { } } -func ShouldRewriteModuleSpecifier(specifier string, compilerOptions *CompilerOptions) bool { - return compilerOptions.RewriteRelativeImportExtensions.IsTrue() && tspath.PathIsRelative(specifier) && !tspath.IsDeclarationFileName(specifier) && tspath.HasTSFileExtension(specifier) -} - func SingleElementSlice[T any](element *T) []*T { if element == nil { return nil diff --git a/tsc/internal/diagnostics/diagnosticMessages.generated.json b/tsc/internal/diagnostics/diagnosticMessages.generated.json index cf66284f21b0a..df01361025699 100644 --- a/tsc/internal/diagnostics/diagnosticMessages.generated.json +++ b/tsc/internal/diagnostics/diagnosticMessages.generated.json @@ -1949,6 +1949,12 @@ "Source_phase_imports_are_only_supported_when_the_module_option_is_set_to_esnext_nodenext_or_preserve_18113": "Source phase imports are only supported when the '--module' option is set to 'esnext', 'nodenext', or 'preserve'.", "Optional_chaining_cannot_be_used_with_import_source_18114": "Optional chaining cannot be used with 'import.source'.", "Source_phase_imports_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls_18115": "Source phase imports are not allowed on statements that compile to CommonJS 'require' calls.", + "Content_mapper_output_extension_0_must_be_non_empty_and_begin_with_a_18116": "Content mapper output extension '{0}' must be non-empty and begin with a '.'.", + "Content_mapper_output_extension_mapping_source_0_is_not_registered_by_this_content_mapper_18117": "Content mapper output extension mapping source '{0}' is not registered by this content mapper.", + "Content_mapper_output_extension_mapping_from_0_to_1_requires_rewriteRelativeImportExtensions_to_be_e_18118": "Content mapper output extension mapping from '{0}' to '{1}' requires 'rewriteRelativeImportExtensions' to be enabled.", + "The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_contains_an_invalid_ma_18119": "The 'typescript.contentMapper.outputExtensions' of the content mapper package '{0}' contains an invalid mapping from '{1}' to '{2}'. Extensions must be non-empty and begin with a '.'.", + "The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_must_be_an_object_with_18120": "The 'typescript.contentMapper.outputExtensions' of the content mapper package '{0}' must be an object with string values.", + "Content_mapper_extension_0_must_not_contain_path_separators_18121": "Content mapper extension '{0}' must not contain path separators.", "nodenext_if_module_is_nodenext_node16_if_module_is_node16_or_node18_otherwise_bundler_69010": "`nodenext` if `module` is `nodenext`; `node16` if `module` is `node16` or `node18`; otherwise, `bundler`.", "File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module_80001": "File is a CommonJS module; it may be converted to an ES module.", "This_constructor_function_may_be_converted_to_a_class_declaration_80002": "This constructor function may be converted to a class declaration.", diff --git a/tsc/internal/diagnostics/diagnosticMessages.json b/tsc/internal/diagnostics/diagnosticMessages.json index be00151a57d6a..50740b177d769 100644 --- a/tsc/internal/diagnostics/diagnosticMessages.json +++ b/tsc/internal/diagnostics/diagnosticMessages.json @@ -8915,5 +8915,29 @@ "Source phase imports are not allowed on statements that compile to CommonJS 'require' calls.": { "category": "Error", "code": 18115 + }, + "Content mapper output extension '{0}' must be non-empty and begin with a '.'.": { + "category": "Error", + "code": 18116 + }, + "Content mapper output extension mapping source '{0}' is not registered by this content mapper.": { + "category": "Error", + "code": 18117 + }, + "Content mapper output extension mapping from '{0}' to '{1}' requires 'rewriteRelativeImportExtensions' to be enabled.": { + "category": "Error", + "code": 18118 + }, + "The 'typescript.contentMapper.outputExtensions' of the content mapper package '{0}' contains an invalid mapping from '{1}' to '{2}'. Extensions must be non-empty and begin with a '.'.": { + "category": "Error", + "code": 18119 + }, + "The 'typescript.contentMapper.outputExtensions' of the content mapper package '{0}' must be an object with string values.": { + "category": "Error", + "code": 18120 + }, + "Content mapper extension '{0}' must not contain path separators.": { + "category": "Error", + "code": 18121 } } diff --git a/tsc/internal/diagnostics/diagnostics_generated.go b/tsc/internal/diagnostics/diagnostics_generated.go index 8a28f92aa3f1f..82ffdbdb7551a 100644 --- a/tsc/internal/diagnostics/diagnostics_generated.go +++ b/tsc/internal/diagnostics/diagnostics_generated.go @@ -3902,6 +3902,18 @@ var Optional_chaining_cannot_be_used_with_import_source = &Message{code: 18114, var Source_phase_imports_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls = &Message{code: 18115, category: CategoryError, key: "Source_phase_imports_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls_18115", text: "Source phase imports are not allowed on statements that compile to CommonJS 'require' calls."} +var Content_mapper_output_extension_0_must_be_non_empty_and_begin_with_a = &Message{code: 18116, category: CategoryError, key: "Content_mapper_output_extension_0_must_be_non_empty_and_begin_with_a_18116", text: "Content mapper output extension '{0}' must be non-empty and begin with a '.'."} + +var Content_mapper_output_extension_mapping_source_0_is_not_registered_by_this_content_mapper = &Message{code: 18117, category: CategoryError, key: "Content_mapper_output_extension_mapping_source_0_is_not_registered_by_this_content_mapper_18117", text: "Content mapper output extension mapping source '{0}' is not registered by this content mapper."} + +var Content_mapper_output_extension_mapping_from_0_to_1_requires_rewriteRelativeImportExtensions_to_be_enabled = &Message{code: 18118, category: CategoryError, key: "Content_mapper_output_extension_mapping_from_0_to_1_requires_rewriteRelativeImportExtensions_to_be_e_18118", text: "Content mapper output extension mapping from '{0}' to '{1}' requires 'rewriteRelativeImportExtensions' to be enabled."} + +var The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_contains_an_invalid_mapping_from_1_to_2_Extensions_must_be_non_empty_and_begin_with_a = &Message{code: 18119, category: CategoryError, key: "The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_contains_an_invalid_ma_18119", text: "The 'typescript.contentMapper.outputExtensions' of the content mapper package '{0}' contains an invalid mapping from '{1}' to '{2}'. Extensions must be non-empty and begin with a '.'."} + +var The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_must_be_an_object_with_string_values = &Message{code: 18120, category: CategoryError, key: "The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_must_be_an_object_with_18120", text: "The 'typescript.contentMapper.outputExtensions' of the content mapper package '{0}' must be an object with string values."} + +var Content_mapper_extension_0_must_not_contain_path_separators = &Message{code: 18121, category: CategoryError, key: "Content_mapper_extension_0_must_not_contain_path_separators_18121", text: "Content mapper extension '{0}' must not contain path separators."} + var X_nodenext_if_module_is_nodenext_node16_if_module_is_node16_or_node18_otherwise_bundler = &Message{code: 69010, category: CategoryMessage, key: "nodenext_if_module_is_nodenext_node16_if_module_is_node16_or_node18_otherwise_bundler_69010", text: "`nodenext` if `module` is `nodenext`; `node16` if `module` is `node16` or `node18`; otherwise, `bundler`."} var File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module = &Message{code: 80001, category: CategorySuggestion, key: "File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module_80001", text: "File is a CommonJS module; it may be converted to an ES module."} @@ -6393,6 +6405,12 @@ var allMessages = [...]**Message{ &Source_phase_imports_are_only_supported_when_the_module_option_is_set_to_esnext_nodenext_or_preserve, &Optional_chaining_cannot_be_used_with_import_source, &Source_phase_imports_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls, + &Content_mapper_output_extension_0_must_be_non_empty_and_begin_with_a, + &Content_mapper_output_extension_mapping_source_0_is_not_registered_by_this_content_mapper, + &Content_mapper_output_extension_mapping_from_0_to_1_requires_rewriteRelativeImportExtensions_to_be_enabled, + &The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_contains_an_invalid_mapping_from_1_to_2_Extensions_must_be_non_empty_and_begin_with_a, + &The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_must_be_an_object_with_string_values, + &Content_mapper_extension_0_must_not_contain_path_separators, &X_nodenext_if_module_is_nodenext_node16_if_module_is_node16_or_node18_otherwise_bundler, &File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module, &This_constructor_function_may_be_converted_to_a_class_declaration, diff --git a/tsc/internal/execute/incremental/buildinfo_contentmapper_test.go b/tsc/internal/execute/incremental/buildinfo_contentmapper_test.go index b18e51059fcb1..ca487741cbe76 100644 --- a/tsc/internal/execute/incremental/buildinfo_contentmapper_test.go +++ b/tsc/internal/execute/incremental/buildinfo_contentmapper_test.go @@ -45,6 +45,25 @@ func TestStaticContentMapperTransformIdentity(t *testing.T) { assert.Assert(t, optionsA.TransformIdentity(&core.CompilerOptions{}) != optionsB.TransformIdentity(&core.CompilerOptions{})) } +func TestContentMapperOutputExtensionsAffectIdentity(t *testing.T) { + t.Parallel() + mapper := &contentmapper.Mapper{Name: "mapper", Version: "1.0.0", Extensions: []string{".astro"}} + mapper.Manifest.DefaultOutputExtensions = map[string]string{".astro": ".js"} + jsIdentity := mapper.TransformIdentity(&core.CompilerOptions{}) + mapper.Manifest.DefaultOutputExtensions[".astro"] = ".jsx" + jsxIdentity := mapper.TransformIdentity(&core.CompilerOptions{}) + assert.Assert(t, jsIdentity != jsxIdentity) + mapper.Definition.OutputExtensions = map[string]string{".astro": ".js"} + assert.Equal(t, mapper.TransformIdentity(&core.CompilerOptions{}), jsIdentity) + mapper.Definition.OutputExtensions[".astro"] = ".jsx" + assert.Equal(t, mapper.TransformIdentity(&core.CompilerOptions{}), jsxIdentity) + mapper.Definition.OutputExtensions = map[string]string{".astro": ".js", ".vue": ".mjs", ".svelte": ".cjs"} + identity := mapper.TransformIdentity(&core.CompilerOptions{}) + for range 100 { + assert.Equal(t, mapper.TransformIdentity(&core.CompilerOptions{}), identity) + } +} + type fakeBuildInfoReader struct { buildInfo *incremental.BuildInfo } diff --git a/tsc/internal/execute/tsctests/contentmapper_watch_test.go b/tsc/internal/execute/tsctests/contentmapper_watch_test.go index bfbbb739cc7a2..14257005a5139 100644 --- a/tsc/internal/execute/tsctests/contentmapper_watch_test.go +++ b/tsc/internal/execute/tsctests/contentmapper_watch_test.go @@ -14,6 +14,7 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/execute/tsc" "github.com/microsoft/TypeScript/tsc/internal/fswatch" "github.com/microsoft/TypeScript/tsc/internal/testutil/contentmappertest" + "github.com/microsoft/TypeScript/tsc/internal/tspath" "github.com/microsoft/TypeScript/tsc/internal/vfs/vfstest" "gotest.tools/v3/assert" ) @@ -82,6 +83,71 @@ func TestContentMapperBuildLifecycle(t *testing.T) { assert.Equal(t, spawner.closes.Load(), int32(1)) } +func TestContentMapperOutputExtensionsIncrementalEmit(t *testing.T) { + t.Parallel() + for _, build := range []bool{false, true} { + for _, override := range []bool{false, true} { + name := "incremental manifest" + if build { + name = "build manifest" + } + if override { + name = strings.Replace(name, "manifest", "override", 1) + } + t.Run(name, func(t *testing.T) { + t.Parallel() + const root = "/home/src/workspaces/project/" + config := `{ + "compilerOptions": { + "composite": true, "module": "preserve", + "rewriteRelativeImportExtensions": true, "outDir": "dist" + }, + "contentMappers": [{ "package": "mapper", "extensions": [".vue"]` + if override { + config += `, "outputExtensions": {".vue": ".js"}` + } + config += `}] + }` + manifest := strings.Replace(contentmappertest.PackageJSON(contentmappertest.VerbatimMapper), + `"exec":`, `"outputExtensions": {".vue": ".js"}, "exec":`, 1) + input := &tscInput{files: FileMap{ + root + "tsconfig.json": config, + root + "app.vue": `export const app = 1;`, + root + "main.ts": `export { app } from "./app.vue";`, + root + "node_modules/mapper/package.json": manifest, + }} + testSys := newTestSys(input, false) + sys := &recordingContentMapperSystem{ + TestSys: testSys, + spawner: &recordingContentMapperSpawner{inner: contentmappertest.NewSpawner()}, + } + args := []string{"--pretty", "false", "--runExternalCode"} + if build { + args = append([]string{"--build"}, args...) + } + run := func(extension string) { + t.Helper() + result := execute.CommandLine(t.Context(), sys, args, testSys) + assert.Equal(t, result.Status, tsc.ExitStatusSuccess, testSys.currentWrite.String()) + for _, file := range []string{"main.js", "main.d.ts"} { + text, ok := testSys.FS().ReadFile(tspath.RootedFilePathFromNormalized(root + "dist/" + file)) + assert.Assert(t, ok) + assert.Assert(t, strings.Contains(text, `"./app`+extension+`"`), text) + } + } + run(".js") + testSys.clearOutput() + if override { + testSys.writeFileNoError(root+"tsconfig.json", strings.Replace(config, `".js"`, `".jsx"`, 1)) + } else { + testSys.writeFileNoError(root+"node_modules/mapper/package.json", strings.Replace(manifest, `".js"`, `".jsx"`, 1)) + } + run(".jsx") + }) + } + } +} + func TestContentMapperSupplementalDiagnosticUsesOriginalFileName(t *testing.T) { t.Parallel() input := &tscInput{files: FileMap{ diff --git a/tsc/internal/fourslash/tests/contentMapperOutputExtensions_test.go b/tsc/internal/fourslash/tests/contentMapperOutputExtensions_test.go new file mode 100644 index 0000000000000..bc17e33790007 --- /dev/null +++ b/tsc/internal/fourslash/tests/contentMapperOutputExtensions_test.go @@ -0,0 +1,110 @@ +package fourslash_test + +import ( + "strings" + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/core" + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + . "github.com/microsoft/TypeScript/tsc/internal/fourslash/tests/util" + "github.com/microsoft/TypeScript/tsc/internal/ls/lsutil" + "github.com/microsoft/TypeScript/tsc/internal/testutil" + "github.com/microsoft/TypeScript/tsc/internal/testutil/contentmappertest" +) + +func TestContentMapperOutputExtensionsAutoImportsUseSource(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + // The verbatim mapper exposes the original TypeScript with identity span mappings. + content := `// @Filename: /tsconfig.json +{ + "compilerOptions": { + "module": "preserve", "rewriteRelativeImportExtensions": true + }, + "contentMappers": [{ + "package": "mapper", "extensions": [".vue"], + "outputExtensions": {".vue": ".js"} + }] +} +// @Filename: /node_modules/mapper/package.json +` + contentmappertest.PackageJSON(contentmappertest.VerbatimMapper) + ` +// @Filename: /Component.vue +export const component = 1; +// @Filename: /main.ts +compon/**/ +` + f, done := fourslash.NewFourslashWithOptions(t, content, &fourslash.FourslashOptions{ + ContentMapperSpawner: contentmappertest.NewSpawner(), RunExternalCode: true, + }) + defer done() + f.VerifyCompletions(t, "", &fourslash.CompletionsExpectedList{ + UserPreferences: &lsutil.UserPreferences{ + IncludeCompletionsForModuleExports: core.TSTrue, + IncludeCompletionsForImportStatements: core.TSTrue, + }, + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{Includes: []fourslash.CompletionsExpectedItem{"component"}}, + }) + f.VerifyApplyCodeActionFromCompletion(t, new(""), &fourslash.ApplyCodeActionFromCompletionOptions{ + Name: "component", Source: "./Component.vue", + Description: "Add import from \"./Component.vue\"", + NewFileContent: new(`import { component } from "./Component.vue"; + +compon +`), + }) +} + +func TestContentMapperOutputExtensionsProjectReferences(t *testing.T) { + t.Parallel() + for _, test := range []struct { + name string + outDir string + }{ + {"contentMapperOutputExtensionsProjectReferencesAligned", "/dist/app"}, + {"contentMapperOutputExtensionsProjectReferencesMisaligned", "/app/dist"}, + } { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + // Both mapped files expose their TypeScript unchanged with identity span mappings. + content := `// @Filename: /node_modules/mapper/package.json +` + contentmappertest.PackageJSON(contentmappertest.VerbatimMapper) + ` +// @Filename: /lib/tsconfig.json +{ + "compilerOptions": { + "composite": true, "module": "preserve", "rootDir": ".", "outDir": "/dist/lib", + "rewriteRelativeImportExtensions": true + }, + "contentMappers": [{ + "package": "mapper", "extensions": [".vue"], "outputExtensions": {".vue": ".js"} + }] +} +// @Filename: /lib/Component.vue +export const component = 1; +// @Filename: /app/tsconfig.json +{ + "compilerOptions": { + "composite": true, "module": "preserve", "rootDir": ".", "outDir": "$outDir", + "rewriteRelativeImportExtensions": true + }, + "references": [{"path": "../lib"}], + "contentMappers": [{ + "package": "mapper", "extensions": [".vue"], "outputExtensions": {".vue": ".js"} + }] +} +// @Filename: /app/main.ts +import { component } from "../lib/Component.vue"; +export const value = component; +` + content = strings.ReplaceAll(content, "$outDir", test.outDir) + f, done := fourslash.NewFourslashWithOptions(t, content, &fourslash.FourslashOptions{ + ContentMapperSpawner: contentmappertest.NewSpawner(), RunExternalCode: true, + }) + defer done() + f.VerifyBaselineNonSuggestionDiagnostics(t) + }) + } +} diff --git a/tsc/internal/ls/autoimport/aliasresolver.go b/tsc/internal/ls/autoimport/aliasresolver.go index f34db1590fc0e..e45dee48efbf1 100644 --- a/tsc/internal/ls/autoimport/aliasresolver.go +++ b/tsc/internal/ls/autoimport/aliasresolver.go @@ -166,6 +166,10 @@ func (r *aliasResolver) ContentMapperExtensions() []string { return nil } +func (r *aliasResolver) ContentMapperExtensionRewrites() []tspath.ExtensionRewrite { + return nil +} + // FileExists implements checker.Program. func (r *aliasResolver) FileExists(fileName tspath.RootedFilePath) bool { panic("unimplemented") diff --git a/tsc/internal/lsp/lsproto/lsp_generated.go b/tsc/internal/lsp/lsproto/lsp_generated.go index 8e86bcc88a333..92024e5221bb3 100644 --- a/tsc/internal/lsp/lsproto/lsp_generated.go +++ b/tsc/internal/lsp/lsproto/lsp_generated.go @@ -9129,6 +9129,9 @@ type ContentMapperManifest struct { // Whether the mapper uses project-scoped dynamic configuration. DynamicConfig *bool `json:"dynamicConfig,omitzero"` + + // Default source-to-runtime output extension mappings. + OutputExtensions *map[string]string `json:"outputExtensions,omitzero"` } var _ json.UnmarshalerFrom = (*ContentMapperManifest)(nil) diff --git a/tsc/internal/lsp/server.go b/tsc/internal/lsp/server.go index dc90637966627..85cdadd7fa803 100644 --- a/tsc/internal/lsp/server.go +++ b/tsc/internal/lsp/server.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "iter" + "maps" "math/rand/v2" "runtime/debug" "slices" @@ -2561,6 +2562,11 @@ func parseContentMapperContributions(values []*lsproto.ContentMapperContribution return result, fmt.Errorf("content mapper contribution %q requests unknown compiler option %q", identity, option) } } + for source, output := range valueOrZero(manifest.OutputExtensions) { + if !contentmapper.IsValidExtension(source) || !contentmapper.IsValidExtension(output) { + return result, fmt.Errorf("content mapper contribution %q has invalid output extension mapping from %q to %q", identity, source, output) + } + } for _, extension := range validExtensions { if !claimedExtensions.AddIfAbsent(strings.ToLower(extension)) { return result, fmt.Errorf("content mapper contributions both claim extension %q", extension) @@ -2584,6 +2590,7 @@ func parseContentMapperContributions(values []*lsproto.ContentMapperContribution DynamicConfig: valueOrZero(manifest.DynamicConfig), ContributionID: identity, } + mapper.Manifest.DefaultOutputExtensions = maps.Clone(valueOrZero(manifest.OutputExtensions)) if manifest.Cwd != nil { if !tspath.PathIsAbsolute(*manifest.Cwd) { return result, fmt.Errorf("content mapper contribution %q has non-absolute cwd", identity) @@ -2597,12 +2604,10 @@ func parseContentMapperContributions(values []*lsproto.ContentMapperContribution } func isValidContributedContentMapperExtension(extension string) bool { - if len(extension) <= 1 || extension[0] != '.' || tspath.GetAnyExtensionFromPath("file"+extension, nil, tspath.CaseSensitive) != extension { + if !contentmapper.IsValidExtension(extension) { return false } - return !slices.ContainsFunc(core.Flatten(tspath.AllSupportedExtensionsWithJson), func(nativeExtension string) bool { - return strings.EqualFold(nativeExtension, extension) - }) + return !contentmapper.HasReservedSourceExtension(extension) } func valueOrZero[T any](value *T) T { diff --git a/tsc/internal/lsp/server_contentmapper_internal_test.go b/tsc/internal/lsp/server_contentmapper_internal_test.go index 1a39bd7167d39..b93da33d73303 100644 --- a/tsc/internal/lsp/server_contentmapper_internal_test.go +++ b/tsc/internal/lsp/server_contentmapper_internal_test.go @@ -4,7 +4,9 @@ import ( "strings" "testing" + "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" + "github.com/microsoft/TypeScript/tsc/internal/tspath" "gotest.tools/v3/assert" ) @@ -26,6 +28,7 @@ func TestParseContentMapperContributions(t *testing.T) { version := "2.3.4" cwd := "/workspace/mapper" compilerOptions := []string{"strict"} + outputExtensions := map[string]string{".vue": ".js"} options := map[string]any{"mode": "embedded"} contributions, err := parseContentMapperContributions([]*lsproto.ContentMapperContribution{ { @@ -34,11 +37,12 @@ func TestParseContentMapperContributions(t *testing.T) { InferredProjectContribution: &lsproto.InferredProjectContentMapperContribution{ Options: &options, Manifest: &lsproto.ContentMapperManifest{ - Name: "Vue mapper", - Version: &version, - Exec: []string{"node", "mapper.js"}, - Cwd: &cwd, - CompilerOptions: &compilerOptions, + Name: "Vue mapper", + Version: &version, + Exec: []string{"node", "mapper.js"}, + Cwd: &cwd, + CompilerOptions: &compilerOptions, + OutputExtensions: &outputExtensions, }, }, }, @@ -53,6 +57,7 @@ func TestParseContentMapperContributions(t *testing.T) { mapper := contributions.Mappers[0] assert.Equal(t, mapper.Identity(), "publisher.extension[0] (Vue mapper@2.3.4)") assert.Equal(t, mapper.PackageDirectory.AsString(), cwd) + assert.DeepEqual(t, mapper.Manifest.DefaultOutputExtensions, map[string]string{".vue": ".js"}) assert.Equal(t, string(mapper.Definition.Options), `{"mode":"embedded"}`) } @@ -98,3 +103,63 @@ func TestParseContentMapperContributionsDefaultsOptionsToObject(t *testing.T) { assert.NilError(t, err) assert.Equal(t, string(contributions.Mappers[0].Definition.Options), `{}`) } + +func TestParseContentMapperContributionsCompoundExtensions(t *testing.T) { + t.Parallel() + for _, extension := range []string{".y.z", ".component.tsx.vue"} { + contributions, err := parseContentMapperContributions([]*lsproto.ContentMapperContribution{{ + ContributorId: "mapper", + Extensions: []string{extension}, + InferredProjectContribution: &lsproto.InferredProjectContentMapperContribution{ + Manifest: &lsproto.ContentMapperManifest{Name: "mapper", Exec: []string{"mapper"}}, + }, + }}) + assert.NilError(t, err) + assert.DeepEqual(t, contributions.Extensions, []string{extension}) + } + for _, nativeExtension := range core.Flatten(tspath.AllSupportedExtensionsWithJson) { + for _, extension := range []string{".component" + nativeExtension, ".component" + strings.ToUpper(nativeExtension)} { + _, err := parseContentMapperContributions([]*lsproto.ContentMapperContribution{{ + ContributorId: "mapper", Extensions: []string{extension}, + }}) + assert.ErrorContains(t, err, "invalid extension") + } + } +} + +func TestParseContentMapperContributionsRejectsInvalidOutputExtensions(t *testing.T) { + t.Parallel() + for _, compilerOptions := range []*[]string{nil, new([]string{}), new([]string{"strict"})} { + outputExtensions := map[string]string{".vue": "js"} + _, err := parseContentMapperContributions([]*lsproto.ContentMapperContribution{{ + ContributorId: "publisher.extension", + Extensions: []string{".vue"}, + InferredProjectContribution: &lsproto.InferredProjectContentMapperContribution{ + Manifest: &lsproto.ContentMapperManifest{ + Name: "mapper", Exec: []string{"mapper"}, + CompilerOptions: compilerOptions, OutputExtensions: &outputExtensions, + }, + }, + }}) + assert.ErrorContains(t, err, `invalid output extension mapping`) + } +} + +func TestParseContentMapperContributionsRejectsExtensionPaths(t *testing.T) { + t.Parallel() + for _, extension := range []string{".vue/foo", `.vue\foo`} { + _, err := parseContentMapperContributions([]*lsproto.ContentMapperContribution{{ + ContributorId: "mapper", Extensions: []string{extension}, + }}) + assert.ErrorContains(t, err, "invalid extension") + for _, outputExtensions := range []map[string]string{{extension: ".js"}, {".vue": extension}} { + _, err := parseContentMapperContributions([]*lsproto.ContentMapperContribution{{ + ContributorId: "mapper", Extensions: []string{".vue"}, + InferredProjectContribution: &lsproto.InferredProjectContentMapperContribution{ + Manifest: &lsproto.ContentMapperManifest{Name: "mapper", Exec: []string{"mapper"}, OutputExtensions: &outputExtensions}, + }, + }}) + assert.ErrorContains(t, err, "invalid output extension mapping") + } + } +} diff --git a/tsc/internal/modulespecifiers/specifiers_test.go b/tsc/internal/modulespecifiers/specifiers_test.go index 3bb7fb7853f01..920ca2c3c71dc 100644 --- a/tsc/internal/modulespecifiers/specifiers_test.go +++ b/tsc/internal/modulespecifiers/specifiers_test.go @@ -132,6 +132,10 @@ func (h *mockModuleSpecifierGenerationHost) ContentMapperExtensions() []string { return h.contentMapperExtensions } +func (h *mockModuleSpecifierGenerationHost) ContentMapperExtensionRewrites() []tspath.ExtensionRewrite { + return nil +} + func (h *mockModuleSpecifierGenerationHost) GetProjectReferenceFromSource(path tspath.PathKey) *tsoptions.SourceOutputAndProjectReference { return nil } diff --git a/tsc/internal/modulespecifiers/types.go b/tsc/internal/modulespecifiers/types.go index a58549cbc358a..20377ea5a7bf1 100644 --- a/tsc/internal/modulespecifiers/types.go +++ b/tsc/internal/modulespecifiers/types.go @@ -51,6 +51,7 @@ type ModuleSpecifierGenerationHost interface { // GetFileIncludeReasons() any // !!! TODO: adapt new resolution cache model CommonSourceDirectory() tspath.RootedDirectoryPath ContentMapperExtensions() []string + ContentMapperExtensionRewrites() []tspath.ExtensionRewrite GetGlobalTypingsCacheLocation() tspath.RootedDirectoryPath CaseSensitivity() tspath.CaseSensitivity BaseDirectory() tspath.RootedDirectoryPath diff --git a/tsc/internal/nodebuilder/types.go b/tsc/internal/nodebuilder/types.go index 92f3d36d1144f..a3f6b928101ef 100644 --- a/tsc/internal/nodebuilder/types.go +++ b/tsc/internal/nodebuilder/types.go @@ -75,4 +75,5 @@ const ( InternalFlagsNoSyntacticPrinter InternalFlags = 1 << 1 InternalFlagsDoNotIncludeSymbolChain InternalFlags = 1 << 2 InternalFlagsAllowUnresolvedNames InternalFlags = 1 << 3 + InternalFlagsRewriteModuleSpecifiers InternalFlags = 1 << 4 ) diff --git a/tsc/internal/outputpaths/outputpaths.go b/tsc/internal/outputpaths/outputpaths.go index 91170f4599201..a68837c1c36bb 100644 --- a/tsc/internal/outputpaths/outputpaths.go +++ b/tsc/internal/outputpaths/outputpaths.go @@ -9,14 +9,16 @@ import ( type OutputPathsHost interface { CommonSourceDirectory() tspath.RootedDirectoryPath ContentMapperExtensions() []string + ContentMapperExtensionRewrites() []tspath.ExtensionRewrite CaseSensitivity() tspath.CaseSensitivity } type OutputPaths struct { - jsFilePath tspath.RootedFilePath - sourceMapFilePath tspath.RootedFilePath - declarationFilePath tspath.RootedFilePath - declarationMapPath tspath.RootedFilePath + jsFilePath tspath.RootedFilePath + sourceMapFilePath tspath.RootedFilePath + declarationFilePath tspath.RootedFilePath + declarationMapPath tspath.RootedFilePath + externalOutputFilePath tspath.RootedFilePath } // DeclarationFilePath implements declarations.OutputPaths. @@ -37,6 +39,10 @@ func (o *OutputPaths) DeclarationMapPath() tspath.RootedFilePath { return o.declarationMapPath } +func (o *OutputPaths) ExternalOutputFilePath() tspath.RootedFilePath { + return o.externalOutputFilePath +} + type ForceEmitPaths struct { Dts bool Js bool @@ -51,6 +57,9 @@ func GetOutputPathsFor(sourceFile *ast.SourceFile, options *core.CompilerOptions isJsonEmittedToSameLocation := isJsonFile && host.CaseSensitivity().CompareFilePaths(fileName, ownOutputFilePath) == 0 paths := &OutputPaths{} + if sourceFile.ContentMapper() != "" { + paths.externalOutputFilePath = GetExternalOutputFileName(sourceFile.FileName(), options, host) + } if sourceFile.ContentMapper() == "" && (force.Js || options.EmitDeclarationOnly != core.TSTrue) && !isJsonEmittedToSameLocation { paths.jsFilePath = ownOutputFilePath if !ast.IsJsonSourceFile(sourceFile) { @@ -66,12 +75,30 @@ func GetOutputPathsFor(sourceFile *ast.SourceFile, options *core.CompilerOptions return paths } -func ForEachEmittedFile(host OutputPathsHost, options *core.CompilerOptions, action func(emitFileNames *OutputPaths, sourceFile *ast.SourceFile) bool, sourceFiles []*ast.SourceFile, forceDtsEmit bool) bool { +func ForEachEmittedFile( + host OutputPathsHost, + options *core.CompilerOptions, + action func(emitFileNames *OutputPaths, sourceFile *ast.SourceFile) bool, + sourceFiles []*ast.SourceFile, + externalOutputSourceFiles []*ast.SourceFile, + forceDtsEmit bool, +) bool { + seen := make(map[*ast.SourceFile]struct{}, len(sourceFiles)) for _, sourceFile := range sourceFiles { + seen[sourceFile] = struct{}{} if action(GetOutputPathsFor(sourceFile, options, host, ForceEmitPaths{Dts: forceDtsEmit}), sourceFile) { return true } } + for _, sourceFile := range externalOutputSourceFiles { + if _, ok := seen[sourceFile]; ok { + continue + } + paths := &OutputPaths{externalOutputFilePath: GetExternalOutputFileName(sourceFile.FileName(), options, host)} + if action(paths, sourceFile) { + return true + } + } return false } @@ -106,6 +133,15 @@ func GetOutputDeclarationFileNameWorker(inputFileName tspath.RootedFilePath, opt return ChangeToDeclarationExtension(path, host) } +func GetExternalOutputFileName(inputFileName tspath.RootedFilePath, options *core.CompilerOptions, host OutputPathsHost) tspath.RootedFilePath { + outputPath := getOutputFileNameWithoutChangingExtension(inputFileName, options.OutDir, host) + rewritten, ok := outputPath.RewriteExtension(host.ContentMapperExtensionRewrites(), host.CaseSensitivity()) + if !ok { + return "" + } + return rewritten +} + func GetOutputExtension(fileName string, jsx core.JsxEmit) string { switch { case tspath.FileExtensionIs(fileName, tspath.ExtensionJson): @@ -148,9 +184,16 @@ func getDeclarationEmitOutputFilePathForFileName(file tspath.RootedFilePath, opt } func ChangeToDeclarationExtension(path tspath.RootedFilePath, host OutputPathsHost) tspath.RootedFilePath { + if rewritten, ok := path.RewriteExtension(host.ContentMapperExtensionRewrites(), host.CaseSensitivity()); ok { + return changeToDeclarationExtension(rewritten) + } if extension := path.LongestExtension(host.ContentMapperExtensions(), tspath.CaseSensitive); extension != "" { return path.RemoveExtension(extension).AppendSuffix(".d" + extension + ".ts") } + return changeToDeclarationExtension(path) +} + +func changeToDeclarationExtension(path tspath.RootedFilePath) tspath.RootedFilePath { pathWithoutExtension := path.RemoveFileExtension() if pathWithoutExtension.AsString() == path.AsString() { if extension := path.AnyExtension(nil, tspath.CaseSensitive); extension != "" { diff --git a/tsc/internal/outputpaths/outputpaths_test.go b/tsc/internal/outputpaths/outputpaths_test.go index e37b0803d5037..00f823d4b3b7d 100644 --- a/tsc/internal/outputpaths/outputpaths_test.go +++ b/tsc/internal/outputpaths/outputpaths_test.go @@ -10,8 +10,9 @@ import ( ) type outputPathsHost struct { - commonSourceDirectory tspath.RootedDirectoryPath - contentMapperExtensions []string + commonSourceDirectory tspath.RootedDirectoryPath + contentMapperExtensions []string + contentMapperExtensionRewrites []tspath.ExtensionRewrite } func (h outputPathsHost) CommonSourceDirectory() tspath.RootedDirectoryPath { @@ -22,10 +23,27 @@ func (h outputPathsHost) ContentMapperExtensions() []string { return h.contentMapperExtensions } +func (h outputPathsHost) ContentMapperExtensionRewrites() []tspath.ExtensionRewrite { + return h.contentMapperExtensionRewrites +} + func (outputPathsHost) CaseSensitivity() tspath.CaseSensitivity { return tspath.CaseInsensitive } +func TestContentMapperOutputExtensionCaseInsensitivePaths(t *testing.T) { + t.Parallel() + host := outputPathsHost{ + commonSourceDirectory: "/src", + contentMapperExtensions: []string{".y.z"}, + contentMapperExtensionRewrites: []tspath.ExtensionRewrite{{Source: ".y.z", Target: ".mjs"}}, + } + options := &core.CompilerOptions{OutDir: "/dist"} + fileName := tspath.RootedFilePath("/src/Widget.Y.Z") + assert.Equal(t, outputpaths.GetExternalOutputFileName(fileName, options, host), tspath.RootedFilePath("/dist/Widget.mjs")) + assert.Equal(t, outputpaths.GetOutputDeclarationFileNameWorker(fileName, options, host), tspath.RootedFilePath("/dist/Widget.d.mts")) +} + func TestGetSourceFileNameInNewDirSourceMatchesCommonDirectory(t *testing.T) { t.Parallel() diff --git a/tsc/internal/packagejson/packagejson.go b/tsc/internal/packagejson/packagejson.go index 05a044a38a9ce..5251e8764611b 100644 --- a/tsc/internal/packagejson/packagejson.go +++ b/tsc/internal/packagejson/packagejson.go @@ -115,9 +115,10 @@ type Fields struct { } type ContentMapperFields struct { - Exec Expected[[]string] `json:"exec"` - CompilerOptions Expected[[]string] `json:"compilerOptions"` - DynamicConfig Expected[bool] `json:"dynamicConfig"` + Exec Expected[[]string] `json:"exec"` + CompilerOptions Expected[[]string] `json:"compilerOptions"` + DynamicConfig Expected[bool] `json:"dynamicConfig"` + OutputExtensions Expected[map[string]string] `json:"outputExtensions"` } type typeScriptFields struct { diff --git a/tsc/internal/packagejson/packagejson_test.go b/tsc/internal/packagejson/packagejson_test.go index e47bb49e293af..a5db586728a31 100644 --- a/tsc/internal/packagejson/packagejson_test.go +++ b/tsc/internal/packagejson/packagejson_test.go @@ -142,14 +142,15 @@ func TestParse(t *testing.T) { content: `{ "name": "test-package", "typescript": { - "contentMapper": { "exec": ["mapper"], "dynamicConfig": true } + "contentMapper": { "exec": ["mapper"], "dynamicConfig": true, "outputExtensions": { ".astro": ".js" } } } }`, want: packagejson.Fields{ HeaderFields: packagejson.HeaderFields{Name: packagejson.ExpectedOf("test-package")}, ContentMapper: packagejson.ExpectedOf(packagejson.ContentMapperFields{ - Exec: packagejson.ExpectedOf([]string{"mapper"}), - DynamicConfig: packagejson.ExpectedOf(true), + Exec: packagejson.ExpectedOf([]string{"mapper"}), + DynamicConfig: packagejson.ExpectedOf(true), + OutputExtensions: packagejson.ExpectedOf(map[string]string{".astro": ".js"}), }), }, }, diff --git a/tsc/internal/printer/emithost.go b/tsc/internal/printer/emithost.go index 993e02ee12f76..1632d0093d6d7 100644 --- a/tsc/internal/printer/emithost.go +++ b/tsc/internal/printer/emithost.go @@ -11,6 +11,7 @@ import ( type EmitHost interface { Options() *core.CompilerOptions SourceFiles() []*ast.SourceFile + ContentMapperExtensionRewrites() []tspath.ExtensionRewrite CaseSensitivity() tspath.CaseSensitivity CommonSourceDirectory() tspath.RootedDirectoryPath IsEmitBlocked(file tspath.RootedFilePath) bool diff --git a/tsc/internal/printer/factory.go b/tsc/internal/printer/factory.go index 9a25da3a384f5..89595d165fad5 100644 --- a/tsc/internal/printer/factory.go +++ b/tsc/internal/printer/factory.go @@ -8,6 +8,7 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/ast" "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/debug" + "github.com/microsoft/TypeScript/tsc/internal/tspath" ) type NodeFactory struct { @@ -1297,13 +1298,39 @@ func (f *NodeFactory) NewAssignmentTargetWrapper(paramName *ast.IdentifierNode, } // Allocates a new Call expression to the `__rewriteRelativeImportExtension` helper. -func (f *NodeFactory) NewRewriteRelativeImportExtensionsHelper(firstArgument *ast.Node, preserveJsx bool) *ast.Expression { +func (f *NodeFactory) NewRewriteRelativeImportExtensionsHelper(firstArgument *ast.Node, preserveJsx bool, extraExtensions []tspath.ExtensionRewrite, caseSensitivity tspath.CaseSensitivity) *ast.Expression { + if len(extraExtensions) == 0 { + f.emitContext.RequestEmitHelper(rewriteRelativeImportExtensionsHelper) + var arguments []*ast.Expression + if preserveJsx { + arguments = []*ast.Expression{firstArgument, f.NewToken(ast.KindTrueKeyword)} + } else { + arguments = []*ast.Expression{firstArgument} + } + return f.NewCallExpression( + f.NewUnscopedHelperName("__rewriteRelativeImportExtension"), + nil, + nil, + f.NewNodeList(arguments), + ast.NodeFlagsNone, + ) + } f.emitContext.RequestEmitHelper(rewriteRelativeImportExtensionsHelper) - var arguments []*ast.Expression - if preserveJsx { - arguments = []*ast.Expression{firstArgument, f.NewToken(ast.KindTrueKeyword)} - } else { - arguments = []*ast.Expression{firstArgument} + arguments := []*ast.Expression{firstArgument} + arguments = append(arguments, f.NewToken(core.IfElse(preserveJsx, ast.KindTrueKeyword, ast.KindFalseKeyword))) + properties := make([]*ast.Node, 0, len(extraExtensions)) + for _, extension := range extraExtensions { + properties = append(properties, f.NewPropertyAssignment( + nil, + f.NewStringLiteral(extension.Source, ast.TokenFlagsNone), + nil, + nil, + f.NewStringLiteral(extension.Target, ast.TokenFlagsNone), + )) + } + arguments = append(arguments, f.NewObjectLiteralExpression(f.NewNodeList(properties), false)) + if caseSensitivity.IsCaseInsensitive() { + arguments = append(arguments, f.NewToken(ast.KindTrueKeyword)) } return f.NewCallExpression( f.NewUnscopedHelperName("__rewriteRelativeImportExtension"), diff --git a/tsc/internal/printer/helpers.go b/tsc/internal/printer/helpers.go index 0c412f8dd86dd..cf1cc14f9eadf 100644 --- a/tsc/internal/printer/helpers.go +++ b/tsc/internal/printer/helpers.go @@ -547,8 +547,17 @@ var rewriteRelativeImportExtensionsHelper = &EmitHelper{ Name: "typescript:rewriteRelativeImportExtensions", ImportName: "__rewriteRelativeImportExtension", Scoped: false, - Text: `var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) { + Text: `var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); }); diff --git a/tsc/internal/transformers/chain.go b/tsc/internal/transformers/chain.go index d57af5e9947b1..fec592e5e2019 100644 --- a/tsc/internal/transformers/chain.go +++ b/tsc/internal/transformers/chain.go @@ -5,6 +5,7 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/binder" "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/printer" + "github.com/microsoft/TypeScript/tsc/internal/tspath" ) type chainedTransformer struct { @@ -24,11 +25,13 @@ func (ch *chainedTransformer) visit(node *ast.Node) *ast.Node { } type TransformOptions struct { - Context *printer.EmitContext - CompilerOptions *core.CompilerOptions - Resolver binder.ReferenceResolver - EmitResolver printer.EmitResolver - GetEmitModuleFormatOfFile func(file ast.HasFileName) core.ModuleKind + Context *printer.EmitContext + CompilerOptions *core.CompilerOptions + Resolver binder.ReferenceResolver + EmitResolver printer.EmitResolver + GetEmitModuleFormatOfFile func(file ast.HasFileName) core.ModuleKind + ContentMapperExtensionRewrites []tspath.ExtensionRewrite + CaseSensitivity tspath.CaseSensitivity } type TransformerFactory = func(opt *TransformOptions) *Transformer diff --git a/tsc/internal/transformers/declarations/transform.go b/tsc/internal/transformers/declarations/transform.go index e8e6c0b22c5f1..34e3d4922f54b 100644 --- a/tsc/internal/transformers/declarations/transform.go +++ b/tsc/internal/transformers/declarations/transform.go @@ -33,6 +33,7 @@ type OutputPaths interface { // Used to be passed in the TransformationContext, which is now just an EmitContext type DeclarationEmitHost interface { modulespecifiers.ModuleSpecifierGenerationHost + ContentMapperExtensionRewrites() []tspath.ExtensionRewrite CaseSensitivity() tspath.CaseSensitivity GetSourceFileFromReference(origin *ast.SourceFile, ref *ast.FileReference) *ast.SourceFile @@ -217,7 +218,7 @@ const declarationEmitNodeBuilderFlags = nodebuilder.FlagsMultilineObjectLiterals nodebuilder.FlagsGenerateNamesForShadowedTypeParams | nodebuilder.FlagsNoTruncation -const declarationEmitInternalNodeBuilderFlags = nodebuilder.InternalFlagsAllowUnresolvedNames +const declarationEmitInternalNodeBuilderFlags = nodebuilder.InternalFlagsAllowUnresolvedNames | nodebuilder.InternalFlagsRewriteModuleSpecifiers // functions as both `visitDeclarationStatements` and `transformRoot`, utilitzing SyntaxList nodes func (tx *DeclarationTransformer) visit(node *ast.Node) *ast.Node { @@ -1603,6 +1604,20 @@ func (tx *DeclarationTransformer) rewriteModuleSpecifier(parent *ast.Node, input return nil } tx.resultHasExternalModuleIndicator = tx.resultHasExternalModuleIndicator || (parent.Kind != ast.KindModuleDeclaration && parent.Kind != ast.KindImportType) + if ast.IsStringLiteral(input) && tx.compilerOptions.RewriteRelativeImportExtensions.IsTrue() { + specifier := tspath.ToModuleSpecifier(input.Text()) + rewrites := tx.host.ContentMapperExtensionRewrites() + caseSensitivity := tx.host.CaseSensitivity() + if !specifier.ShouldRewriteExtension(rewrites, caseSensitivity) { + return input + } + if rewritten, ok := specifier.RewriteExtension(rewrites, caseSensitivity); ok { + result := tx.Factory().NewStringLiteral(rewritten.AsString(), input.AsStringLiteral().TokenFlags) + tx.EmitContext().SetOriginal(result, input) + tx.EmitContext().AssignCommentAndSourceMapRanges(result, input) + return result + } + } return input } diff --git a/tsc/internal/transformers/moduletransforms/commonjsmodule.go b/tsc/internal/transformers/moduletransforms/commonjsmodule.go index 8c479e8b68779..8fc2e10f5b75c 100644 --- a/tsc/internal/transformers/moduletransforms/commonjsmodule.go +++ b/tsc/internal/transformers/moduletransforms/commonjsmodule.go @@ -14,25 +14,27 @@ import ( type CommonJSModuleTransformer struct { transformers.Transformer - topLevelVisitor *ast.NodeVisitor // visits statements at top level of a module - topLevelNestedVisitor *ast.NodeVisitor // visits nested statements at top level of a module - discardedValueVisitor *ast.NodeVisitor // visits expressions whose values would be discarded at runtime - assignmentPatternVisitor *ast.NodeVisitor // visits assignment patterns in a destructuring assignment - compilerOptions *core.CompilerOptions - resolver binder.ReferenceResolver - getEmitModuleFormatOfFile func(file ast.HasFileName) core.ModuleKind - moduleKind core.ModuleKind - languageVersion core.ScriptTarget - currentSourceFile *ast.SourceFile - currentModuleInfo *externalModuleInfo - parentNode *ast.Node // used for ancestor tracking via pushNode/popNode to detect expression identifiers - currentNode *ast.Node // used for ancestor tracking via pushNode/popNode to detect expression identifiers + topLevelVisitor *ast.NodeVisitor // visits statements at top level of a module + topLevelNestedVisitor *ast.NodeVisitor // visits nested statements at top level of a module + discardedValueVisitor *ast.NodeVisitor // visits expressions whose values would be discarded at runtime + assignmentPatternVisitor *ast.NodeVisitor // visits assignment patterns in a destructuring assignment + compilerOptions *core.CompilerOptions + resolver binder.ReferenceResolver + getEmitModuleFormatOfFile func(file ast.HasFileName) core.ModuleKind + moduleKind core.ModuleKind + languageVersion core.ScriptTarget + currentSourceFile *ast.SourceFile + currentModuleInfo *externalModuleInfo + parentNode *ast.Node // used for ancestor tracking via pushNode/popNode to detect expression identifiers + currentNode *ast.Node // used for ancestor tracking via pushNode/popNode to detect expression identifiers + contentMapperExtensionRewrites []tspath.ExtensionRewrite + caseSensitivity tspath.CaseSensitivity } func NewCommonJSModuleTransformer(opts *transformers.TransformOptions) *transformers.Transformer { compilerOptions := opts.CompilerOptions emitContext := opts.Context - tx := &CommonJSModuleTransformer{compilerOptions: compilerOptions, resolver: opts.Resolver, getEmitModuleFormatOfFile: opts.GetEmitModuleFormatOfFile} + tx := &CommonJSModuleTransformer{compilerOptions: compilerOptions, resolver: opts.Resolver, getEmitModuleFormatOfFile: opts.GetEmitModuleFormatOfFile, contentMapperExtensionRewrites: opts.ContentMapperExtensionRewrites, caseSensitivity: opts.CaseSensitivity} tx.topLevelVisitor = emitContext.NewNodeVisitor(tx.visitTopLevel) tx.topLevelNestedVisitor = emitContext.NewNodeVisitor(tx.visitTopLevelNested) tx.discardedValueVisitor = emitContext.NewNodeVisitor(tx.visitDiscardedValue) @@ -680,7 +682,7 @@ func (tx *CommonJSModuleTransformer) createRequireCall(node *ast.Node /*ImportDe var args []*ast.Expression moduleName := getExternalModuleNameLiteral(tx.Factory(), node, tx.currentSourceFile, nil /*host*/, nil /*resolver*/, tx.compilerOptions) if moduleName != nil { - args = append(args, rewriteModuleSpecifier(tx.EmitContext(), moduleName, tx.compilerOptions)) + args = append(args, rewriteModuleSpecifier(tx.EmitContext(), moduleName, tx.compilerOptions, tx.contentMapperExtensionRewrites, tx.caseSensitivity)) } return tx.Factory().NewCallExpression( tx.Factory().NewIdentifier("require"), @@ -1861,9 +1863,9 @@ func (tx *CommonJSModuleTransformer) visitImportCallExpression(node *ast.CallExp argument = externalModuleName } else if firstArgument != nil && rewriteOrShim { if ast.IsStringLiteral(firstArgument) { - argument = rewriteModuleSpecifier(tx.EmitContext(), firstArgument, tx.compilerOptions) + argument = rewriteModuleSpecifier(tx.EmitContext(), firstArgument, tx.compilerOptions, tx.contentMapperExtensionRewrites, tx.caseSensitivity) } else { - argument = tx.Factory().NewRewriteRelativeImportExtensionsHelper(firstArgument, tx.compilerOptions.Jsx == core.JsxEmitPreserve) + argument = tx.Factory().NewRewriteRelativeImportExtensionsHelper(firstArgument, tx.compilerOptions.Jsx == core.JsxEmitPreserve, tx.contentMapperExtensionRewrites, tx.caseSensitivity) } } else { argument = firstArgument @@ -1971,11 +1973,11 @@ func (tx *CommonJSModuleTransformer) shimOrRewriteImportOrRequireCall(node *ast. firstArgument := tx.Visitor().VisitNode(node.Arguments.Nodes[0]) firstArgumentChanged := false if ast.IsStringLiteralLike(firstArgument) { - rewritten := rewriteModuleSpecifier(tx.EmitContext(), firstArgument, tx.compilerOptions) + rewritten := rewriteModuleSpecifier(tx.EmitContext(), firstArgument, tx.compilerOptions, tx.contentMapperExtensionRewrites, tx.caseSensitivity) firstArgumentChanged = rewritten != firstArgument firstArgument = rewritten } else { - firstArgument = tx.Factory().NewRewriteRelativeImportExtensionsHelper(firstArgument, tx.compilerOptions.Jsx == core.JsxEmitPreserve) + firstArgument = tx.Factory().NewRewriteRelativeImportExtensionsHelper(firstArgument, tx.compilerOptions.Jsx == core.JsxEmitPreserve, tx.contentMapperExtensionRewrites, tx.caseSensitivity) firstArgumentChanged = true } diff --git a/tsc/internal/transformers/moduletransforms/esmodule.go b/tsc/internal/transformers/moduletransforms/esmodule.go index 87f20beb2033d..093c02f2f6d5f 100644 --- a/tsc/internal/transformers/moduletransforms/esmodule.go +++ b/tsc/internal/transformers/moduletransforms/esmodule.go @@ -8,16 +8,19 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/printer" "github.com/microsoft/TypeScript/tsc/internal/transformers" + "github.com/microsoft/TypeScript/tsc/internal/tspath" ) type ESModuleTransformer struct { transformers.Transformer - compilerOptions *core.CompilerOptions - resolver binder.ReferenceResolver - getEmitModuleFormatOfFile func(file ast.HasFileName) core.ModuleKind - currentSourceFile *ast.SourceFile - importRequireStatements *importRequireStatements - helperNameSubstitutions map[string]*ast.IdentifierNode + compilerOptions *core.CompilerOptions + resolver binder.ReferenceResolver + getEmitModuleFormatOfFile func(file ast.HasFileName) core.ModuleKind + contentMapperExtensionRewrites []tspath.ExtensionRewrite + caseSensitivity tspath.CaseSensitivity + currentSourceFile *ast.SourceFile + importRequireStatements *importRequireStatements + helperNameSubstitutions map[string]*ast.IdentifierNode } type importRequireStatements struct { @@ -27,7 +30,7 @@ type importRequireStatements struct { func NewESModuleTransformer(opts *transformers.TransformOptions) *transformers.Transformer { compilerOptions := opts.CompilerOptions - tx := &ESModuleTransformer{compilerOptions: compilerOptions, resolver: opts.Resolver, getEmitModuleFormatOfFile: opts.GetEmitModuleFormatOfFile} + tx := &ESModuleTransformer{compilerOptions: compilerOptions, resolver: opts.Resolver, getEmitModuleFormatOfFile: opts.GetEmitModuleFormatOfFile, contentMapperExtensionRewrites: opts.ContentMapperExtensionRewrites, caseSensitivity: opts.CaseSensitivity} return tx.NewTransformer(tx.visit, opts.Context) } @@ -104,7 +107,7 @@ func (tx *ESModuleTransformer) visitImportDeclaration(node *ast.ImportDeclaratio if !tx.compilerOptions.RewriteRelativeImportExtensions.IsTrue() { return node.AsNode() } - updatedModuleSpecifier := rewriteModuleSpecifier(tx.EmitContext(), node.ModuleSpecifier, tx.compilerOptions) + updatedModuleSpecifier := rewriteModuleSpecifier(tx.EmitContext(), node.ModuleSpecifier, tx.compilerOptions, tx.contentMapperExtensionRewrites, tx.caseSensitivity) return tx.Factory().UpdateImportDeclaration( node, nil, /*modifiers*/ @@ -198,7 +201,7 @@ func (tx *ESModuleTransformer) visitExportDeclaration(node *ast.ExportDeclaratio return node.AsNode() } - updatedModuleSpecifier := rewriteModuleSpecifier(tx.EmitContext(), node.ModuleSpecifier, tx.compilerOptions) + updatedModuleSpecifier := rewriteModuleSpecifier(tx.EmitContext(), node.ModuleSpecifier, tx.compilerOptions, tx.contentMapperExtensionRewrites, tx.caseSensitivity) if tx.compilerOptions.Module > core.ModuleKindES2015 || node.ExportClause == nil || !ast.IsNamespaceExport(node.ExportClause) { // Either ill-formed or don't need to be transformed. return tx.Factory().UpdateExportDeclaration( @@ -264,9 +267,9 @@ func (tx *ESModuleTransformer) visitImportOrRequireCall(node *ast.CallExpression var argument *ast.Expression if ast.IsStringLiteralLike(node.Arguments.Nodes[0]) { - argument = rewriteModuleSpecifier(tx.EmitContext(), node.Arguments.Nodes[0], tx.compilerOptions) + argument = rewriteModuleSpecifier(tx.EmitContext(), node.Arguments.Nodes[0], tx.compilerOptions, tx.contentMapperExtensionRewrites, tx.caseSensitivity) } else { - argument = tx.Factory().NewRewriteRelativeImportExtensionsHelper(node.Arguments.Nodes[0], tx.compilerOptions.Jsx == core.JsxEmitPreserve) + argument = tx.Factory().NewRewriteRelativeImportExtensionsHelper(node.Arguments.Nodes[0], tx.compilerOptions.Jsx == core.JsxEmitPreserve, tx.contentMapperExtensionRewrites, tx.caseSensitivity) } var arguments []*ast.Expression @@ -292,7 +295,7 @@ func (tx *ESModuleTransformer) createRequireCall(node *ast.Node /*ImportDeclarat var args []*ast.Expression if moduleName != nil { - args = append(args, rewriteModuleSpecifier(tx.EmitContext(), moduleName, tx.compilerOptions)) + args = append(args, rewriteModuleSpecifier(tx.EmitContext(), moduleName, tx.compilerOptions, tx.contentMapperExtensionRewrites, tx.caseSensitivity)) } if tx.compilerOptions.GetEmitModuleKind() == core.ModuleKindPreserve { diff --git a/tsc/internal/transformers/moduletransforms/utilities.go b/tsc/internal/transformers/moduletransforms/utilities.go index fe931df145136..8ed465e9c7b34 100644 --- a/tsc/internal/transformers/moduletransforms/utilities.go +++ b/tsc/internal/transformers/moduletransforms/utilities.go @@ -19,11 +19,19 @@ func isDeclarationNameOfEnumOrNamespace(emitContext *printer.EmitContext, node * return false } -func rewriteModuleSpecifier(emitContext *printer.EmitContext, node *ast.Expression, compilerOptions *core.CompilerOptions) *ast.Expression { - if node == nil || !ast.IsStringLiteral(node) || !core.ShouldRewriteModuleSpecifier(node.Text(), compilerOptions) { +func rewriteModuleSpecifier(emitContext *printer.EmitContext, node *ast.Expression, compilerOptions *core.CompilerOptions, rewrites []tspath.ExtensionRewrite, caseSensitivity tspath.CaseSensitivity) *ast.Expression { + if node == nil || !ast.IsStringLiteral(node) || !compilerOptions.RewriteRelativeImportExtensions.IsTrue() { return node } - updatedText := tspath.ChangeExtension(node.Text(), outputpaths.GetOutputExtension(node.Text(), compilerOptions.Jsx)) + specifier := tspath.ToModuleSpecifier(node.Text()) + if !specifier.ShouldRewriteExtension(rewrites, caseSensitivity) { + return node + } + updatedSpecifier, rewritten := specifier.RewriteExtension(rewrites, caseSensitivity) + updatedText := updatedSpecifier.AsString() + if !rewritten { + updatedText = tspath.ChangeExtension(node.Text(), outputpaths.GetOutputExtension(node.Text(), compilerOptions.Jsx)) + } if updatedText != node.Text() { updated := emitContext.Factory.NewStringLiteral(updatedText, node.AsStringLiteral().TokenFlags) emitContext.SetOriginal(updated, node) diff --git a/tsc/internal/transformers/tstransforms/importelision_test.go b/tsc/internal/transformers/tstransforms/importelision_test.go index 259db833bcec4..e506d4b51cd88 100644 --- a/tsc/internal/transformers/tstransforms/importelision_test.go +++ b/tsc/internal/transformers/tstransforms/importelision_test.go @@ -54,6 +54,10 @@ func (p *fakeProgram) ContentMapperExtensions() []string { return nil } +func (p *fakeProgram) ContentMapperExtensionRewrites() []tspath.ExtensionRewrite { + return nil +} + func (p *fakeProgram) GetResolvedModuleFromModuleSpecifier(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) *module.ResolvedModule { panic("unimplemented") } diff --git a/tsc/internal/tsoptions/contentmappers.go b/tsc/internal/tsoptions/contentmappers.go index ab79a29e2af13..d05bfa3bc3053 100644 --- a/tsc/internal/tsoptions/contentmappers.go +++ b/tsc/internal/tsoptions/contentmappers.go @@ -1,6 +1,10 @@ package tsoptions import ( + "maps" + "slices" + "strings" + "github.com/microsoft/TypeScript/tsc/internal/ast" "github.com/microsoft/TypeScript/tsc/internal/contentmapper" "github.com/microsoft/TypeScript/tsc/internal/core" @@ -66,5 +70,31 @@ func resolveContentMapperManifest(fs vfs.FS, containingFile tspath.RootedFilePat } compilerOptions, _ := cm.CompilerOptions.GetValue() dynamicConfig, _ := cm.DynamicConfig.GetValue() - return contentmapper.Manifest{Name: name, Version: version, Exec: exec, CompilerOptions: compilerOptions, DynamicConfig: dynamicConfig}, packageDirectory, nil + outputExtensions, valid := cm.OutputExtensions.GetValue() + if cm.OutputExtensions.IsPresent() && !valid { + return contentmapper.Manifest{}, packageDirectory, ast.NewCompilerDiagnostic( + diagnostics.The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_must_be_an_object_with_string_values, + packageName, + ) + } + for _, source := range slices.Sorted(maps.Keys(outputExtensions)) { + output := outputExtensions[source] + for _, extension := range []string{source, output} { + if strings.ContainsAny(extension, "/\\") { + return contentmapper.Manifest{}, packageDirectory, ast.NewCompilerDiagnostic( + diagnostics.Content_mapper_extension_0_must_not_contain_path_separators, + extension, + ) + } + } + if !contentmapper.IsValidExtension(source) || !contentmapper.IsValidExtension(output) { + return contentmapper.Manifest{}, packageDirectory, ast.NewCompilerDiagnostic( + diagnostics.The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_contains_an_invalid_mapping_from_1_to_2_Extensions_must_be_non_empty_and_begin_with_a, + packageName, + source, + output, + ) + } + } + return contentmapper.Manifest{Name: name, Version: version, Exec: exec, CompilerOptions: compilerOptions, DynamicConfig: dynamicConfig, DefaultOutputExtensions: outputExtensions}, packageDirectory, nil } diff --git a/tsc/internal/tsoptions/contentmappers_test.go b/tsc/internal/tsoptions/contentmappers_test.go index 00ee23f9dcf85..2c1566165c674 100644 --- a/tsc/internal/tsoptions/contentmappers_test.go +++ b/tsc/internal/tsoptions/contentmappers_test.go @@ -59,6 +59,82 @@ func TestGetOutputFileNamesExcludesMapperOwnedOutputs(t *testing.T) { assert.DeepEqual(t, slices.Collect(commandLine.GetOutputFileNames()), []tspath.RootedFilePath{"/dist/Component.d.vue.ts"}) } +func TestContentMapperExtensionRewritesPreserveLongestExtension(t *testing.T) { + t.Parallel() + mapper := &contentmapper.Mapper{ + Extensions: []string{".z", ".y.z", ".identity.y.z", ".unmapped.y.z"}, + OutputExtensions: map[string]string{ + ".z": ".js", ".y.z": ".mjs", ".identity.y.z": ".identity.y.z", + }, + } + commandLine := NewParsedCommandLine( + &core.CompilerOptions{Declaration: core.TSTrue, OutDir: "/dist"}, + []tspath.RootedFilePath{"/src/Widget.y.z", "/src/Widget.identity.y.z", "/src/Widget.unmapped.y.z"}, + nil, + "/", + tspath.CaseSensitive, + ) + commandLine.ParsedConfig.ContentMappers = []*contentmapper.Mapper{mapper} + rewrites := commandLine.ContentMapperExtensionRewrites() + assert.Assert(t, slices.IsSortedFunc(rewrites, func(a, b tspath.ExtensionRewrite) int { + return len(b.Source) - len(a.Source) + })) + for _, file := range []string{"./Widget.identity.y.z", "./Widget.unmapped.y.z"} { + rewritten, ok := tspath.ToModuleSpecifier(file).RewriteExtension(rewrites, tspath.CaseSensitive) + assert.Assert(t, !ok) + assert.Equal(t, rewritten.AsString(), file) + } + assert.DeepEqual(t, slices.Collect(commandLine.GetOutputFileNames()), []tspath.RootedFilePath{ + "/dist/Widget.d.mts", + "/dist/Widget.d.identity.y.z.ts", + "/dist/Widget.d.unmapped.y.z.ts", + }) +} + +func TestContentMapperExtensionRewritesAreCached(t *testing.T) { //nolint:paralleltest,tparallel // AllocsPerRun cannot run in parallel tests. + mapper := &contentmapper.Mapper{ + Extensions: []string{".astro", ".y.z"}, + OutputExtensions: map[string]string{".astro": ".js", ".y.z": ".mjs"}, + } + commandLine := NewParsedCommandLine( + &core.CompilerOptions{}, nil, nil, + "/", tspath.CaseSensitive, + ) + commandLine.ParsedConfig.ContentMappers = []*contentmapper.Mapper{mapper} + rewrites := commandLine.ContentMapperExtensionRewrites() + for range 10 { + assert.Assert(t, &commandLine.ContentMapperExtensionRewrites()[0] == &rewrites[0]) + } + assert.Equal(t, testing.AllocsPerRun(100, func() { + commandLine.ContentMapperExtensionRewrites() + }), float64(0)) + withFileNames := commandLine.WithFileNames([]tspath.RootedFilePath{"/src/Card.astro"}) + for range 10 { + t.Run("concurrent", func(t *testing.T) { + t.Parallel() + assert.Assert(t, &commandLine.ContentMapperExtensionRewrites()[0] == &rewrites[0]) + assert.DeepEqual(t, withFileNames.ContentMapperExtensionRewrites(), rewrites) + }) + } +} + +func TestResolveContentMapperManifestRejectsMalformedOutputExtensions(t *testing.T) { + t.Parallel() + for _, value := range []string{`"invalid"`, `null`, `[]`, `{".vue": 1}`, `{".vue": null}`, `{".vue/foo": ".js"}`, `{".vue\\foo": ".js"}`, `{".vue": ".js/foo"}`, `{".vue": ".js\\foo"}`} { + t.Run(value, func(t *testing.T) { + t.Parallel() + fs := vfstest.FromMap(map[string]string{ + "/home/project/node_modules/mapper/package.json": `{ + "name": "mapper", + "typescript": { "contentMapper": { "exec": ["run"], "outputExtensions": ` + value + ` } } + }`, + }, tspath.CaseSensitive) + _, _, diagnostic := resolveContentMapperManifest(fs, "/home/project/tsconfig.json", "mapper") + assert.Assert(t, diagnostic != nil, "expected malformed outputExtensions to be rejected") + }) + } +} + func TestResolveContentMapperManifest(t *testing.T) { t.Parallel() @@ -66,7 +142,7 @@ func TestResolveContentMapperManifest(t *testing.T) { "/home/project/node_modules/vue-ts-mapper/package.json": `{ "name": "vue-ts-mapper", "version": "1.2.3", - "typescript": { "contentMapper": { "exec": ["node", "./dist/mapper.js"], "compilerOptions": ["target", "jsx"] } } + "typescript": { "contentMapper": { "exec": ["node", "./dist/mapper.js"], "compilerOptions": ["target", "jsx"], "outputExtensions": { ".vue": ".js" } } } }`, "/home/node_modules/@scope/noversion/package.json": `{ "name": "@scope/noversion", @@ -86,6 +162,10 @@ func TestResolveContentMapperManifest(t *testing.T) { "name": "bad-exec", "typescript": { "contentMapper": { "exec": "node ./mapper.js" } } }`, + "/home/project/node_modules/bad-output-extension/package.json": `{ + "name": "bad-output-extension", + "typescript": { "contentMapper": { "exec": ["run"], "outputExtensions": { ".vue": "js" } } } + }`, }, tspath.CaseSensitive /*caseSensitivity*/) // Name, version, and the verbatim exec argv are preserved. @@ -96,6 +176,7 @@ func TestResolveContentMapperManifest(t *testing.T) { assert.Equal(t, packageDirectory, tspath.RootedDirectoryPath("/home/project/node_modules/vue-ts-mapper")) assert.DeepEqual(t, manifest.Exec, []string{"node", "./dist/mapper.js"}) assert.DeepEqual(t, manifest.CompilerOptions, []string{"target", "jsx"}) + assert.DeepEqual(t, manifest.DefaultOutputExtensions, map[string]string{".vue": ".js"}) // Resolution walks up node_modules; a package with no version resolves to a name and empty version. manifest, _, diagnostic = resolveContentMapperManifest(fs, "/home/project/src/tsconfig.json", "@scope/noversion") @@ -125,4 +206,7 @@ func TestResolveContentMapperManifest(t *testing.T) { assert.Assert(t, diagnostic != nil, "expected a diagnostic for %s", pkg) assert.Equal(t, diagnostic.Code(), diagnostics.The_typescript_contentMapper_exec_of_the_content_mapper_package_0_must_be_a_non_empty_array_of_strings.Code()) } + + _, _, diagnostic = resolveContentMapperManifest(fs, "/home/project/tsconfig.json", "bad-output-extension") + assert.Equal(t, diagnostic.Code(), diagnostics.The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_contains_an_invalid_mapping_from_1_to_2_Extensions_must_be_non_empty_and_begin_with_a.Code()) } diff --git a/tsc/internal/tsoptions/parsedcommandline.go b/tsc/internal/tsoptions/parsedcommandline.go index 3249b8c37831c..f016fad4ac2a7 100644 --- a/tsc/internal/tsoptions/parsedcommandline.go +++ b/tsc/internal/tsoptions/parsedcommandline.go @@ -73,6 +73,9 @@ type ParsedCommandLine struct { filePaths *collections.Set[tspath.PathKey] filePathsOnce sync.Once + contentMapperExtensionRewrites []tspath.ExtensionRewrite + contentMapperExtensionRewritesOnce sync.Once + locale locale.Locale localeOnce sync.Once } @@ -396,6 +399,54 @@ func (p *ParsedCommandLine) ContentMapperExtensions() []string { }) } +func (p *ParsedCommandLine) ContentMapperExtensionRewrites() []tspath.ExtensionRewrite { + p.contentMapperExtensionRewritesOnce.Do(func() { + p.contentMapperExtensionRewrites = p.computeContentMapperExtensionRewrites() + }) + return p.contentMapperExtensionRewrites +} + +func (p *ParsedCommandLine) computeContentMapperExtensionRewrites() []tspath.ExtensionRewrite { + var result []tspath.ExtensionRewrite + hasNonIdentityMapping := false + ignoreCase := p.CaseSensitivity().IsCaseInsensitive() + for _, mapper := range p.ContentMappers() { + outputExtensions := mapper.EffectiveOutputExtensions() + for _, source := range mapper.Definition.Extensions { + target, ok := getContentMapperOutputExtension(outputExtensions, source, ignoreCase) + if !ok || target == source || ignoreCase && strings.EqualFold(target, source) { + target = source + } else { + hasNonIdentityMapping = true + } + result = append(result, tspath.ExtensionRewrite{Source: source, Target: target}) + } + } + if !hasNonIdentityMapping { + return nil + } + // Identity entries prevent shorter mappings from claiming a longer registered extension. + // The runtime helper uses the first match, so emit the longest extensions first. + slices.SortStableFunc(result, func(a, b tspath.ExtensionRewrite) int { + return len(b.Source) - len(a.Source) + }) + return result +} + +func getContentMapperOutputExtension(outputExtensions map[string]string, sourceExtension string, ignoreCase bool) (string, bool) { + if output, ok := outputExtensions[sourceExtension]; ok { + return output, true + } + if ignoreCase { + for source, output := range outputExtensions { + if strings.EqualFold(source, sourceExtension) { + return output, true + } + } + } + return "", false +} + // GetContentMapperForFileName returns the configured content mapper whose extensions include fileName, // or nil if no content mapper is registered for the file's extension. func (p *ParsedCommandLine) GetContentMapperForFileName(fileName tspath.RootedFilePath) *contentmapper.Mapper { diff --git a/tsc/internal/tsoptions/parsedoptions_test.go b/tsc/internal/tsoptions/parsedoptions_test.go index 827348240b7f5..3bfd5dc5ee81e 100644 --- a/tsc/internal/tsoptions/parsedoptions_test.go +++ b/tsc/internal/tsoptions/parsedoptions_test.go @@ -24,7 +24,7 @@ func TestParsedOptionsEquality(t *testing.T) { check(t, &ParsedOptions{}, nil) check(t, &ParsedOptions{}, &ParsedOptions{}) makeOptions := func() *ParsedOptions { - return &ParsedOptions{ + result := &ParsedOptions{ CompilerOptions: &core.CompilerOptions{Strict: core.TSTrue}, TypeAcquisition: &core.TypeAcquisition{Enable: core.TSTrue, Include: []string{"a"}, Exclude: []string{"b"}}, FileNames: []tspath.RootedFilePath{"/a.ts", "/b.ts"}, @@ -37,6 +37,9 @@ func TestParsedOptionsEquality(t *testing.T) { PackageDirectory: "/node_modules/mapper", ContributionID: "extension", }, nil}, } + result.ContentMappers[0].Definition.OutputExtensions = map[string]string{".vue": ".mjs"} + result.ContentMappers[0].Manifest.DefaultOutputExtensions = map[string]string{".vue": ".js"} + return result } check(t, makeOptions(), makeOptions()) for _, object := range []func(*ParsedOptions) any{ @@ -85,12 +88,14 @@ func TestParsedOptionsEquality(t *testing.T) { }}, {"mapper package", func(p *ParsedOptions) { p.ContentMappers[0].Package = "other" }}, {"mapper extension", func(p *ParsedOptions) { p.ContentMappers[0].Extensions[0] = ".other" }}, + {"mapper output extension", func(p *ParsedOptions) { p.ContentMappers[0].Definition.OutputExtensions[".vue"] = ".js" }}, {"mapper options", func(p *ParsedOptions) { p.ContentMappers[0].Options = []byte(`{}`) }}, {"mapper name", func(p *ParsedOptions) { p.ContentMappers[0].Name = "other" }}, {"mapper version", func(p *ParsedOptions) { p.ContentMappers[0].Version = "2" }}, {"mapper exec", func(p *ParsedOptions) { p.ContentMappers[0].Exec[1] = "other.js" }}, {"mapper compiler options", func(p *ParsedOptions) { p.ContentMappers[0].CompilerOptions[0] = "jsx" }}, {"mapper dynamic config", func(p *ParsedOptions) { p.ContentMappers[0].DynamicConfig = false }}, + {"manifest output extension", func(p *ParsedOptions) { p.ContentMappers[0].Manifest.DefaultOutputExtensions[".vue"] = ".cjs" }}, {"mapper directory", func(p *ParsedOptions) { p.ContentMappers[0].PackageDirectory = "/other" }}, {"mapper contribution", func(p *ParsedOptions) { p.ContentMappers[0].ContributionID = "other" }}, {"nil mapper", func(p *ParsedOptions) { p.ContentMappers[0] = nil }}, diff --git a/tsc/internal/tsoptions/parsinghelpers.go b/tsc/internal/tsoptions/parsinghelpers.go index 53efb8061ae9d..cf3120be398fa 100644 --- a/tsc/internal/tsoptions/parsinghelpers.go +++ b/tsc/internal/tsoptions/parsinghelpers.go @@ -160,6 +160,13 @@ func parseContentMapper(value any) (*contentmapper.Mapper, []*ast.Diagnostic) { } else { errors = append(errors, ast.NewCompilerDiagnostic(diagnostics.Compiler_option_0_requires_a_value_of_type_1, "contentMapper.extensions", "string[]")) } + if outputExtensions, ok := v.Get("outputExtensions"); ok { + if values, isStringMap := parseStringStringMapStrict(outputExtensions); isStringMap { + mapper.Definition.OutputExtensions = values + } else { + errors = append(errors, ast.NewCompilerDiagnostic(diagnostics.Compiler_option_0_requires_a_value_of_type_1, "contentMapper.outputExtensions", "object")) + } + } if options, ok := v.Get("options"); ok { if _, isObject := options.(*collections.OrderedMap[string, any]); !isObject { errors = append(errors, ast.NewCompilerDiagnostic(diagnostics.Compiler_option_0_requires_a_value_of_type_1, "contentMapper.options", "object")) @@ -173,6 +180,22 @@ func parseContentMapper(value any) (*contentmapper.Mapper, []*ast.Diagnostic) { return mapper, errors } +func parseStringStringMapStrict(value any) (map[string]string, bool) { + object, ok := value.(*collections.OrderedMap[string, any]) + if !ok { + return nil, false + } + result := make(map[string]string, object.Size()) + for key, value := range object.Entries() { + str, ok := value.(string) + if !ok { + return nil, false + } + result[key] = str + } + return result, true +} + // parseStringArrayStrict returns the string slice and true only if value is an array whose // elements are all strings. A missing element or wrong element type yields false. func parseStringArrayStrict(value any) ([]string, bool) { diff --git a/tsc/internal/tsoptions/tsconfigparsing.go b/tsc/internal/tsoptions/tsconfigparsing.go index af1e72ac71017..7ead939d811f2 100644 --- a/tsc/internal/tsoptions/tsconfigparsing.go +++ b/tsc/internal/tsoptions/tsconfigparsing.go @@ -1299,7 +1299,6 @@ func parseJsonConfigFileContentWorker( } seenContentMapperExtensions := make(map[string]struct{}, totalContentMapperExtensions) contentMapperExtensions := make([]string, 0, totalContentMapperExtensions) - nativeExtensions := core.Flatten(tspath.AllSupportedExtensionsWithJson) canonicalExtension := func(extension string) string { return fs.CaseSensitivity().Canonicalize(extension) } @@ -1311,9 +1310,9 @@ func parseJsonConfigFileContentWorker( switch { case !strings.HasPrefix(ext, "."): errors = append(errors, setContentMapperDiagnosticLocation(ast.NewCompilerDiagnostic(diagnostics.Content_mapper_file_extension_0_must_begin_with_a, ext), contentMapperSourceFile, extNode)) - case slices.ContainsFunc(nativeExtensions, func(nativeExtension string) bool { - return strings.EqualFold(nativeExtension, ext) - }): + case strings.ContainsAny(ext, "/\\"): + errors = append(errors, setContentMapperDiagnosticLocation(ast.NewCompilerDiagnostic(diagnostics.Content_mapper_extension_0_must_not_contain_path_separators, ext), contentMapperSourceFile, extNode)) + case contentmapper.HasReservedSourceExtension(ext): errors = append(errors, setContentMapperDiagnosticLocation(ast.NewCompilerDiagnostic(diagnostics.Content_mapper_file_extension_0_is_a_built_in_extension_and_cannot_be_registered_by_a_content_mapper, ext), contentMapperSourceFile, extNode)) default: if _, seen := seenContentMapperExtensions[canonicalExt]; seen { @@ -1326,6 +1325,25 @@ func parseJsonConfigFileContentWorker( } } mapper.Definition.Extensions = validExtensions + for _, sourceExtension := range slices.Sorted(maps.Keys(mapper.Definition.OutputExtensions)) { + outputExtension := mapper.Definition.OutputExtensions[sourceExtension] + outputExtensionsNode := getContentMapperSyntax(contentMapperSourceFile, contentMapperIndices[j], "outputExtensions") + if strings.ContainsAny(sourceExtension, "/\\") { + errors = append(errors, setContentMapperDiagnosticLocation(ast.NewCompilerDiagnostic(diagnostics.Content_mapper_extension_0_must_not_contain_path_separators, sourceExtension), contentMapperSourceFile, outputExtensionsNode)) + } else if !contentmapper.IsValidExtension(sourceExtension) { + errors = append(errors, setContentMapperDiagnosticLocation(ast.NewCompilerDiagnostic(diagnostics.Content_mapper_output_extension_0_must_be_non_empty_and_begin_with_a, sourceExtension), contentMapperSourceFile, outputExtensionsNode)) + } + if strings.ContainsAny(outputExtension, "/\\") { + errors = append(errors, setContentMapperDiagnosticLocation(ast.NewCompilerDiagnostic(diagnostics.Content_mapper_extension_0_must_not_contain_path_separators, outputExtension), contentMapperSourceFile, outputExtensionsNode)) + } else if !contentmapper.IsValidExtension(outputExtension) { + errors = append(errors, setContentMapperDiagnosticLocation(ast.NewCompilerDiagnostic(diagnostics.Content_mapper_output_extension_0_must_be_non_empty_and_begin_with_a, outputExtension), contentMapperSourceFile, outputExtensionsNode)) + } + if !slices.ContainsFunc(validExtensions, func(extension string) bool { + return canonicalExtension(extension) == canonicalExtension(sourceExtension) + }) { + errors = append(errors, setContentMapperDiagnosticLocation(ast.NewCompilerDiagnostic(diagnostics.Content_mapper_output_extension_mapping_source_0_is_not_registered_by_this_content_mapper, sourceExtension), contentMapperSourceFile, outputExtensionsNode)) + } + } } if len(contentMappers) > 0 && !(parsedConfig.options != nil && parsedConfig.options.RunExternalCode.IsTrue()) { errors = append(errors, setContentMapperDiagnosticLocation(ast.NewCompilerDiagnostic(diagnostics.Content_mappers_require_the_runExternalCode_command_line_flag_to_be_enabled), contentMapperSourceFile, getContentMappersKeySyntax(contentMapperSourceFile))) @@ -1349,6 +1367,17 @@ func parseJsonConfigFileContentWorker( continue } mapper.Manifest = manifest + for _, sourceExtension := range mapper.Definition.Extensions { + outputExtension, ok := getContentMapperOutputExtension(mapper.EffectiveOutputExtensions(), sourceExtension, fs.CaseSensitivity().IsCaseInsensitive()) + identity := sourceExtension == outputExtension || fs.CaseSensitivity().IsCaseInsensitive() && strings.EqualFold(sourceExtension, outputExtension) + if ok && !identity && !parsedConfig.options.RewriteRelativeImportExtensions.IsTrue() { + errors = append(errors, setContentMapperDiagnosticLocation( + ast.NewCompilerDiagnostic(diagnostics.Content_mapper_output_extension_mapping_from_0_to_1_requires_rewriteRelativeImportExtensions_to_be_enabled, sourceExtension, outputExtension), + contentMapperSourceFile, + getContentMapperSyntax(contentMapperSourceFile, contentMapperIndices[j], ""), + )) + } + } resolvedContentMappers = append(resolvedContentMappers, mapper) } contentMappers = resolvedContentMappers diff --git a/tsc/internal/tsoptions/tsconfigparsing_test.go b/tsc/internal/tsoptions/tsconfigparsing_test.go index 586902b5a24c5..c91042aebfe34 100644 --- a/tsc/internal/tsoptions/tsconfigparsing_test.go +++ b/tsc/internal/tsoptions/tsconfigparsing_test.go @@ -1325,6 +1325,36 @@ func TestContentMappersValidation(t *testing.T) { contentMappers: `[{ "package": "vue-mapper", "extensions": ["vue"] }]`, expectedCode: diagnostics.Content_mapper_file_extension_0_must_begin_with_a.Code(), }, + { + name: "source extension contains slash", + contentMappers: `[{ "package": "x", "extensions": [".vue/foo"] }]`, + expectedCode: diagnostics.Content_mapper_extension_0_must_not_contain_path_separators.Code(), + }, + { + name: "source extension contains backslash", + contentMappers: `[{ "package": "x", "extensions": [".vue\\foo"] }]`, + expectedCode: diagnostics.Content_mapper_extension_0_must_not_contain_path_separators.Code(), + }, + { + name: "output extension key contains slash", + contentMappers: `[{ "package": "x", "extensions": [".vue"], "outputExtensions": {".vue/foo": ".js"} }]`, + expectedCode: diagnostics.Content_mapper_extension_0_must_not_contain_path_separators.Code(), + }, + { + name: "output extension key contains backslash", + contentMappers: `[{ "package": "x", "extensions": [".vue"], "outputExtensions": {".vue\\foo": ".js"} }]`, + expectedCode: diagnostics.Content_mapper_extension_0_must_not_contain_path_separators.Code(), + }, + { + name: "output extension value contains slash", + contentMappers: `[{ "package": "x", "extensions": [".vue"], "outputExtensions": {".vue": ".js/foo"} }]`, + expectedCode: diagnostics.Content_mapper_extension_0_must_not_contain_path_separators.Code(), + }, + { + name: "output extension value contains backslash", + contentMappers: `[{ "package": "x", "extensions": [".vue"], "outputExtensions": {".vue": ".js\\foo"} }]`, + expectedCode: diagnostics.Content_mapper_extension_0_must_not_contain_path_separators.Code(), + }, { name: "built-in extension", contentMappers: `[{ "package": "x", "extensions": [".ts"] }]`, @@ -1484,6 +1514,43 @@ func TestContentMapperExtensionValidationUsesHostCaseSensitivity(t *testing.T) { } } +func TestContentMapperCompoundExtensionValidation(t *testing.T) { + t.Parallel() + for _, caseSensitivity := range []tspath.CaseSensitivity{tspath.CaseInsensitive, tspath.CaseSensitive} { + extensions := []string{".y.z", ".component.tsx.vue"} + for _, extension := range core.Flatten(tspath.AllSupportedExtensionsWithJson) { + extensions = append(extensions, ".component"+extension, ".component"+strings.ToUpper(extension)) + } + for _, extension := range extensions { + t.Run(fmt.Sprintf("%s/caseSensitive=%t", extension, caseSensitivity.IsCaseSensitive()), func(t *testing.T) { + t.Parallel() + files := map[string]string{ + "/tsconfig.json": fmt.Sprintf(`{ "contentMappers": [{ "package": "mapper", "extensions": [%q] }] }`, extension), + "/app.ts": "export {};", + "/node_modules/mapper/package.json": `{ + "name": "mapper", + "typescript": { "contentMapper": { "exec": ["mapper"] } } + }`, + } + fs := tsoptionstest.NewVFS(files, caseSensitivity) + config := testConfig{ + jsonText: files["/tsconfig.json"], configFileName: "tsconfig.json", basePath: "/", + allFileList: files, existingOptions: &core.CompilerOptions{RunExternalCode: core.TSTrue}, + } + parsed := getParsedWithJsonSourceFileApi(config, fs, config.basePath) + if extension == ".y.z" || extension == ".component.tsx.vue" { + assert.Equal(t, len(parsed.Errors), 0) + assert.DeepEqual(t, parsed.ContentMapperExtensions(), []string{extension}) + } else { + assert.Equal(t, len(parsed.Errors), 1) + assert.Equal(t, parsed.Errors[0].Code(), diagnostics.Content_mapper_file_extension_0_is_a_built_in_extension_and_cannot_be_registered_by_a_content_mapper.Code()) + assert.Equal(t, len(parsed.ContentMapperExtensions()), 0) + } + }) + } + } +} + func getParsedWithJsonSourceFileApi(config testConfig, fs vfs.FS, basePath tspath.RootedDirectoryPath) *tsoptions.ParsedCommandLine { configFileName := tspath.ToRootedFilePath(config.configFileName, basePath) path := fs.CaseSensitivity().PathKey(tspath.RootedPath(configFileName)) diff --git a/tsc/internal/tspath/extensionrewrite.go b/tsc/internal/tspath/extensionrewrite.go new file mode 100644 index 0000000000000..3802d2648f060 --- /dev/null +++ b/tsc/internal/tspath/extensionrewrite.go @@ -0,0 +1,55 @@ +package tspath + +type ExtensionRewrite struct { + Source string + Target string +} + +func getExtensionRewrite(path string, rewrites []ExtensionRewrite, caseSensitivity CaseSensitivity) (ExtensionRewrite, bool) { + var best ExtensionRewrite + equal := caseSensitivity.getEqualityComparer() + for _, rewrite := range rewrites { + if len(rewrite.Source) <= len(best.Source) || len(path) <= len(rewrite.Source) { + continue + } + if equal(path[len(path)-len(rewrite.Source):], rewrite.Source) { + best = rewrite + } + } + return best, best.Source != "" +} + +func (r ExtensionRewrite) isIdentity(caseSensitivity CaseSensitivity) bool { + return caseSensitivity.getEqualityComparer()(r.Source, r.Target) +} + +func (f RootedFilePath) GetExtensionRewrite(rewrites []ExtensionRewrite, caseSensitivity CaseSensitivity) (ExtensionRewrite, bool) { + return getExtensionRewrite(f.AsString(), rewrites, caseSensitivity) +} + +func (f RootedFilePath) RewriteExtension(rewrites []ExtensionRewrite, caseSensitivity CaseSensitivity) (RootedFilePath, bool) { + rewrite, ok := f.GetExtensionRewrite(rewrites, caseSensitivity) + if !ok || rewrite.isIdentity(caseSensitivity) { + return f, false + } + suffix := f.AsString()[len(f.AsString())-len(rewrite.Source):] + return f.RemoveExtension(suffix).AppendSuffix(rewrite.Target), true +} + +func (s ModuleSpecifier) RewriteExtension(rewrites []ExtensionRewrite, caseSensitivity CaseSensitivity) (ModuleSpecifier, bool) { + rewrite, ok := getExtensionRewrite(s.AsString(), rewrites, caseSensitivity) + if !ok || rewrite.isIdentity(caseSensitivity) { + return s, false + } + return ToModuleSpecifier(s.AsString()[:len(s.AsString())-len(rewrite.Source)] + rewrite.Target), true +} + +func (s ModuleSpecifier) ShouldRewriteExtension(rewrites []ExtensionRewrite, caseSensitivity CaseSensitivity) bool { + if !s.IsRelative() || IsDeclarationFileName(s.AsString()) { + return false + } + if rewrite, ok := getExtensionRewrite(s.AsString(), rewrites, caseSensitivity); ok { + return !rewrite.isIdentity(caseSensitivity) + } + return HasTSFileExtension(s.AsString()) +} diff --git a/tsc/internal/tspath/extensionrewrite_test.go b/tsc/internal/tspath/extensionrewrite_test.go new file mode 100644 index 0000000000000..02c0581e0f318 --- /dev/null +++ b/tsc/internal/tspath/extensionrewrite_test.go @@ -0,0 +1,79 @@ +package tspath_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/tspath" + "gotest.tools/v3/assert" +) + +func TestRewriteExtension(t *testing.T) { + t.Parallel() + rewrites := []tspath.ExtensionRewrite{ + {Source: ".z", Target: ".js"}, + {Source: ".y.z", Target: ".mjs"}, + } + + rewritten, ok := tspath.ToModuleSpecifier("./Widget.y.z").RewriteExtension(rewrites, tspath.CaseSensitive) + assert.Assert(t, ok) + assert.Equal(t, rewritten, tspath.ToModuleSpecifier("./Widget.mjs")) + + rewritten, ok = tspath.ToModuleSpecifier("./Widget.Y.Z").RewriteExtension(rewrites, tspath.CaseInsensitive) + assert.Assert(t, ok) + assert.Equal(t, rewritten, tspath.ToModuleSpecifier("./Widget.mjs")) + + rewritten, ok = tspath.ToModuleSpecifier(".y").RewriteExtension(rewrites, tspath.CaseSensitive) + assert.Assert(t, !ok) + assert.Equal(t, rewritten, tspath.ToModuleSpecifier(".y")) + + rewrites = append(rewrites, tspath.ExtensionRewrite{Source: ".identity.y.z", Target: ".identity.y.z"}) + rewritten, ok = tspath.ToModuleSpecifier("./Widget.IDENTITY.Y.Z").RewriteExtension(rewrites, tspath.CaseInsensitive) + assert.Assert(t, !ok) + assert.Equal(t, rewritten, tspath.ToModuleSpecifier("./Widget.IDENTITY.Y.Z")) + assert.Assert(t, !tspath.ToModuleSpecifier("./Widget.identity.y.z").ShouldRewriteExtension(rewrites, tspath.CaseSensitive)) +} + +func TestRootedFilePathRewriteExtension(t *testing.T) { + t.Parallel() + rewrites := []tspath.ExtensionRewrite{ + {Source: ".z", Target: ".js"}, + {Source: ".y.z", Target: ".mjs"}, + {Source: ".identity.y.z", Target: ".identity.y.z"}, + } + for _, test := range []struct { + path, expected string + caseSensitivity tspath.CaseSensitivity + changed bool + }{ + {"/src/Widget.y.z", "/src/Widget.mjs", tspath.CaseSensitive, true}, + {"/src/Widget.Y.Z", "/src/Widget.mjs", tspath.CaseInsensitive, true}, + {"/src/Widget.Y.Z", "/src/Widget.Y.Z", tspath.CaseSensitive, false}, + {"/src/Widget.IDENTITY.Y.Z", "/src/Widget.IDENTITY.Y.Z", tspath.CaseInsensitive, false}, + {"/src/Widget.txt", "/src/Widget.txt", tspath.CaseSensitive, false}, + } { + file := tspath.RootedFilePathFromNormalized(test.path) + rewritten, changed := file.RewriteExtension(rewrites, test.caseSensitivity) + assert.Equal(t, rewritten, tspath.RootedFilePathFromNormalized(test.expected)) + assert.Equal(t, changed, test.changed) + } +} + +func TestModuleSpecifierShouldRewriteExtension(t *testing.T) { + t.Parallel() + rewrites := []tspath.ExtensionRewrite{{Source: ".astro", Target: ".js"}} + for _, test := range []struct { + specifier string + expected bool + }{ + {"./Card.astro", true}, + {"../Card.astro", true}, + {"./file.ts", true}, + {"./file.d.ts", false}, + {"./file.d.astro.ts", false}, + {"./file.js", false}, + {"package.astro", false}, + {"/Card.astro", false}, + } { + assert.Equal(t, tspath.ToModuleSpecifier(test.specifier).ShouldRewriteExtension(rewrites, tspath.CaseSensitive), test.expected) + } +} diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.contentmapper new file mode 100644 index 0000000000000..0b70a1a5b418d --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.contentmapper @@ -0,0 +1,19 @@ +//// [/Card.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro .y.z]) +--- Original --- +export interface CardProps { title: string } +export declare const Card: (props: CardProps) => string; +--- Transformed --- +const __VERSION = "1.0.0"; +export interface CardProps { title: string } +export declare const Card: (props: CardProps) => string; + +//// [/Widget.y.z] (ScriptKind: ScriptKindTS, ContentMapper: [.astro .y.z]) +--- Original --- +export declare const Widget: () => string; +--- Transformed --- +const __VERSION = "1.0.0"; +export declare const Widget: () => string; + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.js new file mode 100644 index 0000000000000..081a200e3a1e1 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.js @@ -0,0 +1,92 @@ +//// [tests/cases/compiler/contentMapperOutputExtensions.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js", ".y.z": ".mjs" } + } + } +} + +//// [Card.astro] +const __VERSION = "1.0.0"; +export interface CardProps { title: string } +export declare const Card: (props: CardProps) => string; + +//// [Widget.y.z] +const __VERSION = "1.0.0"; +export declare const Widget: () => string; + +//// [typed.ts] +export const annotated: import("./Card.astro").CardProps = { title: "" }; + +//// [main.ts] +import { annotated } from "./typed"; +export { Card } from "./Card.astro"; +export type { CardProps } from "./Card.astro"; +export { Widget } from "./Widget.y.z"; + +const path = "./Card.astro"; +void import(path); + +export async function load() { return import("./Card.astro"); } +export const inferred = import("./Widget.y.z"); +export const nested = { load: () => import("./Card.astro") }; +export const copied = [annotated]; + + +//// [typed.js] +export const annotated = { title: "" }; +//// [main.js] +var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { + if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } + return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { + return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); + }); + } + return path; +}; +import { annotated } from "./typed"; +export { Card } from "./Card.js"; +export { Widget } from "./Widget.mjs"; +const path = "./Card.astro"; +void import(__rewriteRelativeImportExtension(path, false, { ".astro": ".js", ".y.z": ".mjs" })); +export async function load() { return import("./Card.js"); } +export const inferred = import("./Widget.mjs"); +export const nested = { load: () => import("./Card.js") }; +export const copied = [annotated]; + + +//// [Card.d.ts] +export interface CardProps { + title: string; +} +export declare const Card: (props: CardProps) => string; +//# sourceMappingURL=Card.d.ts.map//// [Widget.d.mts] +export declare const Widget: () => string; +//# sourceMappingURL=Widget.d.mts.map//// [typed.d.ts] +export declare const annotated: import("./Card.js").CardProps; +//# sourceMappingURL=typed.d.ts.map//// [main.d.ts] +export { Card } from "./Card.js"; +export type { CardProps } from "./Card.js"; +export { Widget } from "./Widget.mjs"; +export declare function load(): Promise; +export declare const inferred: Promise; +export declare const nested: { + load: () => Promise; +}; +export declare const copied: import("./Card.js").CardProps[]; +//# sourceMappingURL=main.d.ts.map \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.js.map b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.js.map new file mode 100644 index 0000000000000..a53aa97c5e4db --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.js.map @@ -0,0 +1,8 @@ +//// [Card.d.ts.map] +{"version":3,"file":"Card.d.ts","sources":["../Card.astro"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IAAG,KAAK,EAAE,MAAM,CAAA;CAAE;AAC5C,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,MAAM,CAAC"} +//// [Widget.d.mts.map] +{"version":3,"file":"Widget.d.mts","sources":["../Widget.y.z"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,CAAC,MAAM,MAAM,EAAE,MAAM,MAAM,CAAC"} +//// [main.d.ts.map] +{"version":3,"file":"main.d.ts","sources":["../main.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAc,CAAC;AACpC,YAAY,EAAE,SAAS,EAAE,MAAM,WAAc,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAKtC,wBAAsB,IAAI,wCAAqC;AAC/D,eAAO,MAAM,QAAQ,wCAAyB,CAAC;AAC/C,eAAO,MAAM,MAAM;IAAK,IAAI;CAAgC,CAAC;AAC7D,eAAO,MAAM,MAAM,iCAAc,CAAC"} +//// [typed.d.ts.map] +{"version":3,"file":"typed.d.ts","sources":["../typed.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,EAAE,OAAO,WAAc,EAAE,SAAyB,CAAC"} \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.sourcemap.txt b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.sourcemap.txt new file mode 100644 index 0000000000000..8872ed9edb086 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.sourcemap.txt @@ -0,0 +1,371 @@ +=================================================================== +JsFile: Card.d.ts +mapUrl: Card.d.ts.map +sourceRoot: +sources: ../Card.astro +=================================================================== +------------------------------------------------------------------- +emittedFile:/dist/Card.d.ts +sourceFile:../Card.astro +------------------------------------------------------------------- +>>>export interface CardProps { +1 > +2 >^^^^^^ +3 > ^^^^^^^^^^^ +4 > ^^^^^^^^^ +1 > +2 >export +3 > interface +4 > CardProps +1 >Emitted(1, 1) Source(1, 1) + SourceIndex(0) +2 >Emitted(1, 7) Source(1, 7) + SourceIndex(0) +3 >Emitted(1, 18) Source(1, 18) + SourceIndex(0) +4 >Emitted(1, 27) Source(1, 27) + SourceIndex(0) +--- +>>> title: string; +1 >^^^^ +2 > ^^^^^ +3 > ^^ +4 > ^^^^^^ +5 > ^ +1 > { +2 > title +3 > : +4 > string +5 > +1 >Emitted(2, 5) Source(1, 30) + SourceIndex(0) +2 >Emitted(2, 10) Source(1, 35) + SourceIndex(0) +3 >Emitted(2, 12) Source(1, 37) + SourceIndex(0) +4 >Emitted(2, 18) Source(1, 43) + SourceIndex(0) +5 >Emitted(2, 19) Source(1, 43) + SourceIndex(0) +--- +>>>} +1 >^ +2 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-> +1 > } +1 >Emitted(3, 2) Source(1, 45) + SourceIndex(0) +--- +>>>export declare const Card: (props: CardProps) => string; +1-> +2 >^^^^^^ +3 > ^ +4 > ^^^^^^^ +5 > ^ +6 > ^^^^^^ +7 > ^^^^ +8 > ^^ +9 > ^ +10> ^^^^^ +11> ^^ +12> ^^^^^^^^^ +13> ^^^^^ +14> ^^^^^^ +15> ^ +1-> + > +2 >export +3 > +4 > declare +5 > +6 > const +7 > Card +8 > : +9 > ( +10> props +11> : +12> CardProps +13> ) => +14> string +15> ; +1->Emitted(4, 1) Source(2, 1) + SourceIndex(0) +2 >Emitted(4, 7) Source(2, 7) + SourceIndex(0) +3 >Emitted(4, 8) Source(2, 8) + SourceIndex(0) +4 >Emitted(4, 15) Source(2, 15) + SourceIndex(0) +5 >Emitted(4, 16) Source(2, 16) + SourceIndex(0) +6 >Emitted(4, 22) Source(2, 22) + SourceIndex(0) +7 >Emitted(4, 26) Source(2, 26) + SourceIndex(0) +8 >Emitted(4, 28) Source(2, 28) + SourceIndex(0) +9 >Emitted(4, 29) Source(2, 29) + SourceIndex(0) +10>Emitted(4, 34) Source(2, 34) + SourceIndex(0) +11>Emitted(4, 36) Source(2, 36) + SourceIndex(0) +12>Emitted(4, 45) Source(2, 45) + SourceIndex(0) +13>Emitted(4, 50) Source(2, 50) + SourceIndex(0) +14>Emitted(4, 56) Source(2, 56) + SourceIndex(0) +15>Emitted(4, 57) Source(2, 57) + SourceIndex(0) +--- +>>>//# sourceMappingURL=Card.d.ts.map=================================================================== +JsFile: Widget.d.mts +mapUrl: Widget.d.mts.map +sourceRoot: +sources: ../Widget.y.z +=================================================================== +------------------------------------------------------------------- +emittedFile:/dist/Widget.d.mts +sourceFile:../Widget.y.z +------------------------------------------------------------------- +>>>export declare const Widget: () => string; +1 > +2 >^^^^^^ +3 > ^ +4 > ^^^^^^^ +5 > ^ +6 > ^^^^^^ +7 > ^^^^^^ +8 > ^^ +9 > ^^^^^^ +10> ^^^^^^ +11> ^ +1 > +2 >export +3 > +4 > declare +5 > +6 > const +7 > Widget +8 > : +9 > () => +10> string +11> ; +1 >Emitted(1, 1) Source(1, 1) + SourceIndex(0) +2 >Emitted(1, 7) Source(1, 7) + SourceIndex(0) +3 >Emitted(1, 8) Source(1, 8) + SourceIndex(0) +4 >Emitted(1, 15) Source(1, 15) + SourceIndex(0) +5 >Emitted(1, 16) Source(1, 16) + SourceIndex(0) +6 >Emitted(1, 22) Source(1, 22) + SourceIndex(0) +7 >Emitted(1, 28) Source(1, 28) + SourceIndex(0) +8 >Emitted(1, 30) Source(1, 30) + SourceIndex(0) +9 >Emitted(1, 36) Source(1, 36) + SourceIndex(0) +10>Emitted(1, 42) Source(1, 42) + SourceIndex(0) +11>Emitted(1, 43) Source(1, 43) + SourceIndex(0) +--- +>>>//# sourceMappingURL=Widget.d.mts.map=================================================================== +JsFile: typed.d.ts +mapUrl: typed.d.ts.map +sourceRoot: +sources: ../typed.ts +=================================================================== +------------------------------------------------------------------- +emittedFile:/dist/typed.d.ts +sourceFile:../typed.ts +------------------------------------------------------------------- +>>>export declare const annotated: import("./Card.js").CardProps; +1 > +2 >^^^^^^^^^^^^^^^ +3 > ^^^^^^ +4 > ^^^^^^^^^ +5 > ^^ +6 > ^^^^^^^ +7 > ^^^^^^^^^^^ +8 > ^^ +9 > ^^^^^^^^^ +10> ^ +1 > +2 >export +3 > const +4 > annotated +5 > : +6 > import( +7 > "./Card.astro" +8 > ). +9 > CardProps = { title: "" } +10> ; +1 >Emitted(1, 1) Source(1, 1) + SourceIndex(0) +2 >Emitted(1, 16) Source(1, 8) + SourceIndex(0) +3 >Emitted(1, 22) Source(1, 14) + SourceIndex(0) +4 >Emitted(1, 31) Source(1, 23) + SourceIndex(0) +5 >Emitted(1, 33) Source(1, 25) + SourceIndex(0) +6 >Emitted(1, 40) Source(1, 32) + SourceIndex(0) +7 >Emitted(1, 51) Source(1, 46) + SourceIndex(0) +8 >Emitted(1, 53) Source(1, 48) + SourceIndex(0) +9 >Emitted(1, 62) Source(1, 73) + SourceIndex(0) +10>Emitted(1, 63) Source(1, 74) + SourceIndex(0) +--- +>>>//# sourceMappingURL=typed.d.ts.map=================================================================== +JsFile: main.d.ts +mapUrl: main.d.ts.map +sourceRoot: +sources: ../main.ts +=================================================================== +------------------------------------------------------------------- +emittedFile:/dist/main.d.ts +sourceFile:../main.ts +------------------------------------------------------------------- +>>>export { Card } from "./Card.js"; +1 > +2 >^^^^^^^ +3 > ^^ +4 > ^^^^ +5 > ^^ +6 > ^^^^^^ +7 > ^^^^^^^^^^^ +8 > ^ +9 > ^^^^^^^^^^^-> +1 >import { annotated } from "./typed"; + > +2 >export +3 > { +4 > Card +5 > } +6 > from +7 > "./Card.astro" +8 > ; +1 >Emitted(1, 1) Source(2, 1) + SourceIndex(0) +2 >Emitted(1, 8) Source(2, 8) + SourceIndex(0) +3 >Emitted(1, 10) Source(2, 10) + SourceIndex(0) +4 >Emitted(1, 14) Source(2, 14) + SourceIndex(0) +5 >Emitted(1, 16) Source(2, 16) + SourceIndex(0) +6 >Emitted(1, 22) Source(2, 22) + SourceIndex(0) +7 >Emitted(1, 33) Source(2, 36) + SourceIndex(0) +8 >Emitted(1, 34) Source(2, 37) + SourceIndex(0) +--- +>>>export type { CardProps } from "./Card.js"; +1-> +2 >^^^^^^^^^^^^ +3 > ^^ +4 > ^^^^^^^^^ +5 > ^^ +6 > ^^^^^^ +7 > ^^^^^^^^^^^ +8 > ^ +1-> + > +2 >export type +3 > { +4 > CardProps +5 > } +6 > from +7 > "./Card.astro" +8 > ; +1->Emitted(2, 1) Source(3, 1) + SourceIndex(0) +2 >Emitted(2, 13) Source(3, 13) + SourceIndex(0) +3 >Emitted(2, 15) Source(3, 15) + SourceIndex(0) +4 >Emitted(2, 24) Source(3, 24) + SourceIndex(0) +5 >Emitted(2, 26) Source(3, 26) + SourceIndex(0) +6 >Emitted(2, 32) Source(3, 32) + SourceIndex(0) +7 >Emitted(2, 43) Source(3, 46) + SourceIndex(0) +8 >Emitted(2, 44) Source(3, 47) + SourceIndex(0) +--- +>>>export { Widget } from "./Widget.mjs"; +1 > +2 >^^^^^^^ +3 > ^^ +4 > ^^^^^^ +5 > ^^ +6 > ^^^^^^ +7 > ^^^^^^^^^^^^^^ +8 > ^ +9 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-> +1 > + > +2 >export +3 > { +4 > Widget +5 > } +6 > from +7 > "./Widget.y.z" +8 > ; +1 >Emitted(3, 1) Source(4, 1) + SourceIndex(0) +2 >Emitted(3, 8) Source(4, 8) + SourceIndex(0) +3 >Emitted(3, 10) Source(4, 10) + SourceIndex(0) +4 >Emitted(3, 16) Source(4, 16) + SourceIndex(0) +5 >Emitted(3, 18) Source(4, 18) + SourceIndex(0) +6 >Emitted(3, 24) Source(4, 24) + SourceIndex(0) +7 >Emitted(3, 38) Source(4, 38) + SourceIndex(0) +8 >Emitted(3, 39) Source(4, 39) + SourceIndex(0) +--- +>>>export declare function load(): Promise; +1-> +2 >^^^^^^^^^^^^^^^^^^^^^^^^ +3 > ^^^^ +4 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +5 > ^^^-> +1-> + > + >const path = "./Card.astro"; + >void import(path); + > + > +2 >export async function +3 > load +4 > () { return import("./Card.astro"); } +1->Emitted(4, 1) Source(9, 1) + SourceIndex(0) +2 >Emitted(4, 25) Source(9, 23) + SourceIndex(0) +3 >Emitted(4, 29) Source(9, 27) + SourceIndex(0) +4 >Emitted(4, 69) Source(9, 64) + SourceIndex(0) +--- +>>>export declare const inferred: Promise; +1-> +2 >^^^^^^^^^^^^^^^ +3 > ^^^^^^ +4 > ^^^^^^^^ +5 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +6 > ^ +1-> + > +2 >export +3 > const +4 > inferred +5 > = import("./Widget.y.z") +6 > ; +1->Emitted(5, 1) Source(10, 1) + SourceIndex(0) +2 >Emitted(5, 16) Source(10, 8) + SourceIndex(0) +3 >Emitted(5, 22) Source(10, 14) + SourceIndex(0) +4 >Emitted(5, 30) Source(10, 22) + SourceIndex(0) +5 >Emitted(5, 70) Source(10, 47) + SourceIndex(0) +6 >Emitted(5, 71) Source(10, 48) + SourceIndex(0) +--- +>>>export declare const nested: { +1 > +2 >^^^^^^^^^^^^^^^ +3 > ^^^^^^ +4 > ^^^^^^ +5 > ^^^^^^^^^^^^^^^^^^^^^^^^^^-> +1 > + > +2 >export +3 > const +4 > nested +1 >Emitted(6, 1) Source(11, 1) + SourceIndex(0) +2 >Emitted(6, 16) Source(11, 8) + SourceIndex(0) +3 >Emitted(6, 22) Source(11, 14) + SourceIndex(0) +4 >Emitted(6, 28) Source(11, 20) + SourceIndex(0) +--- +>>> load: () => Promise; +1->^^^^ +2 > ^^^^ +1-> = { +2 > load +1->Emitted(7, 5) Source(11, 25) + SourceIndex(0) +2 >Emitted(7, 9) Source(11, 29) + SourceIndex(0) +--- +>>>}; +1 >^ +2 > ^ +3 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-> +1 >: () => import("./Card.astro") } +2 > ; +1 >Emitted(8, 2) Source(11, 61) + SourceIndex(0) +2 >Emitted(8, 3) Source(11, 62) + SourceIndex(0) +--- +>>>export declare const copied: import("./Card.js").CardProps[]; +1-> +2 >^^^^^^^^^^^^^^^ +3 > ^^^^^^ +4 > ^^^^^^ +5 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +6 > ^ +1-> + > +2 >export +3 > const +4 > copied +5 > = [annotated] +6 > ; +1->Emitted(9, 1) Source(12, 1) + SourceIndex(0) +2 >Emitted(9, 16) Source(12, 8) + SourceIndex(0) +3 >Emitted(9, 22) Source(12, 14) + SourceIndex(0) +4 >Emitted(9, 28) Source(12, 20) + SourceIndex(0) +5 >Emitted(9, 61) Source(12, 34) + SourceIndex(0) +6 >Emitted(9, 62) Source(12, 35) + SourceIndex(0) +--- +>>>//# sourceMappingURL=main.d.ts.map \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.symbols new file mode 100644 index 0000000000000..fa2590bec3d3e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.symbols @@ -0,0 +1,64 @@ +//// [tests/cases/compiler/contentMapperOutputExtensions.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Card.astro, 0, 5)) + +export interface CardProps { title: string } +>CardProps : Symbol(CardProps, Decl(Card.astro, 0, 26)) +>title : Symbol(CardProps.title, Decl(Card.astro, 1, 28)) + +export declare const Card: (props: CardProps) => string; +>Card : Symbol(Card, Decl(Card.astro, 2, 20)) +>props : Symbol(props, Decl(Card.astro, 2, 28)) +>CardProps : Symbol(CardProps, Decl(Card.astro, 0, 26)) + +=== /Widget.y.z === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Widget.y.z, 0, 5)) + +export declare const Widget: () => string; +>Widget : Symbol(Widget, Decl(Widget.y.z, 1, 20)) + +=== /typed.ts === +export const annotated: import("./Card.astro").CardProps = { title: "" }; +>annotated : Symbol(annotated, Decl(typed.ts, 0, 12)) +>CardProps : Symbol(CardProps, Decl(Card.astro, 0, 26)) +>title : Symbol(title, Decl(typed.ts, 0, 60)) + +=== /main.ts === +import { annotated } from "./typed"; +>annotated : Symbol(annotated, Decl(main.ts, 0, 8)) + +export { Card } from "./Card.astro"; +>Card : Symbol(Card, Decl(main.ts, 1, 8)) + +export type { CardProps } from "./Card.astro"; +>CardProps : Symbol(CardProps, Decl(main.ts, 2, 13)) + +export { Widget } from "./Widget.y.z"; +>Widget : Symbol(Widget, Decl(main.ts, 3, 8)) + +const path = "./Card.astro"; +>path : Symbol(path, Decl(main.ts, 5, 5)) + +void import(path); +>path : Symbol(path, Decl(main.ts, 5, 5)) + +export async function load() { return import("./Card.astro"); } +>load : Symbol(load, Decl(main.ts, 6, 18)) +>"./Card.astro" : Symbol("./Card.astro", Decl(Card.astro, 0, 0)) + +export const inferred = import("./Widget.y.z"); +>inferred : Symbol(inferred, Decl(main.ts, 9, 12)) +>"./Widget.y.z" : Symbol("./Widget.y.z", Decl(Widget.y.z, 0, 0)) + +export const nested = { load: () => import("./Card.astro") }; +>nested : Symbol(nested, Decl(main.ts, 10, 12)) +>load : Symbol(load, Decl(main.ts, 10, 23)) +>"./Card.astro" : Symbol("./Card.astro", Decl(Card.astro, 0, 0)) + +export const copied = [annotated]; +>copied : Symbol(copied, Decl(main.ts, 11, 12)) +>annotated : Symbol(annotated, Decl(main.ts, 0, 8)) + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.types new file mode 100644 index 0000000000000..802cb9c2a5077 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensions.types @@ -0,0 +1,74 @@ +//// [tests/cases/compiler/contentMapperOutputExtensions.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export interface CardProps { title: string } +>title : string + +export declare const Card: (props: CardProps) => string; +>Card : (props: CardProps) => string +>props : CardProps + +=== /Widget.y.z === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export declare const Widget: () => string; +>Widget : () => string + +=== /typed.ts === +export const annotated: import("./Card.astro").CardProps = { title: "" }; +>annotated : import("./Card.astro").CardProps +>{ title: "" } : { title: string; } +>title : string +>"" : "" + +=== /main.ts === +import { annotated } from "./typed"; +>annotated : import("./Card.astro").CardProps + +export { Card } from "./Card.astro"; +>Card : (props: import("./Card.astro").CardProps) => string + +export type { CardProps } from "./Card.astro"; +>CardProps : import("./Card.astro").CardProps + +export { Widget } from "./Widget.y.z"; +>Widget : () => string + +const path = "./Card.astro"; +>path : "./Card.astro" +>"./Card.astro" : "./Card.astro" + +void import(path); +>void import(path) : undefined +>import(path) : Promise +>path : "./Card.astro" + +export async function load() { return import("./Card.astro"); } +>load : () => Promise +>import("./Card.astro") : Promise +>"./Card.astro" : "./Card.astro" + +export const inferred = import("./Widget.y.z"); +>inferred : Promise +>import("./Widget.y.z") : Promise +>"./Widget.y.z" : "./Widget.y.z" + +export const nested = { load: () => import("./Card.astro") }; +>nested : { load: () => Promise; } +>{ load: () => import("./Card.astro") } : { load: () => Promise; } +>load : () => Promise +>() => import("./Card.astro") : () => Promise +>import("./Card.astro") : Promise +>"./Card.astro" : "./Card.astro" + +export const copied = [annotated]; +>copied : import("./Card.astro").CardProps[] +>[annotated] : import("./Card.astro").CardProps[] +>annotated : import("./Card.astro").CardProps + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.contentmapper new file mode 100644 index 0000000000000..3b7d61d741d22 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.contentmapper @@ -0,0 +1,10 @@ +//// [/component.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro]) +--- Original --- +export const fromAstro = true; +--- Transformed --- +const __VERSION = "1.0.0"; +export const fromAstro = true; + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.errors.txt b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.errors.txt new file mode 100644 index 0000000000000..f22b66a52e5a5 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.errors.txt @@ -0,0 +1,31 @@ +error TS5056: Cannot write file '/dist/component.js' because it would be overwritten by multiple input files. + + +!!! error TS5056: Cannot write file '/dist/component.js' because it would be overwritten by multiple input files. +==== /tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "module": "preserve", + "rewriteRelativeImportExtensions": true, + "outDir": "/dist" + }, + "contentMappers": [ + { "package": "mapper", "extensions": [".astro"] } + ] + } + +==== /component.ts (0 errors) ==== + export const fromTs = true; + +==== /node_modules/mapper/package.json (0 errors) ==== + { + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js" } + } + } + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.symbols new file mode 100644 index 0000000000000..2369c1dd03f53 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.symbols @@ -0,0 +1,13 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsCollision.ts] //// + +=== /component.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(component.astro, 0, 5)) + +export const fromAstro = true; +>fromAstro : Symbol(fromAstro, Decl(component.astro, 1, 12)) + +=== /component.ts === +export const fromTs = true; +>fromTs : Symbol(fromTs, Decl(component.ts, 0, 12)) + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.types new file mode 100644 index 0000000000000..476612e6568b0 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsCollision.types @@ -0,0 +1,16 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsCollision.ts] //// + +=== /component.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const fromAstro = true; +>fromAstro : true +>true : true + +=== /component.ts === +export const fromTs = true; +>fromTs : true +>true : true + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).contentmapper new file mode 100644 index 0000000000000..4e7e1b5fb81d3 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).contentmapper @@ -0,0 +1,10 @@ +//// [/Card.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro]) +--- Original --- +export const Card = 1; +--- Transformed --- +const __VERSION = "1.0.0"; +export const Card = 1; + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).errors.txt b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).errors.txt new file mode 100644 index 0000000000000..92a98bc9fc831 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).errors.txt @@ -0,0 +1,49 @@ +/main.ts(1,48): error TS2807: This syntax requires an imported helper named '__rewriteRelativeImportExtension' with 4 parameters, which is not compatible with the one in 'tslib'. Consider upgrading your version of 'tslib'. +/require.js(2,47): error TS2807: This syntax requires an imported helper named '__rewriteRelativeImportExtension' with 4 parameters, which is not compatible with the one in 'tslib'. Consider upgrading your version of 'tslib'. + + +==== /tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "target": "esnext", + "rewriteRelativeImportExtensions": true, + "importHelpers": true, + "allowJs": true, + "checkJs": true, + "outDir": "/dist" + }, + "contentMappers": [{ + "package": "mapper", + "extensions": [".astro"], + "outputExtensions": { ".astro": ".js" } + }] + } + +==== /main.ts (1 errors) ==== + export function dynamic(path: string) { return import(path); } + ~~~~~~~~~~~~ +!!! error TS2807: This syntax requires an imported helper named '__rewriteRelativeImportExtension' with 4 parameters, which is not compatible with the one in 'tslib'. Consider upgrading your version of 'tslib'. + export function literal() { return import("./Card.astro"); } + +==== /require.js (1 errors) ==== + /** @param {string} path */ + export function dynamicRequire(path) { return require(path); } + ~~~~~~~~~~~~~ +!!! error TS2807: This syntax requires an imported helper named '__rewriteRelativeImportExtension' with 4 parameters, which is not compatible with the one in 'tslib'. Consider upgrading your version of 'tslib'. + +==== /globals.d.ts (0 errors) ==== + declare function require(path: string): unknown; + +==== /node_modules/mapper/package.json (0 errors) ==== + { + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } + } + +==== /node_modules/tslib/package.json (0 errors) ==== + { "name": "tslib", "types": "index.d.ts" } + +==== /node_modules/tslib/index.d.ts (0 errors) ==== + export declare function __rewriteRelativeImportExtension(path: any, preserveJsx?: boolean): any; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).js new file mode 100644 index 0000000000000..a8c5ce2f09bd5 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).js @@ -0,0 +1,46 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpers.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +//// [package.json] +{ "name": "tslib", "types": "index.d.ts" } + +//// [index.d.ts] +export declare function __rewriteRelativeImportExtension(path: any, preserveJsx?: boolean): any; + +//// [Card.astro] +const __VERSION = "1.0.0"; +export const Card = 1; + +//// [main.ts] +export function dynamic(path: string) { return import(path); } +export function literal() { return import("./Card.astro"); } + +//// [require.js] +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } + +//// [globals.d.ts] +declare function require(path: string): unknown; + + +//// [main.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.dynamic = dynamic; +exports.literal = literal; +const tslib_1 = require("tslib"); +function dynamic(path) { return Promise.resolve(`${tslib_1.__rewriteRelativeImportExtension(path, false, { ".astro": ".js" })}`).then(s => tslib_1.__importStar(require(s))); } +function literal() { return Promise.resolve().then(() => tslib_1.__importStar(require("./Card.js"))); } +//// [require.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.dynamicRequire = dynamicRequire; +const tslib_1 = require("tslib"); +/** @param {string} path */ +function dynamicRequire(path) { return require(tslib_1.__rewriteRelativeImportExtension(path, false, { ".astro": ".js" })); } diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).symbols new file mode 100644 index 0000000000000..954c948bcadee --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).symbols @@ -0,0 +1,38 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpers.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Card.astro, 0, 5)) + +export const Card = 1; +>Card : Symbol(Card, Decl(Card.astro, 1, 12)) + +=== /main.ts === +export function dynamic(path: string) { return import(path); } +>dynamic : Symbol(dynamic, Decl(main.ts, 0, 0)) +>path : Symbol(path, Decl(main.ts, 0, 24)) +>path : Symbol(path, Decl(main.ts, 0, 24)) + +export function literal() { return import("./Card.astro"); } +>literal : Symbol(literal, Decl(main.ts, 0, 62)) +>"./Card.astro" : Symbol("./Card.astro", Decl(Card.astro, 0, 0)) + +=== /require.js === +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } +>dynamicRequire : Symbol(dynamicRequire, Decl(require.js, 0, 0)) +>path : Symbol(path, Decl(require.js, 1, 31)) +>require : Symbol(require, Decl(globals.d.ts, 0, 0)) +>path : Symbol(path, Decl(require.js, 1, 31)) + +=== /globals.d.ts === +declare function require(path: string): unknown; +>require : Symbol(require, Decl(globals.d.ts, 0, 0)) +>path : Symbol(path, Decl(globals.d.ts, 0, 25)) + +=== /node_modules/tslib/index.d.ts === +export declare function __rewriteRelativeImportExtension(path: any, preserveJsx?: boolean): any; +>__rewriteRelativeImportExtension : Symbol(__rewriteRelativeImportExtension, Decl(index.d.ts, 0, 0)) +>path : Symbol(path, Decl(index.d.ts, 0, 57)) +>preserveJsx : Symbol(preserveJsx, Decl(index.d.ts, 0, 67)) + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).types new file mode 100644 index 0000000000000..7d4f73e5ea937 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=commonjs).types @@ -0,0 +1,43 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpers.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const Card = 1; +>Card : 1 +>1 : 1 + +=== /main.ts === +export function dynamic(path: string) { return import(path); } +>dynamic : (path: string) => Promise +>path : string +>import(path) : Promise +>path : string + +export function literal() { return import("./Card.astro"); } +>literal : () => Promise +>import("./Card.astro") : Promise +>"./Card.astro" : "./Card.astro" + +=== /require.js === +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } +>dynamicRequire : (path: string) => unknown +>path : string +>require(path) : unknown +>require : (path: string) => unknown +>path : string + +=== /globals.d.ts === +declare function require(path: string): unknown; +>require : (path: string) => unknown +>path : string + +=== /node_modules/tslib/index.d.ts === +export declare function __rewriteRelativeImportExtension(path: any, preserveJsx?: boolean): any; +>__rewriteRelativeImportExtension : (path: any, preserveJsx?: boolean) => any +>path : any +>preserveJsx : boolean | undefined + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).contentmapper new file mode 100644 index 0000000000000..4e7e1b5fb81d3 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).contentmapper @@ -0,0 +1,10 @@ +//// [/Card.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro]) +--- Original --- +export const Card = 1; +--- Transformed --- +const __VERSION = "1.0.0"; +export const Card = 1; + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).errors.txt b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).errors.txt new file mode 100644 index 0000000000000..92a98bc9fc831 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).errors.txt @@ -0,0 +1,49 @@ +/main.ts(1,48): error TS2807: This syntax requires an imported helper named '__rewriteRelativeImportExtension' with 4 parameters, which is not compatible with the one in 'tslib'. Consider upgrading your version of 'tslib'. +/require.js(2,47): error TS2807: This syntax requires an imported helper named '__rewriteRelativeImportExtension' with 4 parameters, which is not compatible with the one in 'tslib'. Consider upgrading your version of 'tslib'. + + +==== /tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "target": "esnext", + "rewriteRelativeImportExtensions": true, + "importHelpers": true, + "allowJs": true, + "checkJs": true, + "outDir": "/dist" + }, + "contentMappers": [{ + "package": "mapper", + "extensions": [".astro"], + "outputExtensions": { ".astro": ".js" } + }] + } + +==== /main.ts (1 errors) ==== + export function dynamic(path: string) { return import(path); } + ~~~~~~~~~~~~ +!!! error TS2807: This syntax requires an imported helper named '__rewriteRelativeImportExtension' with 4 parameters, which is not compatible with the one in 'tslib'. Consider upgrading your version of 'tslib'. + export function literal() { return import("./Card.astro"); } + +==== /require.js (1 errors) ==== + /** @param {string} path */ + export function dynamicRequire(path) { return require(path); } + ~~~~~~~~~~~~~ +!!! error TS2807: This syntax requires an imported helper named '__rewriteRelativeImportExtension' with 4 parameters, which is not compatible with the one in 'tslib'. Consider upgrading your version of 'tslib'. + +==== /globals.d.ts (0 errors) ==== + declare function require(path: string): unknown; + +==== /node_modules/mapper/package.json (0 errors) ==== + { + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } + } + +==== /node_modules/tslib/package.json (0 errors) ==== + { "name": "tslib", "types": "index.d.ts" } + +==== /node_modules/tslib/index.d.ts (0 errors) ==== + export declare function __rewriteRelativeImportExtension(path: any, preserveJsx?: boolean): any; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).js new file mode 100644 index 0000000000000..ea10f0176051e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).js @@ -0,0 +1,39 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpers.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +//// [package.json] +{ "name": "tslib", "types": "index.d.ts" } + +//// [index.d.ts] +export declare function __rewriteRelativeImportExtension(path: any, preserveJsx?: boolean): any; + +//// [Card.astro] +const __VERSION = "1.0.0"; +export const Card = 1; + +//// [main.ts] +export function dynamic(path: string) { return import(path); } +export function literal() { return import("./Card.astro"); } + +//// [require.js] +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } + +//// [globals.d.ts] +declare function require(path: string): unknown; + + +//// [main.js] +import { __rewriteRelativeImportExtension } from "tslib"; +export function dynamic(path) { return import(__rewriteRelativeImportExtension(path, false, { ".astro": ".js" })); } +export function literal() { return import("./Card.js"); } +//// [require.js] +import { __rewriteRelativeImportExtension } from "tslib"; +/** @param {string} path */ +export function dynamicRequire(path) { return require(__rewriteRelativeImportExtension(path, false, { ".astro": ".js" })); } diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).symbols new file mode 100644 index 0000000000000..954c948bcadee --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).symbols @@ -0,0 +1,38 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpers.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Card.astro, 0, 5)) + +export const Card = 1; +>Card : Symbol(Card, Decl(Card.astro, 1, 12)) + +=== /main.ts === +export function dynamic(path: string) { return import(path); } +>dynamic : Symbol(dynamic, Decl(main.ts, 0, 0)) +>path : Symbol(path, Decl(main.ts, 0, 24)) +>path : Symbol(path, Decl(main.ts, 0, 24)) + +export function literal() { return import("./Card.astro"); } +>literal : Symbol(literal, Decl(main.ts, 0, 62)) +>"./Card.astro" : Symbol("./Card.astro", Decl(Card.astro, 0, 0)) + +=== /require.js === +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } +>dynamicRequire : Symbol(dynamicRequire, Decl(require.js, 0, 0)) +>path : Symbol(path, Decl(require.js, 1, 31)) +>require : Symbol(require, Decl(globals.d.ts, 0, 0)) +>path : Symbol(path, Decl(require.js, 1, 31)) + +=== /globals.d.ts === +declare function require(path: string): unknown; +>require : Symbol(require, Decl(globals.d.ts, 0, 0)) +>path : Symbol(path, Decl(globals.d.ts, 0, 25)) + +=== /node_modules/tslib/index.d.ts === +export declare function __rewriteRelativeImportExtension(path: any, preserveJsx?: boolean): any; +>__rewriteRelativeImportExtension : Symbol(__rewriteRelativeImportExtension, Decl(index.d.ts, 0, 0)) +>path : Symbol(path, Decl(index.d.ts, 0, 57)) +>preserveJsx : Symbol(preserveJsx, Decl(index.d.ts, 0, 67)) + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).types new file mode 100644 index 0000000000000..7d4f73e5ea937 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpers(module=preserve).types @@ -0,0 +1,43 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpers.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const Card = 1; +>Card : 1 +>1 : 1 + +=== /main.ts === +export function dynamic(path: string) { return import(path); } +>dynamic : (path: string) => Promise +>path : string +>import(path) : Promise +>path : string + +export function literal() { return import("./Card.astro"); } +>literal : () => Promise +>import("./Card.astro") : Promise +>"./Card.astro" : "./Card.astro" + +=== /require.js === +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } +>dynamicRequire : (path: string) => unknown +>path : string +>require(path) : unknown +>require : (path: string) => unknown +>path : string + +=== /globals.d.ts === +declare function require(path: string): unknown; +>require : (path: string) => unknown +>path : string + +=== /node_modules/tslib/index.d.ts === +export declare function __rewriteRelativeImportExtension(path: any, preserveJsx?: boolean): any; +>__rewriteRelativeImportExtension : (path: any, preserveJsx?: boolean) => any +>path : any +>preserveJsx : boolean | undefined + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).contentmapper new file mode 100644 index 0000000000000..4e7e1b5fb81d3 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).contentmapper @@ -0,0 +1,10 @@ +//// [/Card.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro]) +--- Original --- +export const Card = 1; +--- Transformed --- +const __VERSION = "1.0.0"; +export const Card = 1; + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).js new file mode 100644 index 0000000000000..aa733196f7d16 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).js @@ -0,0 +1,51 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpersCompatible.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +//// [package.json] +{ "name": "tslib", "types": "index.d.ts" } + +//// [index.d.ts] +export declare function __rewriteRelativeImportExtension( + path: any, + preserveJsx?: boolean, + extraExtensions?: Record, + ignoreCase?: boolean +): any; + +//// [Card.astro] +const __VERSION = "1.0.0"; +export const Card = 1; + +//// [main.ts] +export function dynamic(path: string) { return import(path); } +export function literal() { return import("./Card.astro"); } + +//// [require.js] +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } + +//// [globals.d.ts] +declare function require(path: string): unknown; + + +//// [main.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.dynamic = dynamic; +exports.literal = literal; +const tslib_1 = require("tslib"); +function dynamic(path) { return Promise.resolve(`${tslib_1.__rewriteRelativeImportExtension(path, false, { ".astro": ".js" })}`).then(s => tslib_1.__importStar(require(s))); } +function literal() { return Promise.resolve().then(() => tslib_1.__importStar(require("./Card.js"))); } +//// [require.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.dynamicRequire = dynamicRequire; +const tslib_1 = require("tslib"); +/** @param {string} path */ +function dynamicRequire(path) { return require(tslib_1.__rewriteRelativeImportExtension(path, false, { ".astro": ".js" })); } diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).symbols new file mode 100644 index 0000000000000..74ca18c07b161 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).symbols @@ -0,0 +1,51 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpersCompatible.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Card.astro, 0, 5)) + +export const Card = 1; +>Card : Symbol(Card, Decl(Card.astro, 1, 12)) + +=== /main.ts === +export function dynamic(path: string) { return import(path); } +>dynamic : Symbol(dynamic, Decl(main.ts, 0, 0)) +>path : Symbol(path, Decl(main.ts, 0, 24)) +>path : Symbol(path, Decl(main.ts, 0, 24)) + +export function literal() { return import("./Card.astro"); } +>literal : Symbol(literal, Decl(main.ts, 0, 62)) +>"./Card.astro" : Symbol("./Card.astro", Decl(Card.astro, 0, 0)) + +=== /require.js === +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } +>dynamicRequire : Symbol(dynamicRequire, Decl(require.js, 0, 0)) +>path : Symbol(path, Decl(require.js, 1, 31)) +>require : Symbol(require, Decl(globals.d.ts, 0, 0)) +>path : Symbol(path, Decl(require.js, 1, 31)) + +=== /globals.d.ts === +declare function require(path: string): unknown; +>require : Symbol(require, Decl(globals.d.ts, 0, 0)) +>path : Symbol(path, Decl(globals.d.ts, 0, 25)) + +=== /node_modules/tslib/index.d.ts === +export declare function __rewriteRelativeImportExtension( +>__rewriteRelativeImportExtension : Symbol(__rewriteRelativeImportExtension, Decl(index.d.ts, 0, 0)) + + path: any, +>path : Symbol(path, Decl(index.d.ts, 0, 57)) + + preserveJsx?: boolean, +>preserveJsx : Symbol(preserveJsx, Decl(index.d.ts, 1, 14)) + + extraExtensions?: Record, +>extraExtensions : Symbol(extraExtensions, Decl(index.d.ts, 2, 26)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + + ignoreCase?: boolean +>ignoreCase : Symbol(ignoreCase, Decl(index.d.ts, 3, 45)) + +): any; + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).types new file mode 100644 index 0000000000000..7e5cde545de77 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=commonjs).types @@ -0,0 +1,55 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpersCompatible.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const Card = 1; +>Card : 1 +>1 : 1 + +=== /main.ts === +export function dynamic(path: string) { return import(path); } +>dynamic : (path: string) => Promise +>path : string +>import(path) : Promise +>path : string + +export function literal() { return import("./Card.astro"); } +>literal : () => Promise +>import("./Card.astro") : Promise +>"./Card.astro" : "./Card.astro" + +=== /require.js === +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } +>dynamicRequire : (path: string) => unknown +>path : string +>require(path) : unknown +>require : (path: string) => unknown +>path : string + +=== /globals.d.ts === +declare function require(path: string): unknown; +>require : (path: string) => unknown +>path : string + +=== /node_modules/tslib/index.d.ts === +export declare function __rewriteRelativeImportExtension( +>__rewriteRelativeImportExtension : (path: any, preserveJsx?: boolean, extraExtensions?: Record, ignoreCase?: boolean) => any + + path: any, +>path : any + + preserveJsx?: boolean, +>preserveJsx : boolean | undefined + + extraExtensions?: Record, +>extraExtensions : Record | undefined + + ignoreCase?: boolean +>ignoreCase : boolean | undefined + +): any; + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).contentmapper new file mode 100644 index 0000000000000..4e7e1b5fb81d3 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).contentmapper @@ -0,0 +1,10 @@ +//// [/Card.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro]) +--- Original --- +export const Card = 1; +--- Transformed --- +const __VERSION = "1.0.0"; +export const Card = 1; + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).js new file mode 100644 index 0000000000000..9ff6c306b187b --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).js @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpersCompatible.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +//// [package.json] +{ "name": "tslib", "types": "index.d.ts" } + +//// [index.d.ts] +export declare function __rewriteRelativeImportExtension( + path: any, + preserveJsx?: boolean, + extraExtensions?: Record, + ignoreCase?: boolean +): any; + +//// [Card.astro] +const __VERSION = "1.0.0"; +export const Card = 1; + +//// [main.ts] +export function dynamic(path: string) { return import(path); } +export function literal() { return import("./Card.astro"); } + +//// [require.js] +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } + +//// [globals.d.ts] +declare function require(path: string): unknown; + + +//// [main.js] +import { __rewriteRelativeImportExtension } from "tslib"; +export function dynamic(path) { return import(__rewriteRelativeImportExtension(path, false, { ".astro": ".js" })); } +export function literal() { return import("./Card.js"); } +//// [require.js] +import { __rewriteRelativeImportExtension } from "tslib"; +/** @param {string} path */ +export function dynamicRequire(path) { return require(__rewriteRelativeImportExtension(path, false, { ".astro": ".js" })); } diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).symbols new file mode 100644 index 0000000000000..74ca18c07b161 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).symbols @@ -0,0 +1,51 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpersCompatible.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Card.astro, 0, 5)) + +export const Card = 1; +>Card : Symbol(Card, Decl(Card.astro, 1, 12)) + +=== /main.ts === +export function dynamic(path: string) { return import(path); } +>dynamic : Symbol(dynamic, Decl(main.ts, 0, 0)) +>path : Symbol(path, Decl(main.ts, 0, 24)) +>path : Symbol(path, Decl(main.ts, 0, 24)) + +export function literal() { return import("./Card.astro"); } +>literal : Symbol(literal, Decl(main.ts, 0, 62)) +>"./Card.astro" : Symbol("./Card.astro", Decl(Card.astro, 0, 0)) + +=== /require.js === +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } +>dynamicRequire : Symbol(dynamicRequire, Decl(require.js, 0, 0)) +>path : Symbol(path, Decl(require.js, 1, 31)) +>require : Symbol(require, Decl(globals.d.ts, 0, 0)) +>path : Symbol(path, Decl(require.js, 1, 31)) + +=== /globals.d.ts === +declare function require(path: string): unknown; +>require : Symbol(require, Decl(globals.d.ts, 0, 0)) +>path : Symbol(path, Decl(globals.d.ts, 0, 25)) + +=== /node_modules/tslib/index.d.ts === +export declare function __rewriteRelativeImportExtension( +>__rewriteRelativeImportExtension : Symbol(__rewriteRelativeImportExtension, Decl(index.d.ts, 0, 0)) + + path: any, +>path : Symbol(path, Decl(index.d.ts, 0, 57)) + + preserveJsx?: boolean, +>preserveJsx : Symbol(preserveJsx, Decl(index.d.ts, 1, 14)) + + extraExtensions?: Record, +>extraExtensions : Symbol(extraExtensions, Decl(index.d.ts, 2, 26)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + + ignoreCase?: boolean +>ignoreCase : Symbol(ignoreCase, Decl(index.d.ts, 3, 45)) + +): any; + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).types new file mode 100644 index 0000000000000..7e5cde545de77 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsImportHelpersCompatible(module=preserve).types @@ -0,0 +1,55 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsImportHelpersCompatible.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const Card = 1; +>Card : 1 +>1 : 1 + +=== /main.ts === +export function dynamic(path: string) { return import(path); } +>dynamic : (path: string) => Promise +>path : string +>import(path) : Promise +>path : string + +export function literal() { return import("./Card.astro"); } +>literal : () => Promise +>import("./Card.astro") : Promise +>"./Card.astro" : "./Card.astro" + +=== /require.js === +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } +>dynamicRequire : (path: string) => unknown +>path : string +>require(path) : unknown +>require : (path: string) => unknown +>path : string + +=== /globals.d.ts === +declare function require(path: string): unknown; +>require : (path: string) => unknown +>path : string + +=== /node_modules/tslib/index.d.ts === +export declare function __rewriteRelativeImportExtension( +>__rewriteRelativeImportExtension : (path: any, preserveJsx?: boolean, extraExtensions?: Record, ignoreCase?: boolean) => any + + path: any, +>path : any + + preserveJsx?: boolean, +>preserveJsx : boolean | undefined + + extraExtensions?: Record, +>extraExtensions : Record | undefined + + ignoreCase?: boolean +>ignoreCase : boolean | undefined + +): any; + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.contentmapper new file mode 100644 index 0000000000000..4e7e1b5fb81d3 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.contentmapper @@ -0,0 +1,10 @@ +//// [/Card.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro]) +--- Original --- +export const Card = 1; +--- Transformed --- +const __VERSION = "1.0.0"; +export const Card = 1; + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.errors.txt b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.errors.txt new file mode 100644 index 0000000000000..b1079f000d87c --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.errors.txt @@ -0,0 +1,32 @@ +/main.ts(1,22): error TS2307: Cannot find module './Card.js' or its corresponding type declarations. + + +==== /tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "module": "preserve", + "moduleResolution": "bundler", + "rewriteRelativeImportExtensions": true + }, + "contentMappers": [ + { "package": "mapper", "extensions": [".astro"] } + ] + } + +==== /main.ts (1 errors) ==== + import { Card } from "./Card.js"; + ~~~~~~~~~~~ +!!! error TS2307: Cannot find module './Card.js' or its corresponding type declarations. + +==== /node_modules/mapper/package.json (0 errors) ==== + { + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js" } + } + } + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.js new file mode 100644 index 0000000000000..78d2c1b2a4b17 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.js @@ -0,0 +1,23 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsNoReverseResolution.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js" } + } + } +} + +//// [Card.astro] +const __VERSION = "1.0.0"; +export const Card = 1; + +//// [main.ts] +import { Card } from "./Card.js"; + + +//// [main.js] diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.symbols new file mode 100644 index 0000000000000..13f25c06fc31e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.symbols @@ -0,0 +1,13 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsNoReverseResolution.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Card.astro, 0, 5)) + +export const Card = 1; +>Card : Symbol(Card, Decl(Card.astro, 1, 12)) + +=== /main.ts === +import { Card } from "./Card.js"; +>Card : Symbol(Card, Decl(main.ts, 0, 8)) + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.types new file mode 100644 index 0000000000000..d45b3a733b3af --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsNoReverseResolution.types @@ -0,0 +1,15 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsNoReverseResolution.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const Card = 1; +>Card : 1 +>1 : 1 + +=== /main.ts === +import { Card } from "./Card.js"; +>Card : any + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.contentmapper new file mode 100644 index 0000000000000..4bbc986eb4c37 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.contentmapper @@ -0,0 +1,31 @@ +//// [/Widget.z] (ScriptKind: ScriptKindTS, ContentMapper: [.z .y.z .identity.y.z .unmapped.y.z]) +--- Original --- +export const short = 1; +--- Transformed --- +const __VERSION = "1.0.0"; +export const short = 1; + +//// [/Widget.y.z] (ScriptKind: ScriptKindTS, ContentMapper: [.z .y.z .identity.y.z .unmapped.y.z]) +--- Original --- +export const long = 1; +--- Transformed --- +const __VERSION = "1.0.0"; +export const long = 1; + +//// [/Widget.identity.y.z] (ScriptKind: ScriptKindTS, ContentMapper: [.z .y.z .identity.y.z .unmapped.y.z]) +--- Original --- +export const identity = 1; +--- Transformed --- +const __VERSION = "1.0.0"; +export const identity = 1; + +//// [/Widget.unmapped.y.z] (ScriptKind: ScriptKindTS, ContentMapper: [.z .y.z .identity.y.z .unmapped.y.z]) +--- Original --- +export const unmapped = 1; +--- Transformed --- +const __VERSION = "1.0.0"; +export const unmapped = 1; + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.js new file mode 100644 index 0000000000000..0d24af0fb0dc3 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.js @@ -0,0 +1,78 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsOverlap.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +//// [Widget.z] +const __VERSION = "1.0.0"; +export const short = 1; + +//// [Widget.y.z] +const __VERSION = "1.0.0"; +export const long = 1; + +//// [Widget.identity.y.z] +const __VERSION = "1.0.0"; +export const identity = 1; + +//// [Widget.unmapped.y.z] +const __VERSION = "1.0.0"; +export const unmapped = 1; + +//// [main.ts] +export { short } from "./Widget.z"; +export { long } from "./Widget.y.z"; +export { identity } from "./Widget.identity.y.z"; +export { unmapped } from "./Widget.unmapped.y.z"; +export function literal() { return import("./Widget.y.z"); } +export function dynamic(path: string) { return import(path); } +export const inferred = import("./Widget.y.z"); + + +//// [main.js] +var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { + if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } + return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { + return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); + }); + } + return path; +}; +export { short } from "./Widget.js"; +export { long } from "./Widget.mjs"; +export { identity } from "./Widget.identity.y.z"; +export { unmapped } from "./Widget.unmapped.y.z"; +export function literal() { return import("./Widget.mjs"); } +export function dynamic(path) { return import(__rewriteRelativeImportExtension(path, false, { ".identity.y.z": ".identity.y.z", ".unmapped.y.z": ".unmapped.y.z", ".y.z": ".mjs", ".z": ".js" })); } +export const inferred = import("./Widget.mjs"); + + +//// [Widget.d.ts] +export declare const short = 1; +//// [Widget.d.mts] +export declare const long = 1; +//// [Widget.d.identity.y.z.ts] +export declare const identity = 1; +//// [Widget.d.unmapped.y.z.ts] +export declare const unmapped = 1; +//// [main.d.ts] +export { short } from "./Widget.js"; +export { long } from "./Widget.mjs"; +export { identity } from "./Widget.identity.y.z"; +export { unmapped } from "./Widget.unmapped.y.z"; +export declare function literal(): Promise; +export declare function dynamic(path: string): Promise; +export declare const inferred: Promise; diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.symbols new file mode 100644 index 0000000000000..b187b5a7659e2 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.symbols @@ -0,0 +1,56 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsOverlap.ts] //// + +=== /Widget.z === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Widget.z, 0, 5)) + +export const short = 1; +>short : Symbol(short, Decl(Widget.z, 1, 12)) + +=== /Widget.y.z === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Widget.y.z, 0, 5)) + +export const long = 1; +>long : Symbol(long, Decl(Widget.y.z, 1, 12)) + +=== /Widget.identity.y.z === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Widget.identity.y.z, 0, 5)) + +export const identity = 1; +>identity : Symbol(identity, Decl(Widget.identity.y.z, 1, 12)) + +=== /Widget.unmapped.y.z === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Widget.unmapped.y.z, 0, 5)) + +export const unmapped = 1; +>unmapped : Symbol(unmapped, Decl(Widget.unmapped.y.z, 1, 12)) + +=== /main.ts === +export { short } from "./Widget.z"; +>short : Symbol(short, Decl(main.ts, 0, 8)) + +export { long } from "./Widget.y.z"; +>long : Symbol(long, Decl(main.ts, 1, 8)) + +export { identity } from "./Widget.identity.y.z"; +>identity : Symbol(identity, Decl(main.ts, 2, 8)) + +export { unmapped } from "./Widget.unmapped.y.z"; +>unmapped : Symbol(unmapped, Decl(main.ts, 3, 8)) + +export function literal() { return import("./Widget.y.z"); } +>literal : Symbol(literal, Decl(main.ts, 3, 49)) +>"./Widget.y.z" : Symbol("./Widget.y.z", Decl(Widget.y.z, 0, 0)) + +export function dynamic(path: string) { return import(path); } +>dynamic : Symbol(dynamic, Decl(main.ts, 4, 60)) +>path : Symbol(path, Decl(main.ts, 5, 24)) +>path : Symbol(path, Decl(main.ts, 5, 24)) + +export const inferred = import("./Widget.y.z"); +>inferred : Symbol(inferred, Decl(main.ts, 6, 12)) +>"./Widget.y.z" : Symbol("./Widget.y.z", Decl(Widget.y.z, 0, 0)) + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.types new file mode 100644 index 0000000000000..f758afb5c4380 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverlap.types @@ -0,0 +1,67 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsOverlap.ts] //// + +=== /Widget.z === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const short = 1; +>short : 1 +>1 : 1 + +=== /Widget.y.z === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const long = 1; +>long : 1 +>1 : 1 + +=== /Widget.identity.y.z === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const identity = 1; +>identity : 1 +>1 : 1 + +=== /Widget.unmapped.y.z === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const unmapped = 1; +>unmapped : 1 +>1 : 1 + +=== /main.ts === +export { short } from "./Widget.z"; +>short : 1 + +export { long } from "./Widget.y.z"; +>long : 1 + +export { identity } from "./Widget.identity.y.z"; +>identity : 1 + +export { unmapped } from "./Widget.unmapped.y.z"; +>unmapped : 1 + +export function literal() { return import("./Widget.y.z"); } +>literal : () => Promise +>import("./Widget.y.z") : Promise +>"./Widget.y.z" : "./Widget.y.z" + +export function dynamic(path: string) { return import(path); } +>dynamic : (path: string) => Promise +>path : string +>import(path) : Promise +>path : string + +export const inferred = import("./Widget.y.z"); +>inferred : Promise +>import("./Widget.y.z") : Promise +>"./Widget.y.z" : "./Widget.y.z" + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.contentmapper new file mode 100644 index 0000000000000..ac3df6e9e0fb5 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.contentmapper @@ -0,0 +1,10 @@ +//// [/Card.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro]) +--- Original --- +export declare const Card: () => string; +--- Transformed --- +const __VERSION = "1.0.0"; +export declare const Card: () => string; + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.js new file mode 100644 index 0000000000000..6fd45e5601148 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.js @@ -0,0 +1,30 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsOverride.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js" } + } + } +} + +//// [Card.astro] +const __VERSION = "1.0.0"; +export declare const Card: () => string; + +//// [main.ts] +export { Card } from "./Card.astro"; + + +//// [main.js] +export { Card } from "./Card.astro"; + + +//// [Card.d.astro.ts] +export declare const Card: () => string; +//// [main.d.ts] +export { Card } from "./Card.astro"; diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.symbols new file mode 100644 index 0000000000000..39e80d9f89b59 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.symbols @@ -0,0 +1,13 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsOverride.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Card.astro, 0, 5)) + +export declare const Card: () => string; +>Card : Symbol(Card, Decl(Card.astro, 1, 20)) + +=== /main.ts === +export { Card } from "./Card.astro"; +>Card : Symbol(Card, Decl(main.ts, 0, 8)) + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.types new file mode 100644 index 0000000000000..08e70d61ee6a2 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsOverride.types @@ -0,0 +1,14 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsOverride.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export declare const Card: () => string; +>Card : () => string + +=== /main.ts === +export { Card } from "./Card.astro"; +>Card : () => string + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.contentmapper new file mode 100644 index 0000000000000..4e7e1b5fb81d3 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.contentmapper @@ -0,0 +1,10 @@ +//// [/Card.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro]) +--- Original --- +export const Card = 1; +--- Transformed --- +const __VERSION = "1.0.0"; +export const Card = 1; + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.errors.txt b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.errors.txt new file mode 100644 index 0000000000000..bb571a95582c0 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.errors.txt @@ -0,0 +1,27 @@ +/tsconfig.json(6,9): error TS18118: Content mapper output extension mapping from '.astro' to '.js' requires 'rewriteRelativeImportExtensions' to be enabled. + + +==== /tsconfig.json (1 errors) ==== + { + "compilerOptions": { + "declaration": true + }, + "contentMappers": [ + { "package": "mapper", "extensions": [".astro"] } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS18118: Content mapper output extension mapping from '.astro' to '.js' requires 'rewriteRelativeImportExtensions' to be enabled. + ] + } + +==== /node_modules/mapper/package.json (0 errors) ==== + { + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js" } + } + } + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.js new file mode 100644 index 0000000000000..de5ef06cfaaf5 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.js @@ -0,0 +1,23 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsRequiresRewrite.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js" } + } + } +} + +//// [Card.astro] +const __VERSION = "1.0.0"; +export const Card = 1; + + + + +//// [Card.d.ts] +export declare const Card = 1; diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.symbols new file mode 100644 index 0000000000000..39f01f21832e2 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.symbols @@ -0,0 +1,9 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsRequiresRewrite.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(Card.astro, 0, 5)) + +export const Card = 1; +>Card : Symbol(Card, Decl(Card.astro, 1, 12)) + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.types new file mode 100644 index 0000000000000..2fcb3e98b51b3 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsRequiresRewrite.types @@ -0,0 +1,11 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsRequiresRewrite.ts] //// + +=== /Card.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const Card = 1; +>Card : 1 +>1 : 1 + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).contentmapper new file mode 100644 index 0000000000000..266d192e6c4f5 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).contentmapper @@ -0,0 +1,10 @@ +//// [/actual.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro]) +--- Original --- +export interface ActualProps { title: string } +--- Transformed --- +const __VERSION = "1.0.0"; +export interface ActualProps { title: string } + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).errors.txt b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).errors.txt new file mode 100644 index 0000000000000..7144c3dc4d189 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).errors.txt @@ -0,0 +1,75 @@ +/main.ts(1,28): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(3,33): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(4,43): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(5,45): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(7,51): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(11,35): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./ambient.d.astro.ts". + + +==== /tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "target": "esnext", + "module": "preserve", + "rewriteRelativeImportExtensions": true, + "allowArbitraryExtensions": true, + "rootDir": "/", + "outDir": "/dist" + }, + "contentMappers": [{ + "package": "mapper", + "extensions": [".astro"], + "outputExtensions": { ".astro": ".js" } + }] + } + +==== /components.astro/index.ts (0 errors) ==== + export interface Props { title: string } + +==== /native.ts/index.ts (0 errors) ==== + export interface NativeProps { title: string } + +==== /ambient.d.astro.ts (0 errors) ==== + export interface AmbientProps { title: string } + +==== /existing.d.ts (0 errors) ==== + export declare const existing: import("./components.astro").Props; + +==== /main.ts (6 errors) ==== + export type { Props } from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + export type * from "./components.astro"; + export type * as Namespace from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + export { type Props as InlineProps } from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + import type { Props as ImportedProps } from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + export type Alias = ImportedProps; + import { type Props as InlineImportedProps } from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + export type InlineAlias = InlineImportedProps; + export type ImportType = import("./components.astro").Props; + export declare const props: import("./components.astro").Props; + export type { AmbientProps } from "./ambient.astro"; + ~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./ambient.d.astro.ts". + export type NativeImportType = import("./native.ts").NativeProps; + export type { ActualProps } from "./actual.astro"; + import type { ActualProps } from "./actual.astro"; + export type ActualAlias = ActualProps; + export type ActualImportType = import("./actual.astro").ActualProps; + export declare const actualProps: import("./actual.astro").ActualProps; + +==== /node_modules/mapper/package.json (0 errors) ==== + { + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).js new file mode 100644 index 0000000000000..39b19fd260c74 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).js @@ -0,0 +1,48 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +//// [index.ts] +export interface Props { title: string } + +//// [index.ts] +export interface NativeProps { title: string } + +//// [actual.astro] +const __VERSION = "1.0.0"; +export interface ActualProps { title: string } + +//// [ambient.d.astro.ts] +export interface AmbientProps { title: string } + +//// [existing.d.ts] +export declare const existing: import("./components.astro").Props; + +//// [main.ts] +export type { Props } from "./components.astro"; +export type * from "./components.astro"; +export type * as Namespace from "./components.astro"; +export { type Props as InlineProps } from "./components.astro"; +import type { Props as ImportedProps } from "./components.astro"; +export type Alias = ImportedProps; +import { type Props as InlineImportedProps } from "./components.astro"; +export type InlineAlias = InlineImportedProps; +export type ImportType = import("./components.astro").Props; +export declare const props: import("./components.astro").Props; +export type { AmbientProps } from "./ambient.astro"; +export type NativeImportType = import("./native.ts").NativeProps; +export type { ActualProps } from "./actual.astro"; +import type { ActualProps } from "./actual.astro"; +export type ActualAlias = ActualProps; +export type ActualImportType = import("./actual.astro").ActualProps; +export declare const actualProps: import("./actual.astro").ActualProps; + + +//// [index.js] +//// [index.js] +//// [main.js] diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).symbols new file mode 100644 index 0000000000000..60e155b4caedb --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).symbols @@ -0,0 +1,91 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite.ts] //// + +=== /components.astro/index.ts === +export interface Props { title: string } +>Props : Symbol(Props, Decl(index.ts, 0, 0)) +>title : Symbol(Props.title, Decl(index.ts, 0, 24)) + +=== /native.ts/index.ts === +export interface NativeProps { title: string } +>NativeProps : Symbol(NativeProps, Decl(index.ts, 0, 0)) +>title : Symbol(NativeProps.title, Decl(index.ts, 0, 30)) + +=== /actual.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(actual.astro, 0, 5)) + +export interface ActualProps { title: string } +>ActualProps : Symbol(ActualProps, Decl(actual.astro, 0, 26)) +>title : Symbol(ActualProps.title, Decl(actual.astro, 1, 30)) + +=== /ambient.d.astro.ts === +export interface AmbientProps { title: string } +>AmbientProps : Symbol(AmbientProps, Decl(ambient.d.astro.ts, 0, 0)) +>title : Symbol(AmbientProps.title, Decl(ambient.d.astro.ts, 0, 31)) + +=== /existing.d.ts === +export declare const existing: import("./components.astro").Props; +>existing : Symbol(existing, Decl(existing.d.ts, 0, 20)) +>Props : Symbol(Props, Decl(index.ts, 0, 0)) + +=== /main.ts === +export type { Props } from "./components.astro"; +>Props : Symbol(Props, Decl(main.ts, 0, 13)) + +export type * from "./components.astro"; +export type * as Namespace from "./components.astro"; +>Namespace : Symbol(Namespace, Decl(main.ts, 2, 11)) + +export { type Props as InlineProps } from "./components.astro"; +>Props : Symbol(ImportedProps, Decl(index.ts, 0, 0)) +>InlineProps : Symbol(InlineProps, Decl(main.ts, 3, 8)) + +import type { Props as ImportedProps } from "./components.astro"; +>Props : Symbol(ImportedProps, Decl(index.ts, 0, 0)) +>ImportedProps : Symbol(ImportedProps, Decl(main.ts, 4, 13)) + +export type Alias = ImportedProps; +>Alias : Symbol(Alias, Decl(main.ts, 4, 65)) +>ImportedProps : Symbol(ImportedProps, Decl(main.ts, 4, 13)) + +import { type Props as InlineImportedProps } from "./components.astro"; +>Props : Symbol(ImportedProps, Decl(index.ts, 0, 0)) +>InlineImportedProps : Symbol(InlineImportedProps, Decl(main.ts, 6, 8)) + +export type InlineAlias = InlineImportedProps; +>InlineAlias : Symbol(InlineAlias, Decl(main.ts, 6, 71)) +>InlineImportedProps : Symbol(InlineImportedProps, Decl(main.ts, 6, 8)) + +export type ImportType = import("./components.astro").Props; +>ImportType : Symbol(ImportType, Decl(main.ts, 7, 46)) +>Props : Symbol(ImportedProps, Decl(index.ts, 0, 0)) + +export declare const props: import("./components.astro").Props; +>props : Symbol(props, Decl(main.ts, 9, 20)) +>Props : Symbol(ImportedProps, Decl(index.ts, 0, 0)) + +export type { AmbientProps } from "./ambient.astro"; +>AmbientProps : Symbol(AmbientProps, Decl(main.ts, 10, 13)) + +export type NativeImportType = import("./native.ts").NativeProps; +>NativeImportType : Symbol(NativeImportType, Decl(main.ts, 10, 52)) +>NativeProps : Symbol(NativeProps, Decl(index.ts, 0, 0)) + +export type { ActualProps } from "./actual.astro"; +>ActualProps : Symbol(ActualProps, Decl(main.ts, 12, 13)) + +import type { ActualProps } from "./actual.astro"; +>ActualProps : Symbol(ActualProps, Decl(main.ts, 13, 13)) + +export type ActualAlias = ActualProps; +>ActualAlias : Symbol(ActualAlias, Decl(main.ts, 13, 50)) +>ActualProps : Symbol(ActualProps, Decl(main.ts, 13, 13)) + +export type ActualImportType = import("./actual.astro").ActualProps; +>ActualImportType : Symbol(ActualImportType, Decl(main.ts, 14, 38)) +>ActualProps : Symbol(ActualProps, Decl(actual.astro, 0, 26)) + +export declare const actualProps: import("./actual.astro").ActualProps; +>actualProps : Symbol(actualProps, Decl(main.ts, 16, 20)) +>ActualProps : Symbol(ActualProps, Decl(actual.astro, 0, 26)) + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).types new file mode 100644 index 0000000000000..a1ef46122c4f0 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=false).types @@ -0,0 +1,79 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite.ts] //// + +=== /components.astro/index.ts === +export interface Props { title: string } +>title : string + +=== /native.ts/index.ts === +export interface NativeProps { title: string } +>title : string + +=== /actual.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export interface ActualProps { title: string } +>title : string + +=== /ambient.d.astro.ts === +export interface AmbientProps { title: string } +>title : string + +=== /existing.d.ts === +export declare const existing: import("./components.astro").Props; +>existing : import("./components.astro").Props + +=== /main.ts === +export type { Props } from "./components.astro"; +>Props : ImportedProps + +export type * from "./components.astro"; +export type * as Namespace from "./components.astro"; +>Namespace : typeof import("./components.astro") + +export { type Props as InlineProps } from "./components.astro"; +>Props : any +>InlineProps : any + +import type { Props as ImportedProps } from "./components.astro"; +>Props : any +>ImportedProps : ImportedProps + +export type Alias = ImportedProps; +>Alias : ImportedProps + +import { type Props as InlineImportedProps } from "./components.astro"; +>Props : any +>InlineImportedProps : any + +export type InlineAlias = InlineImportedProps; +>InlineAlias : ImportedProps + +export type ImportType = import("./components.astro").Props; +>ImportType : ImportedProps + +export declare const props: import("./components.astro").Props; +>props : ImportedProps + +export type { AmbientProps } from "./ambient.astro"; +>AmbientProps : import("./ambient.astro").AmbientProps + +export type NativeImportType = import("./native.ts").NativeProps; +>NativeImportType : import("./native.ts").NativeProps + +export type { ActualProps } from "./actual.astro"; +>ActualProps : ActualProps + +import type { ActualProps } from "./actual.astro"; +>ActualProps : ActualProps + +export type ActualAlias = ActualProps; +>ActualAlias : ActualProps + +export type ActualImportType = import("./actual.astro").ActualProps; +>ActualImportType : ActualProps + +export declare const actualProps: import("./actual.astro").ActualProps; +>actualProps : ActualProps + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).contentmapper new file mode 100644 index 0000000000000..266d192e6c4f5 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).contentmapper @@ -0,0 +1,10 @@ +//// [/actual.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro]) +--- Original --- +export interface ActualProps { title: string } +--- Transformed --- +const __VERSION = "1.0.0"; +export interface ActualProps { title: string } + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).errors.txt b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).errors.txt new file mode 100644 index 0000000000000..e2ad963371af6 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).errors.txt @@ -0,0 +1,84 @@ +/main.ts(1,28): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(2,20): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(3,33): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(4,43): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(5,45): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(7,51): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(9,33): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(10,36): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(11,35): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./ambient.d.astro.ts". + + +==== /tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "target": "esnext", + "module": "preserve", + "rewriteRelativeImportExtensions": true, + "allowArbitraryExtensions": true, + "rootDir": "/", + "outDir": "/dist" + }, + "contentMappers": [{ + "package": "mapper", + "extensions": [".astro"], + "outputExtensions": { ".astro": ".js" } + }] + } + +==== /components.astro/index.ts (0 errors) ==== + export interface Props { title: string } + +==== /native.ts/index.ts (0 errors) ==== + export interface NativeProps { title: string } + +==== /ambient.d.astro.ts (0 errors) ==== + export interface AmbientProps { title: string } + +==== /existing.d.ts (0 errors) ==== + export declare const existing: import("./components.astro").Props; + +==== /main.ts (9 errors) ==== + export type { Props } from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + export type * from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + export type * as Namespace from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + export { type Props as InlineProps } from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + import type { Props as ImportedProps } from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + export type Alias = ImportedProps; + import { type Props as InlineImportedProps } from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + export type InlineAlias = InlineImportedProps; + export type ImportType = import("./components.astro").Props; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + export declare const props: import("./components.astro").Props; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + export type { AmbientProps } from "./ambient.astro"; + ~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./ambient.d.astro.ts". + export type NativeImportType = import("./native.ts").NativeProps; + export type { ActualProps } from "./actual.astro"; + import type { ActualProps } from "./actual.astro"; + export type ActualAlias = ActualProps; + export type ActualImportType = import("./actual.astro").ActualProps; + export declare const actualProps: import("./actual.astro").ActualProps; + +==== /node_modules/mapper/package.json (0 errors) ==== + { + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).js new file mode 100644 index 0000000000000..70f249064306a --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).js @@ -0,0 +1,80 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +//// [index.ts] +export interface Props { title: string } + +//// [index.ts] +export interface NativeProps { title: string } + +//// [actual.astro] +const __VERSION = "1.0.0"; +export interface ActualProps { title: string } + +//// [ambient.d.astro.ts] +export interface AmbientProps { title: string } + +//// [existing.d.ts] +export declare const existing: import("./components.astro").Props; + +//// [main.ts] +export type { Props } from "./components.astro"; +export type * from "./components.astro"; +export type * as Namespace from "./components.astro"; +export { type Props as InlineProps } from "./components.astro"; +import type { Props as ImportedProps } from "./components.astro"; +export type Alias = ImportedProps; +import { type Props as InlineImportedProps } from "./components.astro"; +export type InlineAlias = InlineImportedProps; +export type ImportType = import("./components.astro").Props; +export declare const props: import("./components.astro").Props; +export type { AmbientProps } from "./ambient.astro"; +export type NativeImportType = import("./native.ts").NativeProps; +export type { ActualProps } from "./actual.astro"; +import type { ActualProps } from "./actual.astro"; +export type ActualAlias = ActualProps; +export type ActualImportType = import("./actual.astro").ActualProps; +export declare const actualProps: import("./actual.astro").ActualProps; + + +//// [index.js] +//// [index.js] +//// [main.js] + + +//// [index.d.ts] +export interface Props { + title: string; +} +//// [index.d.ts] +export interface NativeProps { + title: string; +} +//// [actual.d.ts] +export interface ActualProps { + title: string; +} +//// [main.d.ts] +export type { Props } from "./components.js"; +export type * from "./components.js"; +export type * as Namespace from "./components.js"; +export { type Props as InlineProps } from "./components.js"; +import type { Props as ImportedProps } from "./components.js"; +export type Alias = ImportedProps; +import { type Props as InlineImportedProps } from "./components.js"; +export type InlineAlias = InlineImportedProps; +export type ImportType = import("./components.js").Props; +export declare const props: import("./components.js").Props; +export type { AmbientProps } from "./ambient.js"; +export type NativeImportType = import("./native.ts").NativeProps; +export type { ActualProps } from "./actual.js"; +import type { ActualProps } from "./actual.js"; +export type ActualAlias = ActualProps; +export type ActualImportType = import("./actual.js").ActualProps; +export declare const actualProps: import("./actual.js").ActualProps; diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).symbols new file mode 100644 index 0000000000000..60e155b4caedb --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).symbols @@ -0,0 +1,91 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite.ts] //// + +=== /components.astro/index.ts === +export interface Props { title: string } +>Props : Symbol(Props, Decl(index.ts, 0, 0)) +>title : Symbol(Props.title, Decl(index.ts, 0, 24)) + +=== /native.ts/index.ts === +export interface NativeProps { title: string } +>NativeProps : Symbol(NativeProps, Decl(index.ts, 0, 0)) +>title : Symbol(NativeProps.title, Decl(index.ts, 0, 30)) + +=== /actual.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(actual.astro, 0, 5)) + +export interface ActualProps { title: string } +>ActualProps : Symbol(ActualProps, Decl(actual.astro, 0, 26)) +>title : Symbol(ActualProps.title, Decl(actual.astro, 1, 30)) + +=== /ambient.d.astro.ts === +export interface AmbientProps { title: string } +>AmbientProps : Symbol(AmbientProps, Decl(ambient.d.astro.ts, 0, 0)) +>title : Symbol(AmbientProps.title, Decl(ambient.d.astro.ts, 0, 31)) + +=== /existing.d.ts === +export declare const existing: import("./components.astro").Props; +>existing : Symbol(existing, Decl(existing.d.ts, 0, 20)) +>Props : Symbol(Props, Decl(index.ts, 0, 0)) + +=== /main.ts === +export type { Props } from "./components.astro"; +>Props : Symbol(Props, Decl(main.ts, 0, 13)) + +export type * from "./components.astro"; +export type * as Namespace from "./components.astro"; +>Namespace : Symbol(Namespace, Decl(main.ts, 2, 11)) + +export { type Props as InlineProps } from "./components.astro"; +>Props : Symbol(ImportedProps, Decl(index.ts, 0, 0)) +>InlineProps : Symbol(InlineProps, Decl(main.ts, 3, 8)) + +import type { Props as ImportedProps } from "./components.astro"; +>Props : Symbol(ImportedProps, Decl(index.ts, 0, 0)) +>ImportedProps : Symbol(ImportedProps, Decl(main.ts, 4, 13)) + +export type Alias = ImportedProps; +>Alias : Symbol(Alias, Decl(main.ts, 4, 65)) +>ImportedProps : Symbol(ImportedProps, Decl(main.ts, 4, 13)) + +import { type Props as InlineImportedProps } from "./components.astro"; +>Props : Symbol(ImportedProps, Decl(index.ts, 0, 0)) +>InlineImportedProps : Symbol(InlineImportedProps, Decl(main.ts, 6, 8)) + +export type InlineAlias = InlineImportedProps; +>InlineAlias : Symbol(InlineAlias, Decl(main.ts, 6, 71)) +>InlineImportedProps : Symbol(InlineImportedProps, Decl(main.ts, 6, 8)) + +export type ImportType = import("./components.astro").Props; +>ImportType : Symbol(ImportType, Decl(main.ts, 7, 46)) +>Props : Symbol(ImportedProps, Decl(index.ts, 0, 0)) + +export declare const props: import("./components.astro").Props; +>props : Symbol(props, Decl(main.ts, 9, 20)) +>Props : Symbol(ImportedProps, Decl(index.ts, 0, 0)) + +export type { AmbientProps } from "./ambient.astro"; +>AmbientProps : Symbol(AmbientProps, Decl(main.ts, 10, 13)) + +export type NativeImportType = import("./native.ts").NativeProps; +>NativeImportType : Symbol(NativeImportType, Decl(main.ts, 10, 52)) +>NativeProps : Symbol(NativeProps, Decl(index.ts, 0, 0)) + +export type { ActualProps } from "./actual.astro"; +>ActualProps : Symbol(ActualProps, Decl(main.ts, 12, 13)) + +import type { ActualProps } from "./actual.astro"; +>ActualProps : Symbol(ActualProps, Decl(main.ts, 13, 13)) + +export type ActualAlias = ActualProps; +>ActualAlias : Symbol(ActualAlias, Decl(main.ts, 13, 50)) +>ActualProps : Symbol(ActualProps, Decl(main.ts, 13, 13)) + +export type ActualImportType = import("./actual.astro").ActualProps; +>ActualImportType : Symbol(ActualImportType, Decl(main.ts, 14, 38)) +>ActualProps : Symbol(ActualProps, Decl(actual.astro, 0, 26)) + +export declare const actualProps: import("./actual.astro").ActualProps; +>actualProps : Symbol(actualProps, Decl(main.ts, 16, 20)) +>ActualProps : Symbol(ActualProps, Decl(actual.astro, 0, 26)) + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).types new file mode 100644 index 0000000000000..a1ef46122c4f0 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite(declaration=true).types @@ -0,0 +1,79 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite.ts] //// + +=== /components.astro/index.ts === +export interface Props { title: string } +>title : string + +=== /native.ts/index.ts === +export interface NativeProps { title: string } +>title : string + +=== /actual.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export interface ActualProps { title: string } +>title : string + +=== /ambient.d.astro.ts === +export interface AmbientProps { title: string } +>title : string + +=== /existing.d.ts === +export declare const existing: import("./components.astro").Props; +>existing : import("./components.astro").Props + +=== /main.ts === +export type { Props } from "./components.astro"; +>Props : ImportedProps + +export type * from "./components.astro"; +export type * as Namespace from "./components.astro"; +>Namespace : typeof import("./components.astro") + +export { type Props as InlineProps } from "./components.astro"; +>Props : any +>InlineProps : any + +import type { Props as ImportedProps } from "./components.astro"; +>Props : any +>ImportedProps : ImportedProps + +export type Alias = ImportedProps; +>Alias : ImportedProps + +import { type Props as InlineImportedProps } from "./components.astro"; +>Props : any +>InlineImportedProps : any + +export type InlineAlias = InlineImportedProps; +>InlineAlias : ImportedProps + +export type ImportType = import("./components.astro").Props; +>ImportType : ImportedProps + +export declare const props: import("./components.astro").Props; +>props : ImportedProps + +export type { AmbientProps } from "./ambient.astro"; +>AmbientProps : import("./ambient.astro").AmbientProps + +export type NativeImportType = import("./native.ts").NativeProps; +>NativeImportType : import("./native.ts").NativeProps + +export type { ActualProps } from "./actual.astro"; +>ActualProps : ActualProps + +import type { ActualProps } from "./actual.astro"; +>ActualProps : ActualProps + +export type ActualAlias = ActualProps; +>ActualAlias : ActualProps + +export type ActualImportType = import("./actual.astro").ActualProps; +>ActualImportType : ActualProps + +export declare const actualProps: import("./actual.astro").ActualProps; +>actualProps : ActualProps + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.contentmapper b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.contentmapper new file mode 100644 index 0000000000000..eaf19994fe837 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.contentmapper @@ -0,0 +1,10 @@ +//// [/actual.astro] (ScriptKind: ScriptKindTS, ContentMapper: [.astro]) +--- Original --- +export const actual = 1; +--- Transformed --- +const __VERSION = "1.0.0"; +export const actual = 1; + +=== Diagnostics === + + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.errors.txt b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.errors.txt new file mode 100644 index 0000000000000..e173b89928c2d --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.errors.txt @@ -0,0 +1,48 @@ +/main.ts(1,27): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". +/main.ts(3,25): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./ambient.d.astro.ts". +/main.ts(4,35): error TS2877: This import uses a '.astro' extension to resolve to an input TypeScript file, but will not be rewritten during emit because it is not a relative path. + + +==== /tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "target": "esnext", + "module": "preserve", + "rewriteRelativeImportExtensions": true, + "allowArbitraryExtensions": true, + "paths": { "alias": ["./actual.astro"] }, + "outDir": "/dist" + }, + "contentMappers": [{ + "package": "mapper", + "extensions": [".astro"], + "outputExtensions": { ".astro": ".js" } + }] + } + +==== /components.astro/index.ts (0 errors) ==== + export const directory = 1; + +==== /ambient.d.astro.ts (0 errors) ==== + export const ambient: number; + +==== /main.ts (3 errors) ==== + import { directory } from "./components.astro"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./components.astro/index.ts". + import { actual } from "./actual.astro"; + import { ambient } from "./ambient.astro"; + ~~~~~~~~~~~~~~~~~ +!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./ambient.d.astro.ts". + import { actual as aliased } from "alias"; + ~~~~~~~ +!!! error TS2877: This import uses a '.astro' extension to resolve to an input TypeScript file, but will not be rewritten during emit because it is not a relative path. + console.log(directory, actual, ambient, aliased); + +==== /node_modules/mapper/package.json (0 errors) ==== + { + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.js b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.js new file mode 100644 index 0000000000000..92e258bdef8d3 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.js @@ -0,0 +1,35 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsUnsafeRewrite.ts] //// + +//// [package.json] +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +//// [index.ts] +export const directory = 1; + +//// [actual.astro] +const __VERSION = "1.0.0"; +export const actual = 1; + +//// [ambient.d.astro.ts] +export const ambient: number; + +//// [main.ts] +import { directory } from "./components.astro"; +import { actual } from "./actual.astro"; +import { ambient } from "./ambient.astro"; +import { actual as aliased } from "alias"; +console.log(directory, actual, ambient, aliased); + + +//// [index.js] +export const directory = 1; +//// [main.js] +import { directory } from "./components.js"; +import { actual } from "./actual.js"; +import { ambient } from "./ambient.js"; +import { actual as aliased } from "alias"; +console.log(directory, actual, ambient, aliased); diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.symbols b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.symbols new file mode 100644 index 0000000000000..a3854663b320d --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.symbols @@ -0,0 +1,40 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsUnsafeRewrite.ts] //// + +=== /components.astro/index.ts === +export const directory = 1; +>directory : Symbol(directory, Decl(index.ts, 0, 12)) + +=== /actual.astro === +const __VERSION = "1.0.0"; +>__VERSION : Symbol(__VERSION, Decl(actual.astro, 0, 5)) + +export const actual = 1; +>actual : Symbol(actual, Decl(actual.astro, 1, 12)) + +=== /ambient.d.astro.ts === +export const ambient: number; +>ambient : Symbol(ambient, Decl(ambient.d.astro.ts, 0, 12)) + +=== /main.ts === +import { directory } from "./components.astro"; +>directory : Symbol(directory, Decl(main.ts, 0, 8)) + +import { actual } from "./actual.astro"; +>actual : Symbol(actual, Decl(main.ts, 1, 8)) + +import { ambient } from "./ambient.astro"; +>ambient : Symbol(ambient, Decl(main.ts, 2, 8)) + +import { actual as aliased } from "alias"; +>actual : Symbol(actual, Decl(actual.astro, 1, 12)) +>aliased : Symbol(aliased, Decl(main.ts, 3, 8)) + +console.log(directory, actual, ambient, aliased); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>directory : Symbol(directory, Decl(main.ts, 0, 8)) +>actual : Symbol(actual, Decl(main.ts, 1, 8)) +>ambient : Symbol(ambient, Decl(main.ts, 2, 8)) +>aliased : Symbol(aliased, Decl(main.ts, 3, 8)) + diff --git a/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.types b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.types new file mode 100644 index 0000000000000..8cce6fb08a709 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contentMapperOutputExtensionsUnsafeRewrite.types @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/contentMapperOutputExtensionsUnsafeRewrite.ts] //// + +=== /components.astro/index.ts === +export const directory = 1; +>directory : 1 +>1 : 1 + +=== /actual.astro === +const __VERSION = "1.0.0"; +>__VERSION : "1.0.0" +>"1.0.0" : "1.0.0" + +export const actual = 1; +>actual : 1 +>1 : 1 + +=== /ambient.d.astro.ts === +export const ambient: number; +>ambient : number + +=== /main.ts === +import { directory } from "./components.astro"; +>directory : 1 + +import { actual } from "./actual.astro"; +>actual : 1 + +import { ambient } from "./ambient.astro"; +>ambient : number + +import { actual as aliased } from "alias"; +>actual : 1 +>aliased : 1 + +console.log(directory, actual, ambient, aliased); +>console.log(directory, actual, ambient, aliased) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>directory : 1 +>actual : 1 +>ambient : number +>aliased : 1 + diff --git a/tsc/testdata/baselines/reference/compiler/rewriteRelativeImportExtensionsDynamicImportCjs.js b/tsc/testdata/baselines/reference/compiler/rewriteRelativeImportExtensionsDynamicImportCjs.js index d3c7b7cc0dda4..8a294aff4eebf 100644 --- a/tsc/testdata/baselines/reference/compiler/rewriteRelativeImportExtensionsDynamicImportCjs.js +++ b/tsc/testdata/baselines/reference/compiler/rewriteRelativeImportExtensionsDynamicImportCjs.js @@ -23,8 +23,17 @@ function getSpecifier() { } //// [index.cjs] "use strict"; -var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) { +var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); }); diff --git a/tsc/testdata/baselines/reference/conformance/emit(jsx=preserve).js b/tsc/testdata/baselines/reference/conformance/emit(jsx=preserve).js index 7832fc09e20c0..a3c8b755a428d 100644 --- a/tsc/testdata/baselines/reference/conformance/emit(jsx=preserve).js +++ b/tsc/testdata/baselines/reference/conformance/emit(jsx=preserve).js @@ -56,8 +56,17 @@ require("node:path"); //// [main.js] -var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) { +var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); }); @@ -80,8 +89,17 @@ acceptAny(import("./foo.js")); import("./foo.js", { with: { attr: "value" } }); import(__rewriteRelativeImportExtension("" + "./foo.ts", true)); //// [js.js] -var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) { +var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); }); diff --git a/tsc/testdata/baselines/reference/conformance/emit(jsx=react).js b/tsc/testdata/baselines/reference/conformance/emit(jsx=react).js index 4089a1744fa36..b23dbd2cad792 100644 --- a/tsc/testdata/baselines/reference/conformance/emit(jsx=react).js +++ b/tsc/testdata/baselines/reference/conformance/emit(jsx=react).js @@ -56,8 +56,17 @@ require("node:path"); //// [main.js] -var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) { +var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); }); @@ -80,8 +89,17 @@ acceptAny(import("./foo.js")); import("./foo.js", { with: { attr: "value" } }); import(__rewriteRelativeImportExtension("" + "./foo.ts")); //// [js.js] -var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) { +var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); }); diff --git a/tsc/testdata/baselines/reference/conformance/emitModuleCommonJS(module=commonjs).js b/tsc/testdata/baselines/reference/conformance/emitModuleCommonJS(module=commonjs).js index fc72d0a81fe4c..f88f314fe5349 100644 --- a/tsc/testdata/baselines/reference/conformance/emitModuleCommonJS(module=commonjs).js +++ b/tsc/testdata/baselines/reference/conformance/emitModuleCommonJS(module=commonjs).js @@ -50,8 +50,17 @@ var __importStar = (this && this.__importStar) || (function () { return result; }; })(); -var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) { +var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); }); @@ -99,8 +108,17 @@ var __importStar = (this && this.__importStar) || (function () { return result; }; })(); -var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) { +var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); }); diff --git a/tsc/testdata/baselines/reference/conformance/emitModuleCommonJS(module=nodenext).js b/tsc/testdata/baselines/reference/conformance/emitModuleCommonJS(module=nodenext).js index 73c169d214e58..c93a9954b8cd0 100644 --- a/tsc/testdata/baselines/reference/conformance/emitModuleCommonJS(module=nodenext).js +++ b/tsc/testdata/baselines/reference/conformance/emitModuleCommonJS(module=nodenext).js @@ -17,8 +17,17 @@ //// [a.js] "use strict"; -var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) { +var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); }); @@ -33,8 +42,17 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte } //// [b.js] "use strict"; -var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) { +var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx, extraExtensions, ignoreCase) { if (typeof path === "string" && /^\.\.?\//.test(path)) { + if (extraExtensions) { + for (var extension in extraExtensions) { + var suffix = path.slice(-extension.length); + if (suffix === extension || ignoreCase && suffix.toLowerCase() === extension.toLowerCase()) { + var output = extraExtensions[extension]; + return output === extension ? path : path.slice(0, -extension.length) + output; + } + } + } return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); }); diff --git a/tsc/testdata/baselines/reference/fourslash/syntaxandSemanticDiagnostics/contentMapperOutputExtensionsProjectReferencesAligned.baseline b/tsc/testdata/baselines/reference/fourslash/syntaxandSemanticDiagnostics/contentMapperOutputExtensionsProjectReferencesAligned.baseline new file mode 100644 index 0000000000000..05ac3aaccd1a0 --- /dev/null +++ b/tsc/testdata/baselines/reference/fourslash/syntaxandSemanticDiagnostics/contentMapperOutputExtensionsProjectReferencesAligned.baseline @@ -0,0 +1,9 @@ +// === Syntax and Semantic Diagnostics === + + +==== /app/main.ts (0 errors) ==== + import { component } from "../lib/Component.vue"; + export const value = component; + +==== /lib/Component.vue (0 errors) ==== + export const component = 1; \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/fourslash/syntaxandSemanticDiagnostics/contentMapperOutputExtensionsProjectReferencesMisaligned.baseline b/tsc/testdata/baselines/reference/fourslash/syntaxandSemanticDiagnostics/contentMapperOutputExtensionsProjectReferencesMisaligned.baseline new file mode 100644 index 0000000000000..38def0fff214f --- /dev/null +++ b/tsc/testdata/baselines/reference/fourslash/syntaxandSemanticDiagnostics/contentMapperOutputExtensionsProjectReferencesMisaligned.baseline @@ -0,0 +1,12 @@ +// === Syntax and Semantic Diagnostics === +/app/main.ts(1,27): error TS2878: This import path is unsafe to rewrite because it resolves to another project, and the relative path between the projects' output files is not the same as the relative path between its input files. + + +==== /app/main.ts (1 errors) ==== + import { component } from "../lib/Component.vue"; + ~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2878: This import path is unsafe to rewrite because it resolves to another project, and the relative path between the projects' output files is not the same as the relative path between its input files. + export const value = component; + +==== /lib/Component.vue (0 errors) ==== + export const component = 1; \ No newline at end of file diff --git a/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensions.ts b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensions.ts new file mode 100644 index 0000000000000..5227946832da6 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensions.ts @@ -0,0 +1,54 @@ +// @runExternalCode: true + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "target": "esnext", + "module": "preserve", + "moduleResolution": "bundler", + "rewriteRelativeImportExtensions": true, + "declaration": true, + "declarationMap": true, + "rootDir": "/", + "outDir": "/dist" + }, + "contentMappers": [ + { "package": "mapper", "extensions": [".astro", ".y.z"] } + ] +} + +// @Filename: /node_modules/mapper/package.json +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js", ".y.z": ".mjs" } + } + } +} + +// @Filename: /Card.astro +export interface CardProps { title: string } +export declare const Card: (props: CardProps) => string; + +// @Filename: /Widget.y.z +export declare const Widget: () => string; + +// @Filename: /typed.ts +export const annotated: import("./Card.astro").CardProps = { title: "" }; + +// @Filename: /main.ts +import { annotated } from "./typed"; +export { Card } from "./Card.astro"; +export type { CardProps } from "./Card.astro"; +export { Widget } from "./Widget.y.z"; + +const path = "./Card.astro"; +void import(path); + +export async function load() { return import("./Card.astro"); } +export const inferred = import("./Widget.y.z"); +export const nested = { load: () => import("./Card.astro") }; +export const copied = [annotated]; diff --git a/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsCollision.ts b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsCollision.ts new file mode 100644 index 0000000000000..df3d83fce9826 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsCollision.ts @@ -0,0 +1,31 @@ +// @runExternalCode: true + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "module": "preserve", + "rewriteRelativeImportExtensions": true, + "outDir": "/dist" + }, + "contentMappers": [ + { "package": "mapper", "extensions": [".astro"] } + ] +} + +// @Filename: /node_modules/mapper/package.json +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js" } + } + } +} + +// @Filename: /component.astro +export const fromAstro = true; + +// @Filename: /component.ts +export const fromTs = true; diff --git a/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsImportHelpers.ts b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsImportHelpers.ts new file mode 100644 index 0000000000000..525504bb63776 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsImportHelpers.ts @@ -0,0 +1,46 @@ +// @runExternalCode: true +// @module: preserve, commonjs + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "target": "esnext", + "rewriteRelativeImportExtensions": true, + "importHelpers": true, + "allowJs": true, + "checkJs": true, + "outDir": "/dist" + }, + "contentMappers": [{ + "package": "mapper", + "extensions": [".astro"], + "outputExtensions": { ".astro": ".js" } + }] +} + +// @Filename: /node_modules/mapper/package.json +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +// @Filename: /node_modules/tslib/package.json +{ "name": "tslib", "types": "index.d.ts" } + +// @Filename: /node_modules/tslib/index.d.ts +export declare function __rewriteRelativeImportExtension(path: any, preserveJsx?: boolean): any; + +// @Filename: /Card.astro +export const Card = 1; + +// @Filename: /main.ts +export function dynamic(path: string) { return import(path); } +export function literal() { return import("./Card.astro"); } + +// @Filename: /require.js +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } + +// @Filename: /globals.d.ts +declare function require(path: string): unknown; diff --git a/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsImportHelpersCompatible.ts b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsImportHelpersCompatible.ts new file mode 100644 index 0000000000000..c4b048cae7d9d --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsImportHelpersCompatible.ts @@ -0,0 +1,51 @@ +// @runExternalCode: true +// @module: preserve, commonjs + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "target": "esnext", + "rewriteRelativeImportExtensions": true, + "importHelpers": true, + "allowJs": true, + "checkJs": true, + "outDir": "/dist" + }, + "contentMappers": [{ + "package": "mapper", + "extensions": [".astro"], + "outputExtensions": { ".astro": ".js" } + }] +} + +// @Filename: /node_modules/mapper/package.json +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +// @Filename: /node_modules/tslib/package.json +{ "name": "tslib", "types": "index.d.ts" } + +// @Filename: /node_modules/tslib/index.d.ts +export declare function __rewriteRelativeImportExtension( + path: any, + preserveJsx?: boolean, + extraExtensions?: Record, + ignoreCase?: boolean +): any; + +// @Filename: /Card.astro +export const Card = 1; + +// @Filename: /main.ts +export function dynamic(path: string) { return import(path); } +export function literal() { return import("./Card.astro"); } + +// @Filename: /require.js +/** @param {string} path */ +export function dynamicRequire(path) { return require(path); } + +// @Filename: /globals.d.ts +declare function require(path: string): unknown; diff --git a/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsNoReverseResolution.ts b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsNoReverseResolution.ts new file mode 100644 index 0000000000000..ec9688264e940 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsNoReverseResolution.ts @@ -0,0 +1,31 @@ +// @runExternalCode: true + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "module": "preserve", + "moduleResolution": "bundler", + "rewriteRelativeImportExtensions": true + }, + "contentMappers": [ + { "package": "mapper", "extensions": [".astro"] } + ] +} + +// @Filename: /node_modules/mapper/package.json +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js" } + } + } +} + +// @Filename: /Card.astro +export const Card = 1; + +// @Filename: /main.ts +import { Card } from "./Card.js"; diff --git a/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsOverlap.ts b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsOverlap.ts new file mode 100644 index 0000000000000..8b5f08bc2c68d --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsOverlap.ts @@ -0,0 +1,50 @@ +// @runExternalCode: true + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "target": "esnext", + "module": "preserve", + "rewriteRelativeImportExtensions": true, + "declaration": true, + "rootDir": "/", + "outDir": "/dist" + }, + "contentMappers": [{ + "package": "mapper", + "extensions": [".z", ".y.z", ".identity.y.z", ".unmapped.y.z"], + "outputExtensions": { + ".z": ".js", + ".y.z": ".mjs", + ".identity.y.z": ".identity.y.z" + } + }] +} + +// @Filename: /node_modules/mapper/package.json +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +// @Filename: /Widget.z +export const short = 1; + +// @Filename: /Widget.y.z +export const long = 1; + +// @Filename: /Widget.identity.y.z +export const identity = 1; + +// @Filename: /Widget.unmapped.y.z +export const unmapped = 1; + +// @Filename: /main.ts +export { short } from "./Widget.z"; +export { long } from "./Widget.y.z"; +export { identity } from "./Widget.identity.y.z"; +export { unmapped } from "./Widget.unmapped.y.z"; +export function literal() { return import("./Widget.y.z"); } +export function dynamic(path: string) { return import(path); } +export const inferred = import("./Widget.y.z"); diff --git a/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsOverride.ts b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsOverride.ts new file mode 100644 index 0000000000000..304ed135eedab --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsOverride.ts @@ -0,0 +1,33 @@ +// @runExternalCode: true + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "declaration": true + }, + "contentMappers": [ + { + "package": "mapper", + "extensions": [".astro"], + "outputExtensions": { ".astro": ".astro" } + } + ] +} + +// @Filename: /node_modules/mapper/package.json +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js" } + } + } +} + +// @Filename: /Card.astro +export declare const Card: () => string; + +// @Filename: /main.ts +export { Card } from "./Card.astro"; diff --git a/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsRequiresRewrite.ts b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsRequiresRewrite.ts new file mode 100644 index 0000000000000..537c83e3e7b66 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsRequiresRewrite.ts @@ -0,0 +1,26 @@ +// @runExternalCode: true + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "declaration": true + }, + "contentMappers": [ + { "package": "mapper", "extensions": [".astro"] } + ] +} + +// @Filename: /node_modules/mapper/package.json +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { + "contentMapper": { + "exec": ["compiler-test-mapper"], + "outputExtensions": { ".astro": ".js" } + } + } +} + +// @Filename: /Card.astro +export const Card = 1; diff --git a/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite.ts b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite.ts new file mode 100644 index 0000000000000..107a54c40e004 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsTypeOnlyUnsafeRewrite.ts @@ -0,0 +1,60 @@ +// @runExternalCode: true +// @declaration: true, false + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "target": "esnext", + "module": "preserve", + "rewriteRelativeImportExtensions": true, + "allowArbitraryExtensions": true, + "rootDir": "/", + "outDir": "/dist" + }, + "contentMappers": [{ + "package": "mapper", + "extensions": [".astro"], + "outputExtensions": { ".astro": ".js" } + }] +} + +// @Filename: /node_modules/mapper/package.json +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +// @Filename: /components.astro/index.ts +export interface Props { title: string } + +// @Filename: /native.ts/index.ts +export interface NativeProps { title: string } + +// @Filename: /actual.astro +export interface ActualProps { title: string } + +// @Filename: /ambient.d.astro.ts +export interface AmbientProps { title: string } + +// @Filename: /existing.d.ts +export declare const existing: import("./components.astro").Props; + +// @Filename: /main.ts +export type { Props } from "./components.astro"; +export type * from "./components.astro"; +export type * as Namespace from "./components.astro"; +export { type Props as InlineProps } from "./components.astro"; +import type { Props as ImportedProps } from "./components.astro"; +export type Alias = ImportedProps; +import { type Props as InlineImportedProps } from "./components.astro"; +export type InlineAlias = InlineImportedProps; +export type ImportType = import("./components.astro").Props; +export declare const props: import("./components.astro").Props; +export type { AmbientProps } from "./ambient.astro"; +export type NativeImportType = import("./native.ts").NativeProps; +export type { ActualProps } from "./actual.astro"; +import type { ActualProps } from "./actual.astro"; +export type ActualAlias = ActualProps; +export type ActualImportType = import("./actual.astro").ActualProps; +export declare const actualProps: import("./actual.astro").ActualProps; diff --git a/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsUnsafeRewrite.ts b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsUnsafeRewrite.ts new file mode 100644 index 0000000000000..fa372f9dee9fd --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contentMapperOutputExtensionsUnsafeRewrite.ts @@ -0,0 +1,41 @@ +// @runExternalCode: true + +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "target": "esnext", + "module": "preserve", + "rewriteRelativeImportExtensions": true, + "allowArbitraryExtensions": true, + "paths": { "alias": ["./actual.astro"] }, + "outDir": "/dist" + }, + "contentMappers": [{ + "package": "mapper", + "extensions": [".astro"], + "outputExtensions": { ".astro": ".js" } + }] +} + +// @Filename: /node_modules/mapper/package.json +{ + "name": "mapper", + "version": "1.0.0", + "typescript": { "contentMapper": { "exec": ["compiler-test-mapper"] } } +} + +// @Filename: /components.astro/index.ts +export const directory = 1; + +// @Filename: /actual.astro +export const actual = 1; + +// @Filename: /ambient.d.astro.ts +export const ambient: number; + +// @Filename: /main.ts +import { directory } from "./components.astro"; +import { actual } from "./actual.astro"; +import { ambient } from "./ambient.astro"; +import { actual as aliased } from "alias"; +console.log(directory, actual, ambient, aliased);