diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index d48ba94f2e..177c1f9fea 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -85,7 +85,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi else { // targetSourceFile is specified (e.g calling emitter from language service or calling getSemanticDiagnostic from language service) if (shouldEmitToOwnFile(targetSourceFile, compilerOptions)) { - let jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, forEach(host.getSourceFiles(), shouldEmitJsx) ? ".jsx" : ".js"); + let jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, shouldEmitJsx(targetSourceFile) ? ".jsx" : ".js"); emitFile(jsFilePath, targetSourceFile); } else if (!isDeclarationFile(targetSourceFile) && (compilerOptions.outFile || compilerOptions.out)) { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 4b93dc3428..bd1cbc1c6e 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -129,13 +129,14 @@ module FourSlash { sourceRoot: "sourceRoot", allowNonTsExtensions: "allowNonTsExtensions", resolveReference: "ResolveReference", // This flag is used to specify entry file for resolve file references. The flag is only allow once per test file + jsx: "jsx", }; // List of allowed metadata names let fileMetadataNames = [metadataOptionNames.fileName, metadataOptionNames.emitThisFile, metadataOptionNames.resolveReference]; let globalMetadataNames = [metadataOptionNames.allowNonTsExtensions, metadataOptionNames.baselineFile, metadataOptionNames.declaration, - metadataOptionNames.mapRoot, metadataOptionNames.module, metadataOptionNames.out, metadataOptionNames.outFile, - metadataOptionNames.outDir, metadataOptionNames.sourceMap, metadataOptionNames.sourceRoot]; + metadataOptionNames.mapRoot, metadataOptionNames.module, metadataOptionNames.out,metadataOptionNames.outFile, + metadataOptionNames.outDir, metadataOptionNames.sourceMap, metadataOptionNames.sourceRoot, metadataOptionNames.jsx]; function convertGlobalOptionsToCompilerOptions(globalOptions: { [idx: string]: string }): ts.CompilerOptions { let settings: ts.CompilerOptions = { target: ts.ScriptTarget.ES5 }; @@ -182,6 +183,20 @@ module FourSlash { case metadataOptionNames.sourceRoot: settings.sourceRoot = globalOptions[prop]; break; + case metadataOptionNames.jsx: + switch (globalOptions[prop].toLowerCase()) { + case "react": + settings.jsx = ts.JsxEmit.React; + break; + case "preserve": + settings.jsx = ts.JsxEmit.Preserve; + break; + default: + ts.Debug.assert(globalOptions[prop] === undefined || globalOptions[prop] === "None"); + settings.jsx = ts.JsxEmit.None; + break; + } + break; } } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 14bb7712ce..b9f76db4c9 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -224,6 +224,14 @@ namespace ts.server { this.roots.push(info); } } + + removeRoot(info: ScriptInfo) { + var scriptInfo = ts.lookUp(this.filenameToScript, info.fileName); + if (scriptInfo) { + this.filenameToScript[info.fileName] = undefined; + this.roots = copyListRemovingItem(info, this.roots); + } + } saveTo(filename: string, tmpfilename: string) { var script = this.getScriptInfo(filename); @@ -345,6 +353,7 @@ namespace ts.server { export class Project { compilerService: CompilerService; projectFilename: string; + projectFileWatcher: FileWatcher; program: ts.Program; filenameToSourceFile: ts.Map = {}; updateGraphSeq = 0; @@ -352,7 +361,7 @@ namespace ts.server { openRefCount = 0; constructor(public projectService: ProjectService, public projectOptions?: ProjectOptions) { - this.compilerService = new CompilerService(this,projectOptions && projectOptions.compilerOptions); + this.compilerService = new CompilerService(this, projectOptions && projectOptions.compilerOptions); } addOpenRef() { @@ -423,6 +432,12 @@ namespace ts.server { info.defaultProject = this; this.compilerService.host.addRoot(info); } + + // remove a root file from project + removeRoot(info: ScriptInfo) { + info.defaultProject = undefined; + this.compilerService.host.removeRoot(info); + } filesToString() { var strBuilder = ""; @@ -517,6 +532,12 @@ namespace ts.server { } } + watchedProjectConfigFileChanged(project: Project) { + this.log("Config File Changed: " + project.projectFilename); + this.updateConfiguredProject(project); + this.updateProjectStructure(); + } + log(msg: string, type = "Err") { this.psLogger.msg(msg, type); } @@ -593,6 +614,19 @@ namespace ts.server { } this.configuredProjects = configuredProjects; } + + removeConfiguredProject(project: Project) { + project.projectFileWatcher.close(); + this.configuredProjects = copyListRemovingItem(project, this.configuredProjects); + + let fileNames = project.getFileNames(); + for (let fileName of fileNames) { + let info = this.getScriptInfo(fileName); + if (info.defaultProject == project){ + info.defaultProject = undefined; + } + } + } setConfiguredProjectRoot(info: ScriptInfo) { for (var i = 0, len = this.configuredProjects.length; i < len; i++) { @@ -647,7 +681,6 @@ namespace ts.server { /** * 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[] = []; @@ -736,18 +769,42 @@ namespace ts.server { return referencingProjects; } + reloadProjects() { + // First check if there is new tsconfig file added for inferred project roots + for (let info of this.openFileRoots) { + this.openOrUpdateConfiguredProjectForFile(info.fileName); + } + this.updateProjectStructure(); + } + + /** + * This function is to update the project structure for every projects. + * It is called on the premise that all the configured projects are + * up to date. + */ updateProjectStructure() { this.log("updating project structure from ...", "Info"); this.printProjects(); + + let unattachedOpenFiles: ScriptInfo[] = []; + let openFileRootsConfigured: ScriptInfo[] = []; + for (let info of this.openFileRootsConfigured) { + let project = info.defaultProject; + if (!project || !(project.getSourceFile(info))) { + info.defaultProject = undefined; + unattachedOpenFiles.push(info); + } + else { + openFileRootsConfigured.push(info); + } + } + this.openFileRootsConfigured = openFileRootsConfigured; // First loop through all open files that are referenced by projects but are not // project roots. For each referenced file, see if the default project still // references that file. If so, then just keep the file in the referenced list. // If not, add the file to an unattached list, to be rechecked later. - var openFilesReferenced: ScriptInfo[] = []; - var unattachedOpenFiles: ScriptInfo[] = []; - for (var i = 0, len = this.openFilesReferenced.length; i < len; i++) { var referencedFile = this.openFilesReferenced[i]; referencedFile.defaultProject.updateGraph(); @@ -857,31 +914,39 @@ namespace ts.server { * Open file whose contents is managed by the client * @param filename is absolute pathname */ - openClientFile(fileName: string) { - var searchPath = ts.normalizePath(getDirectoryPath(fileName)); - this.log("Search path: " + searchPath,"Info"); - var configFileName = this.findConfigFile(searchPath); - if (configFileName) { - this.log("Config file name: " + configFileName, "Info"); - } else { - this.log("no config file"); - } - 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); - } - } + this.openOrUpdateConfiguredProjectForFile(fileName); var info = this.openFile(fileName, true); this.addOpenFile(info); this.printProjects(); return info; } + + openOrUpdateConfiguredProjectForFile(fileName: string) { + let searchPath = ts.normalizePath(getDirectoryPath(fileName)); + this.log("Search path: " + searchPath,"Info"); + let configFileName = this.findConfigFile(searchPath); + if (configFileName) { + this.log("Config file name: " + configFileName, "Info"); + let project = this.findConfiguredProjectByConfigFile(configFileName); + if (!project) { + 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); + } + } + else { + this.updateConfiguredProject(project); + } + } + else { + this.log("No config files found."); + } + } /** * Close file whose contents is managed by the client @@ -959,49 +1024,111 @@ namespace ts.server { } configProjectIsActive(fileName: string) { - for (var i = 0, len = this.configuredProjects.length; i < len; i++) { - if (this.configuredProjects[i].projectFilename == fileName) { - return true; - } - } - return false; + return this.findConfiguredProjectByConfigFile(fileName) === undefined; } - openConfigFile(configFilename: string, clientFileName?: string): ProjectOpenResult { + findConfiguredProjectByConfigFile(configFileName: string) { + for (var i = 0, len = this.configuredProjects.length; i < len; i++) { + if (this.configuredProjects[i].projectFilename == configFileName) { + return this.configuredProjects[i]; + } + } + return undefined; + } + + configFileToProjectOptions(configFilename: string): { succeeded: boolean, projectOptions?: ProjectOptions, error?: ProjectOpenResult } { configFilename = ts.normalizePath(configFilename); // 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); if (rawConfig.error) { - return rawConfig.error; + return { succeeded: false, error: rawConfig.error }; } else { var parsedCommandLine = ts.parseConfigFile(rawConfig.config, this.host, dirPath); if (parsedCommandLine.errors && (parsedCommandLine.errors.length > 0)) { - return { errorMsg: "tsconfig option errors" }; + return { succeeded: false, error: { errorMsg: "tsconfig option errors" }}; } - else if (parsedCommandLine.fileNames) { + else if (parsedCommandLine.fileNames == null) { + return { succeeded: false, error: { errorMsg: "no files found" }} + } + else { 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]; - if (this.host.fileExists(rootFilename)) { - var info = this.openFile(rootFilename, clientFileName == rootFilename); - proj.addRoot(info); - } - else { - return { errorMsg: "specified file " + rootFilename + " not found" }; - } + return { succeeded: true, projectOptions }; + } + } + + } + + openConfigFile(configFilename: string, clientFileName?: string): ProjectOpenResult { + let { succeeded, projectOptions, error } = this.configFileToProjectOptions(configFilename); + if (!succeeded) { + return error; + } + else { + let proj = this.createProject(configFilename, projectOptions); + for (let i = 0, len = projectOptions.files.length; i < len; i++) { + let rootFilename = projectOptions.files[i]; + if (this.host.fileExists(rootFilename)) { + let info = this.openFile(rootFilename, /*openedByClient*/ clientFileName == rootFilename); + proj.addRoot(info); } - proj.finishGraph(); - return { success: true, project: proj }; + else { + return { errorMsg: "specified file " + rootFilename + " not found" }; + } + } + proj.finishGraph(); + proj.projectFileWatcher = this.host.watchFile(configFilename, _ => this.watchedProjectConfigFileChanged(proj)); + return { success: true, project: proj }; + } + } + + updateConfiguredProject(project: Project) { + if (!this.host.fileExists(project.projectFilename)) { + this.log("Config file deleted"); + this.removeConfiguredProject(project); + } + else { + let { succeeded, projectOptions, error } = this.configFileToProjectOptions(project.projectFilename); + if (!succeeded) { + return error; } else { - return { errorMsg: "no files found" }; + let oldFileNames = project.compilerService.host.roots.map(info => info.fileName); + let newFileNames = projectOptions.files; + let fileNamesToRemove = oldFileNames.filter(f => newFileNames.indexOf(f) < 0); + let fileNamesToAdd = newFileNames.filter(f => oldFileNames.indexOf(f) < 0); + + for (let fileName of fileNamesToRemove) { + let info = this.getScriptInfo(fileName); + project.removeRoot(info); + } + + for (let fileName of fileNamesToAdd) { + let info = this.getScriptInfo(fileName); + if (!info) { + info = this.openFile(fileName, false); + } + else { + // if the root file was opened by client, it would belong to either + // openFileRoots or openFileReferenced. + if (this.openFileRoots.indexOf(info) >= 0) { + this.openFileRoots = copyListRemovingItem(info, this.openFileRoots); + } + if (this.openFilesReferenced.indexOf(info) >= 0) { + this.openFilesReferenced = copyListRemovingItem(info, this.openFilesReferenced); + } + this.openFileRootsConfigured.push(info); + } + project.addRoot(info); + } + + project.setProjectOptions(projectOptions); + project.finishGraph(); } } } diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 2d694025c8..ce918abda3 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -31,6 +31,12 @@ declare namespace ts.server.protocol { */ arguments?: any; } + + /** + * Request to reload the project structure for all the opened files + */ + export interface ReloadProjectsRequest extends Message { + } /** * Server-initiated event message diff --git a/src/server/session.ts b/src/server/session.ts index 108cf4726d..da044e7b4c 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -100,6 +100,7 @@ namespace ts.server { export const SignatureHelp = "signatureHelp"; export const TypeDefinition = "typeDefinition"; export const ProjectInfo = "projectInfo"; + export const ReloadProjects = "reloadProjects"; export const Unknown = "unknown"; } @@ -226,6 +227,10 @@ namespace ts.server { this.syntacticCheck(file, project); this.semanticCheck(file, project); } + + private reloadProjects() { + this.projectService.reloadProjects(); + } private updateProjectStructure(seq: number, matchSeq: (seq: number) => boolean, ms = 1500) { setTimeout(() => { @@ -1033,6 +1038,10 @@ namespace ts.server { var { file, needFileNameList } = request.arguments; return {response: this.getProjectInfo(file, needFileNameList), responseRequired: true}; }, + [CommandNames.ReloadProjects]: (request: protocol.ReloadProjectsRequest) => { + this.reloadProjects(); + return {responseRequired: false}; + } }; public addProtocolHandler(command: string, handler: (request: protocol.Request) => {response?: any, responseRequired: boolean}) { if (this.handlers[command]) { diff --git a/src/services/shims.ts b/src/services/shims.ts index e6ec71f368..307062cebd 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -1097,4 +1097,4 @@ module TypeScript.Services { } /* @internal */ -let toolsVersion = "1.5"; +const toolsVersion = "1.6"; diff --git a/tests/baselines/reference/getEmitOutputOutFile.baseline b/tests/baselines/reference/getEmitOutputOutFile.baseline new file mode 100644 index 0000000000..702097a7c0 --- /dev/null +++ b/tests/baselines/reference/getEmitOutputOutFile.baseline @@ -0,0 +1,26 @@ +EmitSkipped: false +FileName : outFile.js +var x = 5; +var Bar = (function () { + function Bar() { + } + return Bar; +})(); +var x1 = "hello world"; +var Foo = (function () { + function Foo() { + } + return Foo; +})(); +FileName : outFile.d.ts +declare var x: number; +declare class Bar { + x: string; + y: number; +} +declare var x1: string; +declare class Foo { + x: string; + y: number; +} + diff --git a/tests/baselines/reference/getEmitOutputTsxFile_Preserve.baseline b/tests/baselines/reference/getEmitOutputTsxFile_Preserve.baseline new file mode 100644 index 0000000000..2ae820dd53 --- /dev/null +++ b/tests/baselines/reference/getEmitOutputTsxFile_Preserve.baseline @@ -0,0 +1,26 @@ +EmitSkipped: false +FileName : tests/cases/fourslash/inputFile1.js.map +{"version":3,"file":"inputFile1.js","sourceRoot":"","sources":["inputFile1.ts"],"names":["Bar","Bar.constructor"],"mappings":"AAAA,kBAAkB;AACjB,IAAI,CAAC,GAAW,CAAC,CAAC;AAClB;IAAAA;IAGAC,CAACA;IAADD,UAACA;AAADA,CAACA,AAHD,IAGC"}FileName : tests/cases/fourslash/inputFile1.js +// regular ts file +var t = 5; +var Bar = (function () { + function Bar() { + } + return Bar; +})(); +//# sourceMappingURL=inputFile1.js.mapFileName : tests/cases/fourslash/inputFile1.d.ts +declare var t: number; +declare class Bar { + x: string; + y: number; +} + +EmitSkipped: false +FileName : tests/cases/fourslash/inputFile2.jsx.map +{"version":3,"file":"inputFile2.jsx","sourceRoot":"","sources":["inputFile2.tsx"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,QAAQ,CAAC;AACjB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,CAAC,CAAC,EAAG,CAAA"}FileName : tests/cases/fourslash/inputFile2.jsx +var y = "my div"; +var x =
; +//# sourceMappingURL=inputFile2.jsx.mapFileName : tests/cases/fourslash/inputFile2.d.ts +declare var y: string; +declare var x: any; + diff --git a/tests/baselines/reference/getEmitOutputTsxFile_React.baseline b/tests/baselines/reference/getEmitOutputTsxFile_React.baseline new file mode 100644 index 0000000000..c796f8e78b --- /dev/null +++ b/tests/baselines/reference/getEmitOutputTsxFile_React.baseline @@ -0,0 +1,26 @@ +EmitSkipped: false +FileName : tests/cases/fourslash/inputFile1.js.map +{"version":3,"file":"inputFile1.js","sourceRoot":"","sources":["inputFile1.ts"],"names":["Bar","Bar.constructor"],"mappings":"AAAA,kBAAkB;AACjB,IAAI,CAAC,GAAW,CAAC,CAAC;AAClB;IAAAA;IAGAC,CAACA;IAADD,UAACA;AAADA,CAACA,AAHD,IAGC"}FileName : tests/cases/fourslash/inputFile1.js +// regular ts file +var t = 5; +var Bar = (function () { + function Bar() { + } + return Bar; +})(); +//# sourceMappingURL=inputFile1.js.mapFileName : tests/cases/fourslash/inputFile1.d.ts +declare var t: number; +declare class Bar { + x: string; + y: number; +} + +EmitSkipped: false +FileName : tests/cases/fourslash/inputFile2.js.map +{"version":3,"file":"inputFile2.js","sourceRoot":"","sources":["inputFile2.tsx"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,QAAQ,CAAC;AACjB,IAAI,CAAC,GAAG,qBAAC,GAAG,KAAC,IAAI,GAAG,CAAE,EAAG,CAAA"}FileName : tests/cases/fourslash/inputFile2.js +var y = "my div"; +var x = React.createElement("div", {"name": y}); +//# sourceMappingURL=inputFile2.js.mapFileName : tests/cases/fourslash/inputFile2.d.ts +declare var y: string; +declare var x: any; + diff --git a/tests/cases/fourslash/getEmitOutputOutFile.ts b/tests/cases/fourslash/getEmitOutputOutFile.ts new file mode 100644 index 0000000000..4a9439737e --- /dev/null +++ b/tests/cases/fourslash/getEmitOutputOutFile.ts @@ -0,0 +1,22 @@ +/// + +// @BaselineFile: getEmitOutputOutFile.baseline +// @declaration: true +// @outFile: outFile.js + +// @Filename: inputFile1.ts +// @emitThisFile: true +//// var x: number = 5; +//// class Bar { +//// x : string; +//// y : number +//// } + +// @Filename: inputFile2.ts +//// var x1: string = "hello world"; +//// class Foo{ +//// x : string; +//// y : number; +//// } + +verify.baselineGetEmitOutput(); \ No newline at end of file diff --git a/tests/cases/fourslash/getEmitOutputTsxFile_Preserve.ts b/tests/cases/fourslash/getEmitOutputTsxFile_Preserve.ts new file mode 100644 index 0000000000..19eda2ad11 --- /dev/null +++ b/tests/cases/fourslash/getEmitOutputTsxFile_Preserve.ts @@ -0,0 +1,22 @@ +/// + +// @BaselineFile: getEmitOutputTsxFile_Preserve.baseline +// @declaration: true +// @sourceMap: true +// @jsx: preserve + +// @Filename: inputFile1.ts +// @emitThisFile: true +////// regular ts file +//// var t: number = 5; +//// class Bar { +//// x : string; +//// y : number +//// } + +// @Filename: inputFile2.tsx +// @emitThisFile: true +//// var y = "my div"; +//// var x =
+ +verify.baselineGetEmitOutput(); \ No newline at end of file diff --git a/tests/cases/fourslash/getEmitOutputTsxFile_React.ts b/tests/cases/fourslash/getEmitOutputTsxFile_React.ts new file mode 100644 index 0000000000..ccfa3bcc0c --- /dev/null +++ b/tests/cases/fourslash/getEmitOutputTsxFile_React.ts @@ -0,0 +1,22 @@ +/// + +// @BaselineFile: getEmitOutputTsxFile_React.baseline +// @declaration: true +// @sourceMap: true +// @jsx: react + +// @Filename: inputFile1.ts +// @emitThisFile: true +////// regular ts file +//// var t: number = 5; +//// class Bar { +//// x : string; +//// y : number +//// } + +// @Filename: inputFile2.tsx +// @emitThisFile: true +//// var y = "my div"; +//// var x =
+ +verify.baselineGetEmitOutput(); \ No newline at end of file