Skip to content

Port TypeScript PR #59767: Rewrite relative import extensions with flag #1138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/microsoft/typescript-go/internal/jsnum"
"github.com/microsoft/typescript-go/internal/module"
"github.com/microsoft/typescript-go/internal/modulespecifiers"
"github.com/microsoft/typescript-go/internal/outputpaths"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/stringutil"
Expand Down Expand Up @@ -14497,6 +14498,34 @@ func (c *Checker) resolveExternalModule(location *ast.Node, moduleReference stri
tsExtension,
)
}
} else if c.compilerOptions.RewriteRelativeImportExtensions.IsTrue() &&
location.Flags&ast.NodeFlagsAmbient == 0 &&
!tspath.IsDeclarationFileName(moduleReference) &&
!ast.IsLiteralImportTypeNode(location) &&
!ast.IsPartOfTypeOnlyImportOrExportDeclaration(location) {
shouldRewrite := c.shouldRewriteModuleSpecifier(moduleReference)
if !resolvedModule.ResolvedUsingTsExtension && shouldRewrite {
relativeToSourceFile := tspath.GetRelativePathFromFile(
tspath.GetNormalizedAbsolutePath(importingSourceFile.FileName(), c.program.GetCurrentDirectory()),
resolvedModule.ResolvedFileName,
tspath.ComparePathsOptions{
UseCaseSensitiveFileNames: c.program.UseCaseSensitiveFileNames(),
CurrentDirectory: c.program.GetCurrentDirectory(),
},
)
c.error(
errorNode,
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 && outputpaths.SourceFileMayBeEmitted(sourceFile, c.compilerOptions) {
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, false),
)
}
// TODO: Add project reference check when GetResolvedProjectReferenceToRedirect is implemented
}
}

Expand Down Expand Up @@ -30379,3 +30408,10 @@ func (c *Checker) GetEmitResolver(file *ast.SourceFile, skipDiagnostics bool) *e
func (c *Checker) GetAliasedSymbol(symbol *ast.Symbol) *ast.Symbol {
return c.resolveAlias(symbol)
}

func (c *Checker) shouldRewriteModuleSpecifier(specifier string) bool {
return c.compilerOptions.RewriteRelativeImportExtensions.IsTrue() &&
tspath.PathIsRelative(specifier) &&
!tspath.IsDeclarationFileName(specifier) &&
tspath.HasTSFileExtension(specifier)
}
16 changes: 16 additions & 0 deletions internal/outputpaths/outputpaths.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,19 @@ func getDeclarationEmitExtensionForPath(fileName string) string {
}
return tspath.ExtensionDts
}

