@lwc/template-compiler 2.7.2 → 2.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/commonjs/codegen/codegen.js +73 -3
  2. package/dist/commonjs/codegen/codegen.js.map +1 -1
  3. package/dist/commonjs/codegen/helpers.js +34 -20
  4. package/dist/commonjs/codegen/helpers.js.map +1 -1
  5. package/dist/commonjs/codegen/index.js +183 -192
  6. package/dist/commonjs/codegen/index.js.map +1 -1
  7. package/dist/commonjs/index.js +12 -5
  8. package/dist/commonjs/index.js.map +1 -1
  9. package/dist/commonjs/parser/attribute.js +3 -3
  10. package/dist/commonjs/parser/attribute.js.map +1 -1
  11. package/dist/commonjs/parser/constants.js +1 -7
  12. package/dist/commonjs/parser/constants.js.map +1 -1
  13. package/dist/commonjs/parser/expression.js +5 -2
  14. package/dist/commonjs/parser/expression.js.map +1 -1
  15. package/dist/commonjs/parser/html.js +2 -1
  16. package/dist/commonjs/parser/html.js.map +1 -1
  17. package/dist/commonjs/parser/index.js +365 -337
  18. package/dist/commonjs/parser/index.js.map +1 -1
  19. package/dist/commonjs/parser/parser.js +85 -30
  20. package/dist/commonjs/parser/parser.js.map +1 -1
  21. package/dist/commonjs/shared/ast.js +303 -0
  22. package/dist/commonjs/shared/ast.js.map +1 -0
  23. package/dist/commonjs/shared/parse5.js +1 -15
  24. package/dist/commonjs/shared/parse5.js.map +1 -1
  25. package/dist/commonjs/shared/types.js +1 -7
  26. package/dist/commonjs/shared/types.js.map +1 -1
  27. package/dist/types/codegen/codegen.d.ts +25 -3
  28. package/dist/types/codegen/helpers.d.ts +11 -5
  29. package/dist/types/codegen/index.d.ts +2 -2
  30. package/dist/types/index.d.ts +1 -2
  31. package/dist/types/parser/attribute.d.ts +7 -7
  32. package/dist/types/parser/constants.d.ts +0 -6
  33. package/dist/types/parser/expression.d.ts +3 -4
  34. package/dist/types/parser/parser.d.ts +57 -28
  35. package/dist/types/shared/ast.d.ts +45 -0
  36. package/dist/types/shared/estree.d.ts +0 -2
  37. package/dist/types/shared/parse5.d.ts +0 -1
  38. package/dist/types/shared/types.d.ts +129 -86
  39. package/package.json +4 -4
  40. package/dist/commonjs/codegen/scope.js +0 -61
  41. package/dist/commonjs/codegen/scope.js.map +0 -1
  42. package/dist/commonjs/shared/ir.js +0 -90
  43. package/dist/commonjs/shared/ir.js.map +0 -1
  44. package/dist/types/codegen/scope.d.ts +0 -8
  45. 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 ir_1 = require("../shared/ir");
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 parseElement(ctx, templateRoot);
67
+ return parseRoot(ctx, templateRoot);
69
68
  });
70
69
  return { root, warnings: ctx.warnings };
71
70
  }
72
71
  exports.default = parse;
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;
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 parseElementLocation(ctx, parse5Elm, parentIRElement) {
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
- parentIRElement === null || parentIRElement === void 0 ? void 0 : parentIRElement.tag,
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
- // 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();
134
+ return location !== null && location !== void 0 ? location : parse5ParentLocation;
116
135
  }
117
- function parseChildren(ctx, parse5Parent, parentIRElement) {
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
- const elmNode = parseElement(ctx, child, parentIRElement);
126
- parsedChildren.push(elmNode);
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
- parsedChildren.push(...textNodes);
185
+ parent.children.push(...textNodes);
131
186
  }
132
187
  else if (parse5Utils.isCommentNode(child)) {
133
188
  const commentNode = parseComment(child);
134
- parsedChildren.push(commentNode);
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(`An internal parsing error occurred during node creation; a text node was found without a sourceCodeLocation.`);
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((0, ir_1.createText)(value, location));
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(`An internal parsing error occurred during node creation; a comment node was found without a sourceCodeLocation.`);
232
+ throw new Error('An internal parsing error occurred during node creation; a comment node was found without a sourceCodeLocation.');
180
233
  }
