@lwc/template-compiler 2.6.2 → 2.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commonjs/codegen/codegen.js +73 -3
- package/dist/commonjs/codegen/codegen.js.map +1 -1
- package/dist/commonjs/codegen/helpers.js +34 -20
- package/dist/commonjs/codegen/helpers.js.map +1 -1
- package/dist/commonjs/codegen/index.js +183 -192
- package/dist/commonjs/codegen/index.js.map +1 -1
- package/dist/commonjs/index.js +12 -5
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/parser/attribute.js +3 -3
- package/dist/commonjs/parser/attribute.js.map +1 -1
- package/dist/commonjs/parser/constants.js +1 -7
- package/dist/commonjs/parser/constants.js.map +1 -1
- package/dist/commonjs/parser/expression.js +5 -2
- package/dist/commonjs/parser/expression.js.map +1 -1
- package/dist/commonjs/parser/html.js +2 -1
- package/dist/commonjs/parser/html.js.map +1 -1
- package/dist/commonjs/parser/index.js +365 -337
- package/dist/commonjs/parser/index.js.map +1 -1
- package/dist/commonjs/parser/parser.js +85 -30
- package/dist/commonjs/parser/parser.js.map +1 -1
- package/dist/commonjs/shared/ast.js +303 -0
- package/dist/commonjs/shared/ast.js.map +1 -0
- package/dist/commonjs/shared/parse5.js +1 -15
- package/dist/commonjs/shared/parse5.js.map +1 -1
- package/dist/commonjs/shared/types.js +1 -7
- package/dist/commonjs/shared/types.js.map +1 -1
- package/dist/types/codegen/codegen.d.ts +25 -3
- package/dist/types/codegen/helpers.d.ts +11 -5
- package/dist/types/codegen/index.d.ts +2 -2
- package/dist/types/index.d.ts +1 -2
- package/dist/types/parser/attribute.d.ts +7 -7
- package/dist/types/parser/constants.d.ts +0 -6
- package/dist/types/parser/expression.d.ts +3 -4
- package/dist/types/parser/parser.d.ts +57 -28
- package/dist/types/shared/ast.d.ts +45 -0
- package/dist/types/shared/estree.d.ts +0 -2
- package/dist/types/shared/parse5.d.ts +0 -1
- package/dist/types/shared/types.d.ts +129 -86
- package/package.json +5 -5
- package/dist/commonjs/codegen/scope.js +0 -61
- package/dist/commonjs/codegen/scope.js.map +0 -1
- package/dist/commonjs/shared/ir.js +0 -90
- package/dist/commonjs/shared/ir.js.map +0 -1
- package/dist/types/codegen/scope.d.ts +0 -8
- package/dist/types/shared/ir.d.ts +0 -15
|
@@ -22,14 +22,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
22
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
23
|
};
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
const shared_1 = require("@lwc/shared");
|
|
26
25
|
const errors_1 = require("@lwc/errors");
|
|
27
26
|
const html_1 = require("./html");
|
|
28
27
|
const attribute_1 = require("./attribute");
|
|
29
28
|
const expression_1 = require("./expression");
|
|
30
29
|
const t = __importStar(require("../shared/estree"));
|
|
31
30
|
const parse5Utils = __importStar(require("../shared/parse5"));
|
|
32
|
-
const
|
|
31
|
+
const ast = __importStar(require("../shared/ast"));
|
|
33
32
|
const types_1 = require("../shared/types");
|
|
34
33
|
const parser_1 = __importDefault(require("./parser"));
|
|
35
34
|
const constants_1 = require("./constants");
|
|
@@ -65,40 +64,62 @@ function parse(source, state) {
|
|
|
65
64
|
}
|
|
66
65
|
const root = ctx.withErrorRecovery(() => {
|
|
67
66
|
const templateRoot = getTemplateRoot(ctx, fragment);
|
|
68
|
-
return
|
|
67
|
+
return parseRoot(ctx, templateRoot);
|
|
69
68
|
});
|
|
70
69
|
return { root, warnings: ctx.warnings };
|
|
71
70
|
}
|
|
72
71
|
exports.default = parse;
|
|
73
|
-
function
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
parseChildren(ctx, parse5Elm,
|
|
90
|
-
|
|
91
|
-
return element;
|
|
72
|
+
function parseRoot(ctx, parse5Elm) {
|
|
73
|
+
const { sourceCodeLocation: rootLocation } = parse5Elm;
|
|
74
|
+
if (!rootLocation) {
|
|
75
|
+
// Parse5 will recover from invalid HTML. When this happens the node's sourceCodeLocation will be undefined.
|
|
76
|
+
// https://github.com/inikulin/parse5/blob/master/packages/parse5/docs/options/parser-options.md#sourcecodelocationinfo
|
|
77
|
+
// This is a defensive check as this should never happen for the root template.
|
|
78
|
+
throw new Error('An internal parsing error occurred during node creation; the root template node does not have a sourceCodeLocation.');
|
|
79
|
+
}
|
|
80
|
+
if (parse5Elm.tagName !== 'template') {
|
|
81
|
+
ctx.throw(errors_1.ParserDiagnostics.ROOT_TAG_SHOULD_BE_TEMPLATE, [parse5Elm.tagName], ast.sourceLocation(rootLocation));
|
|
82
|
+
}
|
|
83
|
+
const parsedAttr = parseAttributes(ctx, parse5Elm, rootLocation);
|
|
84
|
+
const root = ast.root(rootLocation);
|
|
85
|
+
applyRootLwcDirectives(ctx, parsedAttr, root);
|
|
86
|
+
ctx.setRootDirective(root);
|
|
87
|
+
validateRoot(ctx, parsedAttr, root);
|
|
88
|
+
parseChildren(ctx, parse5Elm, root, rootLocation);
|
|
89
|
+
return root;
|
|
92
90
|
}
|
|
93
|
-
function
|
|
91
|
+
function parseElement(ctx, parse5Elm, parentNode, parse5ParentLocation) {
|
|
92
|
+
const parse5ElmLocation = parseElementLocation(ctx, parse5Elm, parse5ParentLocation);
|
|
93
|
+
const parsedAttr = parseAttributes(ctx, parse5Elm, parse5ElmLocation);
|
|
94
|
+
const directive = parseElementDirectives(ctx, parsedAttr, parentNode, parse5ElmLocation);
|
|
95
|
+
const element = parseBaseElement(ctx, parsedAttr, parse5Elm, directive || parentNode, parse5ElmLocation);
|
|
96
|
+
if (element) {
|
|
97
|
+
applyHandlers(ctx, parsedAttr, element);
|
|
98
|
+
applyKey(ctx, parsedAttr, element, parentNode);
|
|
99
|
+
applyLwcDirectives(ctx, parsedAttr, element);
|
|
100
|
+
applyAttributes(ctx, parsedAttr, element);
|
|
101
|
+
validateElement(ctx, element, parse5Elm);
|
|
102
|
+
validateAttributes(ctx, parsedAttr, element);
|
|
103
|
+
validateProperties(ctx, element);
|
|
104
|
+
}
|
|
105
|
+
validateTemplate(ctx, parsedAttr, parse5Elm, parse5ElmLocation);
|
|
106
|
+
const currentNode = element || directive;
|
|
107
|
+
if (currentNode) {
|
|
108
|
+
parseChildren(ctx, parse5Elm, currentNode, parse5ElmLocation);
|
|
109
|
+
validateChildren(ctx, element);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function parseElementLocation(ctx, parse5Elm, parse5ParentLocation) {
|
|
94
113
|
var _a;
|
|
95
114
|
let location = parse5Elm.sourceCodeLocation;
|
|
115
|
+
// AST hierarchy is ForBlock > If > BaseElement, if immediate parent is not a BaseElement it is a template.
|
|
116
|
+
const parentNode = ctx.findAncestor(ast.isBaseElement, () => false);
|
|
96
117
|
if (!location) {
|
|
97
118
|
// Parse5 will recover from invalid HTML. When this happens the element's sourceCodeLocation will be undefined.
|
|
98
119
|
// https://github.com/inikulin/parse5/blob/master/packages/parse5/docs/options/parser-options.md#sourcecodelocationinfo
|
|
99
120
|
ctx.warn(errors_1.ParserDiagnostics.INVALID_HTML_RECOVERY, [
|
|
100
121
|
parse5Elm.tagName,
|
|
101
|
-
|
|
122
|
+
(_a = parentNode === null || parentNode === void 0 ? void 0 : parentNode.name) !== null && _a !== void 0 ? _a : 'template',
|
|
102
123
|
]);
|
|
103
124
|
}
|
|
104
125
|
// With parse5 automatically recovering from invalid HTML, some AST nodes might not have
|
|
@@ -110,33 +131,65 @@ function parseElementLocation(ctx, parse5Elm, parentIRElement) {
|
|
|
110
131
|
current = current.parentNode;
|
|
111
132
|
location = current.sourceCodeLocation;
|
|
112
133
|
}
|
|
113
|
-
|
|
114
|
-
// If there is no parent, use an empty parse5.ElementLocation instead.
|
|
115
|
-
return (_a = location !== null && location !== void 0 ? location : parentIRElement === null || parentIRElement === void 0 ? void 0 : parentIRElement.location) !== null && _a !== void 0 ? _a : parse5Utils.createEmptyElementLocation();
|
|
134
|
+
return location !== null && location !== void 0 ? location : parse5ParentLocation;
|
|
116
135
|
}
|
|
117
|
-
function
|
|
136
|
+
function parseElementDirectives(ctx, parsedAttr, parent, parse5ElmLocation) {
|
|
137
|
+
let current;
|
|
138
|
+
const parsers = [parseForEach, parseForOf, parseIf];
|
|
139
|
+
for (const parser of parsers) {
|
|
140
|
+
const prev = current || parent;
|
|
141
|
+
const node = parser(ctx, parsedAttr, parse5ElmLocation);
|
|
142
|
+
if (node) {
|
|
143
|
+
ctx.addNodeCurrentScope(node);
|
|
144
|
+
prev.children.push(node);
|
|
145
|
+
current = node;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return current;
|
|
149
|
+
}
|
|
150
|
+
function parseBaseElement(ctx, parsedAttr, parse5Elm, parent, parse5ElmLocation) {
|
|
151
|
+
const { tagName: tag } = parse5Elm;
|
|
152
|
+
let element;
|
|
153
|
+
if (tag === 'slot') {
|
|
154
|
+
element = parseSlot(ctx, parsedAttr, parse5ElmLocation);
|
|
155
|
+
// Skip creating template nodes
|
|
156
|
+
}
|
|
157
|
+
else if (tag !== 'template') {
|
|
158
|
+
// Check if the element tag is a valid custom element name and is not part of known standard
|
|
159
|
+
// element name containing a dash.
|
|
160
|
+
if (!tag.includes('-') || constants_1.DASHED_TAGNAME_ELEMENT_SET.has(tag)) {
|
|
161
|
+
element = ast.element(parse5Elm, parse5ElmLocation);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
element = ast.component(parse5Elm, parse5ElmLocation);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (element) {
|
|
168
|
+
ctx.addNodeCurrentScope(element);
|
|
169
|
+
parent.children.push(element);
|
|
170
|
+
}
|
|
171
|
+
return element;
|
|
172
|
+
}
|
|
173
|
+
function parseChildren(ctx, parse5Parent, parent, parse5ParentLocation) {
|
|
118
174
|
var _a;
|
|
119
|
-
const parsedChildren = [];
|
|
120
175
|
const children = ((_a = parse5Utils.getTemplateContent(parse5Parent)) !== null && _a !== void 0 ? _a : parse5Parent).childNodes;
|
|
121
|
-
ctx.parentStack.push(parentIRElement);
|
|
122
176
|
for (const child of children) {
|
|
123
177
|
ctx.withErrorRecovery(() => {
|
|
124
178
|
if (parse5Utils.isElementNode(child)) {
|
|
125
|
-
|
|
126
|
-
|
|
179
|
+
ctx.beginScope();
|
|
180
|
+
parseElement(ctx, child, parent, parse5ParentLocation);
|
|
181
|
+
ctx.endScope();
|
|
127
182
|
}
|
|
128
183
|
else if (parse5Utils.isTextNode(child)) {
|
|
129
184
|
const textNodes = parseText(ctx, child);
|
|
130
|
-
|
|
185
|
+
parent.children.push(...textNodes);
|
|
131
186
|
}
|
|
132
187
|
else if (parse5Utils.isCommentNode(child)) {
|
|
133
188
|
const commentNode = parseComment(child);
|
|
134
|
-
|
|
189
|
+
parent.children.push(commentNode);
|
|
135
190
|
}
|
|
136
191
|
});
|
|
137
192
|
}
|
|
138
|
-
ctx.parentStack.pop();
|
|
139
|
-
parentIRElement.children = parsedChildren;
|
|
140
193
|
}
|
|
141
194
|
function parseText(ctx, parse5Text) {
|
|
142
195
|
const parsedTextNodes = [];
|
|
@@ -145,7 +198,7 @@ function parseText(ctx, parse5Text) {
|
|
|
145
198
|
// Parse5 will recover from invalid HTML. When this happens the node's sourceCodeLocation will be undefined.
|
|
146
199
|
// https://github.com/inikulin/parse5/blob/master/packages/parse5/docs/options/parser-options.md#sourcecodelocationinfo
|
|
147
200
|
// This is a defensive check as this should never happen for TextNode.
|
|
148
|
-
throw new Error(
|
|
201
|
+
throw new Error('An internal parsing error occurred during node creation; a text node was found without a sourceCodeLocation.');
|
|
149
202
|
}
|
|
150
203
|
// Extract the raw source to avoid HTML entity decoding done by parse5
|
|
151
204
|
const rawText = (0, html_1.cleanTextNode)(ctx.getSource(location.startOffset, location.endOffset));
|
|
@@ -161,12 +214,12 @@ function parseText(ctx, parse5Text) {
|
|
|
161
214
|
}
|
|
162
215
|
let value;
|
|
163
216
|
if ((0, expression_1.isExpression)(token)) {
|
|
164
|
-
value = (0, expression_1.parseExpression)(ctx, token, location);
|
|
217
|
+
value = (0, expression_1.parseExpression)(ctx, token, ast.sourceLocation(location));
|
|
165
218
|
}
|
|
166
219
|
else {
|
|
167
|
-
value = (0, html_1.decodeTextContent)(token);
|
|
220
|
+
value = ast.literal((0, html_1.decodeTextContent)(token));
|
|
168
221
|
}
|
|
169
|
-
parsedTextNodes.push(
|
|
222
|
+
parsedTextNodes.push(ast.text(value, location));
|
|
170
223
|
}
|
|
171
224
|
return parsedTextNodes;
|
|
172
225
|
}
|
|
@@ -176,9 +229,9 @@ function parseComment(parse5Comment) {
|
|
|
176
229
|
// Parse5 will recover from invalid HTML. When this happens the node's sourceCodeLocation will be undefined.
|
|
177
230
|
// https://github.com/inikulin/parse5/blob/master/packages/parse5/docs/options/parser-options.md#sourcecodelocationinfo
|
|
178
231
|
// This is a defensive check as this should never happen for CommentNode.
|
|
179
|
-
throw new Error(
|
|
232
|
+
throw new Error('An internal parsing error occurred during node creation; a comment node was found without a sourceCodeLocation.');
|
|
180
233
|
}
|
|
181
|
-
return
|
|
234
|
+
return ast.comment((0, html_1.decodeTextContent)(parse5Comment.data), location);
|
|
182
235
|
}
|
|
183
236
|
function getTemplateRoot(ctx, documentFragment) {
|
|
184
237
|
// Filter all the empty text nodes
|
|
@@ -186,7 +239,7 @@ function getTemplateRoot(ctx, documentFragment) {
|
|
|
186
239
|
(parse5Utils.isTextNode(child) && child.value.trim().length));
|
|
187
240
|
if (validRoots.length > 1) {
|
|
188
241
|
const duplicateRoot = validRoots[1].sourceCodeLocation;
|
|
189
|
-
ctx.
|
|
242
|
+
ctx.throw(errors_1.ParserDiagnostics.MULTIPLE_ROOTS_FOUND, [], duplicateRoot ? ast.sourceLocation(duplicateRoot) : duplicateRoot);
|
|
190
243
|
}
|
|
191
244
|
const [root] = validRoots;
|
|
192
245
|
if (!root || !parse5Utils.isElementNode(root)) {
|
|
@@ -194,266 +247,254 @@ function getTemplateRoot(ctx, documentFragment) {
|
|
|
194
247
|
}
|
|
195
248
|
return root;
|
|
196
249
|
}
|
|
197
|
-
function applyHandlers(ctx,
|
|
250
|
+
function applyHandlers(ctx, parsedAttr, element) {
|
|
198
251
|
let eventHandlerAttribute;
|
|
199
252
|
while ((eventHandlerAttribute = parsedAttr.pick(constants_1.EVENT_HANDLER_RE))) {
|
|
200
253
|
const { name } = eventHandlerAttribute;
|
|
201
|
-
if (!
|
|
202
|
-
ctx.
|
|
254
|
+
if (!ast.isExpression(eventHandlerAttribute.value)) {
|
|
255
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.EVENT_HANDLER_SHOULD_BE_EXPRESSION, eventHandlerAttribute);
|
|
203
256
|
}
|
|
204
257
|
if (!name.match(constants_1.EVENT_HANDLER_NAME_RE)) {
|
|
205
|
-
ctx.
|
|
206
|
-
}
|
|
207
|
-
// Light DOM slots cannot have events because there's no actual `<slot>` element
|
|
208
|
-
if (element.tag === 'slot' && ctx.getRenderMode(element) === types_1.LWCDirectiveRenderMode.light) {
|
|
209
|
-
ctx.throwOnIRNode(errors_1.ParserDiagnostics.LWC_LIGHT_SLOT_INVALID_EVENT_LISTENER, element, [
|
|
210
|
-
name,
|
|
211
|
-
]);
|
|
258
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_EVENT_NAME, eventHandlerAttribute, [name]);
|
|
212
259
|
}
|
|
213
260
|
// Strip the `on` prefix from the event handler name
|
|
214
261
|
const eventName = name.slice(2);
|
|
215
|
-
const
|
|
216
|
-
|
|
262
|
+
const listener = ast.eventListener(eventName, eventHandlerAttribute.value, eventHandlerAttribute.location);
|
|
263
|
+
element.listeners.push(listener);
|
|
217
264
|
}
|
|
218
265
|
}
|
|
219
|
-
function
|
|
266
|
+
function parseIf(ctx, parsedAttr, parse5ElmLocation) {
|
|
220
267
|
const ifAttribute = parsedAttr.pick(constants_1.IF_RE);
|
|
221
268
|
if (!ifAttribute) {
|
|
222
269
|
return;
|
|
223
270
|
}
|
|
224
|
-
if (!
|
|
225
|
-
ctx.
|
|
271
|
+
if (!ast.isExpression(ifAttribute.value)) {
|
|
272
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.IF_DIRECTIVE_SHOULD_BE_EXPRESSION, ifAttribute);
|
|
226
273
|
}
|
|
227
274
|
const [, modifier] = ifAttribute.name.split(':');
|
|
228
275
|
if (!constants_1.VALID_IF_MODIFIER.has(modifier)) {
|
|
229
|
-
ctx.
|
|
276
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.UNEXPECTED_IF_MODIFIER, ifAttribute, [modifier]);
|
|
230
277
|
}
|
|
231
|
-
|
|
232
|
-
element.ifModifier = modifier;
|
|
278
|
+
return ast.ifNode(modifier, ifAttribute.value, ast.sourceLocation(parse5ElmLocation), ifAttribute.location);
|
|
233
279
|
}
|
|
234
|
-
function
|
|
280
|
+
function applyRootLwcDirectives(ctx, parsedAttr, root) {
|
|
235
281
|
const lwcAttribute = parsedAttr.get(constants_1.LWC_RE);
|
|
236
282
|
if (!lwcAttribute) {
|
|
237
283
|
return;
|
|
238
284
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
ctx.throwOnIRNode(errors_1.ParserDiagnostics.UNKNOWN_LWC_DIRECTIVE, element, [
|
|
242
|
-
lwcAttribute.name,
|
|
243
|
-
`<${element.tag}>`,
|
|
244
|
-
]);
|
|
245
|
-
}
|
|
246
|
-
const lwcOpts = {};
|
|
247
|
-
applyLwcDynamicDirective(ctx, element, parsedAttr, lwcOpts);
|
|
248
|
-
applyLwcDomDirective(ctx, element, parsedAttr, lwcOpts);
|
|
249
|
-
applyLwcRenderModeDirective(ctx, element, parsedAttr, lwcOpts);
|
|
250
|
-
applyLwcPreserveCommentsDirective(ctx, element, parsedAttr, lwcOpts);
|
|
251
|
-
applyLwcInnerHtmlDirective(ctx, element, parsedAttr, lwcOpts);
|
|
252
|
-
element.lwc = lwcOpts;
|
|
285
|
+
applyLwcRenderModeDirective(ctx, parsedAttr, root);
|
|
286
|
+
applyLwcPreserveCommentsDirective(ctx, parsedAttr, root);
|
|
253
287
|
}
|
|
254
|
-
function applyLwcRenderModeDirective(ctx,
|
|
255
|
-
const lwcRenderModeAttribute = parsedAttr.
|
|
288
|
+
function applyLwcRenderModeDirective(ctx, parsedAttr, root) {
|
|
289
|
+
const lwcRenderModeAttribute = parsedAttr.pick(constants_1.ROOT_TEMPLATE_DIRECTIVES.RENDER_MODE);
|
|
256
290
|
if (!lwcRenderModeAttribute) {
|
|
257
291
|
return;
|
|
258
292
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
293
|
+
const { value: renderDomAttr } = lwcRenderModeAttribute;
|
|
294
|
+
if (!ast.isStringLiteral(renderDomAttr) ||
|
|
295
|
+
(renderDomAttr.value !== types_1.LWCDirectiveRenderMode.shadow &&
|
|
296
|
+
renderDomAttr.value !== types_1.LWCDirectiveRenderMode.light)) {
|
|
297
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_RENDER_MODE_INVALID_VALUE, root);
|
|
262
298
|
}
|
|
263
|
-
|
|
264
|
-
ctx.throwOnIRNode(errors_1.ParserDiagnostics.UNKNOWN_LWC_DIRECTIVE, element, [
|
|
265
|
-
constants_1.ROOT_TEMPLATE_DIRECTIVES.RENDER_MODE,
|
|
266
|
-
`<${element.tag}>`,
|
|
267
|
-
]);
|
|
268
|
-
}
|
|
269
|
-
lwcOpts.renderMode = lwcRenderModeAttribute.value;
|
|
299
|
+
root.directives.push(ast.renderModeDirective(renderDomAttr.value, lwcRenderModeAttribute.location));
|
|
270
300
|
}
|
|
271
|
-
function applyLwcPreserveCommentsDirective(ctx,
|
|
272
|
-
const lwcPreserveCommentAttribute = parsedAttr.
|
|
301
|
+
function applyLwcPreserveCommentsDirective(ctx, parsedAttr, root) {
|
|
302
|
+
const lwcPreserveCommentAttribute = parsedAttr.pick(constants_1.ROOT_TEMPLATE_DIRECTIVES.PRESERVE_COMMENTS);
|
|
273
303
|
if (!lwcPreserveCommentAttribute) {
|
|
274
304
|
return;
|
|
275
305
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
`<${element.tag}>`,
|
|
280
|
-
]);
|
|
281
|
-
}
|
|
282
|
-
else if (!(0, ir_1.isIRBooleanAttribute)(lwcPreserveCommentAttribute)) {
|
|
283
|
-
ctx.throwOnIRNode(errors_1.ParserDiagnostics.PRESERVE_COMMENTS_MUST_BE_BOOLEAN, element);
|
|
306
|
+
const { value: lwcPreserveCommentsAttr } = lwcPreserveCommentAttribute;
|
|
307
|
+
if (!ast.isBooleanLiteral(lwcPreserveCommentsAttr)) {
|
|
308
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.PRESERVE_COMMENTS_MUST_BE_BOOLEAN, root);
|
|
284
309
|
}
|
|
285
|
-
|
|
310
|
+
root.directives.push(ast.preserveCommentsDirective(lwcPreserveCommentsAttr.value, lwcPreserveCommentAttribute.location));
|
|
286
311
|
}
|
|
287
|
-
function
|
|
288
|
-
const
|
|
289
|
-
if (!
|
|
312
|
+
function applyLwcDirectives(ctx, parsedAttr, element) {
|
|
313
|
+
const lwcAttribute = parsedAttr.get(constants_1.LWC_RE);
|
|
314
|
+
if (!lwcAttribute) {
|
|
290
315
|
return;
|
|
291
316
|
}
|
|
292
|
-
if ((
|
|
293
|
-
ctx.
|
|
294
|
-
|
|
317
|
+
if (!constants_1.LWC_DIRECTIVE_SET.has(lwcAttribute.name)) {
|
|
318
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.UNKNOWN_LWC_DIRECTIVE, element, [
|
|
319
|
+
lwcAttribute.name,
|
|
320
|
+
`<${element.name}>`,
|
|
295
321
|
]);
|
|
296
322
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
323
|
+
// Should not allow render mode or preserve comments on non root nodes
|
|
324
|
+
if (parsedAttr.get(constants_1.ROOT_TEMPLATE_DIRECTIVES.RENDER_MODE)) {
|
|
325
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.UNKNOWN_LWC_DIRECTIVE, element, [
|
|
326
|
+
constants_1.ROOT_TEMPLATE_DIRECTIVES.RENDER_MODE,
|
|
327
|
+
`<${element.name}>`,
|
|
300
328
|
]);
|
|
301
329
|
}
|
|
302
|
-
if (
|
|
303
|
-
ctx.
|
|
304
|
-
|
|
330
|
+
if (parsedAttr.get(constants_1.ROOT_TEMPLATE_DIRECTIVES.PRESERVE_COMMENTS)) {
|
|
331
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.UNKNOWN_LWC_DIRECTIVE, element, [
|
|
332
|
+
constants_1.ROOT_TEMPLATE_DIRECTIVES.PRESERVE_COMMENTS,
|
|
333
|
+
`<${element.name}>`,
|
|
305
334
|
]);
|
|
306
335
|
}
|
|
307
|
-
|
|
336
|
+
applyLwcDynamicDirective(ctx, parsedAttr, element);
|
|
337
|
+
applyLwcDomDirective(ctx, parsedAttr, element);
|
|
338
|
+
applyLwcInnerHtmlDirective(ctx, parsedAttr, element);
|
|
308
339
|
}
|
|
309
|
-
function applyLwcDynamicDirective(ctx,
|
|
310
|
-
const { tag } = element;
|
|
311
|
-
const lwcDynamicAttribute = parsedAttr.pick(
|
|
340
|
+
function applyLwcDynamicDirective(ctx, parsedAttr, element) {
|
|
341
|
+
const { name: tag } = element;
|
|
342
|
+
const lwcDynamicAttribute = parsedAttr.pick('lwc:dynamic');
|
|
312
343
|
if (!lwcDynamicAttribute) {
|
|
313
344
|
return;
|
|
314
345
|
}
|
|
315
346
|
if (!ctx.config.experimentalDynamicDirective) {
|
|
316
|
-
ctx.
|
|
347
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_OPTS_LWC_DYNAMIC, element);
|
|
317
348
|
}
|
|
318
|
-
if (!
|
|
319
|
-
ctx.
|
|
349
|
+
if (!ast.isComponent(element)) {
|
|
350
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_LWC_DYNAMIC_ON_NATIVE_ELEMENT, element, [
|
|
320
351
|
`<${tag}>`,
|
|
321
352
|
]);
|
|
322
353
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
]);
|
|
354
|
+
const { value: lwcDynamicAttr } = lwcDynamicAttribute;
|
|
355
|
+
if (!ast.isExpression(lwcDynamicAttr)) {
|
|
356
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_LWC_DYNAMIC_LITERAL_PROP, element, [`<${tag}>`]);
|
|
327
357
|
}
|
|
328
|
-
|
|
358
|
+
element.directives.push(ast.dynamicDirective(lwcDynamicAttr, lwcDynamicAttr.location));
|
|
329
359
|
}
|
|
330
|
-
function applyLwcDomDirective(ctx,
|
|
331
|
-
const { tag } = element;
|
|
332
|
-
const lwcDomAttribute = parsedAttr.pick(
|
|
360
|
+
function applyLwcDomDirective(ctx, parsedAttr, element) {
|
|
361
|
+
const { name: tag } = element;
|
|
362
|
+
const lwcDomAttribute = parsedAttr.pick('lwc:dom');
|
|
333
363
|
if (!lwcDomAttribute) {
|
|
334
364
|
return;
|
|
335
365
|
}
|
|
336
|
-
if (ctx.
|
|
337
|
-
ctx.
|
|
366
|
+
if (ctx.renderMode === types_1.LWCDirectiveRenderMode.light) {
|
|
367
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_IN_LIGHT_DOM, element, [`<${tag}>`]);
|
|
338
368
|
}
|
|
339
|
-
if (
|
|
340
|
-
ctx.
|
|
369
|
+
if (ast.isComponent(element)) {
|
|
370
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_CUSTOM_ELEMENT, element, [`<${tag}>`]);
|
|
341
371
|
}
|
|
342
|
-
if (
|
|
343
|
-
ctx.
|
|
372
|
+
if (ast.isSlot(element)) {
|
|
373
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_SLOT_ELEMENT, element);
|
|
344
374
|
}
|
|
345
|
-
|
|
346
|
-
|
|
375
|
+
const { value: lwcDomAttr } = lwcDomAttribute;
|
|
376
|
+
if (!ast.isStringLiteral(lwcDomAttr) || lwcDomAttr.value !== types_1.LWCDirectiveDomMode.manual) {
|
|
347
377
|
const possibleValues = Object.keys(types_1.LWCDirectiveDomMode)
|
|
348
378
|
.map((value) => `"${value}"`)
|
|
349
379
|
.join(', or ');
|
|
350
|
-
ctx.
|
|
380
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_VALUE, element, [possibleValues]);
|
|
381
|
+
}
|
|
382
|
+
element.directives.push(ast.domDirective(lwcDomAttr.value, lwcDomAttribute.location));
|
|
383
|
+
}
|
|
384
|
+
function applyLwcInnerHtmlDirective(ctx, parsedAttr, element) {
|
|
385
|
+
const lwcInnerHtmlDirective = parsedAttr.pick(constants_1.LWC_DIRECTIVES.INNER_HTML);
|
|
386
|
+
if (!lwcInnerHtmlDirective) {
|
|
387
|
+
return;
|
|
351
388
|
}
|
|
352
|
-
|
|
389
|
+
if (ast.isComponent(element)) {
|
|
390
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_CUSTOM_ELEMENT, element, [
|
|
391
|
+
`<${element.name}>`,
|
|
392
|
+
]);
|
|
393
|
+
}
|
|
394
|
+
if (ast.isSlot(element)) {
|
|
395
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_ELEMENT, element, [
|
|
396
|
+
`<${element.name}>`,
|
|
397
|
+
]);
|
|
398
|
+
}
|
|
399
|
+
const { value: innerHTMLVal } = lwcInnerHtmlDirective;
|
|
400
|
+
if (!ast.isStringLiteral(innerHTMLVal) && !ast.isExpression(innerHTMLVal)) {
|
|
401
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_VALUE, element, [
|
|
402
|
+
`<${element.name}>`,
|
|
403
|
+
]);
|
|
404
|
+
}
|
|
405
|
+
element.directives.push(ast.innerHTMLDirective(innerHTMLVal, lwcInnerHtmlDirective.location));
|
|
353
406
|
}
|
|
354
|
-
function
|
|
355
|
-
const forEachAttribute = parsedAttr.pick(
|
|
356
|
-
const forItemAttribute = parsedAttr.pick(
|
|
357
|
-
const forIndex = parsedAttr.pick(
|
|
407
|
+
function parseForEach(ctx, parsedAttr, parse5ElmLocation) {
|
|
408
|
+
const forEachAttribute = parsedAttr.pick('for:each');
|
|
409
|
+
const forItemAttribute = parsedAttr.pick('for:item');
|
|
410
|
+
const forIndex = parsedAttr.pick('for:index');
|
|
358
411
|
if (forEachAttribute && forItemAttribute) {
|
|
359
|
-
if (!
|
|
360
|
-
ctx.
|
|
412
|
+
if (!ast.isExpression(forEachAttribute.value)) {
|
|
413
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.FOR_EACH_DIRECTIVE_SHOULD_BE_EXPRESSION, forEachAttribute);
|
|
361
414
|
}
|
|
362
|
-
|
|
363
|
-
|
|
415
|
+
const forItemValue = forItemAttribute.value;
|
|
416
|
+
if (!ast.isStringLiteral(forItemValue)) {
|
|
417
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.FOR_ITEM_DIRECTIVE_SHOULD_BE_STRING, forItemAttribute);
|
|
364
418
|
}
|
|
365
|
-
const item = (0, expression_1.parseIdentifier)(ctx,
|
|
419
|
+
const item = (0, expression_1.parseIdentifier)(ctx, forItemValue.value, forItemAttribute.location);
|
|
366
420
|
let index;
|
|
367
421
|
if (forIndex) {
|
|
368
|
-
|
|
369
|
-
|
|
422
|
+
const forIndexValue = forIndex.value;
|
|
423
|
+
if (!ast.isStringLiteral(forIndexValue)) {
|
|
424
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.FOR_INDEX_DIRECTIVE_SHOULD_BE_STRING, forIndex);
|
|
370
425
|
}
|
|
371
|
-
index = (0, expression_1.parseIdentifier)(ctx,
|
|
426
|
+
index = (0, expression_1.parseIdentifier)(ctx, forIndexValue.value, forIndex.location);
|
|
372
427
|
}
|
|
373
|
-
|
|
374
|
-
expression: forEachAttribute.value,
|
|
375
|
-
item,
|
|
376
|
-
index,
|
|
377
|
-
};
|
|
428
|
+
return ast.forEach(forEachAttribute.value, ast.sourceLocation(parse5ElmLocation), forEachAttribute.location, item, index);
|
|
378
429
|
}
|
|
379
430
|
else if (forEachAttribute || forItemAttribute) {
|
|
380
|
-
ctx.
|
|
431
|
+
ctx.throwAtLocation(errors_1.ParserDiagnostics.FOR_EACH_AND_FOR_ITEM_DIRECTIVES_SHOULD_BE_TOGETHER, ast.sourceLocation(parse5ElmLocation));
|
|
381
432
|
}
|
|
382
433
|
}
|
|
383
|
-
function
|
|
434
|
+
function parseForOf(ctx, parsedAttr, parse5ElmLocation) {
|
|
384
435
|
const iteratorExpression = parsedAttr.pick(constants_1.ITERATOR_RE);
|
|
385
436
|
if (!iteratorExpression) {
|
|
386
437
|
return;
|
|
387
438
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
]);
|
|
439
|
+
const hasForEach = ctx.findSibling(ast.isForEach);
|
|
440
|
+
if (hasForEach) {
|
|
441
|
+
ctx.throwAtLocation(errors_1.ParserDiagnostics.INVALID_FOR_EACH_WITH_ITERATOR, ast.sourceLocation(parse5ElmLocation), [iteratorExpression.name]);
|
|
392
442
|
}
|
|
393
443
|
const iteratorAttributeName = iteratorExpression.name;
|
|
394
444
|
const [, iteratorName] = iteratorAttributeName.split(':');
|
|
395
|
-
if (!
|
|
396
|
-
ctx.
|
|
445
|
+
if (!ast.isExpression(iteratorExpression.value)) {
|
|
446
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.DIRECTIVE_SHOULD_BE_EXPRESSION, iteratorExpression, [
|
|
397
447
|
iteratorExpression.name,
|
|
398
448
|
]);
|
|
399
449
|
}
|
|
400
450
|
const iterator = (0, expression_1.parseIdentifier)(ctx, iteratorName, iteratorExpression.location);
|
|
401
|
-
|
|
402
|
-
expression: iteratorExpression.value,
|
|
403
|
-
iterator,
|
|
404
|
-
};
|
|
451
|
+
return ast.forOf(iteratorExpression.value, iterator, ast.sourceLocation(parse5ElmLocation), iteratorExpression.location);
|
|
405
452
|
}
|
|
406
|
-
function applyKey(ctx, element,
|
|
407
|
-
const { tag } = element;
|
|
453
|
+
function applyKey(ctx, parsedAttr, element, parent) {
|
|
454
|
+
const { name: tag } = element;
|
|
408
455
|
const keyAttribute = parsedAttr.pick('key');
|
|
409
456
|
if (keyAttribute) {
|
|
410
|
-
if (!
|
|
411
|
-
ctx.
|
|
457
|
+
if (!ast.isExpression(keyAttribute.value)) {
|
|
458
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.KEY_ATTRIBUTE_SHOULD_BE_EXPRESSION, keyAttribute);
|
|
412
459
|
}
|
|
413
|
-
const forOfParent = getForOfParent(ctx);
|
|
414
|
-
const forEachParent = getForEachParent(ctx
|
|
460
|
+
const forOfParent = getForOfParent(ctx, parent);
|
|
461
|
+
const forEachParent = getForEachParent(ctx);
|
|
415
462
|
if (forOfParent) {
|
|
416
|
-
if (attributeExpressionReferencesForOfIndex(keyAttribute, forOfParent
|
|
417
|
-
ctx.
|
|
463
|
+
if (attributeExpressionReferencesForOfIndex(keyAttribute, forOfParent)) {
|
|
464
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.KEY_SHOULDNT_REFERENCE_ITERATOR_INDEX, keyAttribute, [tag]);
|
|
418
465
|
}
|
|
419
466
|
}
|
|
420
467
|
else if (forEachParent) {
|
|
421
|
-
if (attributeExpressionReferencesForEachIndex(keyAttribute, forEachParent
|
|
468
|
+
if (attributeExpressionReferencesForEachIndex(keyAttribute, forEachParent)) {
|
|
422
469
|
const name = 'name' in keyAttribute.value && keyAttribute.value.name;
|
|
423
|
-
ctx.
|
|
470
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.KEY_SHOULDNT_REFERENCE_FOR_EACH_INDEX, keyAttribute, [tag, name]);
|
|
424
471
|
}
|
|
425
472
|
}
|
|
426
|
-
element.
|
|
473
|
+
element.directives.push(ast.keyDirective(keyAttribute.value, keyAttribute.location));
|
|
427
474
|
}
|
|
428
|
-
else if (isInIteratorElement(ctx,
|
|
429
|
-
ctx.
|
|
475
|
+
else if (isInIteratorElement(ctx, parent)) {
|
|
476
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.MISSING_KEY_IN_ITERATOR, element, [tag]);
|
|
430
477
|
}
|
|
431
478
|
}
|
|
432
|
-
function
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
element.component = tag;
|
|
440
|
-
}
|
|
441
|
-
function applySlot(ctx, element, parsedAttr) {
|
|
442
|
-
// Early exit if the element is not a slot
|
|
443
|
-
if (element.tag !== 'slot') {
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
if (element.forEach || element.forOf || element.if) {
|
|
447
|
-
ctx.throwOnIRNode(errors_1.ParserDiagnostics.SLOT_TAG_CANNOT_HAVE_DIRECTIVES, element);
|
|
479
|
+
function parseSlot(ctx, parsedAttr, parse5ElmLocation) {
|
|
480
|
+
const location = ast.sourceLocation(parse5ElmLocation);
|
|
481
|
+
const hasDirectives = ctx.findSibling(ast.isForBlock) || ctx.findSibling(ast.isIf);
|
|
482
|
+
if (hasDirectives) {
|
|
483
|
+
ctx.throwAtLocation(errors_1.ParserDiagnostics.SLOT_TAG_CANNOT_HAVE_DIRECTIVES, location);
|
|
448
484
|
}
|
|
449
485
|
// Can't handle slots in applySlot because it would be too late for class and style attrs
|
|
450
|
-
if (ctx.
|
|
486
|
+
if (ctx.renderMode === types_1.LWCDirectiveRenderMode.light) {
|
|
451
487
|
const invalidAttrs = parsedAttr
|
|
452
488
|
.getAttributes()
|
|
453
489
|
.filter(({ name }) => name !== 'name')
|
|
454
490
|
.map(({ name }) => name);
|
|
455
|
-
if (invalidAttrs.length
|
|
456
|
-
|
|
491
|
+
if (invalidAttrs.length) {
|
|
492
|
+
// Light DOM slots cannot have events because there's no actual `<slot>` element
|
|
493
|
+
const eventHandler = invalidAttrs.find((name) => name.match(constants_1.EVENT_HANDLER_NAME_RE));
|
|
494
|
+
if (eventHandler) {
|
|
495
|
+
ctx.throwAtLocation(errors_1.ParserDiagnostics.LWC_LIGHT_SLOT_INVALID_EVENT_LISTENER, location, [eventHandler]);
|
|
496
|
+
}
|
|
497
|
+
ctx.throwAtLocation(errors_1.ParserDiagnostics.LWC_LIGHT_SLOT_INVALID_ATTRIBUTES, location, [
|
|
457
498
|
invalidAttrs.join(','),
|
|
458
499
|
]);
|
|
459
500
|
}
|
|
@@ -462,57 +503,58 @@ function applySlot(ctx, element, parsedAttr) {
|
|
|
462
503
|
let name = '';
|
|
463
504
|
const nameAttribute = parsedAttr.get('name');
|
|
464
505
|
if (nameAttribute) {
|
|
465
|
-
if (
|
|
466
|
-
ctx.
|
|
506
|
+
if (ast.isExpression(nameAttribute.value)) {
|
|
507
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.NAME_ON_SLOT_CANNOT_BE_EXPRESSION, nameAttribute);
|
|
467
508
|
}
|
|
468
|
-
else if (
|
|
469
|
-
name = nameAttribute.value;
|
|
509
|
+
else if (ast.isStringLiteral(nameAttribute.value)) {
|
|
510
|
+
name = nameAttribute.value.value;
|
|
470
511
|
}
|
|
471
512
|
}
|
|
472
|
-
element.slotName = name;
|
|
473
513
|
const alreadySeen = ctx.seenSlots.has(name);
|
|
474
514
|
ctx.seenSlots.add(name);
|
|
475
515
|
if (alreadySeen) {
|
|
476
|
-
|
|
516
|
+
ctx.warnAtLocation(errors_1.ParserDiagnostics.NO_DUPLICATE_SLOTS, location, [
|
|
477
517
|
name === '' ? 'default' : `name="${name}"`,
|
|
478
518
|
]);
|
|
479
519
|
}
|
|
480
|
-
else if (isInIteration(ctx
|
|
481
|
-
|
|
520
|
+
else if (isInIteration(ctx)) {
|
|
521
|
+
ctx.warnAtLocation(errors_1.ParserDiagnostics.NO_SLOTS_IN_ITERATOR, location, [
|
|
482
522
|
name === '' ? 'default' : `name="${name}"`,
|
|
483
523
|
]);
|
|
484
524
|
}
|
|
525
|
+
return ast.slot(name, parse5ElmLocation);
|
|
485
526
|
}
|
|
486
|
-
function applyAttributes(ctx,
|
|
487
|
-
const { tag } = element;
|
|
527
|
+
function applyAttributes(ctx, parsedAttr, element) {
|
|
528
|
+
const { name: tag } = element;
|
|
488
529
|
const attributes = parsedAttr.getAttributes();
|
|
530
|
+
const properties = new Map();
|
|
489
531
|
for (const attr of attributes) {
|
|
490
532
|
const { name } = attr;
|
|
491
533
|
if (!(0, attribute_1.isValidHTMLAttribute)(tag, name)) {
|
|
492
|
-
ctx.
|
|
534
|
+
ctx.warnOnNode(errors_1.ParserDiagnostics.INVALID_HTML_ATTRIBUTE, attr, [name, tag]);
|
|
493
535
|
}
|
|
494
536
|
if (name.match(/[^a-z0-9]$/)) {
|
|
495
|
-
ctx.
|
|
537
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_MUST_END_WITH_ALPHA_NUMERIC_CHARACTER, attr, [name, tag]);
|
|
496
538
|
}
|
|
497
539
|
if (!/^-*[a-z]/.test(name)) {
|
|
498
|
-
ctx.
|
|
540
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_MUST_START_WITH_ALPHABETIC_OR_HYPHEN_CHARACTER, attr, [name, tag]);
|
|
499
541
|
}
|
|
500
542
|
// disallow attr name which combines underscore character with special character.
|
|
501
543
|
// We normalize camel-cased names with underscores caMel -> ca-mel; thus sanitization.
|
|
502
544
|
if (name.match(/_[^a-z0-9]|[^a-z0-9]_/)) {
|
|
503
|
-
ctx.
|
|
545
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_CANNOT_COMBINE_UNDERSCORE_WITH_SPECIAL_CHARS, attr, [name, tag]);
|
|
504
546
|
}
|
|
505
|
-
if (
|
|
547
|
+
if (ast.isStringLiteral(attr.value)) {
|
|
506
548
|
if (name === 'id') {
|
|
507
|
-
const { value } = attr;
|
|
549
|
+
const { value } = attr.value;
|
|
508
550
|
if (/\s+/.test(value)) {
|
|
509
|
-
ctx.
|
|
551
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_ID_ATTRIBUTE, attr, [value]);
|
|
510
552
|
}
|
|
511
|
-
if (isInIteration(ctx
|
|
512
|
-
ctx.
|
|
553
|
+
if (isInIteration(ctx)) {
|
|
554
|
+
ctx.warnOnNode(errors_1.ParserDiagnostics.INVALID_STATIC_ID_IN_ITERATION, attr);
|
|
513
555
|
}
|
|
514
556
|
if (ctx.seenIds.has(value)) {
|
|
515
|
-
ctx.
|
|
557
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.DUPLICATE_ID_FOUND, attr, [value]);
|
|
516
558
|
}
|
|
517
559
|
else {
|
|
518
560
|
ctx.seenIds.add(value);
|
|
@@ -520,162 +562,171 @@ function applyAttributes(ctx, element, parsedAttr) {
|
|
|
520
562
|
}
|
|
521
563
|
}
|
|
522
564
|
// Prevent usage of the slot attribute with expression.
|
|
523
|
-
if (name === 'slot' &&
|
|
524
|
-
ctx.
|
|
565
|
+
if (name === 'slot' && ast.isExpression(attr.value)) {
|
|
566
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.SLOT_ATTRIBUTE_CANNOT_BE_EXPRESSION, attr);
|
|
525
567
|
}
|
|
526
568
|
// the if branch handles
|
|
527
569
|
// 1. All attributes for standard elements except 1 case are handled as attributes
|
|
528
570
|
// 2. For custom elements, only key, slot and data are handled as attributes, rest as properties
|
|
529
571
|
if ((0, attribute_1.isAttribute)(element, name)) {
|
|
530
|
-
|
|
531
|
-
attrs[name] = attr;
|
|
572
|
+
element.attributes.push(attr);
|
|
532
573
|
}
|
|
533
574
|
else {
|
|
534
|
-
const
|
|
535
|
-
|
|
575
|
+
const propName = (0, attribute_1.attributeToPropertyName)(name);
|
|
576
|
+
const existingProp = properties.get(propName);
|
|
577
|
+
if (existingProp) {
|
|
578
|
+
ctx.warnOnNode(errors_1.ParserDiagnostics.DUPLICATE_ATTR_PROP_TRANSFORM, attr, [
|
|
579
|
+
existingProp.attributeName,
|
|
580
|
+
name,
|
|
581
|
+
propName,
|
|
582
|
+
]);
|
|
583
|
+
}
|
|
584
|
+
properties.set(propName, ast.property(propName, name, attr.value, attr.location));
|
|
536
585
|
parsedAttr.pick(name);
|
|
537
586
|
}
|
|
538
587
|
}
|
|
588
|
+
element.properties.push(...properties.values());
|
|
539
589
|
}
|
|
540
|
-
function
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
const rootHasUnknownAttributes = node.attrs.some(({ name }) => !constants_1.ROOT_TEMPLATE_DIRECTIVES_SET.has(name));
|
|
548
|
-
if (rootHasUnknownAttributes) {
|
|
549
|
-
ctx.throwOnIRNode(errors_1.ParserDiagnostics.ROOT_TEMPLATE_HAS_UNKNOWN_ATTRIBUTES, element);
|
|
550
|
-
}
|
|
590
|
+
function validateRoot(ctx, parsedAttr, root) {
|
|
591
|
+
if (parsedAttr.getAttributes().length) {
|
|
592
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.ROOT_TEMPLATE_HAS_UNKNOWN_ATTRIBUTES, root);
|
|
593
|
+
}
|
|
594
|
+
if (!root.location.endTag) {
|
|
595
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.NO_MATCHING_CLOSING_TAGS, root, ['template']);
|
|
551
596
|
}
|
|
597
|
+
}
|
|
598
|
+
function validateElement(ctx, element, parse5Elm) {
|
|
599
|
+
const { tagName: tag, namespaceURI: namespace } = parse5Elm;
|
|
552
600
|
// Check if a non-void element has a matching closing tag.
|
|
553
601
|
//
|
|
554
602
|
// Note: Parse5 currently fails to collect end tag location for element with a tag name
|
|
555
603
|
// containing an upper case character (inikulin/parse5#352).
|
|
556
|
-
const hasClosingTag = Boolean(location.endTag);
|
|
604
|
+
const hasClosingTag = Boolean(element.location.endTag);
|
|
557
605
|
const isVoidElement = constants_1.VOID_ELEMENT_SET.has(tag);
|
|
558
606
|
if (!isVoidElement && !hasClosingTag && tag === tag.toLocaleLowerCase()) {
|
|
559
|
-
ctx.
|
|
607
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.NO_MATCHING_CLOSING_TAGS, element, [tag]);
|
|
560
608
|
}
|
|
561
609
|
if (tag === 'style' && namespace === constants_1.HTML_NAMESPACE_URI) {
|
|
562
|
-
ctx.
|
|
563
|
-
}
|
|
564
|
-
else if ((0, ir_1.isTemplate)(element)) {
|
|
565
|
-
// We check if the template element has some modifier applied to it. Directly checking if one of the
|
|
566
|
-
// IRElement property is impossible. For example when an error occurs during the parsing of the if
|
|
567
|
-
// expression, the `element.if` property remains undefined. It would results in 2 warnings instead of 1:
|
|
568
|
-
// - Invalid if expression
|
|
569
|
-
// - Unexpected template element
|
|
570
|
-
//
|
|
571
|
-
// Checking if the original HTMLElement has some attributes applied is a good enough for now.
|
|
572
|
-
if (!isRoot) {
|
|
573
|
-
if (!node.attrs.length) {
|
|
574
|
-
ctx.throwOnIRNode(errors_1.ParserDiagnostics.NO_DIRECTIVE_FOUND_ON_TEMPLATE, element);
|
|
575
|
-
}
|
|
576
|
-
// Non root templates only support for:each, iterator and if directives
|
|
577
|
-
if (element.on || element.attrs || element.props || element.forKey || element.lwc) {
|
|
578
|
-
ctx.warnOnIRNode(errors_1.ParserDiagnostics.UNKNOWN_TEMPLATE_ATTRIBUTE, element);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
610
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.STYLE_TAG_NOT_ALLOWED_IN_TEMPLATE, element);
|
|
581
611
|
}
|
|
582
612
|
else {
|
|
583
613
|
const isNotAllowedHtmlTag = constants_1.DISALLOWED_HTML_TAGS.has(tag);
|
|
584
614
|
if (namespace === constants_1.HTML_NAMESPACE_URI && isNotAllowedHtmlTag) {
|
|
585
|
-
ctx.
|
|
615
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_TAG_ON_TEMPLATE, element, [tag]);
|
|
586
616
|
}
|
|
587
617
|
const isNotAllowedSvgTag = !constants_1.SUPPORTED_SVG_TAGS.has(tag);
|
|
588
618
|
if (namespace === constants_1.SVG_NAMESPACE_URI && isNotAllowedSvgTag) {
|
|
589
|
-
ctx.
|
|
590
|
-
tag,
|
|
591
|
-
]);
|
|
619
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_SVG_NAMESPACE_IN_TEMPLATE, element, [tag]);
|
|
592
620
|
}
|
|
593
621
|
const isNotAllowedMathMlTag = constants_1.DISALLOWED_MATHML_TAGS.has(tag);
|
|
594
622
|
if (namespace === constants_1.MATHML_NAMESPACE_URI && isNotAllowedMathMlTag) {
|
|
595
|
-
ctx.
|
|
623
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_MATHML_NAMESPACE_IN_TEMPLATE, element, [
|
|
596
624
|
tag,
|
|
597
625
|
]);
|
|
598
626
|
}
|
|
599
|
-
const isKnownTag =
|
|
627
|
+
const isKnownTag = ast.isComponent(element) ||
|
|
600
628
|
constants_1.KNOWN_HTML_ELEMENTS.has(tag) ||
|
|
601
629
|
constants_1.SUPPORTED_SVG_TAGS.has(tag) ||
|
|
602
630
|
constants_1.DASHED_TAGNAME_ELEMENT_SET.has(tag);
|
|
603
631
|
if (!isKnownTag) {
|
|
604
|
-
ctx.
|
|
632
|
+
ctx.warnOnNode(errors_1.ParserDiagnostics.UNKNOWN_HTML_TAG_IN_TEMPLATE, element, [tag]);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function validateTemplate(ctx, parsedAttr, parse5Elm, parse5ElmLocation) {
|
|
637
|
+
if (parse5Elm.tagName === 'template') {
|
|
638
|
+
const location = ast.sourceLocation(parse5ElmLocation);
|
|
639
|
+
// Empty templates not allowed outside of root
|
|
640
|
+
if (!parse5Elm.attrs.length) {
|
|
641
|
+
ctx.throwAtLocation(errors_1.ParserDiagnostics.NO_DIRECTIVE_FOUND_ON_TEMPLATE, location);
|
|
642
|
+
}
|
|
643
|
+
if (parsedAttr.get(constants_1.LWC_DIRECTIVES.INNER_HTML)) {
|
|
644
|
+
ctx.throwAtLocation(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_ELEMENT, location, [
|
|
645
|
+
'<template>',
|
|
646
|
+
]);
|
|
647
|
+
}
|
|
648
|
+
// Non root templates only support for:each, iterator and if directives
|
|
649
|
+
if (parsedAttr.getAttributes().length) {
|
|
650
|
+
ctx.warnAtLocation(errors_1.ParserDiagnostics.UNKNOWN_TEMPLATE_ATTRIBUTE, location);
|
|
605
651
|
}
|
|
606
652
|
}
|
|
607
653
|
}
|
|
608
654
|
function validateChildren(ctx, element) {
|
|
609
|
-
|
|
610
|
-
|
|
655
|
+
if (!element) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const effectiveChildren = ctx.preserveComments
|
|
611
659
|
? element.children
|
|
612
|
-
: element.children.filter((child) => child
|
|
613
|
-
|
|
614
|
-
|
|
660
|
+
: element.children.filter((child) => !ast.isComment(child));
|
|
661
|
+
const hasDomDirective = element.directives.find(ast.isDomDirective);
|
|
662
|
+
if (hasDomDirective && effectiveChildren.length) {
|
|
663
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_CONTENTS, element);
|
|
615
664
|
}
|
|
616
665
|
// prevents lwc:inner-html to be used in an element with content
|
|
617
|
-
if (
|
|
618
|
-
ctx.
|
|
619
|
-
`<${element.
|
|
666
|
+
if (element.directives.find(ast.isInnerHTMLDirective) && effectiveChildren.length) {
|
|
667
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_CONTENTS, element, [
|
|
668
|
+
`<${element.name}>`,
|
|
620
669
|
]);
|
|
621
670
|
}
|
|
622
671
|
}
|
|
623
|
-
function validateAttributes(ctx,
|
|
624
|
-
const { tag } = element;
|
|
672
|
+
function validateAttributes(ctx, parsedAttr, element) {
|
|
673
|
+
const { name: tag } = element;
|
|
625
674
|
const attributes = parsedAttr.getAttributes();
|
|
626
675
|
for (const attr of attributes) {
|
|
627
|
-
const { name: attrName } = attr;
|
|
676
|
+
const { name: attrName, value: attrVal } = attr;
|
|
628
677
|
if ((0, attribute_1.isProhibitedIsAttribute)(attrName)) {
|
|
629
|
-
ctx.
|
|
678
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.IS_ATTRIBUTE_NOT_SUPPORTED, element);
|
|
630
679
|
}
|
|
631
680
|
if ((0, attribute_1.isTabIndexAttribute)(attrName)) {
|
|
632
|
-
if (!
|
|
633
|
-
ctx.
|
|
681
|
+
if (!ast.isExpression(attrVal) && !(0, attribute_1.isValidTabIndexAttributeValue)(attrVal.value)) {
|
|
682
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_TABINDEX_ATTRIBUTE, element);
|
|
634
683
|
}
|
|
635
684
|
}
|
|
636
685
|
// TODO [#1136]: once the template compiler emits the element namespace information to the engine we should
|
|
637
686
|
// restrict the validation of the "srcdoc" attribute on the "iframe" element only if this element is
|
|
638
687
|
// part of the HTML namespace.
|
|
639
688
|
if (tag === 'iframe' && attrName === 'srcdoc') {
|
|
640
|
-
ctx.
|
|
689
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_IFRAME_SRCDOC_ATTRIBUTE, element);
|
|
641
690
|
}
|
|
642
691
|
}
|
|
643
692
|
}
|
|
644
693
|
function validateProperties(ctx, element) {
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
ctx.throwOnIRNode(errors_1.ParserDiagnostics.INVALID_TABINDEX_ATTRIBUTE, element);
|
|
657
|
-
}
|
|
694
|
+
for (const prop of element.properties) {
|
|
695
|
+
const { attributeName: attrName, value } = prop;
|
|
696
|
+
if ((0, attribute_1.isProhibitedIsAttribute)(attrName)) {
|
|
697
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.IS_ATTRIBUTE_NOT_SUPPORTED, element);
|
|
698
|
+
}
|
|
699
|
+
if (
|
|
700
|
+
// tabindex is transformed to tabIndex for properties
|
|
701
|
+
(0, attribute_1.isTabIndexAttribute)(attrName) &&
|
|
702
|
+
!ast.isExpression(value) &&
|
|
703
|
+
!(0, attribute_1.isValidTabIndexAttributeValue)(value.value)) {
|
|
704
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_TABINDEX_ATTRIBUTE, element);
|
|
658
705
|
}
|
|
659
706
|
}
|
|
660
707
|
}
|
|
661
|
-
function parseAttributes(ctx,
|
|
708
|
+
function parseAttributes(ctx, parse5Elm, parse5ElmLocation) {
|
|
662
709
|
const parsedAttrs = new attribute_1.ParsedAttribute();
|
|
663
|
-
const { attrs: attributes } =
|
|
710
|
+
const { attrs: attributes, tagName } = parse5Elm;
|
|
711
|
+
const { attrs: attrLocations } = parse5ElmLocation;
|
|
664
712
|
for (const attr of attributes) {
|
|
665
|
-
|
|
713
|
+
const attrLocation = attrLocations === null || attrLocations === void 0 ? void 0 : attrLocations[(0, attribute_1.attributeName)(attr).toLowerCase()];
|
|
714
|
+
if (!attrLocation) {
|
|
715
|
+
throw new Error('An internal parsing error occurred while parsing attributes; attributes were found without a location.');
|
|
716
|
+
}
|
|
717
|
+
parsedAttrs.append(getTemplateAttribute(ctx, tagName, attr, attrLocation));
|
|
666
718
|
}
|
|
667
719
|
return parsedAttrs;
|
|
668
720
|
}
|
|
669
|
-
function getTemplateAttribute(ctx,
|
|
670
|
-
const name = (0, attribute_1.attributeName)(attribute);
|
|
721
|
+
function getTemplateAttribute(ctx, tag, attribute, attributeLocation) {
|
|
671
722
|
// Convert attribute name to lowercase because the location map keys follow the algorithm defined in the spec
|
|
672
723
|
// https://wicg.github.io/controls-list/html-output/multipage/syntax.html#attribute-name-state
|
|
673
|
-
const
|
|
674
|
-
const
|
|
675
|
-
const { tag } = element;
|
|
724
|
+
const rawAttribute = ctx.getSource(attributeLocation.startOffset, attributeLocation.endOffset);
|
|
725
|
+
const location = ast.sourceLocation(attributeLocation);
|
|
676
726
|
// parse5 automatically converts the casing from camelcase to all lowercase. If the attribute name
|
|
677
727
|
// is not the same before and after the parsing, then the attribute name contains capital letters
|
|
678
|
-
|
|
728
|
+
const attrName = (0, attribute_1.attributeName)(attribute);
|
|
729
|
+
if (!rawAttribute.startsWith(attrName)) {
|
|
679
730
|
ctx.throwAtLocation(errors_1.ParserDiagnostics.INVALID_ATTRIBUTE_CASE, location, [
|
|
680
731
|
rawAttribute,
|
|
681
732
|
tag,
|
|
@@ -683,51 +734,28 @@ function getTemplateAttribute(ctx, element, attribute) {
|
|
|
683
734
|
}
|
|
684
735
|
const isBooleanAttribute = !rawAttribute.includes('=');
|
|
685
736
|
const { value, escapedExpression } = (0, attribute_1.normalizeAttributeValue)(ctx, rawAttribute, tag, attribute, location);
|
|
737
|
+
let attrValue;
|
|
686
738
|
if ((0, expression_1.isExpression)(value) && !escapedExpression) {
|
|
687
|
-
|
|
688
|
-
name,
|
|
689
|
-
location,
|
|
690
|
-
type: types_1.IRAttributeType.Expression,
|
|
691
|
-
value: (0, expression_1.parseExpression)(ctx, value, location),
|
|
692
|
-
};
|
|
739
|
+
attrValue = (0, expression_1.parseExpression)(ctx, value, location);
|
|
693
740
|
}
|
|
694
741
|
else if (isBooleanAttribute) {
|
|
695
|
-
|
|
696
|
-
name,
|
|
697
|
-
location,
|
|
698
|
-
type: types_1.IRAttributeType.Boolean,
|
|
699
|
-
value: true,
|
|
700
|
-
};
|
|
742
|
+
attrValue = ast.literal(true);
|
|
701
743
|
}
|
|
702
744
|
else {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
type: types_1.IRAttributeType.String,
|
|
707
|
-
value,
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
function isInIteration(ctx, element) {
|
|
712
|
-
return ctx.findAncestor({
|
|
713
|
-
predicate: (element) => (0, ir_1.isTemplate)(element) && (element.forOf || element.forEach),
|
|
714
|
-
element,
|
|
715
|
-
});
|
|
745
|
+
attrValue = ast.literal(value);
|
|
746
|
+
}
|
|
747
|
+
return ast.attribute(attrName, attrValue, location);
|
|
716
748
|
}
|
|
717
|
-
function
|
|
718
|
-
return ctx.findAncestor(
|
|
719
|
-
predicate: (element) => element.forOf,
|
|
720
|
-
traversalCond: ({ current }) => (0, ir_1.isTemplate)(current),
|
|
721
|
-
});
|
|
749
|
+
function isInIteration(ctx) {
|
|
750
|
+
return !!ctx.findAncestor(ast.isForBlock);
|
|
722
751
|
}
|
|
723
|
-
function
|
|
724
|
-
return ctx.findAncestor({
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
});
|
|
752
|
+
function getForOfParent(ctx, srcNode) {
|
|
753
|
+
return ctx.findAncestor(ast.isForOf, ({ current }) => !ast.isBaseElement(current), srcNode);
|
|
754
|
+
}
|
|
755
|
+
function getForEachParent(ctx) {
|
|
756
|
+
return ctx.findAncestor(ast.isForEach, ({ parent }) => parent && !ast.isBaseElement(parent));
|
|
729
757
|
}
|
|
730
|
-
function isInIteratorElement(ctx,
|
|
731
|
-
return !!(getForOfParent(ctx) || getForEachParent(ctx
|
|
758
|
+
function isInIteratorElement(ctx, parent) {
|
|
759
|
+
return !!(getForOfParent(ctx, parent) || getForEachParent(ctx));
|
|
732
760
|
}
|
|
733
761
|
//# sourceMappingURL=index.js.map
|