Skip to content

Commit 62d765c

Browse files
committed
Add initial support for resolution mode
1 parent d0c9e32 commit 62d765c

File tree

7 files changed

+224
-41
lines changed

7 files changed

+224
-41
lines changed

internal/ast/ast.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7419,6 +7419,53 @@ func IsImportAttributes(node *Node) bool {
74197419
return node.Kind == KindImportAttributes
74207420
}
74217421

7422+
func (node *ImportAttributesNode) GetResolutionModeOverride( /* !!! grammarErrorOnNode?: (node: Node, diagnostic: DiagnosticMessage) => void*/ ) (core.ResolutionMode, bool) {
7423+
if node == nil {
7424+
return core.ResolutionModeNone, false
7425+
}
7426+
7427+
attributes := node.AsImportAttributes().Attributes
7428+
7429+
if len(attributes.Nodes) != 1 {
7430+
// !!!
7431+
// grammarErrorOnNode?.(
7432+
// node,
7433+
// node.token === SyntaxKind.WithKeyword
7434+
// ? Diagnostics.Type_import_attributes_should_have_exactly_one_key_resolution_mode_with_value_import_or_require
7435+
// : Diagnostics.Type_import_assertions_should_have_exactly_one_key_resolution_mode_with_value_import_or_require,
7436+
// );
7437+
return core.ResolutionModeNone, false
7438+
}
7439+
7440+
elem := attributes.Nodes[0].AsImportAttribute()
7441+
if !IsStringLiteralLike(elem.Name()) {
7442+
return core.ResolutionModeNone, false
7443+
}
7444+
if elem.Name().Text() != "resolution-mode" {
7445+
// !!!
7446+
// grammarErrorOnNode?.(
7447+
// elem.name,
7448+
// node.token === SyntaxKind.WithKeyword
7449+
// ? Diagnostics.resolution_mode_is_the_only_valid_key_for_type_import_attributes
7450+
// : Diagnostics.resolution_mode_is_the_only_valid_key_for_type_import_assertions,
7451+
// );
7452+
return core.ResolutionModeNone, false
7453+
}
7454+
if !IsStringLiteralLike(elem.Value) {
7455+
return core.ResolutionModeNone, false
7456+
}
7457+
if elem.Value.Text() != "import" && elem.Value.Text() != "require" {
7458+
// !!!
7459+
// grammarErrorOnNode?.(elem.value, Diagnostics.resolution_mode_should_be_either_require_or_import);
7460+
return core.ResolutionModeNone, false
7461+
}
7462+
if elem.Value.Text() == "import" {
7463+
return core.ResolutionModeESM, true
7464+
} else {
7465+
return core.ModuleKindCommonJS, true
7466+
}
7467+
}
7468+
74227469
// TypeQueryNode
74237470