181
- return (0, ir_1.createComment)((0, html_1.decodeTextContent)(parse5Comment.data), location);
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.throwAtLocation(errors_1.ParserDiagnostics.MULTIPLE_ROOTS_FOUND, duplicateRoot);
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, element, parsedAttr) {
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 (!(0, ir_1.isIRExpressionAttribute)(eventHandlerAttribute)) {
202
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.EVENT_HANDLER_SHOULD_BE_EXPRESSION, eventHandlerAttribute);
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.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
- ]);
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 on = element.on || (element.on = {});
216
- on[eventName] = eventHandlerAttribute.value;
262
+ const listener = ast.eventListener(eventName, eventHandlerAttribute.value, eventHandlerAttribute.location);
263
+ element.listeners.push(listener);
217
264
  }
218
265
  }
219
- function applyIf(ctx, element, parsedAttr) {
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 (!(0, ir_1.isIRExpressionAttribute)(ifAttribute)) {
225
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.IF_DIRECTIVE_SHOULD_BE_EXPRESSION, ifAttribute);
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.throwOnIRNode(errors_1.ParserDiagnostics.UNEXPECTED_IF_MODIFIER, ifAttribute, [modifier]);
276
+ ctx.throwOnNode(errors_1.ParserDiagnostics.UNEXPECTED_IF_MODIFIER, ifAttribute, [modifier]);
230
277
  }
231
- element.if = ifAttribute.value;
232
- element.ifModifier = modifier;
278
+ return ast.ifNode(modifier, ifAttribute.value, ast.sourceLocation(parse5ElmLocation), ifAttribute.location);
233
279
  }
234
- function applyLwcDirectives(ctx, element, parsedAttr) {
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
- 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;
285
+ applyLwcRenderModeDirective(ctx, parsedAttr, root);
286
+ applyLwcPreserveCommentsDirective(ctx, parsedAttr, root);
253
287
  }
254
- function applyLwcRenderModeDirective(ctx, element, parsedAttr, lwcOpts) {
255
- const lwcRenderModeAttribute = parsedAttr.get(constants_1.ROOT_TEMPLATE_DIRECTIVES.RENDER_MODE);
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
- 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);
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
- 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;
299
+ root.directives.push(ast.renderModeDirective(renderDomAttr.value, lwcRenderModeAttribute.location));
270
300
  }
271
- function applyLwcPreserveCommentsDirective(ctx, element, parsedAttr, lwcOpts) {
272
- const lwcPreserveCommentAttribute = parsedAttr.get(constants_1.ROOT_TEMPLATE_DIRECTIVES.PRESERVE_COMMENTS);
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
- 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);
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
- lwcOpts.preserveComments = lwcPreserveCommentAttribute;
310
+ root.directives.push(ast.preserveCommentsDirective(lwcPreserveCommentsAttr.value, lwcPreserveCommentAttribute.location));
286
311
  }
