Skip to content

Port more resolveExternalModule, fix copy paste error in GetResolutionDiagnostic #1248

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 3 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
58 changes: 37 additions & 21 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -14482,14 +14482,24 @@ func (c *Checker) resolveExternalModule(location *ast.Node, moduleReference stri
mode = c.program.GetDefaultResolutionModeForFile(importingSourceFile)
}

var sourceFile *ast.SourceFile
resolvedModule := c.program.GetResolvedModule(importingSourceFile, moduleReference, mode)
if resolvedModule.IsResolved() {

var resolutionDiagnostic *diagnostics.Message
if errorNode != nil && resolvedModule.IsResolved() {
resolutionDiagnostic = module.GetResolutionDiagnostic(c.compilerOptions, resolvedModule, importingSourceFile)
}

var sourceFile *ast.SourceFile
if resolvedModule.IsResolved() && (resolutionDiagnostic == nil || resolutionDiagnostic == diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set) {
sourceFile = c.program.GetSourceFileForResolvedModule(resolvedModule.ResolvedFileName)
}

if sourceFile != nil {
// !!!
// If there's a resolutionDiagnostic we need to report it even if a sourceFile is found.
if resolutionDiagnostic != nil {
c.error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.ResolvedFileName)
}

if errorNode != nil {
if resolvedModule.ResolvedUsingTsExtension && tspath.IsDeclarationFileName(moduleReference) {
if ast.FindAncestor(location, ast.IsEmittableImport) != nil {
Expand Down Expand Up @@ -14577,7 +14587,7 @@ func (c *Checker) resolveExternalModule(location *ast.Node, moduleReference stri

if sourceFile.Symbol != nil {
if errorNode != nil {
if resolvedModule.IsExternalLibraryImport && !(tspath.ExtensionIsTs(resolvedModule.Extension) || resolvedModule.Extension == tspath.ExtensionJson) {
if resolvedModule.IsExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.Extension) {
c.errorOnImplicitAnyModule(false /*isError*/, errorNode, mode, resolvedModule, moduleReference)
}
if c.moduleKind == core.ModuleKindNode16 || c.moduleKind == core.ModuleKindNode18 {
Expand Down Expand Up @@ -14633,7 +14643,7 @@ func (c *Checker) resolveExternalModule(location *ast.Node, moduleReference stri
return nil
}

if resolvedModule.IsResolved() && !(tspath.ExtensionIsTs(resolvedModule.Extension) || resolvedModule.Extension == tspath.ExtensionJson) {
if resolvedModule.IsResolved() && !resolutionExtensionIsTSOrJson(resolvedModule.Extension) && resolutionDiagnostic == nil || resolutionDiagnostic == diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type {
if isForAugmentation {
c.error(
errorNode,
Expand All @@ -14648,7 +14658,6 @@ func (c *Checker) resolveExternalModule(location *ast.Node, moduleReference stri
}

if moduleNotFoundError != nil {

// See if this was possibly a projectReference redirect
if resolvedModule.IsResolved() {
redirect := c.program.GetOutputAndProjectReference(tspath.ToPath(resolvedModule.ResolvedFileName, c.program.GetCurrentDirectory(), c.program.UseCaseSensitiveFileNames()))
Expand All @@ -14663,29 +14672,36 @@ func (c *Checker) resolveExternalModule(location *ast.Node, moduleReference stri
}
}

// !!!
isExtensionlessRelativePathImport := tspath.PathIsRelative(moduleReference) && !tspath.HasExtension(moduleReference)
resolutionIsNode16OrNext := c.moduleResolutionKind == core.ModuleResolutionKindNode16 || c.moduleResolutionKind == core.ModuleResolutionKindNodeNext
if !c.compilerOptions.GetResolveJsonModule() && tspath.FileExtensionIs(moduleReference, tspath.ExtensionJson) {
c.error(errorNode, diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference)
} else if mode == core.ResolutionModeESM && resolutionIsNode16OrNext && isExtensionlessRelativePathImport {
absoluteRef := tspath.GetNormalizedAbsolutePath(moduleReference, tspath.GetDirectoryPath(importingSourceFile.FileName()))
if suggestedExt := c.getSuggestedImportExtension(absoluteRef); suggestedExt != "" {
c.error(errorNode, diagnostics.Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_node16_or_nodenext_Did_you_mean_0, moduleReference+suggestedExt)
if resolutionDiagnostic != nil {
c.error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.ResolvedFileName)
} else {
isExtensionlessRelativePathImport := tspath.PathIsRelative(moduleReference) && !tspath.HasExtension(moduleReference)
resolutionIsNode16OrNext := c.moduleResolutionKind == core.ModuleResolutionKindNode16 || c.moduleResolutionKind == core.ModuleResolutionKindNodeNext
if !c.compilerOptions.GetResolveJsonModule() && tspath.FileExtensionIs(moduleReference, tspath.ExtensionJson) {
Copy link
Member

Choose a reason for hiding this comment

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

This is in the current snapshot, but does this not kick in for node20 in Strada?

Copy link
Member Author

Choose a reason for hiding this comment

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

See the description on microsoft/TypeScript#61805; it would if Corsa had node20.

c.error(errorNode, diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference)
} else if mode == core.ResolutionModeESM && resolutionIsNode16OrNext && isExtensionlessRelativePathImport {
absoluteRef := tspath.GetNormalizedAbsolutePath(moduleReference, tspath.GetDirectoryPath(importingSourceFile.FileName()))
if suggestedExt := c.getSuggestedImportExtension(absoluteRef); suggestedExt != "" {
c.error(errorNode, diagnostics.Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_node16_or_nodenext_Did_you_mean_0, moduleReference+suggestedExt)
} else {
c.error(errorNode, diagnostics.Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_node16_or_nodenext_Consider_adding_an_extension_to_the_import_path)
}
} else if resolvedModule != nil && resolvedModule.AlternateResult != "" {
errorInfo := c.createModuleNotFoundChain(resolvedModule, errorNode, moduleReference, mode, moduleReference)
c.diagnostics.Add(NewDiagnosticChainForNode(errorInfo, errorNode, moduleNotFoundError, moduleReference))
} else {
c.error(errorNode, diagnostics.Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_node16_or_nodenext_Consider_adding_an_extension_to_the_import_path)
c.error(errorNode, moduleNotFoundError, moduleReference)
}
} else if resolvedModule != nil && resolvedModule.AlternateResult != "" {
errorInfo := c.createModuleNotFoundChain(resolvedModule, errorNode, moduleReference, mode, moduleReference)
c.diagnostics.Add(NewDiagnosticChainForNode(errorInfo, errorNode, moduleNotFoundError, moduleReference))
} else {
c.error(errorNode, moduleNotFoundError, moduleReference)
}
}

return nil
}

func resolutionExtensionIsTSOrJson(ext string) bool {
return tspath.ExtensionIsTs(ext) || ext == tspath.ExtensionJson
}

func (c *Checker) getSuggestedImportSource(moduleReference string, tsExtension string, mode core.ResolutionMode) string {
importSourceWithoutExtension := tspath.RemoveExtension(moduleReference, tsExtension)

Expand Down
54 changes: 1 addition & 53 deletions internal/compiler/fileloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/module"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
Expand Down Expand Up @@ -432,7 +431,7 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(t *parseTask) {
importIndex := index - importsStart

shouldAddFile := moduleName != "" &&
getResolutionDiagnostic(optionsForFile, resolvedModule, file) == nil &&
module.GetResolutionDiagnostic(optionsForFile, resolvedModule, file) == nil &&
!optionsForFile.NoResolve.IsTrue() &&
!(isJsFile && !optionsForFile.GetAllowJS()) &&
(importIndex < 0 || (importIndex < len(file.Imports()) && (ast.IsInJSFile(file.Imports()[importIndex]) || file.Imports()[importIndex].Flags&ast.NodeFlagsJSDoc == 0)))
Expand All @@ -451,57 +450,6 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(t *parseTask) {
}
}

// Returns a DiagnosticMessage if we won't include a resolved module due to its extension.
// The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to.
// This returns a diagnostic even if the module will be an untyped module.
func getResolutionDiagnostic(options *core.CompilerOptions, resolvedModule *module.ResolvedModule, file *ast.SourceFile) *diagnostics.Message {
needJsx := func() *diagnostics.Message {
if options.Jsx != core.JsxEmitNone {
return nil
}
return diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set
}

needAllowJs := func() *diagnostics.Message {
if options.GetAllowJS() || !options.NoImplicitAny.DefaultIfUnknown(options.Strict).IsTrue() {
return nil
}
return diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used
}

needResolveJsonModule := func() *diagnostics.Message {
if options.GetResolveJsonModule() {
return nil
}
return diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used
}

needAllowArbitraryExtensions := func() *diagnostics.Message {
if file.IsDeclarationFile || options.AllowArbitraryExtensions.IsTrue() {
return nil
}
return diagnostics.Module_0_was_resolved_to_1_but_allowArbitraryExtensions_is_not_set
}

switch resolvedModule.Extension {
case tspath.ExtensionTs, tspath.ExtensionDts,
tspath.ExtensionMts, tspath.ExtensionDmts,
tspath.ExtensionCts, tspath.ExtensionDcts:
// These are always allowed.
return nil
case tspath.ExtensionTsx:
return needJsx()
case tspath.ExtensionJsx:
return core.Coalesce(needJsx(), needAllowJs())
case tspath.ExtensionJs, tspath.ExtensionMjs, tspath.ExtensionCjs:
return needAllowJs()
case tspath.ExtensionJson:
return needResolveJsonModule()
default:
return needAllowArbitraryExtensions()
}
}

func (p *fileLoader) createSyntheticImport(text string, file *ast.SourceFile) *ast.Node {
p.factoryMu.Lock()
defer p.factoryMu.Unlock()
Expand Down
56 changes: 56 additions & 0 deletions internal/module/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package module
import (
"strings"

"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/semver"
"github.com/microsoft/typescript-go/internal/tspath"
)
Expand Down Expand Up @@ -97,3 +99,57 @@ func ComparePatternKeys(a, b string) int {
}
return 0
}

// Returns a DiagnosticMessage if we won't include a resolved module due to its extension.
// The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to.
// This returns a diagnostic even if the module will be an untyped module.
func GetResolutionDiagnostic(options *core.CompilerOptions, resolvedModule *ResolvedModule, file *ast.SourceFile) *diagnostics.Message {
needJsx := func() *diagnostics.Message {
if options.Jsx != core.JsxEmitNone {
return nil
}
return diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set
}

needAllowJs := func() *diagnostics.Message {
if options.GetAllowJS() || !options.NoImplicitAny.DefaultIfUnknown(options.Strict).IsTrue() {
return nil
}
return diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type
}

needResolveJsonModule := func() *diagnostics.Message {
if options.GetResolveJsonModule() {
return nil
}
return diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used
}

needAllowArbitraryExtensions := func() *diagnostics.Message {
if file.IsDeclarationFile || options.AllowArbitraryExtensions.IsTrue() {
return nil
}
return diagnostics.Module_0_was_resolved_to_1_but_allowArbitraryExtensions_is_not_set
}

switch resolvedModule.Extension {
case tspath.ExtensionTs, tspath.ExtensionDts,
tspath.ExtensionMts, tspath.ExtensionDmts,
tspath.ExtensionCts, tspath.ExtensionDcts:
// These are always allowed.
return nil
case tspath.ExtensionTsx:
return needJsx()
case tspath.ExtensionJsx:
if message := needJsx(); message != nil {
return message
}
return needAllowJs()
case tspath.ExtensionJs, tspath.ExtensionMjs, tspath.ExtensionCjs:
return needAllowJs()
case tspath.ExtensionJson:
return needResolveJsonModule()
default:
return needAllowArbitraryExtensions()
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/bar.jsx(1,17): error TS6142: Module '/foo' was resolved to '/foo.jsx', but '--jsx' is not set.
/bar.jsx(2,11): error TS17004: Cannot use JSX unless the '--jsx' flag is provided.
/foo.jsx(2,5): error TS17004: Cannot use JSX unless the '--jsx' flag is provided.

Expand All @@ -10,8 +11,10 @@
);
export default Foo;

==== /bar.jsx (1 errors) ====
==== /bar.jsx (2 errors) ====
import Foo from '/foo';
~~~~~~
!!! error TS6142: Module '/foo' was resolved to '/foo.jsx', but '--jsx' is not set.
const a = <Foo />
~~~~~~~
!!! error TS17004: Cannot use JSX unless the '--jsx' flag is provided.
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
/a.ts(1,17): error TS2307: Cannot find module './tsx' or its corresponding type declarations.
/a.ts(1,17): error TS6142: Module './tsx' was resolved to '/tsx.tsx', but '--jsx' is not set.
/a.ts(2,17): error TS6142: Module './jsx' was resolved to '/jsx.jsx', but '--jsx' is not set.


==== /a.ts (1 errors) ====
==== /a.ts (2 errors) ====
import tsx from "./tsx"; // Not allowed.
~~~~~~~
!!! error TS2307: Cannot find module './tsx' or its corresponding type declarations.
!!! error TS6142: Module './tsx' was resolved to '/tsx.tsx', but '--jsx' is not set.
import jsx from "./jsx"; // Not allowed.
~~~~~~~
!!! error TS6142: Module './jsx' was resolved to '/jsx.jsx', but '--jsx' is not set.
import js from "./js"; // OK because it's an untyped module.

==== /tsx.tsx (0 errors) ====
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/a.ts(1,17): error TS6142: Module './jsx' was resolved to '/jsx.jsx', but '--jsx' is not set.


==== /a.ts (1 errors) ====
import jsx from "./jsx";
~~~~~~~
!!! error TS6142: Module './jsx' was resolved to '/jsx.jsx', but '--jsx' is not set.

==== /jsx.jsx (0 errors) ====

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/project/main.ts(8,16): error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './b.js' instead?
/project/main.ts(11,16): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
/project/main.ts(12,16): error TS5097: An import path can only end with a '.tsx' extension when 'allowImportingTsExtensions' is enabled.
/project/main.ts(12,16): error TS6142: Module './c.tsx' was resolved to '/project/c.tsx', but '--jsx' is not set.
/project/main.ts(16,16): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
/project/types.d.ts(2,16): error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './a.js' instead?

Expand Down Expand Up @@ -43,7 +44,7 @@
==== /project/e.txt.ts (0 errors) ====
export {};

==== /project/main.ts (6 errors) ====
==== /project/main.ts (7 errors) ====
import {} from "./a";
import {} from "./a.js";
import {} from "./a.ts";
Expand All @@ -66,6 +67,8 @@
import {} from "./c.tsx";
~~~~~~~~~
!!! error TS5097: An import path can only end with a '.tsx' extension when 'allowImportingTsExtensions' is enabled.
~~~~~~~~~
!!! error TS6142: Module './c.tsx' was resolved to '/project/c.tsx', but '--jsx' is not set.

import {} from "./d";
import {} from "./d/index";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/project/main.ts(8,16): error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './b.js' instead?
/project/main.ts(11,16): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
/project/main.ts(12,16): error TS5097: An import path can only end with a '.tsx' extension when 'allowImportingTsExtensions' is enabled.
/project/main.ts(12,16): error TS6142: Module './c.tsx' was resolved to '/project/c.tsx', but '--jsx' is not set.
/project/main.ts(16,16): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
/project/types.d.ts(2,16): error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './a.js' instead?

Expand Down Expand Up @@ -43,7 +44,7 @@
==== /project/e.txt.ts (0 errors) ====
export {};

==== /project/main.ts (6 errors) ====
==== /project/main.ts (7 errors) ====
import {} from "./a";
import {} from "./a.js";
import {} from "./a.ts";
Expand All @@ -66,6 +67,8 @@
import {} from "./c.tsx";
~~~~~~~~~
!!! error TS5097: An import path can only end with a '.tsx' extension when 'allowImportingTsExtensions' is enabled.
~~~~~~~~~
!!! error TS6142: Module './c.tsx' was resolved to '/project/c.tsx', but '--jsx' is not set.

import {} from "./d";
import {} from "./d/index";
Expand Down
Loading