@design.estate/wcctools 5.1.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/.smartconfig.json +7 -3
  2. package/changelog.md +28 -0
  3. package/cli.js +3 -0
  4. package/dist_shell/bundle.js +2970 -0
  5. package/dist_shell/bundle.js.map +1 -0
  6. package/dist_shell/bundle.js.third-party-notices.json +1722 -0
  7. package/dist_shell/bundle.js.third-party-notices.txt +4195 -0
  8. package/dist_shell/index.html +21 -0
  9. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  10. package/dist_ts/00_commitinfo_data.js +9 -0
  11. package/dist_ts/classes.devapi.d.ts +11 -0
  12. package/dist_ts/classes.devapi.js +21 -0
  13. package/dist_ts/classes.devserver.d.ts +49 -0
  14. package/dist_ts/classes.devserver.js +142 -0
  15. package/dist_ts/cli.d.ts +6 -0
  16. package/dist_ts/cli.js +51 -0
  17. package/dist_ts/index.d.ts +3 -0
  18. package/dist_ts/index.js +4 -0
  19. package/dist_ts/paths.d.ts +4 -0
  20. package/dist_ts/paths.js +6 -0
  21. package/dist_ts/plugins.d.ts +20 -0
  22. package/dist_ts/plugins.js +27 -0
  23. package/dist_ts/shellcontent.d.ts +9 -0
  24. package/dist_ts/shellcontent.js +42 -0
  25. package/dist_ts/standards/baselines.d.ts +27 -0
  26. package/dist_ts/standards/baselines.js +83 -0
  27. package/dist_ts/standards/checker.d.ts +21 -0
  28. package/dist_ts/standards/checker.js +88 -0
  29. package/dist_ts/standards/classes.gitrepo.d.ts +13 -0
  30. package/dist_ts/standards/classes.gitrepo.js +43 -0
  31. package/dist_ts/standards/classes.sourceedits.d.ts +15 -0
  32. package/dist_ts/standards/classes.sourceedits.js +54 -0
  33. package/dist_ts/standards/classes.standardfixer.d.ts +40 -0
  34. package/dist_ts/standards/classes.standardfixer.js +210 -0
  35. package/dist_ts/standards/classes.standardproject.d.ts +80 -0
  36. package/dist_ts/standards/classes.standardproject.js +195 -0
  37. package/dist_ts/standards/classes.standardtree.d.ts +56 -0
  38. package/dist_ts/standards/classes.standardtree.js +207 -0
  39. package/dist_ts/standards/cli.d.ts +7 -0
  40. package/dist_ts/standards/cli.js +89 -0
  41. package/dist_ts/standards/config.d.ts +33 -0
  42. package/dist_ts/standards/config.js +139 -0
  43. package/dist_ts/standards/errors.d.ts +9 -0
  44. package/dist_ts/standards/errors.js +17 -0
  45. package/dist_ts/standards/helpers.ast.d.ts +56 -0
  46. package/dist_ts/standards/helpers.ast.js +172 -0
  47. package/dist_ts/standards/helpers.paths.d.ts +36 -0
  48. package/dist_ts/standards/helpers.paths.js +86 -0
  49. package/dist_ts/standards/index.d.ts +14 -0
  50. package/dist_ts/standards/index.js +15 -0
  51. package/dist_ts/standards/output.d.ts +7 -0
  52. package/dist_ts/standards/output.js +57 -0
  53. package/dist_ts/standards/ratchet.d.ts +15 -0
  54. package/dist_ts/standards/ratchet.js +45 -0
  55. package/dist_ts/standards/rule.d.ts +50 -0
  56. package/dist_ts/standards/rule.js +9 -0
  57. package/dist_ts/standards/rules/barrel.element-index.d.ts +7 -0
  58. package/dist_ts/standards/rules/barrel.element-index.js +108 -0
  59. package/dist_ts/standards/rules/barrel.no-demo-export.d.ts +3 -0
  60. package/dist_ts/standards/rules/barrel.no-demo-export.js +29 -0
  61. package/dist_ts/standards/rules/demo.fixture-not-exported.d.ts +3 -0
  62. package/dist_ts/standards/rules/demo.fixture-not-exported.js +29 -0
  63. package/dist_ts/standards/rules/demo.groups.d.ts +6 -0
  64. package/dist_ts/standards/rules/demo.groups.js +72 -0
  65. package/dist_ts/standards/rules/demo.required.d.ts +3 -0
  66. package/dist_ts/standards/rules/demo.required.js +48 -0
  67. package/dist_ts/standards/rules/demo.separate-file.d.ts +3 -0
  68. package/dist_ts/standards/rules/demo.separate-file.js +38 -0
  69. package/dist_ts/standards/rules/demo.shape.d.ts +3 -0
  70. package/dist_ts/standards/rules/demo.shape.js +38 -0
  71. package/dist_ts/standards/rules/docs.component-index.d.ts +8 -0
  72. package/dist_ts/standards/rules/docs.component-index.js +84 -0
  73. package/dist_ts/standards/rules/element.exported.d.ts +3 -0
  74. package/dist_ts/standards/rules/element.exported.js +33 -0
  75. package/dist_ts/standards/rules/element.literal-tag.d.ts +3 -0
  76. package/dist_ts/standards/rules/element.literal-tag.js +30 -0
  77. package/dist_ts/standards/rules/element.reachable.d.ts +3 -0
  78. package/dist_ts/standards/rules/element.reachable.js +48 -0
  79. package/dist_ts/standards/rules/element.tag-map.d.ts +3 -0
  80. package/dist_ts/standards/rules/element.tag-map.js +83 -0
  81. package/dist_ts/standards/rules/i18n.literals.d.ts +3 -0
  82. package/dist_ts/standards/rules/i18n.literals.js +52 -0
  83. package/dist_ts/standards/rules/index.d.ts +12 -0
  84. package/dist_ts/standards/rules/index.js +83 -0
  85. package/dist_ts/standards/rules/layout.element-folder.d.ts +3 -0
  86. package/dist_ts/standards/rules/layout.element-folder.js +47 -0
  87. package/dist_ts/standards/rules/layout.entry-file-name.d.ts +3 -0
  88. package/dist_ts/standards/rules/layout.entry-file-name.js +71 -0
  89. package/dist_ts/standards/rules/layout.entry.d.ts +3 -0
  90. package/dist_ts/standards/rules/layout.entry.js +38 -0
  91. package/dist_ts/standards/rules/layout.group-folder.d.ts +3 -0
  92. package/dist_ts/standards/rules/layout.group-folder.js +22 -0
  93. package/dist_ts/standards/rules/layout.shared-00.d.ts +3 -0
  94. package/dist_ts/standards/rules/layout.shared-00.js +36 -0
  95. package/dist_ts/standards/rules/naming.class-matches-tag.d.ts +3 -0
  96. package/dist_ts/standards/rules/naming.class-matches-tag.js +26 -0
  97. package/dist_ts/standards/rules/naming.lowercase-files.d.ts +3 -0
  98. package/dist_ts/standards/rules/naming.lowercase-files.js +26 -0
  99. package/dist_ts/standards/rules/naming.tag-prefix.d.ts +3 -0
  100. package/dist_ts/standards/rules/naming.tag-prefix.js +23 -0
  101. package/dist_ts/standards/rules/props.accessor.d.ts +3 -0
  102. package/dist_ts/standards/rules/props.accessor.js +35 -0
  103. package/dist_ts/standards/rules/props.object-no-attribute.d.ts +3 -0
  104. package/dist_ts/standards/rules/props.object-no-attribute.js +42 -0
  105. package/dist_ts/standards/rules/props.reflect-primitive.d.ts +5 -0
  106. package/dist_ts/standards/rules/props.reflect-primitive.js +41 -0
  107. package/dist_ts/standards/rules/props.typed.d.ts +6 -0
  108. package/dist_ts/standards/rules/props.typed.js +64 -0
  109. package/dist_ts/standards/rules/shared.d.ts +38 -0
  110. package/dist_ts/standards/rules/shared.js +103 -0
  111. package/dist_ts/standards/rules/style.bdtheme.d.ts +5 -0
  112. package/dist_ts/standards/rules/style.bdtheme.js +28 -0
  113. package/dist_ts_interfaces/bridge.d.ts +88 -0
  114. package/dist_ts_interfaces/bridge.js +2 -0
  115. package/dist_ts_interfaces/catalog.d.ts +32 -0
  116. package/dist_ts_interfaces/catalog.js +6 -0
  117. package/dist_ts_interfaces/index.d.ts +4 -0
  118. package/dist_ts_interfaces/index.js +5 -0
  119. package/dist_ts_interfaces/plugins.d.ts +2 -0
  120. package/dist_ts_interfaces/plugins.js +3 -0
  121. package/dist_ts_interfaces/requests.d.ts +15 -0
  122. package/dist_ts_interfaces/requests.js +2 -0
  123. package/dist_ts_interfaces/standard.d.ts +93 -0
  124. package/dist_ts_interfaces/standard.js +6 -0
  125. package/dist_ts_shared/index.d.ts +1 -0
  126. package/dist_ts_shared/index.js +2 -0
  127. package/dist_ts_shared/plugins.d.ts +2 -0
  128. package/dist_ts_shared/plugins.js +3 -0
  129. package/dist_ts_shared/previewroute.d.ts +18 -0
  130. package/dist_ts_shared/previewroute.js +47 -0
  131. package/{dist_ts_web → dist_ts_shell}/elements/wcc-contextmenu.js +1 -1
  132. package/dist_ts_shell/elements/wcc-preview-frame.d.ts +23 -0
  133. package/dist_ts_shell/elements/wcc-preview-frame.js +197 -0
  134. package/dist_ts_shell/elements/wcc-properties.d.ts +49 -0
  135. package/{dist_ts_web → dist_ts_shell}/elements/wcc-properties.js +149 -216
  136. package/{dist_ts_web → dist_ts_shell}/elements/wcc-record-button.js +1 -1
  137. package/{dist_ts_web → dist_ts_shell}/elements/wcc-recording-panel.d.ts +2 -2
  138. package/{dist_ts_web → dist_ts_shell}/elements/wcc-recording-panel.js +12 -16
  139. package/dist_ts_shell/elements/wcc-shell.d.ts +87 -0
  140. package/dist_ts_shell/elements/wcc-shell.js +524 -0
  141. package/{dist_ts_web → dist_ts_shell}/elements/wcc-sidebar.d.ts +12 -10
  142. package/{dist_ts_web → dist_ts_shell}/elements/wcc-sidebar.js +137 -183
  143. package/dist_ts_shell/index.d.ts +4 -0
  144. package/dist_ts_shell/index.js +5 -0
  145. package/dist_ts_shell/plugins.d.ts +4 -0
  146. package/dist_ts_shell/plugins.js +5 -0
  147. package/dist_ts_shell/previewconnection.d.ts +30 -0
  148. package/dist_ts_shell/previewconnection.js +86 -0
  149. package/dist_ts_shell/services/recorder.service.js +353 -0
  150. package/dist_ts_web/00_commitinfo_data.js +2 -2
  151. package/dist_ts_web/catalog.d.ts +45 -0
  152. package/dist_ts_web/catalog.js +153 -0
  153. package/dist_ts_web/elementproperties.d.ts +15 -0
  154. package/dist_ts_web/elementproperties.js +89 -0
  155. package/dist_ts_web/elements/wcc-preview.d.ts +19 -0
  156. package/dist_ts_web/elements/wcc-preview.js +164 -0
  157. package/dist_ts_web/elements/wcc-standalone-notice.d.ts +14 -0
  158. package/dist_ts_web/elements/wcc-standalone-notice.js +110 -0
  159. package/dist_ts_web/elements/wcctools.helpers.d.ts +2 -6
  160. package/dist_ts_web/elements/wcctools.helpers.js +1 -7
  161. package/dist_ts_web/index.d.ts +2 -5
  162. package/dist_ts_web/index.js +38 -17
  163. package/dist_ts_web/previewbridge.d.ts +39 -0
  164. package/dist_ts_web/previewbridge.js +153 -0
  165. package/dist_ts_web/renderedtree.d.ts +5 -0
  166. package/dist_ts_web/renderedtree.js +37 -0
  167. package/dist_ts_web/wcctools.interfaces.d.ts +3 -6
  168. package/dist_ts_web/wcctools.interfaces.js +1 -1
  169. package/dist_ts_web/wcctools.plugins.d.ts +3 -1
  170. package/dist_ts_web/wcctools.plugins.js +4 -2
  171. package/package.json +17 -7
  172. package/readme.md +93 -28
  173. package/ts/00_commitinfo_data.ts +8 -0
  174. package/ts/classes.devapi.ts +25 -0
  175. package/ts/classes.devserver.ts +161 -0
  176. package/ts/cli.ts +53 -0
  177. package/ts/index.ts +3 -0
  178. package/ts/paths.ts +7 -0
  179. package/ts/plugins.ts +37 -0
  180. package/ts/shellcontent.ts +42 -0
  181. package/ts/standards/baselines.ts +100 -0
  182. package/ts/standards/checker.ts +114 -0
  183. package/ts/standards/classes.gitrepo.ts +48 -0
  184. package/ts/standards/classes.sourceedits.ts +58 -0
  185. package/ts/standards/classes.standardfixer.ts +234 -0
  186. package/ts/standards/classes.standardproject.ts +250 -0
  187. package/ts/standards/classes.standardtree.ts +220 -0
  188. package/ts/standards/cli.ts +86 -0
  189. package/ts/standards/config.ts +160 -0
  190. package/ts/standards/errors.ts +18 -0
  191. package/ts/standards/helpers.ast.ts +208 -0
  192. package/ts/standards/helpers.paths.ts +119 -0
  193. package/ts/standards/index.ts +22 -0
  194. package/ts/standards/output.ts +65 -0
  195. package/ts/standards/ratchet.ts +63 -0
  196. package/ts/standards/rule.ts +58 -0
  197. package/ts/standards/rules/barrel.element-index.ts +112 -0
  198. package/ts/standards/rules/barrel.no-demo-export.ts +30 -0
  199. package/ts/standards/rules/demo.fixture-not-exported.ts +30 -0
  200. package/ts/standards/rules/demo.groups.ts +74 -0
  201. package/ts/standards/rules/demo.required.ts +49 -0
  202. package/ts/standards/rules/demo.separate-file.ts +40 -0
  203. package/ts/standards/rules/demo.shape.ts +42 -0
  204. package/ts/standards/rules/docs.component-index.ts +88 -0
  205. package/ts/standards/rules/element.exported.ts +35 -0
  206. package/ts/standards/rules/element.literal-tag.ts +30 -0
  207. package/ts/standards/rules/element.reachable.ts +49 -0
  208. package/ts/standards/rules/element.tag-map.ts +87 -0
  209. package/ts/standards/rules/i18n.literals.ts +58 -0
  210. package/ts/standards/rules/index.ts +87 -0
  211. package/ts/standards/rules/layout.element-folder.ts +49 -0
  212. package/ts/standards/rules/layout.entry-file-name.ts +73 -0
  213. package/ts/standards/rules/layout.entry.ts +40 -0
  214. package/ts/standards/rules/layout.group-folder.ts +23 -0
  215. package/ts/standards/rules/layout.shared-00.ts +37 -0
  216. package/ts/standards/rules/naming.class-matches-tag.ts +27 -0
  217. package/ts/standards/rules/naming.lowercase-files.ts +27 -0
  218. package/ts/standards/rules/naming.tag-prefix.ts +24 -0
  219. package/ts/standards/rules/props.accessor.ts +37 -0
  220. package/ts/standards/rules/props.object-no-attribute.ts +45 -0
  221. package/ts/standards/rules/props.reflect-primitive.ts +44 -0
  222. package/ts/standards/rules/props.typed.ts +70 -0
  223. package/ts/standards/rules/shared.ts +127 -0
  224. package/ts/standards/rules/style.bdtheme.ts +31 -0
  225. package/ts_web/00_commitinfo_data.ts +1 -1
  226. package/ts_web/catalog.ts +194 -0
  227. package/ts_web/elementproperties.ts +96 -0
  228. package/ts_web/elements/wcc-preview.ts +111 -0
  229. package/ts_web/elements/wcc-standalone-notice.ts +65 -0
  230. package/ts_web/elements/wcctools.helpers.ts +2 -10
  231. package/ts_web/index.ts +38 -19
  232. package/ts_web/previewbridge.ts +189 -0
  233. package/ts_web/readme.md +26 -85
  234. package/ts_web/renderedtree.ts +43 -0
  235. package/ts_web/wcctools.interfaces.ts +4 -7
  236. package/ts_web/wcctools.plugins.ts +6 -1
  237. package/dist_bundle/bundle.js +0 -92419
  238. package/dist_bundle/bundle.js.map +0 -1
  239. package/dist_ts_web/elements/wcc-dashboard.d.ts +0 -119
  240. package/dist_ts_web/elements/wcc-dashboard.js +0 -700
  241. package/dist_ts_web/elements/wcc-frame.d.ts +0 -22
  242. package/dist_ts_web/elements/wcc-frame.js +0 -256
  243. package/dist_ts_web/elements/wcc-properties.d.ts +0 -43
  244. package/dist_ts_web/pages/index.d.ts +0 -1
  245. package/dist_ts_web/pages/index.js +0 -2
  246. package/dist_ts_web/services/recorder.service.js +0 -353
  247. package/ts_web/elements/wcc-contextmenu.ts +0 -291
  248. package/ts_web/elements/wcc-dashboard.ts +0 -672
  249. package/ts_web/elements/wcc-frame.ts +0 -189
  250. package/ts_web/elements/wcc-properties.ts +0 -1089
  251. package/ts_web/elements/wcc-record-button.ts +0 -108
  252. package/ts_web/elements/wcc-recording-panel.ts +0 -1010
  253. package/ts_web/elements/wcc-sidebar.ts +0 -1470
  254. package/ts_web/pages/index.ts +0 -1
  255. package/ts_web/services/recorder.service.ts +0 -451
  256. package/ts_web/types/dom-mediacapture-stub/index.d.ts +0 -12
  257. package/ts_web/types/dom-mediacapture-stub/package.json +0 -6
  258. package/ts_web/types/dom-webcodecs-stub/index.d.ts +0 -2
  259. package/ts_web/types/dom-webcodecs-stub/package.json +0 -6
  260. /package/{dist_ts_web → dist_ts_shell}/elements/wcc-contextmenu.d.ts +0 -0
  261. /package/{dist_ts_web → dist_ts_shell}/elements/wcc-record-button.d.ts +0 -0
  262. /package/{dist_ts_web → dist_ts_shell}/services/recorder.service.d.ts +0 -0
