11/// <reference path="../../compiler/core.ts" />
22/// <reference path="../../compiler/moduleNameResolver.ts" />
33/// <reference path="../../services/jsTyping.ts"/>
4+ /// <reference path="../../services/semver.ts"/>
45/// <reference path="../types.ts"/>
56/// <reference path="../shared.ts"/>
67
@@ -9,6 +10,10 @@ namespace ts.server.typingsInstaller {
910 devDependencies : MapLike < any > ;
1011 }
1112
13+ interface NpmLock {
14+ dependencies : { [ packageName : string ] : { version : string } } ;
15+ }
16+
1217 export interface Log {
1318 isEnabled ( ) : boolean ;
1419 writeLine ( text : string ) : void ;
@@ -42,7 +47,7 @@ namespace ts.server.typingsInstaller {
4247 }
4348
4449 export abstract class TypingsInstaller {
45- private readonly packageNameToTypingLocation : Map < string > = createMap < string > ( ) ;
50+ private readonly packageNameToTypingLocation : Map < JsTyping . CachedTyping > = createMap < JsTyping . CachedTyping > ( ) ;
4651 private readonly missingTypingsSet : Map < true > = createMap < true > ( ) ;
4752 private readonly knownCachesSet : Map < true > = createMap < true > ( ) ;
4853 private readonly projectWatchers : Map < FileWatcher [ ] > = createMap < FileWatcher [ ] > ( ) ;
@@ -52,7 +57,7 @@ namespace ts.server.typingsInstaller {
5257 private installRunCount = 1 ;
5358 private inFlightRequestCount = 0 ;
5459
55- abstract readonly typesRegistry : Map < void > ;
60+ abstract readonly typesRegistry : Map < MapLike < string > > ;
5661
5762 constructor (
5863 protected readonly installTypingHost : InstallTypingHost ,
@@ -117,7 +122,8 @@ namespace ts.server.typingsInstaller {
117122 this . safeList ,
118123 this . packageNameToTypingLocation ,
119124 req . typeAcquisition ,
120- req . unresolvedImports ) ;
125+ req . unresolvedImports ,
126+ this . typesRegistry ) ;
121127
122128 if ( this . log . isEnabled ( ) ) {
123129 this . log . writeLine ( `Finished typings discovery: ${ JSON . stringify ( discoverTypingsResult ) } ` ) ;
@@ -156,23 +162,30 @@ namespace ts.server.typingsInstaller {
156162 if ( this . log . isEnabled ( ) ) {
157163 this . log . writeLine ( `Processing cache location '${ cacheLocation } '` ) ;
158164 }
159- if ( this . knownCachesSet . get ( cacheLocation ) ) {
165+ if ( this . knownCachesSet . has ( cacheLocation ) ) {
160166 if ( this . log . isEnabled ( ) ) {
161167 this . log . writeLine ( `Cache location was already processed...` ) ;
162168 }
163169 return ;
164170 }
165171 const packageJson = combinePaths ( cacheLocation , "package.json" ) ;
172+ const packageLockJson = combinePaths ( cacheLocation , "package-lock.json" ) ;
166173 if ( this . log . isEnabled ( ) ) {
167174 this . log . writeLine ( `Trying to find '${ packageJson } '...` ) ;
168175 }
169- if ( this . installTypingHost . fileExists ( packageJson ) ) {
176+ if ( this . installTypingHost . fileExists ( packageJson ) && this . installTypingHost . fileExists ( packageLockJson ) ) {
170177 const npmConfig = < NpmConfig > JSON . parse ( this . installTypingHost . readFile ( packageJson ) ) ;
178+ const npmLock = < NpmLock > JSON . parse ( this . installTypingHost . readFile ( packageLockJson ) ) ;
171179 if ( this . log . isEnabled ( ) ) {
172180 this . log . writeLine ( `Loaded content of '${ packageJson } ': ${ JSON . stringify ( npmConfig ) } ` ) ;
181+ this . log . writeLine ( `Loaded content of '${ packageLockJson } '` ) ;
173182 }
174- if ( npmConfig . devDependencies ) {
183+ if ( npmConfig . devDependencies && npmLock . dependencies ) {
175184 for ( const key in npmConfig . devDependencies ) {
185+ if ( ! hasProperty ( npmLock . dependencies , key ) ) {
186+ // if package in package.json but not package-lock.json, skip adding to cache so it is reinstalled on next use
187+ continue ;
188+ }
176189 // key is @types /<package name>
177190 const packageName = getBaseFileName ( key ) ;
178191 if ( ! packageName ) {
@@ -184,18 +197,23 @@ namespace ts.server.typingsInstaller {
184197 continue ;
185198 }
186199 const existingTypingFile = this . packageNameToTypingLocation . get ( packageName ) ;
187- if ( existingTypingFile === typingFile ) {
188- continue ;
189- }
190200 if ( existingTypingFile ) {
201+ if ( existingTypingFile . typingLocation === typingFile ) {
202+ continue ;
203+ }
204+
191205 if ( this . log . isEnabled ( ) ) {
192206 this . log . writeLine ( `New typing for package ${ packageName } from '${ typingFile } ' conflicts with existing typing file '${ existingTypingFile } '` ) ;
193207 }
194208 }
195209 if ( this . log . isEnabled ( ) ) {
196210 this . log . writeLine ( `Adding entry into typings cache: '${ packageName } ' => '${ typingFile } '` ) ;
197211 }
198- this . packageNameToTypingLocation . set ( packageName , typingFile ) ;
212+ const info = getProperty ( npmLock . dependencies , key ) ;
213+ const version = info && info . version ;
214+ const semver = Semver . parse ( version ) ;
215+ const newTyping : JsTyping . CachedTyping = { typingLocation : typingFile , version : semver } ;
216+ this . packageNameToTypingLocation . set ( packageName , newTyping ) ;
199217 }
200218 }
201219 }
@@ -211,10 +229,6 @@ namespace ts.server.typingsInstaller {
211229 if ( this . log . isEnabled ( ) ) this . log . writeLine ( `'${ typing } ' is in missingTypingsSet - skipping...` ) ;
212230 return false ;
213231 }
214- if ( this . packageNameToTypingLocation . get ( typing ) ) {
215- if ( this . log . isEnabled ( ) ) this . log . writeLine ( `'${ typing } ' already has a typing - skipping...` ) ;
216- return false ;
217- }
218232 const validationResult = JsTyping . validatePackageName ( typing ) ;
219233 if ( validationResult !== JsTyping . PackageNameValidationResult . Ok ) {
220234 // add typing name to missing set so we won't process it again
@@ -226,6 +240,10 @@ namespace ts.server.typingsInstaller {
226240 if ( this . log . isEnabled ( ) ) this . log . writeLine ( `Entry for package '${ typing } ' does not exist in local types registry - skipping...` ) ;
227241 return false ;
228242 }
243+ if ( this . packageNameToTypingLocation . get ( typing ) && JsTyping . isTypingUpToDate ( this . packageNameToTypingLocation . get ( typing ) , this . typesRegistry . get ( typing ) ) ) {
244+ if ( this . log . isEnabled ( ) ) this . log . writeLine ( `'${ typing } ' already has an up-to-date typing - skipping...` ) ;
245+ return false ;
246+ }
229247 return true ;
230248 } ) ;
231249 }
@@ -294,9 +312,12 @@ namespace ts.server.typingsInstaller {
294312 this . missingTypingsSet . set ( packageName , true ) ;
295313 continue ;
296314 }
297- if ( ! this . packageNameToTypingLocation . has ( packageName ) ) {
298- this . packageNameToTypingLocation . set ( packageName , typingFile ) ;
299- }
315+
316+ // packageName is guaranteed to exist in typesRegistry by filterTypings
317+ const distTags = this . typesRegistry . get ( packageName ) ;
318+ const newVersion = Semver . parse ( distTags [ `ts${ ts . versionMajorMinor } ` ] || distTags [ latestDistTag ] ) ;
319+ const newTyping : JsTyping . CachedTyping = { typingLocation : typingFile , version : newVersion } ;
320+ this . packageNameToTypingLocation . set ( packageName , newTyping ) ;
300321 installedTypingFiles . push ( typingFile ) ;
301322 }
302323 if ( this . log . isEnabled ( ) ) {
@@ -390,4 +411,6 @@ namespace ts.server.typingsInstaller {
390411 export function typingsName ( packageName : string ) : string {
391412 return `@types/${ packageName } @ts${ versionMajorMinor } ` ;
392413 }
414+
415+ const latestDistTag = "latest" ;
393416}
0 commit comments