@@ -23,6 +23,43 @@ namespace ts.server.typingsInstaller {
2323 return result . resolvedModule && result . resolvedModule . resolvedFileName ;
2424 }
2525
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+
2663 export const NpmViewRequest : "npm view" = "npm view" ;
2764 export const NpmInstallRequest : "npm install" = "npm install" ;
2865
@@ -185,14 +222,54 @@ namespace ts.server.typingsInstaller {
185222 this . knownCachesSet [ cacheLocation ] = true ;
186223 }
187224
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+
188265 private installTypings ( req : DiscoverTypings , cachePath : string , currentlyCachedTypings : string [ ] , typingsToInstall : string [ ] ) {
189266 if ( this . log . isEnabled ( ) ) {
190267 this . log . writeLine ( `Installing typings ${ JSON . stringify ( typingsToInstall ) } ` ) ;
191268 }
192- typingsToInstall = filter ( typingsToInstall , x => ! this . missingTypingsSet [ x ] ) ;
269+ typingsToInstall = this . filterTypings ( typingsToInstall ) ;
193270 if ( typingsToInstall . length === 0 ) {
194271 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` ) ;
196273 }
197274 return ;
198275 }
0 commit comments