@@ -23,6 +23,43 @@ namespace ts.server.typingsInstaller {
23
23
return result . resolvedModule && result . resolvedModule . resolvedFileName ;
24
24
}
25
25
26
+ export enum PackageNameValidationResult {
27
+ Ok ,
28
+ ScopedPackagesNotSupported ,
29
+ NameTooLong ,
30
+ NameStartsWithDot ,
31
+ NameStartsWithUnderscore ,
32
+ NameContainsNonURISafeCharacters
33
+ }
34
+
35
+
36
+ export const MaxPackageNameLength = 214 ;
37
+ /**
38
+ * Validates package name using rules defined at https://docs.npmjs.com/files/package.json
39
+ */
40
+ export function validatePackageName ( packageName : string ) : PackageNameValidationResult {
41
+ Debug . assert ( ! ! packageName , "Package name is not specified" ) ;
42
+ if ( packageName . length > MaxPackageNameLength ) {
43
+ return PackageNameValidationResult . NameTooLong ;
44
+ }
45
+ if ( packageName . charCodeAt ( 0 ) === CharacterCodes . dot ) {
46
+ return PackageNameValidationResult . NameStartsWithDot ;
47
+ }
48
+ if ( packageName . charCodeAt ( 0 ) === CharacterCodes . _ ) {
49
+ return PackageNameValidationResult . NameStartsWithUnderscore ;
50
+ }
51
+ // check if name is scope package like: starts with @ and has one '/' in the middle
52
+ // scoped packages are not currently supported
53
+ // TODO: when support will be added we'll need to split and check both scope and package name
54
+ if ( / ^ @ [ ^ / ] + \/ [ ^ / ] + $ / . test ( packageName ) ) {
55
+ return PackageNameValidationResult . ScopedPackagesNotSupported ;
56
+ }
57
+ if ( encodeURIComponent ( packageName ) !== packageName ) {
58
+ return PackageNameValidationResult . NameContainsNonURISafeCharacters ;
59
+ }
60
+ return PackageNameValidationResult . Ok ;
61
+ }
62
+
26
63
export const NpmViewRequest : "npm view" = "npm view" ;
27
64
export const NpmInstallRequest : "npm install" = "npm install" ;
28
65
@@ -185,14 +222,54 @@ namespace ts.server.typingsInstaller {
185
222
this . knownCachesSet [ cacheLocation ] = true ;
186
223
}
187
224
225
+ private filterTypings ( typingsToInstall : string [ ] ) {
226
+ if ( typingsToInstall . length === 0 ) {
227
+ return typingsToInstall ;
228
+ }
229
+ const result : string [ ] = [ ] ;
230
+ for ( const typing of typingsToInstall ) {
231
+ if ( this . missingTypingsSet [ typing ] ) {
232
+ continue ;
233
+ }
234
+ const validationResult = validatePackageName ( typing ) ;
235
+ if ( validationResult === PackageNameValidationResult . Ok ) {
236
+ result . push ( typing ) ;
237
+ }
238
+ else {
239
+ // add typing name to missing set so we won't process it again
240
+ this . missingTypingsSet [ typing ] = true ;
241
+ if ( this . log . isEnabled ( ) ) {
242
+ switch ( validationResult ) {
243
+ case PackageNameValidationResult . NameTooLong :
244
+ this . log . writeLine ( `Package name '${ typing } ' should be less than ${ MaxPackageNameLength } characters` ) ;
245
+ break ;
246
+ case PackageNameValidationResult . NameStartsWithDot :
247
+ this . log . writeLine ( `Package name '${ typing } ' cannot start with '.'` ) ;
248
+ break ;
249
+ case PackageNameValidationResult . NameStartsWithUnderscore :
250
+ this . log . writeLine ( `Package name '${ typing } ' cannot start with '_'` ) ;
251
+ break ;
252
+ case PackageNameValidationResult . ScopedPackagesNotSupported :
253
+ this . log . writeLine ( `Package '${ typing } ' is scoped and currently is not supported` ) ;
254
+ break ;
255
+ case PackageNameValidationResult . NameContainsNonURISafeCharacters :
256
+ this . log . writeLine ( `Package name '${ typing } ' contains non URI safe characters` ) ;
257
+ break ;
258
+ }
259
+ }
260
+ }
261
+ }
262
+ return result ;
263
+ }
264
+
188
265
private installTypings ( req : DiscoverTypings , cachePath : string , currentlyCachedTypings : string [ ] , typingsToInstall : string [ ] ) {
189
266
if ( this . log . isEnabled ( ) ) {
190
267
this . log . writeLine ( `Installing typings ${ JSON . stringify ( typingsToInstall ) } ` ) ;
191
268
}
192
- typingsToInstall = filter ( typingsToInstall , x => ! this . missingTypingsSet [ x ] ) ;
269
+ typingsToInstall = this . filterTypings ( typingsToInstall ) ;
193
270
if ( typingsToInstall . length === 0 ) {
194
271
if ( this . log . isEnabled ( ) ) {
195
- this . log . writeLine ( `All typings are known to be missing - no need to go any further` ) ;
272
+ this . log . writeLine ( `All typings are known to be missing or invalid - no need to go any further` ) ;
196
273
}
197
274
return ;
198
275
}
0 commit comments