From 953038631156dcbe4ec40d3b1fc4e76c4bf03d9a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 16 Mar 2017 12:54:26 -0700 Subject: [PATCH 01/11] Add dependency on vue-template-compiler Probably it should replace the space-replacing code for individual regions with a one-shot parsing that gets looked up for each mode as updateCurrentTextDocument is called. --- server/npm-shrinkwrap.json | 15 +++++++++++++++ server/package.json | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/server/npm-shrinkwrap.json b/server/npm-shrinkwrap.json index d477915b36..4120390b64 100644 --- a/server/npm-shrinkwrap.json +++ b/server/npm-shrinkwrap.json @@ -2,6 +2,16 @@ "name": "vscode-html-languageserver", "version": "1.0.0", "dependencies": { + "de-indent": { + "version": "1.0.2", + "from": "de-indent@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz" + }, + "he": { + "version": "1.1.1", + "from": "he@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz" + }, "@types/node": { "version": "6.0.65", "from": "@types/node@>=6.0.54 <7.0.0", @@ -123,6 +133,11 @@ "version": "1.0.0", "from": "vscode-uri@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.0.tgz" + }, + "vue-template-compiler": { + "version": "2.2.1", + "from": "vue-template-compiler@latest", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.2.1.tgz" } } } diff --git a/server/package.json b/server/package.json index 63d1ac085e..5b2780d412 100644 --- a/server/package.json +++ b/server/package.json @@ -11,7 +11,8 @@ "vetur-vls": "^0.2.0", "vscode-css-languageservice": "^2.0.0", "vscode-languageserver": "^3.0.5", - "vscode-uri": "^1.0.0" + "vscode-uri": "^1.0.0", + "vue-template-compiler": "^2.2.1" }, "devDependencies": { "@types/node": "^6.0.54", From 980a918eb78d96488e0e8ec429ead31e8c386b37 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 16 Mar 2017 12:56:12 -0700 Subject: [PATCH 02/11] Add vue.d.ts support and typescript support 1. Add an implicit `import Vue from 'vue'` and `new Vue(...)` around the default exported object literal. This provides a lot better intellisense than just a bare object literal because the Typescript language server knows that the object will be used by Vue. 2. Resolve other .vue files so that `import other from './other.vue'` provides correct completions. 3. If lang='typescript' is specified, put the TS language service in TS mode: all errors are reported and completions include only symbols that are known to be correct. 4. Also fix a bug in htmlServerMain so that errors are actually reported. --- server/src/htmlServerMain.ts | 4 +- server/src/modes/javascriptMode.ts | 131 +++++++++++++++++++++-------- server/src/modes/languageModes.ts | 5 +- server/src/modes/typescriptMode.ts | 76 +++++++++++++++++ 4 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 server/src/modes/typescriptMode.ts diff --git a/server/src/htmlServerMain.ts b/server/src/htmlServerMain.ts index 70f65f40a6..40b84c07df 100644 --- a/server/src/htmlServerMain.ts +++ b/server/src/htmlServerMain.ts @@ -40,7 +40,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { workspacePath = params.rootPath; - languageModes = getLanguageModes(); + languageModes = getLanguageModes(workspacePath); documents.onDidClose(e => { languageModes.onDocumentRemoved(e.document); }); @@ -147,7 +147,7 @@ function triggerValidation(textDocument: TextDocument): void { function validateTextDocument(textDocument: TextDocument): void { let diagnostics: Diagnostic[] = []; - if (textDocument.languageId === 'vue-html') { + if (textDocument.languageId === 'vue') { languageModes.getAllModesInDocument(textDocument).forEach(mode => { if (mode.doValidation && validation[mode.getId()]) { pushAll(diagnostics, mode.doValidation(textDocument)); diff --git a/server/src/modes/javascriptMode.ts b/server/src/modes/javascriptMode.ts index 21970c9707..056e96996e 100644 --- a/server/src/modes/javascriptMode.ts +++ b/server/src/modes/javascriptMode.ts @@ -1,46 +1,86 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; import { SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions } from 'vscode-languageserver-types'; import { LanguageMode } from './languageModes'; -import { getWordAtText, startsWith, isWhitespaceOnly, repeat } from '../utils/strings'; +import { getWordAtText, isWhitespaceOnly, repeat } from '../utils/strings'; import { HTMLDocumentRegions } from './embeddedSupport'; +import path = require('path'); +import url = require('url'); -import * as ts from 'typescript'; -import { join } from 'path'; +import { createUpdater, parseVue, isVue } from './typescriptMode'; -const FILE_NAME = 'vscode://javascript/1'; // the same 'file' is used for all contents -const JQUERY_D_TS = join(__dirname, '../../lib/jquery.d.ts'); +import * as ts from 'typescript'; const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; -export function getJavascriptMode(documentRegions: LanguageModelCache): LanguageMode { - let jsDocuments = getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument('javascript')); +export function getJavascriptMode(documentRegions: LanguageModelCache, workspacePath: string): LanguageMode { + let jsDocuments = getLanguageModelCache(10, 60, document => { + const vueDocument = documentRegions.get(document); + if (vueDocument.getLanguagesInDocument().indexOf('typescript') > -1) { + return vueDocument.getEmbeddedDocument('typescript'); + } + return vueDocument.getEmbeddedDocument('javascript'); + }); let compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic }; let currentTextDocument: TextDocument; - let scriptFileVersion: number = 0; + let versions: ts.MapLike = {}; + let docs: ts.MapLike = {}; function updateCurrentTextDocument(doc: TextDocument) { if (!currentTextDocument || doc.uri !== currentTextDocument.uri || doc.version !== currentTextDocument.version) { currentTextDocument = jsDocuments.get(doc); - scriptFileVersion++; + const fileName = trimFileUri(currentTextDocument.uri); + if (docs[fileName] && currentTextDocument.languageId !== docs[fileName].languageId) { + // if languageId changed, we must restart the language service; it can't handle file type changes + jsLanguageService = ts.createLanguageService(host); + } + docs[fileName] = currentTextDocument; + versions[fileName] = (versions[fileName] || 0) + 1; } } - let host = { + + // Patch typescript functions to insert `import Vue from 'vue'` and `new Vue` around export default. + const { createLanguageServiceSourceFile, updateLanguageServiceSourceFile } = createUpdater(); + (ts as any).createLanguageServiceSourceFile = createLanguageServiceSourceFile; + (ts as any).updateLanguageServiceSourceFile = updateLanguageServiceSourceFile; + const configFile = ts.findConfigFile(workspacePath, ts.sys.fileExists, 'tsconfig.json') || + ts.findConfigFile(workspacePath, ts.sys.fileExists, 'jsconfig.json'); + const files = ts.parseJsonConfigFileContent({}, + ts.sys, + workspacePath, + compilerOptions, + configFile, + undefined, + [{ extension: 'vue', isMixedContent: true }]).fileNames; + + let host: ts.LanguageServiceHost = { getCompilationSettings: () => compilerOptions, - getScriptFileNames: () => [FILE_NAME, JQUERY_D_TS], - getScriptVersion: (fileName: string) => { - if (fileName === FILE_NAME) { - return String(scriptFileVersion); + getScriptFileNames: () => files, + getScriptVersion: filename => filename in versions ? versions[filename].toString() : '1', + getScriptKind(fileName) { + if(isVue(fileName) && docs[fileName]) { + return docs[fileName].languageId === 'typescript' ? ts.ScriptKind.TS : ts.ScriptKind.JS; } - return '1'; // default lib an jquery.d.ts are static + else { + return (ts as any).getScriptKindFromFileName(fileName); + } + }, + resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModule[] { + // in the normal case, delegate to ts.resolveModuleName + // in the relative-imported.vue case, manually build a resolved filename + return moduleNames.map(name => + path.isAbsolute(name) || !isVue(name) ? + ts.resolveModuleName(name, containingFile, compilerOptions, ts.sys).resolvedModule : + { + resolvedFileName: path.join(path.dirname(containingFile), name), + extension: docs[name] && docs[name].languageId === 'typescript' ? ts.Extension.Ts : ts.Extension.Js, + }) }, getScriptSnapshot: (fileName: string) => { - let text = ''; - if (startsWith(fileName, 'vscode:')) { - if (fileName === FILE_NAME) { - text = currentTextDocument.getText(); - } - } else { - text = ts.sys.readFile(fileName) || ''; + let text = fileName in docs ? docs[fileName].getText() : (ts.sys.readFile(fileName) || ''); + if (isVue(fileName)) { + // Note: This is required in addition to the parsing in embeddedSupport because + // this works for .vue files that aren't even loaded by VS Code yet. + text = parseVue(text); } return { getText: (start, end) => text.substring(start, end), @@ -48,9 +88,10 @@ export function getJavascriptMode(documentRegions: LanguageModelCache void 0 }; }, - getCurrentDirectory: () => '', - getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options) + getCurrentDirectory: () => workspacePath, + getDefaultLibFileName: ts.getDefaultLibFilePath, }; + let jsLanguageService = ts.createLanguageService(host); let settings: any = {}; @@ -64,8 +105,11 @@ export function getJavascriptMode(documentRegions: LanguageModelCache { + const filename = trimFileUri(document.uri); + const diagnostics = [...jsLanguageService.getSyntacticDiagnostics(filename), + ...jsLanguageService.getSemanticDiagnostics(filename)]; + + return diagnostics.map(diag => { return { range: convertRange(currentTextDocument, diag), severity: DiagnosticSeverity.Error, @@ -75,8 +119,9 @@ export function getJavascriptMode(documentRegions: LanguageModelCache { return { @@ -174,7 +223,8 @@ export function getJavascriptMode(documentRegions: LanguageModelCache d.fileName === FILE_NAME).map(d => { + return definition.map(d => { return { uri: document.uri, range: convertRange(currentTextDocument, d.textSpan) @@ -223,9 +274,10 @@ export function getJavascriptMode(documentRegions: LanguageModelCache d.fileName === FILE_NAME).map(d => { + return references.map(d => { return { uri: document.uri, range: convertRange(currentTextDocument, d.textSpan) @@ -245,7 +297,8 @@ export function getJavascriptMode(documentRegions: LanguageModelCache(10, 60, document => getDocumentRegions(vls, document)); @@ -63,8 +63,9 @@ export function getLanguageModes(): LanguageModes { css: getCSSMode(vls, documentRegions), scss: getSCSSMode(vls, documentRegions), less: getLESSMode(vls, documentRegions), - javascript: getJavascriptMode(documentRegions) + javascript: getJavascriptMode(documentRegions, workspacePath) }; + modes['typescript'] = modes.javascript; return { getModeAtPosition(document: TextDocument, position: Position): LanguageMode { diff --git a/server/src/modes/typescriptMode.ts b/server/src/modes/typescriptMode.ts new file mode 100644 index 0000000000..d6fcae4a10 --- /dev/null +++ b/server/src/modes/typescriptMode.ts @@ -0,0 +1,76 @@ +import * as ts from 'typescript'; +import path = require('path'); +import { parseComponent } from "vue-template-compiler"; + +export function isVue(filename: string): boolean { + return path.extname(filename) === '.vue'; +} + +export function parseVue(text: string): string { + const output = parseComponent(text, { pad: 'space' }); + if (output && output.script && output.script.content) { + return output.script.content; + } + else { + return text; + } +} + +export function createUpdater() { + const clssf = ts.createLanguageServiceSourceFile; + const ulssf = ts.updateLanguageServiceSourceFile; + return { + createLanguageServiceSourceFile(fileName: string, scriptSnapshot: ts.IScriptSnapshot, scriptTarget: ts.ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ts.ScriptKind): ts.SourceFile { + let sourceFile = clssf(fileName, scriptSnapshot, scriptTarget, version, setNodeParents, scriptKind); + if (isVue(fileName)) { + modifyVueSource(sourceFile); + } + return sourceFile; + }, + updateLanguageServiceSourceFile(sourceFile: ts.SourceFile, scriptSnapshot: ts.IScriptSnapshot, version: string, textChangeRange: ts.TextChangeRange, aggressiveChecks?: boolean): ts.SourceFile { + sourceFile = ulssf(sourceFile, scriptSnapshot, version, textChangeRange, aggressiveChecks); + if (isVue(sourceFile.fileName)) { + modifyVueSource(sourceFile); + } + return sourceFile; + } + } +} + +/** Works like Array.prototype.find, returning `undefined` if no element satisfying the predicate is found. */ +function find(array: T[], predicate: (element: T, index: number) => boolean): T | undefined { + for (let i = 0; i < array.length; i++) { + const value = array[i]; + if (predicate(value, i)) { + return value; + } + } + return undefined; +} + +function modifyVueSource(sourceFile: ts.SourceFile): void { + const exportDefaultObject = find(sourceFile.statements, st => st.kind === ts.SyntaxKind.ExportAssignment && + (st as ts.ExportAssignment).expression.kind === ts.SyntaxKind.ObjectLiteralExpression); + if (exportDefaultObject) { + // 1. add `import Vue from './vue' + // (the span of the statement is (0,0) to avoid overlapping existing statements) + const zero = (n: T) => ts.setTextRange(n, { pos: 0, end: 0 }); + const vueImport = zero(ts.createImportDeclaration(undefined, + undefined, + zero(ts.createImportClause(undefined, + zero(ts.createNamedImports([ + zero(ts.createImportSpecifier( + zero(ts.createIdentifier('Vue')), + zero(ts.createIdentifier('Vue'))))])))), + zero(ts.createLiteral('vue')))); + sourceFile.statements.unshift(vueImport); + + // 2. find the export default and wrap it in `new Vue(...)` if it exists and is an object literal + // (the span of the construct call is the same as the object literal) + const objectLiteral = (exportDefaultObject as ts.ExportAssignment).expression as ts.ObjectLiteralExpression; + const o = (n: T) => ts.setTextRange(n, objectLiteral); + const vue = ts.setTextRange(ts.createIdentifier('Vue'), { pos: objectLiteral.pos, end: objectLiteral.pos + 1 }); + (exportDefaultObject as ts.ExportAssignment).expression = o(ts.createNew(vue, undefined, [objectLiteral])); + o(((exportDefaultObject as ts.ExportAssignment).expression as ts.NewExpression).arguments); + } +} \ No newline at end of file From ed82b9b9acccd431dbfc4d790c999b6664a1e963 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 17 Mar 2017 08:52:14 -0700 Subject: [PATCH 03/11] Improve names and explanation of range-setting code It's required to make sure that getXXXAtLocation works correctly, because if the ranges are missing from the synthetic nodes, the language service asserts, and if the ranges are wrong, the language service's AST search gets confused. --- server/src/modes/typescriptMode.ts | 32 ++++++++++++++++++------------ 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/server/src/modes/typescriptMode.ts b/server/src/modes/typescriptMode.ts index d6fcae4a10..267e53cbaf 100644 --- a/server/src/modes/typescriptMode.ts +++ b/server/src/modes/typescriptMode.ts @@ -53,24 +53,30 @@ function modifyVueSource(sourceFile: ts.SourceFile): void { (st as ts.ExportAssignment).expression.kind === ts.SyntaxKind.ObjectLiteralExpression); if (exportDefaultObject) { // 1. add `import Vue from './vue' - // (the span of the statement is (0,0) to avoid overlapping existing statements) - const zero = (n: T) => ts.setTextRange(n, { pos: 0, end: 0 }); - const vueImport = zero(ts.createImportDeclaration(undefined, + // (the span of the inserted statement must be (0,0) to avoid overlapping existing statements) + const setZeroPos = getWrapperRangeSetter({ pos: 0, end: 0 }); + const vueImport = setZeroPos(ts.createImportDeclaration(undefined, undefined, - zero(ts.createImportClause(undefined, - zero(ts.createNamedImports([ - zero(ts.createImportSpecifier( - zero(ts.createIdentifier('Vue')), - zero(ts.createIdentifier('Vue'))))])))), - zero(ts.createLiteral('vue')))); + setZeroPos(ts.createImportClause(undefined, + setZeroPos(ts.createNamedImports([ + setZeroPos(ts.createImportSpecifier( + setZeroPos(ts.createIdentifier('Vue')), + setZeroPos(ts.createIdentifier('Vue'))))])))), + setZeroPos(ts.createLiteral('vue')))); sourceFile.statements.unshift(vueImport); // 2. find the export default and wrap it in `new Vue(...)` if it exists and is an object literal - // (the span of the construct call is the same as the object literal) + // (the span of the wrapping construct call and *all* its members must be the same as the object literal it wraps) const objectLiteral = (exportDefaultObject as ts.ExportAssignment).expression as ts.ObjectLiteralExpression; - const o = (n: T) => ts.setTextRange(n, objectLiteral); + const setObjPos = getWrapperRangeSetter(objectLiteral); const vue = ts.setTextRange(ts.createIdentifier('Vue'), { pos: objectLiteral.pos, end: objectLiteral.pos + 1 }); - (exportDefaultObject as ts.ExportAssignment).expression = o(ts.createNew(vue, undefined, [objectLiteral])); - o(((exportDefaultObject as ts.ExportAssignment).expression as ts.NewExpression).arguments); + (exportDefaultObject as ts.ExportAssignment).expression = setObjPos(ts.createNew(vue, undefined, [objectLiteral])); + setObjPos(((exportDefaultObject as ts.ExportAssignment).expression as ts.NewExpression).arguments); } +} + +/** Create a function that calls setTextRange on synthetic wrapper nodes that need a valid range */ +function getWrapperRangeSetter(wrapped: ts.TextRange): (wrapperNode: T) => T { + return (wrapperNode: T) => ts.setTextRange(wrapperNode, wrapped); + } \ No newline at end of file From ad75fa6b8dae47563ff312537dc4d0f5fc34e981 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 17 Mar 2017 09:00:22 -0700 Subject: [PATCH 04/11] Remove missed merge marker --- server/src/modes/javascriptMode.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/src/modes/javascriptMode.ts b/server/src/modes/javascriptMode.ts index 87e06baadc..056e96996e 100644 --- a/server/src/modes/javascriptMode.ts +++ b/server/src/modes/javascriptMode.ts @@ -8,11 +8,7 @@ import url = require('url'); import { createUpdater, parseVue, isVue } from './typescriptMode'; -<<<<<<< HEAD import * as ts from 'typescript'; -======= -const FILE_NAME = 'vscode://javascript/1'; // the same 'file' is used for all contents ->>>>>>> master const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; From 164e998e6b6f2f401f84ece83da5db75c52ff093 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 17 Mar 2017 09:52:18 -0700 Subject: [PATCH 05/11] Fix vue module resolution --- server/src/modes/javascriptMode.ts | 9 ++++++++- server/src/modes/typescriptMode.ts | 6 +----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/server/src/modes/javascriptMode.ts b/server/src/modes/javascriptMode.ts index 056e96996e..9c6c0baa4e 100644 --- a/server/src/modes/javascriptMode.ts +++ b/server/src/modes/javascriptMode.ts @@ -21,7 +21,14 @@ export function getJavascriptMode(documentRegions: LanguageModelCache = {}; let docs: ts.MapLike = {}; diff --git a/server/src/modes/typescriptMode.ts b/server/src/modes/typescriptMode.ts index 267e53cbaf..3b775578ff 100644 --- a/server/src/modes/typescriptMode.ts +++ b/server/src/modes/typescriptMode.ts @@ -57,11 +57,7 @@ function modifyVueSource(sourceFile: ts.SourceFile): void { const setZeroPos = getWrapperRangeSetter({ pos: 0, end: 0 }); const vueImport = setZeroPos(ts.createImportDeclaration(undefined, undefined, - setZeroPos(ts.createImportClause(undefined, - setZeroPos(ts.createNamedImports([ - setZeroPos(ts.createImportSpecifier( - setZeroPos(ts.createIdentifier('Vue')), - setZeroPos(ts.createIdentifier('Vue'))))])))), + setZeroPos(ts.createImportClause(ts.createIdentifier('Vue'), undefined)), setZeroPos(ts.createLiteral('vue')))); sourceFile.statements.unshift(vueImport); From 33f3ba7064297414a2a05e1d307aafab81758590 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 17 Mar 2017 10:47:34 -0700 Subject: [PATCH 06/11] Switch to ES6 maps --- server/src/modes/javascriptMode.ts | 20 ++++++++++---------- server/tsconfig.json | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/server/src/modes/javascriptMode.ts b/server/src/modes/javascriptMode.ts index 9c6c0baa4e..11e0199365 100644 --- a/server/src/modes/javascriptMode.ts +++ b/server/src/modes/javascriptMode.ts @@ -30,18 +30,18 @@ export function getJavascriptMode(documentRegions: LanguageModelCache = {}; - let docs: ts.MapLike = {}; + let versions = new Map(); + let docs = new Map(); function updateCurrentTextDocument(doc: TextDocument) { if (!currentTextDocument || doc.uri !== currentTextDocument.uri || doc.version !== currentTextDocument.version) { currentTextDocument = jsDocuments.get(doc); const fileName = trimFileUri(currentTextDocument.uri); - if (docs[fileName] && currentTextDocument.languageId !== docs[fileName].languageId) { + if (docs.has(fileName) && currentTextDocument.languageId !== docs.get(fileName).languageId) { // if languageId changed, we must restart the language service; it can't handle file type changes jsLanguageService = ts.createLanguageService(host); } - docs[fileName] = currentTextDocument; - versions[fileName] = (versions[fileName] || 0) + 1; + docs.set(fileName, currentTextDocument); + versions.set(fileName, (versions.get(fileName) || 0) + 1); } } @@ -62,10 +62,10 @@ export function getJavascriptMode(documentRegions: LanguageModelCache compilerOptions, getScriptFileNames: () => files, - getScriptVersion: filename => filename in versions ? versions[filename].toString() : '1', + getScriptVersion: filename => versions.has(filename) ? versions.get(filename).toString() : '1', getScriptKind(fileName) { - if(isVue(fileName) && docs[fileName]) { - return docs[fileName].languageId === 'typescript' ? ts.ScriptKind.TS : ts.ScriptKind.JS; + if(isVue(fileName) && docs.has(fileName)) { + return docs.get(fileName).languageId === 'typescript' ? ts.ScriptKind.TS : ts.ScriptKind.JS; } else { return (ts as any).getScriptKindFromFileName(fileName); @@ -79,11 +79,11 @@ export function getJavascriptMode(documentRegions: LanguageModelCache { - let text = fileName in docs ? docs[fileName].getText() : (ts.sys.readFile(fileName) || ''); + let text = docs.has(fileName) ? docs.get(fileName).getText() : (ts.sys.readFile(fileName) || ''); if (isVue(fileName)) { // Note: This is required in addition to the parsing in embeddedSupport because // this works for .vue files that aren't even loaded by VS Code yet. diff --git a/server/tsconfig.json b/server/tsconfig.json index 47549f2a12..e26e637b50 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -5,7 +5,7 @@ "outDir": "../client/server", "sourceMap": true, "lib": [ - "es5", "es2015.promise", "dom" + "es5", "es2015.collection", "es2015.promise", "dom" ] } } \ No newline at end of file From c99fab775de3d287d07881aa2bec81ff37279e85 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 17 Mar 2017 11:07:38 -0700 Subject: [PATCH 07/11] Use built-in config as default+make it correct --- server/src/modes/javascriptMode.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/src/modes/javascriptMode.ts b/server/src/modes/javascriptMode.ts index 11e0199365..38fbecfe1e 100644 --- a/server/src/modes/javascriptMode.ts +++ b/server/src/modes/javascriptMode.ts @@ -24,9 +24,10 @@ export function getJavascriptMode(documentRegions: LanguageModelCache compilerOptions, @@ -68,6 +74,7 @@ export function getJavascriptMode(documentRegions: LanguageModelCache Date: Fri, 17 Mar 2017 11:49:43 -0700 Subject: [PATCH 08/11] Correctly read contents of js/tsconfig --- server/src/modes/javascriptMode.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/src/modes/javascriptMode.ts b/server/src/modes/javascriptMode.ts index 38fbecfe1e..711a6c5361 100644 --- a/server/src/modes/javascriptMode.ts +++ b/server/src/modes/javascriptMode.ts @@ -52,13 +52,14 @@ export function getJavascriptMode(documentRegions: LanguageModelCache Date: Fri, 17 Mar 2017 20:44:54 -0700 Subject: [PATCH 09/11] Register unseen vue files when TSLS requests them The Typescript language service references files that are not open for editing, and thus don't have entries in the language model cache. Since they don't have entries, there is no way to know whether their script block is Javascript or Typescript. The fix is to load these files and register them with the language model cache. --- server/src/modes/javascriptMode.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/server/src/modes/javascriptMode.ts b/server/src/modes/javascriptMode.ts index 711a6c5361..bfd0d4aab1 100644 --- a/server/src/modes/javascriptMode.ts +++ b/server/src/modes/javascriptMode.ts @@ -69,10 +69,12 @@ export function getJavascriptMode(documentRegions: LanguageModelCache compilerOptions, getScriptFileNames: () => files, - getScriptVersion: filename => versions.has(filename) ? versions.get(filename).toString() : '1', + getScriptVersion: filename => versions.has(filename) ? versions.get(filename).toString() : '0', getScriptKind(fileName) { - if(isVue(fileName) && docs.has(fileName)) { - return docs.get(fileName).languageId === 'typescript' ? ts.ScriptKind.TS : ts.ScriptKind.JS; + if(isVue(fileName)) { + const doc = docs.get(fileName) || + jsDocuments.get(TextDocument.create('file://' + fileName, 'vue', 0, ts.sys.readFile(fileName))); + return doc.languageId === 'typescript' ? ts.ScriptKind.TS : ts.ScriptKind.JS; } else { // NOTE: Typescript 2.3 should export getScriptKindFromFileName. Then this cast should be removed. @@ -82,13 +84,20 @@ export function getJavascriptMode(documentRegions: LanguageModelCache - path.isAbsolute(name) || !isVue(name) ? - ts.resolveModuleName(name, containingFile, compilerOptions, ts.sys).resolvedModule : - { - resolvedFileName: path.join(path.dirname(containingFile), name), - extension: docs.has(name) && docs.get(name).languageId === 'typescript' ? ts.Extension.Ts : ts.Extension.Js, - }) + return moduleNames.map(name => { + if (path.isAbsolute(name) || !isVue(name)) { + return ts.resolveModuleName(name, containingFile, compilerOptions, ts.sys).resolvedModule; + } + else { + const resolvedFileName = path.join(path.dirname(containingFile), name); + const doc = docs.get(resolvedFileName) || + jsDocuments.get(TextDocument.create('file://' + name, 'vue', 0, ts.sys.readFile(resolvedFileName))); + return { + resolvedFileName, + extension: doc.languageId === 'typescript' ? ts.Extension.Ts : ts.Extension.Js, + }; + } + }); }, getScriptSnapshot: (fileName: string) => { let text = docs.has(fileName) ? docs.get(fileName).getText() : (ts.sys.readFile(fileName) || ''); From a5a3387d185fa61be9458b81785da30870d36603 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 20 Mar 2017 10:02:29 -0700 Subject: [PATCH 10/11] Use vscode-uri instead of url to parse URIs --- server/src/modes/javascriptMode.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/src/modes/javascriptMode.ts b/server/src/modes/javascriptMode.ts index bfd0d4aab1..9722a11b05 100644 --- a/server/src/modes/javascriptMode.ts +++ b/server/src/modes/javascriptMode.ts @@ -3,11 +3,10 @@ import { SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, import { LanguageMode } from './languageModes'; import { getWordAtText, isWhitespaceOnly, repeat } from '../utils/strings'; import { HTMLDocumentRegions } from './embeddedSupport'; -import path = require('path'); -import url = require('url'); - import { createUpdater, parseVue, isVue } from './typescriptMode'; +import Uri from 'vscode-uri'; +import path = require('path'); import * as ts from 'typescript'; const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; @@ -354,7 +353,7 @@ export function getJavascriptMode(documentRegions: LanguageModelCache Date: Mon, 20 Mar 2017 11:47:57 -0700 Subject: [PATCH 11/11] Correctly normalise slashes on Windows TS server and VS Code normalise them in different ways. Standardise by passing everything through vscode-uri --- server/src/modes/javascriptMode.ts | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/server/src/modes/javascriptMode.ts b/server/src/modes/javascriptMode.ts index bfd0d4aab1..32f7983106 100644 --- a/server/src/modes/javascriptMode.ts +++ b/server/src/modes/javascriptMode.ts @@ -3,8 +3,8 @@ import { SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, import { LanguageMode } from './languageModes'; import { getWordAtText, isWhitespaceOnly, repeat } from '../utils/strings'; import { HTMLDocumentRegions } from './embeddedSupport'; +import Uri from 'vscode-uri'; import path = require('path'); -import url = require('url'); import { createUpdater, parseVue, isVue } from './typescriptMode'; @@ -36,7 +36,7 @@ export function getJavascriptMode(documentRegions: LanguageModelCache compilerOptions, getScriptFileNames: () => files, - getScriptVersion: filename => versions.has(filename) ? versions.get(filename).toString() : '0', + getScriptVersion(filename) { + filename = normalizeFileName(filename); + return versions.has(filename) ? versions.get(filename).toString() : '0'; + }, getScriptKind(fileName) { if(isVue(fileName)) { + const uri = Uri.file(fileName); + fileName = uri.fsPath; const doc = docs.get(fileName) || - jsDocuments.get(TextDocument.create('file://' + fileName, 'vue', 0, ts.sys.readFile(fileName))); + jsDocuments.get(TextDocument.create(uri.toString(), 'vue', 0, ts.sys.readFile(fileName))); return doc.languageId === 'typescript' ? ts.ScriptKind.TS : ts.ScriptKind.JS; } else { @@ -89,9 +94,10 @@ export function getJavascriptMode(documentRegions: LanguageModelCache { + fileName = normalizeFileName(fileName); let text = docs.has(fileName) ? docs.get(fileName).getText() : (ts.sys.readFile(fileName) || ''); if (isVue(fileName)) { // Note: This is required in addition to the parsing in embeddedSupport because @@ -354,7 +361,11 @@ export function getJavascriptMode(documentRegions: LanguageModelCache