@futdevpro/dynamo-eslint 1.20.29 → 1.20.55

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 (93) hide show
  1. package/README.md +80 -0
  2. package/build/configs/ngx.d.ts +29 -0
  3. package/build/configs/ngx.d.ts.map +1 -1
  4. package/build/configs/ngx.js +57 -0
  5. package/build/configs/ngx.js.map +1 -1
  6. package/build/plugin/index.d.ts +5 -0
  7. package/build/plugin/index.d.ts.map +1 -1
  8. package/build/plugin/index.js +13 -0
  9. package/build/plugin/index.js.map +1 -1
  10. package/build/plugin/rules/angular/empty-state-guard.util.d.ts +58 -0
  11. package/build/plugin/rules/angular/empty-state-guard.util.d.ts.map +1 -0
  12. package/build/plugin/rules/angular/empty-state-guard.util.js +158 -0
  13. package/build/plugin/rules/angular/empty-state-guard.util.js.map +1 -0
  14. package/build/plugin/rules/angular/empty-state-requires-guard.d.ts +10 -0
  15. package/build/plugin/rules/angular/empty-state-requires-guard.d.ts.map +1 -0
  16. package/build/plugin/rules/angular/empty-state-requires-guard.js +61 -0
  17. package/build/plugin/rules/angular/empty-state-requires-guard.js.map +1 -0
  18. package/build/plugin/rules/angular/module-load-failed-branch.d.ts +11 -0
  19. package/build/plugin/rules/angular/module-load-failed-branch.d.ts.map +1 -0
  20. package/build/plugin/rules/angular/module-load-failed-branch.js +58 -0
  21. package/build/plugin/rules/angular/module-load-failed-branch.js.map +1 -0
  22. package/build/plugin/rules/angular/no-unlisted-inner-html.d.ts +15 -0
  23. package/build/plugin/rules/angular/no-unlisted-inner-html.d.ts.map +1 -0
  24. package/build/plugin/rules/angular/no-unlisted-inner-html.js +106 -0
  25. package/build/plugin/rules/angular/no-unlisted-inner-html.js.map +1 -0
  26. package/build/plugin/rules/angular/no-unused-component.d.ts +11 -0
  27. package/build/plugin/rules/angular/no-unused-component.d.ts.map +1 -0
  28. package/build/plugin/rules/angular/no-unused-component.js +78 -0
  29. package/build/plugin/rules/angular/no-unused-component.js.map +1 -0
  30. package/build/plugin/rules/angular/scroll-container-min-h-0.d.ts +11 -0
  31. package/build/plugin/rules/angular/scroll-container-min-h-0.d.ts.map +1 -0
  32. package/build/plugin/rules/angular/scroll-container-min-h-0.js +59 -0
  33. package/build/plugin/rules/angular/scroll-container-min-h-0.js.map +1 -0
  34. package/build/plugin/rules/angular/ui-building-blocks-baseline.interface.d.ts +15 -0
  35. package/build/plugin/rules/angular/ui-building-blocks-baseline.interface.d.ts.map +1 -0
  36. package/build/plugin/rules/angular/ui-building-blocks-baseline.interface.js +3 -0
  37. package/build/plugin/rules/angular/ui-building-blocks-baseline.interface.js.map +1 -0
  38. package/build/plugin/rules/angular/ui-building-blocks-element.interface.d.ts +19 -0
  39. package/build/plugin/rules/angular/ui-building-blocks-element.interface.d.ts.map +1 -0
  40. package/build/plugin/rules/angular/ui-building-blocks-element.interface.js +3 -0
  41. package/build/plugin/rules/angular/ui-building-blocks-element.interface.js.map +1 -0
  42. package/build/plugin/rules/angular/ui-building-blocks-rule-context.util.d.ts +28 -0
  43. package/build/plugin/rules/angular/ui-building-blocks-rule-context.util.d.ts.map +1 -0
  44. package/build/plugin/rules/angular/ui-building-blocks-rule-context.util.js +50 -0
  45. package/build/plugin/rules/angular/ui-building-blocks-rule-context.util.js.map +1 -0
  46. package/build/plugin/rules/angular/ui-building-blocks-scroll-finding.interface.d.ts +12 -0
  47. package/build/plugin/rules/angular/ui-building-blocks-scroll-finding.interface.d.ts.map +1 -0
  48. package/build/plugin/rules/angular/ui-building-blocks-scroll-finding.interface.js +3 -0
  49. package/build/plugin/rules/angular/ui-building-blocks-scroll-finding.interface.js.map +1 -0
  50. package/build/plugin/rules/angular/ui-building-blocks.util.d.ts +111 -0
  51. package/build/plugin/rules/angular/ui-building-blocks.util.d.ts.map +1 -0
  52. package/build/plugin/rules/angular/ui-building-blocks.util.js +377 -0
  53. package/build/plugin/rules/angular/ui-building-blocks.util.js.map +1 -0
  54. package/build/plugin/rules/angular/unused-component-finding.interface.d.ts +14 -0
  55. package/build/plugin/rules/angular/unused-component-finding.interface.d.ts.map +1 -0
  56. package/build/plugin/rules/angular/unused-component-finding.interface.js +3 -0
  57. package/build/plugin/rules/angular/unused-component-finding.interface.js.map +1 -0
  58. package/build/plugin/rules/angular/unused-component.util.d.ts +86 -0
  59. package/build/plugin/rules/angular/unused-component.util.d.ts.map +1 -0
  60. package/build/plugin/rules/angular/unused-component.util.js +243 -0
  61. package/build/plugin/rules/angular/unused-component.util.js.map +1 -0
  62. package/build/plugin/rules/angular/xss-surface-baseline-files.interface.d.ts +8 -0
  63. package/build/plugin/rules/angular/xss-surface-baseline-files.interface.d.ts.map +1 -0
  64. package/build/plugin/rules/angular/xss-surface-baseline-files.interface.js +3 -0
  65. package/build/plugin/rules/angular/xss-surface-baseline-files.interface.js.map +1 -0
  66. package/build/plugin/rules/angular/xss-surface-occurrences.interface.d.ts +6 -0
  67. package/build/plugin/rules/angular/xss-surface-occurrences.interface.d.ts.map +1 -0
  68. package/build/plugin/rules/angular/xss-surface-occurrences.interface.js +3 -0
  69. package/build/plugin/rules/angular/xss-surface-occurrences.interface.js.map +1 -0
  70. package/build/plugin/rules/angular/xss-surface.util.d.ts +44 -0
  71. package/build/plugin/rules/angular/xss-surface.util.d.ts.map +1 -0
  72. package/build/plugin/rules/angular/xss-surface.util.js +121 -0
  73. package/build/plugin/rules/angular/xss-surface.util.js.map +1 -0
  74. package/build/plugin/rules/prefer-interface-over-duplicate-types.d.ts.map +1 -1
  75. package/build/plugin/rules/prefer-interface-over-duplicate-types.js +31 -19
  76. package/build/plugin/rules/prefer-interface-over-duplicate-types.js.map +1 -1
  77. package/build/scripts/lint-strict.d.ts +62 -0
  78. package/build/scripts/lint-strict.d.ts.map +1 -0
  79. package/build/scripts/lint-strict.js +151 -0
  80. package/build/scripts/lint-strict.js.map +1 -0
  81. package/build/scripts/ui-building-blocks-baseline.d.ts +21 -0
  82. package/build/scripts/ui-building-blocks-baseline.d.ts.map +1 -0
  83. package/build/scripts/ui-building-blocks-baseline.js +123 -0
  84. package/build/scripts/ui-building-blocks-baseline.js.map +1 -0
  85. package/build/scripts/unused-components-baseline.d.ts +17 -0
  86. package/build/scripts/unused-components-baseline.d.ts.map +1 -0
  87. package/build/scripts/unused-components-baseline.js +62 -0
  88. package/build/scripts/unused-components-baseline.js.map +1 -0
  89. package/build/scripts/xss-surface-baseline.d.ts +17 -0
  90. package/build/scripts/xss-surface-baseline.d.ts.map +1 -0
  91. package/build/scripts/xss-surface-baseline.js +73 -0
  92. package/build/scripts/xss-surface-baseline.js.map +1 -0
  93. package/package.json +7 -3
