Skip to content

Commit 89006f5

Browse files
authored
feat(language-service): map sfc compiler errors outside the template inner content (#5045)
1 parent b085f86 commit 89006f5

File tree

13 files changed

+114
-57
lines changed

13 files changed

+114
-57
lines changed

packages/component-meta/lib/base.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,7 @@ function readVueComponentDefaultProps(
719719
default?: string;
720720
required?: boolean;
721721
}> = {};
722+
const { sfc } = root;
722723

723724
scriptSetupWorker();
724725
scriptWorker();
@@ -727,12 +728,12 @@ function readVueComponentDefaultProps(
727728

728729
function scriptSetupWorker() {
729730

730-
const ast = root._sfc.scriptSetup?.ast;
731+
const ast = sfc.scriptSetup?.ast;
731732
if (!ast) {
732733
return;
733734
}
734735

735-
const codegen = vue.tsCodegen.get(root._sfc);
736+
const codegen = vue.tsCodegen.get(sfc);
736737
const scriptSetupRanges = codegen?.getScriptSetupRanges();
737738

738739
if (scriptSetupRanges?.withDefaults?.argNode) {
@@ -785,13 +786,14 @@ function readVueComponentDefaultProps(
785786

786787
function scriptWorker() {
787788

788-
const sfc = root._sfc;
789+
const ast = sfc.script?.ast;
790+
if (!ast) {
791+
return;
792+
}
789793

790-
if (sfc.script) {
791-
const scriptResult = readTsComponentDefaultProps(sfc.script.ast, 'default', printer, ts);
792-
for (const [key, value] of Object.entries(scriptResult)) {
793-
result[key] = value;
794-
}
794+
const scriptResult = readTsComponentDefaultProps(ast, 'default', printer, ts);
795+
for (const [key, value] of Object.entries(scriptResult)) {
796+
result[key] = value;
795797
}
796798
}
797799
}

packages/language-core/lib/virtualFile/vueFile.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ export class VueVirtualCode implements VirtualCode {
1717

1818
// computeds
1919

20-
_vueSfc = computedVueSfc(this.plugins, this.fileName, this.languageId, this._snapshot);
21-
_sfc = computedSfc(this.ts, this.plugins, this.fileName, this._snapshot, this._vueSfc);
22-
_mappings = computed(() => {
20+
private _vueSfc = computedVueSfc(this.plugins, this.fileName, this.languageId, this._snapshot);
21+
private _sfc = computedSfc(this.ts, this.plugins, this.fileName, this._snapshot, this._vueSfc);
22+
private _embeddedCodes = computedEmbeddedCodes(this.plugins, this.fileName, this._sfc);
23+
private _mappings = computed(() => {
2324
const snapshot = this._snapshot();
2425
return [{
2526
sourceOffsets: [0],
@@ -28,16 +29,21 @@ export class VueVirtualCode implements VirtualCode {
2829
data: allCodeFeatures,
2930
}];
3031
});
31-
_embeddedCodes = computedEmbeddedCodes(this.plugins, this.fileName, this._sfc);
3232

3333
// others
3434

35-
get embeddedCodes() {
36-
return this._embeddedCodes();
37-
}
3835
get snapshot() {
3936
return this._snapshot();
4037
}
38+
get vueSfc() {
39+
return this._vueSfc();
40+
}
41+
get sfc() {
42+
return this._sfc;
43+
}
44+
get embeddedCodes() {
45+
return this._embeddedCodes();
46+
}
4147
get mappings() {
4248
return this._mappings();
4349
}

packages/language-service/lib/ideFeatures/nameCasing.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export async function convertTagName(
2424
return;
2525
}
2626

27-
const { template } = root._sfc;
27+
const { template } = root.sfc;
2828
if (!template) {
2929
return;
3030
}
@@ -71,7 +71,7 @@ export async function convertAttrName(
7171
return;
7272
}
7373

74-
const { template } = root._sfc;
74+
const { template } = root.sfc;
7575
if (!template) {
7676
return;
7777
}
@@ -172,8 +172,8 @@ export async function detect(
172172

173173
const result = new Set<TagNameCasing>();
174174

175-
if (file._sfc.template?.ast) {
176-
for (const element of vue.forEachElementNode(file._sfc.template.ast)) {
175+
if (file.sfc.template?.ast) {
176+
for (const element of vue.forEachElementNode(file.sfc.template.ast)) {
177177
if (element.tagType === 1 satisfies CompilerDOM.ElementTypes) {
178178
if (element.tag !== hyphenateTag(element.tag)) {
179179
// TagName
@@ -208,7 +208,7 @@ function getTemplateTagsAndAttrs(sourceFile: VirtualCode): Tags {
208208
if (!(sourceFile instanceof vue.VueVirtualCode)) {
209209
return;
210210
}
211-
const ast = sourceFile._sfc.template?.ast;
211+
const ast = sourceFile.sfc.template?.ast;
212212
const tags: Tags = new Map();
213213
if (ast) {
214214
for (const node of vue.forEachElementNode(ast)) {

packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,8 @@ export function create(
6060
return;
6161
}
6262

63-
const blocks = [
64-
root._sfc.script,
65-
root._sfc.scriptSetup,
66-
].filter(block => !!block);
63+
const { sfc } = root;
64+
const blocks = [sfc.script, sfc.scriptSetup].filter(block => !!block);
6765
if (!blocks.length) {
6866
return;
6967
}

packages/language-service/lib/plugins/vue-complete-define-assignment.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ export function create(): LanguageServicePlugin {
3636
return;
3737
}
3838

39-
const codegen = tsCodegen.get(root._sfc);
40-
const scriptSetup = root._sfc.scriptSetup;
39+
const { sfc } = root;
40+
const codegen = tsCodegen.get(sfc);
41+
const scriptSetup = sfc.scriptSetup;
4142
const scriptSetupRanges = codegen?.getScriptSetupRanges();
4243
if (!scriptSetup || !scriptSetupRanges) {
4344
return;

packages/language-service/lib/plugins/vue-document-drop.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,16 @@ export function create(
5555
return;
5656
}
5757

58-
let baseName = importUri.slice(importUri.lastIndexOf('/') + 1);
59-
baseName = baseName.slice(0, baseName.lastIndexOf('.'));
60-
61-
const newName = capitalize(camelize(baseName));
62-
const sfc = root._sfc;
58+
const { sfc } = root;
6359
const script = sfc.scriptSetup ?? sfc.script;
64-
6560
if (!script) {
6661
return;
6762
}
6863

64+
let baseName = importUri.slice(importUri.lastIndexOf('/') + 1);
65+
baseName = baseName.slice(0, baseName.lastIndexOf('.'));
66+
const newName = capitalize(camelize(baseName));
67+
6968
const additionalEdit: vscode.WorkspaceEdit = {};
7069
const code = [...forEachEmbeddedCode(root)].find(code => code.id === (sfc.scriptSetup ? 'scriptsetup_raw' : 'script_raw'))!;
7170
const lastImportNode = getLastImportNode(ts, script.ast);

packages/language-service/lib/plugins/vue-document-links.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ export function create(): LanguageServicePlugin {
2727
}
2828

2929
const result: vscode.DocumentLink[] = [];
30-
const codegen = tsCodegen.get(root._sfc);
30+
31+
const { sfc } = root;
32+
const codegen = tsCodegen.get(sfc);
3133
const scopedClasses = codegen?.getGeneratedTemplate()?.scopedClasses ?? [];
3234
const styleClasses = new Map<string, {
3335
index: number;
@@ -36,8 +38,8 @@ export function create(): LanguageServicePlugin {
3638
}[]>();
3739
const option = root.vueCompilerOptions.experimentalResolveStyleCssClasses;
3840

39-
for (let i = 0; i < root._sfc.styles.length; i++) {
40-
const style = root._sfc.styles[i];
41+
for (let i = 0; i < sfc.styles.length; i++) {
42+
const style = sfc.styles[i];
4143
if (option === 'always' || (option === 'scoped' && style.scoped)) {
4244
for (const className of style.classNames) {
4345
if (!styleClasses.has(className.text.slice(1))) {

packages/language-service/lib/plugins/vue-extract-file.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function create(
5353
return;
5454
}
5555

56-
const sfc = root._sfc;
56+
const { sfc } = root;
5757
const script = sfc.scriptSetup ?? sfc.script;
5858
if (!sfc.template || !script) {
5959
return;
@@ -95,7 +95,7 @@ export function create(
9595
return codeAction;
9696
}
9797

98-
const sfc = root._sfc;
98+
const { sfc } = root;
9999
const script = sfc.scriptSetup ?? sfc.script;
100100
if (!sfc.template || !script) {
101101
return codeAction;

packages/language-service/lib/plugins/vue-inlayhints.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,21 @@ export function create(ts: typeof import('typescript')): LanguageServicePlugin {
3030

3131
const result: vscode.InlayHint[] = [];
3232

33-
const codegen = tsCodegen.get(virtualCode._sfc);
33+
const codegen = tsCodegen.get(virtualCode.sfc);
3434
const inlayHints = [
3535
...codegen?.getGeneratedTemplate()?.inlayHints ?? [],
3636
...codegen?.getGeneratedScript()?.inlayHints ?? [],
3737
];
3838
const scriptSetupRanges = codegen?.getScriptSetupRanges();
3939

40-
if (scriptSetupRanges?.defineProps?.destructured && virtualCode._sfc.scriptSetup?.ast) {
40+
if (scriptSetupRanges?.defineProps?.destructured && virtualCode.sfc.scriptSetup?.ast) {
4141
const setting = 'vue.inlayHints.destructuredProps';
4242
const enabled = await getSettingEnabled(setting);
4343

4444
if (enabled) {
4545
for (const [prop, isShorthand] of findDestructuredProps(
4646
ts,
47-
virtualCode._sfc.scriptSetup.ast,
47+
virtualCode.sfc.scriptSetup.ast,
4848
scriptSetupRanges.defineProps.destructured.keys()
4949
)) {
5050
const name = prop.text;
@@ -62,9 +62,9 @@ export function create(ts: typeof import('typescript')): LanguageServicePlugin {
6262
}
6363

6464
const blocks = [
65-
virtualCode._sfc.template,
66-
virtualCode._sfc.script,
67-
virtualCode._sfc.scriptSetup,
65+
virtualCode.sfc.template,
66+
virtualCode.sfc.script,
67+
virtualCode.sfc.scriptSetup,
6868
];
6969
const start = document.offsetAt(range.start);
7070
const end = document.offsetAt(range.end);

packages/language-service/lib/plugins/vue-sfc.ts

+56-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { loadLanguageBlocks } from './data';
1010
let sfcDataProvider: html.IHTMLDataProvider | undefined;
1111

1212
export function create(): LanguageServicePlugin {
13-
const htmlPlugin = createHtmlService({
13+
const htmlService = createHtmlService({
1414
documentSelector: ['vue-root-tags'],
1515
useDefaultDataProvider: false,
1616
getCustomData(context) {
@@ -23,7 +23,7 @@ export function create(): LanguageServicePlugin {
2323
const formatSettings = await context.env.getConfiguration?.<html.HTMLFormatConfiguration>('html.format') ?? {};
2424
const blockTypes = ['template', 'script', 'style'];
2525

26-
for (const customBlock of root._sfc.customBlocks) {
26+
for (const customBlock of root.sfc.customBlocks) {
2727
blockTypes.push(customBlock.type);
2828
}
2929

@@ -41,14 +41,21 @@ export function create(): LanguageServicePlugin {
4141
},
4242
});
4343
return {
44-
...htmlPlugin,
44+
...htmlService,
4545
name: 'vue-sfc',
46+
capabilities: {
47+
...htmlService.capabilities,
48+
diagnosticProvider: {
49+
interFileDependencies: false,
50+
workspaceDiagnostics: false,
51+
}
52+
},
4653
create(context) {
47-
const htmlPluginInstance = htmlPlugin.create(context);
54+
const htmlServiceInstance = htmlService.create(context);
4855

4956
return {
5057

51-
...htmlPluginInstance,
58+
...htmlServiceInstance,
5259

5360
provideDocumentLinks: undefined,
5461

@@ -73,11 +80,53 @@ export function create(): LanguageServicePlugin {
7380
return options;
7481
},
7582

83+
provideDiagnostics(document, token) {
84+
return worker(document, context, async root => {
85+
const { vueSfc, sfc } = root;
86+
if (!vueSfc) {
87+
return;
88+
}
89+
90+
const originalResult = await htmlServiceInstance.provideDiagnostics?.(document, token);
91+
const sfcErrors: vscode.Diagnostic[] = [];
92+
const { template } = sfc;
93+
94+
const {
95+
startTagEnd = Infinity,
96+
endTagStart = -Infinity
97+
} = template ?? {};
98+
99+
for (const error of vueSfc.errors) {
100+
if ('code' in error) {
101+
const start = error.loc?.start.offset ?? 0;
102+
const end = error.loc?.end.offset ?? 0;
103+
if (end < startTagEnd || start >= endTagStart) {
104+
sfcErrors.push({
105+
range: {
106+
start: document.positionAt(start),
107+
end: document.positionAt(end),
108+
},
109+
severity: 1 satisfies typeof vscode.DiagnosticSeverity.Error,
110+
code: error.code,
111+
source: 'vue',
112+
message: error.message,
113+
});
114+
}
115+
}
116+
}
117+
118+
return [
119+
...originalResult ?? [],
120+
...sfcErrors
121+
];
122+
});
123+
},
124+
76125
provideDocumentSymbols(document) {
77126
return worker(document, context, root => {
78127

79128
const result: vscode.DocumentSymbol[] = [];
80-
const sfc = root._sfc;
129+
const { sfc } = root;
81130

82131
if (sfc.template) {
83132
result.push({
@@ -162,7 +211,7 @@ export function create(): LanguageServicePlugin {
162211
},
163212

164213
async provideCompletionItems(document, position, context, token) {
165-
const result = await htmlPluginInstance.provideCompletionItems?.(document, position, context, token);
214+
const result = await htmlServiceInstance.provideCompletionItems?.(document, position, context, token);
166215
if (!result) {
167216
return;
168217
}

packages/language-service/lib/plugins/vue-template.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ export function create(
365365

366366
const originalResult = await baseServiceInstance.provideDiagnostics?.(document, token);
367367
const templateErrors: vscode.Diagnostic[] = [];
368-
const { template } = root._sfc;
368+
const { template } = root.sfc;
369369

370370
if (template) {
371371

@@ -428,7 +428,7 @@ export function create(
428428
return;
429429
}
430430

431-
const { template } = root._sfc;
431+
const { template } = root.sfc;
432432
if (!template) {
433433
return;
434434
}
@@ -520,7 +520,7 @@ export function create(
520520
})());
521521
return [];
522522
}
523-
const scriptSetupRanges = tsCodegen.get(vueCode._sfc)?.getScriptSetupRanges();
523+
const scriptSetupRanges = tsCodegen.get(vueCode.sfc)?.getScriptSetupRanges();
524524
const names = new Set<string>();
525525
const tags: html.ITagData[] = [];
526526

@@ -534,7 +534,7 @@ export function create(
534534
}
535535

536536
for (const binding of scriptSetupRanges?.bindings ?? []) {
537-
const name = vueCode._sfc.scriptSetup!.content.slice(binding.range.start, binding.range.end);
537+
const name = vueCode.sfc.scriptSetup!.content.slice(binding.range.start, binding.range.end);
538538
if (casing.tag === TagNameCasing.Kebab) {
539539
names.add(hyphenateTag(name));
540540
}

0 commit comments

Comments
 (0)