Skip to content

Commit 1bbeb2e

Browse files
authored
Merge branch 'main' into feat/2280
2 parents 4de79a7 + e7283bb commit 1bbeb2e

386 files changed

Lines changed: 735 additions & 25 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package fourslash_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/microsoft/typescript-go/internal/fourslash"
7+
"github.com/microsoft/typescript-go/internal/testutil"
8+
)
9+
10+
// TestAutoImportCJSWithNodeModuleKind verifies that auto-imports use require()
11+
// syntax in CJS files when using node16/node20/nodenext module kinds with a
12+
// package.json that has "type": "commonjs".
13+
func TestAutoImportCJSWithNodeModuleKind(t *testing.T) {
14+
t.Parallel()
15+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
16+
const content = `// @Filename: /tsconfig.json
17+
{
18+
"compilerOptions": {
19+
"allowJs": true,
20+
"module": "node20",
21+
"checkJs": true,
22+
"noEmit": true
23+
}
24+
}
25+
// @Filename: /package.json
26+
{ "type": "commonjs" }
27+
// @Filename: /lib.js
28+
module.exports = { LIB_VERSION: 1 };
29+
// @Filename: /main.js
30+
module.exports.foo = 0;
31+
LIB_VERSION/**/`
32+
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
33+
defer done()
34+
35+
f.GoToMarker(t, "")
36+
f.VerifyImportFixAtPosition(t, []string{
37+
`const { LIB_VERSION } = require("./lib");
38+
39+
module.exports.foo = 0;
40+
LIB_VERSION`,
41+
}, nil /*preferences*/)
42+
}
43+
44+
// TestAutoImportCJSWithNodeModuleKindEmptyFile verifies that auto-imports use
45+
// require() syntax even in empty CJS files when using node module kinds.
46+
func TestAutoImportCJSWithNodeModuleKindEmptyFile(t *testing.T) {
47+
t.Parallel()
48+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
49+
const content = `// @Filename: /tsconfig.json
50+
{
51+
"compilerOptions": {
52+
"allowJs": true,
53+
"module": "node20",
54+
"checkJs": true,
55+
"noEmit": true
56+
}
57+
}
58+
// @Filename: /package.json
59+
{ "type": "commonjs" }
60+
// @Filename: /lib.js
61+
module.exports = { LIB_VERSION: 1 };
62+
// @Filename: /main.js
63+
LIB_VERSION/**/`
64+
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
65+
defer done()
66+
67+
f.GoToMarker(t, "")
68+
f.VerifyImportFixAtPosition(t, []string{
69+
`const { LIB_VERSION } = require("./lib");
70+
71+
LIB_VERSION`,
72+
}, nil /*preferences*/)
73+
}
74+
75+
// TestAutoImportCJSWithModuleDetectionForce verifies that auto-imports use
76+
// require() syntax in JS files with CJS syntax when moduleDetection is "force",
77+
// even with --module preserve where GetImpliedNodeFormatForEmit won't help.
78+
func TestAutoImportCJSWithModuleDetectionForce(t *testing.T) {
79+
t.Parallel()
80+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
81+
const content = `// @Filename: /tsconfig.json
82+
{
83+
"compilerOptions": {
84+
"allowJs": true,
85+
"module": "preserve",
86+
"moduleDetection": "force",
87+
"checkJs": true,
88+
"noEmit": true
89+
}
90+
}
91+
// @Filename: /lib.js
92+
export const LIB_VERSION = 1;
93+
// @Filename: /main.js
94+
const path = require("path");
95+
LIB_VERSION/**/`
96+
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
97+
defer done()
98+
99+
f.GoToMarker(t, "")
100+
f.VerifyImportFixAtPosition(t, []string{
101+
`const path = require("path");
102+
const { LIB_VERSION } = require("./lib");
103+
LIB_VERSION`,
104+
}, nil /*preferences*/)
105+
}

internal/ls/autoimport/fix.go

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -859,27 +859,84 @@ func (v *View) shouldUseRequire() bool {
859859
return shouldUseRequire
860860
}
861861

