@lwc/template-compiler 2.6.2 → 2.7.0

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