@@ -9,7 +9,6 @@ namespace ts.JsTyping {
99 }
1010
1111 interface PackageJson {
12- _requiredBy ?: string [ ] ;
1312 dependencies ?: MapLike < string > ;
1413 devDependencies ?: MapLike < string > ;
1514 name ?: string ;
@@ -152,17 +151,8 @@ namespace ts.JsTyping {
152151 const possibleSearchDirs = new Set ( fileNames . map ( getDirectoryPath ) ) ;
153152 possibleSearchDirs . add ( projectRootPath ) ;
154153 possibleSearchDirs . forEach ( ( searchDir ) => {
155- const packageJsonPath = combinePaths ( searchDir , "package.json" ) ;
156- getTypingNamesFromJson ( packageJsonPath , filesToWatch ) ;
157-
158- const bowerJsonPath = combinePaths ( searchDir , "bower.json" ) ;
159- getTypingNamesFromJson ( bowerJsonPath , filesToWatch ) ;
160-
161- const bowerComponentsPath = combinePaths ( searchDir , "bower_components" ) ;
162- getTypingNamesFromPackagesFolder ( bowerComponentsPath , filesToWatch ) ;
163-
164- const nodeModulesPath = combinePaths ( searchDir , "node_modules" ) ;
165- getTypingNamesFromPackagesFolder ( nodeModulesPath , filesToWatch ) ;
154+ getTypingNames ( searchDir , "bower.json" , "bower_components" , filesToWatch ) ;
155+ getTypingNames ( searchDir , "package.json" , "node_modules" , filesToWatch ) ;
166156 } ) ;
167157 if ( ! typeAcquisition . disableFilenameBasedTypeAcquisition ) {
168158 getTypingNamesFromSourceFileNames ( fileNames ) ;
@@ -214,17 +204,104 @@ namespace ts.JsTyping {
214204 }
215205
216206 /**
217- * Get the typing info from common package manager json files like package.json or bower.json
207+ * Adds inferred typings from manifest/module pairs (think package.json + node_modules)
208+ *
209+ * @param projectRootPath is the path to the directory where to look for package.json, bower.json and other typing information
210+ * @param manifestName is the name of the manifest (package.json or bower.json)
211+ * @param modulesDirName is the directory name for modules (node_modules or bower_components). Should be lowercase!
212+ * @param filesToWatch are the files to watch for changes. We will push things into this array.
218213 */
219- function getTypingNamesFromJson ( jsonPath : string , filesToWatch : Push < string > ) {
220- if ( ! host . fileExists ( jsonPath ) ) {
214+ function getTypingNames ( projectRootPath : string , manifestName : string , modulesDirName : string , filesToWatch : string [ ] ) : void {
215+ // First, we check the manifests themselves. They're not
216+ // _required_, but they allow us to do some filtering when dealing
217+ // with big flat dep directories.
218+ const manifestPath = combinePaths ( projectRootPath , manifestName ) ;
219+ let manifest ;
220+ let manifestTypingNames ;
221+ if ( host . fileExists ( manifestPath ) ) {
222+ filesToWatch . push ( manifestPath ) ;
223+ manifest = readConfigFile ( manifestPath , path => host . readFile ( path ) ) . config ;
224+ manifestTypingNames = flatMap ( [ manifest . dependencies , manifest . devDependencies , manifest . optionalDependencies , manifest . peerDependencies ] , getOwnKeys ) ;
225+ addInferredTypings ( manifestTypingNames , `Typing names in '${ manifestPath } ' dependencies` ) ;
226+ }
227+
228+ // Now we scan the directories for typing information in
229+ // already-installed dependencies (if present). Note that this
230+ // step happens regardless of whether a manifest was present,
231+ // which is certainly a valid configuration, if an unusual one.
232+ const packagesFolderPath = combinePaths ( projectRootPath , modulesDirName ) ;
233+ filesToWatch . push ( packagesFolderPath ) ;
234+ if ( ! host . directoryExists ( packagesFolderPath ) ) {
221235 return ;
222236 }
223237
224- filesToWatch . push ( jsonPath ) ;
225- const jsonConfig : PackageJson = readConfigFile ( jsonPath , path => host . readFile ( path ) ) . config ;
226- const jsonTypingNames = flatMap ( [ jsonConfig . dependencies , jsonConfig . devDependencies , jsonConfig . optionalDependencies , jsonConfig . peerDependencies ] , getOwnKeys ) ;
227- addInferredTypings ( jsonTypingNames , `Typing names in '${ jsonPath } ' dependencies` ) ;
238+ // There's two cases we have to take into account here:
239+ // 1. If manifest is undefined, then we're not using a manifest.
240+ // That means that we should scan _all_ dependencies at the top
241+ // level of the modulesDir.
242+ // 2. If manifest is defined, then we can do some special
243+ // filtering to reduce the amount of scanning we need to do.
244+ //
245+ // Previous versions of this algorithm checked for a `_requiredBy`
246+ // field in the package.json, but that field is only present in
247+ // `npm@>=3 <7`.
248+
249+ // Package names that do **not** provide their own typings, so
250+ // we'll look them up.
251+ const packageNames : string [ ] = [ ] ;
252+
253+ const dependencyManifestNames = manifestTypingNames
254+ // This is #1 described above.
255+ ? manifestTypingNames . map ( typingName => combinePaths ( packagesFolderPath , typingName , manifestName ) )
256+ // And #2. Depth = 3 because scoped packages look like `node_modules/@foo/bar/package.json`
257+ : host . readDirectory ( packagesFolderPath , [ Extension . Json ] , /*excludes*/ undefined , /*includes*/ undefined , /*depth*/ 3 )
258+ . filter ( manifestPath => {
259+ if ( getBaseFileName ( manifestPath ) !== manifestName ) {
260+ return false ;
261+ }
262+ // It's ok to treat
263+ // `node_modules/@foo/bar/package.json` as a manifest,
264+ // but not `node_modules/jquery/nested/package.json`.
265+ // We only assume depth 3 is ok for formally scoped
266+ // packages. So that needs this dance here.
267+ const pathComponents = getPathComponents ( normalizePath ( manifestPath ) ) ;
268+ const isScoped = pathComponents [ pathComponents . length - 3 ] [ 0 ] === "@" ;
269+ return isScoped && pathComponents [ pathComponents . length - 4 ] . toLowerCase ( ) === modulesDirName || // `node_modules/@foo/bar`
270+ ! isScoped && pathComponents [ pathComponents . length - 3 ] . toLowerCase ( ) === modulesDirName ; // `node_modules/foo`
271+ } ) ;
272+
273+ if ( log ) log ( `Searching for typing names in ${ packagesFolderPath } ; all files: ${ JSON . stringify ( dependencyManifestNames ) } ` ) ;
274+
275+ // Once we have the names of things to look up, we iterate over
276+ // and either collect their included typings, or add them to the
277+ // list of typings we need to look up separately.
278+ for ( const manifestPath of dependencyManifestNames ) {
279+ const normalizedFileName = normalizePath ( manifestPath ) ;
280+ const result = readConfigFile ( normalizedFileName , ( path : string ) => host . readFile ( path ) ) ;
281+ const manifest : PackageJson = result . config ;
282+
283+ // If the package has its own d.ts typings, those will take precedence. Otherwise the package name will be used
284+ // to download d.ts files from DefinitelyTyped
285+ if ( ! manifest . name ) {
286+ continue ;
287+ }
288+ const ownTypes = manifest . types || manifest . typings ;
289+ if ( ownTypes ) {
290+ const absolutePath = getNormalizedAbsolutePath ( ownTypes , getDirectoryPath ( normalizedFileName ) ) ;
291+ if ( host . fileExists ( absolutePath ) ) {
292+ if ( log ) log ( ` Package '${ manifest . name } ' provides its own types.` ) ;
293+ inferredTypings . set ( manifest . name , absolutePath ) ;
294+ }
295+ else {
296+ if ( log ) log ( ` Package '${ manifest . name } ' provides its own types but they are missing.` ) ;
297+ }
298+ }
299+ else {
300+ packageNames . push ( manifest . name ) ;
301+ }
302+ }
303+
304+ addInferredTypings ( packageNames , " Found package names" ) ;
228305 }
229306
230307 /**
@@ -251,58 +328,6 @@ namespace ts.JsTyping {
251328 addInferredTyping ( "react" ) ;
252329 }
253330 }
254-
255- /**
256- * Infer typing names from packages folder (ex: node_module, bower_components)
257- * @param packagesFolderPath is the path to the packages folder
258- */
259- function getTypingNamesFromPackagesFolder ( packagesFolderPath : string , filesToWatch : Push < string > ) {
260- filesToWatch . push ( packagesFolderPath ) ;
261-
262- // Todo: add support for ModuleResolutionHost too
263- if ( ! host . directoryExists ( packagesFolderPath ) ) {
264- return ;
265- }
266-
267- // depth of 2, so we access `node_modules/foo` but not `node_modules/foo/bar`
268- const fileNames = host . readDirectory ( packagesFolderPath , [ Extension . Json ] , /*excludes*/ undefined , /*includes*/ undefined , /*depth*/ 2 ) ;
269- if ( log ) log ( `Searching for typing names in ${ packagesFolderPath } ; all files: ${ JSON . stringify ( fileNames ) } ` ) ;
270- const packageNames : string [ ] = [ ] ;
271- for ( const fileName of fileNames ) {
272- const normalizedFileName = normalizePath ( fileName ) ;
273- const baseFileName = getBaseFileName ( normalizedFileName ) ;
274- if ( baseFileName !== "package.json" && baseFileName !== "bower.json" ) {
275- continue ;
276- }
277- const result = readConfigFile ( normalizedFileName , ( path : string ) => host . readFile ( path ) ) ;
278- const packageJson : PackageJson = result . config ;
279-
280- // npm 3's package.json contains a "_requiredBy" field
281- // we should include all the top level module names for npm 2, and only module names whose
282- // "_requiredBy" field starts with "#" or equals "/" for npm 3.
283- if ( baseFileName === "package.json" && packageJson . _requiredBy &&
284- filter ( packageJson . _requiredBy , ( r : string ) => r [ 0 ] === "#" || r === "/" ) . length === 0 ) {
285- continue ;
286- }
287-
288- // If the package has its own d.ts typings, those will take precedence. Otherwise the package name will be used
289- // to download d.ts files from DefinitelyTyped
290- if ( ! packageJson . name ) {
291- continue ;
292- }
293- const ownTypes = packageJson . types || packageJson . typings ;
294- if ( ownTypes ) {
295- const absolutePath = getNormalizedAbsolutePath ( ownTypes , getDirectoryPath ( normalizedFileName ) ) ;
296- if ( log ) log ( ` Package '${ packageJson . name } ' provides its own types.` ) ;
297- inferredTypings . set ( packageJson . name , absolutePath ) ;
298- }
299- else {
300- packageNames . push ( packageJson . name ) ;
301- }
302- }
303- addInferredTypings ( packageNames , " Found package names" ) ;
304- }
305-
306331 }
307332
308333 export const enum NameValidationResult {
0 commit comments