@@ -0,0 +1,87 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import * as ast from '../helpers.ast.js';
3
+ import type { IStandardElement } from '../classes.standardproject.js';
4
+ import type { IRuleFinding, IStandardRule, TFixAction } from '../rule.js';
5
+ import { catalogElements, elementKey, isCustomElementName } from './shared.js';
6
+
7
+ const ts = plugins.typescript;
8
+
9
+ /** The tags of a file's `declare global { interface HTMLElementTagNameMap { … } }` and the first such interface. */
10
+ const tagMapOf = (sourceFileArg: ast.TSourceFile): { tags: Set<string>; declaration: plugins.typescript.InterfaceDeclaration | null } => {
11
+ const tags = new Set<string>();
12
+ let declaration: plugins.typescript.InterfaceDeclaration | null = null;
13
+ for (const statement of sourceFileArg.statements) {
14
+ if (!ts.isModuleDeclaration(statement) || !ts.isIdentifier(statement.name) || statement.name.text !== 'global'
15
+ || !statement.body || !ts.isModuleBlock(statement.body)) {
16
+ continue;
17
+ }
18
+ for (const inner of statement.body.statements) {
19
+ if (ts.isInterfaceDeclaration(inner) && inner.name.text === 'HTMLElementTagNameMap') {
20
+ declaration ??= inner;
21
+ for (const member of inner.members) {
22
+ if (ts.isPropertySignature(member) && member.name && (ts.isStringLiteral(member.name) || ts.isIdentifier(member.name))) {
23
+ tags.add(member.name.text);
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
29
+ return { tags, declaration };
30
+ };
31
+
32
+ /** Adds every missing element of one file to its tag map, or appends a tag map block to the file. */
33
+ const tagMapFix = (missingArg: IStandardElement[]): TFixAction => (builderArg) => {
34
+ const sourceFile = missingArg[0].sourceFile;
35
+ const file = missingArg[0].file;
36
+ const text = sourceFile.text;
37
+ const { declaration } = tagMapOf(sourceFile);
38
+ if (declaration) {
39
+ const closeBrace = declaration.end - 1;
40
+ const lineStart = text.lastIndexOf('\n', closeBrace - 1) + 1;
41
+ const firstMember = declaration.members[0];
42
+ const indentation = firstMember
43
+ ? ast.indentationAt(text, firstMember.getStart())
44
+ : `${ast.indentationAt(text, declaration.getStart())} `;
45
+ const lines = missingArg.map((elementArg) => `${indentation}'${elementArg.tag}': ${elementArg.className};\n`).join('');
46
+ if (/^[ \t]*$/.test(text.slice(lineStart, closeBrace))) {
47
+ builderArg.edit(file, lineStart, lineStart, lines);
48
+ } else {
49
+ builderArg.edit(file, closeBrace, closeBrace, `\n${lines}`);
50
+ }
51
+ return;
52
+ }
53
+ const members = missingArg.map((elementArg) => ` '${elementArg.tag}': ${elementArg.className};\n`).join('');
54
+ const prefix = text.length === 0 || text.endsWith('\n') ? '' : '\n';
55
+ builderArg.edit(file, text.length, text.length, `${prefix}\ndeclare global {\n interface HTMLElementTagNameMap {\n${members} }\n}\n`);
56
+ };
57
+
58
+ /** The entry file declares `declare global { interface HTMLElementTagNameMap { '<tag>': <Class> } }`. */
59
+ export const elementTagMapRule: IStandardRule = {
60
+ id: 'element/tag-map',
61
+ severity: { base: 'warn' },
62
+ fixMode: 'auto',
63
+ detect: ({ project }) => {
64
+ const findings: IRuleFinding[] = [];
65
+ const byFile = new Map<string, IStandardElement[]>();
66
+ for (const element of catalogElements(project)) {
67
+ if (element.tag && !tagMapOf(element.sourceFile).tags.has(element.tag)) {
68
+ byFile.set(element.file, [...(byFile.get(element.file) ?? []), element]);
69
+ }
70
+ }
71
+ for (const missing of byFile.values()) {
72
+ // The tag is written into the source: only valid custom element names are.
73
+ const fixable = missing.every((elementArg) => elementArg.className && isCustomElementName(elementArg.tag!));
74
+ const fix = fixable ? tagMapFix(missing) : undefined;
75
+ for (const element of missing) {
76
+ findings.push({
77
+ file: element.file,
78
+ position: element.decoratorCall.getStart(),
79
+ key: elementKey(element),
80
+ message: `HTMLElementTagNameMap of ${element.file} does not declare '${element.tag}'`,
81
+ fix,
82
+ });
83
+ }
84
+ }
85
+ return findings;
86
+ },
87
+ };
@@ -0,0 +1,58 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import * as ast from '../helpers.ast.js';
3
+ import { isDemoFile } from '../helpers.paths.js';
4
+ import type { IRuleFinding, IStandardRule } from '../rule.js';
5
+
6
+ const ts = plugins.typescript;
7
+
8
+ /** The words belong to the catalog; demos, demo data, the i18n catalog and the demo pages show sample content. */
9
+ const isExempt = (fileArg: string): boolean =>
10
+ isDemoFile(fileArg)
11
+ || fileArg.startsWith('ts_web/i18n/')
12
+ || fileArg.startsWith('ts_web/demos/')
13
+ || fileArg.startsWith('ts_web/pages/');
14
+
15
+ const wordAttributes = 'aria-label|aria-description|aria-valuetext|title|placeholder|label|text|heading|subheading|alt';
16
+ const wordProperties = new Set(['name', 'label', 'text', 'heading', 'subheading', 'title', 'placeholder', 'message', 'description', 'confirmLabel', 'cancelLabel', 'emptyText', 'tooltip', 'hint']);
17
+ const word = /^[A-Z][a-z]+(?:[ '’,.?!…-]+[A-Za-z]+)*[.?!…]?$/;
18
+
19
+ /** The patterns of dees-catalog's scripts/check-literal-ratchet.cjs that apply to template and string text. */
20
+ const textPatterns = (): RegExp[] => [
21
+ // aria-label="Progress"
22
+ new RegExp(`\\s(?:${wordAttributes})="([A-Za-z][^"$]*[a-z][^"$]*)"`, 'g'),
23
+ // >Cancel< and > Back <
24
+ /(?<![=-])>\s*([A-Z][a-z]+(?:[ '’,.?!…-]+[A-Za-z]+)*[.?!…]?)\s*</g,
25
+ ];
26
+
27
+ /** dees: no English literal a reader sees outside demo files, `ts_web/i18n/`, `ts_web/demos/` and `ts_web/pages/`. A ratchet. */
28
+ export const i18nLiteralsRule: IStandardRule = {
29
+ id: 'i18n/literals',
30
+ severity: { dees: 'error' },
31
+ fixMode: 'none',
32
+ detect: ({ project }) => {
33
+ const findings: IRuleFinding[] = [];
34
+ for (const file of project.webFiles) {
35
+ if (isExempt(file)) {
36
+ continue;
37
+ }
38
+ const visit = (nodeArg: ast.TNode): void => {
39
+ if (ts.isStringLiteral(nodeArg) || ts.isNoSubstitutionTemplateLiteral(nodeArg)
40
+ || ts.isTemplateHead(nodeArg) || ts.isTemplateMiddle(nodeArg) || ts.isTemplateTail(nodeArg)) {
41
+ const text = nodeArg.getText();
42
+ for (const pattern of textPatterns()) {
43
+ for (const match of text.matchAll(pattern)) {
44
+ findings.push({ file, position: nodeArg.getStart() + match.index, key: file, message: `English literal ${match[0].trim()}` });
45
+ }
46
+ }
47
+ }
48
+ if (ts.isPropertyAssignment(nodeArg) && (ts.isIdentifier(nodeArg.name) || ts.isStringLiteral(nodeArg.name))
49
+ && wordProperties.has(nodeArg.name.text) && ts.isStringLiteral(nodeArg.initializer) && word.test(nodeArg.initializer.text)) {
50
+ findings.push({ file, position: nodeArg.getStart(), key: file, message: `English literal ${nodeArg.getText()}` });
51
+ }
52
+ ts.forEachChild(nodeArg, visit);
53
+ };
54
+ visit(project.sourceFile(file));
55
+ }
56
+ return findings;
57
+ },
58
+ };
@@ -0,0 +1,87 @@
1
+ import type { IStandardRule } from '../rule.js';
2
+ import { barrelElementIndexRule } from './barrel.element-index.js';
3
+ import { barrelNoDemoExportRule } from './barrel.no-demo-export.js';
4
+ import { demoFixtureNotExportedRule } from './demo.fixture-not-exported.js';
5
+ import { demoGroupsRule } from './demo.groups.js';
6
+ import { demoRequiredRule } from './demo.required.js';
7
+ import { demoSeparateFileRule } from './demo.separate-file.js';
8
+ import { demoShapeRule } from './demo.shape.js';
9
+ import { docsComponentIndexRule } from './docs.component-index.js';
10
+ import { elementExportedRule } from './element.exported.js';
11
+ import { elementLiteralTagRule } from './element.literal-tag.js';
12
+ import { elementReachableRule } from './element.reachable.js';
13
+ import { elementTagMapRule } from './element.tag-map.js';
14
+ import { i18nLiteralsRule } from './i18n.literals.js';
15
+ import { layoutElementFolderRule } from './layout.element-folder.js';
16
+ import { layoutEntryFileNameRule } from './layout.entry-file-name.js';
17
+ import { layoutEntryRule } from './layout.entry.js';
18
+ import { layoutGroupFolderRule } from './layout.group-folder.js';
19
+ import { layoutShared00Rule } from './layout.shared-00.js';
20
+ import { namingClassMatchesTagRule } from './naming.class-matches-tag.js';
21
+ import { namingLowercaseFilesRule } from './naming.lowercase-files.js';
22
+ import { namingTagPrefixRule } from './naming.tag-prefix.js';
23
+ import { propsAccessorRule } from './props.accessor.js';
24
+ import { propsObjectNoAttributeRule } from './props.object-no-attribute.js';
25
+ import { propsReflectPrimitiveRule } from './props.reflect-primitive.js';
26
+ import { propsTypedRule } from './props.typed.js';
27
+ import { styleBdThemeRule } from './style.bdtheme.js';
28
+
29
+ export { reflectExemptNames } from './props.reflect-primitive.js';
30
+ export { renderComponentIndex } from './docs.component-index.js';
31
+
32
+ /** Every rule of docs/standard.md, in the order of the document. */
33
+ export const standardRules: readonly IStandardRule[] = [
34
+ layoutEntryRule,
35
+ layoutElementFolderRule,
36
+ layoutEntryFileNameRule,
37
+ layoutGroupFolderRule,
38
+ layoutShared00Rule,
39
+ namingLowercaseFilesRule,
40
+ namingTagPrefixRule,
41
+ namingClassMatchesTagRule,
42
+ elementLiteralTagRule,
43
+ elementExportedRule,
44
+ elementTagMapRule,
45
+ elementReachableRule,
46
+ barrelElementIndexRule,
47
+ barrelNoDemoExportRule,
48
+ demoShapeRule,
49
+ demoRequiredRule,
50
+ demoSeparateFileRule,
51
+ demoGroupsRule,
52
+ demoFixtureNotExportedRule,
53
+ propsAccessorRule,
54
+ propsTypedRule,
55
+ propsReflectPrimitiveRule,
56
+ propsObjectNoAttributeRule,
57
+ styleBdThemeRule,
58
+ i18nLiteralsRule,
59
+ docsComponentIndexRule,
60
+ ];
61
+
62
+ /**
63
+ * The order `wcctools fix` applies rules in: content edits first, then file moves (which rewrite imports),
64
+ * then barrels and reachability on the final layout, then the generated docs.
65
+ */
66
+ export const fixOrder: readonly string[] = [
67
+ 'element/exported',
68
+ 'element/tag-map',
69
+ 'props/accessor',
70
+ 'props/typed',
71
+ 'props/object-no-attribute',
72
+ 'props/reflect-primitive',
73
+ 'demo/groups',
74
+ 'demo/required',
75
+ 'barrel/no-demo-export',
76
+ 'demo/fixture-not-exported',
77
+ 'naming/lowercase-files',
78
+ 'layout/shared-00',
79
+ 'layout/entry-file-name',
80
+ 'layout/element-folder',
81
+ 'barrel/element-index',
82
+ 'element/reachable',
83
+ 'docs/component-index',
84
+ ];
85
+
86
+ /** Findings the checker itself produces for the allowlist. */
87
+ export const allowlistStaleRuleId = 'allowlist/stale';
@@ -0,0 +1,49 @@
1
+ import { baseOf, dirOf } from '../helpers.paths.js';
2
+ import type { IRuleFinding, IStandardRule } from '../rule.js';
3
+ import { catalogElements, elementKey, elementsDir, isCustomElementName, isElementParentDir, isInElementFolder } from './shared.js';
4
+
5
+ /** Every element's entry file lives in its own element folder `ts_web/elements/[00group-<area>/]<tag>/<tag>.ts`. */
6
+ export const layoutElementFolderRule: IStandardRule = {
7
+ id: 'layout/element-folder',
8
+ severity: { base: 'warn' },
9
+ fixMode: 'auto',
10
+ detect: ({ project }) => {
11
+ const findings: IRuleFinding[] = [];
12
+ const elements = catalogElements(project);
13
+ for (const element of elements) {
14
+ if (!element.tag || isInElementFolder(element)) {
15
+ continue;
16
+ }
17
+ const currentDir = dirOf(element.file);
18
+ const group = /^ts_web\/elements\/(00group-[^/]+)(\/|$)/.exec(currentDir)?.[1];
19
+ const folder = `${elementsDir}/${group ? `${group}/` : ''}${element.tag}`;
20
+ const finding: IRuleFinding = {
21
+ file: element.file,
22
+ position: element.decoratorCall.getStart(),
23
+ key: elementKey(element),
24
+ message: `<${element.tag}> is declared in ${element.file}; its element folder is ${folder}/`,
25
+ };
26
+ const elementsInFile = elements.filter((otherArg) => otherArg.file === element.file);
27
+ const folderTaken = [...project.files.keys()].some((fileArg) => fileArg.startsWith(`${folder}/`)) || project.exists(folder);
28
+ // Only flat files move mechanically: a misnamed folder holds more than this element. The folder
29
+ // is named after the tag, so only a valid custom element name is usable.
30
+ if (isCustomElementName(element.tag) && elementsInFile.length === 1 && isElementParentDir(currentDir) && !folderTaken) {
31
+ const tag = element.tag;
32
+ const companions = [...project.files.keys()].filter((fileArg) =>
33
+ fileArg !== element.file && dirOf(fileArg) === currentDir && baseOf(fileArg).startsWith(`${tag}.`) && baseOf(fileArg) !== 'index.ts');
34
+ finding.fix = (builderArg) => {
35
+ const entry = `${folder}/${tag}.ts`;
36
+ const index = `${folder}/index.ts`;
37
+ builderArg.create(index, `export * from './${tag}.js';\n`);
38
+ builderArg.move(element.file, entry);
39
+ for (const companion of companions) {
40
+ builderArg.move(companion, `${folder}/${baseOf(companion)}`);
41
+ }
42
+ builderArg.redirectBarrelExports(element.file, index);
43
+ };
44
+ }
45
+ findings.push(finding);
46
+ }
47
+ return findings;
48
+ },
49
+ };
@@ -0,0 +1,73 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import * as ast from '../helpers.ast.js';
3
+ import { baseOf, dirOf, isDemoFile, resolveSpecifier } from '../helpers.paths.js';
4
+ import type { IRuleFinding, IStandardRule } from '../rule.js';
5
+ import { catalogElements, elementKey, isCustomElementName, isInElementFolder, rootIdentifier } from './shared.js';
6
+
7
+ const ts = plugins.typescript;
8
+
9
+ /** Inside an element folder the entry file is `<tag>.ts` and the demo file is `<tag>.demo.ts`. */
10
+ export const layoutEntryFileNameRule: IStandardRule = {
11
+ id: 'layout/entry-file-name',
12
+ severity: { base: 'warn' },
13
+ fixMode: 'auto',
14
+ detect: ({ project }) => {
15
+ const findings: IRuleFinding[] = [];
16
+ const elements = catalogElements(project);
17
+ for (const element of elements) {
18
+ if (!element.tag || !isInElementFolder(element)) {
19
+ continue;
20
+ }
21
+ const tag = element.tag;
22
+ // The expected file names come from the tag: only a valid custom element name is usable for a move.
23
+ const usableTag = isCustomElementName(tag);
24
+ const folder = dirOf(element.file);
25
+ const entry = `${folder}/${tag}.ts`;
26
+ if (element.file !== entry) {
27
+ const finding: IRuleFinding = {
28
+ file: element.file,
29
+ position: element.decoratorCall.getStart(),
30
+ key: elementKey(element),
31
+ message: `the entry file of <${tag}> is ${baseOf(element.file)}; expected ${tag}.ts`,
32
+ };
33
+ if (usableTag && elements.filter((otherArg) => otherArg.file === element.file).length === 1 && !project.exists(entry)) {
34
+ finding.fix = (builderArg) => builderArg.move(element.file, entry);
35
+ }
36
+ findings.push(finding);
37
+ }
38
+ // The demo file the static demo is imported from.
39
+ const demoMember = ast.findStaticProperty(element.classNode, 'demo');
40
+ const initializer = demoMember?.initializer;
41
+ if (!initializer) {
42
+ continue;
43
+ }
44
+ const references = ts.isArrayLiteralExpression(initializer) ? [...initializer.elements] : [initializer];
45
+ const demoFiles = new Set<string>();
46
+ for (const reference of references) {
47
+ const identifier = rootIdentifier(reference);
48
+ const source = identifier ? ast.importSourceOf(element.sourceFile, identifier) : null;
49
+ const resolved = source ? resolveSpecifier(element.file, source, (fileArg) => project.files.has(fileArg)) : null;
50
+ if (resolved && isDemoFile(resolved.target) && dirOf(resolved.target) === folder) {
51
+ demoFiles.add(resolved.target);
52
+ }
53
+ }
54
+ const expectedDemo = `${folder}/${tag}.demo.ts`;
55
+ for (const demoFile of demoFiles) {
56
+ if (demoFile === expectedDemo) {
57
+ continue;
58
+ }
59
+ const finding: IRuleFinding = {
60
+ file: element.file,
61
+ position: demoMember!.getStart(),
62
+ key: elementKey(element),
63
+ message: `the demo file of <${tag}> is ${baseOf(demoFile)}; expected ${tag}.demo.ts`,
64
+ };
65
+ if (usableTag && demoFiles.size === 1 && !project.exists(expectedDemo)) {
66
+ finding.fix = (builderArg) => builderArg.move(demoFile, expectedDemo);
67
+ }
68
+ findings.push(finding);
69
+ }
70
+ }
71
+ return findings;
72
+ },
73
+ };
@@ -0,0 +1,40 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import * as ast from '../helpers.ast.js';
3
+ import type { IRuleFinding, IStandardRule } from '../rule.js';
4
+
5
+ const ts = plugins.typescript;
6
+
7
+ /** `html/index.ts` exists and calls `setupWccTools({ sections })`; `html/index.html` exists. */
8
+ export const layoutEntryRule: IStandardRule = {
9
+ id: 'layout/entry',
10
+ severity: { base: 'error' },
11
+ fixMode: 'none',
12
+ detect: ({ project }) => {
13
+ const findings: IRuleFinding[] = [];
14
+ const entry = 'html/index.ts';
15
+ if (!project.files.has(entry)) {
16
+ findings.push({ file: entry, position: 0, key: entry, message: 'html/index.ts is missing' });
17
+ } else {
18
+ let found = false;
19
+ const visit = (nodeArg: ast.TNode): void => {
20
+ if (ts.isCallExpression(nodeArg) && ast.calleeName(nodeArg.expression) === 'setupWccTools') {
21
+ const config = nodeArg.arguments[0];
22
+ if (config && ts.isObjectLiteralExpression(config) && config.properties.some((propertyArg) =>
23
+ (ts.isPropertyAssignment(propertyArg) || ts.isShorthandPropertyAssignment(propertyArg))
24
+ && ts.isIdentifier(propertyArg.name) && propertyArg.name.text === 'sections')) {
25
+ found = true;
26
+ }
27
+ }
28
+ ts.forEachChild(nodeArg, visit);
29
+ };
30
+ visit(project.sourceFile(entry));
31
+ if (!found) {
32
+ findings.push({ file: entry, position: 0, key: entry, message: 'html/index.ts does not call setupWccTools({ sections })' });
33
+ }
34
+ }
35
+ if (!project.files.has('html/index.html')) {
36
+ findings.push({ file: 'html/index.html', position: 0, key: 'html/index.html', message: 'html/index.html is missing' });
37
+ }
38
+ return findings;
39
+ },
40
+ };
@@ -0,0 +1,23 @@
1
+ import type { IRuleFinding, IStandardRule } from '../rule.js';
2
+ import { catalogElements, elementKey } from './shared.js';
3
+
4
+ /** dees: element folders sit under `ts_web/elements/00group-<area>/`. */
5
+ export const layoutGroupFolderRule: IStandardRule = {
6
+ id: 'layout/group-folder',
7
+ severity: { dees: 'warn' },
8
+ fixMode: 'none',
9
+ detect: ({ project }) => {
10
+ const findings: IRuleFinding[] = [];
11
+ for (const element of catalogElements(project)) {
12
+ if (element.file.startsWith('ts_web/elements/') && !/^ts_web\/elements\/00group-[^/]+\//.test(element.file)) {
13
+ findings.push({
14
+ file: element.file,
15
+ position: element.decoratorCall.getStart(),
16
+ key: elementKey(element),
17
+ message: `<${element.tag ?? '?'}> is not inside a ts_web/elements/00group-<area>/ folder`,
18
+ });
19
+ }
20
+ }
21
+ return findings;
22
+ },
23
+ };
@@ -0,0 +1,37 @@
1
+ import { baseOf, dirOf, isDemoFile } from '../helpers.paths.js';
2
+ import type { IRuleFinding, IStandardRule } from '../rule.js';
3
+ import { elementsDir } from './shared.js';
4
+
5
+ /** A module at the root of `ts_web/elements/` that declares no element is named `00<name>.ts`. */
6
+ export const layoutShared00Rule: IStandardRule = {
7
+ id: 'layout/shared-00',
8
+ severity: { base: 'info' },
9
+ fixMode: 'auto',
10
+ detect: ({ project }) => {
11
+ const findings: IRuleFinding[] = [];
12
+ const rootElements = project.elements.filter((elementArg) => dirOf(elementArg.file) === elementsDir);
13
+ const elementFiles = new Set(project.elements.map((elementArg) => elementArg.file));
14
+ for (const file of project.webFiles) {
15
+ const name = baseOf(file);
16
+ if (dirOf(file) !== elementsDir || name === 'index.ts' || name.startsWith('00') || isDemoFile(file) || elementFiles.has(file)) {
17
+ continue;
18
+ }
19
+ // Companion files of a flat element (`<tag>.styles.ts`) move with the element instead.
20
+ if (rootElements.some((elementArg) => elementArg.tag && name.startsWith(`${elementArg.tag}.`))) {
21
+ continue;
22
+ }
23
+ const target = `${elementsDir}/00${name}`;
24
+ const finding: IRuleFinding = {
25
+ file,
26
+ position: 0,
27
+ key: file,
28
+ message: `${name} declares no element; shared modules at the root of ts_web/elements are named 00${name}`,
29
+ };
30
+ if (!project.exists(target)) {
31
+ finding.fix = (builderArg) => builderArg.move(file, target);
32
+ }
33
+ findings.push(finding);
34
+ }
35
+ return findings;
36
+ },
37
+ };
@@ -0,0 +1,27 @@
1
+ import type { IRuleFinding, IStandardRule } from '../rule.js';
2
+ import { catalogElements, elementKey } from './shared.js';
3
+
4
+ /** The class name, lowercased, equals the tag with hyphens removed. */
5
+ export const namingClassMatchesTagRule: IStandardRule = {
6
+ id: 'naming/class-matches-tag',
7
+ severity: { base: 'warn' },
8
+ fixMode: 'none',
9
+ allowlistCategories: ['published'],
10
+ detect: ({ project }) => {
11
+ const findings: IRuleFinding[] = [];
12
+ for (const element of catalogElements(project)) {
13
+ if (!element.tag || !element.className) {
14
+ continue;
15
+ }
16
+ if (element.className.toLowerCase() !== element.tag.replace(/-/g, '')) {
17
+ findings.push({
18
+ file: element.file,
19
+ position: element.classNode.name!.getStart(),
20
+ key: elementKey(element),
21
+ message: `class ${element.className} does not match <${element.tag}>`,
22
+ });
23
+ }
24
+ }
25
+ return findings;
26
+ },
27
+ };
@@ -0,0 +1,27 @@
1
+ import type { IRuleFinding, IStandardRule } from '../rule.js';
2
+
3
+ /** File and folder names under `ts_web/` are lowercase. */
4
+ export const namingLowercaseFilesRule: IStandardRule = {
5
+ id: 'naming/lowercase-files',
6
+ severity: { base: 'error' },
7
+ fixMode: 'auto',
8
+ detect: ({ project }) => {
9
+ const findings: IRuleFinding[] = [];
10
+ const lowered = new Map<string, number>();
11
+ for (const file of project.files.keys()) {
12
+ lowered.set(file.toLowerCase(), (lowered.get(file.toLowerCase()) ?? 0) + 1);
13
+ }
14
+ for (const file of project.webFiles) {
15
+ if (!/[A-Z]/.test(file)) {
16
+ continue;
17
+ }
18
+ const target = file.toLowerCase();
19
+ const finding: IRuleFinding = { file, position: 0, key: file, message: `${file} is not lowercase; expected ${target}` };
20
+ if (lowered.get(target) === 1 && !project.exists(target)) {
21
+ finding.fix = (builderArg) => builderArg.move(file, target);
22
+ }
23
+ findings.push(finding);
24
+ }
25
+ return findings;
26
+ },
27
+ };
@@ -0,0 +1,24 @@
1
+ import type { IRuleFinding, IStandardRule } from '../rule.js';
2
+ import { elementKey } from './shared.js';
3
+
4
+ /** Every tag starts with `<tagPrefix>-`. Applies to fixture elements in demo files too: they share the page. */
5
+ export const namingTagPrefixRule: IStandardRule = {
6
+ id: 'naming/tag-prefix',
7
+ severity: { base: 'error' },
8
+ fixMode: 'none',
9
+ allowlistCategories: ['published'],
10
+ detect: ({ project, config }) => {
11
+ const findings: IRuleFinding[] = [];
12
+ for (const element of project.elements) {
13
+ if (element.tag && !element.tag.startsWith(`${config.tagPrefix}-`)) {
14
+ findings.push({
15
+ file: element.file,
16
+ position: element.decoratorCall.getStart(),
17
+ key: elementKey(element),
18
+ message: `<${element.tag}> does not start with the tag prefix '${config.tagPrefix}-'`,
19
+ });
20
+ }
21
+ }
22
+ return findings;
23
+ },
24
+ };
@@ -0,0 +1,37 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import * as ast from '../helpers.ast.js';
3
+ import type { IRuleFinding, IStandardRule } from '../rule.js';
4
+ import { propertyKey } from './shared.js';
5
+
6
+ const ts = plugins.typescript;
7
+
8
+ /** Members decorated with `@property` or `@state` use the `accessor` keyword (standard decorators). */
9
+ export const propsAccessorRule: IStandardRule = {
10
+ id: 'props/accessor',
11
+ severity: { base: 'error' },
12
+ fixMode: 'auto',
13
+ detect: ({ project }) => {
14
+ const findings: IRuleFinding[] = [];
15
+ if (project.experimentalDecorators) {
16
+ return findings;
17
+ }
18
+ for (const property of project.properties) {
19
+ const member = property.member;
20
+ if (!ts.isPropertyDeclaration(member) || ast.hasModifier(member, ts.SyntaxKind.AccessorKeyword)) {
21
+ continue;
22
+ }
23
+ const finding: IRuleFinding = {
24
+ file: property.file,
25
+ position: member.name.getStart(),
26
+ key: propertyKey(property),
27
+ message: `@${property.decorator} ${property.name} is a field; standard decorators need 'accessor ${property.name}'`,
28
+ };
29
+ if (!ast.hasModifier(member, ts.SyntaxKind.DeclareKeyword)) {
30
+ const at = member.name.getStart();
31
+ finding.fix = (builderArg) => builderArg.edit(property.file, at, at, 'accessor ');
32
+ }
33
+ findings.push(finding);
34
+ }
35
+ return findings;
36
+ },
37
+ };
@@ -0,0 +1,45 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import * as ast from '../helpers.ast.js';
3
+ import type { IRuleFinding, IStandardRule } from '../rule.js';
4
+ import { addOptionFix } from './props.typed.js';
5
+ import { coreTypeOf, isArrayTypeNode, propertyKey, publicProperties } from './shared.js';
6
+
7
+ const ts = plugins.typescript;
8
+
9
+ /** An array, object literal type or `I`-prefixed interface annotation. */
10
+ const isObjectAnnotation = (typeArg: ast.TTypeNode | null): boolean => {
11
+ if (!typeArg) {
12
+ return false;
13
+ }
14
+ return isArrayTypeNode(typeArg)
15
+ || ts.isTypeLiteralNode(typeArg)
16
+ || (ts.isTypeReferenceNode(typeArg) && ts.isIdentifier(typeArg.typeName) && /^I[A-Z]/.test(typeArg.typeName.text));
17
+ };
18
+
19
+ /** A public property holding an object or array declares `type: Object`/`type: Array` or `attribute: false`. */
20
+ export const propsObjectNoAttributeRule: IStandardRule = {
21
+ id: 'props/object-no-attribute',
22
+ severity: { base: 'warn' },
23
+ fixMode: 'auto',
24
+ detect: ({ project }) => {
25
+ const findings: IRuleFinding[] = [];
26
+ for (const property of publicProperties(project)) {
27
+ if (property.dynamicOptions || !isObjectAnnotation(coreTypeOf(property))) {
28
+ continue;
29
+ }
30
+ const options = property.options;
31
+ const type = options ? ast.objectPropertyText(options, 'type') : null;
32
+ if (type === 'Object' || type === 'Array' || (options && ast.objectPropertyText(options, 'attribute') === 'false')) {
33
+ continue;
34
+ }
35
+ findings.push({
36
+ file: property.file,
37
+ position: property.call.getStart(),
38
+ key: propertyKey(property),
39
+ message: `@property ${property.name} holds an object or array but declares neither type: Object/Array nor attribute: false`,
40
+ fix: addOptionFix(property, 'attribute: false', 'last'),
41
+ });
42
+ }
43
+ return findings;
44
+ },
45
+ };