package/README.md CHANGED
@@ -169,6 +169,64 @@ This package includes custom ESLint rules for Dynamo conventions:
169
169
  ### Type Rules
170
170
  - **`@futdevpro/dynamo/explicit-types`**: Enforces explicit type annotations on all TypeScript declarations (functions, variables, parameters, class properties, destructuring)
171
171
 
172
+ ### Security Rules
173
+ - **`@futdevpro/dynamo/no-unlisted-inner-html`** (BFR-QA-005): XSS-surface guard — reports every `[innerHTML]`
174
+ binding (`.html` and inline `.ts` templates) and every `bypassSecurityTrust…(…)` call, EXCEPT in reviewed files on
175
+ the `allow` list and up to the committed per-file **baseline**. So today's surfaces pass and every NEW one fails.
176
+ Options: `allow: string[]` (path suffixes) · `baselineFile` (default `.dynamo/xss-surface-baseline.json`) ·
177
+ `escapePipe` (path suffix of your escaping pipe — it must escape `& < > "` BEFORE producing HTML). Comments and
178
+ `*.spec.ts` files are ignored.
179
+
180
+ **Adopt it in a project** (run in the folder ESLint runs in, e.g. `client/`):
181
+ ```bash
182
+ npx dynamo-xss-surface-baseline # writes .dynamo/xss-surface-baseline.json — commit it
183
+ npx dynamo-xss-surface-baseline --check # exit 1 if the surface grew beyond the committed baseline
184
+ ```
185
+ The `ngx` preset switches the rule on (`error`) **only when that baseline file exists** — without it nothing
186
+ changes, so a dependency bump never breaks an existing lint step. Regenerate the baseline only after a reviewed
187
+ change. ⚠️ A rule-level baseline on purpose: ESLint's own `--suppress-rule` is applied only by the ESLint CLI, not
188
+ by the `ESLint` API that `ng lint` uses (measured on eslint 9.39.5).
189
+
190
+ ### UI Building-Block Rules
191
+ - **`@futdevpro/dynamo/scroll-container-min-h-0`** (BFR-QA-004 ①): a scroll container (`overflow-(y-)auto|scroll`, or
192
+ a component tag you list as a scroller) that will NOT scroll — between it and the element giving the chain its
193
+ height (`h-full`, a fixed height) a column-flex item lacks `min-h-0`, so the chain grows to the content height.
194
+ Not reported: a scroll container with its own fixed height, an outer scroll container on the way, an
195
+ `absolute`/`fixed` ancestor, a component host without classes in the template (its `host`/`:host` styles are not
196
+ visible), or no height source in the template at all. The chain is followed inside one template.
197
+ - **`@futdevpro/dynamo/module-load-failed-branch`** (BFR-QA-004 ②): a module (`…/_modules/<name>/`) whose templates
198
+ show a loading state (`…Loading…`, `inProgress_$`, `loading-skeleton`) but none of which shows a failed state
199
+ (`@if (…error|…failed…)`, a `…load-failed` / `…error-state` component, a `load…error|failed` test id). A list OF
200
+ errors (`errors`) is not a failed state.
201
+
202
+ **Adopt them in a project** (run in the folder ESLint runs in, e.g. `client/`):
203
+ ```bash
204
+ npx dynamo-ui-building-blocks-baseline # writes .dynamo/ui-building-blocks-baseline.json — commit it
205
+ npx dynamo-ui-building-blocks-baseline --scroller-tag app-pull-to-refresh # a component that IS a scroll container
206
+ npx dynamo-ui-building-blocks-baseline --check # exit 1 on a new case
207
+ ```
208
+ Exactly like the XSS guard: the `ngx` preset switches both rules on (`error`) **only when that baseline file
209
+ exists**, so a dependency bump never breaks a `--max-warnings=0` lint step. The listed scroller tags live in the
210
+ baseline's `settings`, so the rules and the generator always agree. Lower the baseline when you fix a case.
211
+
212
+ ### Unused Component Rule
213
+ - **`@futdevpro/dynamo/no-unused-component`** (BFR-QA-006, project-wide): an Angular `@Component` that is built but
214
+ used NOWHERE in the folder ESLint runs in — its selector is in no comment-free template (`.html`, or an inline
215
+ `template:` in backticks or quotes) and nothing references its class in a usage position (route, lazy route,
216
+ `dialog.open(…)`, bootstrap, `createComponent(…)`, an assignment…). NOT usages: `import … from` lines (a route can
217
+ disappear while its import stays), `imports` / `declarations` / `exports` entries, the component's own file, specs
218
+ and stories. When a reference exists only in a comment (`// X_Component,`, `<!-- <app-x> -->`) the message says a
219
+ finished feature may be switched off. Meant for **apps** — in a library every public component is "unused" inside
220
+ the package.
221
+
222
+ **Adopt it in a project** (run in the folder ESLint runs in, e.g. `client/`):
223
+ ```bash
224
+ npx dynamo-unused-components-baseline # writes .dynamo/unused-components-baseline.json — commit it
225
+ npx dynamo-unused-components-baseline --check # exit 1 on a new unused component
226
+ ```
227
+ Same gate as the other baseline guards: the `ngx` preset switches it on (`error`) **only when that file exists**.
228
+ The project scan is cached per process and re-stamped every 3 s (editor lint-on-save).
229
+
172
230
  ## 🛠️ CLI Validation Tools
173
231
 
174
232
  Use these commands to validate your codebase:
@@ -208,6 +266,28 @@ Add to your `package.json`:
208
266
  }
