wrec 0.40.3 → 0.42.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/scripts/lint.js CHANGED
@@ -37,16 +37,16 @@
37
37
  // - checkbox checked bindings that do not reference Boolean properties
38
38
  // - radio checked bindings that do not reference String properties
39
39
 
40
- import fs from 'node:fs';
41
- import path from 'node:path';
42
- import ts from 'typescript';
43
- import {parse} from 'node-html-parser';
40
+ import fs from "node:fs";
41
+ import path from "node:path";
42
+ import ts from "typescript";
43
+ import { parse } from "node-html-parser";
44
44
  import {
45
45
  collectWrecClasses,
46
46
  getMemberName,
47
47
  getPropertyAssignmentNames,
48
- hasStaticModifier
49
- } from './ast-utils.js';
48
+ hasStaticModifier,
49
+ } from "./ast-utils.js";
50
50
 
51
51
  // Regular expressions
52
52
  const CSS_PROPERTY_RE = /([a-zA-Z-]+)\s*:\s*([^;}]+)/g;
@@ -56,107 +56,95 @@ const REFS_TEST_RE = /this\.[a-zA-Z_$][\w$]*(\.[a-zA-Z_$][\w$]*)*/;
56
56
  const THIS_CALL_RE = /this\.([A-Za-z_$][\w$]*)\s*\(/g;
57
57
  const THIS_REF_RE = /this\.([A-Za-z_$][\w$]*)(\.[A-Za-z_$][\w$]*)*/g;
58
58
 
59
- const GETTER_PREFIX = 'get ';
59
+ const GETTER_PREFIX = "get ";
60
60
  const HTML_ALLOWED_CHILDREN = new Map([
61
- ['select', new Set(['option'])],
62
- ['table', new Set(['tbody', 'thead', 'tr'])],
63
- ['tbody', new Set(['tr'])],
64
- ['thead', new Set(['tr'])],
65
- ['tr', new Set(['td', 'th'])],
66
- ['ul', new Set(['li'])]
61
+ ["select", new Set(["option"])],
62
+ ["table", new Set(["tbody", "thead", "tr"])],
63
+ ["tbody", new Set(["tr"])],
64
+ ["thead", new Set(["tr"])],
65
+ ["tr", new Set(["td", "th"])],
66
+ ["ul", new Set(["li"])],
67
67
  ]);
68
68
  const HTML_ALLOWED_PARENTS = new Map([
69
- ['legend', new Set(['fieldset'])],
70
- ['li', new Set(['ol', 'ul'])],
71
- ['option', new Set(['select'])],
72
- ['tbody', new Set(['table'])],
73
- ['td', new Set(['tr'])],
74
- ['th', new Set(['tr'])],
75
- ['thead', new Set(['table'])],
76
- ['tr', new Set(['table', 'tbody', 'thead'])]
69
+ ["legend", new Set(["fieldset"])],
70
+ ["li", new Set(["ol", "ul"])],
71
+ ["option", new Set(["select"])],
72
+ ["tbody", new Set(["table"])],
73
+ ["td", new Set(["tr"])],
74
+ ["th", new Set(["tr"])],
75
+ ["thead", new Set(["table"])],
76
+ ["tr", new Set(["table", "tbody", "thead"])],
77
77
  ]);
78
78
  const HTML_GLOBAL_ATTRIBUTES = new Set([
79
- 'aria-label',
80
- 'class',
81
- 'disabled',
82
- 'hidden',
83
- 'id',
84
- 'part',
85
- 'role',
86
- 'slot',
87
- 'style',
88
- 'tabindex',
89
- 'title'
79
+ "aria-label",
80
+ "class",
81
+ "disabled",
82
+ "hidden",
83
+ "id",
84
+ "part",
85
+ "role",
86
+ "slot",
87
+ "style",
88
+ "tabindex",
89
+ "title",
90
90
  ]);
91
91
  const HTML_TAG_ATTRIBUTES = new Map([
92
- ['a', new Set(['href', 'rel', 'target'])],
93
- ['button', new Set(['name', 'type', 'value'])],
94
- ['div', new Set([])],
95
- ['fieldset', new Set(['name'])],
96
- ['form', new Set(['action', 'method', 'name'])],
97
- ['img', new Set(['alt', 'height', 'src', 'width'])],
98
- [
99
- 'input',
100
- new Set([
101
- 'checked',
102
- 'max',
103
- 'min',
104
- 'name',
105
- 'placeholder',
106
- 'step',
107
- 'type',
108
- 'value'
109
- ])
110
- ],
111
- ['label', new Set(['for'])],
112
- ['legend', new Set([])],
113
- ['li', new Set(['value'])],
114
- ['option', new Set(['label', 'selected', 'value'])],
115
- ['p', new Set([])],
116
- ['select', new Set(['multiple', 'name', 'value'])],
117
- ['span', new Set([])],
118
- ['table', new Set([])],
119
- ['tbody', new Set([])],
120
- ['td', new Set(['colspan', 'rowspan'])],
121
- ['textarea', new Set(['name', 'placeholder', 'rows', 'value'])],
122
- ['th', new Set(['colspan', 'rowspan', 'scope'])],
123
- ['thead', new Set([])],
124
- ['tr', new Set([])],
125
- ['ul', new Set([])]
92
+ ["a", new Set(["href", "rel", "target"])],
93
+ ["button", new Set(["name", "type", "value"])],
94
+ ["div", new Set([])],
95
+ ["fieldset", new Set(["name"])],
96
+ ["form", new Set(["action", "method", "name"])],
97
+ ["img", new Set(["alt", "height", "src", "width"])],
98
+ ["input", new Set(["checked", "max", "min", "name", "placeholder", "step", "type", "value"])],
99
+ ["label", new Set(["for"])],
100
+ ["legend", new Set([])],
101
+ ["li", new Set(["value"])],
102
+ ["option", new Set(["label", "selected", "value"])],
103
+ ["p", new Set([])],
104
+ ["select", new Set(["multiple", "name", "value"])],
105
+ ["span", new Set([])],
106
+ ["table", new Set([])],
107
+ ["tbody", new Set([])],
108
+ ["td", new Set(["colspan", "rowspan"])],
109
+ ["textarea", new Set(["name", "placeholder", "rows", "value"])],
110
+ ["th", new Set(["colspan", "rowspan", "scope"])],
111
+ ["thead", new Set([])],
112
+ ["tr", new Set([])],
113
+ ["ul", new Set([])],
126
114
  ]);
127
- const NATIVE_FORM_CONTROL_TAGS = new Set(['input', 'select', 'textarea']);
128
- const PLACEHOLDER_PREFIX = '__WREC_PLACEHOLDER__';
129
- const RESERVED_PROPERTY_NAMES = new Set(['class', 'style']);
115
+ const NATIVE_FORM_CONTROL_TAGS = new Set(["input", "select", "textarea"]);
116
+ const PLACEHOLDER_PREFIX = "__WREC_PLACEHOLDER__";
117
+ const RESERVED_PROPERTY_NAMES = new Set(["class", "style"]);
130
118
  const SUPPORTED_EVENT_NAMES = new Set([
131
- 'blur',
132
- 'change',
133
- 'click',
134
- 'dblclick',
135
- 'focus',
136
- 'focusin',
137
- 'focusout',
138
- 'input',
139
- 'keydown',
140
- 'keypress',
141
- 'keyup',
142
- 'mousedown',
143
- 'mouseenter',
144
- 'mouseleave',
145
- 'mousemove',
146
- 'mouseout',
147
- 'mouseover',
148
- 'mouseup',
149
- 'paste'
119
+ "blur",
120
+ "change",
121
+ "click",
122
+ "dblclick",
123
+ "focus",
124
+ "focusin",
125
+ "focusout",
126
+ "input",
127
+ "keydown",
128
+ "keypress",
129
+ "keyup",
130
+ "mousedown",
131
+ "mouseenter",
132
+ "mouseleave",
133
+ "mousemove",
134
+ "mouseout",
135
+ "mouseover",
136
+ "mouseup",
137
+ "paste",
150
138
  ]);
151
139
  const SUPPORTED_PROPERTY_TYPE_NAMES = new Set([
152
- 'Array',
153
- 'Boolean',
154
- 'HTMLElement',
155
- 'Number',
156
- 'Object',
157
- 'String'
140
+ "Array",
141
+ "Boolean",
142
+ "HTMLElement",
143
+ "Number",
144
+ "Object",
145
+ "String",
158
146
  ]);
159
- const WREC_REF_NAME = '__wrec';
147
+ const WREC_REF_NAME = "__wrec";
160
148
 
161
149
  const componentPropertyCache = new Map();
162
150
 
@@ -169,10 +157,10 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
169
157
  findings.invalidEventHandlers,
170
158
  metadata.location
171
159
  ? {
172
- location: metadata.location,
173
- message: `"${codeNode.text}" is not a defined instance method`
174
- }
175
- : `"${codeNode.text}" is not a defined instance method`
160
+ location: metadata.location,
161
+ message: `"${codeNode.text}" is not a defined instance method`,
162
+ }
163
+ : `"${codeNode.text}" is not a defined instance method`,
176
164
  );
177
165
  }
178
166
  }
@@ -180,6 +168,20 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
180
168
  // Walks the expression tree and records any issues that are found.
181
169
  function visit(node) {
182
170
  if (ts.isPropertyAccessExpression(node) && isWrecRooted(node.expression)) {
171
+ if (isPrivateName(node.name)) {
172
+ const name = getPrivateNameText(node.name);
173
+ const hasPrivateMember =
174
+ metadata.privateMethods.has(name) || metadata.privateProperties.has(name);
175
+ if (!hasPrivateMember) {
176
+ const finding = metadata.location ? { location: metadata.location, message: name } : name;
177
+ uniquePush(
178
+ isCallCallee(node) ? findings.undefinedMethods : findings.undefinedProperties,
179
+ finding,
180
+ );
181
+ }
182
+ return;
183
+ }
184
+
183
185
  const ownerType = checker.getTypeAtLocation(node.expression);
184
186
  const symbol = ownerType.getProperty(node.name.text);
185
187
  if (!symbol) {
@@ -187,12 +189,12 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
187
189
  if (isCallCallee(node)) {
188
190
  uniquePush(
189
191
  findings.undefinedMethods,
190
- metadata.location ? {location: metadata.location, message: name} : name
192
+ metadata.location ? { location: metadata.location, message: name } : name,
191
193
  );
192
194
  } else {
193
195
  uniquePush(
194
196
  findings.undefinedProperties,
195
- metadata.location ? {location: metadata.location, message: name} : name
197
+ metadata.location ? { location: metadata.location, message: name } : name,
196
198
  );
197
199
  }
198
200
  }
@@ -207,33 +209,39 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
207
209
  uniquePush(
208
210
  findings.undefinedContextFunctions,
209
211
  metadata.location
210
- ? {location: metadata.location, message: callee.text}
211
- : callee.text
212
+ ? { location: metadata.location, message: callee.text }
213
+ : callee.text,
212
214
  );
213
215
  }
214
216
  }
215
- } else if (
216
- ts.isPropertyAccessExpression(callee) &&
217
- isWrecRooted(callee.expression)
218
- ) {
217
+ } else if (ts.isPropertyAccessExpression(callee) && isWrecRooted(callee.expression)) {
218
+ if (isPrivateName(callee.name)) {
219
+ const name = getPrivateNameText(callee.name);
220
+ if (!metadata.privateMethods.has(name)) {
221
+ uniquePush(
222
+ findings.undefinedMethods,
223
+ metadata.location ? { location: metadata.location, message: name } : name,
224
+ );
225
+ }
226
+ node.arguments.forEach(visit);
227
+ return;
228
+ }
229
+
219
230
  const ownerType = checker.getTypeAtLocation(callee.expression);
220
231
  const symbol = ownerType.getProperty(callee.name.text);
221
232
  if (!symbol) {
222
233
  uniquePush(
223
234
  findings.undefinedMethods,
224
235
  metadata.location
225
- ? {location: metadata.location, message: callee.name.text}
226
- : callee.name.text
236
+ ? { location: metadata.location, message: callee.name.text }
237
+ : callee.name.text,
227
238
  );
228
239
  }
229
240
  }
230
241
 
231
242
  const signature =
232
243
  checker.getResolvedSignature(node) ??
233
- checker.getSignaturesOfType(
234
- checker.getTypeAtLocation(callee),
235
- ts.SignatureKind.Call
236
- )[0];
244
+ checker.getSignaturesOfType(checker.getTypeAtLocation(callee), ts.SignatureKind.Call)[0];
237
245
 
238
246
  if (signature) {
239
247
  const parameters = signature.getParameters();
@@ -242,10 +250,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
242
250
  declaration &&
243
251
  ts.isFunctionLike(declaration) &&
244
252
  declaration.parameters.length > 0 &&
245
- Boolean(
246
- declaration.parameters[declaration.parameters.length - 1]
247
- .dotDotDotToken
248
- );
253
+ Boolean(declaration.parameters[declaration.parameters.length - 1].dotDotDotToken);
249
254
 
250
255
  if (!isRest && node.arguments.length > parameters.length) {
251
256
  node.arguments.slice(parameters.length).forEach((argument, index) => {
@@ -254,7 +259,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
254
259
  argumentIndex: parameters.length + index + 1,
255
260
  location: metadata.location ?? null,
256
261
  methodName: toUserFacingExpression(callee.getText()),
257
- parameterCount: parameters.length
262
+ parameterCount: parameters.length,
258
263
  });
259
264
  });
260
265
  }
@@ -262,9 +267,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
262
267
  node.arguments.forEach((argument, index) => {
263
268
  let parameterSymbol = parameters[index];
264
269
  let isRestArgument =
265
- Boolean(isRest) &&
266
- parameters.length > 0 &&
267
- index >= parameters.length - 1;
270
+ Boolean(isRest) && parameters.length > 0 && index >= parameters.length - 1;
268
271
  if (!parameterSymbol && isRest && parameters.length > 0) {
269
272
  parameterSymbol = parameters[parameters.length - 1];
270
273
  }
@@ -275,7 +278,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
275
278
  checker,
276
279
  parameterSymbol,
277
280
  declaration ?? classNode,
278
- isRestArgument
281
+ isRestArgument,
279
282
  );
280
283
  if (!checker.isTypeAssignableTo(argumentType, parameterType)) {
281
284
  findings.incompatibleArguments.push({
@@ -284,17 +287,14 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
284
287
  location: metadata.location ?? null,
285
288
  methodName: toUserFacingExpression(callee.getText()),
286
289
  parameterName: parameterSymbol.getName(),
287
- parameterType: checker.typeToString(parameterType)
290
+ parameterType: checker.typeToString(parameterType),
288
291
  });
289
292
  }
290
293
  });
291
294
  }
292
295
  }
293
296
 
294
- if (
295
- ts.isBinaryExpression(node) &&
296
- isArithmeticOperator(node.operatorToken.kind)
297
- ) {
297
+ if (ts.isBinaryExpression(node) && isArithmeticOperator(node.operatorToken.kind)) {
298
298
  const leftType = checker.getTypeAtLocation(node.left);
299
299
  const rightType = checker.getTypeAtLocation(node.right);
300
300
 
@@ -305,7 +305,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
305
305
  message:
306
306
  `left operand "${toUserFacingExpression(node.left.getText())}" ` +
307
307
  `has type ${checker.typeToString(leftType)}, ` +
308
- 'but arithmetic operators require number'
308
+ "but arithmetic operators require number",
309
309
  });
