Skip to content

Commit a88fef7

Browse files
authored
Handle synthetic tslib and JSX factory imports (microsoft#780)
1 parent 1da2605 commit a88fef7

File tree

72 files changed

+823
-1812
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+823
-1812
lines changed

internal/ast/utilities.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,10 @@ func WalkUpBindingElementsAndPatterns(binding *Node) *Node {
11841184
return node.Parent
11851185
}
11861186

1187+
func IsSourceFileJS(file *SourceFile) bool {
1188+
return file.ScriptKind == core.ScriptKindJS || file.ScriptKind == core.ScriptKindJSX
1189+
}
1190+
11871191
func IsInJSFile(node *Node) bool {
11881192
return node != nil && node.Flags&NodeFlagsJavaScriptFile != 0
11891193
}
@@ -2585,3 +2589,53 @@ func IsRequireCall(node *Node, requireStringLiteralLikeArgument bool) bool {
25852589
func IsUnterminatedLiteral(node *Node) bool {
25862590
return node.LiteralLikeData().TokenFlags&TokenFlagsUnterminated != 0
25872591
}
2592+
2593+
func GetJSXImplicitImportBase(compilerOptions *core.CompilerOptions, file *SourceFile) string {
2594+
jsxImportSourcePragma := getPragmaFromSourceFile(file, "jsximportsource")
2595+
jsxRuntimePragma := getPragmaFromSourceFile(file, "jsxruntime")
2596+
if getPragmaArgument(jsxRuntimePragma, "factory") == "classic" {
2597+
return ""
2598+
}
2599+
if compilerOptions.Jsx == core.JsxEmitReactJSX ||
2600+
compilerOptions.Jsx == core.JsxEmitReactJSXDev ||
2601+
compilerOptions.JsxImportSource != "" ||
2602+
jsxImportSourcePragma != nil ||
2603+
getPragmaArgument(jsxRuntimePragma, "factory") == "automatic" {
2604+
result := getPragmaArgument(jsxImportSourcePragma, "factory")
2605+
if result == "" {
2606+
result = compilerOptions.JsxImportSource
2607+
}
2608+
if result == "" {
2609+
result = "react"
2610+
}
2611+
return result
2612+
}
2613+
return ""
2614+
}
2615+
2616+
func GetJSXRuntimeImport(base string, options *core.CompilerOptions) string {
2617+
if base == "" {
2618+
return base
2619+
}
2620+
return base + "/" + core.IfElse(options.Jsx == core.JsxEmitReactJSXDev, "jsx-dev-runtime", "jsx-runtime")
2621+
}
2622+
2623+
func getPragmaFromSourceFile(file *SourceFile, name string) *Pragma {
2624+
if file != nil {
2625+
for i := range file.Pragmas {
2626+
if file.Pragmas[i].Name == name {
2627+
return &file.Pragmas[i]
2628+
}
2629+
}
2630+
}
2631+
return nil
2632+
}
2633+
2634+
func getPragmaArgument(pragma *Pragma, name string) string {
2635+
if pragma != nil {
2636+
if arg, ok := pragma.Args[name]; ok {
2637+
return arg.Value
2638+
}
2639+
}
2640+
return ""
2641+
}

internal/checker/checker.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,8 @@ type Program interface {
525525
GetImpliedNodeFormatForEmit(sourceFile *ast.SourceFile) core.ModuleKind
526526
GetResolvedModule(currentSourceFile *ast.SourceFile, moduleReference string) *ast.SourceFile
527527
GetSourceFileMetaData(path tspath.Path) *ast.SourceFileMetaData
528+
GetJSXRuntimeImportSpecifier(path tspath.Path) (moduleReference string, specifier *ast.Node)
529+
GetImportHelpersImportSpecifier(path tspath.Path) *ast.Node
528530
}
529531

530532
type Host interface{}

internal/checker/jsx.go

Lines changed: 6 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,13 +1246,13 @@ func (c *Checker) getJsxNamespaceContainerForImplicitImport(location *ast.Node)
12461246
if links != nil && links.jsxImplicitImportContainer != nil {
12471247
return core.IfElse(links.jsxImplicitImportContainer == c.unknownSymbol, nil, links.jsxImplicitImportContainer)
12481248
}
1249-
runtimeImportSpecifier := getJSXRuntimeImport(getJSXImplicitImportBase(c.compilerOptions, file), c.compilerOptions)
1250-
if runtimeImportSpecifier == "" {
1249+
1250+
moduleReference, specifier := c.getJSXRuntimeImportSpecifier(file)
1251+
if moduleReference == "" {
12511252
return nil
12521253
}
12531254
errorMessage := diagnostics.This_JSX_tag_requires_the_module_path_0_to_exist_but_none_could_be_found_Make_sure_you_have_types_for_the_appropriate_package_installed
1254-
specifier := c.getJSXRuntimeImportSpecifier(file, runtimeImportSpecifier)
1255-
mod := c.resolveExternalModule(core.OrElse(specifier, location), runtimeImportSpecifier, errorMessage, location, false)
1255+
mod := c.resolveExternalModule(core.OrElse(specifier, location), moduleReference, errorMessage, location, false)
12561256
var result *ast.Symbol
12571257
if mod != nil && mod != c.unknownSymbol {
12581258
result = c.getMergedSymbol(c.resolveSymbol(mod))
@@ -1263,45 +1263,8 @@ func (c *Checker) getJsxNamespaceContainerForImplicitImport(location *ast.Node)
12631263
return result
12641264
}
12651265

1266-
func (c *Checker) getJSXRuntimeImportSpecifier(file *ast.SourceFile, specifierText string) *ast.Node {
1267-
// Synthesized JSX import is either first or after tslib
1268-
jsxImportIndex := core.IfElse(c.compilerOptions.ImportHelpers.IsTrue(), 1, 0)
1269-
if jsxImportIndex >= len(file.Imports) {
1270-
return nil
1271-
}
1272-
specifier := file.Imports[jsxImportIndex]
1273-
// Debug.assert(nodeIsSynthesized(specifier) && specifier.Text == specifierText, __TEMPLATE__("Expected sourceFile.imports[", jsxImportIndex, "] to be the synthesized JSX runtime import"))
1274-
return specifier
1275-
}
1276-
1277-
func getJSXImplicitImportBase(compilerOptions *core.CompilerOptions, file *ast.SourceFile) string {
1278-
jsxImportSourcePragma := getPragmaFromSourceFile(file, "jsximportsource")
1279-
jsxRuntimePragma := getPragmaFromSourceFile(file, "jsxruntime")
1280-
if getPragmaArgument(jsxRuntimePragma, "factory") == "classic" {
1281-
return ""
1282-
}
1283-
if compilerOptions.Jsx == core.JsxEmitReactJSX ||
1284-
compilerOptions.Jsx == core.JsxEmitReactJSXDev ||
1285-
compilerOptions.JsxImportSource != "" ||
1286-
jsxImportSourcePragma != nil ||
1287-
getPragmaArgument(jsxRuntimePragma, "factory") == "automatic" {
1288-
result := getPragmaArgument(jsxImportSourcePragma, "factory")
1289-
if result == "" {
1290-
result = compilerOptions.JsxImportSource
1291-
}
1292-
if result == "" {
1293-
result = "react"
1294-
}
1295-
return result
1296-
}
1297-
return ""
1298-
}
1299-
1300-
func getJSXRuntimeImport(base string, options *core.CompilerOptions) string {
1301-
if base == "" {
1302-
return base
1303-
}
1304-
return base + "/" + core.IfElse(options.Jsx == core.JsxEmitReactJSXDev, "jsx-dev-runtime", "jsx-runtime")
1266+
func (c *Checker) getJSXRuntimeImportSpecifier(file *ast.SourceFile) (moduleReference string, specifier *ast.Node) {
1267+
return c.program.GetJSXRuntimeImportSpecifier(file.Path())
13051268
}
13061269

13071270
func getPragmaFromSourceFile(file *ast.SourceFile, name string) *ast.Pragma {

internal/compiler/fileloader.go

Lines changed: 98 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"iter"
66
"slices"
77
"strings"
8+
"sync"
89
"sync/atomic"
910

1011
"github.com/microsoft/typescript-go/internal/ast"
@@ -30,12 +31,22 @@ type fileLoader struct {
3031

3132
totalFileCount atomic.Int32
3233
libFileCount atomic.Int32
34+
35+
factoryMu sync.Mutex
36+
factory ast.NodeFactory
3337
}
3438

3539
type processedFiles struct {
36-
files []*ast.SourceFile
37-
resolvedModules map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule]
38-
sourceFileMetaDatas map[tspath.Path]*ast.SourceFileMetaData
40+
files []*ast.SourceFile
41+
resolvedModules map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule]
42+
sourceFileMetaDatas map[tspath.Path]*ast.SourceFileMetaData
43+
jsxRuntimeImportSpecifiers map[tspath.Path]*jsxRuntimeImportSpecifier
44+
importHelpersImportSpecifiers map[tspath.Path]*ast.Node
45+
}
46+
47+
type jsxRuntimeImportSpecifier struct {
48+
moduleReference string
49+
specifier *ast.Node
3950
}
4051

4152
func processAllProgramFiles(
@@ -78,6 +89,8 @@ func processAllProgramFiles(
7889

7990
resolvedModules := make(map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule], totalFileCount)
8091
sourceFileMetaDatas := make(map[tspath.Path]*ast.SourceFileMetaData, totalFileCount)
92+
var jsxRuntimeImportSpecifiers map[tspath.Path]*jsxRuntimeImportSpecifier
93+
var importHelpersImportSpecifiers map[tspath.Path]*ast.Node
8194

8295
for task := range loader.collectTasks(loader.rootTasks) {
8396
file := task.file
@@ -89,15 +102,29 @@ func processAllProgramFiles(
89102
path := file.Path()
90103
resolvedModules[path] = task.resolutionsInFile
91104
sourceFileMetaDatas[path] = task.metadata
105+
if task.jsxRuntimeImportSpecifier != nil {
106+
if jsxRuntimeImportSpecifiers == nil {
107+
jsxRuntimeImportSpecifiers = make(map[tspath.Path]*jsxRuntimeImportSpecifier, totalFileCount)
108+
}
109+
jsxRuntimeImportSpecifiers[path] = task.jsxRuntimeImportSpecifier
110+
}
111+
if task.importHelpersImportSpecifier != nil {
112+
if importHelpersImportSpecifiers == nil {
113+
importHelpersImportSpecifiers = make(map[tspath.Path]*ast.Node, totalFileCount)
114+
}
115+
importHelpersImportSpecifiers[path] = task.importHelpersImportSpecifier
116+
}
92117
}
93118
loader.sortLibs(libFiles)
94119

95120
allFiles := append(libFiles, files...)
96121

97122
return processedFiles{
98-
files: allFiles,
99-
resolvedModules: resolvedModules,
100-
sourceFileMetaDatas: sourceFileMetaDatas,
123+
files: allFiles,
124+
resolvedModules: resolvedModules,
125+
sourceFileMetaDatas: sourceFileMetaDatas,
126+
jsxRuntimeImportSpecifiers: jsxRuntimeImportSpecifiers,
127+
importHelpersImportSpecifiers: importHelpersImportSpecifiers,
101128
}
102129
}
103130

@@ -203,8 +230,10 @@ type parseTask struct {
203230
isLib bool
204231
subTasks []*parseTask
205232

206-
metadata *ast.SourceFileMetaData
207-
resolutionsInFile module.ModeAwareCache[*module.ResolvedModule]
233+
metadata *ast.SourceFileMetaData
234+
resolutionsInFile module.ModeAwareCache[*module.ResolvedModule]
235+
importHelpersImportSpecifier *ast.Node
236+
jsxRuntimeImportSpecifier *jsxRuntimeImportSpecifier
208237
}
209238

210239
func (t *parseTask) start(loader *fileLoader) {
@@ -245,12 +274,14 @@ func (t *parseTask) start(loader *fileLoader) {
245274
}
246275
}
247276

248-
importsAndAugmentations, resolutionsInFile := loader.resolveImportsAndModuleAugmentations(file)
249-
for _, imp := range importsAndAugmentations {
277+
toParse, resolutionsInFile, importHelpersImportSpecifier, jsxRuntimeImportSpecifier := loader.resolveImportsAndModuleAugmentations(file)
278+
for _, imp := range toParse {
250279
t.addSubTask(imp, false)
251280
}
252281

253282
t.resolutionsInFile = resolutionsInFile
283+
t.importHelpersImportSpecifier = importHelpersImportSpecifier
284+
t.jsxRuntimeImportSpecifier = jsxRuntimeImportSpecifier
254285

255286
loader.startTasks(t.subTasks)
256287
})
@@ -286,13 +317,50 @@ func (p *fileLoader) resolveTripleslashPathReference(moduleName string, containi
286317
return tspath.NormalizePath(referencedFileName)
287318
}
288319

289-
func (p *fileLoader) resolveImportsAndModuleAugmentations(file *ast.SourceFile) ([]string, module.ModeAwareCache[*module.ResolvedModule]) {
290-
if len(file.Imports) > 0 || len(file.ModuleAugmentations) > 0 {
291-
toParse := make([]string, 0, len(file.Imports))
292-
moduleNames := getModuleNames(file)
320+
const externalHelpersModuleNameText = "tslib" // TODO(jakebailey): dedupe
321+
322+
func (p *fileLoader) resolveImportsAndModuleAugmentations(file *ast.SourceFile) (
323+
toParse []string,
324+
resolutionsInFile module.ModeAwareCache[*module.ResolvedModule],
325+
importHelpersImportSpecifier *ast.Node,
326+
jsxRuntimeImportSpecifier_ *jsxRuntimeImportSpecifier,
327+
) {
328+
moduleNames := make([]*ast.Node, 0, len(file.Imports)+len(file.ModuleAugmentations)+2)
329+
moduleNames = append(moduleNames, file.Imports...)
330+
for _, imp := range file.ModuleAugmentations {
331+
if imp.Kind == ast.KindStringLiteral {
332+
moduleNames = append(moduleNames, imp)
333+
}
334+
// Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`.
335+
}
336+
337+
isJavaScriptFile := ast.IsSourceFileJS(file)
338+
isExternalModuleFile := ast.IsExternalModule(file)
339+
340+
if isJavaScriptFile || (!file.IsDeclarationFile && (p.compilerOptions.GetIsolatedModules() || isExternalModuleFile)) {
341+
if p.compilerOptions.ImportHelpers.IsTrue() {
342+
specifier := p.createSyntheticImport(externalHelpersModuleNameText, file)
343+
moduleNames = append(moduleNames, specifier)
344+
importHelpersImportSpecifier = specifier
345+
}
346+
347+
jsxImport := ast.GetJSXRuntimeImport(ast.GetJSXImplicitImportBase(p.compilerOptions, file), p.compilerOptions)
348+
if jsxImport != "" {
349+
specifier := p.createSyntheticImport(jsxImport, file)
350+
moduleNames = append(moduleNames, specifier)
351+
jsxRuntimeImportSpecifier_ = &jsxRuntimeImportSpecifier{
352+
moduleReference: jsxImport,
353+
specifier: specifier,
354+
}
355+
}
356+
}
357+
358+
if len(moduleNames) != 0 {
359+
toParse = make([]string, 0, len(moduleNames))
360+
293361
resolutions := p.resolveModuleNames(moduleNames, file)
294362

295-
resolutionsInFile := make(module.ModeAwareCache[*module.ResolvedModule], len(resolutions))
363+
resolutionsInFile = make(module.ModeAwareCache[*module.ResolvedModule], len(resolutions))
296364

297365
for i, resolution := range resolutions {
298366
resolvedFileName := resolution.ResolvedFileName
@@ -325,10 +393,9 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(file *ast.SourceFile)
325393
toParse = append(toParse, resolvedFileName)
326394
}
327395
}
328-
329-
return toParse, resolutionsInFile
330396
}
331-
return nil, nil
397+
398+
return toParse, resolutionsInFile, importHelpersImportSpecifier, jsxRuntimeImportSpecifier_
332399
}
333400

334401
func (p *fileLoader) resolveModuleNames(entries []*ast.Node, file *ast.SourceFile) []*module.ResolvedModule {
@@ -349,3 +416,16 @@ func (p *fileLoader) resolveModuleNames(entries []*ast.Node, file *ast.SourceFil
349416

350417
return resolvedModules
351418
}
419+
420+
func (p *fileLoader) createSyntheticImport(text string, file *ast.SourceFile) *ast.Node {
421+
p.factoryMu.Lock()
422+
defer p.factoryMu.Unlock()
423+
externalHelpersModuleReference := p.factory.NewStringLiteral(text)
424+
importDecl := p.factory.NewImportDeclaration(nil, nil, externalHelpersModuleReference, nil)
425+
// !!! addInternalEmitFlags(importDecl, InternalEmitFlags.NeverApplyImportHelper);
426+
externalHelpersModuleReference.Parent = importDecl
427+
importDecl.Parent = file.AsNode()
428+
// !!! externalHelpersModuleReference.Flags &^= ast.NodeFlagsSynthesized
429+
// !!! importDecl.Flags &^= ast.NodeFlagsSynthesized
430+
return externalHelpersModuleReference
431+
}

internal/compiler/program.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -263,17 +263,6 @@ func (p *Program) findSourceFile(candidate string, reason FileIncludeReason) *as
263263
return p.filesByPath[path]
264264
}
265265

266-
func getModuleNames(file *ast.SourceFile) []*ast.Node {
267-
res := slices.Clone(file.Imports)
268-
for _, imp := range file.ModuleAugmentations {
269-
if imp.Kind == ast.KindStringLiteral {
270-
res = append(res, imp)
271-
}
272-
// Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`.
273-
}
274-
return res
275-
}
276-
277266
func (p *Program) GetSyntacticDiagnostics(sourceFile *ast.SourceFile) []*ast.Diagnostic {
278267
return p.getDiagnosticsHelper(sourceFile, false /*ensureBound*/, false /*ensureChecked*/, p.getSyntacticDiagnosticsForFile)
279268
}
@@ -681,3 +670,14 @@ type FileIncludeReason struct {
681670
func (p *Program) UnsupportedExtensions() []string {
682671
return p.unsupportedExtensions
683672
}
673+
674+
func (p *Program) GetJSXRuntimeImportSpecifier(path tspath.Path) (moduleReference string, specifier *ast.Node) {
675+
if result := p.jsxRuntimeImportSpecifiers[path]; result != nil {
676+
return result.moduleReference, result.specifier
677+
}
678+
return "", nil
679+
}
680+
681+
func (p *Program) GetImportHelpersImportSpecifier(path tspath.Path) *ast.Node {
682+
return p.importHelpersImportSpecifiers[path]
683+
}

internal/parser/parser.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ func (p *Parser) finishSourceFile(result *ast.SourceFile, isDeclarationFile bool
345345
result.Pragmas = getCommentPragmas(&p.factory, p.sourceText)
346346
processPragmasIntoFields(result)
347347
result.SetDiagnostics(attachFileToDiagnostics(p.diagnostics, result))
348-
result.ExternalModuleIndicator = isFileProbablyExternalModule(result)
348+
result.ExternalModuleIndicator = isFileProbablyExternalModule(result) // !!!
349349
result.IsDeclarationFile = isDeclarationFile
350350
result.LanguageVersion = p.languageVersion
351351
result.LanguageVariant = p.languageVariant

internal/parser/references.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,6 @@ import (
99
)
1010

1111
func collectExternalModuleReferences(file *ast.SourceFile) {
12-
// !!!
13-
// If we are importing helpers, we need to add a synthetic reference to resolve the
14-
// helpers library. (A JavaScript file without `externalModuleIndicator` set might be
15-
// a CommonJS module; `commonJSModuleIndicator` doesn't get set until the binder has
16-
// run. We synthesize a helpers import for it just in case; it will never be used if
17-
// the binder doesn't find and set a `commonJSModuleIndicator`.)
18-
// if (isJavaScriptFile || (!file.isDeclarationFile && (getIsolatedModules(options) || isExternalModule(file)))) {
19-
// if (options.importHelpers) {
20-
// // synthesize 'import "tslib"' declaration
21-
// imports = [createSyntheticImport(externalHelpersModuleNameText, file)];
22-
// }
23-
// const jsxImport = getJSXRuntimeImport(getJSXImplicitImportBase(options, file), options);
24-
// if (jsxImport) {
25-
// // synthesize `import "base/jsx-runtime"` declaration
26-
// (imports ||= []).push(createSyntheticImport(jsxImport, file));
27-
// }
28-
// }
2912
for _, node := range file.Statements.Nodes {
3013
collectModuleReferences(file, node, false /*inAmbientModule*/)
3114
}

0 commit comments

Comments
 (0)