@@ -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 , "package.json" , "node_modules" , filesToWatch ) ;
155
+ getTypingNames ( searchDir , "bower.json" , "bower_components" , filesToWatch ) ;
166
156
} ) ;
167
157
if ( ! typeAcquisition . disableFilenameBasedTypeAcquisition ) {
168
158
getTypingNamesFromSourceFileNames ( fileNames ) ;
@@ -214,17 +204,103 @@ 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)
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
+
216
+ // First, we check the manifests themselves. They're not
217
+ // _required_, but they allow us to do some filtering when dealing
218
+ // with big flat dep directories.
219
+ const jsonPath = combinePaths ( projectRootPath , manifestName ) ;
220
+ let jsonConfig ;
221
+ let jsonTypingNames ;
222
+ if ( host . fileExists ( jsonPath ) ) {
223
+ filesToWatch . push ( jsonPath ) ;
224
+ jsonConfig = readConfigFile ( jsonPath , path => host . readFile ( path ) ) . config ;
225
+ jsonTypingNames = flatMap ( [ jsonConfig . dependencies , jsonConfig . devDependencies , jsonConfig . optionalDependencies , jsonConfig . peerDependencies ] , getOwnKeys ) ;
226
+ addInferredTypings ( jsonTypingNames , `Typing names in '${ jsonPath } ' dependencies` ) ;
227
+ }
228
+ else {
229
+ jsonConfig = undefined ;
230
+ }
231
+
232
+
233
+ // Now we scan the directories for typing information in
234
+ // already-installed dependencies (if present). Note that this
235
+ // step happens regardless of whether a manifest was present,
236
+ // which is certainly a valid configuration, if an unusual one.
237
+ const packagesFolderPath = combinePaths ( projectRootPath , modulesDirName ) ;
238
+ if ( ! host . directoryExists ( packagesFolderPath ) ) {
221
239
return ;
222
240
}
241
+ if ( log ) log ( `Searching for typing names in ${ packagesFolderPath } ` ) ;
242
+
243
+ // There's two cases we have to take into account here:
244
+ // 1. If jsonConfig is undefined, then we're not using a manifest.
245
+ // That means that we should scan _all_ dependencies at the top
246
+ // level of the modulesDir.
247
+ // 2. If jsonConfig is defined, then we can do some special
248
+ // filtering to reduce the amount of scanning we need to do.
249
+ //
250
+ // Previous versions of this algorithm checked for a `_requiredBy`
251
+ // field in the package.json, but that field is only present in
252
+ // `npm@>=3 <7`.
253
+
254
+ // Package names that do **not** provide their own typings, so
255
+ // we'll look them up.
256
+ const packageNames : string [ ] = [ ] ;
223
257
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` ) ;
258
+ const dependencyManifestNames = jsonTypingNames
259
+ // This is #1 described above.
260
+ ? jsonTypingNames . map ( typingName => combinePaths ( packagesFolderPath , typingName , manifestName ) )
261
+ // And #2. Depth = 3 because scoped packages look like `node_modules/@foo/bar/package.json`
262
+ : host . readDirectory ( packagesFolderPath , [ Extension . Json ] , /*excludes*/ undefined , /*includes*/ undefined , /*depth*/ 3 )
263
+ . filter ( jsonPath => {
264
+ // It's ok to treat
265
+ // `node_modules/@foo/bar/package.json` as a manifest,
266
+ // but not `node_modules/jquery/nested/package.json`.
267
+ // We only assume depth 3 is ok for formally scoped
268
+ // packages. So that needs this dance here.
269
+ const pathComponents = getPathComponents ( normalizePath ( jsonPath ) ) ;
270
+ const isScoped = pathComponents [ 1 ] [ 0 ] === "@" ;
271
+ if (
272
+ ( isScoped && pathComponents . length === 4 )
273
+ || ( ! isScoped && pathComponents . length === 3 )
274
+ ) {
275
+ return getBaseFileName ( jsonPath ) === manifestName ;
276
+ }
277
+ else {
278
+ return false ;
279
+ }
280
+ } ) ;
281
+
282
+ for ( const manifestPath of dependencyManifestNames ) {
283
+ const normalizedFileName = normalizePath ( manifestPath ) ;
284
+ const result = readConfigFile ( normalizedFileName , ( path : string ) => host . readFile ( path ) ) ;
285
+ const manifest : PackageJson = result . config ;
286
+
287
+ // If the package has its own d.ts typings, those will take precedence. Otherwise the package name will be used
288
+ // to download d.ts files from DefinitelyTyped
289
+ if ( ! manifest . name ) {
290
+ continue ;
291
+ }
292
+ const ownTypes = manifest . types || manifest . typings ;
293
+ if ( ownTypes ) {
294
+ const absolutePath = getNormalizedAbsolutePath ( ownTypes , getDirectoryPath ( normalizedFileName ) ) ;
295
+ if ( log ) log ( ` Package '${ manifest . name } ' provides its own types.` ) ;
296
+ inferredTypings . set ( manifest . name , absolutePath ) ;
297
+ }
298
+ else {
299
+ packageNames . push ( manifest . name ) ;
300
+ }
301
+ }
302
+
303
+ addInferredTypings ( packageNames , " Found package names" ) ;
228
304
}
229
305
230
306
/**
@@ -251,58 +327,6 @@ namespace ts.JsTyping {
251
327
addInferredTyping ( "react" ) ;
252
328
}
253
329
}
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
330
}
307
331
308
332
export const enum NameValidationResult {
0 commit comments