@@ -9,7 +9,6 @@ namespace ts.JsTyping {
9
9
}
10
10
11
11
interface PackageJson {
12
- _requiredBy ?: string [ ] ;
13
12
dependencies ?: MapLike < string > ;
14
13
devDependencies ?: MapLike < string > ;
15
14
name ?: string ;
@@ -152,17 +151,8 @@ namespace ts.JsTyping {
152
151
const possibleSearchDirs = new Set ( fileNames . map ( getDirectoryPath ) ) ;
153
152
possibleSearchDirs . add ( projectRootPath ) ;
154
153
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 ) ;
166
156
} ) ;
167
157
if ( ! typeAcquisition . disableFilenameBasedTypeAcquisition ) {
168
158
getTypingNamesFromSourceFileNames ( fileNames ) ;
@@ -214,17 +204,104 @@ namespace ts.JsTyping {
214
204
}
215
205
216
206
/**
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.
218
213
*/
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 ) ) {
221
235
return ;
222
236
}
223
237
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" ) ;
228
305
}
229
306
230
307
/**
@@ -251,58 +328,6 @@ namespace ts.JsTyping {
251
328
addInferredTyping ( "react" ) ;
252
329
}
253
330
}
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
-
306
331
}
307
332
308
333
export const enum NameValidationResult {
0 commit comments