Skip to content

Commit 191befa

Browse files
committed
fix: trigger more compiler errors
1 parent 7c16dca commit 191befa

File tree

3 files changed

+145
-8
lines changed

3 files changed

+145
-8
lines changed

packages/language-core/lib/template/transforms/transformElement.ts

Lines changed: 88 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
import { createCompilerError, ElementTypes, ErrorCodes, type NodeTransform, NodeTypes } from '@vue/compiler-dom';
1+
import {
2+
createCompilerError,
3+
type DirectiveNode,
4+
ElementTypes,
5+
ErrorCodes,
6+
findDir,
7+
isStaticExp,
8+
isTemplateNode,
9+
type NodeTransform,
10+
NodeTypes,
11+
type TemplateChildNode,
12+
} from '@vue/compiler-dom';
213
import { isBuiltInDirective } from '@vue/shared';
314

415
export const transformElement: NodeTransform = (node, context) => {
@@ -10,10 +21,6 @@ export const transformElement: NodeTransform = (node, context) => {
1021
const isComponent = node.tagType === ElementTypes.COMPONENT;
1122
const isSlotOutlet = node.tagType === ElementTypes.SLOT;
1223

13-
if (isComponent) {
14-
context.components.add(node.tag);
15-
}
16-
1724
for (const prop of node.props) {
1825
if (prop.type !== NodeTypes.DIRECTIVE) {
1926
continue;
@@ -45,12 +52,86 @@ export const transformElement: NodeTransform = (node, context) => {
4552
continue;
4653
}
4754

48-
const result = context.directiveTransforms[prop.name]?.(prop, node, context);
49-
if (isSlotOutlet && (result?.needRuntime || !isBuiltInDirective(prop.name))) {
55+
const runtimeDirectives: DirectiveNode[] = [];
56+
const directiveTransform = context.directiveTransforms[prop.name];
57+
if (directiveTransform) {
58+
const { needRuntime } = directiveTransform(prop, node, context);
59+
if (needRuntime) {
60+
runtimeDirectives.push(prop);
61+
}
62+
}
63+
else if (!isBuiltInDirective(prop.name)) {
64+
runtimeDirectives.push(prop);
65+
}
66+
67+
if (isSlotOutlet && runtimeDirectives.length) {
5068
context.onError(
5169
createCompilerError(ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET, prop.loc),
5270
);
5371
}
5472
}
73+
74+
if (isComponent) {
75+
let hasTemplateSlots = false;
76+
let hasNamedDefaultSlot = false;
77+
const implicitDefaultChildren: TemplateChildNode[] = [];
78+
const seenSlotNames = new Set<string>();
79+
const onComponentSlot = findDir(node, 'slot', true);
80+
81+
for (const child of node.children) {
82+
let slotDir: DirectiveNode | undefined;
83+
if (!isTemplateNode(child) || !(slotDir = findDir(child, 'slot', true))) {
84+
if (child.type !== NodeTypes.COMMENT) {
85+
implicitDefaultChildren.push(child);
86+
}
87+
continue;
88+
}
89+
90+
if (onComponentSlot) {
91+
context.onError(
92+
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc),
93+
);
94+
break;
95+
}
96+
97+
if (findDir(child, /^(?:if|else-if|else|for)$/, true)) {
98+
continue;
99+
}
100+
101+
hasTemplateSlots = true;
102+
const staticSlotName = slotDir.arg
103+
? isStaticExp(slotDir.arg)
104+
? slotDir.arg.content
105+
: undefined
106+
: 'default';
107+
108+
if (staticSlotName) {
109+
if (seenSlotNames.has(staticSlotName)) {
110+
context.onError(
111+
createCompilerError(ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES, slotDir.loc),
112+
);
113+
continue;
114+
}
115+
seenSlotNames.add(staticSlotName);
116+
if (staticSlotName === 'default') {
117+
hasNamedDefaultSlot = true;
118+
}
119+
}
120+
}
121+
122+
if (
123+
hasTemplateSlots && hasNamedDefaultSlot
124+
&& implicitDefaultChildren.some(node => node.type !== NodeTypes.TEXT || !!node.content.trim())
125+
) {
126+
context.onError(
127+
createCompilerError(
128+
ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
129+
implicitDefaultChildren[0]!.loc,
130+
),
131+
);
132+
}
133+
134+
context.components.add(node.tag);
135+
}
55136
};
56137
};

packages/language-core/lib/template/transforms/vFor.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createCompilerError, ErrorCodes, type ForNode, NodeTypes } from '@vue/compiler-dom';
1+
import { createCompilerError, ErrorCodes, findProp, type ForNode, isTemplateNode, NodeTypes } from '@vue/compiler-dom';
22
import { createStructuralDirectiveTransform } from '../utils';
33

44
export const transformFor = createStructuralDirectiveTransform(
@@ -32,5 +32,21 @@ export const transformFor = createStructuralDirectiveTransform(
3232
};
3333

3434
context.replaceNode(forNode);
35+
36+
return () => {
37+
if (isTemplateNode(node)) {
38+
for (const child of node.children) {
39+
if (child.type === NodeTypes.ELEMENT) {
40+
const key = findProp(child, 'key');
41+
if (key) {
42+
context.onError(
43+
createCompilerError(ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT, key.loc),
44+
);
45+
break;
46+
}
47+
}
48+
}
49+
}
50+
};
3551
},
3652
);

packages/language-core/lib/template/transforms/vIf.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import {
2+
type AttributeNode,
23
createCompilerError,
34
type DirectiveNode,
45
type ElementNode,
56
ErrorCodes,
7+
findProp,
68
type IfBranchNode,
79
type IfNode,
810
NodeTypes,
@@ -62,6 +64,16 @@ export const transformIf = createStructuralDirectiveTransform(
6264
branch.children.unshift(...comments);
6365
}
6466

67+
if (branch.userKey) {
68+
for (const { userKey } of sibling.branches) {
69+
if (isSameKey(userKey, branch.userKey)) {
70+
context.onError(
71+
createCompilerError(ErrorCodes.X_V_IF_SAME_KEY, branch.userKey.loc),
72+
);
73+
}
74+
}
75+
}
76+
6577
sibling.branches.push(branch);
6678
traverseNode(branch, context);
6779
context.currentNode = null;
@@ -83,5 +95,33 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
8395
loc: node.loc,
8496
condition: dir.name === 'else' ? undefined : dir.exp,
8597
children: [node],
98+
userKey: findProp(node, 'key'),
8699
};
87100
}
101+
102+
function isSameKey(
103+
a: AttributeNode | DirectiveNode | undefined,
104+
b: AttributeNode | DirectiveNode,
105+
): boolean {
106+
if (!a || a.type !== b.type) {
107+
return false;
108+
}
109+
if (a.type === NodeTypes.ATTRIBUTE) {
110+
if (a.value!.content !== (b as AttributeNode).value!.content) {
111+
return false;
112+
}
113+
}
114+
else {
115+
const exp = a.exp!;
116+
const branchExp = (b as DirectiveNode).exp!;
117+
if (
118+
exp.type !== branchExp.type
119+
|| exp.type !== NodeTypes.SIMPLE_EXPRESSION
120+
|| exp.isStatic !== (branchExp as SimpleExpressionNode).isStatic
121+
|| exp.content !== (branchExp as SimpleExpressionNode).content
122+
) {
123+
return false;
124+
}
125+
}
126+
return true;
127+
}

0 commit comments

Comments
 (0)