287
- function applyLwcInnerHtmlDirective(ctx, element, parsedAttr, lwcOpts) {
288
- const lwcInnerHtmlDirective = parsedAttr.pick(constants_1.LWC_DIRECTIVES.INNER_HTML);
289
- if (!lwcInnerHtmlDirective) {
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 ((0, ir_1.isCustomElement)(element)) {
293
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_CUSTOM_ELEMENT, element, [
294
- `<${element.tag}>`,
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
- if (element.tag === 'slot' || element.tag === 'template') {
298
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_ELEMENT, element, [
299
- `<${element.tag}>`,
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 (lwcInnerHtmlDirective.type === types_1.IRAttributeType.Boolean) {
303
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_VALUE, element, [
304
- `<${element.tag}>`,
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
- lwcOpts.innerHTML = lwcInnerHtmlDirective.value;
336
+ applyLwcDynamicDirective(ctx, parsedAttr, element);
337
+ applyLwcDomDirective(ctx, parsedAttr, element);
338
+ applyLwcInnerHtmlDirective(ctx, parsedAttr, element);
308
339
  }
309
- function applyLwcDynamicDirective(ctx, element, parsedAttr, lwcOpts) {
310
- const { tag } = element;
311
- const lwcDynamicAttribute = parsedAttr.pick(constants_1.LWC_DIRECTIVES.DYNAMIC);
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.throwOnIRNode(errors_1.ParserDiagnostics.INVALID_OPTS_LWC_DYNAMIC, element);
347
+ ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_OPTS_LWC_DYNAMIC, element);
317
348
  }
318
- if (!(0, ir_1.isCustomElement)(element)) {
319
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.INVALID_LWC_DYNAMIC_ON_NATIVE_ELEMENT, element, [
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
- if (!(0, ir_1.isIRExpressionAttribute)(lwcDynamicAttribute)) {
324
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.INVALID_LWC_DYNAMIC_LITERAL_PROP, element, [
325
- `<${tag}>`,
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
- lwcOpts.dynamic = lwcDynamicAttribute.value;
358
+ element.directives.push(ast.dynamicDirective(lwcDynamicAttr, lwcDynamicAttr.location));
329
359
  }
330
- function applyLwcDomDirective(ctx, element, parsedAttr, lwcOpts) {
331
- const { tag } = element;
332
- const lwcDomAttribute = parsedAttr.pick(constants_1.LWC_DIRECTIVES.DOM);
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.getRenderMode(element) === types_1.LWCDirectiveRenderMode.light) {
337
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_IN_LIGHT_DOM, element, [`<${tag}>`]);
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 ((0, ir_1.isCustomElement)(element)) {
340
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_CUSTOM_ELEMENT, element, [`<${tag}>`]);
369
+ if (ast.isComponent(element)) {
370
+ ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_CUSTOM_ELEMENT, element, [`<${tag}>`]);
341
371
  }
342
- if (tag === 'slot') {
343
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_SLOT_ELEMENT, element);
372
+ if (ast.isSlot(element)) {
373
+ ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_SLOT_ELEMENT, element);
344
374
  }
345
- if (!(0, ir_1.isIRStringAttribute)(lwcDomAttribute) ||
346
- shared_1.hasOwnProperty.call(types_1.LWCDirectiveDomMode, lwcDomAttribute.value) === false) {
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.throwOnIRNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_VALUE, element, [possibleValues]);
380
+ ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_VALUE, element, [possibleValues]);
381
+ }
382
+ element.directives.push(ast.domDirective(lwcDomAttr.value, lwcDomAttribute.location));
383
+ }
384
+ function applyLwcInnerHtmlDirective(ctx, parsedAttr, element) {
385
+ const lwcInnerHtmlDirective = parsedAttr.pick(constants_1.LWC_DIRECTIVES.INNER_HTML);
386
+ if (!lwcInnerHtmlDirective) {
387
+ return;
351
388
  }
352
- lwcOpts.dom = lwcDomAttribute.value;
389
+ if (ast.isComponent(element)) {
390
+ ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_CUSTOM_ELEMENT, element, [
391
+ `<${element.name}>`,
392
+ ]);
393
+ }
394
+ if (ast.isSlot(element)) {
395
+ ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_ELEMENT, element, [
396
+ `<${element.name}>`,
397
+ ]);
398
+ }
399
+ const { value: innerHTMLVal } = lwcInnerHtmlDirective;
400
+ if (!ast.isStringLiteral(innerHTMLVal) && !ast.isExpression(innerHTMLVal)) {
401
+ ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_VALUE, element, [
402
+ `<${element.name}>`,
403
+ ]);
404
+ }
405
+ element.directives.push(ast.innerHTMLDirective(innerHTMLVal, lwcInnerHtmlDirective.location));
353
406
  }
354
- function 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);
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 (!(0, ir_1.isIRExpressionAttribute)(forEachAttribute)) {
360
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.FOR_EACH_DIRECTIVE_SHOULD_BE_EXPRESSION, forEachAttribute);
412
+ if (!ast.isExpression(forEachAttribute.value)) {
413
+ ctx.throwOnNode(errors_1.ParserDiagnostics.FOR_EACH_DIRECTIVE_SHOULD_BE_EXPRESSION, forEachAttribute);
361
414
  }
362
- if (!(0, ir_1.isIRStringAttribute)(forItemAttribute)) {
363
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.FOR_ITEM_DIRECTIVE_SHOULD_BE_STRING, forItemAttribute);
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, forItemAttribute.value, forItemAttribute.location);
419
+ const item = (0, expression_1.parseIdentifier)(ctx, forItemValue.value, forItemAttribute.location);
366
420
  let index;
367
421
  if (forIndex) {
368
- if (!(0, ir_1.isIRStringAttribute)(forIndex)) {
369
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.FOR_INDEX_DIRECTIVE_SHOULD_BE_STRING, forIndex);
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, forIndex.value, forIndex.location);
426
+ index = (0, expression_1.parseIdentifier)(ctx, forIndexValue.value, forIndex.location);
372
427
  }
373
- element.forEach = {
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.throwOnIRNode(errors_1.ParserDiagnostics.FOR_EACH_AND_FOR_ITEM_DIRECTIVES_SHOULD_BE_TOGETHER, element);
431
+ ctx.throwAtLocation(errors_1.ParserDiagnostics.FOR_EACH_AND_FOR_ITEM_DIRECTIVES_SHOULD_BE_TOGETHER, ast.sourceLocation(parse5ElmLocation));
381
432
  }
382
433
  }