862+
// fileSyntaxKind represents the detected module syntax of a source file.
863+
type fileSyntaxKind int
864+
865+
const (
866+
fileSyntaxKindAmbiguous fileSyntaxKind = iota
867+
fileSyntaxKindESM
868+
fileSyntaxKindCJS
869+
)
870+
871+
// detectSyntax returns whether a source file has unambiguous ESM or CJS syntax.
872+
// When moduleDetection is "force", ExternalModuleIndicator may be set to the
873+
// source file node itself rather than a genuine syntax indicator, so we fall back
874+
// to inspecting the file's Imports() to find actual import/export declarations.
875+
func detectSyntax(file *ast.SourceFile, options *core.CompilerOptions) fileSyntaxKind {
876+
hasESM, hasCJS := detectSyntaxIndicators(file, options)
877+
switch {
878+
case hasCJS && !hasESM:
879+
return fileSyntaxKindCJS
880+
case hasESM && !hasCJS:
881+
return fileSyntaxKindESM
882+
default:
883+
return fileSyntaxKindAmbiguous
884+
}
885+
}
886+
887+
// detectSyntaxIndicators checks whether a source file contains genuine ESM
888+
// and/or CJS syntax. Under moduleDetection "force", the cached
889+
// ExternalModuleIndicator may be the source file itself rather than a real
890+
// statement, so we look at Imports() for actual import/export declarations.
891+
func detectSyntaxIndicators(file *ast.SourceFile, options *core.CompilerOptions) (hasESM bool, hasCJS bool) {
892+
hasCJS = file.CommonJSModuleIndicator != nil
893+
if options.GetEmitModuleDetectionKind() != core.ModuleDetectionKindForce {
894+
// ExternalModuleIndicator is reliable when moduleDetection is not "force"
895+
hasESM = file.ExternalModuleIndicator != nil
896+
return hasESM, hasCJS
897+
}
898+
// Under moduleDetection "force", ExternalModuleIndicator is set to
899+
// file.AsNode() when there is no genuine ESM syntax, so only trust it
900+
// when it points to a real statement node.
901+
if file.ExternalModuleIndicator != nil && file.ExternalModuleIndicator != file.AsNode() {
902+
return true, hasCJS
903+
}
904+
// Fall back to scanning Imports() for actual import/export declarations
905+
// (not require() calls or dynamic imports).
906+
for _, imp := range file.Imports() {
907+
if imp.Flags&ast.NodeFlagsSynthesized != 0 {
908+
continue
909+
}
910+
parent := imp.Parent
911+
if parent == nil {
912+
continue
913+
}
914+
switch parent.Kind {
915+
case ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindExportDeclaration:
916+
return true, hasCJS
917+
case ast.KindExternalModuleReference:
918+
// import x = require("...") — this is ESM-ish syntax
919+
return true, hasCJS
920+
}
921+
}
922+
return hasESM, hasCJS
923+
}
924+
862925
func (v *View) computeShouldUseRequire() bool {
863926
// 1. TypeScript files don't use require variable declarations
864927
if !tspath.HasJSFileExtension(v.importingFile.FileName()) {
865928
return false
866929
}
867930

868931
// 2. If the current source file is unambiguously CJS or ESM, go with that
869-
switch {
870-
case v.importingFile.CommonJSModuleIndicator != nil && v.importingFile.ExternalModuleIndicator == nil:
932+
switch detectSyntax(v.importingFile, v.program.Options()) {
933+
case fileSyntaxKindCJS:
871934
return true
872-
case v.importingFile.ExternalModuleIndicator != nil && v.importingFile.CommonJSModuleIndicator == nil:
935+
case fileSyntaxKindESM:
873936
return false
874937
}
875938

876-
// 3. If there's a tsconfig/jsconfig, use its module setting
877-
if v.program.Options().ConfigFilePath != "" {
878-
return v.program.Options().GetEmitModuleKind() < core.ModuleKindES2015
879-
}
880-
881-
// 4. In --module nodenext, assume we're not emitting JS -> JS, so use
882-
// whatever syntax Node expects based on the detected module kind
939+
// 3. Use the implied node format to determine CJS vs ESM
883940
// TODO: consider removing `impliedNodeFormatForEmit`
884941
switch v.program.GetImpliedNodeFormatForEmit(v.importingFile) {
885942
case core.ModuleKindCommonJS:
@@ -888,14 +945,21 @@ func (v *View) computeShouldUseRequire() bool {
888945
return false
889946
}
890947

948+
// 4. If there's a tsconfig/jsconfig, use its module setting
949+
if v.program.Options().ConfigFilePath != "" {
950+
return v.program.Options().GetEmitModuleKind() < core.ModuleKindES2015
951+
}
952+
891953
// 5. Match the first other JS file in the program that's unambiguously CJS or ESM
892954
for _, otherFile := range v.program.GetSourceFiles() {
893955
switch {
894956
case otherFile == v.importingFile, !ast.IsSourceFileJS(otherFile), v.program.IsSourceFileFromExternalLibrary(otherFile):
895957
continue
896-
case otherFile.CommonJSModuleIndicator != nil && otherFile.ExternalModuleIndicator == nil:
958+
}
959+
switch detectSyntax(otherFile, v.program.Options()) {
960+
case fileSyntaxKindCJS:
897961
return true
898-
case otherFile.ExternalModuleIndicator != nil && otherFile.CommonJSModuleIndicator == nil:
962+
case fileSyntaxKindESM:
899963
return false
900964
}
901965
}

internal/lsp/server.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,17 +1101,17 @@ func (s *Server) handleInitialize(ctx context.Context, params *lsproto.Initializ
11011101
},
11021102
},
11031103
},
1104-
// SemanticTokensProvider: &lsproto.SemanticTokensOptionsOrRegistrationOptions{
1105-
// Options: &lsproto.SemanticTokensOptions{
1106-
// Legend: ls.SemanticTokensLegend(s.clientCapabilities.TextDocument.SemanticTokens),
1107-
// Full: &lsproto.BooleanOrSemanticTokensFullDelta{
1108-
// Boolean: new(true),
1109-
// },
1110-
// Range: &lsproto.BooleanOrEmptyObject{
1111-
// Boolean: new(true),
1112-
// },
1113-
// },
1114-
// },
1104+
SemanticTokensProvider: &lsproto.SemanticTokensOptionsOrRegistrationOptions{
1105+
Options: &lsproto.SemanticTokensOptions{
1106+
Legend: ls.SemanticTokensLegend(s.clientCapabilities.TextDocument.SemanticTokens),
1107+
Full: &lsproto.BooleanOrSemanticTokensFullDelta{
1108+
Boolean: new(true),
1109+
},
1110+
Range: &lsproto.BooleanOrEmptyObject{
1111+
Boolean: new(true),
1112+
},
1113+
},
1114+
},
11151115
},
11161116
}
11171117

