Skip to content

Commit

Permalink
Merge pull request #681 from ktsn/template-type-checking
Browse files Browse the repository at this point in the history
Type checking for template expressions
  • Loading branch information
octref authored Apr 15, 2019
2 parents 6d9fe80 + ab4a5cc commit 782d5b6
Show file tree
Hide file tree
Showing 32 changed files with 1,397 additions and 202 deletions.
7 changes: 3 additions & 4 deletions client/vueMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ export function activate(context: vscode.ExtensionContext) {

context.subscriptions.push(
vscode.commands.registerCommand('vetur.chooseTypeScriptRefactoring', (args: any) => {
client.sendRequest<vscode.Command | undefined>('requestCodeActionEdits', args)
.then(command =>
command && vscode.commands.executeCommand(command.command, ...command.arguments!)
);
client
.sendRequest<vscode.Command | undefined>('requestCodeActionEdits', args)
.then(command => command && vscode.commands.executeCommand(command.command, ...command.arguments!));
})
);

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,11 @@
"vetur.dev.vlsPath": {
"type": "string",
"description": "Path to VLS for Vetur developers. There are two ways of using it. \n\n1. Clone vuejs/vetur from GitHub, build it and point it to the ABSOLUTE path of `/server`.\n2. `yarn global add vue-language-server` and point Vetur to the installed location (`yarn global dir` + node_modules/vue-language-server)"
},
"vetur.experimental.templateTypeCheck": {
"type": "boolean",
"default": true,
"description": "Type-check interpolation expressions in <template> region"
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,14 @@
"vscode-languageserver": "^5.3.0-next.4",
"vscode-languageserver-types": "^3.15.0-next.1",
"vscode-uri": "^1.0.1",
"vue-eslint-parser": "^6.0.3",
"vue-onsenui-helper-json": "^1.0.2",
"vuetify-helper-json": "^1.0.0"
},
"devDependencies": {
"@types/eslint": "^4.16.5",
"@types/eslint-scope": "^3.7.0",
"@types/eslint-visitor-keys": "^1.0.0",
"@types/glob": "^7.1.0",
"@types/js-beautify": "^1.8.0",
"@types/lodash": "^4.14.118",
Expand Down
33 changes: 28 additions & 5 deletions server/src/modes/script/bridge.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
// this bridge file will be injected into TypeScript service
import { renderHelperName, componentHelperName, iterationHelperName, listenerHelperName } from './transformTemplate';

// This bridge file will be injected into TypeScript language service
// it enable type checking and completion, yet still preserve precise option type

export const moduleName = 'vue-editor-bridge';

export const fileName = 'vue-temp/vue-editor-bridge.ts';

export const oldContent = `
const renderHelpers = `
export declare const ${renderHelperName}: {
<T>(Component: (new (...args: any[]) => T), fn: (this: T) => any): any;
};
export declare const ${componentHelperName}: {
(tag: string, data: any, children: any[]): any;
};
export declare const ${iterationHelperName}: {
<T>(list: T[], fn: (value: T, index: number) => any): any;
<T>(obj: { [key: string]: T }, fn: (value: T, key: string, index: number) => any): any;
(num: number, fn: (value: number) => any): any;
<T>(obj: object, fn: (value: any, key: string, index: number) => any): any;
};
export declare const ${listenerHelperName}: {
<T>(vm: T, listener: (this: T, ...args: any[]) => any): any;
};
`;

export const oldContent =
`
import Vue from 'vue';
export interface GeneralOption extends Vue.ComponentOptions<Vue> {
[key: string]: any;
}
export default function bridge<T>(t: T & GeneralOption): T {
return t;
}`;
}
` + renderHelpers;

export const content = `
export const content =
`
import Vue from 'vue';
const func = Vue.extend;
export default func;
`;
` + renderHelpers;
84 changes: 61 additions & 23 deletions server/src/modes/script/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,35 +102,73 @@ export async function getJavascriptMode(
},

doValidation(doc: TextDocument): Diagnostic[] {
const { scriptDoc, service } = updateCurrentTextDocument(doc);
if (!languageServiceIncludesFile(service, doc.uri)) {
return [];
const templateDiags = getTemplateDiagnostics();
const scriptDiags = getScriptDiagnostics();
return [...templateDiags, ...scriptDiags];

function getScriptDiagnostics(): Diagnostic[] {
const { scriptDoc, service } = updateCurrentTextDocument(doc);
if (!languageServiceIncludesFile(service, doc.uri)) {
return [];
}

const fileFsPath = getFileFsPath(doc.uri);
const rawScriptDiagnostics = [
...service.getSyntacticDiagnostics(fileFsPath),
...service.getSemanticDiagnostics(fileFsPath)
];

return rawScriptDiagnostics.map(diag => {
const tags: DiagnosticTag[] = [];

if (diag.reportsUnnecessary) {
tags.push(DiagnosticTag.Unnecessary);
}

// syntactic/semantic diagnostic always has start and length
// so we can safely cast diag to TextSpan
return <Diagnostic>{
range: convertRange(scriptDoc, diag as ts.TextSpan),
severity: DiagnosticSeverity.Error,
message: tsModule.flattenDiagnosticMessageText(diag.messageText, '\n'),
tags,
code: diag.code,
source: 'Vetur'
};
});
}

const fileFsPath = getFileFsPath(doc.uri);
const diagnostics = [
...service.getSyntacticDiagnostics(fileFsPath),
...service.getSemanticDiagnostics(fileFsPath)
];
function getTemplateDiagnostics(): Diagnostic[] {
const enabledTemplateValidation = config.vetur.experimental.templateTypeCheck;
if (!enabledTemplateValidation) {
return [];
}

return diagnostics.map(diag => {
const tags: DiagnosticTag[] = [];
// Add suffix to process this doc as vue template.
const templateDoc = TextDocument.create(doc.uri + '.template', doc.languageId, doc.version, doc.getText());

if (diag.reportsUnnecessary) {
tags.push(DiagnosticTag.Unnecessary);
const { templateService } = updateCurrentTextDocument(templateDoc);
if (!languageServiceIncludesFile(templateService, templateDoc.uri)) {
return [];
}

// syntactic/semantic diagnostic always has start and length
// so we can safely cast diag to TextSpan
return <Diagnostic>{
range: convertRange(scriptDoc, diag as ts.TextSpan),
severity: DiagnosticSeverity.Error,
message: tsModule.flattenDiagnosticMessageText(diag.messageText, '\n'),
tags,
code: diag.code,
source: 'Vetur'
};
});
const templateFileFsPath = getFileFsPath(templateDoc.uri);
// We don't need syntactic diagnostics because
// compiled template is always valid JavaScript syntax.
const rawTemplateDiagnostics = templateService.getSemanticDiagnostics(templateFileFsPath);

return rawTemplateDiagnostics.map(diag => {
// syntactic/semantic diagnostic always has start and length
// so we can safely cast diag to TextSpan
return {
range: convertRange(templateDoc, diag as ts.TextSpan),
severity: DiagnosticSeverity.Error,
message: ts.flattenDiagnosticMessageText(diag.messageText, '\n'),
code: diag.code,
source: 'Vetur'
};
});
}
},
doComplete(doc: TextDocument, position: Position): CompletionList {
const { scriptDoc, service } = updateCurrentTextDocument(doc);
Expand Down
Loading

0 comments on commit 782d5b6

Please sign in to comment.