383
- function applyIterator(ctx, element, parsedAttr) {
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
- if (element.forEach) {
389
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.INVALID_FOR_EACH_WITH_ITERATOR, element, [
390
- iteratorExpression.name,
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 (!(0, ir_1.isIRExpressionAttribute)(iteratorExpression)) {
396
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.DIRECTIVE_SHOULD_BE_EXPRESSION, iteratorExpression, [
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
- element.forOf = {
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, parsedAttr) {
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 (!(0, ir_1.isIRExpressionAttribute)(keyAttribute)) {
411
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.KEY_ATTRIBUTE_SHOULD_BE_EXPRESSION, keyAttribute);
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, element);
460
+ const forOfParent = getForOfParent(ctx, parent);
461
+ const forEachParent = getForEachParent(ctx);
415
462
  if (forOfParent) {
416
- if (attributeExpressionReferencesForOfIndex(keyAttribute, forOfParent.forOf)) {
417
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.KEY_SHOULDNT_REFERENCE_ITERATOR_INDEX, keyAttribute, [tag]);
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.forEach)) {
468
+ if (attributeExpressionReferencesForEachIndex(keyAttribute, forEachParent)) {
422
469
  const name = 'name' in keyAttribute.value && keyAttribute.value.name;
423
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.KEY_SHOULDNT_REFERENCE_FOR_EACH_INDEX, keyAttribute, [tag, name]);
470
+ ctx.throwOnNode(errors_1.ParserDiagnostics.KEY_SHOULDNT_REFERENCE_FOR_EACH_INDEX, keyAttribute, [tag, name]);
424
471
  }
425
472
  }
426
- element.forKey = keyAttribute.value;
473
+ element.directives.push(ast.keyDirective(keyAttribute.value, keyAttribute.location));
427
474
  }
428
- else if (isInIteratorElement(ctx, element) && !(0, ir_1.isTemplate)(element)) {
429
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.MISSING_KEY_IN_ITERATOR, element, [tag]);
475
+ else if (isInIteratorElement(ctx, parent)) {
476
+ ctx.throwOnNode(errors_1.ParserDiagnostics.MISSING_KEY_IN_ITERATOR, element, [tag]);
430
477
  }
431
478
  }
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);
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.getRenderMode(element) === types_1.LWCDirectiveRenderMode.light) {
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 > 0) {
456
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.LWC_LIGHT_SLOT_INVALID_ATTRIBUTES, element, [
491
+ if (invalidAttrs.length) {
492
+ // Light DOM slots cannot have events because there's no actual `<slot>` element
493
+ const eventHandler = invalidAttrs.find((name) => name.match(constants_1.EVENT_HANDLER_NAME_RE));
494
+ if (eventHandler) {
495
+ ctx.throwAtLocation(errors_1.ParserDiagnostics.LWC_LIGHT_SLOT_INVALID_EVENT_LISTENER, location, [eventHandler]);
496
+ }
497
+ ctx.throwAtLocation(errors_1.ParserDiagnostics.LWC_LIGHT_SLOT_INVALID_ATTRIBUTES, location, [
457
498
  invalidAttrs.join(','),
458
499
  ]);
459
500
  }
@@ -462,57 +503,58 @@ function applySlot(ctx, element, parsedAttr) {
462
503
  let name = '';
463
504
  const nameAttribute = parsedAttr.get('name');
464
505
  if (nameAttribute) {
465
- if ((0, ir_1.isIRExpressionAttribute)(nameAttribute)) {
466
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.NAME_ON_SLOT_CANNOT_BE_EXPRESSION, nameAttribute);
506
+ if (ast.isExpression(nameAttribute.value)) {
507
+ ctx.throwOnNode(errors_1.ParserDiagnostics.NAME_ON_SLOT_CANNOT_BE_EXPRESSION, nameAttribute);
467
508
  }
468
- else if ((0, ir_1.isIRStringAttribute)(nameAttribute)) {
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
- return ctx.warnOnIRNode(errors_1.ParserDiagnostics.NO_DUPLICATE_SLOTS, element, [
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, element)) {
481
- return ctx.warnOnIRNode(errors_1.ParserDiagnostics.NO_SLOTS_IN_ITERATOR, element, [
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, element, parsedAttr) {
487
- const { tag } = element;
527
+ function applyAttributes(ctx, parsedAttr, element) {
528
+ const { name: tag } = element;
488
529
  const attributes = parsedAttr.getAttributes();
530
+ const properties = new Map();
489
531
  for (const attr of attributes) {
490
532
  const { name } = attr;
491
533
  if (!(0, attribute_1.isValidHTMLAttribute)(tag, name)) {
492
- ctx.warnOnIRNode(errors_1.ParserDiagnostics.INVALID_HTML_ATTRIBUTE, attr, [name, tag]);
534
+ ctx.warnOnNode(errors_1.ParserDiagnostics.INVALID_HTML_ATTRIBUTE, attr, [name, tag]);
493
535
  }
494
536
  if (name.match(/[^a-z0-9]$/)) {
495
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_MUST_END_WITH_ALPHA_NUMERIC_CHARACTER, attr, [name, tag]);
537
+ ctx.throwOnNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_MUST_END_WITH_ALPHA_NUMERIC_CHARACTER, attr, [name, tag]);
496
538
  }
497
539
  if (!/^-*[a-z]/.test(name)) {
498
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_MUST_START_WITH_ALPHABETIC_OR_HYPHEN_CHARACTER, attr, [name, tag]);
540
+ ctx.throwOnNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_MUST_START_WITH_ALPHABETIC_OR_HYPHEN_CHARACTER, attr, [name, tag]);
499
541
  }
500
542
  // disallow attr name which combines underscore character with special character.
501
543
  // We normalize camel-cased names with underscores caMel -> ca-mel; thus sanitization.
502
544
  if (name.match(/_[^a-z0-9]|[^a-z0-9]_/)) {
503
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_CANNOT_COMBINE_UNDERSCORE_WITH_SPECIAL_CHARS, attr, [name, tag]);
545
+ ctx.throwOnNode(errors_1.ParserDiagnostics.ATTRIBUTE_NAME_CANNOT_COMBINE_UNDERSCORE_WITH_SPECIAL_CHARS, attr, [name, tag]);
504
546
  }
505
- if ((0, ir_1.isIRStringAttribute)(attr)) {
547
+ if (ast.isStringLiteral(attr.value)) {
506
548
  if (name === 'id') {
507
- const { value } = attr;
549
+ const { value } = attr.value;
508
550
  if (/\s+/.test(value)) {
509
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.INVALID_ID_ATTRIBUTE, attr, [value]);
551
+ ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_ID_ATTRIBUTE, attr, [value]);
510
552
  }
511
- if (isInIteration(ctx, element)) {
512
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.INVALID_STATIC_ID_IN_ITERATION, attr);
553
+ if (isInIteration(ctx)) {
554
+ ctx.warnOnNode(errors_1.ParserDiagnostics.INVALID_STATIC_ID_IN_ITERATION, attr);
513
555
  }
514
556
  if (ctx.seenIds.has(value)) {
515
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.DUPLICATE_ID_FOUND, attr, [value]);
557
+ ctx.throwOnNode(errors_1.ParserDiagnostics.DUPLICATE_ID_FOUND, attr, [value]);
516
558
  }
517
559
  else {
518
560
  ctx.seenIds.add(value);
@@ -520,162 +562,171 @@ function applyAttributes(ctx, element, parsedAttr) {
520
562
  }
521
563
  }
522
564
  // Prevent usage of the slot attribute with expression.
523
- if (name === 'slot' && (0, ir_1.isIRExpressionAttribute)(attr)) {
524
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.SLOT_ATTRIBUTE_CANNOT_BE_EXPRESSION, attr);
565
+ if (name === 'slot' && ast.isExpression(attr.value)) {
566
+ ctx.throwOnNode(errors_1.ParserDiagnostics.SLOT_ATTRIBUTE_CANNOT_BE_EXPRESSION, attr);
525
567
  }
526
568
  // the if branch handles
527
569
  // 1. All attributes for standard elements except 1 case are handled as attributes
528
570
  // 2. For custom elements, only key, slot and data are handled as attributes, rest as properties
529
571
  if ((0, attribute_1.isAttribute)(element, name)) {
530
- const attrs = element.attrs || (element.attrs = {});
531
- attrs[name] = attr;
572
+ element.attributes.push(attr);
532
573
  }
533
574
  else {
534
- const props = element.props || (element.props = {});
535
- props[(0, attribute_1.attributeToPropertyName)(name)] = attr;
575
+ const propName = (0, attribute_1.attributeToPropertyName)(name);
576
+ const existingProp = properties.get(propName);
577
+ if (existingProp) {
578
+ ctx.warnOnNode(errors_1.ParserDiagnostics.DUPLICATE_ATTR_PROP_TRANSFORM, attr, [
579
+ existingProp.attributeName,
580
+ name,
581
+ propName,
582
+ ]);
583
+ }
584
+ properties.set(propName, ast.property(propName, name, attr.value, attr.location));
536
585
  parsedAttr.pick(name);
537
586
  }
538
587
  }
588
+ element.properties.push(...properties.values());
539
589
  }
540
- function 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
- }
590
+ function validateRoot(ctx, parsedAttr, root) {
591
+ if (parsedAttr.getAttributes().length) {
592
+ ctx.throwOnNode(errors_1.ParserDiagnostics.ROOT_TEMPLATE_HAS_UNKNOWN_ATTRIBUTES, root);
593
+ }
594
+ if (!root.location.endTag) {
595
+ ctx.throwOnNode(errors_1.ParserDiagnostics.NO_MATCHING_CLOSING_TAGS, root, ['template']);
551
596
  }
597
+ }
598
+ function validateElement(ctx, element, parse5Elm) {
599
+ const { tagName: tag, namespaceURI: namespace } = parse5Elm;
552
600
  // Check if a non-void element has a matching closing tag.
553
601
  //
554
602
  // Note: Parse5 currently fails to collect end tag location for element with a tag name
555
603
  // containing an upper case character (inikulin/parse5#352).
556
- const hasClosingTag = Boolean(location.endTag);
604
+ const hasClosingTag = Boolean(element.location.endTag);
557
605
  const isVoidElement = constants_1.VOID_ELEMENT_SET.has(tag);
558
606
  if (!isVoidElement && !hasClosingTag && tag === tag.toLocaleLowerCase()) {
559
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.NO_MATCHING_CLOSING_TAGS, element, [tag]);
607
+ ctx.throwOnNode(errors_1.ParserDiagnostics.NO_MATCHING_CLOSING_TAGS, element, [tag]);
560
608
  }
561
609
  if (tag === 'style' && namespace === constants_1.HTML_NAMESPACE_URI) {
562
- ctx.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
- }
610
+ ctx.throwOnNode(errors_1.ParserDiagnostics.STYLE_TAG_NOT_ALLOWED_IN_TEMPLATE, element);
581
611
  }
582
612
  else {
583
613
  const isNotAllowedHtmlTag = constants_1.DISALLOWED_HTML_TAGS.has(tag);
584
614
  if (namespace === constants_1.HTML_NAMESPACE_URI && isNotAllowedHtmlTag) {
585
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.FORBIDDEN_TAG_ON_TEMPLATE, element, [tag]);
615
+ ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_TAG_ON_TEMPLATE, element, [tag]);
586
616
  }