internal/tsoptions/parsinghelpers.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ func parseNumber(value any) *int {
6363
if num, ok := value.(int); ok {
6464
return &num
6565
}
66+
if num, ok := value.(float64); ok {
67+
n := int(num)
68+
return &n
69+
}
6670
return nil
6771
}
6872

testdata/baselines/reference/config/tsconfigParsing/parses tsconfig with compilerOptions, files, include, and exclude with json api.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ CompilerOptions::
4949
},
5050
"strict": true,
5151
"target": 4,
52+
"maxNodeModuleJsDepth": 1,
5253
"configFilePath": "/apath/tsconfig.json",
5354
"pathsBasePath": "/apath"
5455
}

testdata/baselines/reference/config/tsconfigParsing/parses tsconfig with compilerOptions, files, include, and exclude with jsonSourceFile api.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ CompilerOptions::
4949
},
5050
"strict": true,
5151
"target": 4,
52+
"maxNodeModuleJsDepth": 1,
5253
"configFilePath": "/apath/tsconfig.json",
5354
"pathsBasePath": "/apath"
5455
}

testdata/baselines/reference/submoduleAccepted/compiler/declarationEmitClassMemberWithComputedPropertyName.js.diff renamed to testdata/baselines/reference/submodule/compiler/declarationEmitClassMemberWithComputedPropertyName.js.diff

File renamed without changes.

testdata/baselines/reference/submodule/compiler/blockScopedBindingsInDownlevelGenerator(target=es2015).errors.txt.diff renamed to testdata/baselines/reference/submoduleAccepted/compiler/blockScopedBindingsInDownlevelGenerator(target=es2015).errors.txt.diff

File renamed without changes.

testdata/baselines/reference/submodule/compiler/ctsFileInEsnextHelpers.errors.txt.diff renamed to testdata/baselines/reference/submoduleAccepted/compiler/ctsFileInEsnextHelpers.errors.txt.diff

File renamed without changes.

testdata/baselines/reference/submodule/compiler/declarationEmitUnknownImport(target=es2015).errors.txt.diff renamed to testdata/baselines/reference/submoduleAccepted/compiler/declarationEmitUnknownImport(target=es2015).errors.txt.diff

File renamed without changes.

0 commit comments

Comments
 (0)