209
267
  ```
210
268
 
269
+ ### Strict lint gate — `dynamo-lint-strict`
270
+
271
+ A release/CI gate whose exit code comes from ESLint's **aggregated** counters (the project's own flat config):
272
+
273
+ ```bash
274
+ dynamo-lint-strict [patterns...] [--max-warnings <n>] [--json]
275
+ dynamo-lint-strict src --max-warnings 0 # zero-warning gate
276
+ dynamo-lint-strict src --max-warnings 1200 # ratchet for a known warning backlog
277
+ ```
278
+
279
+ | exit | meaning |
280
+ |---|---|
281
+ | `0` | 0 errors (parsing errors included) and warnings within the budget |
282
+ | `1` | any error, or more warnings than `--max-warnings` |
283
+ | `2` | lint could not run — config error, no file matched, crash (**never** a pass) |
284
+
285
+ - **Warning budget:** without `--max-warnings` warnings are reported but don't fail; set it explicitly for a gate.
286
+ - **`--json`:** the last stdout line is `{ errorCount, fatalErrorCount, warningCount, fileCount, maxWarnings, legacyEslintignore, passed, exitCode }`.
287
+ - **`.eslintignore` is dead in ESLint 9 flat config** — the files listed there ARE linted. The gate reports it loudly;
288
+ move the patterns into an `ignores` entry of `eslint.config.*`:
289
+ `module.exports = [ ...require('@futdevpro/dynamo-eslint/ngx'), { ignores: [ 'dist-electron/**' ] } ];`
290
+
211
291
  ### Explicit legacy boolean-name exceptions
212
292
 
213
293
  `dynamo-validate-naming` is strict by default. A consumer that must preserve an existing persisted or public
@@ -1,3 +1,32 @@
1
1
  declare const ngxBaseConfig: any;
2
2
  declare const angularEslint: any;
3
+ declare const fs: any;
4
+ declare const path: any;
5
+ /**
6
+ * BFR-QA-005 — the XSS-surface guard is switched on in a project by COMMITTING its baseline
7
+ * (`npx dynamo-xss-surface-baseline` in the folder ESLint runs in). Without the file the rule stays off: measured
8
+ * 2026-09-23, 60 `[innerHTML]` + 43 `bypassSecurityTrust` exist in 13 repos, and fatal lint steps (one with
9
+ * `--max-warnings=0`) would break on the bump. With the file: today's surfaces pass, every NEW one is an error.
10
+ */
11
+ declare const xssSurfaceGuard: Record<string, string>;
12
+ /**
13
+ * BFR-QA-004 — the UI-building-blocks guard (scroll container without a `min-h-0` chain, module with a loading state
14
+ * but no load-failed state) is switched on the same way: by COMMITTING its baseline
15
+ * (`npx dynamo-ui-building-blocks-baseline`). Measured 2026-09-24 on 29 Angular projects: 1 scroll finding in 290
16
+ * scroll containers and 19 of 94 loading modules without a failed state in 11 projects — without the file nothing
17
+ * changes, so `--max-warnings=0` lint steps do not break on the bump.
18
+ */
19
+ declare const uiBuildingBlocksGuard: Record<string, string>;
20
+ /**
21
+ * BFR-QA-003 — the empty-state guard rides on the SAME baseline file, but switches on only when that file carries the
22
+ * `emptyStatesWithoutGuard` section (i.e. it was regenerated with a version that knows the rule). A baseline written
23
+ * before the rule existed would otherwise make every current unguarded message a NEW error on the package bump.
24
+ */
25
+ declare const emptyStateGuard: Record<string, string>;
26
+ /**
27
+ * BFR-QA-006 — the built-but-unused component guard (project-wide) is switched on the same way, by COMMITTING its
28
+ * baseline (`npx dynamo-unused-components-baseline`). Measured 2026-09-24 on 35 Angular apps: 81 of 1209 components
29
+ * used nowhere (18 with a commented-out reference). Only for `.ts` (a component is declared there). Apps only.
30
+ */
31
+ declare const unusedComponentGuard: Record<string, string>;
3
32
  //# sourceMappingURL=ngx.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ngx.d.ts","sourceRoot":"","sources":["../../src/configs/ngx.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,aAAa,KAAoB,CAAC;AACxC,QAAA,MAAM,aAAa,KAA4B,CAAC"}
1
+ {"version":3,"file":"ngx.d.ts","sourceRoot":"","sources":["../../src/configs/ngx.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,aAAa,KAAoB,CAAC;AACxC,QAAA,MAAM,aAAa,KAA4B,CAAC;AAChD,QAAA,MAAM,EAAE,KAAgB,CAAC;AACzB,QAAA,MAAM,IAAI,KAAkB,CAAC;AAE7B;;;;;GAKG;AACH,QAAA,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAEtC,CAAC;AAEP;;;;;;GAMG;AACH,QAAA,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAM1C,CAAC;AAET;;;;GAIG;AACH,QAAA,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAaxC,CAAC;AAEL;;;;GAIG;AACH,QAAA,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGzC,CAAC"}
@@ -1,5 +1,55 @@
1
1
  const ngxBaseConfig = require('./base');
2
2
  const angularEslint = require('angular-eslint');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ /**
6
+ * BFR-QA-005 — the XSS-surface guard is switched on in a project by COMMITTING its baseline
7
+ * (`npx dynamo-xss-surface-baseline` in the folder ESLint runs in). Without the file the rule stays off: measured
8
+ * 2026-09-23, 60 `[innerHTML]` + 43 `bypassSecurityTrust` exist in 13 repos, and fatal lint steps (one with
9
+ * `--max-warnings=0`) would break on the bump. With the file: today's surfaces pass, every NEW one is an error.
10
+ */
11
+ const xssSurfaceGuard = fs.existsSync(path.join(process.cwd(), '.dynamo', 'xss-surface-baseline.json'))
12
+ ? { '@futdevpro/dynamo/no-unlisted-inner-html': 'error' }
13
+ : {};
14
+ /**
15
+ * BFR-QA-004 — the UI-building-blocks guard (scroll container without a `min-h-0` chain, module with a loading state
16
+ * but no load-failed state) is switched on the same way: by COMMITTING its baseline
17
+ * (`npx dynamo-ui-building-blocks-baseline`). Measured 2026-09-24 on 29 Angular projects: 1 scroll finding in 290
18
+ * scroll containers and 19 of 94 loading modules without a failed state in 11 projects — without the file nothing
19
+ * changes, so `--max-warnings=0` lint steps do not break on the bump.
20
+ */
21
+ const uiBuildingBlocksGuard = fs.existsSync(path.join(process.cwd(), '.dynamo', 'ui-building-blocks-baseline.json'))
22
+ ? {
23
+ '@futdevpro/dynamo/scroll-container-min-h-0': 'error',
24
+ '@futdevpro/dynamo/module-load-failed-branch': 'error',
25
+ }
26
+ : {};
27
+ /**
28
+ * BFR-QA-003 — the empty-state guard rides on the SAME baseline file, but switches on only when that file carries the
29
+ * `emptyStatesWithoutGuard` section (i.e. it was regenerated with a version that knows the rule). A baseline written
30
+ * before the rule existed would otherwise make every current unguarded message a NEW error on the package bump.
31
+ */
32
+ const emptyStateGuard = (() => {
33
+ const file = path.join(process.cwd(), '.dynamo', 'ui-building-blocks-baseline.json');
34
+ try {
35
+ const parsed = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : undefined;
36
+ const hasSection = typeof parsed === 'object' && parsed !== null
37
+ && typeof Reflect.get(parsed, 'emptyStatesWithoutGuard') === 'object';
38
+ return hasSection ? { '@futdevpro/dynamo/empty-state-requires-guard': 'error' } : {};
39
+ }
40
+ catch {
41
+ // an unreadable baseline is reported by the other two rules (`baselineUnreadable`) — this one stays off
42
+ return {};
43
+ }
44
+ })();
45
+ /**
46
+ * BFR-QA-006 — the built-but-unused component guard (project-wide) is switched on the same way, by COMMITTING its
47
+ * baseline (`npx dynamo-unused-components-baseline`). Measured 2026-09-24 on 35 Angular apps: 81 of 1209 components
48
+ * used nowhere (18 with a commented-out reference). Only for `.ts` (a component is declared there). Apps only.
49
+ */
50
+ const unusedComponentGuard = fs.existsSync(path.join(process.cwd(), '.dynamo', 'unused-components-baseline.json'))
51
+ ? { '@futdevpro/dynamo/no-unused-component': 'error' }
52
+ : {};
3
53
  /**
4
54
  * Rulesets For Dynamo Based Systems
5
55
  * NGX: Angular Projects
@@ -45,6 +95,10 @@ module.exports = [
45
95
  '@futdevpro/dynamo/no-async-ngoninit': 'warn',
46
96
  '@futdevpro/dynamo/no-component-direct-api': 'warn',
47
97
  '@futdevpro/dynamo/effect-signal-loop': 'warn',
98
+ ...xssSurfaceGuard,
99
+ ...uiBuildingBlocksGuard,
100
+ ...emptyStateGuard,
101
+ ...unusedComponentGuard,
48
102
  },
49
103
  },
50
104
  {
@@ -83,6 +137,9 @@ module.exports = [
83
137
  '@futdevpro/dynamo/angular-for-track-logic': 'warn',
84
138
  '@futdevpro/dynamo/no-ngif-directive': 'warn',
85
139
  '@futdevpro/dynamo/no-ngfor-directive': 'warn',
140
+ ...xssSurfaceGuard,
141
+ ...uiBuildingBlocksGuard,
142
+ ...emptyStateGuard,
86
143
  },
87
144
  },
88
145
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"ngx.js","sourceRoot":"","sources":["../../src/configs/ngx.ts"],"names":[],"mappings":"AAAA,MAAM,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AACxC,MAAM,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,OAAO,GAAG;IACf,GAAG,aAAa;IAChB;QACE,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;QAC9B,eAAe,EAAE;YACf,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,eAAe;YACnC,OAAO,EAAE;gBACP,kBAAkB;gBAClB,MAAM,EAAE,UAAU;gBAClB,QAAQ,EAAE,UAAU;gBACpB,OAAO,EAAE,UAAU;gBACnB,OAAO,EAAE,UAAU;gBACnB,MAAM,EAAE,UAAU;gBAClB,MAAM,EAAE,UAAU;gBAClB,SAAS,EAAE,UAAU;gBACrB,UAAU,EAAE,UAAU;gBACtB,MAAM,EAAE,UAAU;gBAClB,OAAO,EAAE,UAAU;gBACnB,OAAO,EAAE,UAAU;aACpB;SACF;QACD,OAAO,EAAE;YACP,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO;YAC3B,iBAAiB,EAAE,aAAa,CAAC,QAAQ;SAC1C;QACD,KAAK,EAAE;YACL,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK;YACzB,yBAAyB;YACzB,oCAAoC,EAAE,OAAO;YAC7C,oCAAoC,EAAE,OAAO;YAC7C,0CAA0C,EAAE,OAAO;YACnD,iCAAiC,EAAE,OAAO;YAC1C,kCAAkC,EAAE,OAAO;YAC3C,8CAA8C,EAAE,OAAO;YACvD,yCAAyC,EAAE,OAAO;YAClD,8CAA8C,EAAE,OAAO;YAEvD,4CAA4C;YAC5C,qCAAqC,EAAE,MAAM;YAC7C,2CAA2C,EAAE,MAAM;YACnD,sCAAsC,EAAE,MAAM;SAC/C;KACF;IACD;QACE,KAAK,EAAE,CAAC,WAAW,CAAC;QACpB,eAAe,EAAE;YACf,MAAM,EAAE,aAAa,CAAC,cAAc;YACpC,aAAa,EAAE;gBACb,WAAW,EAAE,QAAQ;gBACrB,UAAU,EAAE,QAAQ;aACrB;SACF;QACD,OAAO,EAAE;YACP,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO;YAC3B,0BAA0B,EAAE,aAAa,CAAC,cAAc;SACzD;QACD,KAAK,EAAE;YACL,wCAAwC,EAAE,OAAO;YACjD,gDAAgD,EAAE,CAAE,MAAM,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;YAClF,iCAAiC,EAAE,OAAO;YAC1C,0EAA0E;YAC1E,yEAAyE;YACzE,yEAAyE;YACzE,6DAA6D;YAC7D,4EAA4E;YAC5E,gEAAgE;YAChE,8DAA8D;YAC9D,oFAAoF;YACpF,2FAA2F;YAC3F,4EAA4E;YAC5E,6CAA6C,EAAE,CAAE,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;YAC/E,kDAAkD,EAAE,OAAO;YAC3D,2CAA2C,EAAE,OAAO;YACpD,gDAAgD,EAAE,MAAM;YAExD,kEAAkE;YAClE,8CAA8C,EAAE,MAAM;YACtD,2CAA2C,EAAE,MAAM;YACnD,qCAAqC,EAAE,MAAM;YAC7C,sCAAsC,EAAE,MAAM;SAC/C;KACF;CACF,CAAC"}
1
+ {"version":3,"file":"ngx.js","sourceRoot":"","sources":["../../src/configs/ngx.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,11 @@ declare const _default: {
24
24
  'angular-for-track-logic': import("eslint").Rule.RuleModule;
25
25
  'no-ngif-directive': import("eslint").Rule.RuleModule;
26
26
  'no-ngfor-directive': import("eslint").Rule.RuleModule;
27
+ 'no-unlisted-inner-html': import("eslint").Rule.RuleModule;
28
+ 'scroll-container-min-h-0': import("eslint").Rule.RuleModule;
29
+ 'module-load-failed-branch': import("eslint").Rule.RuleModule;
30
+ 'empty-state-requires-guard': import("eslint").Rule.RuleModule;
31
+ 'no-unused-component': import("eslint").Rule.RuleModule;
27
32
  };
28
33
  configs: {
29
34
  recommended: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,kBA0DE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,kBAkEE"}
@@ -24,6 +24,11 @@ const no_method_call_in_template_1 = tslib_1.__importDefault(require("./rules/an
24
24
  const angular_for_track_logic_1 = tslib_1.__importDefault(require("./rules/angular/angular-for-track-logic"));
25
25
  const no_ngif_directive_1 = tslib_1.__importDefault(require("./rules/angular/no-ngif-directive"));
26
26
  const no_ngfor_directive_1 = tslib_1.__importDefault(require("./rules/angular/no-ngfor-directive"));
27
+ const no_unlisted_inner_html_1 = tslib_1.__importDefault(require("./rules/angular/no-unlisted-inner-html"));
28
+ const scroll_container_min_h_0_1 = tslib_1.__importDefault(require("./rules/angular/scroll-container-min-h-0"));
29
+ const module_load_failed_branch_1 = tslib_1.__importDefault(require("./rules/angular/module-load-failed-branch"));
30
+ const empty_state_requires_guard_1 = tslib_1.__importDefault(require("./rules/angular/empty-state-requires-guard"));
31
+ const no_unused_component_1 = tslib_1.__importDefault(require("./rules/angular/no-unused-component"));
27
32
  module.exports = {
28
33
  rules: {
29
34
  'explicit-types': explicit_types_1.default,
@@ -52,6 +57,14 @@ module.exports = {
52
57
  'angular-for-track-logic': angular_for_track_logic_1.default,
53
58
  'no-ngif-directive': no_ngif_directive_1.default,
54
59
  'no-ngfor-directive': no_ngfor_directive_1.default,
60
+ // BFR-QA-005 — XSS-surface guard (baseline-aware)
61
+ 'no-unlisted-inner-html': no_unlisted_inner_html_1.default,
62
+ // BFR-QA-004 — UI building blocks guard (baseline-aware)
63
+ 'scroll-container-min-h-0': scroll_container_min_h_0_1.default,
64
+ 'module-load-failed-branch': module_load_failed_branch_1.default,
65
+ 'empty-state-requires-guard': empty_state_requires_guard_1.default,
66
+ // BFR-QA-006 — built-but-unused component guard (baseline-aware, project-wide)
67
+ 'no-unused-component': no_unused_component_1.default,
55
68
  },
56
69
  configs: {
57
70
  recommended: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":";;AAAA,oFAAuD;AACvD,uFAA0D;AAC1D,sFAAyD;AACzD,2FAA6D;AAC7D,uFAA4D;AAC5D,kIAAgF;AAChF,0GAAiE;AACjE,kHAAmE;AACnE,sHAA4E;AAC5E,sFAAwD;AAExD,+EAAiD;AACjD,iFAAmD;AACnD,+FAAiE;AACjE,iGAAkE;AAClE,2GAA4E;AAC5E,+FAAiE;AACjE,6GAA8E;AAE9E,kGAAoE;AACpE,8GAA+E;AAC/E,oGAAsE;AACtE,oHAAoF;AACpF,8GAA+E;AAC/E,kGAAoE;AACpE,oGAAsE;AAEtE,iBAAS;IACP,KAAK,EAAE;QACL,gBAAgB,EAAE,wBAAiB;QACnC,cAAc,EAAE,sBAAe;QAC/B,gBAAgB,EAAE,wBAAgB;QAClC,cAAc,EAAE,sBAAiB;QACjC,uCAAuC,EAAE,+CAAmB;QAC5D,2BAA2B,EAAE,mCAAgB;QAC7C,+BAA+B,EAAE,uCAAc;QAC/C,iCAAiC,EAAE,yCAAqB;QACxD,iBAAiB,EAAE,yBAAiB;QAEpC,iBAAiB,EAAE,yBAAkB;QAErC,gDAAgD;QAChD,YAAY,EAAE,oBAAY;QAC1B,aAAa,EAAE,qBAAa;QAC5B,oBAAoB,EAAE,4BAAoB;QAC1C,qBAAqB,EAAE,6BAAoB;QAC3C,0BAA0B,EAAE,kCAAyB;QACrD,oBAAoB,EAAE,4BAAoB;QAC1C,2BAA2B,EAAE,mCAA0B;QAEvD,kEAAkE;QAClE,mBAAmB,EAAE,2BAAmB;QACxC,yBAAyB,EAAE,iCAAwB;QACnD,oBAAoB,EAAE,4BAAoB;QAC1C,4BAA4B,EAAE,oCAA0B;QACxD,yBAAyB,EAAE,iCAAwB;QACnD,mBAAmB,EAAE,2BAAmB;QACxC,oBAAoB,EAAE,4BAAoB;KAC3C;IACD,OAAO,EAAE;QACP,WAAW,EAAE;YACX,KAAK,EAAE;gBACL,kCAAkC,EAAE,MAAM;gBAC1C,gCAAgC,EAAE,MAAM;gBACxC,kCAAkC,EAAE,MAAM;gBAC1C,gCAAgC,EAAE,MAAM;gBACxC,yDAAyD,EAAE,MAAM;gBACjE,6CAA6C,EAAE,MAAM;gBACrD,iDAAiD,EAAE,MAAM;gBACzD,mDAAmD,EAAE,MAAM;gBAC3D,mCAAmC,EAAE,MAAM;gBAE3C,mCAAmC,EAAE,MAAM;gBAE3C,gDAAgD;gBAChD,8BAA8B,EAAE,MAAM;gBACtC,+BAA+B,EAAE,MAAM;gBACvC,sCAAsC,EAAE,MAAM;gBAC9C,uCAAuC,EAAE,MAAM;gBAC/C,4CAA4C,EAAE,MAAM;gBACpD,sCAAsC,EAAE,MAAM;gBAC9C,6CAA6C,EAAE,MAAM;aACtD;SACF;KACF;CACF,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":";;AAAA,oFAAuD;AACvD,uFAA0D;AAC1D,sFAAyD;AACzD,2FAA6D;AAC7D,uFAA4D;AAC5D,kIAAgF;AAChF,0GAAiE;AACjE,kHAAmE;AACnE,sHAA4E;AAC5E,sFAAwD;AAExD,+EAAiD;AACjD,iFAAmD;AACnD,+FAAiE;AACjE,iGAAkE;AAClE,2GAA4E;AAC5E,+FAAiE;AACjE,6GAA8E;AAE9E,kGAAoE;AACpE,8GAA+E;AAC/E,oGAAsE;AACtE,oHAAoF;AACpF,8GAA+E;AAC/E,kGAAoE;AACpE,oGAAsE;AACtE,4GAA6E;AAC7E,gHAAgF;AAChF,kHAAmF;AACnF,oHAAqF;AACrF,sGAAwE;AAExE,iBAAS;IACP,KAAK,EAAE;QACL,gBAAgB,EAAE,wBAAiB;QACnC,cAAc,EAAE,sBAAe;QAC/B,gBAAgB,EAAE,wBAAgB;QAClC,cAAc,EAAE,sBAAiB;QACjC,uCAAuC,EAAE,+CAAmB;QAC5D,2BAA2B,EAAE,mCAAgB;QAC7C,+BAA+B,EAAE,uCAAc;QAC/C,iCAAiC,EAAE,yCAAqB;QACxD,iBAAiB,EAAE,yBAAiB;QAEpC,iBAAiB,EAAE,yBAAkB;QAErC,gDAAgD;QAChD,YAAY,EAAE,oBAAY;QAC1B,aAAa,EAAE,qBAAa;QAC5B,oBAAoB,EAAE,4BAAoB;QAC1C,qBAAqB,EAAE,6BAAoB;QAC3C,0BAA0B,EAAE,kCAAyB;QACrD,oBAAoB,EAAE,4BAAoB;QAC1C,2BAA2B,EAAE,mCAA0B;QAEvD,kEAAkE;QAClE,mBAAmB,EAAE,2BAAmB;QACxC,yBAAyB,EAAE,iCAAwB;QACnD,oBAAoB,EAAE,4BAAoB;QAC1C,4BAA4B,EAAE,oCAA0B;QACxD,yBAAyB,EAAE,iCAAwB;QACnD,mBAAmB,EAAE,2BAAmB;QACxC,oBAAoB,EAAE,4BAAoB;QAC1C,kDAAkD;QAClD,wBAAwB,EAAE,gCAAuB;QACjD,yDAAyD;QACzD,0BAA0B,EAAE,kCAAwB;QACpD,2BAA2B,EAAE,mCAA0B;QACvD,4BAA4B,EAAE,oCAA2B;QACzD,+EAA+E;QAC/E,qBAAqB,EAAE,6BAAqB;KAC7C;IACD,OAAO,EAAE;QACP,WAAW,EAAE;YACX,KAAK,EAAE;gBACL,kCAAkC,EAAE,MAAM;gBAC1C,gCAAgC,EAAE,MAAM;gBACxC,kCAAkC,EAAE,MAAM;gBAC1C,gCAAgC,EAAE,MAAM;gBACxC,yDAAyD,EAAE,MAAM;gBACjE,6CAA6C,EAAE,MAAM;gBACrD,iDAAiD,EAAE,MAAM;gBACzD,mDAAmD,EAAE,MAAM;gBAC3D,mCAAmC,EAAE,MAAM;gBAE3C,mCAAmC,EAAE,MAAM;gBAE3C,gDAAgD;gBAChD,8BAA8B,EAAE,MAAM;gBACtC,+BAA+B,EAAE,MAAM;gBACvC,sCAAsC,EAAE,MAAM;gBAC9C,uCAAuC,EAAE,MAAM;gBAC/C,4CAA4C,EAAE,MAAM;gBACpD,sCAAsC,EAAE,MAAM;gBAC9C,6CAA6C,EAAE,MAAM;aACtD;SACF;KACF;CACF,CAAC"}
@@ -0,0 +1,58 @@
1
+ /** One empty-state message of a file, as the BFR-QA-003 rule sees it. */
2
+ export interface EmptyStateGuard_Finding_Interface {
3
+ /** Offset in the SOURCE file (inline templates are shifted to their place in the `.ts`). */
4
+ offset: number;
5
+ /** An ancestor `@if` / `@else if` block of the SAME template is a loading / failed guard. */
6
+ ownGuard: boolean;
7
+ }
8
+ /**
9
+ * BFR-QA-003 — an empty-state message ("Nincs …" — also in an empty-state component's `message` — or a `@for … @empty` branch)
10
+ * must sit behind a loading- or failed-state guard: "empty", "loading" and "failed" are THREE states — an unguarded
11
+ * "Nincs X" also shows WHILE loading and AFTER a failed load (a false statement to the user). Measured by the QA lane on
12
+ * the organizer: 5 of 18 empty-state messages had no guard.
13
+ *
14
+ * The guard may be OWN (an ancestor `@if` / `@else if` in the same template) or the PARENT's: the component is
15
+ * rendered by a template that itself guards (the kanban case — the list's "Nincs lejárt feladat." is guarded by the
16
+ * board's `@if (tasksLoad.inProgress_$()) … @else if (failed_$())`). Logic ported from the QA lane's
17
+ * `empty-state-guard-audit.ts` v2 (parent-chain version, which removed the v1's 14 false alarms).
18
+ *
19
+ * KNOWN LIMIT (kept from the audit): a guard handed over at runtime through an `[input]` is not visible.
20
+ */
21
+ export declare class EmptyStateGuard_Util {
22
+ /** The rule id. */
23
+ static readonly ruleId: string;
24
+ /**
25
+ * An empty-state message: a Hungarian "none / not yet" sentence (also inside an empty-state component's `message`),
26
+ * or an `@empty` branch. ⚠️ The empty-state COMPONENT TAG alone does not count — measured on the organizer: the same
27
+ * `<app-a-empty-data>` is also a "nothing selected yet" placeholder ("Válasszon ki egy feladatot…"), which claims no
28
+ * emptiness. Nor does a PERMISSION denial ("Nincs jogosultságod…" — measured on the organizer's permission-denied
29
+ * page: no data is loaded there, so there is nothing to guard).
30
+ * KNOWN LIMIT: a message bound through i18n (`[message]="texts.none"`) is not visible to a template rule.
31
+ */
32
+ static readonly emptyStatePattern: RegExp;
33
+ /** A loading- or failed-state guard in a condition / template. */
34
+ static readonly guardPattern: RegExp;
35
+ /** The empty-state messages of a file, each with whether an ancestor block of its own template guards it. */
36
+ static findings(text: string, filename: string): EmptyStateGuard_Finding_Interface[];
37
+ /**
38
+ * Where an empty state ends: an `@empty` block at its closing brace, a message inside a tag's attributes at the
39
+ * tag's `>` (measured: `message="Nincs bolt"` + `description="Még nincs felvéve bolt…"` = ONE state), otherwise
40
+ * at its line end.
41
+ */
42
+ private static extentOf;
43
+ /**
44
+ * Per line: whether an ANCESTOR `@if` / `@else if` block is a guard (brace-depth tracking; the `@empty` line itself
45
+ * belongs to its `@for`, which is not a guard).
46
+ */
47
+ static enclosingGuards(lines: string[]): boolean[];
48
+ /**
49
+ * Whether the component owning `filename`'s template is rendered by a template that guards (recursively up the
50
+ * parent chain). `root` = the project root (the scan of `no-unused-component` is reused and cached).
51
+ */
52
+ static guardedByParent(filename: string, root: string): boolean;
53
+ /** The element selectors (`app-x`) of the component that owns the file's template. */
54
+ static ownSelectors(filename: string): string[];
55
+ /** Recursive parent-chain walk: a template rendering one of `selectors` guards, or ITS parents do. */
56
+ private static parentGuards;
57
+ }
58
+ //# sourceMappingURL=empty-state-guard.util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-state-guard.util.d.ts","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/empty-state-guard.util.ts"],"names":[],"mappings":"AAKA,yEAAyE;AACzE,MAAM,WAAW,iCAAiC;IAChD,4FAA4F;IAC5F,MAAM,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,oBAAoB;IAE/B,mBAAmB;IACnB,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAkD;IAEhF;;;;;;;OAOG;IACH,MAAM,CAAC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAGvC;IAEF,kEAAkE;IAClE,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CACqE;IAEzG,6GAA6G;IAC7G,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iCAAiC,EAAE;IA4BpF;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ;IAmBvB;;;OAGG;IACH,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE;IA2BlD;;;OAGG;IACH,MAAM,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAW/D,sFAAsF;IACtF,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE;IAa/C,sGAAsG;IACtG,OAAO,CAAC,MAAM,CAAC,YAAY;CAkC5B"}
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EmptyStateGuard_Util = void 0;
4
+ const fs_1 = require("fs");
5
+ const ui_building_blocks_util_1 = require("./ui-building-blocks.util");
6
+ const unused_component_util_1 = require("./unused-component.util");
7
+ /**
8
+ * BFR-QA-003 — an empty-state message ("Nincs …" — also in an empty-state component's `message` — or a `@for … @empty` branch)
9
+ * must sit behind a loading- or failed-state guard: "empty", "loading" and "failed" are THREE states — an unguarded
10
+ * "Nincs X" also shows WHILE loading and AFTER a failed load (a false statement to the user). Measured by the QA lane on
11
+ * the organizer: 5 of 18 empty-state messages had no guard.
12
+ *
13
+ * The guard may be OWN (an ancestor `@if` / `@else if` in the same template) or the PARENT's: the component is
14
+ * rendered by a template that itself guards (the kanban case — the list's "Nincs lejárt feladat." is guarded by the
15
+ * board's `@if (tasksLoad.inProgress_$()) … @else if (failed_$())`). Logic ported from the QA lane's
16
+ * `empty-state-guard-audit.ts` v2 (parent-chain version, which removed the v1's 14 false alarms).
17
+ *
18
+ * KNOWN LIMIT (kept from the audit): a guard handed over at runtime through an `[input]` is not visible.
19
+ */
20
+ class EmptyStateGuard_Util {
21
+ /** The rule id. */
22
+ static ruleId = '@futdevpro/dynamo/empty-state-requires-guard';
23
+ /**
24
+ * An empty-state message: a Hungarian "none / not yet" sentence (also inside an empty-state component's `message`),
25
+ * or an `@empty` branch. ⚠️ The empty-state COMPONENT TAG alone does not count — measured on the organizer: the same
26
+ * `<app-a-empty-data>` is also a "nothing selected yet" placeholder ("Válasszon ki egy feladatot…"), which claims no
27
+ * emptiness. Nor does a PERMISSION denial ("Nincs jogosultságod…" — measured on the organizer's permission-denied
28
+ * page: no data is loaded there, so there is nothing to guard).
29
+ * KNOWN LIMIT: a message bound through i18n (`[message]="texts.none"`) is not visible to a template rule.
30
+ */
31
+ static emptyStatePattern = new RegExp('(?:Nincs|Nincsenek|Még nincs|Még nincsenek|Meg nincs|Meg nincsenek)\\s+(?!jogosult)[a-zA-ZÀ-ſ]|'
32
+ + '@empty\\b');
33
+ /** A loading- or failed-state guard in a condition / template. */
34
+ static guardPattern = /inProgress|failed_?\$?\(|loadInProgress|isLoading|loading_?\$?\(|skeleton|load-failed|Loaded_?\$?\(/i;
35
+ /** The empty-state messages of a file, each with whether an ancestor block of its own template guards it. */
36
+ static findings(text, filename) {
37
+ const result = [];
38
+ ui_building_blocks_util_1.UiBuildingBlocks_Util.templates(text, filename).forEach((template) => {
39
+ const code = ui_building_blocks_util_1.UiBuildingBlocks_Util.blankComments(template.text);
40
+ const guarded = EmptyStateGuard_Util.enclosingGuards(code.split('\n'));
41
+ const regex = new RegExp(EmptyStateGuard_Util.emptyStatePattern.source, 'g');
42
+ // ONE finding per empty state: anything inside an `@empty` block belongs to that block
43
+ // (measured: `@empty { <app-a-empty-data message="Nincs…"> }` counted twice before)
44
+ const covered = [];
45
+ let match = regex.exec(code);
46
+ while (match !== null) {
47
+ const at = match.index;
48
+ if (!covered.some(([start, end]) => at > start && at < end)) {
49
+ const line = code.slice(0, at).split('\n').length - 1;
50
+ result.push({ offset: template.offset + at, ownGuard: guarded[line] });
51
+ covered.push([at, EmptyStateGuard_Util.extentOf(code, at, match[0])]);
52
+ }
53
+ match = regex.exec(code);
54
+ }
55
+ });
56
+ return result;
57
+ }
58
+ /**
59
+ * Where an empty state ends: an `@empty` block at its closing brace, a message inside a tag's attributes at the
60
+ * tag's `>` (measured: `message="Nincs bolt"` + `description="Még nincs felvéve bolt…"` = ONE state), otherwise
61
+ * at its line end.
62
+ */
63
+ static extentOf(code, at, matched) {
64
+ if (matched.startsWith('@empty')) {
65
+ let depth = 0;
66
+ for (let i = code.indexOf('{', at); i >= 0 && i < code.length; i++) {
67
+ depth += code[i] === '{' ? 1 : code[i] === '}' ? -1 : 0;
68
+ if (!depth) {
69
+ return i;
70
+ }
71
+ }
72
+ return code.length;
73
+ }
74
+ const inTag = code.lastIndexOf('<', at) > code.lastIndexOf('>', at);
75
+ const end = code.indexOf(inTag ? '>' : '\n', at);
76
+ return end < 0 ? code.length : end;
77
+ }
78
+ /**
79
+ * Per line: whether an ANCESTOR `@if` / `@else if` block is a guard (brace-depth tracking; the `@empty` line itself
80
+ * belongs to its `@for`, which is not a guard).
81
+ */
82
+ static enclosingGuards(lines) {
83
+ const out = lines.map(() => false);
84
+ const stack = [];
85
+ let depth = 0;
86
+ lines.forEach((line, index) => {
87
+ out[index] = stack.some((frame) => frame.guard);
88
+ const control = /@(if|else if|else|for|empty)\s*(\([^)]*\))?/.exec(line);
89
+ const opens = (line.match(/\{/g) ?? []).length;
90
+ const closes = (line.match(/\}/g) ?? []).length;
91
+ if (control && opens > 0) {
92
+ stack.push({
93
+ guard: (control[1] === 'if' || control[1] === 'else if') && EmptyStateGuard_Util.guardPattern.test(control[2] ?? ''),
94
+ depth: depth,
95
+ });
96
+ out[index] = stack.some((frame) => frame.guard);
97
+ }
98
+ depth += opens - closes;
99
+ while (stack.length && depth <= stack[stack.length - 1].depth) {
100
+ stack.pop();
101
+ }
102
+ });
103
+ return out;
104
+ }
105
+ /**
106
+ * Whether the component owning `filename`'s template is rendered by a template that guards (recursively up the
107
+ * parent chain). `root` = the project root (the scan of `no-unused-component` is reused and cached).
108
+ */
109
+ static guardedByParent(filename, root) {
110
+ const selectors = EmptyStateGuard_Util.ownSelectors(filename);
111
+ if (!selectors.length) {
112
+ return false;
113
+ }
114
+ const sources = unused_component_util_1.UnusedComponent_Util.projectIndex(root).sources;
115
+ return EmptyStateGuard_Util.parentGuards(selectors, sources, new Set());
116
+ }
117
+ /** The element selectors (`app-x`) of the component that owns the file's template. */
118
+ static ownSelectors(filename) {
119
+ const tsFile = filename.endsWith('.html') ? filename.replace(/\.html$/, '.ts') : filename;
120
+ if (!(0, fs_1.existsSync)(tsFile)) {
121
+ return [];
122
+ }
123
+ return unused_component_util_1.UnusedComponent_Util.componentsOf((0, fs_1.readFileSync)(tsFile, 'utf8'))
124
+ .flatMap((component) => component.selectors)
125
+ .map((selector) => (/^[a-z][\w-]*/.exec(selector) ?? [''])[0])
126
+ .filter((element) => element.includes('-'));
127
+ }
128
+ /** Recursive parent-chain walk: a template rendering one of `selectors` guards, or ITS parents do. */
129
+ static parentGuards(selectors, sources, seen) {
130
+ const fresh = selectors.filter((s) => !seen.has(s));
131
+ if (!fresh.length) {
132
+ return false;
133
+ }
134
+ fresh.forEach((s) => {
135
+ seen.add(s);
136
+ });
137
+ return sources.some((source) => {
138
+ const templates = ui_building_blocks_util_1.UiBuildingBlocks_Util.templates(source.text, source.file)
139
+ .map((t) => ui_building_blocks_util_1.UiBuildingBlocks_Util.blankComments(t.text)).join('\n');
140
+ if (!fresh.some((s) => new RegExp('<' + s + '(?![\\w-])').test(templates))) {
141
+ return false;
142
+ }
143
+ if (EmptyStateGuard_Util.guardPattern.test(templates)) {
144
+ return true;
145
+ }
146
+ const parentTs = source.file.endsWith('.html') ? source.file.replace(/\.html$/, '.ts') : source.file;
147
+ const parent = sources.find((s) => s.file === parentTs);
148
+ const parentSelectors = parent
149
+ ? unused_component_util_1.UnusedComponent_Util.componentsOf(parent.text).flatMap((c) => c.selectors)
150
+ .map((selector) => (/^[a-z][\w-]*/.exec(selector) ?? [''])[0])
151
+ .filter((element) => element.includes('-'))
152
+ : [];
153
+ return EmptyStateGuard_Util.parentGuards(parentSelectors, sources, seen);
154
+ });
155
+ }
156
+ }
157
+ exports.EmptyStateGuard_Util = EmptyStateGuard_Util;
158
+ //# sourceMappingURL=empty-state-guard.util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-state-guard.util.js","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/empty-state-guard.util.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { Rule } from 'eslint';
2
+ /**
3
+ * BFR-QA-003 — an empty-state message must sit behind a loading- or failed-state guard (own ancestor block, or the
4
+ * rendering parent's). Baseline-counted per file like `scroll-container-min-h-0`: the accepted count of a file is
5
+ * silent, every ADDITIONAL unguarded message is reported. Switched on by the `ngx` preset only when the project's
6
+ * UI-building-blocks baseline carries the `emptyStatesWithoutGuard` section.
7
+ */
8
+ declare const rule: Rule.RuleModule;
9
+ export default rule;
10
+ //# sourceMappingURL=empty-state-requires-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-state-requires-guard.d.ts","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/empty-state-requires-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAO9B;;;;;GAKG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAqDhB,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const empty_state_guard_util_1 = require("./empty-state-guard.util");
4
+ const ui_building_blocks_rule_context_util_1 = require("./ui-building-blocks-rule-context.util");
5
+ const ui_building_blocks_util_1 = require("./ui-building-blocks.util");
6
+ /**
7
+ * BFR-QA-003 — an empty-state message must sit behind a loading- or failed-state guard (own ancestor block, or the
8
+ * rendering parent's). Baseline-counted per file like `scroll-container-min-h-0`: the accepted count of a file is
9
+ * silent, every ADDITIONAL unguarded message is reported. Switched on by the `ngx` preset only when the project's
10
+ * UI-building-blocks baseline carries the `emptyStatesWithoutGuard` section.
11
+ */
12
+ const rule = {
13
+ meta: {
14
+ type: 'problem',
15
+ docs: {
16
+ description: 'An empty-state message ("Nincs …", a `@for … @empty` branch) needs a loading / failed guard.',
17
+ recommended: true,
18
+ },
19
+ schema: ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.schema,
20
+ messages: {
21
+ noGuard: 'This empty-state message has no loading / failed guard — not in an ancestor `@if` of this template, '
22
+ + 'and not in any template that renders this component. While the data loads, or after a failed load, the '
23
+ + 'user would read "none" — a false statement. Put it in the `@else` of a loading / failed chain (e.g. '
24
+ + '`@if (load.inProgress_$()) {…} @else if (load.failed_$()) {…} @else { … }`). ({{count}} in this file, '
25
+ + 'baseline {{baseline}}; BFR-QA-003)',
26
+ baselineUnreadable: ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.baselineUnreadableMessage,
27
+ },
28
+ },
29
+ create(context) {
30
+ const filename = context.filename ?? '';
31
+ if (!ui_building_blocks_util_1.UiBuildingBlocks_Util.isInScope(filename)) {
32
+ return {};
33
+ }
34
+ return {
35
+ Program() {
36
+ const text = context.sourceCode.text ?? '';
37
+ const own = empty_state_guard_util_1.EmptyStateGuard_Util.findings(text, filename)
38
+ .filter((finding) => !finding.ownGuard);
39
+ if (!own.length) {
40
+ return;
41
+ }
42
+ const cwd = context.cwd ?? process.cwd();
43
+ // the parent chain is looked up only when this template has unguarded messages (the project scan is cached)
44
+ if (empty_state_guard_util_1.EmptyStateGuard_Util.guardedByParent(filename, cwd)) {
45
+ return;
46
+ }
47
+ const baseline = ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.baseline(context);
48
+ const accepted = baseline.emptyStatesWithoutGuard?.[ui_building_blocks_util_1.UiBuildingBlocks_Util.relativeKey(filename, cwd)] ?? 0;
49
+ own.slice(accepted).forEach((finding) => {
50
+ context.report({
51
+ loc: ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.loc(text, finding.offset),
52
+ messageId: 'noGuard',
53
+ data: { count: String(own.length), baseline: String(accepted) },
54
+ });
55
+ });
56
+ },
57
+ };
58
+ },
59
+ };
60
+ exports.default = rule;
61
+ //# sourceMappingURL=empty-state-requires-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-state-requires-guard.js","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/empty-state-requires-guard.ts"],"names":[],"mappings":";;AAEA,qEAAmG;AAEnG,iGAA2F;AAC3F,uEAAkE;AAElE;;;;;GAKG;AACH,MAAM,IAAI,GAAoB;IAC5B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,8FAA8F;YAC3G,WAAW,EAAE,IAAI;SAClB;QACD,MAAM,EAAE,wEAAiC,CAAC,MAAM;QAChD,QAAQ,EAAE;YACR,OAAO,EAAE,sGAAsG;kBAC3G,yGAAyG;kBACzG,sGAAsG;kBACtG,wGAAwG;kBACxG,oCAAoC;YACxC,kBAAkB,EAAE,wEAAiC,CAAC,yBAAyB;SAChF;KACF;IACD,MAAM,CAAC,OAAyB;QAC9B,MAAM,QAAQ,GAAW,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;QAEhD,IAAI,CAAC,+CAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO;YACL,OAAO;gBACL,MAAM,IAAI,GAAW,OAAO,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC;gBACnD,MAAM,GAAG,GAAwC,6CAAoB,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;qBAC3F,MAAM,CAAC,CAAC,OAA0C,EAAW,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAEtF,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;oBAChB,OAAO;gBACT,CAAC;gBACD,MAAM,GAAG,GAAW,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;gBAEjD,4GAA4G;gBAC5G,IAAI,6CAAoB,CAAC,eAAe,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC;oBACxD,OAAO;gBACT,CAAC;gBACD,MAAM,QAAQ,GAAwC,wEAAiC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAC1G,MAAM,QAAQ,GACZ,QAAQ,CAAC,uBAAuB,EAAE,CAAC,+CAAqB,CAAC,WAAW,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;gBAE5F,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,OAA0C,EAAQ,EAAE;oBAC/E,OAAO,CAAC,MAAM,CAAC;wBACb,GAAG,EAAE,wEAAiC,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC;wBAChE,SAAS,EAAE,SAAS;wBACpB,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;qBAChE,CAAC,CAAC;gBACL,CAAC,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,kBAAe,IAAI,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { Rule } from 'eslint';
2
+ /**
3
+ * BFR-QA-004 ② — a module (`…/_modules/<name>/`) whose templates show a LOADING state (`…Loading…`, `inProgress_$`,
4
+ * `loading-skeleton`) must also show a FAILED state somewhere (`@if (…error|…failed…)`, a `…load-failed` /
5
+ * `…error-state` component, a `load…error|failed` test id) — "empty", "loading" and "failed" are three states; without
6
+ * the third a failed load looks like an empty screen (organizer BUG-QA-ORG-032/-034).
7
+ * Reported on each loading state of such a module, unless the module is in the committed baseline.
8
+ */
9
+ declare const rule: Rule.RuleModule;
10
+ export default rule;
11
+ //# sourceMappingURL=module-load-failed-branch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-load-failed-branch.d.ts","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/module-load-failed-branch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAM9B;;;;;;GAMG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAgDhB,CAAC;AAEF,eAAe,IAAI,CAAC"}