@@ -35,6 +35,10 @@ namespace ts.server {
35
35
( event : ProjectServiceEvent ) : void ;
36
36
}
37
37
38
+ export interface SafeList {
39
+ [ name : string ] : { match : RegExp , exclude ?: Array < Array < string | number > > , types ?: string [ ] } ;
40
+ }
41
+
38
42
function prepareConvertersForEnumLikeCompilerOptions ( commandLineOptions : CommandLineOption [ ] ) : Map < Map < number > > {
39
43
const map : Map < Map < number > > = createMap < Map < number > > ( ) ;
40
44
for ( const option of commandLineOptions ) {
@@ -57,6 +61,32 @@ namespace ts.server {
57
61
"smart" : IndentStyle . Smart
58
62
} ) ;
59
63
64
+ const defaultTypeSafeList : SafeList = {
65
+ "jquery" : {
66
+ // jquery files can have names like "jquery-1.10.2.min.js" (or "jquery.intellisense.js")
67
+ "match" : / j q u e r y ( - ( \. ? \d + ) + ) ? ( \. i n t e l l i s e n s e ) ? ( \. m i n ) ? \. j s $ / i,
68
+ "types" : [ "jquery" ]
69
+ } ,
70
+ "WinJS" : {
71
+ // e.g. c:/temp/UWApp1/lib/winjs-4.0.1/js/base.js
72
+ "match" : / ^ ( .* \/ w i n j s - [ . \d ] + ) \/ j s \/ b a s e \. j s $ / i, // If the winjs/base.js file is found..
73
+ "exclude" : [ [ "^" , 1 , "/.*" ] ] , // ..then exclude all files under the winjs folder
74
+ "types" : [ "winjs" ] // And fetch the @types package for WinJS
75
+ } ,
76
+ "Kendo" : {
77
+ // e.g. /Kendo3/wwwroot/lib/kendo/kendo.all.min.js
78
+ "match" : / ^ ( .* \/ k e n d o ) \/ k e n d o \. a l l \. m i n \. j s $ / i,
79
+ "exclude" : [ [ "^" , 1 , "/.*" ] ] ,
80
+ "types" : [ "kendo-ui" ]
81
+ } ,
82
+ "Office Nuget" : {
83
+ // e.g. /scripts/Office/1/excel-15.debug.js
84
+ "match" : / ^ ( .* \/ o f f i c e \/ 1 ) \/ e x c e l - \d + \. d e b u g \. j s $ / i, // Office NuGet package is installed under a "1/office" folder
85
+ "exclude" : [ [ "^" , 1 , "/.*" ] ] , // Exclude that whole folder if the file indicated above is found in it
86
+ "types" : [ "office" ] // @types package to fetch instead
87
+ }
88
+ } ;
89
+
60
90
export function convertFormatOptions ( protocolOptions : protocol . FormatCodeSettings ) : FormatCodeSettings {
61
91
if ( typeof protocolOptions . indentStyle === "string" ) {
62
92
protocolOptions . indentStyle = indentStyle . get ( protocolOptions . indentStyle . toLowerCase ( ) ) ;
@@ -259,6 +289,7 @@ namespace ts.server {
259
289
private readonly throttledOperations : ThrottledOperations ;
260
290
261
291
private readonly hostConfiguration : HostConfiguration ;
292
+ private static safelist : SafeList = defaultTypeSafeList ;
262
293
263
294
private changedFiles : ScriptInfo [ ] ;
264
295
@@ -284,8 +315,6 @@ namespace ts.server {
284
315
285
316
this . typingsCache = new TypingsCache ( this . typingsInstaller ) ;
286
317
287
- // ts.disableIncrementalParsing = true;
288
-
289
318
this . hostConfiguration = {
290
319
formatCodeOptions : getDefaultFormatCodeSettings ( this . host ) ,
291
320
hostInfo : "Unknown host" ,
@@ -831,7 +860,7 @@ namespace ts.server {
831
860
getDirectoryPath ( configFilename ) ,
832
861
/*existingOptions*/ { } ,
833
862
configFilename ,
834
- /*resolutionStack*/ [ ] ,
863
+ /*resolutionStack*/ [ ] ,
835
864
this . hostConfiguration . extraFileExtensions ) ;
836
865
837
866
if ( parsedCommandLine . errors . length ) {
@@ -1399,13 +1428,104 @@ namespace ts.server {
1399
1428
this . refreshInferredProjects ( ) ;
1400
1429
}
1401
1430
1431
+ /** Makes a filename safe to insert in a RegExp */
1432
+ private static filenameEscapeRegexp = / [ - \/ \\ ^ $ * + ? . ( ) | [ \] { } ] / g;
1433
+ private static escapeFilenameForRegex ( filename : string ) {
1434
+ return filename . replace ( this . filenameEscapeRegexp , "\\$&" ) ;
1435
+ }
1436
+
1437
+ resetSafeList ( ) : void {
1438
+ ProjectService . safelist = defaultTypeSafeList ;
1439
+ }
1440
+
1441
+ loadSafeList ( fileName : string ) : void {
1442
+ const raw : SafeList = JSON . parse ( this . host . readFile ( fileName , "utf-8" ) ) ;
1443
+ // Parse the regexps
1444
+ for ( const k of Object . keys ( raw ) ) {
1445
+ raw [ k ] . match = new RegExp ( raw [ k ] . match as { } as string , "i" ) ;
1446
+ }
1447
+ // raw is now fixed and ready
1448
+ ProjectService . safelist = raw ;
1449
+ }
1450
+
1451
+ applySafeList ( proj : protocol . ExternalProject ) : void {
1452
+ const { rootFiles, typeAcquisition } = proj ;
1453
+ const types = ( typeAcquisition && typeAcquisition . include ) || [ ] ;
1454
+
1455
+ const excludeRules : string [ ] = [ ] ;
1456
+
1457
+ const normalizedNames = rootFiles . map ( f => normalizeSlashes ( f . fileName ) ) ;
1458
+
1459
+ for ( const name of Object . keys ( ProjectService . safelist ) ) {
1460
+ const rule = ProjectService . safelist [ name ] ;
1461
+ for ( const root of normalizedNames ) {
1462
+ if ( rule . match . test ( root ) ) {
1463
+ this . logger . info ( `Excluding files based on rule ${ name } ` ) ;
1464
+
1465
+ // If the file matches, collect its types packages and exclude rules
1466
+ if ( rule . types ) {
1467
+ for ( const type of rule . types ) {
1468
+ if ( types . indexOf ( type ) < 0 ) {
1469
+ types . push ( type ) ;
1470
+ }
1471
+ }
1472
+ }
1473
+
1474
+ if ( rule . exclude ) {
1475
+ for ( const exclude of rule . exclude ) {
1476
+ const processedRule = root . replace ( rule . match , ( ...groups : Array < string > ) => {
1477
+ return exclude . map ( groupNumberOrString => {
1478
+ // RegExp group numbers are 1-based, but the first element in groups
1479
+ // is actually the original string, so it all works out in the end.
1480
+ if ( typeof groupNumberOrString === "number" ) {
1481
+ if ( typeof groups [ groupNumberOrString ] !== "string" ) {
1482
+ // Specification was wrong - exclude nothing!
1483
+ this . logger . info ( `Incorrect RegExp specification in safelist rule ${ name } - not enough groups` ) ;
1484
+ // * can't appear in a filename; escape it because it's feeding into a RegExp
1485
+ return "\\*" ;
1486
+ }
1487
+ return ProjectService . escapeFilenameForRegex ( groups [ groupNumberOrString ] ) ;
1488
+ }
1489
+ return groupNumberOrString ;
1490
+ } ) . join ( "" ) ;
1491
+ } ) ;
1492
+
1493
+ if ( excludeRules . indexOf ( processedRule ) === - 1 ) {
1494
+ excludeRules . push ( processedRule ) ;
1495
+ }
1496
+ }
1497
+ }
1498
+ else {
1499
+ // If not rules listed, add the default rule to exclude the matched file
1500
+ const escaped = ProjectService . escapeFilenameForRegex ( root ) ;
1501
+ if ( excludeRules . indexOf ( escaped ) < 0 ) {
1502
+ excludeRules . push ( escaped ) ;
1503
+ }
1504
+ }
1505
+ }
1506
+ }
1507
+
1508
+ // Copy back this field into the project if needed
1509
+ if ( types . length > 0 ) {
1510
+ proj . typeAcquisition = proj . typeAcquisition || { } ;
1511
+ proj . typeAcquisition . include = types ;
1512
+ }
1513
+ }
1514
+
1515
+ const excludeRegexes = excludeRules . map ( e => new RegExp ( e , "i" ) ) ;
1516
+ proj . rootFiles = proj . rootFiles . filter ( ( _file , index ) => ! excludeRegexes . some ( re => re . test ( normalizedNames [ index ] ) ) ) ;
1517
+ }
1518
+
1402
1519
openExternalProject ( proj : protocol . ExternalProject , suppressRefreshOfInferredProjects = false ) : void {
1403
1520
// typingOptions has been deprecated and is only supported for backward compatibility
1404
1521
// purposes. It should be removed in future releases - use typeAcquisition instead.
1405
1522
if ( proj . typingOptions && ! proj . typeAcquisition ) {
1406
1523
const typeAcquisition = convertEnableAutoDiscoveryToEnable ( proj . typingOptions ) ;
1407
1524
proj . typeAcquisition = typeAcquisition ;
1408
1525
}
1526
+
1527
+ this . applySafeList ( proj ) ;
1528
+
1409
1529
let tsConfigFiles : NormalizedPath [ ] ;
1410
1530
const rootFiles : protocol . ExternalFile [ ] = [ ] ;
1411
1531
for ( const file of proj . rootFiles ) {
0 commit comments