Skip to content

Commit

Permalink
Merge pull request #94 from sandersn/typescript-vue-dts-support
Browse files Browse the repository at this point in the history
Add vue.d.ts and typescript support
  • Loading branch information
octref authored Mar 21, 2017
2 parents 532ba8c + 5680067 commit b405d4a
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 42 deletions.
15 changes: 15 additions & 0 deletions server/npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"vetur-vls": "^0.2.5",
"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",
Expand Down
2 changes: 1 addition & 1 deletion server/src/htmlServerMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {

workspacePath = params.rootPath;

languageModes = getLanguageModes();
languageModes = getLanguageModes(workspacePath);
documents.onDidClose(e => {
languageModes.onDocumentRemoved(e.document);
});
Expand Down
166 changes: 129 additions & 37 deletions server/src/modes/javascriptMode.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,127 @@
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 { createUpdater, parseVue, isVue } from './typescriptMode';

import Uri from 'vscode-uri';
import path = require('path');
import * as ts from 'typescript';
import { join } from 'path';

const FILE_NAME = 'vscode://javascript/1'; // the same 'file' is used for all contents

const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;

export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>): LanguageMode {
let jsDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument('javascript'));
export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>, workspacePath: string): LanguageMode {
let jsDocuments = getLanguageModelCache<TextDocument>(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 compilerOptions: ts.CompilerOptions = {
allowNonTsExtensions: true,
allowJs: true,
lib: ['lib.dom.d.ts', 'lib.es2017.d.ts'],
target: ts.ScriptTarget.Latest,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
module: ts.ModuleKind.CommonJS,
allowSyntheticDefaultImports: true
};
let currentTextDocument: TextDocument;
let scriptFileVersion: number = 0;
let versions = new Map<string, number>();
let docs = new Map<string, TextDocument>();
function updateCurrentTextDocument(doc: TextDocument) {
if (!currentTextDocument || doc.uri !== currentTextDocument.uri || doc.version !== currentTextDocument.version) {
currentTextDocument = jsDocuments.get(doc);
scriptFileVersion++;
const fileName = trimFileUri(doc.uri);
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
compilerOptions.allowJs = docs.get(fileName).languageId !== 'typescript';
jsLanguageService = ts.createLanguageService(host);
}
docs.set(fileName, currentTextDocument);
versions.set(fileName, (versions.get(fileName) || 0) + 1);
}
}
let host = {

// Patch typescript functions to insert `import Vue from 'vue'` and `new Vue` around export default.
// NOTE: Typescript 2.3 should add an API to allow this, and then this code should use that API.
const { createLanguageServiceSourceFile, updateLanguageServiceSourceFile } = createUpdater();
(ts as any).createLanguageServiceSourceFile = createLanguageServiceSourceFile;
(ts as any).updateLanguageServiceSourceFile = updateLanguageServiceSourceFile;
const configFilename = ts.findConfigFile(workspacePath, ts.sys.fileExists, 'tsconfig.json') ||
ts.findConfigFile(workspacePath, ts.sys.fileExists, 'jsconfig.json');
const configJson = configFilename && ts.readConfigFile(configFilename, ts.sys.readFile).config || {};
const parsedConfig = ts.parseJsonConfigFileContent(configJson,
ts.sys,
workspacePath,
compilerOptions,
configFilename,
undefined,
[{ extension: 'vue', isMixedContent: true }]);
const files = parsedConfig.fileNames;
compilerOptions = parsedConfig.options;
compilerOptions.allowNonTsExtensions = true;

let host: ts.LanguageServiceHost = {
getCompilationSettings: () => compilerOptions,
getScriptFileNames: () => [FILE_NAME],
getScriptVersion: (fileName: string) => {
if (fileName === FILE_NAME) {
return String(scriptFileVersion);
getScriptFileNames: () => files,
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(uri.toString(), 'vue', 0, ts.sys.readFile(fileName)));
return doc.languageId === 'typescript' ? ts.ScriptKind.TS : ts.ScriptKind.JS;
}
return '1'; // default lib an jquery.d.ts are static
else {
// NOTE: Typescript 2.3 should export getScriptKindFromFileName. Then this cast should be removed.
return (ts as any).getScriptKindFromFileName(fileName);
}
},
getScriptSnapshot: (fileName: string) => {
let text = '';
if (startsWith(fileName, 'vscode:')) {
if (fileName === FILE_NAME) {
text = currentTextDocument.getText();
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 => {
if (path.isAbsolute(name) || !isVue(name)) {
return ts.resolveModuleName(name, containingFile, compilerOptions, ts.sys).resolvedModule;
}
} else {
text = ts.sys.readFile(fileName) || '';
else {
const uri = Uri.file(path.join(path.dirname(containingFile), name));
const resolvedFileName = uri.fsPath;
const doc = docs.get(resolvedFileName) ||
jsDocuments.get(TextDocument.create(uri.toString(), 'vue', 0, ts.sys.readFile(resolvedFileName)));
return {
resolvedFileName,
extension: doc.languageId === 'typescript' ? ts.Extension.Ts : ts.Extension.Js,
};
}
});
},
getScriptSnapshot: (fileName: string) => {
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
// 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),
getLength: () => text.length,
getChangeRange: () => void 0
};
},
getCurrentDirectory: () => '',
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options)
getCurrentDirectory: () => workspacePath,
getDefaultLibFileName: ts.getDefaultLibFilePath,
};

let jsLanguageService = ts.createLanguageService(host);

