Skip to content

Commit

Permalink
CR feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
zhengbli committed Oct 14, 2015
1 parent 9ed5b4c commit 002f0c0
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 93 deletions.
6 changes: 3 additions & 3 deletions src/compiler/commandLineParser.ts
Expand Up @@ -389,15 +389,15 @@ namespace ts {
catch (e) {
return { error: createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message) };
}
return parseConfigFileText(fileName, text);
return parseConfigFileTextToJson(fileName, text);
}

/**
* Parse the text of the tsconfig.json file
* @param fileName The path to the config file
* @param jsonText The text of the config file
*/
export function parseConfigFileText(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } {
export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } {
try {
return { config: /\S/.test(jsonText) ? JSON.parse(jsonText) : {} };
}
Expand All @@ -412,7 +412,7 @@ namespace ts {
* @param basePath A root directory to resolve relative path entries in the config
* file to. e.g. outDir
*/
export function parseConfigFile(json: any, host: ParseConfigHost, basePath: string): ParsedCommandLine {
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string): ParsedCommandLine {
let errors: Diagnostic[] = [];

return {
Expand Down
15 changes: 3 additions & 12 deletions src/compiler/core.ts
Expand Up @@ -833,24 +833,15 @@ namespace ts {
}
}

export function doTwoArraysHaveTheSameElements<T>(array1: Array<T>, array2: Array<T>): Boolean {
export function arrayStructurallyIsEqualTo<T>(array1: Array<T>, array2: Array<T>): boolean {
if (!array1 || !array2) {
return false;
}

if (array1.length != array2.length) {
if (array1.length !== array2.length) {
return false;
}

array1 = array1.sort();
array2 = array2.sort();

for (let i = 0; i < array1.length; i++) {
if (array1[i] != array2[i]) {
return false;
}
}

return true;
return arrayIsEqualTo(array1.sort(), array2.sort());
}
}
77 changes: 36 additions & 41 deletions src/compiler/sys.ts
Expand Up @@ -199,32 +199,19 @@ namespace ts {
const _path = require("path");
const _os = require("os");

class WatchedFileSet {
private watchedFiles: WatchedFile[] = [];
private nextFileToCheck = 0;
private watchTimer: any;

// average async stat takes about 30 microseconds
// set chunk size to do 30 files in < 1 millisecond
constructor(public interval = 2500, public chunkSize = 30) {
}

private static copyListRemovingItem<T>(item: T, list: T[]) {
let copiedList: T[] = [];
for (var i = 0, len = list.length; i < len; i++) {
if (list[i] != item) {
copiedList.push(list[i]);
}
}
return copiedList;
}

private static getModifiedTime(fileName: string): Date {
// average async stat takes about 30 microseconds
// set chunk size to do 30 files in < 1 millisecond
function createWatchedFileSet(interval = 2500, chunkSize = 30) {
let watchedFiles: WatchedFile[] = [];
let nextFileToCheck = 0;
let watchTimer: any;

function getModifiedTime(fileName: string): Date {
return _fs.statSync(fileName).mtime;
}

private poll(checkedIndex: number) {
let watchedFile = this.watchedFiles[checkedIndex];
function poll(checkedIndex: number) {
let watchedFile = watchedFiles[checkedIndex];
if (!watchedFile) {
return;
}
Expand All @@ -234,7 +221,7 @@ namespace ts {
watchedFile.callback(watchedFile.fileName);
}
else if (watchedFile.mtime.getTime() !== stats.mtime.getTime()) {
watchedFile.mtime = WatchedFileSet.getModifiedTime(watchedFile.fileName);
watchedFile.mtime = getModifiedTime(watchedFile.fileName);
watchedFile.callback(watchedFile.fileName, watchedFile.mtime.getTime() === 0);
}
});
Expand All @@ -243,42 +230,50 @@ namespace ts {
// this implementation uses polling and
// stat due to inconsistencies of fs.watch
// and efficiency of stat on modern filesystems
private startWatchTimer() {
this.watchTimer = setInterval(() => {
function startWatchTimer() {
watchTimer = setInterval(() => {
let count = 0;
let nextToCheck = this.nextFileToCheck;
let nextToCheck = nextFileToCheck;
let firstCheck = -1;
while ((count < this.chunkSize) && (nextToCheck !== firstCheck)) {
this.poll(nextToCheck);
while ((count < chunkSize) && (nextToCheck !== firstCheck)) {
poll(nextToCheck);
if (firstCheck < 0) {
firstCheck = nextToCheck;
}
nextToCheck++;
if (nextToCheck === this.watchedFiles.length) {
if (nextToCheck === watchedFiles.length) {
nextToCheck = 0;
}
count++;
}
this.nextFileToCheck = nextToCheck;
}, this.interval);
nextFileToCheck = nextToCheck;
}, interval);
}

addFile(fileName: string, callback: (fileName: string, removed?: boolean) => void): WatchedFile {
function addFile(fileName: string, callback: (fileName: string, removed?: boolean) => void): WatchedFile {
let file: WatchedFile = {
fileName,
callback,
mtime: WatchedFileSet.getModifiedTime(fileName)
mtime: getModifiedTime(fileName)
};

this.watchedFiles.push(file);
if (this.watchedFiles.length === 1) {
this.startWatchTimer();
watchedFiles.push(file);
if (watchedFiles.length === 1) {
startWatchTimer();
}
return file;
}

removeFile(file: WatchedFile) {
this.watchedFiles = WatchedFileSet.copyListRemovingItem(file, this.watchedFiles);
function removeFile(file: WatchedFile) {
watchedFiles = copyListRemovingItem(file, watchedFiles);
}

return {
getModifiedTime: getModifiedTime,
poll: poll,
startWatchTimer: startWatchTimer,
addFile: addFile,
removeFile: removeFile
}
}

Expand All @@ -295,7 +290,7 @@ namespace ts {
// changes for large reference sets? If so, do we want
// to increase the chunk size or decrease the interval
// time dynamically to match the large reference set?
let watchedFileSet = new WatchedFileSet();
let watchedFileSet = createWatchedFileSet();

function isNode4OrLater(): Boolean {
return parseInt(process.version.charAt(1)) >= 4;
Expand Down Expand Up @@ -417,7 +412,7 @@ namespace ts {
// In watchDirectory we only care about adding and removing files (when event name is
// "rename"); changes made within files are handled by corresponding fileWatchers (when
// event name is "change")
if (eventName == "rename") {
if (eventName === "rename") {
// When deleting a file, the passed baseFileName is null
callback(!relativeFileName ? relativeFileName : normalizePath(ts.combinePaths(path, relativeFileName)));
};
Expand Down
78 changes: 49 additions & 29 deletions src/compiler/tsc.ts
Expand Up @@ -147,15 +147,17 @@ namespace ts {

export function executeCommandLine(args: string[]): void {
let commandLine = parseCommandLine(args);
let configFileName: string; // Configuration file name (if any)
let configFileWatcher: FileWatcher; // Configuration file watcher
let directoryWatcher: FileWatcher; // Directory watcher to monitor source file addition/removal
let cachedProgram: Program; // Program cached from last compilation
let rootFileNames: string[]; // Root fileNames for compilation
let compilerOptions: CompilerOptions; // Compiler options for compilation
let compilerHost: CompilerHost; // Compiler host
let hostGetSourceFile: typeof compilerHost.getSourceFile; // getSourceFile method from default host
let timerHandle: number; // Handle for 0.25s wait timer
let configFileName: string; // Configuration file name (if any)
let cachedConfigFileText: string; // Cached configuration file text, used for reparsing (if any)
let configFileWatcher: FileWatcher; // Configuration file watcher
let directoryWatcher: FileWatcher; // Directory watcher to monitor source file addition/removal
let cachedProgram: Program; // Program cached from last compilation
let rootFileNames: string[]; // Root fileNames for compilation
let compilerOptions: CompilerOptions; // Compiler options for compilation
let compilerHost: CompilerHost; // Compiler host
let hostGetSourceFile: typeof compilerHost.getSourceFile; // getSourceFile method from default host
let timerHandleForRecompilation: number; // Handle for 0.25s wait timer to trigger recompilation
let timerHandleForDirectoryChanges: number; // Handle for 0.25s wait timer to trigger directory change handler

if (commandLine.options.locale) {
if (!isJSONSupported()) {
Expand Down Expand Up @@ -232,16 +234,22 @@ namespace ts {

performCompilation();

function configFileToParsedCommandLine(configFilename: string): ParsedCommandLine {
let result = readConfigFile(configFileName, sys.readFile);
if (result.error) {
reportWatchDiagnostic(result.error);
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
return;
function parseConfigFile(): ParsedCommandLine {
if (!cachedConfigFileText) {
try {
cachedConfigFileText = sys.readFile(configFileName);
}
catch (e) {
let error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message);
reportWatchDiagnostic(error);
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
return;
}
}

let result = parseConfigFileTextToJson(configFileName, cachedConfigFileText);
let configObject = result.config;
let configParseResult = parseConfigFile(configObject, sys, getDirectoryPath(configFileName));
let configParseResult = parseJsonConfigFileContent(configObject, sys, getDirectoryPath(configFileName));
if (configParseResult.errors.length > 0) {
reportDiagnostics(configParseResult.errors);
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
Expand All @@ -255,7 +263,7 @@ namespace ts {

if (!cachedProgram) {
if (configFileName) {
let configParseResult = configFileToParsedCommandLine(configFileName);
let configParseResult = parseConfigFile();
rootFileNames = configParseResult.fileNames;
compilerOptions = extend(commandLine.options, configParseResult.options);
}
Expand Down Expand Up @@ -322,42 +330,54 @@ namespace ts {
rootFileNames.splice(index, 1);
}
}
startTimer();
startTimerForRecompilation();
}

// If the configuration file changes, forget cached program and start the recompilation timer
function configFileChanged() {
setCachedProgram(undefined);
startTimer();
cachedConfigFileText = undefined;
startTimerForRecompilation();
}

function watchedDirectoryChanged(fileName: string) {
if (fileName && !ts.isSupportedSourceFileName(fileName)) {
return;
}

let parsedCommandLine = configFileToParsedCommandLine(configFileName);
let newFileNames = parsedCommandLine.fileNames.map(compilerHost.getCanonicalFileName);
let canonicalRootFileNames = rootFileNames.map(compilerHost.getCanonicalFileName);
startTimerForHandlingDirectoryChanges();
}

function startTimerForHandlingDirectoryChanges() {
if (timerHandleForDirectoryChanges) {
clearTimeout(timerHandleForDirectoryChanges);
}
timerHandleForDirectoryChanges = setTimeout(directoryChangeHandler, 250);
}

function directoryChangeHandler() {
let parsedCommandLine = parseConfigFile();
let newFileNames = ts.map(parsedCommandLine.fileNames, compilerHost.getCanonicalFileName);
let canonicalRootFileNames = ts.map(rootFileNames, compilerHost.getCanonicalFileName);

if (!doTwoArraysHaveTheSameElements(newFileNames, canonicalRootFileNames)) {
if (!arrayStructurallyIsEqualTo(newFileNames, canonicalRootFileNames)) {
setCachedProgram(undefined);
startTimer();
startTimerForRecompilation();
}
}

// Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch
// operations (such as saving all modified files in an editor) a chance to complete before we kick
// off a new compilation.
function startTimer() {
if (timerHandle) {
clearTimeout(timerHandle);
function startTimerForRecompilation() {
if (timerHandleForRecompilation) {
clearTimeout(timerHandleForRecompilation);
}
timerHandle = setTimeout(recompile, 250);
timerHandleForRecompilation = setTimeout(recompile, 250);
}

function recompile() {
timerHandle = undefined;
timerHandleForRecompilation = undefined;
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
performCompilation();
}
Expand Down
10 changes: 10 additions & 0 deletions src/compiler/utilities.ts
Expand Up @@ -2406,4 +2406,14 @@ namespace ts {
}
}
}

export function copyListRemovingItem<T>(item: T, list: T[]) {
var copiedList: T[] = [];
for (var i = 0, len = list.length; i < len; i++) {
if (list[i] != item) {
copiedList.push(list[i]);
}
}
return copiedList;
}
}
11 changes: 5 additions & 6 deletions src/server/editorServices.ts
Expand Up @@ -556,12 +556,11 @@ namespace ts.server {
}

this.log("Detected source file changes: " + fileName);

let { succeeded, projectOptions, error } = this.configFileToProjectOptions(project.projectFilename);
let newRootFiles = projectOptions.files.map(f => this.getCanonicalFileName(f));
let currentRootFiles = project.getRootFiles().map(f => this.getCanonicalFileName(f));
let newRootFiles = ts.map(projectOptions.files, this.getCanonicalFileName);
let currentRootFiles = ts.map(project.getRootFiles(), this.getCanonicalFileName);

if (!doTwoArraysHaveTheSameElements(currentRootFiles, newRootFiles)) {
if (!arrayStructurallyIsEqualTo(currentRootFiles, newRootFiles)) {
// For configured projects, the change is made outside the tsconfig file, and
// it is not likely to affect the project for other files opened by the client. We can
// just update the current project.
Expand Down Expand Up @@ -1159,12 +1158,12 @@ namespace ts.server {
// file references will be relative to dirPath (or absolute)
var dirPath = ts.getDirectoryPath(configFilename);
var contents = this.host.readFile(configFilename)
var rawConfig: { config?: ProjectOptions; error?: Diagnostic; } = ts.parseConfigFileText(configFilename, contents);
var rawConfig: { config?: ProjectOptions; error?: Diagnostic; } = ts.parseConfigFileTextToJson(configFilename, contents);
if (rawConfig.error) {
return { succeeded: false, error: rawConfig.error };
}
else {
var parsedCommandLine = ts.parseConfigFile(rawConfig.config, this.host, dirPath);
var parsedCommandLine = ts.parseJsonConfigFileContent(rawConfig.config, this.host, dirPath);
if (parsedCommandLine.errors && (parsedCommandLine.errors.length > 0)) {
return { succeeded: false, error: { errorMsg: "tsconfig option errors" } };
}
Expand Down
4 changes: 2 additions & 2 deletions src/services/shims.ts
Expand Up @@ -990,7 +990,7 @@ namespace ts {
() => {
let text = sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength());

let result = parseConfigFileText(fileName, text);
let result = parseConfigFileTextToJson(fileName, text);

if (result.error) {
return {
Expand All @@ -1000,7 +1000,7 @@ namespace ts {
};
}

var configFile = parseConfigFile(result.config, this.host, getDirectoryPath(normalizeSlashes(fileName)));
var configFile = parseJsonConfigFileContent(result.config, this.host, getDirectoryPath(normalizeSlashes(fileName)));

return {
options: configFile.options,
Expand Down

0 comments on commit 002f0c0

Please sign in to comment.