@lwc/template-compiler 2.7.2 → 2.9.0

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