@@ -46,6 +46,34 @@ func (p *ProgramOptions) canUseProjectReferenceSource() bool {
4646 return p .UseSourceOfProjectReference && ! p .Config .CompilerOptions ().DisableSourceOfProjectReferenceRedirect .IsTrue ()
4747}
4848
49+ type lazyValue [T any ] struct {
50+ value * T
51+ once sync.Once
52+ initialized atomic.Bool
53+ }
54+
55+ func (l * lazyValue [T ]) getValue (compute func () * T ) * T {
56+ l .once .Do (func () {
57+ if l .value == nil {
58+ l .value = compute ()
59+ }
60+ l .initialized .Store (true )
61+ })
62+ return l .value
63+ }
64+
65+ func (l * lazyValue [T ]) tryReuse (from * lazyValue [T ]) {
66+ if from .initialized .Load () {
67+ l .value = from .value
68+ l .initialized .Store (true )
69+ }
70+ }
71+
72+ type packageNamesInfo struct {
73+ resolved * collections.Set [string ]
74+ unresolved * collections.Set [string ]
75+ }
76+
4977type Program struct {
5078 opts ProgramOptions
5179 checkerPool CheckerPool
@@ -68,15 +96,11 @@ type Program struct {
6896 sourceFilesToEmit []* ast.SourceFile
6997
7098 // Cached unresolved imports for ATA
71- unresolvedImportsOnce sync.Once
72- unresolvedImports * collections.Set [string ]
73- knownSymlinks * symlinks.KnownSymlinks
74- knownSymlinksOnce sync.Once
99+ unresolvedImports lazyValue [collections.Set [string ]]
100+ knownSymlinks lazyValue [symlinks.KnownSymlinks ]
75101
76102 // Used by auto-imports
77- packageNamesOnce sync.Once
78- resolvedPackageNames * collections.Set [string ]
79- unresolvedPackageNames * collections.Set [string ]
103+ packageNames lazyValue [packageNamesInfo ]
80104
81105 // Used by workspace/symbol
82106 hasTSFileOnce sync.Once
@@ -264,22 +288,17 @@ func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHos
264288 usesUriStyleNodeCoreModules : p .usesUriStyleNodeCoreModules ,
265289 programDiagnostics : p .programDiagnostics ,
266290 hasEmitBlockingDiagnostics : p .hasEmitBlockingDiagnostics ,
267- unresolvedImports : p .unresolvedImports ,
268- resolvedPackageNames : p .resolvedPackageNames ,
269- unresolvedPackageNames : p .unresolvedPackageNames ,
270- knownSymlinks : p .knownSymlinks ,
271291 }
292+ result .unresolvedImports .tryReuse (& p .unresolvedImports )
293+ result .knownSymlinks .tryReuse (& p .knownSymlinks )
294+ result .packageNames .tryReuse (& p .packageNames )
272295 result .initCheckerPool ()
273296 index := core .FindIndex (result .files , func (file * ast.SourceFile ) bool { return file .Path () == newFile .Path () })
274297 result .files = slices .Clone (result .files )
275298 result .files [index ] = newFile
276299 result .filesByPath = maps .Clone (result .filesByPath )
277300 result .filesByPath [newFile .Path ()] = newFile
278301 updateFileIncludeProcessor (result )
279- result .knownSymlinks = symlinks .NewKnownSymlink (result .GetCurrentDirectory (), result .UseCaseSensitiveFileNames ())
280- if len (result .resolvedModules ) > 0 || len (result .typeResolutionsInFile ) > 0 {
281- result .knownSymlinks .SetSymlinksFromResolutions (result .ForEachResolvedModule , result .ForEachResolvedTypeReferenceDirective )
282- }
283302 return result , true
284303}
285304
@@ -335,13 +354,7 @@ func (p *Program) GetConfigFileParsingDiagnostics() []*ast.Diagnostic {
335354// GetUnresolvedImports returns the unresolved imports for this program.
336355// The result is cached and computed only once.
337356func (p * Program ) GetUnresolvedImports () * collections.Set [string ] {
338- p .unresolvedImportsOnce .Do (func () {
339- if p .unresolvedImports == nil {
340- p .unresolvedImports = p .extractUnresolvedImports ()
341- }
342- })
343-
344- return p .unresolvedImports
357+ return p .unresolvedImports .getValue (p .extractUnresolvedImports )
345358}
346359
347360func (p * Program ) extractUnresolvedImports () * collections.Set [string ] {
@@ -1689,62 +1702,58 @@ func (p *Program) SourceFileMayBeEmitted(sourceFile *ast.SourceFile, forceDtsEmi
16891702}
16901703
16911704func (p * Program ) ResolvedPackageNames () * collections.Set [string ] {
1692- p .collectPackageNames ()
1693- return p .resolvedPackageNames
1705+ return p .collectPackageNames ().resolved
16941706}
16951707
16961708func (p * Program ) UnresolvedPackageNames () * collections.Set [string ] {
1697- p .collectPackageNames ()
1698- return p .unresolvedPackageNames
1699- }
1700-
1701- func (p * Program ) collectPackageNames () {
1702- p .packageNamesOnce .Do (func () {
1703- if p .resolvedPackageNames == nil {
1704- p .resolvedPackageNames = & collections.Set [string ]{}
1705- p .unresolvedPackageNames = & collections.Set [string ]{}
1706- for _ , file := range p .files {
1707- if p .IsSourceFileDefaultLibrary (file .Path ()) || p .IsSourceFileFromExternalLibrary (file ) || strings .Contains (file .FileName (), "/node_modules/" ) {
1708- // Checking for /node_modules/ is a little imprecise, but ATA treats locally installed typings
1709- // as root files, which would not pass IsSourceFileFromExternalLibrary.
1709+ return p .collectPackageNames ().unresolved
1710+ }
1711+
1712+ func (p * Program ) collectPackageNames () * packageNamesInfo {
1713+ return p .packageNames .getValue (func () * packageNamesInfo {
1714+ packageNames := & packageNamesInfo {& collections.Set [string ]{}, & collections.Set [string ]{}}
1715+ for _ , file := range p .files {
1716+ if p .IsSourceFileDefaultLibrary (file .Path ()) || p .IsSourceFileFromExternalLibrary (file ) || strings .Contains (file .FileName (), "/node_modules/" ) {
1717+ // Checking for /node_modules/ is a little imprecise, but ATA treats locally installed typings
1718+ // as root files, which would not pass IsSourceFileFromExternalLibrary.
1719+ continue
1720+ }
1721+ for _ , imp := range file .Imports () {
1722+ if tspath .IsExternalModuleNameRelative (imp .Text ()) {
17101723 continue
17111724 }
1712- for _ , imp := range file .Imports () {
1713- if tspath .IsExternalModuleNameRelative (imp .Text ()) {
1714- continue
1715- }
1716- if resolvedModules , ok := p .resolvedModules [file .Path ()]; ok {
1717- key := module.ModeAwareCacheKey {Name : imp .Text (), Mode : p .GetModeForUsageLocation (file , imp )}
1718- if resolvedModule , ok := resolvedModules [key ]; ok && resolvedModule .IsResolved () {
1719- if ! resolvedModule .IsExternalLibraryImport {
1720- continue
1721- }
1722- // Priority order for getting package name:
1723- // 1. PackageId.Name (requires both name and version in package.json)
1724- name := resolvedModule .PackageId .Name
1725- if name == "" {
1726- // 2. GetPackageScopeForPath - get name from package.json in the package directory
1727- if packageScope := p .resolver .GetPackageScopeForPath (resolvedModule .ResolvedFileName ); packageScope != nil && packageScope .Exists () {
1728- if scopeName , ok := packageScope .Contents .Name .GetValue (); ok {
1729- name = scopeName
1730- }
1725+ if resolvedModules , ok := p .resolvedModules [file .Path ()]; ok {
1726+ key := module.ModeAwareCacheKey {Name : imp .Text (), Mode : p .GetModeForUsageLocation (file , imp )}
1727+ if resolvedModule , ok := resolvedModules [key ]; ok && resolvedModule .IsResolved () {
1728+ if ! resolvedModule .IsExternalLibraryImport {
1729+ continue
1730+ }
1731+ // Priority order for getting package name:
1732+ // 1. PackageId.Name (requires both name and version in package.json)
1733+ name := resolvedModule .PackageId .Name
1734+ if name == "" {
1735+ // 2. GetPackageScopeForPath - get name from package.json in the package directory
1736+ if packageScope := p .resolver .GetPackageScopeForPath (resolvedModule .ResolvedFileName ); packageScope != nil && packageScope .Exists () {
1737+ if scopeName , ok := packageScope .Contents .Name .GetValue (); ok {
1738+ name = scopeName
17311739 }
17321740 }
1733- if name == "" {
1734- // 3. GetPackageNameFromDirectory - extract from node_modules path
1735- name = modulespecifiers .GetPackageNameFromDirectory (resolvedModule .ResolvedFileName )
1736- }
1737- // 4. If all fail, don't add empty string
1738- if name != "" {
1739- p .resolvedPackageNames .Add (name )
1740- }
1741- continue
17421741 }
1742+ if name == "" {
1743+ // 3. GetPackageNameFromDirectory - extract from node_modules path
1744+ name = modulespecifiers .GetPackageNameFromDirectory (resolvedModule .ResolvedFileName )
1745+ }
1746+ // 4. If all fail, don't add empty string
1747+ if name != "" {
1748+ packageNames .resolved .Add (name )
1749+ }
1750+ continue
17431751 }
1744- p .unresolvedPackageNames .Add (imp .Text ())
17451752 }
1753+ packageNames .unresolved .Add (imp .Text ())
17461754 }
17471755 }
1756+ return packageNames
17481757 })
17491758}
17501759
@@ -1766,54 +1775,52 @@ func (p *Program) HasTSFile() bool {
17661775}
17671776
17681777func (p * Program ) GetSymlinkCache () * symlinks.KnownSymlinks {
1769- p .knownSymlinksOnce .Do (func () {
1770- if p .knownSymlinks == nil {
1771- p .knownSymlinks = symlinks .NewKnownSymlink (p .GetCurrentDirectory (), p .UseCaseSensitiveFileNames ())
1778+ return p .knownSymlinks .getValue (func () * symlinks.KnownSymlinks {
1779+ knownSymlinks := symlinks .NewKnownSymlink (p .GetCurrentDirectory (), p .UseCaseSensitiveFileNames ())
1780+
1781+ // Resolved modules store realpath information when they're resolved inside node_modules
1782+ if len (p .resolvedModules ) > 0 || len (p .typeResolutionsInFile ) > 0 {
1783+ knownSymlinks .SetSymlinksFromResolutions (p .ForEachResolvedModule , p .ForEachResolvedTypeReferenceDirective )
1784+ }
17721785
1773- // Resolved modules store realpath information when they're resolved inside node_modules
1774- if len (p .resolvedModules ) > 0 || len (p .typeResolutionsInFile ) > 0 {
1775- p .knownSymlinks .SetSymlinksFromResolutions (p .ForEachResolvedModule , p .ForEachResolvedTypeReferenceDirective )
1786+ // Check other dependencies for symlinks
1787+ var seenPackageJsons collections.Set [tspath.Path ]
1788+ for filePath , meta := range p .sourceFileMetaDatas {
1789+ if meta .PackageJsonDirectory == "" ||
1790+ ! p .SourceFileMayBeEmitted (p .GetSourceFileByPath (filePath ), false ) ||
1791+ ! seenPackageJsons .AddIfAbsent (p .toPath (meta .PackageJsonDirectory )) {
1792+ continue
1793+ }
1794+ packageJsonName := tspath .CombinePaths (meta .PackageJsonDirectory , "package.json" )
1795+ info := p .GetPackageJsonInfo (packageJsonName )
1796+ if info .GetContents () == nil {
1797+ continue
17761798 }
17771799
1778- // Check other dependencies for symlinks
1779- var seenPackageJsons collections.Set [tspath.Path ]
1780- for filePath , meta := range p .sourceFileMetaDatas {
1781- if meta .PackageJsonDirectory == "" ||
1782- ! p .SourceFileMayBeEmitted (p .GetSourceFileByPath (filePath ), false ) ||
1783- ! seenPackageJsons .AddIfAbsent (p .toPath (meta .PackageJsonDirectory )) {
1800+ for dep := range info .GetContents ().GetRuntimeDependencyNames ().Keys () {
1801+ // Skip work in common case: we already saved a symlink for this package directory
1802+ // in the node_modules adjacent to this package.json
1803+ possibleDirectoryPath := p .toPath (tspath .CombinePaths (meta .PackageJsonDirectory , "node_modules" , dep ))
1804+ if knownSymlinks .HasDirectory (possibleDirectoryPath ) {
17841805 continue
17851806 }
1786- packageJsonName := tspath .CombinePaths (meta .PackageJsonDirectory , "package.json" )
1787- info := p .GetPackageJsonInfo (packageJsonName )
1788- if info .GetContents () == nil {
1789- continue
1790- }
1791-
1792- for dep := range info .GetContents ().GetRuntimeDependencyNames ().Keys () {
1793- // Skip work in common case: we already saved a symlink for this package directory
1794- // in the node_modules adjacent to this package.json
1795- possibleDirectoryPath := p .toPath (tspath .CombinePaths (meta .PackageJsonDirectory , "node_modules" , dep ))
1796- if p .knownSymlinks .HasDirectory (possibleDirectoryPath ) {
1807+ if ! strings .HasPrefix (dep , "@types" ) {
1808+ possibleTypesDirectoryPath := p .toPath (tspath .CombinePaths (meta .PackageJsonDirectory , "node_modules" , module .GetTypesPackageName (dep )))
1809+ if knownSymlinks .HasDirectory (possibleTypesDirectoryPath ) {
17971810 continue
17981811 }
1799- if ! strings .HasPrefix (dep , "@types" ) {
1800- possibleTypesDirectoryPath := p .toPath (tspath .CombinePaths (meta .PackageJsonDirectory , "node_modules" , module .GetTypesPackageName (dep )))
1801- if p .knownSymlinks .HasDirectory (possibleTypesDirectoryPath ) {
1802- continue
1803- }
1804- }
1812+ }
18051813
1806- if packageResolution := p .resolver .ResolvePackageDirectory (dep , packageJsonName , core .ResolutionModeCommonJS , nil ); packageResolution .IsResolved () {
1807- p .knownSymlinks .ProcessResolution (
1808- tspath .CombinePaths (packageResolution .OriginalPath , "package.json" ),
1809- tspath .CombinePaths (packageResolution .ResolvedFileName , "package.json" ),
1810- )
1811- }
1814+ if packageResolution := p .resolver .ResolvePackageDirectory (dep , packageJsonName , core .ResolutionModeCommonJS , nil ); packageResolution .IsResolved () {
1815+ knownSymlinks .ProcessResolution (
1816+ tspath .CombinePaths (packageResolution .OriginalPath , "package.json" ),
1817+ tspath .CombinePaths (packageResolution .ResolvedFileName , "package.json" ),
1818+ )
18121819 }
18131820 }
18141821 }
1822+ return knownSymlinks
18151823 })
1816- return p .knownSymlinks
18171824}
18181825
18191826func (p * Program ) ResolveModuleName (moduleName string , containingFile string , resolutionMode core.ResolutionMode ) * module.ResolvedModule {
0 commit comments