587
617
  const isNotAllowedSvgTag = !constants_1.SUPPORTED_SVG_TAGS.has(tag);
588
618
  if (namespace === constants_1.SVG_NAMESPACE_URI && isNotAllowedSvgTag) {
589
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.FORBIDDEN_SVG_NAMESPACE_IN_TEMPLATE, element, [
590
- tag,
591
- ]);
619
+ ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_SVG_NAMESPACE_IN_TEMPLATE, element, [tag]);
592
620
  }
593
621
  const isNotAllowedMathMlTag = constants_1.DISALLOWED_MATHML_TAGS.has(tag);
594
622
  if (namespace === constants_1.MATHML_NAMESPACE_URI && isNotAllowedMathMlTag) {
595
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.FORBIDDEN_MATHML_NAMESPACE_IN_TEMPLATE, element, [
623
+ ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_MATHML_NAMESPACE_IN_TEMPLATE, element, [
596
624
  tag,
597
625
  ]);
598
626
  }
599
- const isKnownTag = (0, ir_1.isCustomElement)(element) ||
627
+ const isKnownTag = ast.isComponent(element) ||
600
628
  constants_1.KNOWN_HTML_ELEMENTS.has(tag) ||
601
629
  constants_1.SUPPORTED_SVG_TAGS.has(tag) ||