let settings: any = {};
Expand All @@ -63,8 +135,11 @@ export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
doValidation(document: TextDocument): Diagnostic[] {
updateCurrentTextDocument(document);
const diagnostics = jsLanguageService.getSyntacticDiagnostics(FILE_NAME);
return diagnostics.map((diag): Diagnostic => {
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,
Expand All @@ -74,8 +149,9 @@ export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
doComplete(document: TextDocument, position: Position): CompletionList {
updateCurrentTextDocument(document);
let filename = trimFileUri(document.uri);
let offset = currentTextDocument.offsetAt(position);
let completions = jsLanguageService.getCompletionsAtPosition(FILE_NAME, offset);
let completions = jsLanguageService.getCompletionsAtPosition(filename, offset);
if (!completions) {
return { isIncomplete: false, items: [] };
}
Expand All @@ -101,7 +177,8 @@ export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
doResolve(document: TextDocument, item: CompletionItem): CompletionItem {
updateCurrentTextDocument(document);
let details = jsLanguageService.getCompletionEntryDetails(FILE_NAME, item.data.offset, item.label);
let filename = trimFileUri(document.uri);
let details = jsLanguageService.getCompletionEntryDetails(filename, item.data.offset, item.label);
if (details) {
item.detail = ts.displayPartsToString(details.displayParts);
item.documentation = ts.displayPartsToString(details.documentation);
Expand All @@ -111,7 +188,8 @@ export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
doHover(document: TextDocument, position: Position): Hover {
updateCurrentTextDocument(document);
let info = jsLanguageService.getQuickInfoAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
let filename = trimFileUri(document.uri);
let info = jsLanguageService.getQuickInfoAtPosition(filename, currentTextDocument.offsetAt(position));
if (info) {
let contents = ts.displayPartsToString(info.displayParts);
return {
Expand All @@ -123,7 +201,8 @@ export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
doSignatureHelp(document: TextDocument, position: Position): SignatureHelp {
updateCurrentTextDocument(document);
let signHelp = jsLanguageService.getSignatureHelpItems(FILE_NAME, currentTextDocument.offsetAt(position));
let filename = trimFileUri(document.uri);
let signHelp = jsLanguageService.getSignatureHelpItems(filename, currentTextDocument.offsetAt(position));
if (signHelp) {
let ret: SignatureHelp = {
activeSignature: signHelp.selectedItemIndex,
Expand Down Expand Up @@ -160,7 +239,8 @@ export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
findDocumentHighlight(document: TextDocument, position: Position): DocumentHighlight[] {
updateCurrentTextDocument(document);
let occurrences = jsLanguageService.getOccurrencesAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
let filename = trimFileUri(document.uri);
let occurrences = jsLanguageService.getOccurrencesAtPosition(filename, currentTextDocument.offsetAt(position));
if (occurrences) {
return occurrences.map(entry => {
return {
Expand All @@ -173,7 +253,8 @@ export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
findDocumentSymbols(document: TextDocument): SymbolInformation[] {
updateCurrentTextDocument(document);
let items = jsLanguageService.getNavigationBarItems(FILE_NAME);
let filename = trimFileUri(document.uri);
let items = jsLanguageService.getNavigationBarItems(filename);
if (items) {
let result: SymbolInformation[] = [];
let existing = {};
Expand Down Expand Up @@ -209,9 +290,10 @@ export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
findDefinition(document: TextDocument, position: Position): Definition {
updateCurrentTextDocument(document);
let definition = jsLanguageService.getDefinitionAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
let filename = trimFileUri(document.uri);
let definition = jsLanguageService.getDefinitionAtPosition(filename, currentTextDocument.offsetAt(position));
if (definition) {
return definition.filter(d => d.fileName === FILE_NAME).map(d => {
return definition.map(d => {
return {
uri: document.uri,
range: convertRange(currentTextDocument, d.textSpan)
Expand All @@ -222,9 +304,10 @@ export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
findReferences(document: TextDocument, position: Position): Location[] {
updateCurrentTextDocument(document);
let references = jsLanguageService.getReferencesAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
let filename = trimFileUri(document.uri);
let references = jsLanguageService.getReferencesAtPosition(filename, currentTextDocument.offsetAt(position));
if (references) {
return references.filter(d => d.fileName === FILE_NAME).map(d => {
return references.map(d => {
return {
uri: document.uri,
range: convertRange(currentTextDocument, d.textSpan)
Expand All @@ -244,7 +327,8 @@ export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocume
end -= range.end.character;
lastLineRange = Range.create(Position.create(range.end.line, 0), range.end);
}
let edits = jsLanguageService.getFormattingEditsForRange(FILE_NAME, start, end, formatSettings);
let filename = trimFileUri(document.uri);
let edits = jsLanguageService.getFormattingEditsForRange(filename, start, end, formatSettings);
if (edits) {
let result = [];
for (let edit of edits) {
Expand Down Expand Up @@ -275,6 +359,14 @@ export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocume
};
};

function trimFileUri(uri: string): string {
return Uri.parse(uri).fsPath;
}

function normalizeFileName(fileName: string): string {
return Uri.file(fileName).fsPath;
}

function convertRange(document: TextDocument, span: { start: number, length: number }): Range {
let startPosition = document.positionAt(span.start);
let endPosition = document.positionAt(span.start + span.length);
Expand Down
5 changes: 3 additions & 2 deletions server/src/modes/languageModes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export interface LanguageModeRange extends Range {
attributeValue?: boolean;
}

export function getLanguageModes(): LanguageModes {
export function getLanguageModes(workspacePath: string): LanguageModes {

var vls = getVls();
let documentRegions = getLanguageModelCache<HTMLDocumentRegions>(10, 60, document => getDocumentRegions(vls, document));
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit b405d4a

Please sign in to comment.