@lwc/template-compiler 2.7.1 → 2.7.2

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