@lwc/template-compiler 2.6.2 → 2.7.0
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 +12 -4
- package/dist/commonjs/parser/attribute.js.map +1 -1
- package/dist/commonjs/parser/constants.js +20 -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 +356 -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 +302 -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 +12 -7
- package/dist/types/parser/constants.d.ts +3 -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 +128 -86
- package/package.json +4 -4
- 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]);
|
|
351
381
|
}
|
|
352
|
-
|
|
382
|
+
element.directives.push(ast.domDirective(lwcDomAttr.value, lwcDomAttribute.location));
|
|
353
383
|
}
|
|
354
|
-
function
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
384
|
+
function applyLwcInnerHtmlDirective(ctx, parsedAttr, element) {
|
|
385
|
+
const lwcInnerHtmlDirective = parsedAttr.pick(constants_1.LWC_DIRECTIVES.INNER_HTML);
|
|
386
|
+
if (!lwcInnerHtmlDirective) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
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));
|
|
406
|
+
}
|
|
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,57 @@ 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();
|
|
489
530
|
for (const attr of attributes) {
|
|
490
531
|
const { name } = attr;
|
|
491
532
|
if (!(0, attribute_1.isValidHTMLAttribute)(tag, name)) {
|
|
492
|
-
ctx.
|
|
533
|
+
ctx.warnOnNode(errors_1.ParserDiagnostics.INVALID_HTML_ATTRIBUTE, attr, [name, tag]);
|
|
493
534
|
}
|
|
494
535
|
if (name.match(/[^a-z0-9]$/)) {
|
|
495
|
-
ctx.
|
|
536
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_MUST_END_WITH_ALPHA_NUMERIC_CHARACTER, attr, [name, tag]);
|
|
496
537
|
}
|
|
497
538
|
if (!/^-*[a-z]/.test(name)) {
|
|
498
|
-
ctx.
|
|
539
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_MUST_START_WITH_ALPHABETIC_OR_HYPHEN_CHARACTER, attr, [name, tag]);
|
|
499
540
|
}
|
|
500
541
|
// disallow attr name which combines underscore character with special character.
|
|
501
542
|
// We normalize camel-cased names with underscores caMel -> ca-mel; thus sanitization.
|
|
502
543
|
if (name.match(/_[^a-z0-9]|[^a-z0-9]_/)) {
|
|
503
|
-
ctx.
|
|
544
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_CANNOT_COMBINE_UNDERSCORE_WITH_SPECIAL_CHARS, attr, [name, tag]);
|
|
504
545
|
}
|
|
505
|
-
if (
|
|
546
|
+
if (ast.isStringLiteral(attr.value)) {
|
|
506
547
|
if (name === 'id') {
|
|
507
|
-
const { value } = attr;
|
|
548
|
+
const { value } = attr.value;
|
|
508
549
|
if (/\s+/.test(value)) {
|
|
509
|
-
ctx.
|
|
550
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_ID_ATTRIBUTE, attr, [value]);
|
|
510
551
|
}
|
|
511
|
-
if (isInIteration(ctx
|
|
512
|
-
ctx.
|
|
552
|
+
if (isInIteration(ctx)) {
|
|
553
|
+
ctx.warnOnNode(errors_1.ParserDiagnostics.INVALID_STATIC_ID_IN_ITERATION, attr);
|
|
513
554
|
}
|
|
514
555
|
if (ctx.seenIds.has(value)) {
|
|
515
|
-
ctx.
|
|
556
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.DUPLICATE_ID_FOUND, attr, [value]);
|
|
516
557
|
}
|
|
517
558
|
else {
|
|
518
559
|
ctx.seenIds.add(value);
|
|
@@ -520,162 +561,163 @@ function applyAttributes(ctx, element, parsedAttr) {
|
|
|
520
561
|
}
|
|
521
562
|
}
|
|
522
563
|
// Prevent usage of the slot attribute with expression.
|
|
523
|
-
if (name === 'slot' &&
|
|
524
|
-
ctx.
|
|
564
|
+
if (name === 'slot' && ast.isExpression(attr.value)) {
|
|
565
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.SLOT_ATTRIBUTE_CANNOT_BE_EXPRESSION, attr);
|
|
525
566
|
}
|
|
526
567
|
// the if branch handles
|
|
527
568
|
// 1. All attributes for standard elements except 1 case are handled as attributes
|
|
528
569
|
// 2. For custom elements, only key, slot and data are handled as attributes, rest as properties
|
|
529
570
|
if ((0, attribute_1.isAttribute)(element, name)) {
|
|
530
|
-
|
|
531
|
-
attrs[name] = attr;
|
|
571
|
+
element.attributes.push(attr);
|
|
532
572
|
}
|
|
533
573
|
else {
|
|
534
|
-
const
|
|
535
|
-
|
|
574
|
+
const propName = (0, attribute_1.attributeToPropertyName)(name);
|
|
575
|
+
element.properties.push(ast.property(propName, attr.value, attr.location));
|
|
536
576
|
parsedAttr.pick(name);
|
|
537
577
|
}
|
|
538
578
|
}
|
|
539
579
|
}
|
|
540
|
-
function
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (isRoot) {
|
|
544
|
-
if (!(0, ir_1.isTemplate)(element)) {
|
|
545
|
-
ctx.throwOnIRNode(errors_1.ParserDiagnostics.ROOT_TAG_SHOULD_BE_TEMPLATE, element, [tag]);
|
|
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
|
-
}
|
|
580
|
+
function validateRoot(ctx, parsedAttr, root) {
|
|
581
|
+
if (parsedAttr.getAttributes().length) {
|
|
582
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.ROOT_TEMPLATE_HAS_UNKNOWN_ATTRIBUTES, root);
|
|
551
583
|
}
|
|
584
|
+
if (!root.location.endTag) {
|
|
585
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.NO_MATCHING_CLOSING_TAGS, root, ['template']);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function validateElement(ctx, element, parse5Elm) {
|
|
589
|
+
const { tagName: tag, namespaceURI: namespace } = parse5Elm;
|
|
552
590
|
// Check if a non-void element has a matching closing tag.
|
|
553
591
|
//
|
|
554
592
|
// Note: Parse5 currently fails to collect end tag location for element with a tag name
|
|
555
593
|
// containing an upper case character (inikulin/parse5#352).
|
|
556
|
-
const hasClosingTag = Boolean(location.endTag);
|
|
594
|
+
const hasClosingTag = Boolean(element.location.endTag);
|
|
557
595
|
const isVoidElement = constants_1.VOID_ELEMENT_SET.has(tag);
|
|
558
596
|
if (!isVoidElement && !hasClosingTag && tag === tag.toLocaleLowerCase()) {
|
|
559
|
-
ctx.
|
|
597
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.NO_MATCHING_CLOSING_TAGS, element, [tag]);
|
|
560
598
|
}
|
|
561
599
|
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
|
-
}
|
|
600
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.STYLE_TAG_NOT_ALLOWED_IN_TEMPLATE, element);
|
|
581
601
|
}
|
|
582
602
|
else {
|
|
583
603
|
const isNotAllowedHtmlTag = constants_1.DISALLOWED_HTML_TAGS.has(tag);
|
|
584
604
|
if (namespace === constants_1.HTML_NAMESPACE_URI && isNotAllowedHtmlTag) {
|
|
585
|
-
ctx.
|
|
605
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_TAG_ON_TEMPLATE, element, [tag]);
|
|
586
606
|
}
|
|
587
607
|
const isNotAllowedSvgTag = !constants_1.SUPPORTED_SVG_TAGS.has(tag);
|
|
588
608
|
if (namespace === constants_1.SVG_NAMESPACE_URI && isNotAllowedSvgTag) {
|
|
589
|
-
ctx.
|
|
590
|
-
tag,
|
|
591
|
-
]);
|
|
609
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_SVG_NAMESPACE_IN_TEMPLATE, element, [tag]);
|
|
592
610
|
}
|
|
593
611
|
const isNotAllowedMathMlTag = constants_1.DISALLOWED_MATHML_TAGS.has(tag);
|
|
594
612
|
if (namespace === constants_1.MATHML_NAMESPACE_URI && isNotAllowedMathMlTag) {
|
|
595
|
-
ctx.
|
|
613
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_MATHML_NAMESPACE_IN_TEMPLATE, element, [
|
|
596
614
|
tag,
|
|
597
615
|
]);
|
|
598
616
|
}
|
|
599
|
-
const isKnownTag =
|
|
617
|
+
const isKnownTag = ast.isComponent(element) ||
|
|
600
618
|
constants_1.KNOWN_HTML_ELEMENTS.has(tag) ||
|
|
601
619
|
constants_1.SUPPORTED_SVG_TAGS.has(tag) ||
|
|
602
620
|
constants_1.DASHED_TAGNAME_ELEMENT_SET.has(tag);
|
|
603
621
|
if (!isKnownTag) {
|
|
604
|
-
ctx.
|
|
622
|
+
ctx.warnOnNode(errors_1.ParserDiagnostics.UNKNOWN_HTML_TAG_IN_TEMPLATE, element, [tag]);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function validateTemplate(ctx, parsedAttr, parse5Elm, parse5ElmLocation) {
|
|
627
|
+
if (parse5Elm.tagName === 'template') {
|
|
628
|
+
const location = ast.sourceLocation(parse5ElmLocation);
|
|
629
|
+
// Empty templates not allowed outside of root
|
|
630
|
+
if (!parse5Elm.attrs.length) {
|
|
631
|
+
ctx.throwAtLocation(errors_1.ParserDiagnostics.NO_DIRECTIVE_FOUND_ON_TEMPLATE, location);
|
|
632
|
+
}
|
|
633
|
+
if (parsedAttr.get(constants_1.LWC_DIRECTIVES.INNER_HTML)) {
|
|
634
|
+
ctx.throwAtLocation(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_ELEMENT, location, [
|
|
635
|
+
'<template>',
|
|
636
|
+
]);
|
|
637
|
+
}
|
|
638
|
+
// Non root templates only support for:each, iterator and if directives
|
|
639
|
+
if (parsedAttr.getAttributes().length) {
|
|
640
|
+
ctx.warnAtLocation(errors_1.ParserDiagnostics.UNKNOWN_TEMPLATE_ATTRIBUTE, location);
|
|
605
641
|
}
|
|
606
642
|
}
|
|
607
643
|
}
|
|
608
644
|
function validateChildren(ctx, element) {
|
|
609
|
-
|
|
610
|
-
|
|
645
|
+
if (!element) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
const effectiveChildren = ctx.preserveComments
|
|
611
649
|
? element.children
|
|
612
|
-
: element.children.filter((child) => child
|
|
613
|
-
|
|
614
|
-
|
|
650
|
+
: element.children.filter((child) => !ast.isComment(child));
|
|
651
|
+
const hasDomDirective = element.directives.find(ast.isDomDirective);
|
|
652
|
+
if (hasDomDirective && effectiveChildren.length) {
|
|
653
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_CONTENTS, element);
|
|
615
654
|
}
|
|
616
655
|
// prevents lwc:inner-html to be used in an element with content
|
|
617
|
-
if (
|
|
618
|
-
ctx.
|
|
619
|
-
`<${element.
|
|
656
|
+
if (element.directives.find(ast.isInnerHTMLDirective) && effectiveChildren.length) {
|
|
657
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_CONTENTS, element, [
|
|
658
|
+
`<${element.name}>`,
|
|
620
659
|
]);
|
|
621
660
|
}
|
|
622
661
|
}
|
|
623
|
-
function validateAttributes(ctx,
|
|
624
|
-
const { tag } = element;
|
|
662
|
+
function validateAttributes(ctx, parsedAttr, element) {
|
|
663
|
+
const { name: tag } = element;
|
|
625
664
|
const attributes = parsedAttr.getAttributes();
|
|
626
665
|
for (const attr of attributes) {
|
|
627
|
-
const { name: attrName } = attr;
|
|
666
|
+
const { name: attrName, value: attrVal } = attr;
|
|
628
667
|
if ((0, attribute_1.isProhibitedIsAttribute)(attrName)) {
|
|
629
|
-
ctx.
|
|
668
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.IS_ATTRIBUTE_NOT_SUPPORTED, element);
|
|
630
669
|
}
|
|
631
670
|
if ((0, attribute_1.isTabIndexAttribute)(attrName)) {
|
|
632
|
-
if (!
|
|
633
|
-
ctx.
|
|
671
|
+
if (!ast.isExpression(attrVal) && !(0, attribute_1.isValidTabIndexAttributeValue)(attrVal.value)) {
|
|
672
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_TABINDEX_ATTRIBUTE, element);
|
|
634
673
|
}
|
|
635
674
|
}
|
|
636
675
|
// TODO [#1136]: once the template compiler emits the element namespace information to the engine we should
|
|
637
676
|
// restrict the validation of the "srcdoc" attribute on the "iframe" element only if this element is
|
|
638
677
|
// part of the HTML namespace.
|
|
639
678
|
if (tag === 'iframe' && attrName === 'srcdoc') {
|
|
640
|
-
ctx.
|
|
679
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_IFRAME_SRCDOC_ATTRIBUTE, element);
|
|
641
680
|
}
|
|
642
681
|
}
|
|
643
682
|
}
|
|
644
683
|
function validateProperties(ctx, element) {
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
684
|
+
for (const prop of element.properties) {
|
|
685
|
+
const { name, value } = prop;
|
|
686
|
+
const attrName = (0, attribute_1.propertyToAttributeName)(name);
|
|
687
|
+
if ((0, attribute_1.isProhibitedIsAttribute)(attrName)) {
|
|
688
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.IS_ATTRIBUTE_NOT_SUPPORTED, element);
|
|
689
|
+
}
|
|
690
|
+
if (
|
|
691
|
+
// tabindex is transformed to tabIndex for properties
|
|
692
|
+
(0, attribute_1.isTabIndexAttribute)(attrName) &&
|
|
693
|
+
!ast.isExpression(value) &&
|
|
694
|
+
!(0, attribute_1.isValidTabIndexAttributeValue)(value.value)) {
|
|
695
|
+
ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_TABINDEX_ATTRIBUTE, element);
|
|
658
696
|
}
|
|
659
697
|
}
|
|
660
698
|
}
|
|
661
|
-
function parseAttributes(ctx,
|
|
699
|
+
function parseAttributes(ctx, parse5Elm, parse5ElmLocation) {
|
|
662
700
|
const parsedAttrs = new attribute_1.ParsedAttribute();
|
|
663
|
-
const { attrs: attributes } =
|
|
701
|
+
const { attrs: attributes, tagName } = parse5Elm;
|
|
702
|
+
const { attrs: attrLocations } = parse5ElmLocation;
|
|
664
703
|
for (const attr of attributes) {
|
|
665
|
-
|
|
704
|
+
const attrLocation = attrLocations === null || attrLocations === void 0 ? void 0 : attrLocations[(0, attribute_1.attributeName)(attr).toLowerCase()];
|
|
705
|
+
if (!attrLocation) {
|
|
706
|
+
throw new Error('An internal parsing error occurred while parsing attributes; attributes were found without a location.');
|
|
707
|
+
}
|
|
708
|
+
parsedAttrs.append(getTemplateAttribute(ctx, tagName, attr, attrLocation));
|
|
666
709
|
}
|
|
667
710
|
return parsedAttrs;
|
|
668
711
|
}
|
|
669
|
-
function getTemplateAttribute(ctx,
|
|
670
|
-
const name = (0, attribute_1.attributeName)(attribute);
|
|
712
|
+
function getTemplateAttribute(ctx, tag, attribute, attributeLocation) {
|
|
671
713
|
// Convert attribute name to lowercase because the location map keys follow the algorithm defined in the spec
|
|
672
714
|
// https://wicg.github.io/controls-list/html-output/multipage/syntax.html#attribute-name-state
|
|
673
|
-
const
|
|
674
|
-
const
|
|
675
|
-
const { tag } = element;
|
|
715
|
+
const rawAttribute = ctx.getSource(attributeLocation.startOffset, attributeLocation.endOffset);
|
|
716
|
+
const location = ast.sourceLocation(attributeLocation);
|
|
676
717
|
// parse5 automatically converts the casing from camelcase to all lowercase. If the attribute name
|
|
677
718
|
// is not the same before and after the parsing, then the attribute name contains capital letters
|
|
678
|
-
|
|
719
|
+
const attrName = (0, attribute_1.attributeName)(attribute);
|
|
720
|
+
if (!rawAttribute.startsWith(attrName)) {
|
|
679
721
|
ctx.throwAtLocation(errors_1.ParserDiagnostics.INVALID_ATTRIBUTE_CASE, location, [
|
|
680
722
|
rawAttribute,
|
|
681
723
|
tag,
|
|
@@ -683,51 +725,28 @@ function getTemplateAttribute(ctx, element, attribute) {
|
|
|
683
725
|
}
|
|
684
726
|
const isBooleanAttribute = !rawAttribute.includes('=');
|
|
685
727
|
const { value, escapedExpression } = (0, attribute_1.normalizeAttributeValue)(ctx, rawAttribute, tag, attribute, location);
|
|
728
|
+
let attrValue;
|
|
686
729
|
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
|
-
};
|
|
730
|
+
attrValue = (0, expression_1.parseExpression)(ctx, value, location);
|
|
693
731
|
}
|
|
694
732
|
else if (isBooleanAttribute) {
|
|
695
|
-
|
|
696
|
-
name,
|
|
697
|
-
location,
|
|
698
|
-
type: types_1.IRAttributeType.Boolean,
|
|
699
|
-
value: true,
|
|
700
|
-
};
|
|
733
|
+
attrValue = ast.literal(true);
|
|
701
734
|
}
|
|
702
735
|
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
|
-
});
|
|
736
|
+
attrValue = ast.literal(value);
|
|
737
|
+
}
|
|
738
|
+
return ast.attribute(attrName, attrValue, location);
|
|
716
739
|
}
|
|
717
|
-
function
|
|
718
|
-
return ctx.findAncestor(
|
|
719
|
-
predicate: (element) => element.forOf,
|
|
720
|
-
traversalCond: ({ current }) => (0, ir_1.isTemplate)(current),
|
|
721
|
-
});
|
|
740
|
+
function isInIteration(ctx) {
|
|
741
|
+
return !!ctx.findAncestor(ast.isForBlock);
|
|
722
742
|
}
|
|
723
|
-
function
|
|
724
|
-
return ctx.findAncestor({
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
});
|
|
743
|
+
function getForOfParent(ctx, srcNode) {
|
|
744
|
+
return ctx.findAncestor(ast.isForOf, ({ current }) => !ast.isBaseElement(current), srcNode);
|
|
745
|
+
}
|
|
746
|
+
function getForEachParent(ctx) {
|
|
747
|
+
return ctx.findAncestor(ast.isForEach, ({ parent }) => parent && !ast.isBaseElement(parent));
|
|
729
748
|
}
|
|
730
|
-
function isInIteratorElement(ctx,
|
|
731
|
-
return !!(getForOfParent(ctx) || getForEachParent(ctx
|
|
749
|
+
function isInIteratorElement(ctx, parent) {
|
|
750
|
+
return !!(getForOfParent(ctx, parent) || getForEachParent(ctx));
|
|
732
751
|
}
|
|
733
752
|
//# sourceMappingURL=index.js.map
|