diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 6c85d8bcfc0..c6cf82a6834 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -362,14 +362,14 @@ namespace FourSlash { } // Opens a file given its 0-based index or fileName - public openFile(index: number, content?: string): void; - public openFile(name: string, content?: string): void; - public openFile(indexOrName: any, content?: string) { + public openFile(index: number, content?: string, scriptKindName?: string): void; + public openFile(name: string, content?: string, scriptKindName?: string): void; + public openFile(indexOrName: any, content?: string, scriptKindName?: string) { const fileToOpen: FourSlashFile = this.findFile(indexOrName); fileToOpen.fileName = ts.normalizeSlashes(fileToOpen.fileName); this.activeFile = fileToOpen; // Let the host know that this file is now open - this.languageServiceAdapterHost.openFile(fileToOpen.fileName, content); + this.languageServiceAdapterHost.openFile(fileToOpen.fileName, content, scriptKindName); } public verifyErrorExistsBetweenMarkers(startMarkerName: string, endMarkerName: string, negative: boolean) { @@ -2790,10 +2790,10 @@ namespace FourSlashInterface { // Opens a file, given either its index as it // appears in the test source, or its filename // as specified in the test metadata - public file(index: number, content?: string): void; - public file(name: string, content?: string): void; - public file(indexOrName: any, content?: string): void { - this.state.openFile(indexOrName, content); + public file(index: number, content?: string, scriptKindName?: string): void; + public file(name: string, content?: string, scriptKindName?: string): void; + public file(indexOrName: any, content?: string, scriptKindName?: string): void { + this.state.openFile(indexOrName, content, scriptKindName); } } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 7e0be67f5a4..8aaff65febc 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -163,7 +163,7 @@ namespace Harness.LanguageService { throw new Error("No script with name '" + fileName + "'"); } - public openFile(fileName: string, content?: string): void { + public openFile(fileName: string, content?: string, scriptKindName?: string): void { } /** @@ -528,9 +528,9 @@ namespace Harness.LanguageService { this.client = client; } - openFile(fileName: string, content?: string): void { - super.openFile(fileName, content); - this.client.openFile(fileName, content); + openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void { + super.openFile(fileName, content, scriptKindName); + this.client.openFile(fileName, content, scriptKindName); } editScript(fileName: string, start: number, end: number, newText: string) { diff --git a/src/server/client.ts b/src/server/client.ts index e8122b39055..caeab6e3f3c 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -120,8 +120,8 @@ namespace ts.server { return response; } - openFile(fileName: string, content?: string): void { - var args: protocol.OpenRequestArgs = { file: fileName, fileContent: content }; + openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void { + var args: protocol.OpenRequestArgs = { file: fileName, fileContent: content, scriptKindName }; this.processRequest(CommandNames.Open, args); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 4c054664a18..3c00e925321 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -34,6 +34,7 @@ namespace ts.server { fileWatcher: FileWatcher; formatCodeOptions = ts.clone(CompilerService.defaultFormatCodeOptions); path: Path; + scriptKind: ScriptKind; constructor(private host: ServerHost, public fileName: string, public content: string, public isOpen = false) { this.path = toPath(fileName, host.getCurrentDirectory(), createGetCanonicalFileName(host.useCaseSensitiveFileNames)); @@ -215,8 +216,16 @@ namespace ts.server { return this.roots.map(root => root.fileName); } - getScriptKind() { - return ScriptKind.Unknown; + getScriptKind(fileName: string) { + const info = this.getScriptInfo(fileName); + if (!info) { + return undefined; + } + + if (!info.scriptKind) { + info.scriptKind = getScriptKindFromFileName(fileName); + } + return info.scriptKind; } getScriptVersion(filename: string) { @@ -1013,7 +1022,7 @@ namespace ts.server { * @param filename is absolute pathname * @param fileContent is a known version of the file content that is more up to date than the one on disk */ - openFile(fileName: string, openedByClient: boolean, fileContent?: string) { + openFile(fileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) { fileName = ts.normalizePath(fileName); let info = ts.lookUp(this.filenameToScriptInfo, fileName); if (!info) { @@ -1028,6 +1037,7 @@ namespace ts.server { } if (content !== undefined) { info = new ScriptInfo(this.host, fileName, content, openedByClient); + info.scriptKind = scriptKind; info.setFormatOptions(this.getFormatCodeOptions()); this.filenameToScriptInfo[fileName] = info; if (!info.isOpen) { @@ -1077,9 +1087,9 @@ namespace ts.server { * @param filename is absolute pathname * @param fileContent is a known version of the file content that is more up to date than the one on disk */ - openClientFile(fileName: string, fileContent?: string) { + openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind) { this.openOrUpdateConfiguredProjectForFile(fileName); - const info = this.openFile(fileName, /*openedByClient*/ true, fileContent); + const info = this.openFile(fileName, /*openedByClient*/ true, fileContent, scriptKind); this.addOpenFile(info); this.printProjects(); return info; diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 5ed227ebaa7..10cebb2ddb7 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -518,6 +518,11 @@ declare namespace ts.server.protocol { * Then the known content will be used upon opening instead of the disk copy */ fileContent?: string; + /** + * Used to specify the script kind of the file explicitly. It could be one of the following: + * "TS", "JS", "TSX", "JSX" + */ + scriptKindName?: "TS" | "JS" | "TSX" | "JSX"; } /** diff --git a/src/server/session.ts b/src/server/session.ts index f0975a3f947..6fe8ed7b075 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -529,9 +529,9 @@ namespace ts.server { * @param fileName is the name of the file to be opened * @param fileContent is a version of the file content that is known to be more up to date than the one on disk */ - private openClientFile(fileName: string, fileContent?: string) { + private openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind) { const file = ts.normalizePath(fileName); - this.projectService.openClientFile(file, fileContent); + this.projectService.openClientFile(file, fileContent, scriptKind); } private getQuickInfo(line: number, offset: number, fileName: string): protocol.QuickInfoResponseBody { @@ -967,7 +967,22 @@ namespace ts.server { }, [CommandNames.Open]: (request: protocol.Request) => { const openArgs = request.arguments; - this.openClientFile(openArgs.file, openArgs.fileContent); + let scriptKind: ScriptKind; + switch (openArgs.scriptKindName) { + case "TS": + scriptKind = ScriptKind.TS; + break; + case "JS": + scriptKind = ScriptKind.JS; + break; + case "TSX": + scriptKind = ScriptKind.TSX; + break; + case "JSX": + scriptKind = ScriptKind.JSX; + break; + } + this.openClientFile(openArgs.file, openArgs.fileContent, scriptKind); return {responseRequired: false}; }, [CommandNames.Quickinfo]: (request: protocol.Request) => { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 6bc5e5c03e6..270c7d85d45 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -858,12 +858,15 @@ namespace ts { } export function getScriptKind(fileName: string, host?: LanguageServiceHost): ScriptKind { - // First check to see if the script kind can be determined from the file name - var scriptKind = getScriptKindFromFileName(fileName); - if (scriptKind === ScriptKind.Unknown && host && host.getScriptKind) { - // Next check to see if the host can resolve the script kind + // First check to see if the script kind was specified by the host. Chances are the host + // may override the default script kind for the file extension. + let scriptKind: ScriptKind; + if (host && host.getScriptKind) { scriptKind = host.getScriptKind(fileName); } + if (!scriptKind || scriptKind === ScriptKind.Unknown) { + scriptKind = getScriptKindFromFileName(fileName); + } return ensureScriptKind(fileName, scriptKind); } } \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 34d508d5c7e..48d69f0a85e 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -110,8 +110,8 @@ declare namespace FourSlashInterface { type(definitionIndex?: number): void; position(position: number, fileIndex?: number): any; position(position: number, fileName?: string): any; - file(index: number, content?: string): any; - file(name: string, content?: string): any; + file(index: number, content?: string, scriptKindName?: string): any; + file(name: string, content?: string, scriptKindName?: string): any; } class verifyNegatable { private negative; diff --git a/tests/cases/fourslash/server/openFileWithSyntaxKind.ts b/tests/cases/fourslash/server/openFileWithSyntaxKind.ts new file mode 100644 index 00000000000..dcb1e54999e --- /dev/null +++ b/tests/cases/fourslash/server/openFileWithSyntaxKind.ts @@ -0,0 +1,19 @@ +/// + +// Because the fourslash runner automatically opens the first file with the default setting, +// to test the openFile function, the targeted file cannot be the first one. + +// @Filename: dumbFile.ts +//// var x; + +// @allowJs: true +// @Filename: test.ts +//// /** +//// * @type {number} +//// */ +//// var t; +//// t. + +goTo.file("test.ts", /*content*/ undefined, "JS"); +goTo.eof(); +verify.completionListContains("toExponential");