310
310
  }
311
311
 
@@ -316,7 +316,7 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
316
316
  message:
317
317
  `right operand "${toUserFacingExpression(node.right.getText())}" ` +
318
318
  `has type ${checker.typeToString(rightType)}, ` +
319
- 'but arithmetic operators require number'
319
+ "but arithmetic operators require number",
320
320
  });
321
321
  }
322
322
  }
@@ -333,34 +333,27 @@ function analyzeCodeNode(codeNode, checker, classNode, findings, metadata) {
333
333
  // can be analyzed as if it were normal code. This gives TypeScript
334
334
  // enough // context to understand available properties, methods, and
335
335
  // context functions when the linter validates those expressions.
336
- function buildAugmentedSource(
337
- sourceFile,
338
- classNode,
339
- supportedProps,
340
- contextKeys,
341
- codeItems
342
- ) {
336
+ function buildAugmentedSource(sourceFile, classNode, supportedProps, contextKeys, codeItems) {
343
337
  const propLines = [];
344
338
  for (const [name, info] of supportedProps.entries()) {
345
339
  propLines.push(` ${JSON.stringify(name)}: ${info.typeText};`);
346
340
  }
347
341
 
348
342
  const contextLine = contextKeys.length
349
- ? `const {${contextKeys.join(', ')}} = ${classNode.name.text}.context;`
350
- : '';
343
+ ? `const {${contextKeys.join(", ")}} = ${classNode.name.text}.context;`
344
+ : "";
351
345
 
352
346
  const helperBlocks = codeItems.map((item, index) => {
353
347
  const targetType =
354
- item.context === 'static'
348
+ item.context === "static"
355
349
  ? `typeof ${classNode.name.text}`
356
350
  : `${classNode.name.text} & __WrecSupportedProps`;
357
351
  const rewrittenText = item.text.replace(/\bthis\b/g, WREC_REF_NAME);
358
- const helperBody =
359
- item.shape === 'block' ? rewrittenText : `return (${rewrittenText});`;
352
+ const helperBody = item.shape === "block" ? rewrittenText : `return (${rewrittenText});`;
360
353
  return `
361
354
  function __wrec_expr_${index}() {
362
355
  const ${WREC_REF_NAME} = null as unknown as ${targetType};
363
- ${item.checkContextCalls ? contextLine : ''}
356
+ ${item.checkContextCalls ? contextLine : ""}
364
357
  ${helperBody}
365
358
  }
366
359
  `;
@@ -368,11 +361,11 @@ function __wrec_expr_${index}() {
368
361
 
369
362
  const propInterface = `
370
363
  type __WrecSupportedProps = {
371
- ${propLines.join('\n')}
364
+ ${propLines.join("\n")}
372
365
  };
373
366
  `;
374
367
 
375
- return `${sourceFile.text}\n${propInterface}\n${helperBlocks.join('\n')}`;
368
+ return `${sourceFile.text}\n${propInterface}\n${helperBlocks.join("\n")}`;
376
369
  }
377
370
 
378
371
  // Collects all instance method and accessor names defined in a component class.
@@ -427,10 +420,7 @@ function collectHelperCodeNodes(augmentedSourceFile) {
427
420
  // Finds generated helper functions and
428
421
  // stores their bodies by index.
429
422
  function visit(node) {
430
- if (
431
- ts.isFunctionDeclaration(node) &&
432
- node.name?.text.startsWith('__wrec_expr_')
433
- ) {
423
+ if (ts.isFunctionDeclaration(node) && node.name?.text.startsWith("__wrec_expr_")) {
434
424
  const match = node.name.text.match(/(\d+)$/);
435
425
  const index = match ? Number(match[1]) : -1;
436
426
  if (index >= 0 && node.body) helpers[index] = node.body;
@@ -455,19 +445,17 @@ function collectMethodCodeItems(classNode) {
455
445
  ts.isSetAccessorDeclaration(member)
456
446
  ) {
457
447
  if (!member.body) continue;
458
- if (!member.body.getText().includes('this.')) continue;
448
+ if (!member.body.getText().includes("this.")) continue;
459
449
  codeItems.push({
460
450
  checkContextCalls: false,
461
- context: 'instance',
451
+ context: "instance",
462
452
  eventHandler: false,
463
- kind: 'method',
453
+ kind: "method",
464
454
  location: member
465
455
  .getSourceFile()
466
- .getLineAndCharacterOfPosition(
467
- member.name.getStart(member.getSourceFile())
468
- ),
469
- shape: 'block',
470
- text: member.body.getText()
456
+ .getLineAndCharacterOfPosition(member.name.getStart(member.getSourceFile())),
457
+ shape: "block",
458
+ text: member.body.getText(),
471
459
  });
472
460
  }
473
461
  }
@@ -475,6 +463,29 @@ function collectMethodCodeItems(classNode) {
475
463
  return codeItems;
476
464
  }
477
465
 
466
+ // Collects private instance member names defined in a component class.
467
+ function collectPrivateMembers(classNode) {
468
+ const privateMethods = new Set();
469
+ const privateProperties = new Set();
470
+
471
+ for (const member of classNode.members) {
472
+ const name = getMemberName(member);
473
+ if (!name?.startsWith("#")) continue;
474
+
475
+ if (ts.isMethodDeclaration(member)) {
476
+ privateMethods.add(name);
477
+ } else if (
478
+ ts.isGetAccessorDeclaration(member) ||
479
+ ts.isPropertyDeclaration(member) ||
480
+ ts.isSetAccessorDeclaration(member)
481
+ ) {
482
+ privateProperties.add(name);
483
+ }
484
+ }
485
+
486
+ return { privateMethods, privateProperties };
487
+ }
488
+
478
489
  // Collects the property names declared in
479
490
  // a component's static properties object.
480
491
  function collectSupportedPropertyNames(classNode) {
@@ -486,7 +497,7 @@ function collectSupportedPropertyNames(classNode) {
486
497
 
487
498
  const name = getMemberName(member);
488
499
  if (
489
- name !== 'properties' ||
500
+ name !== "properties" ||
490
501
  !member.initializer ||
491
502
  !ts.isObjectLiteralExpression(member.initializer)
492
503
  ) {
@@ -524,7 +535,7 @@ function collectUseStateMapErrors(classNode, supportedProps, findings) {
524
535
  ts.isCallExpression(node) &&
525
536
  ts.isPropertyAccessExpression(node.expression) &&
526
537
  ts.isThis(node.expression.expression) &&
527
- node.expression.name.text === 'useState'
538
+ node.expression.name.text === "useState"
528
539
  ) {
529
540
  const mapArg = node.arguments[1];
530
541
  if (mapArg && ts.isObjectLiteralExpression(mapArg)) {
@@ -541,16 +552,12 @@ function collectUseStateMapErrors(classNode, supportedProps, findings) {
541
552
 
542
553
  const componentProp = property.initializer.text;
543
554
  if (!supportedProps.has(componentProp)) {
544
- findings.invalidUseStateMaps.push(
545
- {
546
- location: node
547
- .getSourceFile()
548
- .getLineAndCharacterOfPosition(property.getStart()),
549
- message:
550
- `useState maps state property "${statePath}" to ` +
551
- `missing component property "${componentProp}"`
552
- }
553
- );
555
+ findings.invalidUseStateMaps.push({
556
+ location: node.getSourceFile().getLineAndCharacterOfPosition(property.getStart()),
557
+ message:
558
+ `useState maps state property "${statePath}" to ` +
559
+ `missing component property "${componentProp}"`,
560
+ });
554
561
  }
555
562
  }
556
563
  }
@@ -576,7 +583,7 @@ function createProgram(filePath, sourceText) {
576
583
  skipLibCheck: true,
577
584
  strict: true,
578
585
  target: ts.ScriptTarget.ESNext,
579
- useDefineForClassFields: true
586
+ useDefineForClassFields: true,
580
587
  };
581
588
 
582
589
  const host = {
@@ -585,35 +592,22 @@ function createProgram(filePath, sourceText) {
585
592
  if (path.resolve(fileName) === filePath) return true;
586
593
  return defaultHost.fileExists(fileName);
587
594
  },
588
- getSourceFile(
589
- fileName,
590
- languageVersion,
591
- onError,
592
- shouldCreateNewSourceFile
593
- ) {
595
+ getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile) {
594
596
  if (path.resolve(fileName) === filePath) {
595
- const kind = fileName.endsWith('.ts')
596
- ? ts.ScriptKind.TS
597
- : ts.ScriptKind.JS;
598
- return ts.createSourceFile(
599
- fileName,
600
- sourceText,
601
- languageVersion,
602
- true,
603
- kind
604
- );
597
+ const kind = fileName.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS;
598
+ return ts.createSourceFile(fileName, sourceText, languageVersion, true, kind);
605
599
  }
606
600
  return defaultHost.getSourceFile(
607
601
  fileName,
608
602
  languageVersion,
609
603
  onError,
610
- shouldCreateNewSourceFile
604
+ shouldCreateNewSourceFile,
611
605
  );
612
606
  },
613
607
  readFile(fileName) {
614
608
  if (path.resolve(fileName) === filePath) return sourceText;
615
609
  return defaultHost.readFile(fileName);
616
- }
610
+ },
617
611
  };
618
612
 
619
613
  return ts.createProgram([filePath], compilerOptions, host);
@@ -638,28 +632,19 @@ function extractProperties(sourceFile, checker, classNode) {
638
632
  const name = getMemberName(member);
639
633
  if (!name || !member.initializer) continue;
640
634
 
641
- if (
642
- name === 'context' &&
643
- ts.isObjectLiteralExpression(member.initializer)
644
- ) {
635
+ if (name === "context" && ts.isObjectLiteralExpression(member.initializer)) {
645
636
  contextKeys = member.initializer.properties
646
- .map(property => getMemberName(property))
637
+ .map((property) => getMemberName(property))
647
638
  .filter(Boolean);
648
639
  continue;
649
640
  }
650
641
 
651
- if (
652
- name === 'formAssociated' &&
653
- member.initializer.kind === ts.SyntaxKind.TrueKeyword
654
- ) {
642
+ if (name === "formAssociated" && member.initializer.kind === ts.SyntaxKind.TrueKeyword) {
655
643
  formAssociated = true;
656
644
  continue;
657
645
  }
658
646
 
659
- if (
660
- name !== 'properties' ||
661
- !ts.isObjectLiteralExpression(member.initializer)
662
- ) {
647
+ if (name !== "properties" || !ts.isObjectLiteralExpression(member.initializer)) {
663
648
  continue;
664
649
  }
665
650
 
@@ -670,13 +655,13 @@ function extractProperties(sourceFile, checker, classNode) {
670
655
  continue;
671
656
  }
672
657
  const propertyLocation = sourceFile.getLineAndCharacterOfPosition(
673
- property.name.getStart(sourceFile)
658
+ property.name.getStart(sourceFile),
674
659
  );
675
660
 
676
661
  if (
677
662
  supportedProps.has(propName) &&
678
- !duplicateProperties.some(
679
- finding => getLocatedFindingMessage(finding).startsWith(`"${propName}" `)
663
+ !duplicateProperties.some((finding) =>
664
+ getLocatedFindingMessage(finding).startsWith(`"${propName}" `),
680
665
  )
681
666
  ) {
682
667
  duplicateProperties.push({
@@ -684,18 +669,16 @@ function extractProperties(sourceFile, checker, classNode) {
684
669
  message:
685
670
  `"${propName}" first declared at ` +
686
671
  `${formatLocation(propertyLocations.get(propName))}, ` +
687
- `duplicated at ${formatLocation(propertyLocation)}`
672
+ `duplicated at ${formatLocation(propertyLocation)}`,
688
673
  });
689
674
  }
690
675
  if (
691
676
  RESERVED_PROPERTY_NAMES.has(propName) &&
692
- !reservedProperties.some(
693
- finding => getLocatedFindingMessage(finding) === propName
694
- )
677
+ !reservedProperties.some((finding) => getLocatedFindingMessage(finding) === propName)
695
678
  ) {
696
679
  reservedProperties.push({
697
680
  location: propertyLocation,
698
- message: propName
681
+ message: propName,
699
682
  });
700
683
  }
701
684
  if (!propertyLocations.has(propName)) {
@@ -703,22 +686,18 @@ function extractProperties(sourceFile, checker, classNode) {
703
686
  }
704
687
 
705
688
  const config = property.initializer;
706
- propertyEntries.push({config, propName, property});
707
- const typeProp = getObjectProperty(config, 'type');
708
- const computedProp = getObjectProperty(config, 'computed');
689
+ propertyEntries.push({ config, propName, property });
690
+ const typeProp = getObjectProperty(config, "type");
691
+ const computedProp = getObjectProperty(config, "computed");
709
692
 
710
- let typeText = 'unknown';
693
+ let typeText = "unknown";
711
694
  let typeNode;
712
695
  if (typeProp && ts.isPropertyAssignment(typeProp)) {
713
- typeText = getPropertyTypeText(
714
- checker,
715
- sourceFile,
716
- typeProp.initializer
717
- );
696
+ typeText = getPropertyTypeText(checker, sourceFile, typeProp.initializer);
718
697
  typeNode = typeNodeFromConstructorExpression(typeProp.initializer);
719
698
  }
720
699
 
721
- supportedProps.set(propName, {typeNode, typeText});
700
+ supportedProps.set(propName, { typeNode, typeText });
722
701
 
723
702
  if (
724
703
  computedProp &&
@@ -727,11 +706,11 @@ function extractProperties(sourceFile, checker, classNode) {
727
706
  ts.isNoSubstitutionTemplateLiteral(computedProp.initializer))
728
707
  ) {
729
708
  computedExprs.push({
730
- kind: 'computed',
709
+ kind: "computed",
731
710
  text: computedProp.initializer.text.trim(),
732
711
  location: sourceFile.getLineAndCharacterOfPosition(
733
- computedProp.initializer.getStart(sourceFile)
734
- )
712
+ computedProp.initializer.getStart(sourceFile),
713
+ ),
735
714
  });
736
715
  }
737
716
  }
@@ -744,17 +723,12 @@ function extractProperties(sourceFile, checker, classNode) {
744
723
  duplicateProperties,
745
724
  formAssociated,
746
725
  propertyEntries,
747
- reservedProperties
726
+ reservedProperties,
748
727
  };
749
728
  }
750
729
 
751
730
  // Extracts analyzable expressions from static html and css templates.
752
- function extractTemplateExpressions(
753
- classNode,
754
- findings,
755
- componentPropertyMaps,
756
- supportedProps
757
- ) {
731
+ function extractTemplateExpressions(classNode, findings, componentPropertyMaps, supportedProps) {
758
732
  const expressions = [];
759
733
 
760
734
  for (const member of classNode.members) {
@@ -762,66 +736,60 @@ function extractTemplateExpressions(
762
736
  if (!ts.isPropertyDeclaration(member)) continue;
763
737
 
764
738
  const name = getMemberName(member);
765
- if ((name !== 'html' && name !== 'css') || !member.initializer) continue;
739
+ if ((name !== "html" && name !== "css") || !member.initializer) continue;
766
740
  if (!ts.isTaggedTemplateExpression(member.initializer)) continue;
767
741
 
768
742
  const tag = member.initializer.tag.getText();
769
- if (tag !== 'html' && tag !== 'css') continue;
743
+ if (tag !== "html" && tag !== "css") continue;
770
744
 
771
- const {template} = member.initializer;
745
+ const { template } = member.initializer;
772
746
  if (ts.isTemplateExpression(template)) {
773
747
  for (const span of template.templateSpans) {
774
- const trimmed = getExpressionText(
775
- member.getSourceFile(),
776
- span.expression
777
- );
748
+ const trimmed = getExpressionText(member.getSourceFile(), span.expression);
778
749
  if (trimmed) {
779
750
  const location = span.expression
780
751
  .getSourceFile()
781
752
  .getLineAndCharacterOfPosition(span.expression.getStart());
782
753
  expressions.push({
783
- context: 'static',
754
+ context: "static",
784
755
  eventHandler: false,
785
756
  kind: tag,
786
757
  text: trimmed,
787
- location
758
+ location,
788
759
  });
789
760
  }
790
761
  }
791
762
  }
792
763
 
793
764
  const rendered = getTemplateLiteralText(template);
794
- const resolveLocation = createTemplateLocationResolver(
795
- member.getSourceFile(),
796
- template
797
- );
765
+ const resolveLocation = createTemplateLocationResolver(member.getSourceFile(), template);
798
766
 
799
- if (tag === 'css') {
767
+ if (tag === "css") {
800
768
  CSS_PROPERTY_RE.lastIndex = 0;
801
769
  while (true) {
802
770
  const match = CSS_PROPERTY_RE.exec(rendered);
803
771
  if (!match) break;
804
- const rawValue = match[2] ?? '';
772
+ const rawValue = match[2] ?? "";
805
773
  const value = rawValue.trim();
806
774
  if (value && REFS_TEST_RE.test(value)) {
807
775
  const valueOffsetInMatch = match[0].lastIndexOf(rawValue);
808
776
  const leadingWhitespace = rawValue.length - rawValue.trimStart().length;
809
777
  const valueLocation = resolveLocation(
810
- match.index + valueOffsetInMatch + leadingWhitespace
778
+ match.index + valueOffsetInMatch + leadingWhitespace,
811
779
  );
812
780
  expressions.push({
813
- context: 'instance',
781
+ context: "instance",
814
782
  eventHandler: false,
815
- kind: 'css',
783
+ kind: "css",
816
784
  text: value,
817
- location: valueLocation
785
+ location: valueLocation,
818
786
  });
819
787
  }
820
788
  }
821
789
  continue;
822
790
  }
823
791
 
824
- const root = parse(rendered, {comment: true});
792
+ const root = parse(rendered, { comment: true });
825
793
  walkHtmlNode(
826
794
  root,
827
795
  expressions,
@@ -829,7 +797,7 @@ function extractTemplateExpressions(
829
797
  componentPropertyMaps,
830
798
  supportedProps,
831
799
  new Set(),
832
- resolveLocation
800
+ resolveLocation,
833
801
  );
834
802
  }
835
803
 
@@ -851,7 +819,7 @@ function findDefinedTagNames(sourceFile) {
851
819
  if (
852
820
  ts.isCallExpression(node) &&
853
821
  ts.isPropertyAccessExpression(node.expression) &&
854
- node.expression.name.text === 'define' &&
822
+ node.expression.name.text === "define" &&
855
823
  ts.isIdentifier(node.expression.expression) &&
856
824
  node.arguments.length > 0 &&
857
825
  ts.isStringLiteral(node.arguments[0])
@@ -868,9 +836,9 @@ function findDefinedTagNames(sourceFile) {
868
836
  // Recursively finds Wrec component files under a directory
869
837
  // and reports each match.
870
838
  function findWrecFiles(rootDir, onMatch) {
871
- const walk = currentDir => {
839
+ const walk = (currentDir) => {
872
840
  const entries = fs
873
- .readdirSync(currentDir, {withFileTypes: true})
841
+ .readdirSync(currentDir, { withFileTypes: true })
874
842
  .sort((a, b) => a.name.localeCompare(b.name));
875
843
 
876
844
  for (const entry of entries) {
@@ -882,7 +850,7 @@ function findWrecFiles(rootDir, onMatch) {
882
850
  }
883
851
 
884
852
  if (!entry.isFile()) continue;
885
- if (!fullPath.endsWith('.js') && !fullPath.endsWith('.ts')) continue;
853
+ if (!fullPath.endsWith(".js") && !fullPath.endsWith(".ts")) continue;
886
854
  if (isWrecComponentFile(fullPath)) onMatch(fullPath);
887
855
  }
888
856
  };
@@ -898,7 +866,7 @@ function createTemplateLocationResolver(sourceFile, template) {
898
866
  segments.push({
899
867
  renderedEnd: template.text.length,
900
868
  renderedStart: 0,
901
- sourceStart: template.getStart(sourceFile) + 1
869
+ sourceStart: template.getStart(sourceFile) + 1,
902
870
  });
903
871
  } else {
904
872
  let renderedStart = 0;
@@ -906,7 +874,7 @@ function createTemplateLocationResolver(sourceFile, template) {
906
874
  segments.push({
907
875
  renderedEnd: headText.length,
908
876
  renderedStart,
909
- sourceStart: template.head.getStart(sourceFile) + 1
877
+ sourceStart: template.head.getStart(sourceFile) + 1,
910
878
  });
911
879
  renderedStart += headText.length;
912
880
 
@@ -915,29 +883,27 @@ function createTemplateLocationResolver(sourceFile, template) {
915
883
  segments.push({
916
884
  renderedEnd: renderedStart + span.literal.text.length,
917
885
  renderedStart,
918
- sourceStart: span.literal.getStart(sourceFile) + 1
886
+ sourceStart: span.literal.getStart(sourceFile) + 1,
919
887
  });
920
888
  renderedStart += span.literal.text.length;
921
889
  });
922
890
  }
923
891
 
924
- return offset => {
892
+ return (offset) => {
925
893
  const segment =
926
894
  segments.find(
927
- candidate =>
928
- offset >= candidate.renderedStart && offset <= candidate.renderedEnd
895
+ (candidate) => offset >= candidate.renderedStart && offset <= candidate.renderedEnd,
929
896
  ) ?? segments[segments.length - 1];
930
897
  if (!segment) return null;
931
898
 
932
- const sourceOffset =
933
- segment.sourceStart + Math.max(0, offset - segment.renderedStart);
899
+ const sourceOffset = segment.sourceStart + Math.max(0, offset - segment.renderedStart);
934
900
  return sourceFile.getLineAndCharacterOfPosition(sourceOffset);
935
901
  };
936
902
  }
937
903
 
938
904
  // Formats an optional source location as line and column text.
939
905
  function formatLocation(location) {
940
- if (!location) return '';
906
+ if (!location) return "";
941
907
  return `:${location.line + 1}:${location.character + 1}`;
942
908
  }
943
909
 
@@ -961,13 +927,13 @@ function getHtmlAttributeLocation(node, attrName, resolveLocation) {
961
927
 
962
928
  // Formats a lint finding that may optionally include a source location.
963
929
  function formatMaybeLocatedFinding(finding) {
964
- if (typeof finding === 'string') return finding;
930
+ if (typeof finding === "string") return finding;
965
931
  return `${formatLocation(finding.location)} ${finding.message}`.trim();
966
932
  }
967
933
 
968
934
  // Gets the message text from a lint finding that may include a location.
969
935
  function getLocatedFindingMessage(finding) {
970
- return typeof finding === 'string' ? finding : finding.message;
936
+ return typeof finding === "string" ? finding : finding.message;
971
937
  }
972
938
 
973
939
  // Compares lint findings that may optionally include source locations.
@@ -976,19 +942,13 @@ function compareLocatedFindings(a, b) {
976
942
  }
977
943
 
978
944
  // Formats the collected lint findings into the command-line report output.
979
- function formatReport(
980
- filePath,
981
- supportedProps,
982
- allExpressions,
983
- findings,
984
- options = {}
985
- ) {
945
+ function formatReport(filePath, supportedProps, allExpressions, findings, options = {}) {
986
946
  const {
987
947
  fileLabel = filePath,
988
948
  showFileHeader = true,
989
949
  showDetailsForCleanFile = true,
990
950
  showNoIssuesMessage = true,
991
- verbose = false
951
+ verbose = false,
992
952
  } = options;
993
953
  const lines = [];
994
954
 
@@ -1022,224 +982,216 @@ function formatReport(
1022
982
  if (showFileHeader) lines.push(`file: ${fileLabel}`);
1023
983
 
1024
984
  if (verbose && (hasIssues || showDetailsForCleanFile)) {
1025
- lines.push('properties:');
985
+ lines.push("properties:");
1026
986
  if (supportedProps.size === 0) {
1027
- lines.push(' none');
987
+ lines.push(" none");
1028
988
  } else {
1029
- for (const [name, info] of [...supportedProps.entries()].sort(
1030
- ([a], [b]) => a.localeCompare(b)
989
+ for (const [name, info] of [...supportedProps.entries()].sort(([a], [b]) =>
990
+ a.localeCompare(b),
1031
991
  )) {
1032
992
  lines.push(` ${name}: ${info.typeText}`);
1033
993
  }
1034
994
  }
1035
995
 
1036
- lines.push('expressions:');
996
+ lines.push("expressions:");
1037
997
  if (allExpressions.length === 0) {
1038
- lines.push(' none');
998
+ lines.push(" none");
1039
999
  } else {
1040
- allExpressions.forEach(expr => {
1041
- lines.push(
1042
- ` [${expr.kind}]${formatLocation(expr.location)} ${expr.text}`
1043
- );
1000
+ allExpressions.forEach((expr) => {
1001
+ lines.push(` [${expr.kind}]${formatLocation(expr.location)} ${expr.text}`);
1044
1002
  });
1045
1003
  }
1046
1004
  }
1047
1005
 
1048
1006
  if (findings.duplicateProperties.length > 0) {
1049
- lines.push('duplicate properties:');
1050
- findings.duplicateProperties.forEach(finding =>
1051
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1007
+ lines.push("duplicate properties:");
1008
+ findings.duplicateProperties.forEach((finding) =>
1009
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1052
1010
  );
1053
1011
  }
1054
1012
 
1055
1013
  if (findings.extraArguments.length > 0) {
1056
- lines.push('extra arguments:');
1057
- findings.extraArguments.forEach(finding => {
1058
- const locationPrefix = finding.location
1059
- ? `${formatLocation(finding.location)} `
1060
- : '';
1014
+ lines.push("extra arguments:");
1015
+ findings.extraArguments.forEach((finding) => {
1016
+ const locationPrefix = finding.location ? `${formatLocation(finding.location)} ` : "";
1061
1017
  lines.push(
1062
1018
  ` ${locationPrefix}${finding.methodName}: argument ${finding.argumentIndex} ` +
1063
1019
  `"${finding.argument}" exceeds the ` +
1064
- `${finding.parameterCount}-parameter signature`
1020
+ `${finding.parameterCount}-parameter signature`,
1065
1021
  );
1066
1022
  });
1067
1023
  }
1068
1024
 
1069
1025
  if (findings.incompatibleArguments.length > 0) {
1070
- lines.push('incompatible arguments:');
1071
- findings.incompatibleArguments.forEach(finding => {
1072
- const locationPrefix = finding.location
1073
- ? `${formatLocation(finding.location)} `
1074
- : '';
1026
+ lines.push("incompatible arguments:");
1027
+ findings.incompatibleArguments.forEach((finding) => {
1028
+ const locationPrefix = finding.location ? `${formatLocation(finding.location)} ` : "";
1075
1029
  lines.push(
1076
1030
  ` ${locationPrefix}${finding.methodName}: argument "${finding.argument}" ` +
1077
1031
  `has type ${finding.argumentType}, but parameter ` +
1078
- `"${finding.parameterName}" expects ${finding.parameterType}`
1032
+ `"${finding.parameterName}" expects ${finding.parameterType}`,
1079
1033
  );
1080
1034
  });
1081
1035
  }
1082
1036
 
1083
1037
  if (findings.incompatibleDeclareTypes.length > 0) {
1084
- lines.push('incompatible declare types:');
1085
- findings.incompatibleDeclareTypes.forEach(finding =>
1086
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1038
+ lines.push("incompatible declare types:");
1039
+ findings.incompatibleDeclareTypes.forEach((finding) =>
1040
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1087
1041
  );
1088
1042
  }
1089
1043
 
1090
1044
  if (findings.invalidCheckedBindings.length > 0) {
1091
- lines.push('invalid checked bindings:');
1092
- findings.invalidCheckedBindings.forEach(finding =>
1093
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1045
+ lines.push("invalid checked bindings:");
1046
+ findings.invalidCheckedBindings.forEach((finding) =>
1047
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1094
1048
  );
1095
1049
  }
1096
1050
 
1097
1051
  if (findings.invalidComputedProperties.length > 0) {
1098
- lines.push('invalid computed properties:');
1099
- findings.invalidComputedProperties.forEach(finding =>
1100
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1052
+ lines.push("invalid computed properties:");
1053
+ findings.invalidComputedProperties.forEach((finding) =>
1054
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1101
1055
  );
1102
1056
  }
1103
1057
 
1104
1058
  if (findings.invalidDefaultValues.length > 0) {
1105
- lines.push('invalid default values:');
1106
- findings.invalidDefaultValues.forEach(finding =>
1107
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1059
+ lines.push("invalid default values:");
1060
+ findings.invalidDefaultValues.forEach((finding) =>
1061
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1108
1062
  );
1109
1063
  }
1110
1064
 
1111
1065
  if (findings.invalidEventHandlers.length > 0) {
1112
- lines.push('invalid event handler references:');
1113
- findings.invalidEventHandlers.forEach(finding =>
1114
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1066
+ lines.push("invalid event handler references:");
1067
+ findings.invalidEventHandlers.forEach((finding) =>
1068
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1115
1069
  );
1116
1070
  }
1117
1071
 
1118
1072
  if (findings.invalidFormAssocValues.length > 0) {
1119
- lines.push('invalid form-assoc values:');
1120
- findings.invalidFormAssocValues.forEach(finding =>
1121
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1073
+ lines.push("invalid form-assoc values:");
1074
+ findings.invalidFormAssocValues.forEach((finding) =>
1075
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1122
1076
  );
1123
1077
  }
1124
1078
 
1125
1079
  if (findings.invalidHtmlNesting.length > 0) {
1126
- lines.push('invalid html nesting:');
1127
- findings.invalidHtmlNesting.forEach(finding =>
1128
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1080
+ lines.push("invalid html nesting:");
1081
+ findings.invalidHtmlNesting.forEach((finding) =>
1082
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1129
1083
  );
1130
1084
  }
1131
1085
 
1132
1086
  if (findings.invalidRefAttributes.length > 0) {
1133
- lines.push('invalid ref attributes:');
1134
- findings.invalidRefAttributes.forEach(finding =>
1135
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1087
+ lines.push("invalid ref attributes:");
1088
+ findings.invalidRefAttributes.forEach((finding) =>
1089
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1136
1090
  );
1137
1091
  }
1138
1092
 
1139
1093
  if (findings.invalidTypeProperties.length > 0) {
1140
- lines.push('invalid type properties:');
1141
- findings.invalidTypeProperties.forEach(finding =>
1142
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1094
+ lines.push("invalid type properties:");
1095
+ findings.invalidTypeProperties.forEach((finding) =>
1096
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1143
1097
  );
1144
1098
  }
1145
1099
 
1146
1100
  if (findings.invalidUsedByReferences.length > 0) {
1147
- lines.push('invalid usedBy references:');
1148
- findings.invalidUsedByReferences.forEach(finding =>
1149
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1101
+ lines.push("invalid usedBy references:");
1102
+ findings.invalidUsedByReferences.forEach((finding) =>
1103
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1150
1104
  );
1151
1105
  }
1152
1106
 
1153
1107
  if (findings.invalidUseStateMaps.length > 0) {
1154
- lines.push('invalid useState map entries:');
1155
- findings.invalidUseStateMaps.forEach(finding =>
1156
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1108
+ lines.push("invalid useState map entries:");
1109
+ findings.invalidUseStateMaps.forEach((finding) =>
1110
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1157
1111
  );
1158
1112
  }
1159
1113
 
1160
1114
  if (findings.invalidValueBindings.length > 0) {
1161
- lines.push('invalid value bindings:');
1162
- findings.invalidValueBindings.forEach(finding =>
1163
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1115
+ lines.push("invalid value bindings:");
1116
+ findings.invalidValueBindings.forEach((finding) =>
1117
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1164
1118
  );
1165
1119
  }
1166
1120
 
1167
1121
  if (findings.invalidValuesConfigurations.length > 0) {
1168
- lines.push('invalid values configurations:');
1169
- findings.invalidValuesConfigurations.forEach(finding =>
1170
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1122
+ lines.push("invalid values configurations:");
1123
+ findings.invalidValuesConfigurations.forEach((finding) =>
1124
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1171
1125
  );
1172
1126
  }
1173
1127
 
1174
1128
  if (findings.missingRequiredMembers.length > 0) {
1175
- lines.push('missing required members:');
1176
- findings.missingRequiredMembers.forEach(finding =>
1177
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1129
+ lines.push("missing required members:");
1130
+ findings.missingRequiredMembers.forEach((finding) =>
1131
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1178
1132
  );
1179
1133
  }
1180
1134
 
1181
1135
  if (findings.missingTypeProperties.length > 0) {
1182
- lines.push('missing type properties:');
1183
- findings.missingTypeProperties.forEach(finding =>
1184
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1136
+ lines.push("missing type properties:");
1137
+ findings.missingTypeProperties.forEach((finding) =>
1138
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1185
1139
  );
1186
1140
  }
1187
1141
 
1188
1142
  if (findings.reservedProperties.length > 0) {
1189
- lines.push('reserved property names:');
1190
- findings.reservedProperties.forEach(finding =>
1191
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1143
+ lines.push("reserved property names:");
1144
+ findings.reservedProperties.forEach((finding) =>
1145
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1192
1146
  );
1193
1147
  }
1194
1148
 
1195
1149
  if (findings.typeErrors.length > 0) {
1196
- lines.push('type errors:');
1197
- findings.typeErrors.forEach(finding => {
1198
- const locationPrefix = finding.location
1199
- ? `${formatLocation(finding.location)} `
1200
- : '';
1150
+ lines.push("type errors:");
1151
+ findings.typeErrors.forEach((finding) => {
1152
+ const locationPrefix = finding.location ? `${formatLocation(finding.location)} ` : "";
1201
1153
  lines.push(` ${locationPrefix}${finding.expression}: ${finding.message}`);
1202
1154
  });
1203
1155
  }
1204
1156
 
1205
1157
  if (findings.undefinedContextFunctions.length > 0) {
1206
- lines.push('undefined context functions:');
1207
- findings.undefinedContextFunctions.forEach(finding =>
1208
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1158
+ lines.push("undefined context functions:");
1159
+ findings.undefinedContextFunctions.forEach((finding) =>
1160
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1209
1161
  );
1210
1162
  }
1211
1163
 
1212
1164
  if (findings.undefinedMethods.length > 0) {
1213
- lines.push('undefined methods:');
1214
- findings.undefinedMethods.forEach(finding =>
1215
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1165
+ lines.push("undefined methods:");
1166
+ findings.undefinedMethods.forEach((finding) =>
1167
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1216
1168
  );
1217
1169
  }
1218
1170
 
1219
1171
  if (findings.undefinedProperties.length > 0) {
1220
- lines.push('undefined properties:');
1221
- findings.undefinedProperties.forEach(finding =>
1222
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1172
+ lines.push("undefined properties:");
1173
+ findings.undefinedProperties.forEach((finding) =>
1174
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1223
1175
  );
1224
1176
  }
1225
1177
 
1226
1178
  if (findings.unsupportedEventNames.length > 0) {
1227
- lines.push('unsupported event names:');
1228
- findings.unsupportedEventNames.forEach(finding =>
1229
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1179
+ lines.push("unsupported event names:");
1180
+ findings.unsupportedEventNames.forEach((finding) =>
1181
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1230
1182
  );
1231
1183
  }
1232
1184
 
1233
1185
  if (findings.unsupportedHtmlAttributes.length > 0) {
1234
- lines.push('unsupported html attributes:');
1235
- findings.unsupportedHtmlAttributes.forEach(finding =>
1236
- lines.push(` ${formatMaybeLocatedFinding(finding)}`)
1186
+ lines.push("unsupported html attributes:");
1187
+ findings.unsupportedHtmlAttributes.forEach((finding) =>
1188
+ lines.push(` ${formatMaybeLocatedFinding(finding)}`),
1237
1189
  );
1238
1190
  }
1239
1191
 
1240
- if (!hasIssues && showNoIssuesMessage) lines.push('no issues found');
1192
+ if (!hasIssues && showNoIssuesMessage) lines.push("no issues found");
1241
1193
 
1242
- return `${lines.join('\n')}\n`;
1194
+ return `${lines.join("\n")}\n`;
1243
1195
  }
1244
1196
 
1245
1197
  // Builds a map from tag names to supported properties
@@ -1252,40 +1204,24 @@ function getComponentPropertyMaps(filePath, sourceText, seen = new Set()) {
1252
1204
  if (seen.has(resolved)) return new Map();
1253
1205
  seen.add(resolved);
1254
1206
 
1255
- const text = sourceText ?? fs.readFileSync(resolved, 'utf8');
1256
- const scriptKind = resolved.endsWith('.ts')
1257
- ? ts.ScriptKind.TS
1258
- : ts.ScriptKind.JS;
1259
- const sourceFile = ts.createSourceFile(
1260
- resolved,
1261
- text,
1262
- ts.ScriptTarget.ESNext,
1263
- true,
1264
- scriptKind
1265
- );
1207
+ const text = sourceText ?? fs.readFileSync(resolved, "utf8");
1208
+ const scriptKind = resolved.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS;
1209
+ const sourceFile = ts.createSourceFile(resolved, text, ts.ScriptTarget.ESNext, true, scriptKind);
1266
1210
  const tagNames = findDefinedTagNames(sourceFile);
1267
1211
  const propertyMaps = new Map();
1268
1212
 
1269
1213
  for (const classNode of collectWrecClasses(sourceFile)) {
1270
- const tagName = classNode.name
1271
- ? tagNames.get(classNode.name.text)
1272
- : undefined;
1214
+ const tagName = classNode.name ? tagNames.get(classNode.name.text) : undefined;
1273
1215
  if (!tagName) continue;
1274
1216
  propertyMaps.set(tagName, collectSupportedPropertyNames(classNode));
1275
1217
  }
1276
1218
 
1277
1219
  for (const statement of sourceFile.statements) {
1278
- if (
1279
- !ts.isImportDeclaration(statement) ||
1280
- !ts.isStringLiteral(statement.moduleSpecifier)
1281
- ) {
1220
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) {
1282
1221
  continue;
1283
1222
  }
1284
1223
 
1285
- const importPath = resolveImportPath(
1286
- path.dirname(resolved),
1287
- statement.moduleSpecifier.text
1288
- );
1224
+ const importPath = resolveImportPath(path.dirname(resolved), statement.moduleSpecifier.text);
1289
1225
  if (!importPath) continue;
1290
1226
 
1291
1227
  const importedMaps = getComponentPropertyMaps(importPath, undefined, seen);
@@ -1323,22 +1259,19 @@ function getGetterName(reference) {
1323
1259
  // Returns a lowercased HTML tag name for a parsed HTML node.
1324
1260
  function getHtmlTagName(node) {
1325
1261
  const tagName = node.rawTagName || node.tagName;
1326
- return typeof tagName === 'string' ? tagName.toLowerCase() : '';
1262
+ return typeof tagName === "string" ? tagName.toLowerCase() : "";
1327
1263
  }
1328
1264
 
1329
1265
  // Returns the literal input type attribute value when one is present.
1330
1266
  function getInputType(node) {
1331
- const type = node.getAttribute('type');
1332
- return typeof type === 'string' ? type.toLowerCase() : undefined;
1267
+ const type = node.getAttribute("type");
1268
+ return typeof type === "string" ? type.toLowerCase() : undefined;
1333
1269
  }
1334
1270
 
1335
1271
  // Gets an object-literal property with the given key.
1336
1272
  function getObjectProperty(objectLiteral, key) {
1337
1273
  for (const property of objectLiteral.properties) {
1338
- if (
1339
- !ts.isPropertyAssignment(property) &&
1340
- !ts.isShorthandPropertyAssignment(property)
1341
- ) {
1274
+ if (!ts.isPropertyAssignment(property) && !ts.isShorthandPropertyAssignment(property)) {
1342
1275
  continue;
1343
1276
  }
1344
1277
  const name = getMemberName(property);
@@ -1349,15 +1282,9 @@ function getObjectProperty(objectLiteral, key) {
1349
1282
  // Resolves the effective parameter type,
1350
1283
  // including element types for rest parameters.
1351
1284
  function getParameterType(checker, parameterSymbol, location, isRestArgument) {
1352
- const parameterType = checker.getTypeOfSymbolAtLocation(
1353
- parameterSymbol,
1354
- location
1355
- );
1285
+ const parameterType = checker.getTypeOfSymbolAtLocation(parameterSymbol, location);
1356
1286
  if (!isRestArgument) return parameterType;
1357
- if (
1358
- !checker.isArrayType(parameterType) &&
1359
- !checker.isTupleType(parameterType)
1360
- ) {
1287
+ if (!checker.isArrayType(parameterType) && !checker.isTupleType(parameterType)) {
1361
1288
  return parameterType;
1362
1289
  }
1363
1290
 
@@ -1365,19 +1292,24 @@ function getParameterType(checker, parameterSymbol, location, isRestArgument) {
1365
1292
  return typeArguments[0] ?? parameterType;
1366
1293
  }
1367
1294
 
1295
+ // Returns the text form of a private identifier.
1296
+ function getPrivateNameText(name) {
1297
+ return name.text;
1298
+ }
1299
+
1368
1300
  // Returns the Wrec property config type name for a normalized type string.
1369
1301
  function getPropertyConfigTypeName(typeName) {
1370
1302
  switch (typeName) {
1371
- case 'array':
1372
- return 'Array';
1373
- case 'boolean':
1374
- return 'Boolean';
1375
- case 'number':
1376
- return 'Number';
1377
- case 'object':
1378
- return 'Object';
1379
- case 'string':
1380
- return 'String';
1303
+ case "array":
1304
+ return "Array";
1305
+ case "boolean":
1306
+ return "Boolean";
1307
+ case "number":
1308
+ return "Number";
1309
+ case "object":
1310
+ return "Object";
1311
+ case "string":
1312
+ return "String";
1381
1313
  default:
1382
1314
  return typeName;
1383
1315
  }
@@ -1413,10 +1345,7 @@ function getStringArrayLiteral(property) {
1413
1345
 
1414
1346
  const values = [];
1415
1347
  for (const element of property.initializer.elements) {
1416
- if (
1417
- !ts.isStringLiteral(element) &&
1418
- !ts.isNoSubstitutionTemplateLiteral(element)
1419
- ) {
1348
+ if (!ts.isStringLiteral(element) && !ts.isNoSubstitutionTemplateLiteral(element)) {
1420
1349
  return undefined;
1421
1350
  }
1422
1351
  values.push(element.text);
@@ -1428,22 +1357,19 @@ function getStringArrayLiteral(property) {
1428
1357
  function getValuesConfigurationError(property) {
1429
1358
  if (!property || !ts.isPropertyAssignment(property)) return undefined;
1430
1359
 
1431
- const {initializer} = property;
1360
+ const { initializer } = property;
1432
1361
  if (!ts.isArrayLiteralExpression(initializer)) {
1433
- return 'values must be a literal array of strings';
1362
+ return "values must be a literal array of strings";
1434
1363
  }
1435
1364
 
1436
1365
  if (initializer.elements.length === 0) {
1437
- return 'values must not be empty';
1366
+ return "values must not be empty";
1438
1367
  }
1439
1368
 
1440
1369
  const seenValues = new Set();
1441
1370
  for (const element of initializer.elements) {
1442
- if (
1443
- !ts.isStringLiteral(element) &&
1444
- !ts.isNoSubstitutionTemplateLiteral(element)
1445
- ) {
1446
- return 'values must contain only string literals';
1371
+ if (!ts.isStringLiteral(element) && !ts.isNoSubstitutionTemplateLiteral(element)) {
1372
+ return "values must contain only string literals";
1447
1373
  }
1448
1374
 
1449
1375
  if (seenValues.has(element.text)) {
@@ -1494,29 +1420,23 @@ function getTypeSyntaxText(sourceFile, expression) {
1494
1420
 
1495
1421
  if (ts.isIdentifier(expression)) {
1496
1422
  switch (expression.text) {
1497
- case 'String':
1498
- return 'string';
1499
- case 'Number':
1500
- return 'number';
1501
- case 'Boolean':
1502
- return 'boolean';
1503
- case 'Array':
1504
- return 'unknown[]';
1505
- case 'Object':
1506
- return 'object';
1423
+ case "String":
1424
+ return "string";
1425
+ case "Number":
1426
+ return "number";
1427
+ case "Boolean":
1428
+ return "boolean";
1429
+ case "Array":
1430
+ return "unknown[]";
1431
+ case "Object":
1432
+ return "object";
1507
1433
  default:
1508
1434
  return expression.getText(sourceFile);
1509
1435
  }
1510
1436
  }
1511
1437
 
1512
- if (
1513
- ts.isCallExpression(expression) &&
1514
- ts.isIdentifier(expression.expression)
1515
- ) {
1516
- if (
1517
- expression.expression.text === 'Array' &&
1518
- expression.typeArguments?.length === 1
1519
- ) {
1438
+ if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)) {
1439
+ if (expression.expression.text === "Array" && expression.typeArguments?.length === 1) {
1520
1440
  return `${expression.typeArguments[0].getText(sourceFile)}[]`;
1521
1441
  }
1522
1442
  }
@@ -1533,7 +1453,7 @@ function hasStaticHtmlDefinition(classNode) {
1533
1453
  for (const member of classNode.members) {
1534
1454
  if (!hasStaticModifier(member)) continue;
1535
1455
  if (!ts.isPropertyDeclaration(member)) continue;
1536
- if (getMemberName(member) === 'html') return true;
1456
+ if (getMemberName(member) === "html") return true;
1537
1457
  }
1538
1458
  return false;
1539
1459
  }
@@ -1559,9 +1479,7 @@ function isDeclarePropertyDeclaration(member) {
1559
1479
  return (
1560
1480
  ts.isPropertyDeclaration(member) &&
1561
1481
  ts.canHaveModifiers(member) &&
1562
- ts
1563
- .getModifiers(member)
1564
- ?.some(mod => mod.kind === ts.SyntaxKind.DeclareKeyword)
1482
+ ts.getModifiers(member)?.some((mod) => mod.kind === ts.SyntaxKind.DeclareKeyword)
1565
1483
  );
1566
1484
  }
1567
1485
 
@@ -1588,15 +1506,11 @@ function isNativeFormControl(node) {
1588
1506
  // Returns whether a type represents an object-like value other than an array.
1589
1507
  function isNonArrayObjectLikeType(checker, type) {
1590
1508
  if (type.isUnion()) {
1591
- return type.types.every(member =>
1592
- isNonArrayObjectLikeType(checker, member)
1593
- );
1509
+ return type.types.every((member) => isNonArrayObjectLikeType(checker, member));
1594
1510
  }
1595
1511
 
1596
1512
  if (type.isIntersection()) {
1597
- return type.types.every(member =>
1598
- isNonArrayObjectLikeType(checker, member)
1599
- );
1513
+ return type.types.every((member) => isNonArrayObjectLikeType(checker, member));
1600
1514
  }
1601
1515
 
1602
1516
  return (
@@ -1609,7 +1523,7 @@ function isNonArrayObjectLikeType(checker, type) {
1609
1523
  // Returns whether a type is fully numeric or any-like for arithmetic checks.
1610
1524
  function isNumericLikeType(type) {
1611
1525
  const parts = type.isUnion() ? type.types : [type];
1612
- return parts.every(part => {
1526
+ return parts.every((part) => {
1613
1527
  const flags = part.flags;
1614
1528
  return Boolean(
1615
1529
  flags &
@@ -1617,23 +1531,26 @@ function isNumericLikeType(type) {
1617
1531
  ts.TypeFlags.NumberLiteral |
1618
1532
  ts.TypeFlags.BigInt |
1619
1533
  ts.TypeFlags.BigIntLiteral |
1620
- ts.TypeFlags.Any)
1534
+ ts.TypeFlags.Any),
1621
1535
  );
1622
1536
  });
1623
1537
  }
1624
1538
 
1539
+ // Returns whether a node is a private identifier.
1540
+ function isPrivateName(node) {
1541
+ return ts.isPrivateIdentifier(node);
1542
+ }
1543
+
1625
1544
  // Returns whether a file defines at least one Wrec component class.
1626
1545
  function isWrecComponentFile(filePath) {
1627
- const sourceText = fs.readFileSync(filePath, 'utf8');
1628
- const scriptKind = filePath.endsWith('.ts')
1629
- ? ts.ScriptKind.TS
1630
- : ts.ScriptKind.JS;
1546
+ const sourceText = fs.readFileSync(filePath, "utf8");
1547
+ const scriptKind = filePath.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS;
1631
1548
  const sourceFile = ts.createSourceFile(
1632
1549
  filePath,
1633
1550
  sourceText,
1634
1551
  ts.ScriptTarget.ESNext,
1635
1552
  true,
1636
- scriptKind
1553
+ scriptKind,
1637
1554
  );
1638
1555
  return collectWrecClasses(sourceFile).length > 0;
1639
1556
  }
@@ -1643,10 +1560,7 @@ function isWrecRooted(expression) {
1643
1560
  if (ts.isIdentifier(expression) && expression.text === WREC_REF_NAME) {
1644
1561
  return true;
1645
1562
  }
1646
- if (
1647
- ts.isPropertyAccessExpression(expression) ||
1648
- ts.isElementAccessExpression(expression)
1649
- ) {
1563
+ if (ts.isPropertyAccessExpression(expression) || ts.isElementAccessExpression(expression)) {
1650
1564
  return isWrecRooted(expression.expression);
1651
1565
  }
1652
1566
  return false;
@@ -1655,7 +1569,7 @@ function isWrecRooted(expression) {
1655
1569
  // Lints a component file by path after resolving and reading it.
1656
1570
  export function lintFile(filePath, options = {}) {
1657
1571
  const resolved = validateFilePath(filePath);
1658
- return lintSource(resolved, fs.readFileSync(resolved, 'utf8'), options);
1572
+ return lintSource(resolved, fs.readFileSync(resolved, "utf8"), options);
1659
1573
  }
1660
1574
 
1661
1575
  // Lints provided component source text and returns a formatted report.
@@ -1666,7 +1580,7 @@ export function lintSource(filePath, sourceText, options = {}) {
1666
1580
 
1667
1581
  const checker = baseProgram.getTypeChecker();
1668
1582
  const classNode = collectWrecClasses(sourceFile)[0];
1669
- if (!classNode) throw new Error('file must define a subclass of Wrec');
1583
+ if (!classNode) throw new Error("file must define a subclass of Wrec");
1670
1584
  const componentPropertyMaps = getComponentPropertyMaps(filePath, sourceText);
1671
1585
 
1672
1586
  const {
@@ -1676,10 +1590,11 @@ export function lintSource(filePath, sourceText, options = {}) {
1676
1590
  duplicateProperties,
1677
1591
  formAssociated,
1678
1592
  propertyEntries,
1679
- reservedProperties
1593
+ reservedProperties,
1680
1594
  } = extractProperties(sourceFile, checker, classNode);
1681
1595
  const declaredPropertyTypes = collectDeclaredPropertyTypes(classNode);
1682
1596
  const getterNames = collectGetterNames(classNode);
1597
+ const privateMembers = collectPrivateMembers(classNode);
1683
1598
  const allMethods = collectClassMethods(classNode);
1684
1599
  const findings = {
1685
1600
  duplicateProperties,
@@ -1706,48 +1621,44 @@ export function lintSource(filePath, sourceText, options = {}) {
1706
1621
  undefinedMethods: [],
1707
1622
  undefinedProperties: [],
1708
1623
  unsupportedEventNames: [],
1709
- unsupportedHtmlAttributes: []
1624
+ unsupportedHtmlAttributes: [],
1710
1625
  };
1711
1626
  const templateExprs = extractTemplateExpressions(
1712
1627
  classNode,
1713
1628
  findings,
1714
1629
  componentPropertyMaps,
1715
- supportedProps
1630
+ supportedProps,
1716
1631
  );
1717
1632
  const methodCodeItems = collectMethodCodeItems(classNode);
1718
1633
  const allExpressions = [...templateExprs, ...computedExprs];
1719
- const allCodeItems = [...allExpressions, ...methodCodeItems].map(item => ({
1720
- checkContextCalls: item.kind !== 'method',
1721
- shape: 'shape' in item ? item.shape : 'expression',
1722
- ...item
1634
+ const allCodeItems = [...allExpressions, ...methodCodeItems].map((item) => ({
1635
+ checkContextCalls: item.kind !== "method",
1636
+ shape: "shape" in item ? item.shape : "expression",
1637
+ ...item,
1723
1638
  }));
1724
1639
 
1725
- if (allMethods.has('formAssociatedCallback') && !formAssociated) {
1640
+ if (allMethods.has("formAssociatedCallback") && !formAssociated) {
1726
1641
  const callbackMember = classNode.members.find(
1727
- member =>
1728
- ts.isMethodDeclaration(member) &&
1729
- getMemberName(member) === 'formAssociatedCallback'
1642
+ (member) =>
1643
+ ts.isMethodDeclaration(member) && getMemberName(member) === "formAssociatedCallback",
1730
1644
  );
1731
- findings.missingRequiredMembers.push(
1732
- {
1733
- location: callbackMember
1734
- ? callbackMember
1645
+ findings.missingRequiredMembers.push({
1646
+ location: callbackMember
1647
+ ? callbackMember
1735
1648
  .getSourceFile()
1736
1649
  .getLineAndCharacterOfPosition(
1737
- callbackMember.name.getStart(callbackMember.getSourceFile())
1650
+ callbackMember.name.getStart(callbackMember.getSourceFile()),
1738
1651
  )
1739
- : null,
1740
- message:
1741
- 'formAssociatedCallback is defined, but static formAssociated is not true'
1742
- }
1743
- );
1652
+ : null,
1653
+ message: "formAssociatedCallback is defined, but static formAssociated is not true",
1654
+ });
1744
1655
  }
1745
1656
  if (!hasStaticHtmlDefinition(classNode)) {
1746
1657
  findings.missingRequiredMembers.push({
1747
1658
  location: sourceFile.getLineAndCharacterOfPosition(
1748
- classNode.name?.getStart(sourceFile) ?? classNode.getStart(sourceFile)
1659
+ classNode.name?.getStart(sourceFile) ?? classNode.getStart(sourceFile),
1749
1660
  ),
1750
- message: 'static html property must be defined'
1661
+ message: "static html property must be defined",
1751
1662
  });
1752
1663
  }
1753
1664
 
@@ -1756,7 +1667,7 @@ export function lintSource(filePath, sourceText, options = {}) {
1756
1667
  classNode,
1757
1668
  supportedProps,
1758
1669
  contextKeys,
1759
- allCodeItems
1670
+ allCodeItems,
1760
1671
  );
1761
1672
  const augmentedProgram = createProgram(filePath, augmentedSource);
1762
1673
  const augmentedSourceFile = augmentedProgram.getSourceFile(filePath);
@@ -1765,7 +1676,7 @@ export function lintSource(filePath, sourceText, options = {}) {
1765
1676
  const augmentedChecker = augmentedProgram.getTypeChecker();
1766
1677
  const augmentedClassNode = collectWrecClasses(augmentedSourceFile)[0];
1767
1678
  if (!augmentedClassNode) {
1768
- throw new Error('unable to find Wrec subclass after augmentation');
1679
+ throw new Error("unable to find Wrec subclass after augmentation");
1769
1680
  }
1770
1681
 
1771
1682
  const helperCodeNodes = collectHelperCodeNodes(augmentedSourceFile);
@@ -1778,24 +1689,20 @@ export function lintSource(filePath, sourceText, options = {}) {
1778
1689
  propertyEntries,
1779
1690
  getterNames,
1780
1691
  allMethods,
1781
- findings
1692
+ findings,
1782
1693
  );
1783
1694
  collectUseStateMapErrors(classNode, supportedProps, findings);
1784
1695
 
1785
- allExpressions.forEach(expr => {
1786
- if (
1787
- expr.eventHandler &&
1788
- IDENTIFIER_RE.test(expr.text) &&
1789
- !allMethods.has(expr.text)
1790
- ) {
1696
+ allExpressions.forEach((expr) => {
1697
+ if (expr.eventHandler && IDENTIFIER_RE.test(expr.text) && !allMethods.has(expr.text)) {
1791
1698
  uniquePush(
1792
1699
  findings.invalidEventHandlers,
1793
1700
  expr.location
1794
1701
  ? {
1795
- location: expr.location,
1796
- message: `"${expr.text}" is not a defined instance method`
1797
- }
1798
- : `"${expr.text}" is not a defined instance method`
1702
+ location: expr.location,
1703
+ message: `"${expr.text}" is not a defined instance method`,
1704
+ }
1705
+ : `"${expr.text}" is not a defined instance method`,
1799
1706
  );
1800
1707
  }
1801
1708
  });
@@ -1808,20 +1715,19 @@ export function lintSource(filePath, sourceText, options = {}) {
1808
1715
  checkContextCalls: allCodeItems[index]?.checkContextCalls ?? true,
1809
1716
  eventHandler: allCodeItems[index]?.eventHandler ?? false,
1810
1717
  location: allCodeItems[index]?.location ?? null,
1811
- sourceFile: augmentedSourceFile
1718
+ privateMethods: privateMembers.privateMethods,
1719
+ privateProperties: privateMembers.privateProperties,
1720
+ sourceFile: augmentedSourceFile,
1812
1721
  });
1813
1722
  });
1814
1723
 
1815
1724
  findings.duplicateProperties.sort(compareLocatedFindings);
1816
1725
  findings.extraArguments.sort(
1817
- (a, b) =>
1818
- a.methodName.localeCompare(b.methodName) ||
1819
- a.argumentIndex - b.argumentIndex
1726
+ (a, b) => a.methodName.localeCompare(b.methodName) || a.argumentIndex - b.argumentIndex,
1820
1727
  );
1821
1728
  findings.incompatibleArguments.sort(
1822
1729
  (a, b) =>
1823
- a.methodName.localeCompare(b.methodName) ||
1824
- a.parameterName.localeCompare(b.parameterName)
1730
+ a.methodName.localeCompare(b.methodName) || a.parameterName.localeCompare(b.parameterName),
1825
1731
  );
1826
1732
  findings.incompatibleDeclareTypes.sort(compareLocatedFindings);
1827
1733
  findings.invalidCheckedBindings.sort(compareLocatedFindings);
@@ -1846,30 +1752,22 @@ export function lintSource(filePath, sourceText, options = {}) {
1846
1752
  findings.unsupportedEventNames.sort(compareLocatedFindings);
1847
1753
  findings.unsupportedHtmlAttributes.sort(compareLocatedFindings);
1848
1754
 
1849
- return formatReport(
1850
- filePath,
1851
- supportedProps,
1852
- allExpressions,
1853
- findings,
1854
- options
1855
- );
1755
+ return formatReport(filePath, supportedProps, allExpressions, findings, options);
1856
1756
  }
1857
1757
 
1858
1758
  // Runs the command-line interface for the linter.
1859
1759
  function main() {
1860
1760
  const args = process.argv.slice(2);
1861
- const unknownFlags = args.filter(
1862
- arg => arg.startsWith('--') && arg !== '--verbose'
1863
- );
1761
+ const unknownFlags = args.filter((arg) => arg.startsWith("--") && arg !== "--verbose");
1864
1762
  if (unknownFlags.length > 0) {
1865
1763
  fail(`unknown option: ${unknownFlags[0]}`);
1866
1764
  }
1867
1765
 
1868
- const verbose = args.includes('--verbose');
1869
- const positionalArgs = args.filter(arg => arg !== '--verbose');
1766
+ const verbose = args.includes("--verbose");
1767
+ const positionalArgs = args.filter((arg) => arg !== "--verbose");
1870
1768
 
1871
1769
  if (positionalArgs.length > 1) {
1872
- fail('usage: node scripts/wrec-lint.js [--verbose] {file-path}');
1770
+ fail("usage: node scripts/wrec-lint.js [--verbose] {file-path}");
1873
1771
  }
1874
1772
 
1875
1773
  const [filePath] = positionalArgs;
@@ -1878,24 +1776,23 @@ function main() {
1878
1776
  process.stdout.write(
1879
1777
  lintFile(validateFilePath(filePath), {
1880
1778
  showFileHeader: false,
1881
- verbose
1882
- })
1779
+ verbose,
1780
+ }),
1883
1781
  );
1884
1782
  return;
1885
1783
  }
1886
1784
 
1887
1785
  const rootDir = process.cwd();
1888
1786
  let previousHadIssues = false;
1889
- findWrecFiles(rootDir, matchedFile => {
1787
+ findWrecFiles(rootDir, (matchedFile) => {
1890
1788
  const report = lintFile(matchedFile, {
1891
- fileLabel:
1892
- path.relative(rootDir, matchedFile) || path.basename(matchedFile),
1789
+ fileLabel: path.relative(rootDir, matchedFile) || path.basename(matchedFile),
1893
1790
  showDetailsForCleanFile: false,
1894
1791
  showNoIssuesMessage: false,
1895
- verbose
1792
+ verbose,
1896
1793
  });
1897
- const currentHasIssues = report.trim().includes('\n');
1898
- if (previousHadIssues) process.stdout.write('\n');
1794
+ const currentHasIssues = report.trim().includes("\n");
1795
+ if (previousHadIssues) process.stdout.write("\n");
1899
1796
  process.stdout.write(report);
1900
1797
  previousHadIssues = currentHasIssues;
1901
1798
  });
@@ -1904,7 +1801,7 @@ function main() {
1904
1801
  // Determines whether a symbol should be treated as a required context function.
1905
1802
  function requiresContextFunction(symbol, sourceFile) {
1906
1803
  const declarations = symbol.declarations ?? [];
1907
- return declarations.some(declaration => {
1804
+ return declarations.some((declaration) => {
1908
1805
  if (isImportLikeDeclaration(declaration)) return true;
1909
1806
  return declaration.getSourceFile() === sourceFile;
1910
1807
  });
@@ -1912,66 +1809,58 @@ function requiresContextFunction(symbol, sourceFile) {
1912
1809
 
1913
1810
  // Resolves a relative import path to an existing source file.
1914
1811
  function resolveImportPath(baseDir, importPath) {
1915
- if (!importPath.startsWith('.')) return undefined;
1812
+ if (!importPath.startsWith(".")) return undefined;
1916
1813
 
1917
1814
  const candidates = [
1918
1815
  path.resolve(baseDir, importPath),
1919
1816
  path.resolve(baseDir, `${importPath}.js`),
1920
1817
  path.resolve(baseDir, `${importPath}.ts`),
1921
- path.resolve(baseDir, importPath, 'index.js'),
1922
- path.resolve(baseDir, importPath, 'index.ts')
1818
+ path.resolve(baseDir, importPath, "index.js"),
1819
+ path.resolve(baseDir, importPath, "index.ts"),
1923
1820
  ];
1924
1821
 
1925
- return candidates.find(candidate => fs.existsSync(candidate));
1822
+ return candidates.find((candidate) => fs.existsSync(candidate));
1926
1823
  }
1927
1824
 
1928
1825
  // Rewrites synthetic receiver references back to this for reporting.
1929
1826
  function toUserFacingExpression(text) {
1930
- return text.replaceAll(WREC_REF_NAME, 'this');
1827
+ return text.replaceAll(WREC_REF_NAME, "this");
1931
1828
  }
1932
1829
 
1933
1830
  // Returns whether a static property config type is compatible with a declare type.
1934
- function typeExpressionMatchesDeclaredType(
1935
- checker,
1936
- typeExpression,
1937
- declaredTypeNode
1938
- ) {
1831
+ function typeExpressionMatchesDeclaredType(checker, typeExpression, declaredTypeNode) {
1939
1832
  const typeKind = typeExpressionKind(typeExpression);
1940
1833
  const declaredType = checker.getTypeFromTypeNode(declaredTypeNode);
1941
1834
 
1942
- if (typeKind === 'Boolean') {
1835
+ if (typeKind === "Boolean") {
1943
1836
  return checker.isTypeAssignableTo(checker.getBooleanType(), declaredType);
1944
1837
  }
1945
1838
 
1946
- if (typeKind === 'Number') {
1839
+ if (typeKind === "Number") {
1947
1840
  return checker.isTypeAssignableTo(checker.getNumberType(), declaredType);
1948
1841
  }
1949
1842
 
1950
- if (typeKind === 'String') {
1843
+ if (typeKind === "String") {
1951
1844
  return checker.isTypeAssignableTo(checker.getStringType(), declaredType);
1952
1845
  }
1953
1846
 
1954
- if (typeKind === 'Object') {
1847
+ if (typeKind === "Object") {
1955
1848
  return isNonArrayObjectLikeType(checker, declaredType);
1956
1849
  }
1957
1850
 
1958
- if (typeKind === 'Array') {
1959
- const declaredTypes = declaredType.isUnion()
1960
- ? declaredType.types
1961
- : [declaredType];
1851
+ if (typeKind === "Array") {
1852
+ const declaredTypes = declaredType.isUnion() ? declaredType.types : [declaredType];
1962
1853
  return declaredTypes.every(
1963
- type =>
1854
+ (type) =>
1964
1855
  Boolean(type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) ||
1965
1856
  checker.isArrayType(type) ||
1966
- checker.isTupleType(type)
1857
+ checker.isTupleType(type),
1967
1858
  );
1968
1859
  }
1969
1860
 
1970
- if (typeKind === 'HTMLElement') {
1861
+ if (typeKind === "HTMLElement") {
1971
1862
  const elementType = getConstructedType(checker, typeExpression);
1972
- return elementType
1973
- ? checker.isTypeAssignableTo(declaredType, elementType)
1974
- : false;
1863
+ return elementType ? checker.isTypeAssignableTo(declaredType, elementType) : false;
1975
1864
  }
1976
1865
 
1977
1866
  const runtimeType = checker.getTypeAtLocation(typeExpression);
@@ -1989,25 +1878,22 @@ function typeExpressionKind(expression) {
1989
1878
  function typeNodeFromConstructorExpression(expression) {
1990
1879
  if (ts.isIdentifier(expression)) {
1991
1880
  switch (expression.text) {
1992
- case 'String':
1881
+ case "String":
1993
1882
  return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
1994
- case 'Number':
1883
+ case "Number":
1995
1884
  return ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
1996
- case 'Boolean':
1885
+ case "Boolean":
1997
1886
  return ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
1998
- case 'Object':
1887
+ case "Object":
1999
1888
  return ts.factory.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
2000
1889
  default:
2001
1890
  return ts.factory.createTypeReferenceNode(expression.text);
2002
1891
  }
2003
1892
  }
2004
1893
 
2005
- if (
2006
- ts.isCallExpression(expression) &&
2007
- ts.isIdentifier(expression.expression)
2008
- ) {
1894
+ if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)) {
2009
1895
  const name = expression.expression.text;
2010
- if (name === 'Array' && expression.typeArguments?.length === 1) {
1896
+ if (name === "Array" && expression.typeArguments?.length === 1) {
2011
1897
  return ts.factory.createArrayTypeNode(expression.typeArguments[0]);
2012
1898
  }
2013
1899
  }
@@ -2022,9 +1908,7 @@ function typeNodeFromConstructorExpression(expression) {
2022
1908
  // Pushes a value into an array only if it is not already present.
2023
1909
  function uniquePush(array, value) {
2024
1910
  const valueText = getLocatedFindingMessage(value);
2025
- if (
2026
- !array.some(existing => getLocatedFindingMessage(existing) === valueText)
2027
- ) {
1911
+ if (!array.some((existing) => getLocatedFindingMessage(existing) === valueText)) {
2028
1912
  array.push(value);
2029
1913
  }
2030
1914
  }
@@ -2036,15 +1920,15 @@ function validateCheckedBinding(
2036
1920
  attrValue,
2037
1921
  findings,
2038
1922
  supportedProps,
2039
- resolveLocation
1923
+ resolveLocation,
2040
1924
  ) {
2041
- if (getHtmlTagName(node) !== 'input') return;
1925
+ if (getHtmlTagName(node) !== "input") return;
2042
1926
 
2043
- const [baseAttrName] = attrName.split(':');
2044
- if (baseAttrName !== 'checked') return;
1927
+ const [baseAttrName] = attrName.split(":");
1928
+ if (baseAttrName !== "checked") return;
2045
1929
 
2046
1930
  const inputType = getInputType(node);
2047
- if (inputType !== 'checkbox' && inputType !== 'radio') return;
1931
+ if (inputType !== "checkbox" && inputType !== "radio") return;
2048
1932
 
2049
1933
  const propName = getPropertyNameInAttribute(attrValue);
2050
1934
  if (!propName) return;
@@ -2052,19 +1936,17 @@ function validateCheckedBinding(
2052
1936
  const propInfo = supportedProps.get(propName);
2053
1937
  if (!propInfo) return;
2054
1938
 
2055
- const expectedType = inputType === 'checkbox' ? 'boolean' : 'string';
1939
+ const expectedType = inputType === "checkbox" ? "boolean" : "string";
2056
1940
  if (propInfo.typeText === expectedType) return;
2057
1941
 
2058
1942
  const expectedTypeName = getPropertyConfigTypeName(expectedType);
2059
1943
 
2060
- findings.invalidCheckedBindings.push(
2061
- {
2062
- location: getHtmlAttributeLocation(node, attrName, resolveLocation),
2063
- message:
2064
- `input type="${inputType}" attribute "${attrName}" refers to ` +
2065
- `property "${propName}" whose type is not ${expectedTypeName}`
2066
- }
2067
- );
1944
+ findings.invalidCheckedBindings.push({
1945
+ location: getHtmlAttributeLocation(node, attrName, resolveLocation),
1946
+ message:
1947
+ `input type="${inputType}" attribute "${attrName}" refers to ` +
1948
+ `property "${propName}" whose type is not ${expectedTypeName}`,
1949
+ });
2068
1950
  }
2069
1951
 
2070
1952
  // Validates value bindings on native form controls.
@@ -2074,10 +1956,10 @@ function validateValueBinding(
2074
1956
  attrValue,
2075
1957
  findings,
2076
1958
  supportedProps,
2077
- resolveLocation
1959
+ resolveLocation,
2078
1960
  ) {
2079
- const [baseAttrName] = attrName.split(':');
2080
- if (baseAttrName !== 'value') return;
1961
+ const [baseAttrName] = attrName.split(":");
1962
+ if (baseAttrName !== "value") return;
2081
1963
  if (!isNativeFormControl(node)) return;
2082
1964
 
2083
1965
  const propName = getPropertyNameInAttribute(attrValue);
@@ -2086,18 +1968,16 @@ function validateValueBinding(
2086
1968
  const propInfo = supportedProps.get(propName);
2087
1969
  if (!propInfo) return;
2088
1970
 
2089
- if (propInfo.typeText === 'number' || propInfo.typeText === 'string') {
1971
+ if (propInfo.typeText === "number" || propInfo.typeText === "string") {
2090
1972
  return;
2091
1973
  }
2092
1974
 
2093
- findings.invalidValueBindings.push(
2094
- {
2095
- location: getHtmlAttributeLocation(node, attrName, resolveLocation),
2096
- message:
2097
- `${getHtmlTagName(node)} attribute "${attrName}" refers to property ` +
2098
- `"${propName}" whose type is not String or Number`
2099
- }
2100
- );
1975
+ findings.invalidValueBindings.push({
1976
+ location: getHtmlAttributeLocation(node, attrName, resolveLocation),
1977
+ message:
1978
+ `${getHtmlTagName(node)} attribute "${attrName}" refers to property ` +
1979
+ `"${propName}" whose type is not String or Number`,
1980
+ });
2101
1981
  }
2102
1982
 
2103
1983
  // Validates computed property references and method calls.
@@ -2107,46 +1987,33 @@ function validateComputedProperty(
2107
1987
  supportedProps,
2108
1988
  classMethods,
2109
1989
  findings,
2110
- location
1990
+ location,
2111
1991
  ) {
2112
1992
  for (const match of computedText.matchAll(THIS_REF_RE)) {
2113
1993
  const referencedName = match[1];
2114
- if (
2115
- !supportedProps.has(referencedName) &&
2116
- !classMethods.has(referencedName)
2117
- ) {
2118
- findings.invalidComputedProperties.push(
2119
- {
2120
- location,
2121
- message:
2122
- `property "${propName}" computed references ` +
2123
- `missing property "${referencedName}"`
2124
- }
2125
- );
1994
+ if (!supportedProps.has(referencedName) && !classMethods.has(referencedName)) {
1995
+ findings.invalidComputedProperties.push({
1996
+ location,
1997
+ message:
1998
+ `property "${propName}" computed references ` + `missing property "${referencedName}"`,
1999
+ });
2126
2000
  }
2127
2001
  }
2128
2002
 
2129
2003
  for (const match of computedText.matchAll(THIS_CALL_RE)) {
2130
2004
  const methodName = match[1];
2131
2005
  if (!classMethods.has(methodName)) {
2132
- findings.invalidComputedProperties.push(
2133
- {
2134
- location,
2135
- message:
2136
- `property "${propName}" computed calls ` +
2137
- `non-method instance member "${methodName}"`
2138
- }
2139
- );
2006
+ findings.invalidComputedProperties.push({
2007
+ location,
2008
+ message:
2009
+ `property "${propName}" computed calls ` + `non-method instance member "${methodName}"`,
2010
+ });
2140
2011
  }
2141
2012
  }
2142
2013
  }
2143
2014
 
2144
2015
  // Validates that computed properties do not form dependency cycles.
2145
- function validateComputedPropertyCycles(
2146
- computedDependencies,
2147
- computedLocations,
2148
- findings
2149
- ) {
2016
+ function validateComputedPropertyCycles(computedDependencies, computedLocations, findings) {
2150
2017
  const computedNames = [...computedDependencies.keys()].sort();
2151
2018
  const dependencyCountMap = new Map();
2152
2019
  const dependentsMap = new Map();
@@ -2183,20 +2050,16 @@ function validateComputedPropertyCycles(
2183
2050
  if (orderedNames.length === computedNames.length) return;
2184
2051
 
2185
2052
  const cycleNames = computedNames.filter(
2186
- computedName => dependencyCountMap.get(computedName) > 0
2053
+ (computedName) => dependencyCountMap.get(computedName) > 0,
2187
2054
  );
2188
2055
  const cycleLocation = cycleNames
2189
- .map(name => computedLocations.get(name))
2056
+ .map((name) => computedLocations.get(name))
2190
2057
  .filter(Boolean)
2191
- .sort(
2192
- (a, b) => a.line - b.line || a.character - b.character
2193
- )[0];
2194
- findings.invalidComputedProperties.push(
2195
- {
2196
- location: cycleLocation ?? null,
2197
- message: `computed properties form a cycle: ${cycleNames.join(', ')}`
2198
- }
2199
- );
2058
+ .sort((a, b) => a.line - b.line || a.character - b.character)[0];
2059
+ findings.invalidComputedProperties.push({
2060
+ location: cycleLocation ?? null,
2061
+ message: `computed properties form a cycle: ${cycleNames.join(", ")}`,
2062
+ });
2200
2063
  }
2201
2064
 
2202
2065
  // Validates that a default value matches the declared property type.
@@ -2207,50 +2070,44 @@ function validateDefaultValue(checker, typeExpression, valueExpression) {
2207
2070
  const valueType = checker.getTypeAtLocation(valueExpression);
2208
2071
  const valueTypeName = checker.typeToString(valueType);
2209
2072
 
2210
- if (typeKind === 'String') {
2211
- if (
2212
- !(valueType.flags & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral))
2213
- ) {
2214
- return {typeName: 'String', valueTypeName};
2073
+ if (typeKind === "String") {
2074
+ if (!(valueType.flags & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral))) {
2075
+ return { typeName: "String", valueTypeName };
2215
2076
  }
2216
2077
  return undefined;
2217
2078
  }
2218
2079
 
2219
- if (typeKind === 'Number') {
2220
- if (
2221
- !(valueType.flags & (ts.TypeFlags.Number | ts.TypeFlags.NumberLiteral))
2222
- ) {
2223
- return {typeName: 'Number', valueTypeName};
2080
+ if (typeKind === "Number") {
2081
+ if (!(valueType.flags & (ts.TypeFlags.Number | ts.TypeFlags.NumberLiteral))) {
2082
+ return { typeName: "Number", valueTypeName };
2224
2083
  }
2225
2084
  return undefined;
2226
2085
  }
2227
2086
 
2228
- if (typeKind === 'Boolean') {
2229
- if (
2230
- !(valueType.flags & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral))
2231
- ) {
2232
- return {typeName: 'Boolean', valueTypeName};
2087
+ if (typeKind === "Boolean") {
2088
+ if (!(valueType.flags & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral))) {
2089
+ return { typeName: "Boolean", valueTypeName };
2233
2090
  }
2234
2091
  return undefined;
2235
2092
  }
2236
2093
 
2237
- if (typeKind === 'Array') {
2094
+ if (typeKind === "Array") {
2238
2095
  if (!checker.isArrayType(valueType) && !checker.isTupleType(valueType)) {
2239
- return {typeName: 'Array', valueTypeName};
2096
+ return { typeName: "Array", valueTypeName };
2240
2097
  }
2241
2098
  return undefined;
2242
2099
  }
2243
2100
 
2244
- if (typeKind === 'Object') {
2101
+ if (typeKind === "Object") {
2245
2102
  if (!isNonArrayObjectLikeType(checker, valueType)) {
2246
- return {typeName: 'Object', valueTypeName};
2103
+ return { typeName: "Object", valueTypeName };
2247
2104
  }
2248
2105
  }
2249
2106
 
2250
- if (typeKind === 'HTMLElement') {
2107
+ if (typeKind === "HTMLElement") {
2251
2108
  const elementType = getConstructedType(checker, typeExpression);
2252
2109
  if (!elementType || !checker.isTypeAssignableTo(valueType, elementType)) {
2253
- return {typeName: 'HTMLElement', valueTypeName};
2110
+ return { typeName: "HTMLElement", valueTypeName };
2254
2111
  }
2255
2112
  }
2256
2113
 
@@ -2261,8 +2118,8 @@ function validateDefaultValue(checker, typeExpression, valueExpression) {
2261
2118
  function validateFilePath(filePath) {
2262
2119
  const resolved = path.resolve(filePath);
2263
2120
  const ext = path.extname(resolved);
2264
- if (ext !== '.js' && ext !== '.ts') {
2265
- throw new Error('argument must be a path to a .js or .ts file');
2121
+ if (ext !== ".js" && ext !== ".ts") {
2122
+ throw new Error("argument must be a path to a .js or .ts file");
2266
2123
  }
2267
2124
  if (!fs.existsSync(resolved)) {
2268
2125
  throw new Error(`file not found: ${resolved}`);
@@ -2271,30 +2128,20 @@ function validateFilePath(filePath) {
2271
2128
  }
2272
2129
 
2273
2130
  // Validates the syntax of a form-assoc attribute value.
2274
- function validateFormAssocAttribute(
2275
- node,
2276
- attrName,
2277
- attrValue,
2278
- findings,
2279
- resolveLocation
2280
- ) {
2281
- if (attrName !== 'form-assoc') return;
2131
+ function validateFormAssocAttribute(node, attrName, attrValue, findings, resolveLocation) {
2132
+ if (attrName !== "form-assoc") return;
2282
2133
 
2283
- const pairs = attrValue.split(',');
2134
+ const pairs = attrValue.split(",");
2284
2135
  for (const pair of pairs) {
2285
2136
  const trimmed = pair.trim();
2286
- const [propName, fieldName, ...rest] = trimmed
2287
- .split(':')
2288
- .map(part => part.trim());
2137
+ const [propName, fieldName, ...rest] = trimmed.split(":").map((part) => part.trim());
2289
2138
  if (!trimmed || rest.length > 0 || !propName || !fieldName) {
2290
- findings.invalidFormAssocValues.push(
2291
- {
2292
- location: getHtmlAttributeLocation(node, attrName, resolveLocation),
2293
- message:
2294
- `form-assoc="${attrValue}" is invalid; expected ` +
2295
- '"property:field" or a comma-separated list of them'
2296
- }
2297
- );
2139
+ findings.invalidFormAssocValues.push({
2140
+ location: getHtmlAttributeLocation(node, attrName, resolveLocation),
2141
+ message:
2142
+ `form-assoc="${attrValue}" is invalid; expected ` +
2143
+ '"property:field" or a comma-separated list of them',
2144
+ });
2298
2145
  return;
2299
2146
  }
2300
2147
  }
@@ -2307,79 +2154,66 @@ function validateFormAssocPropertyMappings(
2307
2154
  attrValue,
2308
2155
  findings,
2309
2156
  componentPropertyMaps,
2310
- resolveLocation
2157
+ resolveLocation,
2311
2158
  ) {
2312
- if (attrName !== 'form-assoc') return;
2313
- const tagName = (node.rawTagName || node.tagName || '').toLowerCase();
2159
+ if (attrName !== "form-assoc") return;
2160
+ const tagName = (node.rawTagName || node.tagName || "").toLowerCase();
2314
2161
  const supportedProps = componentPropertyMaps.get(tagName);
2315
2162
  if (!supportedProps) return;
2316
2163
 
2317
- const pairs = attrValue.split(',');
2164
+ const pairs = attrValue.split(",");
2318
2165
  for (const pair of pairs) {
2319
- const [propName] = pair.split(':').map(part => part.trim());
2166
+ const [propName] = pair.split(":").map((part) => part.trim());
2320
2167
  if (!propName) continue;
2321
2168
  if (!supportedProps.has(propName)) {
2322
- findings.invalidFormAssocValues.push(
2323
- {
2324
- location: getHtmlAttributeLocation(node, attrName, resolveLocation),
2325
- message:
2326
- `form-assoc="${attrValue}" refers to ` +
2327
- `missing component property "${propName}"`
2328
- }
2329
- );
2169
+ findings.invalidFormAssocValues.push({
2170
+ location: getHtmlAttributeLocation(node, attrName, resolveLocation),
2171
+ message:
2172
+ `form-assoc="${attrValue}" refers to ` + `missing component property "${propName}"`,
2173
+ });
2330
2174
  }
2331
2175
  }
2332
2176
  }
2333
2177
 
2334
2178
  // Validates that an HTML attribute is supported for the current element.
2335
2179
  function validateHtmlAttribute(node, attrName, findings, resolveLocation) {
2336
- if (attrName.startsWith('aria-') || attrName.startsWith('data-')) return;
2337
- if (attrName.startsWith('on')) return;
2338
- if (attrName === 'form-assoc') return;
2339
- if (attrName === 'ref') return;
2180
+ if (attrName.startsWith("aria-") || attrName.startsWith("data-")) return;
2181
+ if (attrName.startsWith("on")) return;
2182
+ if (attrName === "form-assoc") return;
2183
+ if (attrName === "ref") return;
2340
2184
 
2341
- const [baseAttrName] = attrName.split(':');
2185
+ const [baseAttrName] = attrName.split(":");
2342
2186
  if (HTML_GLOBAL_ATTRIBUTES.has(baseAttrName)) return;
2343
2187
 
2344
- const tagName = (node.rawTagName || node.tagName || '').toLowerCase();
2345
- if (!tagName || tagName.includes('-')) return;
2188
+ const tagName = (node.rawTagName || node.tagName || "").toLowerCase();
2189
+ if (!tagName || tagName.includes("-")) return;
2346
2190
 
2347
2191
  const supported = getSupportedHtmlAttributes(tagName);
2348
2192
  if (!supported) return;
2349
2193
  if (supported.has(baseAttrName)) return;
2350
2194
 
2351
- findings.unsupportedHtmlAttributes.push(
2352
- {
2353
- location: getHtmlAttributeLocation(node, attrName, resolveLocation),
2354
- message: `${tagName} attribute "${attrName}" is not supported`
2355
- }
2356
- );
2195
+ findings.unsupportedHtmlAttributes.push({
2196
+ location: getHtmlAttributeLocation(node, attrName, resolveLocation),
2197
+ message: `${tagName} attribute "${attrName}" is not supported`,
2198
+ });
2357
2199
  }
2358
2200
 
2359
2201
  // Validates required parent-child relationships for supported HTML tags.
2360
2202
  function validateHtmlNesting(node, findings, resolveLocation) {
2361
2203
  const tagName = getHtmlTagName(node);
2362
- if (!tagName || tagName.includes('-')) return;
2204
+ if (!tagName || tagName.includes("-")) return;
2363
2205
 
2364
2206
  const parentNode = node.parentNode?.nodeType === 1 ? node.parentNode : null;
2365
- const parentTagName = parentNode ? getHtmlTagName(parentNode) : '';
2207
+ const parentTagName = parentNode ? getHtmlTagName(parentNode) : "";
2366
2208
  const allowedParents = HTML_ALLOWED_PARENTS.get(tagName);
2367
- if (
2368
- allowedParents &&
2369
- (!parentTagName || !allowedParents.has(parentTagName))
2370
- ) {
2371
- const parentDescription = parentTagName
2372
- ? `<${parentTagName}>`
2373
- : 'the document root';
2374
- findings.invalidHtmlNesting.push(
2375
- {
2376
- location: getHtmlNodeLocation(node, resolveLocation),
2377
- message:
2378
- `<${tagName}> must be nested inside ${[...allowedParents]
2379
- .map(name => `<${name}>`)
2380
- .join(' or ')}, but parent is ${parentDescription}`
2381
- }
2382
- );
2209
+ if (allowedParents && (!parentTagName || !allowedParents.has(parentTagName))) {
2210
+ const parentDescription = parentTagName ? `<${parentTagName}>` : "the document root";
2211
+ findings.invalidHtmlNesting.push({
2212
+ location: getHtmlNodeLocation(node, resolveLocation),
2213
+ message: `<${tagName}> must be nested inside ${[...allowedParents]
2214
+ .map((name) => `<${name}>`)
2215
+ .join(" or ")}, but parent is ${parentDescription}`,
2216
+ });
2383
2217
  }
2384
2218
 
2385
2219
  const allowedChildren = HTML_ALLOWED_CHILDREN.get(tagName);
@@ -2388,15 +2222,13 @@ function validateHtmlNesting(node, findings, resolveLocation) {
2388
2222
  for (const child of node.childNodes ?? []) {
2389
2223
  if (child.nodeType !== 1) continue;
2390
2224
  const childTagName = getHtmlTagName(child);
2391
- if (!childTagName || childTagName.includes('-')) continue;
2225
+ if (!childTagName || childTagName.includes("-")) continue;
2392
2226
  if (allowedChildren.has(childTagName)) continue;
2393
2227
 
2394
- findings.invalidHtmlNesting.push(
2395
- {
2396
- location: getHtmlNodeLocation(child, resolveLocation),
2397
- message: `<${childTagName}> is not allowed directly inside <${tagName}>`
2398
- }
2399
- );
2228
+ findings.invalidHtmlNesting.push({
2229
+ location: getHtmlNodeLocation(child, resolveLocation),
2230
+ message: `<${childTagName}> is not allowed directly inside <${tagName}>`,
2231
+ });
2400
2232
  }
2401
2233
  }
2402
2234
 
@@ -2409,14 +2241,14 @@ function validatePropertyConfigs(
2409
2241
  propertyEntries,
2410
2242
  getterNames,
2411
2243
  classMethods,
2412
- findings
2244
+ findings,
2413
2245
  ) {
2414
2246
  const computedDependencies = new Map();
2415
2247
  const computedLocations = new Map();
2416
2248
  const computedPropNames = new Set();
2417
2249
 
2418
- for (const {config, propName} of propertyEntries) {
2419
- const computedProp = getObjectProperty(config, 'computed');
2250
+ for (const { config, propName } of propertyEntries) {
2251
+ const computedProp = getObjectProperty(config, "computed");
2420
2252
  if (
2421
2253
  computedProp &&
2422
2254
  ts.isPropertyAssignment(computedProp) &&
@@ -2425,81 +2257,59 @@ function validatePropertyConfigs(
2425
2257
  ) {
2426
2258
  computedLocations.set(
2427
2259
  propName,
2428
- sourceFile.getLineAndCharacterOfPosition(
2429
- computedProp.initializer.getStart(sourceFile)
2430
- )
2260
+ sourceFile.getLineAndCharacterOfPosition(computedProp.initializer.getStart(sourceFile)),
2431
2261
  );
2432
2262
  computedPropNames.add(propName);
2433
2263
  }
2434
2264
  }
2435
2265
 
2436
- for (const {config, propName, property} of propertyEntries) {
2437
- const computedProp = getObjectProperty(config, 'computed');
2266
+ for (const { config, propName, property } of propertyEntries) {
2267
+ const computedProp = getObjectProperty(config, "computed");
2438
2268
  const declaredTypeNode = declaredPropertyTypes.get(propName);
2439
- const typeProp = getObjectProperty(config, 'type');
2440
- const usedByProp = getObjectProperty(config, 'usedBy');
2441
- const valueProp = getObjectProperty(config, 'value');
2442
- const valuesProp = getObjectProperty(config, 'values');
2269
+ const typeProp = getObjectProperty(config, "type");
2270
+ const usedByProp = getObjectProperty(config, "usedBy");
2271
+ const valueProp = getObjectProperty(config, "value");
2272
+ const valuesProp = getObjectProperty(config, "values");
2443
2273
  const valuesConfigError = getValuesConfigurationError(valuesProp);
2444
2274
  const propertyLocation = sourceFile.getLineAndCharacterOfPosition(
2445
- property.name.getStart(sourceFile)
2275
+ property.name.getStart(sourceFile),
2446
2276
  );
2447
2277
 
2448
2278
  const typeExpression =
2449
- typeProp && ts.isPropertyAssignment(typeProp)
2450
- ? typeProp.initializer
2451
- : undefined;
2279
+ typeProp && ts.isPropertyAssignment(typeProp) ? typeProp.initializer : undefined;
2452
2280
 
2453
2281
  if (!typeExpression) {
2454
- findings.missingTypeProperties.push(
2455
- {
2456
- location: propertyLocation,
2457
- message: `property "${propName}" does not specify a type`
2458
- }
2459
- );
2460
- } else if (
2461
- SUPPORTED_PROPERTY_TYPE_NAMES.has(
2462
- getPropertyTypeGenericBaseName(sourceFile, typeExpression)
2463
- )
2464
- ) {
2465
- findings.invalidTypeProperties.push(
2466
- {
2467
- location: propertyLocation,
2468
- message:
2469
- `property "${propName}" type cannot use generic syntax like ` +
2470
- `"${typeExpression.getText(sourceFile).trim()}"; use ` +
2471
- `"${getPropertyTypeGenericBaseName(sourceFile, typeExpression)}" instead`
2472
- }
2473
- );
2282
+ findings.missingTypeProperties.push({
2283
+ location: propertyLocation,
2284
+ message: `property "${propName}" does not specify a type`,
2285
+ });
2474
2286
  } else if (
2475
- !SUPPORTED_PROPERTY_TYPE_NAMES.has(typeExpressionKind(typeExpression))
2287
+ SUPPORTED_PROPERTY_TYPE_NAMES.has(getPropertyTypeGenericBaseName(sourceFile, typeExpression))
2476
2288
  ) {
2477
- findings.invalidTypeProperties.push(
2478
- {
2289
+ findings.invalidTypeProperties.push({
2290
+ location: propertyLocation,
2291
+ message:
2292
+ `property "${propName}" type cannot use generic syntax like ` +
2293
+ `"${typeExpression.getText(sourceFile).trim()}"; use ` +
2294
+ `"${getPropertyTypeGenericBaseName(sourceFile, typeExpression)}" instead`,
2295
+ });
2296
+ } else if (!SUPPORTED_PROPERTY_TYPE_NAMES.has(typeExpressionKind(typeExpression))) {
2297
+ findings.invalidTypeProperties.push({
2298
+ location: propertyLocation,
2299
+ message:
2300
+ `property "${propName}" type must be one of ` +
2301
+ "Boolean, Number, String, Object, Array, or HTMLElement",
2302
+ });
2303
+ } else if (declaredTypeNode) {
2304
+ if (!typeExpressionMatchesDeclaredType(checker, typeExpression, declaredTypeNode)) {
2305
+ findings.incompatibleDeclareTypes.push({
2479
2306
  location: propertyLocation,
2480
2307
  message:
2481
- `property "${propName}" type must be one of ` +
2482
- 'Boolean, Number, String, Object, Array, or HTMLElement'
2483
- }
2484
- );
2485
- } else if (declaredTypeNode) {
2486
- if (
2487
- !typeExpressionMatchesDeclaredType(
2488
- checker,
2489
- typeExpression,
2490
- declaredTypeNode
2491
- )
2492
- ) {
2493
- findings.incompatibleDeclareTypes.push(
2494
- {
2495
- location: propertyLocation,
2496
- message:
2497
- `property "${propName}" declare type ` +
2498
- `"${getPropertyTypeTextFromNode(sourceFile, declaredTypeNode)}" ` +
2499
- `is not compatible with static properties type ` +
2500
- `"${getPropertyConfigTypeName(typeExpressionKind(typeExpression))}"`
2501
- }
2502
- );
2308
+ `property "${propName}" declare type ` +
2309
+ `"${getPropertyTypeTextFromNode(sourceFile, declaredTypeNode)}" ` +
2310
+ `is not compatible with static properties type ` +
2311
+ `"${getPropertyConfigTypeName(typeExpressionKind(typeExpression))}"`,
2312
+ });
2503
2313
  }
2504
2314
  }
2505
2315
 
@@ -2511,26 +2321,20 @@ function validatePropertyConfigs(
2511
2321
  if (isGetterReference(methodName)) {
2512
2322
  const getterName = getGetterName(methodName);
2513
2323
  if (getterNames.has(getterName)) continue;
2514
- findings.invalidUsedByReferences.push(
2515
- {
2516
- location: propertyLocation,
2517
- message:
2518
- `property "${propName}" usedBy references ` +
2519
- `missing getter "${getterName}"`
2520
- }
2521
- );
2324
+ findings.invalidUsedByReferences.push({
2325
+ location: propertyLocation,
2326
+ message:
2327
+ `property "${propName}" usedBy references ` + `missing getter "${getterName}"`,
2328
+ });
2522
2329
  continue;
2523
2330
  }
2524
2331
 
2525
2332
  if (!classMethods.has(methodName)) {
2526
- findings.invalidUsedByReferences.push(
2527
- {
2528
- location: propertyLocation,
2529
- message:
2530
- `property "${propName}" usedBy references ` +
2531
- `missing method "${methodName}"`
2532
- }
2533
- );
2333
+ findings.invalidUsedByReferences.push({
2334
+ location: propertyLocation,
2335
+ message:
2336
+ `property "${propName}" usedBy references ` + `missing method "${methodName}"`,
2337
+ });
2534
2338
  }
2535
2339
  }
2536
2340
  }
@@ -2544,10 +2348,7 @@ function validatePropertyConfigs(
2544
2348
  ) {
2545
2349
  computedDependencies.set(
2546
2350
  propName,
2547
- collectComputedDependencies(
2548
- computedProp.initializer.text,
2549
- computedPropNames
2550
- )
2351
+ collectComputedDependencies(computedProp.initializer.text, computedPropNames),
2551
2352
  );
2552
2353
  validateComputedProperty(
2553
2354
  propName,
@@ -2555,28 +2356,24 @@ function validatePropertyConfigs(
2555
2356
  supportedProps,
2556
2357
  classMethods,
2557
2358
  findings,
2558
- computedLocations.get(propName) ?? propertyLocation
2359
+ computedLocations.get(propName) ?? propertyLocation,
2559
2360
  );
2560
2361
  }
2561
2362
 
2562
2363
  if (valuesConfigError) {
2563
- findings.invalidValuesConfigurations.push(
2564
- {
2565
- location: propertyLocation,
2566
- message: `property "${propName}" ${valuesConfigError}`
2567
- }
2568
- );
2364
+ findings.invalidValuesConfigurations.push({
2365
+ location: propertyLocation,
2366
+ message: `property "${propName}" ${valuesConfigError}`,
2367
+ });
2569
2368
  }
2570
2369
 
2571
2370
  const values = getStringArrayLiteral(valuesProp);
2572
2371
  if (values) {
2573
- if (typeExpressionKind(typeExpression) !== 'String') {
2574
- findings.invalidValuesConfigurations.push(
2575
- {
2576
- location: propertyLocation,
2577
- message: `property "${propName}" uses values, but its type is not String`
2578
- }
2579
- );
2372
+ if (typeExpressionKind(typeExpression) !== "String") {
2373
+ findings.invalidValuesConfigurations.push({
2374
+ location: propertyLocation,
2375
+ message: `property "${propName}" uses values, but its type is not String`,
2376
+ });
2580
2377
  }
2581
2378
 
2582
2379
  if (
@@ -2586,52 +2383,34 @@ function validatePropertyConfigs(
2586
2383
  ts.isNoSubstitutionTemplateLiteral(valueProp.initializer)) &&
2587
2384
  !values.includes(valueProp.initializer.text)
2588
2385
  ) {
2589
- findings.invalidDefaultValues.push(
2590
- {
2591
- location: propertyLocation,
2592
- message:
2593
- `property "${propName}" default value ` +
2594
- `"${valueProp.initializer.text}" is not in values`
2595
- }
2596
- );
2386
+ findings.invalidDefaultValues.push({
2387
+ location: propertyLocation,
2388
+ message:
2389
+ `property "${propName}" default value ` +
2390
+ `"${valueProp.initializer.text}" is not in values`,
2391
+ });
2597
2392
  }
2598
2393
  }
2599
2394
 
2600
2395
  if (valueProp && ts.isPropertyAssignment(valueProp)) {
2601
- const mismatch = validateDefaultValue(
2602
- checker,
2603
- typeExpression,
2604
- valueProp.initializer
2605
- );
2396
+ const mismatch = validateDefaultValue(checker, typeExpression, valueProp.initializer);
2606
2397
  if (mismatch) {
2607
- findings.invalidDefaultValues.push(
2608
- {
2609
- location: propertyLocation,
2610
- message:
2611
- `property "${propName}" default value ` +
2612
- `has type ${mismatch.valueTypeName}, ` +
2613
- `but declared type is ${mismatch.typeName}`
2614
- }
2615
- );
2398
+ findings.invalidDefaultValues.push({
2399
+ location: propertyLocation,
2400
+ message:
2401
+ `property "${propName}" default value ` +
2402
+ `has type ${mismatch.valueTypeName}, ` +
2403
+ `but declared type is ${mismatch.typeName}`,
2404
+ });
2616
2405
  }
2617
2406
  }
2618
2407
  }
2619
2408
 
2620
- validateComputedPropertyCycles(
2621
- computedDependencies,
2622
- computedLocations,
2623
- findings
2624
- );
2409
+ validateComputedPropertyCycles(computedDependencies, computedLocations, findings);
2625
2410
  }
2626
2411
 
2627
2412
  // Validates that a ref attribute targets a unique HTMLElement property.
2628
- function validateRefAttribute(
2629
- attrValue,
2630
- supportedProps,
2631
- findings,
2632
- seenRefProps,
2633
- location
2634
- ) {
2413
+ function validateRefAttribute(attrValue, supportedProps, findings, seenRefProps, location) {
2635
2414
  if (!attrValue) return;
2636
2415
 
2637
2416
  const propName = attrValue.trim();
@@ -2639,36 +2418,27 @@ function validateRefAttribute(
2639
2418
 
2640
2419
  const propInfo = supportedProps.get(propName);
2641
2420
  if (!propInfo) {
2642
- findings.invalidRefAttributes.push(
2643
- {
2644
- location,
2645
- message: `ref="${attrValue}" refers to missing property "${propName}"`
2646
- }
2647
- );
2421
+ findings.invalidRefAttributes.push({
2422
+ location,
2423
+ message: `ref="${attrValue}" refers to missing property "${propName}"`,
2424
+ });
2648
2425
  return;
2649
2426
  }
2650
2427
 
2651
- if (propInfo.typeText !== 'HTMLElement') {
2652
- findings.invalidRefAttributes.push(
2653
- {
2654
- location,
2655
- message:
2656
- `ref="${attrValue}" refers to property "${propName}" ` +
2657
- 'whose type is not HTMLElement'
2658
- }
2659
- );
2428
+ if (propInfo.typeText !== "HTMLElement") {
2429
+ findings.invalidRefAttributes.push({
2430
+ location,
2431
+ message:
2432
+ `ref="${attrValue}" refers to property "${propName}" ` + "whose type is not HTMLElement",
2433
+ });
2660
2434
  return;
2661
2435
  }
2662
2436
 
2663
2437
  if (seenRefProps.has(propName)) {
2664
- findings.invalidRefAttributes.push(
2665
- {
2666
- location,
2667
- message:
2668
- `ref="${attrValue}" is a duplicate reference ` +
2669
- `to the property "${propName}"`
2670
- }
2671
- );
2438
+ findings.invalidRefAttributes.push({
2439
+ location,
2440
+ message: `ref="${attrValue}" is a duplicate reference ` + `to the property "${propName}"`,
2441
+ });
2672
2442
  return;
2673
2443
  }
2674
2444
 
@@ -2677,19 +2447,16 @@ function validateRefAttribute(
2677
2447
 
2678
2448
  // Validates event names used in value-binding attributes.
2679
2449
  function validateValueBindingEvent(node, attrName, findings, resolveLocation) {
2680
- const [realAttrName, eventName] = attrName.split(':');
2681
- if (realAttrName !== 'value' || !eventName) return;
2450
+ const [realAttrName, eventName] = attrName.split(":");
2451
+ if (realAttrName !== "value" || !eventName) return;
2682
2452
  if (SUPPORTED_EVENT_NAMES.has(eventName)) return;
2683
2453
 
2684
- const tagName = node.rawTagName || node.tagName || 'element';
2685
- findings.unsupportedEventNames.push(
2686
- {
2687
- location: getHtmlAttributeLocation(node, attrName, resolveLocation),
2688
- message:
2689
- `${tagName} attribute "${attrName}" refers to ` +
2690
- `an unsupported event name "${eventName}"`
2691
- }
2692
- );
2454
+ const tagName = node.rawTagName || node.tagName || "element";
2455
+ findings.unsupportedEventNames.push({
2456
+ location: getHtmlAttributeLocation(node, attrName, resolveLocation),
2457
+ message:
2458
+ `${tagName} attribute "${attrName}" refers to ` + `an unsupported event name "${eventName}"`,
2459
+ });
2693
2460
  }
2694
2461
 
2695
2462
  // Walks parsed HTML nodes to extract expressions and apply HTML validations.
@@ -2700,82 +2467,59 @@ function walkHtmlNode(
2700
2467
  componentPropertyMaps,
2701
2468
  supportedProps,
2702
2469
  seenRefProps,
2703
- resolveLocation
2470
+ resolveLocation,
2704
2471
  ) {
2705
2472
  if (node.nodeType === 1) {
2706
2473
  validateHtmlNesting(node, findings, resolveLocation);
2707
2474
 
2708
2475
  for (const [attrName, attrValue] of Object.entries(node.attributes)) {
2709
2476
  if (!attrValue) continue;
2710
- validateFormAssocAttribute(
2711
- node,
2712
- attrName,
2713
- attrValue,
2714
- findings,
2715
- resolveLocation
2716
- );
2477
+ validateFormAssocAttribute(node, attrName, attrValue, findings, resolveLocation);
2717
2478
  validateFormAssocPropertyMappings(
2718
2479
  node,
2719
2480
  attrName,
2720
2481
  attrValue,
2721
2482
  findings,
2722
2483
  componentPropertyMaps,
2723
- resolveLocation
2724
- );
2725
- validateCheckedBinding(
2726
- node,
2727
- attrName,
2728
- attrValue,
2729
- findings,
2730
- supportedProps,
2731
- resolveLocation
2484
+ resolveLocation,
2732
2485
  );
2486
+ validateCheckedBinding(node, attrName, attrValue, findings, supportedProps, resolveLocation);
2733
2487
  validateHtmlAttribute(node, attrName, findings, resolveLocation);
2734
- validateValueBinding(
2735
- node,
2736
- attrName,
2737
- attrValue,
2738
- findings,
2739
- supportedProps,
2740
- resolveLocation
2741
- );
2488
+ validateValueBinding(node, attrName, attrValue, findings, supportedProps, resolveLocation);
2742
2489
  validateValueBindingEvent(node, attrName, findings, resolveLocation);
2743
- if (attrName === 'ref') {
2490
+ if (attrName === "ref") {
2744
2491
  validateRefAttribute(
2745
2492
  attrValue,
2746
2493
  supportedProps,
2747
2494
  findings,
2748
2495
  seenRefProps,
2749
- getHtmlAttributeLocation(node, attrName, resolveLocation)
2496
+ getHtmlAttributeLocation(node, attrName, resolveLocation),
2750
2497
  );
2751
2498
  }
2752
2499
  if (
2753
2500
  REFS_TEST_RE.test(attrValue) ||
2754
- (attrName.startsWith('on') && IDENTIFIER_RE.test(attrValue))
2501
+ (attrName.startsWith("on") && IDENTIFIER_RE.test(attrValue))
2755
2502
  ) {
2756
2503
  expressions.push({
2757
- context: 'instance',
2758
- eventHandler: attrName.startsWith('on'),
2759
- kind: 'html',
2504
+ context: "instance",
2505
+ eventHandler: attrName.startsWith("on"),
2506
+ kind: "html",
2760
2507
  text: attrValue.trim(),
2761
- location: getHtmlAttributeLocation(node, attrName, resolveLocation)
2508
+ location: getHtmlAttributeLocation(node, attrName, resolveLocation),
2762
2509
  });
2763
2510
  }
2764
2511
  }
2765
2512
  }
2766
2513
 
2767
- if (
2768
- (node.nodeType === 3 || node.nodeType === 8) &&
2769
- typeof node.rawText === 'string'
2770
- ) {
2514
+ if ((node.nodeType === 3 || node.nodeType === 8) && typeof node.rawText === "string") {
2771
2515
  const text = node.rawText.trim();
2772
2516
  if (text && REFS_TEST_RE.test(text)) {
2773
2517
  expressions.push({
2774
- context: 'instance',
2518
+ context: "instance",
2775
2519
  eventHandler: false,
2776
- kind: 'html',
2520
+ kind: "html",
2777
2521
  text,
2778
- location: getHtmlNodeLocation(node, resolveLocation)
2522
+ location: getHtmlNodeLocation(node, resolveLocation),
2779
2523
  });
2780
2524
  }
2781
2525
  }
@@ -2788,7 +2532,7 @@ function walkHtmlNode(
2788
2532
  componentPropertyMaps,
2789
2533
  supportedProps,
2790
2534
  seenRefProps,
2791
- resolveLocation
2535
+ resolveLocation,
2792
2536
  );
2793
2537
  }
2794
2538
  }