|
7 | 7 | "reflect" |
8 | 8 | "slices" |
9 | 9 | "strings" |
| 10 | + "sync" |
10 | 11 | "testing" |
11 | 12 |
|
12 | 13 | "github.com/microsoft/TypeScript/tsc/internal/ast" |
@@ -577,6 +578,97 @@ import type { Value } from "f" with { "resolution-mode": "require" };`, |
577 | 578 | assert.DeepEqual(t, program.UnresolvedPackageNames().Keys(), map[string]struct{}{"missing": {}, "evaluation-missing": {}}) |
578 | 579 | } |
579 | 580 |
|
| 581 | +type moduleResolutionRecorder struct { |
| 582 | + module.Resolver |
| 583 | + mu sync.Mutex |
| 584 | + requests []module.ModeAwareCacheKey |
| 585 | + unresolvedTypes bool |
| 586 | +} |
| 587 | + |
| 588 | +func (r *moduleResolutionRecorder) ResolveModuleNameWithPhase(name, file string, mode core.ResolutionMode, phase module.ImportPhase, redirect module.ResolvedProjectReference) (*module.ResolvedModule, []module.DiagAndArgs, error) { |
| 589 | + r.mu.Lock() |
| 590 | + r.requests = append(r.requests, module.ModeAwareCacheKey{Name: name, Mode: mode, Phase: phase}) |
| 591 | + r.mu.Unlock() |
| 592 | + if r.unresolvedTypes && phase == module.ImportPhaseEvaluation { |
| 593 | + return nil, nil, nil |
| 594 | + } |
| 595 | + return r.Resolver.ResolveModuleNameWithPhase(name, file, mode, phase, redirect) |
| 596 | +} |
| 597 | + |
| 598 | +func TestProgramModuleResolutionReuse(t *testing.T) { |
| 599 | + t.Parallel() |
| 600 | + for _, test := range []struct { |
| 601 | + name string |
| 602 | + prefix string |
| 603 | + trace core.Tristate |
| 604 | + resolverTrace core.Tristate |
| 605 | + unresolvedTypes bool |
| 606 | + evaluationCalls int |
| 607 | + requireCalls int |
| 608 | + }{ |
| 609 | + {name: "source", evaluationCalls: 1}, |
| 610 | + {name: "mixed", prefix: `import "./a.js";`, evaluationCalls: 1}, |
| 611 | + {name: "modes", prefix: `import type { Value } from "./a.js" with { "resolution-mode": "require" };`, evaluationCalls: 1, requireCalls: 1}, |
| 612 | + {name: "unresolved", unresolvedTypes: true, evaluationCalls: 1}, |
| 613 | + {name: "trace", trace: core.TSTrue, evaluationCalls: 2}, |
| 614 | + {name: "resolver trace", resolverTrace: core.TSTrue, evaluationCalls: 2}, |
| 615 | + } { |
| 616 | + t.Run(test.name, func(t *testing.T) { |
| 617 | + t.Parallel() |
| 618 | + fs := vfstest.FromMap(map[string]string{ |
| 619 | + "/src/index.ts": test.prefix + ` |
| 620 | +import source a from "./a.js"; |
| 621 | +import.source("./a.js");`, |
| 622 | + "/src/a.js": "export const value = 1;", |
| 623 | + "/src/a.d.ts": "export declare const value: number; export interface Value {}", |
| 624 | + }, true) |
| 625 | + var resolver *moduleResolutionRecorder |
| 626 | + program := NewProgram(ProgramOptions{ |
| 627 | + Config: &tsoptions.ParsedCommandLine{ |
| 628 | + ParsedConfig: &tsoptions.ParsedOptions{ |
| 629 | + FileNames: []string{"/src/index.ts"}, |
| 630 | + CompilerOptions: &core.CompilerOptions{ |
| 631 | + Module: core.ModuleKindESNext, |
| 632 | + ModuleResolution: core.ModuleResolutionKindBundler, |
| 633 | + NoLib: core.TSTrue, |
| 634 | + AllowJs: core.TSTrue, |
| 635 | + TraceResolution: test.trace, |
| 636 | + }, |
| 637 | + }, |
| 638 | + }, |
| 639 | + Host: NewCompilerHost("/src", fs, "", nil, nil, nil), |
| 640 | + CreateModuleResolver: func(options module.ResolverOptions) module.Resolver { |
| 641 | + if test.resolverTrace.IsTrue() { |
| 642 | + options.CompilerOptions = options.CompilerOptions.Clone() |
| 643 | + options.CompilerOptions.TraceResolution = test.resolverTrace |
| 644 | + } |
| 645 | + resolver = &moduleResolutionRecorder{Resolver: module.NewResolver(options), unresolvedTypes: test.unresolvedTypes} |
| 646 | + return resolver |
| 647 | + }, |
| 648 | + }) |
| 649 | + counts := make(map[module.ModeAwareCacheKey]int) |
| 650 | + for _, request := range resolver.requests { |
| 651 | + counts[request]++ |
| 652 | + } |
| 653 | + assert.Equal(t, counts[module.ModeAwareCacheKey{Name: "./a.js", Mode: core.ResolutionModeESM, Phase: module.ImportPhaseSource}], 2) |
| 654 | + assert.Equal(t, counts[module.ModeAwareCacheKey{Name: "./a.js", Mode: core.ResolutionModeESM}], test.evaluationCalls) |
| 655 | + assert.Equal(t, counts[module.ModeAwareCacheKey{Name: "./a.js", Mode: core.ResolutionModeCommonJS}], test.requireCalls) |
| 656 | + file := program.GetSourceFile("/src/index.ts") |
| 657 | + for _, entry := range file.Imports() { |
| 658 | + if module.GetImportPhaseForUsage(entry) == module.ImportPhaseSource { |
| 659 | + assert.Equal(t, program.GetResolvedModuleFromModuleSpecifier(file, entry).ResolvedFileName, "/src/a.js") |
| 660 | + } |
| 661 | + } |
| 662 | + assert.Equal(t, program.GetResolvedModule(file, "./a.js", core.ResolutionModeESM).IsResolved(), !test.unresolvedTypes) |
| 663 | + if test.unresolvedTypes { |
| 664 | + assert.Assert(t, program.GetSourceFile("/src/a.js") != nil) |
| 665 | + } else { |
| 666 | + assert.Assert(t, program.GetSourceFile("/src/a.d.ts") != nil) |
| 667 | + } |
| 668 | + }) |
| 669 | + } |
| 670 | +} |
| 671 | + |
580 | 672 | func BenchmarkNewProgram(b *testing.B) { |
581 | 673 | if !bundled.Embedded { |
582 | 674 | // Without embedding, we'd need to read all of the lib files out from disk into the MapFS. |
|
0 commit comments