Skip to content

Commit 30575db

Browse files
committed
Added caching, more tests
1 parent f9ae3e4 commit 30575db

12 files changed

Lines changed: 924 additions & 288 deletions

File tree

Jakefile.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ var harnessSources = harnessCoreSources.concat([
160160
"reuseProgramStructure.ts",
161161
"cachingInServerLSHost.ts",
162162
"moduleResolution.ts",
163-
"tsconfigParsing.ts"
163+
"tsconfigParsing.ts",
164+
"expandFiles.ts"
164165
].map(function (f) {
165166
return path.join(unittestsDirectory, f);
166167
})).concat([

src/compiler/commandLineParser.ts

Lines changed: 360 additions & 114 deletions
Large diffs are not rendered by default.

src/compiler/core.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ namespace ts {
2626
remove,
2727
forEachValue: forEachValueInMap,
2828
reduce,
29-
clear
29+
clear,
30+
mergeFrom
3031
};
3132

3233
function forEachValueInMap(f: (key: Path, value: T) => void) {
@@ -61,6 +62,16 @@ namespace ts {
6162
files = {};
6263
}
6364

65+
function mergeFrom(other: FileMap<T>) {
66+
other.forEachValue(mergeFromOther);
67+
}
68+
69+
function mergeFromOther(key: Path, value: T) {
70+
if (!contains(key)) {
71+
set(key, value);
72+
}
73+
}
74+
6475
function toKey(path: Path): string {
6576
return keyMapper ? keyMapper(path) : path;
6677
}
@@ -822,6 +833,28 @@ namespace ts {
822833
return compareValues(aComponents.length, bComponents.length);
823834
}
824835

836+
export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean) {
837+
if (parent === undefined || child === undefined) return false;
838+
if (parent === child) return true;
839+
parent = removeTrailingDirectorySeparator(parent);
840+
child = removeTrailingDirectorySeparator(child);
841+
if (parent === child) return true;
842+
const parentComponents = getNormalizedPathComponents(parent, currentDirectory);
843+
const childComponents = getNormalizedPathComponents(child, currentDirectory);
844+
if (childComponents.length < parentComponents.length) {
845+
return false;
846+
}
847+
848+
for (let i = 0; i < parentComponents.length; ++i) {
849+
const result = compareStrings(parentComponents[i], childComponents[i], ignoreCase);
850+
if (result !== Comparison.EqualTo) {
851+
return false;
852+
}
853+
}
854+
855+
return true;
856+
}
857+
825858
export function fileExtensionIs(path: string, extension: string): boolean {
826859
const pathLen = path.length;
827860
const extLen = extension.length;
@@ -850,6 +883,59 @@ namespace ts {
850883
return false;
851884
}
852885

886+
/**
887+
* Extension boundaries by priority. Lower numbers indicate higher priorities, and are
888+
* aligned to the offset of the highest priority extension in the
889+
* allSupportedExtensions array.
890+
*/
891+
export const enum ExtensionPriority {
892+
TypeScriptFiles = 0,
893+
DeclarationAndJavaScriptFiles = 2,
894+
Limit = 5,
895+
896+
Highest = TypeScriptFiles,
897+
Lowest = DeclarationAndJavaScriptFiles,
898+
}
899+
900+
export function getExtensionPriority(path: string, supportedExtensions: string[]): ExtensionPriority {
901+
for (let i = supportedExtensions.length - 1; i >= 0; i--) {
902+
if (fileExtensionIs(path, supportedExtensions[i])) {
903+
return adjustExtensionPriority(<ExtensionPriority>i);
904+
}
905+
}
906+
907+
// If its not in the list of supported extensions, this is likely a
908+
// TypeScript file with a non-ts extension
909+
return ExtensionPriority.Highest;
910+
}
911+
912+
/**
913+
* Adjusts an extension priority to be the highest priority within the same range.
914+
*/
915+
export function adjustExtensionPriority(extensionPriority: ExtensionPriority): ExtensionPriority {
916+
if (extensionPriority < ExtensionPriority.DeclarationAndJavaScriptFiles) {
917+
return ExtensionPriority.TypeScriptFiles;
918+
}
919+
else if (extensionPriority < ExtensionPriority.Limit) {
920+
return ExtensionPriority.DeclarationAndJavaScriptFiles;
921+
}
922+
else {
923+
return ExtensionPriority.Limit;
924+
}
925+
}
926+
927+
/**
928+
* Gets the next lowest extension priority for a given priority.
929+
*/
930+
export function getNextLowestExtensionPriority(extensionPriority: ExtensionPriority): ExtensionPriority {
931+
if (extensionPriority < ExtensionPriority.DeclarationAndJavaScriptFiles) {
932+
return ExtensionPriority.DeclarationAndJavaScriptFiles;
933+
}
934+
else {
935+
return ExtensionPriority.Limit;
936+
}
937+
}
938+
853939
const extensionsToRemove = [".d.ts", ".ts", ".js", ".tsx", ".jsx"];
854940
export function removeFileExtension(path: string): string {
855941
for (const ext of extensionsToRemove) {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2020,6 +2020,10 @@
20202020
"category": "Error",
20212021
"code": 5009
20222022
},
2023+
"File specification cannot end in a recursive directory wildcard ('**'): '{0}'.": {
2024+
"category": "Error",
2025+
"code": 5010
2026+
},
20232027
"File specification cannot contain multiple recursive directory wildcards ('**'): '{0}'.": {
20242028
"category": "Error",
20252029
"code": 5011

src/compiler/types.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace ts {
1515

1616
forEachValue(f: (key: Path, v: T) => void): void;
1717
reduce<U>(f: (memo: U, value: T, key: Path) => U, initial: U): U;
18+
mergeFrom(other: FileMap<T>): void;
1819
clear(): void;
1920
}
2021

@@ -1588,11 +1589,17 @@ namespace ts {
15881589
readDirectory(rootDir: string, extension: string, exclude: string[]): string[];
15891590

15901591
/**
1591-
* Gets a value indicating whether the specified path exists.
1592+
* Gets a value indicating whether the specified path exists and is a file.
15921593
* @param path The path to test.
15931594
*/
15941595
fileExists(path: string): boolean;
15951596

1597+
/**
1598+
* Gets a value indicating whether the specified path exists and is a directory.
1599+
* @param path The path to test.
1600+
*/
1601+
directoryExists(path: string): boolean;
1602+
15961603
/**
15971604
* Reads the files names in the directory.
15981605
* @param rootDir The directory path.
@@ -2460,6 +2467,17 @@ namespace ts {
24602467
options: CompilerOptions;
24612468
fileNames: string[];
24622469
errors: Diagnostic[];
2470+
wildcardDirectories?: Map<WatchDirectoryFlags>;
2471+
}
2472+
2473+
export const enum WatchDirectoryFlags {
2474+
None = 0,
2475+
Recursive = 1 << 0,
2476+
}
2477+
2478+
export interface ExpandResult {
2479+
fileNames: string[];
2480+
wildcardDirectories: Map<WatchDirectoryFlags>;
24632481
}
24642482

24652483
/* @internal */

src/harness/external/chai.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,14 @@ declare module chai {
167167
module assert {
168168
function equal(actual: any, expected: any, message?: string): void;
169169
function notEqual(actual: any, expected: any, message?: string): void;
170-
function deepEqual(actual: any, expected: any, message?: string): void;
171-
function notDeepEqual(actual: any, expected: any, message?: string): void;
170+
function deepEqual<T>(actual: T, expected: T, message?: string): void;
171+
function notDeepEqual<T>(actual: T, expected: T, message?: string): void;
172172
function lengthOf(object: any[], length: number, message?: string): void;
173173
function isTrue(value: any, message?: string): void;
174174
function isFalse(value: any, message?: string): void;
175175
function isNull(value: any, message?: string): void;
176176
function isNotNull(value: any, message?: string): void;
177+
function isUndefined(value: any, message?: string): void;
178+
function isDefined(value: any, message?: string): void;
177179
}
178180
}

src/harness/harnessLanguageService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ namespace Harness.LanguageService {
266266
throw new Error("Not implemented.");
267267
}
268268
fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; }
269+
directoryExists(directoryName: string) { return false; }
269270
readFile(fileName: string) {
270271
const snapshot = this.nativeHost.getScriptSnapshot(fileName);
271272
return snapshot && snapshot.getText(0, snapshot.getLength());

src/harness/projectsRunner.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ class ProjectRunner extends RunnerBase {
213213
const configParseHost: ts.ParseConfigHost = {
214214
useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(),
215215
fileExists,
216+
directoryExists,
216217
readDirectory,
217218
readDirectoryNames,
218219
readFileNames
@@ -297,6 +298,10 @@ class ProjectRunner extends RunnerBase {
297298
return Harness.IO.fileExists(getFileNameInTheProjectTest(fileName));
298299
}
299300

301+
function directoryExists(directoryName: string): boolean {
302+
return Harness.IO.directoryExists(getFileNameInTheProjectTest(directoryName));
303+
}
304+
300305
function getSourceFileText(fileName: string): string {
301306
let text: string = undefined;
302307
try {

src/harness/rwcRunner.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ namespace RWC {
7979
const configParseHost: ts.ParseConfigHost = {
8080
useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(),
8181
fileExists: Harness.IO.fileExists,
82+
directoryExists: Harness.IO.directoryExists,
8283
readDirectory: Harness.IO.readDirectory,
8384
readDirectoryNames: Harness.IO.readDirectoryNames,
8485
readFileNames: Harness.IO.readFileNames,

src/server/editorServices.ts

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ namespace ts.server {
119119
if (!resolution) {
120120
const existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, moduleName);
121121
if (moduleResolutionIsValid(existingResolution)) {
122-
// ok, it is safe to use existing module resolution results
122+
// ok, it is safe to use existing module resolution results
123123
resolution = existingResolution;
124124
}
125125
else {
@@ -144,8 +144,8 @@ namespace ts.server {
144144
}
145145

146146
if (resolution.resolvedModule) {
147-
// TODO: consider checking failedLookupLocations
148-
// TODO: use lastCheckTime to track expiration for module name resolution
147+
// TODO: consider checking failedLookupLocations
148+
// TODO: use lastCheckTime to track expiration for module name resolution
149149
return true;
150150
}
151151

@@ -354,6 +354,7 @@ namespace ts.server {
354354
export interface ProjectOptions {
355355
// these fields can be present in the project file
356356
files?: string[];
357+
wildcardDirectories?: ts.Map<ts.WatchDirectoryFlags>;
357358
compilerOptions?: ts.CompilerOptions;
358359
}
359360

@@ -362,6 +363,7 @@ namespace ts.server {
362363
projectFilename: string;
363364
projectFileWatcher: FileWatcher;
364365
directoryWatcher: FileWatcher;
366+
directoriesWatchedForWildcards: Map<FileWatcher>;
365367
// Used to keep track of what directories are watched for this project
366368
directoriesWatchedForTsconfig: string[] = [];
367369
program: ts.Program;
@@ -510,7 +512,7 @@ namespace ts.server {
510512
openFileRootsConfigured: ScriptInfo[] = [];
511513
// a path to directory watcher map that detects added tsconfig files
512514
directoryWatchersForTsconfig: ts.Map<FileWatcher> = {};
513-
// count of how many projects are using the directory watcher. If the
515+
// count of how many projects are using the directory watcher. If the
514516
// number becomes 0 for a watcher, then we should close it.
515517
directoryWatchersRefCount: ts.Map<number> = {};
516518
hostConfiguration: HostConfiguration;
@@ -590,11 +592,11 @@ namespace ts.server {
590592
// We check if the project file list has changed. If so, we update the project.
591593
if (!arrayIsEqualTo(currentRootFiles && currentRootFiles.sort(), newRootFiles && newRootFiles.sort())) {
592594
// For configured projects, the change is made outside the tsconfig file, and
593-
// it is not likely to affect the project for other files opened by the client. We can
595+
// it is not likely to affect the project for other files opened by the client. We can
594596
// just update the current project.
595597
this.updateConfiguredProject(project);
596598

597-
// Call updateProjectStructure to clean up inferred projects we may have
599+
// Call updateProjectStructure to clean up inferred projects we may have
598600
// created for the new files
599601
this.updateProjectStructure();
600602
}
@@ -739,6 +741,8 @@ namespace ts.server {
739741
if (project.isConfiguredProject()) {
740742
project.projectFileWatcher.close();
741743
project.directoryWatcher.close();
744+
forEachValue(project.directoriesWatchedForWildcards, watcher => { watcher.close(); });
745+
delete project.directoriesWatchedForWildcards;
742746
this.configuredProjects = copyListRemovingItem(project, this.configuredProjects);
743747
}
744748
else {
@@ -816,8 +820,8 @@ namespace ts.server {
816820
* @param info The file that has been closed or newly configured
817821
*/
818822
closeOpenFile(info: ScriptInfo) {
819-
// Closing file should trigger re-reading the file content from disk. This is
820-
// because the user may chose to discard the buffer content before saving
823+
// Closing file should trigger re-reading the file content from disk. This is
824+
// because the user may chose to discard the buffer content before saving
821825
// to the disk, and the server's version of the file can be out of sync.
822826
info.svc.reloadFromFile(info.fileName);
823827

@@ -915,8 +919,8 @@ namespace ts.server {
915919
}
916920

917921
/**
918-
* This function is to update the project structure for every projects.
919-
* It is called on the premise that all the configured projects are
922+
* This function is to update the project structure for every projects.
923+
* It is called on the premise that all the configured projects are
920924
* up to date.
921925
*/
922926
updateProjectStructure() {
@@ -970,7 +974,7 @@ namespace ts.server {
970974

971975
if (rootFile.defaultProject && rootFile.defaultProject.isConfiguredProject()) {
972976
// If the root file has already been added into a configured project,
973-
// meaning the original inferred project is gone already.
977+
// meaning the original inferred project is gone already.
974978
if (!rootedProject.isConfiguredProject()) {
975979
this.removeProject(rootedProject);
976980
}
@@ -1075,9 +1079,9 @@ namespace ts.server {
10751079
}
10761080

10771081
/**
1078-
* This function tries to search for a tsconfig.json for the given file. If we found it,
1082+
* This function tries to search for a tsconfig.json for the given file. If we found it,
10791083
* we first detect if there is already a configured project created for it: if so, we re-read
1080-
* the tsconfig file content and update the project; otherwise we create a new one.
1084+
* the tsconfig file content and update the project; otherwise we create a new one.
10811085
*/
10821086
openOrUpdateConfiguredProjectForFile(fileName: string) {
10831087
const searchPath = ts.normalizePath(getDirectoryPath(fileName));
@@ -1215,7 +1219,8 @@ namespace ts.server {
12151219
else {
12161220
const projectOptions: ProjectOptions = {
12171221
files: parsedCommandLine.fileNames,
1218-
compilerOptions: parsedCommandLine.options
1222+
wildcardDirectories: parsedCommandLine.wildcardDirectories,
1223+
compilerOptions: parsedCommandLine.options,
12191224
};
12201225
return { succeeded: true, projectOptions };
12211226
}
@@ -1241,12 +1246,30 @@ namespace ts.server {
12411246
}
12421247
project.finishGraph();
12431248
project.projectFileWatcher = this.host.watchFile(configFilename, _ => this.watchedProjectConfigFileChanged(project));
1244-
this.log("Add recursive watcher for: " + ts.getDirectoryPath(configFilename));
1249+
1250+
const configDirectoryPath = ts.getDirectoryPath(configFilename);
1251+
1252+
this.log("Add recursive watcher for: " + configDirectoryPath);
12451253
project.directoryWatcher = this.host.watchDirectory(
1246-
ts.getDirectoryPath(configFilename),
1254+
configDirectoryPath,
12471255
path => this.directoryWatchedForSourceFilesChanged(project, path),
12481256
/*recursive*/ true
12491257
);
1258+
1259+
project.directoriesWatchedForWildcards = reduceProperties(projectOptions.wildcardDirectories, (watchers, flag, directory) => {
1260+
if (comparePaths(configDirectoryPath, directory, ".", !this.host.useCaseSensitiveFileNames) !== Comparison.EqualTo) {
1261+
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
1262+
this.log(`Add ${ recursive ? "recursive " : ""}watcher for: ${directory}`);
1263+
watchers[directory] = this.host.watchDirectory(
1264+
directory,
1265+
path => this.directoryWatchedForSourceFilesChanged(project, path),
1266+
recursive
1267+
);
1268+
}
1269+
1270+
return watchers;
1271+
}, <Map<FileWatcher>>{});
1272+
12501273
return { success: true, project: project };
12511274
}
12521275
}
@@ -1280,7 +1303,7 @@ namespace ts.server {
12801303
info = this.openFile(fileName, /*openedByClient*/ false);
12811304
}
12821305
else {
1283-
// if the root file was opened by client, it would belong to either
1306+
// if the root file was opened by client, it would belong to either
12841307
// openFileRoots or openFileReferenced.
12851308
if (info.isOpen) {
12861309
if (this.openFileRoots.indexOf(info) >= 0) {

0 commit comments

Comments
 (0)