602
630
  constants_1.DASHED_TAGNAME_ELEMENT_SET.has(tag);
603
631
  if (!isKnownTag) {
604
- ctx.warnOnIRNode(errors_1.ParserDiagnostics.UNKNOWN_HTML_TAG_IN_TEMPLATE, element, [tag]);
632
+ ctx.warnOnNode(errors_1.ParserDiagnostics.UNKNOWN_HTML_TAG_IN_TEMPLATE, element, [tag]);
633
+ }
634
+ }
635
+ }
636
+ function validateTemplate(ctx, parsedAttr, parse5Elm, parse5ElmLocation) {
637
+ if (parse5Elm.tagName === 'template') {
638
+ const location = ast.sourceLocation(parse5ElmLocation);
639
+ // Empty templates not allowed outside of root
640
+ if (!parse5Elm.attrs.length) {
641
+ ctx.throwAtLocation(errors_1.ParserDiagnostics.NO_DIRECTIVE_FOUND_ON_TEMPLATE, location);
642
+ }
643
+ if (parsedAttr.get(constants_1.LWC_DIRECTIVES.INNER_HTML)) {
644
+ ctx.throwAtLocation(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_ELEMENT, location, [
645
+ '<template>',
646
+ ]);
647
+ }
648
+ // Non root templates only support for:each, iterator and if directives
649
+ if (parsedAttr.getAttributes().length) {
650
+ ctx.warnAtLocation(errors_1.ParserDiagnostics.UNKNOWN_TEMPLATE_ATTRIBUTE, location);
605
651
  }
606
652
  }
607
653
  }
