Skip to content

Commit 782d5b6

Browse files
authored
Merge pull request #681 from ktsn/template-type-checking
Type checking for template expressions
2 parents 6d9fe80 + ab4a5cc commit 782d5b6

32 files changed

+1397
-202
lines changed

client/vueMain.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ export function activate(context: vscode.ExtensionContext) {
2222

2323
context.subscriptions.push(
2424
vscode.commands.registerCommand('vetur.chooseTypeScriptRefactoring', (args: any) => {
25-
client.sendRequest<vscode.Command | undefined>('requestCodeActionEdits', args)
26-
.then(command =>
27-
command && vscode.commands.executeCommand(command.command, ...command.arguments!)
28-
);
25+
client
26+
.sendRequest<vscode.Command | undefined>('requestCodeActionEdits', args)
27+
.then(command => command && vscode.commands.executeCommand(command.command, ...command.arguments!));
2928
})
3029
);
3130

package.json

+5
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,11 @@
412412
"vetur.dev.vlsPath": {
413413
"type": "string",
414414
"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)"
415+
},
416+
"vetur.experimental.templateTypeCheck": {
417+
"type": "boolean",
418+
"default": true,
419+
"description": "Type-check interpolation expressions in <template> region"
415420
}
416421
}
417422
}

server/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,14 @@
4444
"vscode-languageserver": "^5.3.0-next.4",
4545
"vscode-languageserver-types": "^3.15.0-next.1",
4646
"vscode-uri": "^1.0.1",
47+
"vue-eslint-parser": "^6.0.3",
4748
"vue-onsenui-helper-json": "^1.0.2",
4849
"vuetify-helper-json": "^1.0.0"
4950
},
5051
"devDependencies": {
5152
"@types/eslint": "^4.16.5",
53+
"@types/eslint-scope": "^3.7.0",
54+
"@types/eslint-visitor-keys": "^1.0.0",
5255
"@types/glob": "^7.1.0",
5356
"@types/js-beautify": "^1.8.0",
5457
"@types/lodash": "^4.14.118",

server/src/modes/script/bridge.ts

+28-5
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,44 @@
1-
// this bridge file will be injected into TypeScript service
1+
import { renderHelperName, componentHelperName, iterationHelperName, listenerHelperName } from './transformTemplate';
2+
3+
// This bridge file will be injected into TypeScript language service
24
// it enable type checking and completion, yet still preserve precise option type
35

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

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

8-
export const oldContent = `
10+
const renderHelpers = `
11+
export declare const ${renderHelperName}: {
12+
<T>(Component: (new (...args: any[]) => T), fn: (this: T) => any): any;
13+
};
14+
export declare const ${componentHelperName}: {
15+
(tag: string, data: any, children: any[]): any;
16+
};
17+
export declare const ${iterationHelperName}: {
18+
<T>(list: T[], fn: (value: T, index: number) => any): any;
19+
<T>(obj: { [key: string]: T }, fn: (value: T, key: string, index: number) => any): any;
20+
(num: number, fn: (value: number) => any): any;
21+
<T>(obj: object, fn: (value: any, key: string, index: number) => any): any;
22+
};
23+
export declare const ${listenerHelperName}: {
24+
<T>(vm: T, listener: (this: T, ...args: any[]) => any): any;
25+
};
26+
`;
27+
28+
export const oldContent =
29+
`
930
import Vue from 'vue';
1031
export interface GeneralOption extends Vue.ComponentOptions<Vue> {
1132
[key: string]: any;
1233
}
1334
export default function bridge<T>(t: T & GeneralOption): T {
1435
return t;
15-
}`;
36+
}
37+
` + renderHelpers;
1638

17-
export const content = `
39+
export const content =
40+
`
1841
import Vue from 'vue';
1942
const func = Vue.extend;
2043
export default func;
21-
`;
44+
` + renderHelpers;

server/src/modes/script/javascript.ts

+61-23
Original file line numberDiff line numberDiff line change
@@ -102,35 +102,73 @@ export async function getJavascriptMode(
102102
},
103103

104104
doValidation(doc: TextDocument): Diagnostic[] {
105-
const { scriptDoc, service } = updateCurrentTextDocument(doc);
106-
if (!languageServiceIncludesFile(service, doc.uri)) {
107-
return [];
105+
const templateDiags = getTemplateDiagnostics();
106+
const scriptDiags = getScriptDiagnostics();
107+
return [...templateDiags, ...scriptDiags];
108+
109+
function getScriptDiagnostics(): Diagnostic[] {
110+
const { scriptDoc, service } = updateCurrentTextDocument(doc);
111+
if (!languageServiceIncludesFile(service, doc.uri)) {
112+
return [];
113+
}
114+
115+
const fileFsPath = getFileFsPath(doc.uri);
116+
const rawScriptDiagnostics = [
117+
...service.getSyntacticDiagnostics(fileFsPath),
118+
...service.getSemanticDiagnostics(fileFsPath)
119+
];
120+
121+
return rawScriptDiagnostics.map(diag => {
122+
const tags: DiagnosticTag[] = [];
123+
124+
if (diag.reportsUnnecessary) {
125+
tags.push(DiagnosticTag.Unnecessary);
126+
}
127+
128+
// syntactic/semantic diagnostic always has start and length
129+
// so we can safely cast diag to TextSpan
130+
return <Diagnostic>{
131+
range: convertRange(scriptDoc, diag as ts.TextSpan),
132+
severity: DiagnosticSeverity.Error,
133+
message: tsModule.flattenDiagnosticMessageText(diag.messageText, '\n'),
134+
tags,
135+
code: diag.code,
136+
source: 'Vetur'
137+
};
138+
});
108139
}
109140

110-
const fileFsPath = getFileFsPath(doc.uri);
111-
const diagnostics = [
112-
...service.getSyntacticDiagnostics(fileFsPath),
113-
...service.getSemanticDiagnostics(fileFsPath)
114-
];
141+
function getTemplateDiagnostics(): Diagnostic[] {
142+
const enabledTemplateValidation = config.vetur.experimental.templateTypeCheck;
143+
if (!enabledTemplateValidation) {
144+
return [];
145+
}
115146

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

119-
if (diag.reportsUnnecessary) {
120-
tags.push(DiagnosticTag.Unnecessary);
150+
const { templateService } = updateCurrentTextDocument(templateDoc);
151+
if (!languageServiceIncludesFile(templateService, templateDoc.uri)) {
152+
return [];
121153
}
122154

123-
// syntactic/semantic diagnostic always has start and length
124-
// so we can safely cast diag to TextSpan
125-
return <Diagnostic>{
126-
range: convertRange(scriptDoc, diag as ts.TextSpan),
127-
severity: DiagnosticSeverity.Error,
128-
message: tsModule.flattenDiagnosticMessageText(diag.messageText, '\n'),
129-
tags,
130-
code: diag.code,
131-
source: 'Vetur'
132-
};
133-
});
155+
const templateFileFsPath = getFileFsPath(templateDoc.uri);
156+
// We don't need syntactic diagnostics because
157+
// compiled template is always valid JavaScript syntax.
158+
const rawTemplateDiagnostics = templateService.getSemanticDiagnostics(templateFileFsPath);
159+
160+
return rawTemplateDiagnostics.map(diag => {
161+
// syntactic/semantic diagnostic always has start and length
162+
// so we can safely cast diag to TextSpan
163+
return {
164+
range: convertRange(templateDoc, diag as ts.TextSpan),
165+
severity: DiagnosticSeverity.Error,
166+
message: ts.flattenDiagnosticMessageText(diag.messageText, '\n'),
167+
code: diag.code,
168+
source: 'Vetur'
169+
};
170+
});
171+
}
134172
},
135173
doComplete(doc: TextDocument, position: Position): CompletionList {
136174
const { scriptDoc, service } = updateCurrentTextDocument(doc);

0 commit comments

Comments
 (0)