// SourceFileMayBeEmitted checks if a source file may be emitted by the compiler
func SourceFileMayBeEmitted(sourceFile *ast.SourceFile, options *core.CompilerOptions) bool {
if options.NoEmit.IsTrue() || options.EmitDeclarationOnly.IsTrue() {
return false
}
// Check if this source file is a declaration file
if tspath.IsDeclarationFileName(sourceFile.FileName()) {
return false
}
// Check if this is a JS file and allowJs is disabled
if tspath.HasJSFileExtension(sourceFile.FileName()) && !options.AllowJs.IsTrue() {
return false
}
return true
}
4 changes: 2 additions & 2 deletions internal/tsoptions/declscompiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -810,8 +810,8 @@ var commonOptionsWithBuild = []*CommandLineOption{
AffectsSemanticDiagnostics: true,
AffectsBuildInfo: true,
Category: diagnostics.Modules,
// description: diagnostics.Rewrite_ts_tsx_mts_and_cts_file_extensions_in_relative_import_paths_to_their_JavaScript_equivalent_in_output_files,
DefaultValueDescription: false,
Description: diagnostics.Rewrite_ts_tsx_mts_and_cts_file_extensions_in_relative_import_paths_to_their_JavaScript_equivalent_in_output_files,
DefaultValueDescription: false,
},
{
Name: "resolvePackageJsonExports",
Expand Down
13 changes: 13 additions & 0 deletions internal/tspath/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,10 @@ func GetRelativePathFromDirectory(fromDirectory string, to string, options Compa
return GetPathFromPathComponents(pathComponents)
}

func GetRelativePathFromFile(from string, to string, options ComparePathsOptions) string {
return EnsurePathIsNonModuleName(GetRelativePathFromDirectory(GetDirectoryPath(from), to, options))
}

func ConvertToRelativePath(absoluteOrRelativePath string, options ComparePathsOptions) string {
if !IsRootedDiskPath(absoluteOrRelativePath) {
return absoluteOrRelativePath
Expand Down Expand Up @@ -768,6 +772,15 @@ func PathIsRelative(path string) bool {
return false
}

// EnsurePathIsNonModuleName ensures a path is either absolute (prefixed with `/` or `c:`) or dot-relative (prefixed
// with `./` or `../`) so as not to be confused with an unprefixed module name.
func EnsurePathIsNonModuleName(path string) string {
if !PathIsAbsolute(path) && !PathIsRelative(path) {
return "./" + path
}
return path
}

func IsExternalModuleNameRelative(moduleName string) bool {
// TypeScript 1.0 spec (April 2014): 11.2.1
// An external module name is "relative" if the first term is "." or "..".
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
index.ts(1,22): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./foo.ts/index.ts".


==== index.ts (1 errors) ====
import foo = require("./foo.ts"); // Error
~~~~~~~~~~
!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./foo.ts/index.ts".
import type _foo = require("./foo.ts"); // Ok

==== foo.ts/index.ts (0 errors) ====
export = {};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
index.ts(1,22): error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./foo.ts/index.ts".


==== index.ts (1 errors) ====
import foo = require("./foo.ts"); // Error
~~~~~~~~~~
!!! error TS2876: This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to "./foo.ts/index.ts".
import type _foo = require("./foo.ts"); // Ok

==== foo.ts/index.ts (0 errors) ====
export = {};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/index.ts(2,16): error TS2877: This import uses a '.ts' extension to resolve to an input TypeScript file, but will not be rewritten during emit because it is not a relative path.


==== /package.json (0 errors) ====
{
"name": "pkg",
"type": "module",
"imports": {
"#foo.ts": "./foo.ts",
"#internal/*": "./internal/*"
},
"exports": {
"./*.ts": {
"source": "./*.ts",
"default": "./*.js"
}
}
}

==== /foo.ts (0 errors) ====
export {};

==== /internal/foo.ts (0 errors) ====
export {};

==== /index.ts (1 errors) ====
import {} from "#foo.ts"; // Ok
import {} from "#internal/foo.ts"; // Error
~~~~~~~~~~~~~~~~~~
!!! error TS2877: This import uses a '.ts' extension to resolve to an input TypeScript file, but will not be rewritten during emit because it is not a relative path.
import {} from "pkg/foo.ts"; // Ok

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/index.ts(2,16): error TS2877: This import uses a '.ts' extension to resolve to an input TypeScript file, but will not be rewritten during emit because it is not a relative path.


==== /package.json (0 errors) ====
{
"name": "pkg",
"type": "module",
"imports": {
"#foo.ts": "./foo.ts",
"#internal/*": "./internal/*"
},
"exports": {
"./*.ts": {
"source": "./*.ts",
"default": "./*.js"
}
}
}

==== /foo.ts (0 errors) ====
export {};

==== /internal/foo.ts (0 errors) ====
export {};

==== /index.ts (1 errors) ====
import {} from "#foo.ts"; // Ok
import {} from "#internal/foo.ts"; // Error
~~~~~~~~~~~~~~~~~~
!!! error TS2877: This import uses a '.ts' extension to resolve to an input TypeScript file, but will not be rewritten during emit because it is not a relative path.
import {} from "pkg/foo.ts"; // Ok

This file was deleted.

Loading