608
654
  function validateChildren(ctx, element) {
609
- var _a, _b;
610
- const effectiveChildren = ctx.getPreserveComments(element)
655
+ if (!element) {
656
+ return;
657
+ }
658
+ const effectiveChildren = ctx.preserveComments
611
659
  ? element.children
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);
660
+ : element.children.filter((child) => !ast.isComment(child));
661
+ const hasDomDirective = element.directives.find(ast.isDomDirective);
662
+ if (hasDomDirective && effectiveChildren.length) {
663
+ ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_DOM_INVALID_CONTENTS, element);
615
664
  }
616
665
  // prevents lwc:inner-html to be used in an element with content
617
- if (((_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}>`,
666
+ if (element.directives.find(ast.isInnerHTMLDirective) && effectiveChildren.length) {
667
+ ctx.throwOnNode(errors_1.ParserDiagnostics.LWC_INNER_HTML_INVALID_CONTENTS, element, [
668
+ `<${element.name}>`,
620
669
  ]);
621
670
  }
622
671
  }
623
- function validateAttributes(ctx, element, parsedAttr) {
624
- const { tag } = element;
672
+ function validateAttributes(ctx, parsedAttr, element) {
673
+ const { name: tag } = element;
625
674
  const attributes = parsedAttr.getAttributes();
626
675
  for (const attr of attributes) {
627
- const { name: attrName } = attr;
676
+ const { name: attrName, value: attrVal } = attr;
628
677
  if ((0, attribute_1.isProhibitedIsAttribute)(attrName)) {
629
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.IS_ATTRIBUTE_NOT_SUPPORTED, element);
678
+ ctx.throwOnNode(errors_1.ParserDiagnostics.IS_ATTRIBUTE_NOT_SUPPORTED, element);
630
679
  }
631
680
  if ((0, attribute_1.isTabIndexAttribute)(attrName)) {
632
- if (!(0, ir_1.isIRExpressionAttribute)(attr) && !(0, attribute_1.isValidTabIndexAttributeValue)(attr.value)) {
633
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.INVALID_TABINDEX_ATTRIBUTE, element);
681
+ if (!ast.isExpression(attrVal) && !(0, attribute_1.isValidTabIndexAttributeValue)(attrVal.value)) {
682
+ ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_TABINDEX_ATTRIBUTE, element);
634
683
  }
635
684
  }
636
685
  // TODO [#1136]: once the template compiler emits the element namespace information to the engine we should
637
686
  // restrict the validation of the "srcdoc" attribute on the "iframe" element only if this element is
638
687
  // part of the HTML namespace.
639
688
  if (tag === 'iframe' && attrName === 'srcdoc') {
640
- ctx.throwOnIRNode(errors_1.ParserDiagnostics.FORBIDDEN_IFRAME_SRCDOC_ATTRIBUTE, element);
689
+ ctx.throwOnNode(errors_1.ParserDiagnostics.FORBIDDEN_IFRAME_SRCDOC_ATTRIBUTE, element);
641
690
  }
642
691
  }
643
692
  }
644
693
  function validateProperties(ctx, element) {
645
- const { 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
- }
694
+ for (const prop of element.properties) {
695
+ const { attributeName: attrName, value } = prop;
696
+ if ((0, attribute_1.isProhibitedIsAttribute)(attrName)) {
697
+ ctx.throwOnNode(errors_1.ParserDiagnostics.IS_ATTRIBUTE_NOT_SUPPORTED, element);
698
+ }
699
+ if (
700
+ // tabindex is transformed to tabIndex for properties
701
+ (0, attribute_1.isTabIndexAttribute)(attrName) &&
702
+ !ast.isExpression(value) &&
703
+ !(0, attribute_1.isValidTabIndexAttributeValue)(value.value)) {
704
+ ctx.throwOnNode(errors_1.ParserDiagnostics.INVALID_TABINDEX_ATTRIBUTE, element);
658
705
  }
659
706
  }
660
707
  }
661
- function parseAttributes(ctx, element, node) {
708
+ function parseAttributes(ctx, parse5Elm, parse5ElmLocation) {
662
709
  const parsedAttrs = new attribute_1.ParsedAttribute();
663
- const { attrs: attributes } = node;
710
+ const { attrs: attributes, tagName } = parse5Elm;
711
+ const { attrs: attrLocations } = parse5ElmLocation;
664
712
  for (const attr of attributes) {
665
- parsedAttrs.append(getTemplateAttribute(ctx, element, attr));
713
+ const attrLocation = attrLocations === null || attrLocations === void 0 ? void 0 : attrLocations[(0, attribute_1.attributeName)(attr).toLowerCase()];
714
+ if (!attrLocation) {
715
+ throw new Error('An internal parsing error occurred while parsing attributes; attributes were found without a location.');
716
+ }
717
+ parsedAttrs.append(getTemplateAttribute(ctx, tagName, attr, attrLocation));
666
718
  }
667
719
  return parsedAttrs;
668
720
  }
669
- function getTemplateAttribute(ctx, element, attribute) {
670
- const name = (0, attribute_1.attributeName)(attribute);
721
+ function getTemplateAttribute(ctx, tag, attribute, attributeLocation) {
671
722
  // Convert attribute name to lowercase because the location map keys follow the algorithm defined in the spec
672
723
  // https://wicg.github.io/controls-list/html-output/multipage/syntax.html#attribute-name-state
673
- const location = element.location.attrs[name.toLowerCase()];
674
- const rawAttribute = ctx.getSource(location.startOffset, location.endOffset);
675
- const { tag } = element;
724
+ const rawAttribute = ctx.getSource(attributeLocation.startOffset, attributeLocation.endOffset);
725
+ const location = ast.sourceLocation(attributeLocation);
676
726
  // parse5 automatically converts the casing from camelcase to all lowercase. If the attribute name
677
727
  // is not the same before and after the parsing, then the attribute name contains capital letters
678
- if (!rawAttribute.startsWith(name)) {
728
+ const attrName = (0, attribute_1.attributeName)(attribute);
729
+ if (!rawAttribute.startsWith(attrName)) {
679
730
  ctx.throwAtLocation(errors_1.ParserDiagnostics.INVALID_ATTRIBUTE_CASE, location, [
680
731
  rawAttribute,
681
732
  tag,
@@ -683,51 +734,28 @@ function getTemplateAttribute(ctx, element, attribute) {
683
734
  }
684
735
  const isBooleanAttribute = !rawAttribute.includes('=');
685
736
  const { value, escapedExpression } = (0, attribute_1.normalizeAttributeValue)(ctx, rawAttribute, tag, attribute, location);
737
+ let attrValue;
686
738
  if ((0, expression_1.isExpression)(value) && !escapedExpression) {
687
- return {
688
- name,
689
- location,
690
- type: types_1.IRAttributeType.Expression,
691
- value: (0, expression_1.parseExpression)(ctx, value, location),
692
- };
739
+ attrValue = (0, expression_1.parseExpression)(ctx, value, location);
693
740
  }
694
741
  else if (isBooleanAttribute) {
695
- return {
696
- name,
697
- location,
698
- type: types_1.IRAttributeType.Boolean,
699
- value: true,
700
- };
742
+ attrValue = ast.literal(true);
701
743
  }
702
744
  else {
703
- 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
- });
745
+ attrValue = ast.literal(value);
746
+ }
747
+ return ast.attribute(attrName, attrValue, location);
716
748
  }
717
- function getForOfParent(ctx) {
718
- return ctx.findAncestor({
719
- predicate: (element) => element.forOf,
720
- traversalCond: ({ current }) => (0, ir_1.isTemplate)(current),
721
- });
749
+ function isInIteration(ctx) {
750
+ return !!ctx.findAncestor(ast.isForBlock);
722
751
  }
723
- function getForEachParent(ctx, element) {
724
- return ctx.findAncestor({
725
- element,
726
- predicate: (element) => element.forEach,
727
- traversalCond: ({ parent }) => parent && (0, ir_1.isTemplate)(parent),
728
- });
752
+ function getForOfParent(ctx, srcNode) {
753
+ return ctx.findAncestor(ast.isForOf, ({ current }) => !ast.isBaseElement(current), srcNode);
754
+ }
755
+ function getForEachParent(ctx) {
756
+ return ctx.findAncestor(ast.isForEach, ({ parent }) => parent && !ast.isBaseElement(parent));
729
757
  }
730
- function isInIteratorElement(ctx, element) {
731
- return !!(getForOfParent(ctx) || getForEachParent(ctx, element));
758
+ function isInIteratorElement(ctx, parent) {
759
+ return !!(getForOfParent(ctx, parent) || getForEachParent(ctx));
732
760
  }
733
761
  //# sourceMappingURL=index.js.map