diff --git a/src/compiler/program.ts b/src/compiler/program.ts index a4c3a65f29c5d..cb12e6da4d5db 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -10,6 +10,22 @@ module ts { /** The version of the TypeScript compiler release */ export let version = "1.5.0.0"; + export function findConfigFile(searchPath: string): string { + var fileName = "tsconfig.json"; + while (true) { + if (sys.fileExists(fileName)) { + return fileName; + } + var parentPath = getDirectoryPath(searchPath); + if (parentPath === searchPath) { + break; + } + searchPath = parentPath; + fileName = "../" + fileName; + } + return undefined; + } + export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { let currentDirectory: string; let existingDirectories: Map = {}; diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 3f82d6c8cf509..a0d21106f4357 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -132,23 +132,6 @@ module ts { return typeof JSON === "object" && typeof JSON.parse === "function"; } - function findConfigFile(): string { - var searchPath = normalizePath(sys.getCurrentDirectory()); - var fileName = "tsconfig.json"; - while (true) { - if (sys.fileExists(fileName)) { - return fileName; - } - var parentPath = getDirectoryPath(searchPath); - if (parentPath === searchPath) { - break; - } - searchPath = parentPath; - fileName = "../" + fileName; - } - return undefined; - } - export function executeCommandLine(args: string[]): void { var commandLine = parseCommandLine(args); var configFileName: string; // Configuration file name (if any) @@ -198,7 +181,8 @@ module ts { } } else if (commandLine.fileNames.length === 0 && isJSONSupported()) { - configFileName = findConfigFile(); + var searchPath = normalizePath(sys.getCurrentDirectory()); + configFileName = findConfigFile(searchPath); } if (commandLine.fileNames.length === 0 && !configFileName) { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 1f9df9b46f03c..034549ab464fe 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -272,14 +272,24 @@ module ts.server { export class Project { compilerService: CompilerService; - projectOptions: ProjectOptions; projectFilename: string; program: ts.Program; filenameToSourceFile: ts.Map = {}; updateGraphSeq = 0; + /** Used for configured projects which may have multiple open roots */ + openRefCount = 0; - constructor(public projectService: ProjectService) { - this.compilerService = new CompilerService(this); + constructor(public projectService: ProjectService, public projectOptions?: ProjectOptions) { + this.compilerService = new CompilerService(this,projectOptions && projectOptions.compilerOptions); + } + + addOpenRef() { + this.openRefCount++; + } + + deleteOpenRef() { + this.openRefCount--; + return this.openRefCount; } openReferencedFile(filename: string) { @@ -299,6 +309,10 @@ module ts.server { } } + isRoot(info: ScriptInfo) { + return this.compilerService.host.roots.some(root => root === info); + } + removeReferencedFile(info: ScriptInfo) { this.compilerService.host.removeReferencedFile(info); this.updateGraph(); @@ -375,11 +389,16 @@ module ts.server { export class ProjectService { filenameToScriptInfo: ts.Map = {}; - // open, non-configured files in two lists + // open, non-configured root files openFileRoots: ScriptInfo[] = []; - openFilesReferenced: ScriptInfo[] = []; - // projects covering open files + // projects built from openFileRoots inferredProjects: Project[] = []; + // projects specified by a tsconfig.json file + configuredProjects: Project[] = []; + // open files referenced by a project + openFilesReferenced: ScriptInfo[] = []; + // open files that are roots of a configured project + openFileRootsConfigured: ScriptInfo[] = []; hostConfiguration: HostConfiguration; constructor(public host: ServerHost, public psLogger: Logger, public eventHandler?: ProjectServiceEventHandler) { @@ -484,44 +503,77 @@ module ts.server { this.printProjects(); } + updateConfiguredProjectList() { + var configuredProjects: Project[] = []; + for (var i = 0, len = this.configuredProjects.length; i < len; i++) { + if (this.configuredProjects[i].openRefCount > 0) { + configuredProjects.push(this.configuredProjects[i]); + } + } + this.configuredProjects = configuredProjects; + } + + setConfiguredProjectRoot(info: ScriptInfo) { + for (var i = 0, len = this.configuredProjects.length; i < len; i++) { + let configuredProject = this.configuredProjects[i]; + if (configuredProject.isRoot(info)) { + info.defaultProject = configuredProject; + configuredProject.addOpenRef(); + return true; + } + } + return false; + } + addOpenFile(info: ScriptInfo) { - this.findReferencingProjects(info); - if (info.defaultProject) { - this.openFilesReferenced.push(info); + if (this.setConfiguredProjectRoot(info)) { + this.openFileRootsConfigured.push(info); } else { - // create new inferred project p with the newly opened file as root - info.defaultProject = this.createInferredProject(info); - var openFileRoots: ScriptInfo[] = []; - // for each inferred project root r - for (var i = 0, len = this.openFileRoots.length; i < len; i++) { - var r = this.openFileRoots[i]; - // if r referenced by the new project - if (info.defaultProject.getSourceFile(r)) { - // remove project rooted at r - this.inferredProjects = - copyListRemovingItem(r.defaultProject, this.inferredProjects); - // put r in referenced open file list - this.openFilesReferenced.push(r); - // set default project of r to the new project - r.defaultProject = info.defaultProject; - } - else { - // otherwise, keep r as root of inferred project - openFileRoots.push(r); + this.findReferencingProjects(info); + if (info.defaultProject) { + this.openFilesReferenced.push(info); + } + else { + // create new inferred project p with the newly opened file as root + info.defaultProject = this.createInferredProject(info); + var openFileRoots: ScriptInfo[] = []; + // for each inferred project root r + for (var i = 0, len = this.openFileRoots.length; i < len; i++) { + var r = this.openFileRoots[i]; + // if r referenced by the new project + if (info.defaultProject.getSourceFile(r)) { + // remove project rooted at r + this.inferredProjects = + copyListRemovingItem(r.defaultProject, this.inferredProjects); + // put r in referenced open file list + this.openFilesReferenced.push(r); + // set default project of r to the new project + r.defaultProject = info.defaultProject; + } + else { + // otherwise, keep r as root of inferred project + openFileRoots.push(r); + } } + this.openFileRoots = openFileRoots; + this.openFileRoots.push(info); } - this.openFileRoots = openFileRoots; - this.openFileRoots.push(info); } + this.updateConfiguredProjectList(); } + /** + * Remove this file from the set of open, non-configured files. + * @param info The file that has been closed or newly configured + * @param openedByConfig True if info has become a root of a configured project + */ closeOpenFile(info: ScriptInfo) { var openFileRoots: ScriptInfo[] = []; var removedProject: Project; for (var i = 0, len = this.openFileRoots.length; i < len; i++) { // if closed file is root of project - if (info == this.openFileRoots[i]) { + if (info === this.openFileRoots[i]) { // remove that project and remember it removedProject = info.defaultProject; } @@ -530,9 +582,29 @@ module ts.server { } } this.openFileRoots = openFileRoots; + if (!removedProject) { + var openFileRootsConfigured: ScriptInfo[] = []; + + for (var i = 0, len = this.openFileRootsConfigured.length; i < len; i++) { + if (info === this.openFileRootsConfigured[i]) { + if (info.defaultProject.deleteOpenRef() === 0) { + removedProject = info.defaultProject; + } + } + else { + openFileRootsConfigured.push(this.openFileRootsConfigured[i]); + } + } + + this.openFileRootsConfigured = openFileRootsConfigured; + } if (removedProject) { - // remove project from inferred projects list - this.inferredProjects = copyListRemovingItem(removedProject, this.inferredProjects); + if (removedProject.isConfiguredProject()) { + this.configuredProjects = copyListRemovingItem(removedProject, this.configuredProjects); + } + else { + this.inferredProjects = copyListRemovingItem(removedProject, this.inferredProjects); + } var openFilesReferenced: ScriptInfo[] = []; var orphanFiles: ScriptInfo[] = []; // for all open, referenced files f @@ -564,14 +636,22 @@ module ts.server { var referencingProjects: Project[] = []; info.defaultProject = undefined; for (var i = 0, len = this.inferredProjects.length; i < len; i++) { - this.inferredProjects[i].updateGraph(); - if (this.inferredProjects[i] != excludedProject) { - if (this.inferredProjects[i].getSourceFile(info)) { - info.defaultProject = this.inferredProjects[i]; - referencingProjects.push(this.inferredProjects[i]); + var inferredProject = this.inferredProjects[i]; + inferredProject.updateGraph(); + if (inferredProject != excludedProject) { + if (inferredProject.getSourceFile(info)) { + info.defaultProject = inferredProject; + referencingProjects.push(inferredProject); } } } + for (var i = 0, len = this.configuredProjects.length; i < len; i++) { + var configuredProject = this.configuredProjects[i]; + configuredProject.updateGraph(); + if (configuredProject.getSourceFile(info)) { + info.defaultProject = configuredProject; + } + } return referencingProjects; } @@ -676,9 +756,23 @@ module ts.server { * @param filename is absolute pathname */ - openClientFile(filename: string) { - // TODO: tsconfig check - var info = this.openFile(filename, true); + openClientFile(fileName: string) { + var searchPath = ts.normalizePath(getDirectoryPath(fileName)); + var configFileName = ts.findConfigFile(searchPath); + if (configFileName) { + configFileName = getAbsolutePath(configFileName, searchPath); + } + if (configFileName && (!this.configProjectIsActive(configFileName))) { + var configResult = this.openConfigFile(configFileName, fileName); + if (!configResult.success) { + this.log("Error opening config file " + configFileName + " " + configResult.errorMsg); + } + else { + this.log("Opened configuration file " + configFileName,"Info"); + this.configuredProjects.push(configResult.project); + } + } + var info = this.openFile(fileName, true); this.addOpenFile(info); this.printProjects(); return info; @@ -699,19 +793,6 @@ module ts.server { this.printProjects(); } - getProjectsReferencingFile(filename: string) { - var scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename); - if (scriptInfo) { - var projects: Project[] = []; - for (var i = 0, len = this.inferredProjects.length; i < len; i++) { - if (this.inferredProjects[i].getSourceFile(scriptInfo)) { - projects.push(this.inferredProjects[i]); - } - } - return projects; - } - } - getProjectForFile(filename: string) { var scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename); if (scriptInfo) { @@ -724,9 +805,9 @@ module ts.server { if (scriptInfo) { this.psLogger.startGroup(); this.psLogger.info("Projects for " + filename) - var projects = this.getProjectsReferencingFile(filename); + var projects = this.findReferencingProjects(scriptInfo); for (var i = 0, len = projects.length; i < len; i++) { - this.psLogger.info("Inferred Project " + i.toString()); + this.psLogger.info("Project " + i.toString()); } this.psLogger.endGroup(); } @@ -744,18 +825,42 @@ module ts.server { this.psLogger.info(project.filesToString()); this.psLogger.info("-----------------------------------------------"); } - this.psLogger.info("Open file roots: ") + for (var i = 0, len = this.configuredProjects.length; i < len; i++) { + var project = this.configuredProjects[i]; + project.updateGraph(); + this.psLogger.info("Project (configured) " + (i+this.inferredProjects.length).toString()); + this.psLogger.info(project.filesToString()); + this.psLogger.info("-----------------------------------------------"); + } + this.psLogger.info("Open file roots of inferred projects: ") for (var i = 0, len = this.openFileRoots.length; i < len; i++) { this.psLogger.info(this.openFileRoots[i].fileName); } - this.psLogger.info("Open files referenced: ") + this.psLogger.info("Open files referenced by inferred or configured projects: ") for (var i = 0, len = this.openFilesReferenced.length; i < len; i++) { - this.psLogger.info(this.openFilesReferenced[i].fileName); + var fileInfo = this.openFilesReferenced[i].fileName; + if (this.openFilesReferenced[i].defaultProject.isConfiguredProject()) { + fileInfo += " (configured)"; + } + this.psLogger.info(fileInfo); + } + this.psLogger.info("Open file roots of configured projects: ") + for (var i = 0, len = this.openFileRootsConfigured.length; i < len; i++) { + this.psLogger.info(this.openFileRootsConfigured[i].fileName); } this.psLogger.endGroup(); } - openConfigFile(configFilename: string): ProjectOpenResult { + configProjectIsActive(fileName: string) { + for (var i = 0, len = this.configuredProjects.length; i < len; i++) { + if (this.configuredProjects[i].projectFilename == fileName) { + return true; + } + } + return false; + } + + openConfigFile(configFilename: string, clientFileName?: string): ProjectOpenResult { configFilename = ts.normalizePath(configFilename); // file references will be relative to dirPath (or absolute) var dirPath = ts.getDirectoryPath(configFilename); @@ -764,33 +869,27 @@ module ts.server { return { errorMsg: "tsconfig syntax error" }; } else { - // REVIEW: specify no base path so can get absolute path below - var parsedCommandLine = ts.parseConfigFile(rawConfig); - - if (parsedCommandLine.errors) { - // TODO: gather diagnostics and transmit + var parsedCommandLine = ts.parseConfigFile(rawConfig, dirPath); + if (parsedCommandLine.errors && (parsedCommandLine.errors.length > 0)) { return { errorMsg: "tsconfig option errors" }; } else if (parsedCommandLine.fileNames) { - var proj = this.createProject(configFilename); + var projectOptions: ProjectOptions = { + files: parsedCommandLine.fileNames, + compilerOptions: parsedCommandLine.options + }; + var proj = this.createProject(configFilename, projectOptions); for (var i = 0, len = parsedCommandLine.fileNames.length; i < len; i++) { var rootFilename = parsedCommandLine.fileNames[i]; - var normRootFilename = ts.normalizePath(rootFilename); - normRootFilename = getAbsolutePath(normRootFilename, dirPath); - if (this.host.fileExists(normRootFilename)) { - // TODO: pass true for file exiplicitly opened - var info = this.openFile(normRootFilename, false); + if (ts.sys.fileExists(rootFilename)) { + var info = this.openFile(rootFilename, clientFileName == rootFilename); proj.addRoot(info); } else { return { errorMsg: "specified file " + rootFilename + " not found" }; } } - var projectOptions: ProjectOptions = { - files: parsedCommandLine.fileNames, - compilerOptions: parsedCommandLine.options - }; - proj.setProjectOptions(projectOptions); + proj.finishGraph(); return { success: true, project: proj }; } else { @@ -799,10 +898,10 @@ module ts.server { } } - createProject(projectFilename: string) { - var eproj = new Project(this); - eproj.projectFilename = projectFilename; - return eproj; + createProject(projectFilename: string, projectOptions?: ProjectOptions) { + var project = new Project(this, projectOptions); + project.projectFilename = projectFilename; + return project; } } @@ -811,14 +910,17 @@ module ts.server { host: LSHost; languageService: ts.LanguageService; classifier: ts.Classifier; - settings = ts.getDefaultCompilerOptions(); + settings: ts.CompilerOptions; documentRegistry = ts.createDocumentRegistry(); - constructor(public project: Project) { + constructor(public project: Project, opt?: ts.CompilerOptions) { this.host = new LSHost(project.projectService.host, project); - // override default ES6 (remove when compiler default back at ES5) - this.settings.target = ts.ScriptTarget.ES5; - this.host.setCompilationSettings(this.settings); + if (opt) { + this.setCompilerOptions(opt); + } + else { + this.setCompilerOptions(ts.getDefaultCompilerOptions()); + } this.languageService = ts.createLanguageService(this.host, this.documentRegistry); this.classifier = ts.createClassifier(); } diff --git a/src/server/session.ts b/src/server/session.ts index 79eefb879da43..47ef40a880977 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -550,11 +550,11 @@ module ts.server { } return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => { - if (completions.isMemberCompletion || entry.name.indexOf(prefix) == 0) { + if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) == 0)) { result.push(entry); } return result; - }, []); + }, []).sort((a, b) => a.name.localeCompare(b.name)); } getCompletionEntryDetails(line: number, offset: number, diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json new file mode 100644 index 0000000000000..2c8538c61e32a --- /dev/null +++ b/src/server/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitAny": true, + "removeComments": true, + "preserveConstEnums": true, + "out": "../../built/local/tsserver.js", + "sourceMap": true + }, + "files": [ + "node.d.ts", + "editorServices.ts", + "protocol.d.ts", + "server.ts", + "session.ts" + ] +} diff --git a/tests/baselines/reference/APISample_compile.js b/tests/baselines/reference/APISample_compile.js index 0fd72e8015824..0810e27cfa541 100644 --- a/tests/baselines/reference/APISample_compile.js +++ b/tests/baselines/reference/APISample_compile.js @@ -1475,6 +1475,7 @@ declare module "typescript" { declare module "typescript" { /** The version of the TypeScript compiler release */ let version: string; + function findConfigFile(searchPath: string): string; function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; function getPreEmitDiagnostics(program: Program): Diagnostic[]; function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string; diff --git a/tests/baselines/reference/APISample_compile.types b/tests/baselines/reference/APISample_compile.types index 28763a896ddfe..ecdce6e34156b 100644 --- a/tests/baselines/reference/APISample_compile.types +++ b/tests/baselines/reference/APISample_compile.types @@ -4736,6 +4736,10 @@ declare module "typescript" { let version: string; >version : string + function findConfigFile(searchPath: string): string; +>findConfigFile : (searchPath: string) => string +>searchPath : string + function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; >createCompilerHost : (options: CompilerOptions, setParentNodes?: boolean) => CompilerHost >options : CompilerOptions diff --git a/tests/baselines/reference/APISample_linter.js b/tests/baselines/reference/APISample_linter.js index a947821455de8..36fef7edf47f1 100644 --- a/tests/baselines/reference/APISample_linter.js +++ b/tests/baselines/reference/APISample_linter.js @@ -1506,6 +1506,7 @@ declare module "typescript" { declare module "typescript" { /** The version of the TypeScript compiler release */ let version: string; + function findConfigFile(searchPath: string): string; function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; function getPreEmitDiagnostics(program: Program): Diagnostic[]; function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string; diff --git a/tests/baselines/reference/APISample_linter.types b/tests/baselines/reference/APISample_linter.types index 34dc65aa351d1..ee26ac6fe79de 100644 --- a/tests/baselines/reference/APISample_linter.types +++ b/tests/baselines/reference/APISample_linter.types @@ -4882,6 +4882,10 @@ declare module "typescript" { let version: string; >version : string + function findConfigFile(searchPath: string): string; +>findConfigFile : (searchPath: string) => string +>searchPath : string + function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; >createCompilerHost : (options: CompilerOptions, setParentNodes?: boolean) => CompilerHost >options : CompilerOptions diff --git a/tests/baselines/reference/APISample_transform.js b/tests/baselines/reference/APISample_transform.js index 00c2c63ac1031..5fd12e2605148 100644 --- a/tests/baselines/reference/APISample_transform.js +++ b/tests/baselines/reference/APISample_transform.js @@ -1507,6 +1507,7 @@ declare module "typescript" { declare module "typescript" { /** The version of the TypeScript compiler release */ let version: string; + function findConfigFile(searchPath: string): string; function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; function getPreEmitDiagnostics(program: Program): Diagnostic[]; function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string; diff --git a/tests/baselines/reference/APISample_transform.types b/tests/baselines/reference/APISample_transform.types index c5b769888dbb3..04462edf7fbb9 100644 --- a/tests/baselines/reference/APISample_transform.types +++ b/tests/baselines/reference/APISample_transform.types @@ -4832,6 +4832,10 @@ declare module "typescript" { let version: string; >version : string + function findConfigFile(searchPath: string): string; +>findConfigFile : (searchPath: string) => string +>searchPath : string + function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; >createCompilerHost : (options: CompilerOptions, setParentNodes?: boolean) => CompilerHost >options : CompilerOptions diff --git a/tests/baselines/reference/APISample_watcher.js b/tests/baselines/reference/APISample_watcher.js index 91039f112b7f3..f5ce2567447b0 100644 --- a/tests/baselines/reference/APISample_watcher.js +++ b/tests/baselines/reference/APISample_watcher.js @@ -1544,6 +1544,7 @@ declare module "typescript" { declare module "typescript" { /** The version of the TypeScript compiler release */ let version: string; + function findConfigFile(searchPath: string): string; function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; function getPreEmitDiagnostics(program: Program): Diagnostic[]; function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string; diff --git a/tests/baselines/reference/APISample_watcher.types b/tests/baselines/reference/APISample_watcher.types index 09322aaa77135..0ee78d1aa32e5 100644 --- a/tests/baselines/reference/APISample_watcher.types +++ b/tests/baselines/reference/APISample_watcher.types @@ -5005,6 +5005,10 @@ declare module "typescript" { let version: string; >version : string + function findConfigFile(searchPath: string): string; +>findConfigFile : (searchPath: string) => string +>searchPath : string + function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; >createCompilerHost : (options: CompilerOptions, setParentNodes?: boolean) => CompilerHost >options : CompilerOptions