74247471
type TypeQueryNode struct {

internal/ast/utilities.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2714,6 +2714,22 @@ func IsTypeOnlyImportOrExportDeclaration(node *Node) bool {
27142714
return IsTypeOnlyImportDeclaration(node) || isTypeOnlyExportDeclaration(node)
27152715
}
27162716

2717+
func IsExclusivelyTypeOnlyImportOrExport(node *Node) bool {
2718+
switch node.Kind {
2719+
case KindExportDeclaration:
2720+
return node.AsExportDeclaration().IsTypeOnly
2721+
case KindImportDeclaration:
2722+
if importClause := node.AsImportDeclaration().ImportClause; importClause != nil {
2723+
return importClause.AsImportClause().IsTypeOnly
2724+
}
2725+
case KindJSDocImportTag:
2726+
if importClause := node.AsJSDocImportTag().ImportClause; importClause != nil {
2727+
return importClause.AsImportClause().IsTypeOnly
2728+
}
2729+
}
2730+
return false
2731+
}
2732+
27172733
func GetSourceFileOfModule(module *Symbol) *SourceFile {
27182734
declaration := module.ValueDeclaration
27192735
if declaration == nil {

internal/checker/checker.go

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ type Program interface {
523523
BindSourceFiles()
524524
GetEmitModuleFormatOfFile(sourceFile *ast.SourceFile) core.ModuleKind
525525
GetImpliedNodeFormatForEmit(sourceFile *ast.SourceFile) core.ModuleKind
526-
GetResolvedModule(currentSourceFile *ast.SourceFile, moduleReference string) *ast.SourceFile
526+
GetResolvedModule(currentSourceFile *ast.SourceFile, moduleReference string, mode core.ResolutionMode) *ast.SourceFile
527527
GetSourceFileMetaData(path tspath.Path) *ast.SourceFileMetaData
528528
GetJSXRuntimeImportSpecifier(path tspath.Path) (moduleReference string, specifier *ast.Node)
529529
GetImportHelpersImportSpecifier(path tspath.Path) *ast.Node
@@ -5038,7 +5038,7 @@ func (c *Checker) checkImportAttributes(declaration *ast.Node) {
50385038
if importAttributesType != c.emptyObjectType {
50395039
c.checkTypeAssignableTo(c.getTypeFromImportAttributes(node), c.getNullableType(importAttributesType, TypeFlagsUndefined), node, nil)
50405040
}
5041-
isTypeOnly := isExclusivelyTypeOnlyImportOrExport(declaration)
5041+
isTypeOnly := ast.IsExclusivelyTypeOnlyImportOrExport(declaration)
50425042
override := c.getResolutionModeOverride(node.AsImportAttributes(), isTypeOnly)
50435043
isImportAttributes := node.AsImportAttributes().Token == ast.KindWithKeyword
50445044
if isTypeOnly && override != core.ResolutionModeNone {
@@ -5076,22 +5076,6 @@ func (c *Checker) checkImportAttributes(declaration *ast.Node) {
50765076
}
50775077
}
50785078

5079-
func isExclusivelyTypeOnlyImportOrExport(node *ast.Node) bool {
5080-
switch node.Kind {
5081-
case ast.KindExportDeclaration:
5082-
return node.AsExportDeclaration().IsTypeOnly
5083-
case ast.KindImportDeclaration:
5084-
if importClause := node.AsImportDeclaration().ImportClause; importClause != nil {
5085-
return importClause.AsImportClause().IsTypeOnly
5086-
}
5087-
case ast.KindJSDocImportTag:
5088-
if importClause := node.AsJSDocImportTag().ImportClause; importClause != nil {
5089-
return importClause.AsImportClause().IsTypeOnly
5090-
}
5091-
}
5092-
return false
5093-
}
5094-
50955079
func (c *Checker) getTypeFromImportAttributes(node *ast.Node) *Type {
50965080
links := c.typeNodeLinks.Get(node)
50975081
if links.resolvedType == nil {
@@ -6360,6 +6344,7 @@ func (c *Checker) checkAliasSymbol(node *ast.Node) {
63606344
c.error(node, diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_module_is_set_to_preserve)
63616345
}
63626346
// !!!
6347+
63636348
// if c.compilerOptions.VerbatimModuleSyntax.IsTrue() && !ast.IsTypeOnlyImportOrExportDeclaration(node) && node.Flags&ast.NodeFlagsAmbient == 0 && targetFlags&ast.SymbolFlagsConstEnum != 0 {
63646349
// constEnumDeclaration := target.ValueDeclaration
63656350
// redirect := host.getRedirectReferenceForResolutionFromSourceOfProject(ast.GetSourceFileOfNode(constEnumDeclaration).ResolvedPath)
@@ -14260,7 +14245,8 @@ func (c *Checker) resolveExternalModule(location *ast.Node, moduleReference stri
1426014245
return ambientModule
1426114246
}
1426214247
// !!! The following only implements simple module resolution
14263-
sourceFile := c.program.GetResolvedModule(ast.GetSourceFileOfNode(location), moduleReference)
14248+
resolutionMode := c.compilerOptions.GetResolutionMode()
14249+
sourceFile := c.program.GetResolvedModule(ast.GetSourceFileOfNode(location), moduleReference, resolutionMode)
1426414250
if sourceFile != nil {
1426514251
// !!!
1426614252
if sourceFile.Symbol != nil {

internal/compiler/fileloader.go

Lines changed: 135 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,12 @@ func (t *parseTask) start(loader *fileLoader) {
245245
loader.wg.Queue(func() {
246246
file := loader.parseSourceFile(t.normalizedFilePath)
247247
t.file = file
248-
loader.wg.Queue(func() {
249-
t.metadata = loader.loadSourceFileMetaData(file.Path())
250-
})
248+
t.metadata = loader.loadSourceFileMetaData(file.Path())
249+
250+
fileWithMetadata := &fileWithMetadata{
251+
file: file,
252+
meta: t.metadata,
253+
}
251254

252255
// !!! if noResolve, skip all of this
253256
t.subTasks = make([]*parseTask, 0, len(file.ReferencedFiles)+len(file.Imports)+len(file.ModuleAugmentations))
@@ -258,7 +261,8 @@ func (t *parseTask) start(loader *fileLoader) {
258261
}
259262

260263
for _, ref := range file.TypeReferenceDirectives {
261-
resolved := loader.resolver.ResolveTypeReferenceDirective(ref.FileName, file.FileName(), core.ModuleKindCommonJS /* !!! */, nil)
264+
resolutionMode := getModeForTypeReferenceDirectiveInFile(ref, fileWithMetadata, loader.compilerOptions)
265+
resolved := loader.resolver.ResolveTypeReferenceDirective(ref.FileName, file.FileName(), resolutionMode, nil)
262266
if resolved.IsResolved() {
263267
t.addSubTask(resolved.ResolvedFileName, false)
264268
}
@@ -274,7 +278,7 @@ func (t *parseTask) start(loader *fileLoader) {
274278
}
275279
}
276280

277-
toParse, resolutionsInFile, importHelpersImportSpecifier, jsxRuntimeImportSpecifier := loader.resolveImportsAndModuleAugmentations(file)
281+
toParse, resolutionsInFile, importHelpersImportSpecifier, jsxRuntimeImportSpecifier := loader.resolveImportsAndModuleAugmentations(fileWithMetadata)
278282
for _, imp := range toParse {
279283
t.addSubTask(imp, false)
280284
}
@@ -319,12 +323,13 @@ func (p *fileLoader) resolveTripleslashPathReference(moduleName string, containi
319323

320324
const externalHelpersModuleNameText = "tslib" // TODO(jakebailey): dedupe
321325

322-
func (p *fileLoader) resolveImportsAndModuleAugmentations(file *ast.SourceFile) (
326+
func (p *fileLoader) resolveImportsAndModuleAugmentations(item *fileWithMetadata) (
323327
toParse []string,
324328
resolutionsInFile module.ModeAwareCache[*module.ResolvedModule],
325329
importHelpersImportSpecifier *ast.Node,
326330
jsxRuntimeImportSpecifier_ *jsxRuntimeImportSpecifier,
327331
) {
332+
file := item.file
328333
moduleNames := make([]*ast.Node, 0, len(file.Imports)+len(file.ModuleAugmentations)+2)
329334
moduleNames = append(moduleNames, file.Imports...)
330335
for _, imp := range file.ModuleAugmentations {
@@ -358,16 +363,17 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(file *ast.SourceFile)
358363
if len(moduleNames) != 0 {
359364
toParse = make([]string, 0, len(moduleNames))
360365

361-
resolutions := p.resolveModuleNames(moduleNames, file)
366+
resolutions := p.resolveModuleNames(moduleNames, item)
367+
optionsForFile := p.getCompilerOptionsForFile(file)
362368

363369
resolutionsInFile = make(module.ModeAwareCache[*module.ResolvedModule], len(resolutions))
364370

365-
for i, resolution := range resolutions {
366-
resolvedFileName := resolution.ResolvedFileName
371+
for _, resolution := range resolutions {
372+
resolvedFileName := resolution.resolvedModule.ResolvedFileName
367373
// TODO(ercornel): !!!: check if from node modules
368374

369-
mode := core.ModuleKindCommonJS // !!!
370-
resolutionsInFile[module.ModeAwareCacheKey{Name: moduleNames[i].Text(), Mode: mode}] = resolution
375+
mode := getModeForUsageLocation(item, resolution.node, optionsForFile)
376+
resolutionsInFile[module.ModeAwareCacheKey{Name: resolution.node.Text(), Mode: mode}] = resolution.resolvedModule
371377

372378
// add file to program only if:
373379
// - resolution was successful
@@ -387,7 +393,7 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(file *ast.SourceFile)
387393
} else {
388394
hasAllowedExtension = tspath.FileExtensionIsOneOf(resolvedFileName, tspath.SupportedTSExtensionsFlat)
389395
}
390-
shouldAddFile := resolution.IsResolved() && hasAllowedExtension
396+
shouldAddFile := resolution.resolvedModule.IsResolved() && hasAllowedExtension
391397
// TODO(ercornel): !!!: other checks on whether or not to add the file
392398

393399
if shouldAddFile {
@@ -400,20 +406,20 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(file *ast.SourceFile)
400406
return toParse, resolutionsInFile, importHelpersImportSpecifier, jsxRuntimeImportSpecifier_
401407
}
402408

403-
func (p *fileLoader) resolveModuleNames(entries []*ast.Node, file *ast.SourceFile) []*module.ResolvedModule {
409+
func (p *fileLoader) resolveModuleNames(entries []*ast.Node, item *fileWithMetadata) []*resolution {
404410
if len(entries) == 0 {
405411
return nil
406412
}
407413

408-
resolvedModules := make([]*module.ResolvedModule, 0, len(entries))
414+
resolvedModules := make([]*resolution, 0, len(entries))
409415

410416
for _, entry := range entries {
411417
moduleName := entry.Text()
412418
if moduleName == "" {
413419
continue
414420
}
415-
resolvedModule := p.resolver.ResolveModuleName(moduleName, file.FileName(), core.ModuleKindCommonJS /* !!! */, nil)
416-
resolvedModules = append(resolvedModules, resolvedModule)
421+
resolvedModule := p.resolver.ResolveModuleName(moduleName, item.file.FileName(), item.meta.ImpliedNodeFormat, nil)
422+
resolvedModules = append(resolvedModules, &resolution{node: entry, resolvedModule: resolvedModule})
417423
}
418424

419425
return resolvedModules
@@ -431,3 +437,116 @@ func (p *fileLoader) createSyntheticImport(text string, file *ast.SourceFile) *a
431437
// !!! importDecl.Flags &^= ast.NodeFlagsSynthesized
432438
return externalHelpersModuleReference
433439
}
440+
441+
type fileWithMetadata struct {
442+
file *ast.SourceFile
443+
meta *ast.SourceFileMetaData
444+
}
445+
446+
type resolution struct {
447+
node *ast.Node
448+
resolvedModule *module.ResolvedModule
449+
}
450+
451+
func (p *fileLoader) getCompilerOptionsForFile(file *ast.SourceFile) *core.CompilerOptions {
452+
// !!! return getRedirectReferenceForResolution(file)?.commandLine.options || options;
453+
return p.compilerOptions
454+
}
455+
456+
func getModeForTypeReferenceDirectiveInFile(ref *ast.FileReference, item *fileWithMetadata, options *core.CompilerOptions) core.ResolutionMode {
457+
if ref.ResolutionMode != core.ResolutionModeNone {
458+
return ref.ResolutionMode
459+
} else {
460+
return getDefaultResolutionModeForFile(item, options)
461+
}
462+
}
463+
464+
func getDefaultResolutionModeForFile(item *fileWithMetadata, options *core.CompilerOptions) core.ResolutionMode {
465+
if importSyntaxAffectsModuleResolution(options) {
466+
return ast.GetImpliedNodeFormatForEmitWorker(item.file.FileName(), options, item.meta)
467+
} else {
468+
return core.ResolutionModeNone
469+
}
470+
}
471+
472+
func getModeForUsageLocation(item *fileWithMetadata, usage *ast.Node, options *core.CompilerOptions) core.ResolutionMode {
473+
if ast.IsImportDeclaration(usage.Parent) || ast.IsExportDeclaration(usage.Parent) || ast.IsJSDocImportTag(usage.Parent) {
474+
isTypeOnly := ast.IsExclusivelyTypeOnlyImportOrExport(usage.Parent)
475+
if isTypeOnly {
476+
var override core.ResolutionMode
477+
var ok bool
478+
switch usage.Parent.Kind {
479+
case ast.KindImportDeclaration:
480+
override, ok = usage.Parent.AsImportDeclaration().Attributes.GetResolutionModeOverride()
481+
case ast.KindExportDeclaration:
482+
override, ok = usage.Parent.AsExportDeclaration().Attributes.GetResolutionModeOverride()
483+
case ast.KindJSDocImportTag:
484+
override, ok = usage.Parent.AsJSDocImportTag().Attributes.GetResolutionModeOverride()
485+
}
486+
if ok {
487+
return override
488+
}
489+
}
490+
}
491+
if usage.Parent.Parent != nil && ast.IsImportTypeNode(usage.Parent.Parent) {
492+
if override, ok := usage.Parent.Parent.AsImportTypeNode().Attributes.GetResolutionModeOverride(); ok {
493+
return override
494+
}
495+
}
496+
497+
if options != nil && importSyntaxAffectsModuleResolution(options) {
498+
return getEmitSyntaxForUsageLocationWorker(item, usage, options)
499+
}
500+
501+
return core.ResolutionModeNone
502+
}
503+
504+
func importSyntaxAffectsModuleResolution(options *core.CompilerOptions) bool {
505+
moduleResolution := options.ModuleResolution
506+
return core.ModuleResolutionKindNode16 <= moduleResolution && moduleResolution <= core.ModuleResolutionKindNodeNext ||
507+
options.ResolvePackageJsonExports.IsTrue() || options.ResolvePackageJsonImports.IsTrue()
508+
}
509+
510+
func getEmitSyntaxForUsageLocationWorker(item *fileWithMetadata, usage *ast.Node, options *core.CompilerOptions) core.ResolutionMode {
511+
if options == nil {
512+
// This should always be provided, but we try to fail somewhat
513+
// gracefully to allow projects like ts-node time to update.
514+
return core.ResolutionModeNone
515+
}
516+
517+
exprParentParent := ast.WalkUpParenthesizedExpressions(usage.Parent).Parent
518+
if exprParentParent != nil && ast.IsImportEqualsDeclaration(exprParentParent) || ast.IsRequireCall(usage.Parent) {
519+
return core.ModuleKindCommonJS
520+
}
521+
if ast.IsImportCall(ast.WalkUpParenthesizedExpressions(usage.Parent)) {
522+
if shouldTransformImportCallWorker(item, options) {
523+
return core.ModuleKindCommonJS
524+
} else {
525+
return core.ModuleKindESNext
526+
}
527+
}
528+
// If we're in --module preserve on an input file, we know that an import
529+
// is an import. But if this is a declaration file, we'd prefer to use the
530+
// impliedNodeFormat. Since we want things to be consistent between the two,
531+
// we need to issue errors when the user writes ESM syntax in a definitely-CJS
532+
// file, until/unless declaration emit can indicate a true ESM import. On the
533+
// other hand, writing CJS syntax in a definitely-ESM file is fine, since declaration
534+
// emit preserves the CJS syntax.
535+
fileEmitMode := ast.GetEmitModuleFormatOfFileWorker(item.file, options, item.meta)
536+
if fileEmitMode == core.ModuleKindCommonJS {
537+
return core.ModuleKindCommonJS
538+
} else {
539+
if fileEmitMode.IsNonNodeESM() || fileEmitMode == core.ModuleKindPreserve {
540+
return core.ModuleKindESNext
541+
}
542+
}
543+
return core.ModuleKindNone
544+
}
545+
546+
func shouldTransformImportCallWorker(item *fileWithMetadata, options *core.CompilerOptions) bool {
547+
moduleKind := options.GetEmitModuleKind()
548+
if core.ModuleKindNode16 <= moduleKind && moduleKind <= core.ModuleKindNodeNext || moduleKind == core.ModuleKindPreserve {
549+
return false
550+
}
551+
return ast.GetImpliedNodeFormatForEmitWorker(item.file.FileName(), options, item.meta) < core.ModuleKindES2015
552+
}

internal/compiler/program.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,9 @@ func (p *Program) GetTypeCheckerForFile(file *ast.SourceFile) *checker.Checker {
259259
return p.checkersByFile[file]
260260
}
261261

262-
func (p *Program) GetResolvedModule(file *ast.SourceFile, moduleReference string) *ast.SourceFile {
262+
func (p *Program) GetResolvedModule(file *ast.SourceFile, moduleReference string, mode core.ResolutionMode) *ast.SourceFile {
263263
if resolutions, ok := p.resolvedModules[file.Path()]; ok {
264-
if resolved, ok := resolutions[module.ModeAwareCacheKey{Name: moduleReference, Mode: core.ModuleKindCommonJS}]; ok {
264+
if resolved, ok := resolutions[module.ModeAwareCacheKey{Name: moduleReference, Mode: mode}]; ok {
265265
return p.findSourceFile(resolved.ResolvedFileName, FileIncludeReason{FileIncludeKindImport, 0})
266266
}
267267
}

0 commit comments

Comments
 (0)