@futdevpro/dynamo-eslint 1.20.29 → 1.20.51
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/README.md +58 -0
- package/build/configs/ngx.d.ts +23 -0
- package/build/configs/ngx.d.ts.map +1 -1
- package/build/configs/ngx.js +37 -0
- package/build/configs/ngx.js.map +1 -1
- package/build/plugin/index.d.ts +4 -0
- package/build/plugin/index.d.ts.map +1 -1
- package/build/plugin/index.js +11 -0
- package/build/plugin/index.js.map +1 -1
- package/build/plugin/rules/angular/module-load-failed-branch.d.ts +11 -0
- package/build/plugin/rules/angular/module-load-failed-branch.d.ts.map +1 -0
- package/build/plugin/rules/angular/module-load-failed-branch.js +58 -0
- package/build/plugin/rules/angular/module-load-failed-branch.js.map +1 -0
- package/build/plugin/rules/angular/no-unlisted-inner-html.d.ts +15 -0
- package/build/plugin/rules/angular/no-unlisted-inner-html.d.ts.map +1 -0
- package/build/plugin/rules/angular/no-unlisted-inner-html.js +106 -0
- package/build/plugin/rules/angular/no-unlisted-inner-html.js.map +1 -0
- package/build/plugin/rules/angular/no-unused-component.d.ts +11 -0
- package/build/plugin/rules/angular/no-unused-component.d.ts.map +1 -0
- package/build/plugin/rules/angular/no-unused-component.js +78 -0
- package/build/plugin/rules/angular/no-unused-component.js.map +1 -0
- package/build/plugin/rules/angular/scroll-container-min-h-0.d.ts +11 -0
- package/build/plugin/rules/angular/scroll-container-min-h-0.d.ts.map +1 -0
- package/build/plugin/rules/angular/scroll-container-min-h-0.js +59 -0
- package/build/plugin/rules/angular/scroll-container-min-h-0.js.map +1 -0
- package/build/plugin/rules/angular/ui-building-blocks-baseline.interface.d.ts +10 -0
- package/build/plugin/rules/angular/ui-building-blocks-baseline.interface.d.ts.map +1 -0
- package/build/plugin/rules/angular/ui-building-blocks-baseline.interface.js +3 -0
- package/build/plugin/rules/angular/ui-building-blocks-baseline.interface.js.map +1 -0
- package/build/plugin/rules/angular/ui-building-blocks-element.interface.d.ts +14 -0
- package/build/plugin/rules/angular/ui-building-blocks-element.interface.d.ts.map +1 -0
- package/build/plugin/rules/angular/ui-building-blocks-element.interface.js +3 -0
- package/build/plugin/rules/angular/ui-building-blocks-element.interface.js.map +1 -0
- package/build/plugin/rules/angular/ui-building-blocks-rule-context.util.d.ts +28 -0
- package/build/plugin/rules/angular/ui-building-blocks-rule-context.util.d.ts.map +1 -0
- package/build/plugin/rules/angular/ui-building-blocks-rule-context.util.js +50 -0
- package/build/plugin/rules/angular/ui-building-blocks-rule-context.util.js.map +1 -0
- package/build/plugin/rules/angular/ui-building-blocks-scroll-finding.interface.d.ts +12 -0
- package/build/plugin/rules/angular/ui-building-blocks-scroll-finding.interface.d.ts.map +1 -0
- package/build/plugin/rules/angular/ui-building-blocks-scroll-finding.interface.js +3 -0
- package/build/plugin/rules/angular/ui-building-blocks-scroll-finding.interface.js.map +1 -0
- package/build/plugin/rules/angular/ui-building-blocks.util.d.ts +102 -0
- package/build/plugin/rules/angular/ui-building-blocks.util.d.ts.map +1 -0
- package/build/plugin/rules/angular/ui-building-blocks.util.js +337 -0
- package/build/plugin/rules/angular/ui-building-blocks.util.js.map +1 -0
- package/build/plugin/rules/angular/unused-component-finding.interface.d.ts +14 -0
- package/build/plugin/rules/angular/unused-component-finding.interface.d.ts.map +1 -0
- package/build/plugin/rules/angular/unused-component-finding.interface.js +3 -0
- package/build/plugin/rules/angular/unused-component-finding.interface.js.map +1 -0
- package/build/plugin/rules/angular/unused-component.util.d.ts +86 -0
- package/build/plugin/rules/angular/unused-component.util.d.ts.map +1 -0
- package/build/plugin/rules/angular/unused-component.util.js +243 -0
- package/build/plugin/rules/angular/unused-component.util.js.map +1 -0
- package/build/plugin/rules/angular/xss-surface-baseline-files.interface.d.ts +8 -0
- package/build/plugin/rules/angular/xss-surface-baseline-files.interface.d.ts.map +1 -0
- package/build/plugin/rules/angular/xss-surface-baseline-files.interface.js +3 -0
- package/build/plugin/rules/angular/xss-surface-baseline-files.interface.js.map +1 -0
- package/build/plugin/rules/angular/xss-surface-occurrences.interface.d.ts +6 -0
- package/build/plugin/rules/angular/xss-surface-occurrences.interface.d.ts.map +1 -0
- package/build/plugin/rules/angular/xss-surface-occurrences.interface.js +3 -0
- package/build/plugin/rules/angular/xss-surface-occurrences.interface.js.map +1 -0
- package/build/plugin/rules/angular/xss-surface.util.d.ts +44 -0
- package/build/plugin/rules/angular/xss-surface.util.d.ts.map +1 -0
- package/build/plugin/rules/angular/xss-surface.util.js +121 -0
- package/build/plugin/rules/angular/xss-surface.util.js.map +1 -0
- package/build/scripts/ui-building-blocks-baseline.d.ts +21 -0
- package/build/scripts/ui-building-blocks-baseline.d.ts.map +1 -0
- package/build/scripts/ui-building-blocks-baseline.js +108 -0
- package/build/scripts/ui-building-blocks-baseline.js.map +1 -0
- package/build/scripts/unused-components-baseline.d.ts +17 -0
- package/build/scripts/unused-components-baseline.d.ts.map +1 -0
- package/build/scripts/unused-components-baseline.js +62 -0
- package/build/scripts/unused-components-baseline.js.map +1 -0
- package/build/scripts/xss-surface-baseline.d.ts +17 -0
- package/build/scripts/xss-surface-baseline.d.ts.map +1 -0
- package/build/scripts/xss-surface-baseline.js +73 -0
- package/build/scripts/xss-surface-baseline.js.map +1 -0
- package/package.json +6 -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:
|
package/build/configs/ngx.d.ts
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
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-006 — the built-but-unused component guard (project-wide) is switched on the same way, by COMMITTING its
|
|
22
|
+
* baseline (`npx dynamo-unused-components-baseline`). Measured 2026-09-24 on 35 Angular apps: 81 of 1209 components
|
|
23
|
+
* used nowhere (18 with a commented-out reference). Only for `.ts` (a component is declared there). Apps only.
|
|
24
|
+
*/
|
|
25
|
+
declare const unusedComponentGuard: Record<string, string>;
|
|
3
26
|
//# 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,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGzC,CAAC"}
|
package/build/configs/ngx.js
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
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-006 — the built-but-unused component guard (project-wide) is switched on the same way, by COMMITTING its
|
|
29
|
+
* baseline (`npx dynamo-unused-components-baseline`). Measured 2026-09-24 on 35 Angular apps: 81 of 1209 components
|
|
30
|
+
* used nowhere (18 with a commented-out reference). Only for `.ts` (a component is declared there). Apps only.
|
|
31
|
+
*/
|
|
32
|
+
const unusedComponentGuard = fs.existsSync(path.join(process.cwd(), '.dynamo', 'unused-components-baseline.json'))
|
|
33
|
+
? { '@futdevpro/dynamo/no-unused-component': 'error' }
|
|
34
|
+
: {};
|
|
3
35
|
/**
|
|
4
36
|
* Rulesets For Dynamo Based Systems
|
|
5
37
|
* NGX: Angular Projects
|
|
@@ -45,6 +77,9 @@ module.exports = [
|
|
|
45
77
|
'@futdevpro/dynamo/no-async-ngoninit': 'warn',
|
|
46
78
|
'@futdevpro/dynamo/no-component-direct-api': 'warn',
|
|
47
79
|
'@futdevpro/dynamo/effect-signal-loop': 'warn',
|
|
80
|
+
...xssSurfaceGuard,
|
|
81
|
+
...uiBuildingBlocksGuard,
|
|
82
|
+
...unusedComponentGuard,
|
|
48
83
|
},
|
|
49
84
|
},
|
|
50
85
|
{
|
|
@@ -83,6 +118,8 @@ module.exports = [
|
|
|
83
118
|
'@futdevpro/dynamo/angular-for-track-logic': 'warn',
|
|
84
119
|
'@futdevpro/dynamo/no-ngif-directive': 'warn',
|
|
85
120
|
'@futdevpro/dynamo/no-ngfor-directive': 'warn',
|
|
121
|
+
...xssSurfaceGuard,
|
|
122
|
+
...uiBuildingBlocksGuard,
|
|
86
123
|
},
|
|
87
124
|
},
|
|
88
125
|
];
|
package/build/configs/ngx.js.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"ngx.js","sourceRoot":"","sources":["../../src/configs/ngx.ts"],"names":[],"mappings":"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"}
|
package/build/plugin/index.d.ts
CHANGED
|
@@ -24,6 +24,10 @@ 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
|
+
'no-unused-component': import("eslint").Rule.RuleModule;
|
|
27
31
|
};
|
|
28
32
|
configs: {
|
|
29
33
|
recommended: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,kBAiEE"}
|
package/build/plugin/index.js
CHANGED
|
@@ -24,6 +24,10 @@ 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 no_unused_component_1 = tslib_1.__importDefault(require("./rules/angular/no-unused-component"));
|
|
27
31
|
module.exports = {
|
|
28
32
|
rules: {
|
|
29
33
|
'explicit-types': explicit_types_1.default,
|
|
@@ -52,6 +56,13 @@ module.exports = {
|
|
|
52
56
|
'angular-for-track-logic': angular_for_track_logic_1.default,
|
|
53
57
|
'no-ngif-directive': no_ngif_directive_1.default,
|
|
54
58
|
'no-ngfor-directive': no_ngfor_directive_1.default,
|
|
59
|
+
// BFR-QA-005 — XSS-surface guard (baseline-aware)
|
|
60
|
+
'no-unlisted-inner-html': no_unlisted_inner_html_1.default,
|
|
61
|
+
// BFR-QA-004 — UI building blocks guard (baseline-aware)
|
|
62
|
+
'scroll-container-min-h-0': scroll_container_min_h_0_1.default,
|
|
63
|
+
'module-load-failed-branch': module_load_failed_branch_1.default,
|
|
64
|
+
// BFR-QA-006 — built-but-unused component guard (baseline-aware, project-wide)
|
|
65
|
+
'no-unused-component': no_unused_component_1.default,
|
|
55
66
|
},
|
|
56
67
|
configs: {
|
|
57
68
|
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;
|
|
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,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,+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,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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ui_building_blocks_rule_context_util_1 = require("./ui-building-blocks-rule-context.util");
|
|
4
|
+
const ui_building_blocks_util_1 = require("./ui-building-blocks.util");
|
|
5
|
+
/**
|
|
6
|
+
* BFR-QA-004 ② — a module (`…/_modules/<name>/`) whose templates show a LOADING state (`…Loading…`, `inProgress_$`,
|
|
7
|
+
* `loading-skeleton`) must also show a FAILED state somewhere (`@if (…error|…failed…)`, a `…load-failed` /
|
|
8
|
+
* `…error-state` component, a `load…error|failed` test id) — "empty", "loading" and "failed" are three states; without
|
|
9
|
+
* the third a failed load looks like an empty screen (organizer BUG-QA-ORG-032/-034).
|
|
10
|
+
* Reported on each loading state of such a module, unless the module is in the committed baseline.
|
|
11
|
+
*/
|
|
12
|
+
const rule = {
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'problem',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'A module that shows a loading state must also show a load-failed state.',
|
|
17
|
+
recommended: true,
|
|
18
|
+
},
|
|
19
|
+
schema: ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.schema,
|
|
20
|
+
messages: {
|
|
21
|
+
noFailedBranch: 'Module {{module}} shows a loading state but no template of it shows a load-FAILED state — a '
|
|
22
|
+
+ 'failed load will look like an empty screen. Add an error branch (e.g. `@if (failed_$()) { … }` with a '
|
|
23
|
+
+ 'retry). (BFR-QA-004)',
|
|
24
|
+
baselineUnreadable: ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.baselineUnreadableMessage,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
create(context) {
|
|
28
|
+
const filename = context.filename ?? '';
|
|
29
|
+
const moduleDir = ui_building_blocks_util_1.UiBuildingBlocks_Util.moduleDir(filename);
|
|
30
|
+
if (!moduleDir || !ui_building_blocks_util_1.UiBuildingBlocks_Util.isInScope(filename)) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
Program() {
|
|
35
|
+
const text = context.sourceCode.text ?? '';
|
|
36
|
+
const loading = ui_building_blocks_util_1.UiBuildingBlocks_Util.loadingOffsets(text, filename);
|
|
37
|
+
if (!loading.length) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const baseline = ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.baseline(context);
|
|
41
|
+
const moduleKey = ui_building_blocks_util_1.UiBuildingBlocks_Util.relativeKey(moduleDir, context.cwd ?? process.cwd());
|
|
42
|
+
if (baseline.modulesWithoutLoadFailedBranch.includes(moduleKey)
|
|
43
|
+
|| ui_building_blocks_util_1.UiBuildingBlocks_Util.moduleHasFailedBranch(moduleDir, { filename: filename, text: text })) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
loading.forEach((offset) => {
|
|
47
|
+
context.report({
|
|
48
|
+
loc: ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.loc(text, offset),
|
|
49
|
+
messageId: 'noFailedBranch',
|
|
50
|
+
data: { module: moduleKey },
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
exports.default = rule;
|
|
58
|
+
//# sourceMappingURL=module-load-failed-branch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-load-failed-branch.js","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/module-load-failed-branch.ts"],"names":[],"mappings":";;AAGA,iGAA2F;AAC3F,uEAAkE;AAElE;;;;;;GAMG;AACH,MAAM,IAAI,GAAoB;IAC5B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,yEAAyE;YACtF,WAAW,EAAE,IAAI;SAClB;QACD,MAAM,EAAE,wEAAiC,CAAC,MAAM;QAChD,QAAQ,EAAE;YACR,cAAc,EAAE,8FAA8F;kBAC1G,wGAAwG;kBACxG,sBAAsB;YAC1B,kBAAkB,EAAE,wEAAiC,CAAC,yBAAyB;SAChF;KACF;IACD,MAAM,CAAC,OAAyB;QAC9B,MAAM,QAAQ,GAAW,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;QAChD,MAAM,SAAS,GAAuB,+CAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAEhF,IAAI,CAAC,SAAS,IAAI,CAAC,+CAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7D,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO;YACL,OAAO;gBACL,MAAM,IAAI,GAAW,OAAO,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC;gBACnD,MAAM,OAAO,GAAa,+CAAqB,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;gBAE/E,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;oBACpB,OAAO;gBACT,CAAC;gBACD,MAAM,QAAQ,GAAwC,wEAAiC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAC1G,MAAM,SAAS,GAAW,+CAAqB,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;gBAErG,IAAI,QAAQ,CAAC,8BAA8B,CAAC,QAAQ,CAAC,SAAS,CAAC;uBAC1D,+CAAqB,CAAC,qBAAqB,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;oBAChG,OAAO;gBACT,CAAC;gBACD,OAAO,CAAC,OAAO,CAAC,CAAC,MAAc,EAAQ,EAAE;oBACvC,OAAO,CAAC,MAAM,CAAC;wBACb,GAAG,EAAE,wEAAiC,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC;wBACxD,SAAS,EAAE,gBAAgB;wBAC3B,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE;qBAC5B,CAAC,CAAC;gBACL,CAAC,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,kBAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Rule } from 'eslint';
|
|
2
|
+
/**
|
|
3
|
+
* BFR-QA-005 — XSS-surface guard: every `[innerHTML]` binding and every `DomSanitizer.bypassSecurityTrust…(…)` call is
|
|
4
|
+
* reported, EXCEPT
|
|
5
|
+
* - in a reviewed, allow-listed file (`allow`), and
|
|
6
|
+
* - up to the committed per-file BASELINE count (`.dynamo/xss-surface-baseline.json`, written by
|
|
7
|
+
* `npx dynamo-xss-surface-baseline`) — so the surfaces that exist today do not fail, only NEW ones do.
|
|
8
|
+
* Optional `escapePipe`: the project's escaping pipe must escape `& < > "` before it produces HTML.
|
|
9
|
+
*
|
|
10
|
+
* Why a rule-level baseline and not ESLint's `--suppress-rule`: the native suppressions are applied only by the ESLint
|
|
11
|
+
* CLI, NOT by the `ESLint` API that `ng lint` uses (measured on eslint 9.39.5) — Angular consumers would never see it.
|
|
12
|
+
*/
|
|
13
|
+
declare const rule: Rule.RuleModule;
|
|
14
|
+
export default rule;
|
|
15
|
+
//# sourceMappingURL=no-unlisted-inner-html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-unlisted-inner-html.d.ts","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/no-unlisted-inner-html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAgC9B;;;;;;;;;;GAUG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAsFhB,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const path = tslib_1.__importStar(require("path"));
|
|
5
|
+
const xss_surface_util_1 = require("./xss-surface.util");
|
|
6
|
+
/** Offset → { line, column } in a source text. */
|
|
7
|
+
function toLoc(text, index) {
|
|
8
|
+
const before = text.slice(0, index);
|
|
9
|
+
const line = before.split('\n').length;
|
|
10
|
+
const lastNewline = before.lastIndexOf('\n');
|
|
11
|
+
return { line: line, column: lastNewline === -1 ? index : index - lastNewline - 1 };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* BFR-QA-005 — XSS-surface guard: every `[innerHTML]` binding and every `DomSanitizer.bypassSecurityTrust…(…)` call is
|
|
15
|
+
* reported, EXCEPT
|
|
16
|
+
* - in a reviewed, allow-listed file (`allow`), and
|
|
17
|
+
* - up to the committed per-file BASELINE count (`.dynamo/xss-surface-baseline.json`, written by
|
|
18
|
+
* `npx dynamo-xss-surface-baseline`) — so the surfaces that exist today do not fail, only NEW ones do.
|
|
19
|
+
* Optional `escapePipe`: the project's escaping pipe must escape `& < > "` before it produces HTML.
|
|
20
|
+
*
|
|
21
|
+
* Why a rule-level baseline and not ESLint's `--suppress-rule`: the native suppressions are applied only by the ESLint
|
|
22
|
+
* CLI, NOT by the `ESLint` API that `ng lint` uses (measured on eslint 9.39.5) — Angular consumers would never see it.
|
|
23
|
+
*/
|
|
24
|
+
const rule = {
|
|
25
|
+
meta: {
|
|
26
|
+
type: 'problem',
|
|
27
|
+
docs: {
|
|
28
|
+
description: 'Forbid new `[innerHTML]` bindings and `bypassSecurityTrust*` calls outside the reviewed allow-list '
|
|
29
|
+
+ 'and the committed baseline (XSS surface).',
|
|
30
|
+
recommended: true,
|
|
31
|
+
},
|
|
32
|
+
schema: [
|
|
33
|
+
{
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
allow: { type: 'array', items: { type: 'string' } },
|
|
37
|
+
baselineFile: { type: 'string' },
|
|
38
|
+
escapePipe: { type: 'string' },
|
|
39
|
+
},
|
|
40
|
+
additionalProperties: false,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
messages: {
|
|
44
|
+
innerHtml: 'New `[innerHTML]` binding ({{count}} in this file, baseline {{baseline}}): a string reaches the DOM '
|
|
45
|
+
+ 'as HTML. Bind text instead, or pipe it through the escaping pipe and add the file to the reviewed `allow` '
|
|
46
|
+
+ 'list (BFR-QA-005).',
|
|
47
|
+
bypass: 'New `bypassSecurityTrust…` call ({{count}} in this file, baseline {{baseline}}): it switches Angular\'s '
|
|
48
|
+
+ 'sanitizer off for this value. Add the file to the reviewed `allow` list only after review (BFR-QA-005).',
|
|
49
|
+
escapePipe: 'The escaping pipe is not sound: {{problem}} — user text would reach `[innerHTML]` unescaped '
|
|
50
|
+
+ '(BFR-QA-005).',
|
|
51
|
+
baselineUnreadable: 'The XSS-surface baseline {{file}} cannot be read ({{reason}}) — regenerate it with '
|
|
52
|
+
+ '`npx dynamo-xss-surface-baseline`.',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
create(context) {
|
|
56
|
+
const filename = context.filename ?? '';
|
|
57
|
+
if (!xss_surface_util_1.XssSurface_Util.isInScope(filename)) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
const options = context.options[0] ?? {};
|
|
61
|
+
const cwd = context.cwd ?? process.cwd();
|
|
62
|
+
const key = xss_surface_util_1.XssSurface_Util.relativeKey(filename, cwd);
|
|
63
|
+
return {
|
|
64
|
+
Program() {
|
|
65
|
+
const text = context.sourceCode.text ?? '';
|
|
66
|
+
const first = { line: 1, column: 0 };
|
|
67
|
+
if (options.escapePipe && key.endsWith(xss_surface_util_1.XssSurface_Util.normalize(options.escapePipe))) {
|
|
68
|
+
xss_surface_util_1.XssSurface_Util.checkEscapePipe(text).forEach((problem) => {
|
|
69
|
+
context.report({ loc: { start: first, end: first }, messageId: 'escapePipe', data: { problem: problem } });
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if ((options.allow ?? []).some((suffix) => key.endsWith(xss_surface_util_1.XssSurface_Util.normalize(suffix)))) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const baselinePath = path.resolve(cwd, options.baselineFile ?? xss_surface_util_1.XssSurface_Util.defaultBaselineFile);
|
|
76
|
+
let baseline;
|
|
77
|
+
try {
|
|
78
|
+
baseline = xss_surface_util_1.XssSurface_Util.readBaseline(baselinePath);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
context.report({
|
|
82
|
+
loc: { start: first, end: first },
|
|
83
|
+
messageId: 'baselineUnreadable',
|
|
84
|
+
data: { file: baselinePath, reason: error instanceof Error ? error.message : String(error) },
|
|
85
|
+
});
|
|
86
|
+
baseline = {};
|
|
87
|
+
}
|
|
88
|
+
const found = xss_surface_util_1.XssSurface_Util.scan(text, filename);
|
|
89
|
+
const accepted = baseline[key] ?? { innerHtml: 0, bypass: 0 };
|
|
90
|
+
['innerHtml', 'bypass'].forEach((kind) => {
|
|
91
|
+
// the occurrences BEYOND the accepted count are the new ones (the later ones in the file)
|
|
92
|
+
found[kind].slice(accepted[kind]).forEach((offset) => {
|
|
93
|
+
const start = toLoc(text, offset);
|
|
94
|
+
context.report({
|
|
95
|
+
loc: { start: start, end: { line: start.line, column: start.column + 1 } },
|
|
96
|
+
messageId: kind,
|
|
97
|
+
data: { count: String(found[kind].length), baseline: String(accepted[kind]) },
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
exports.default = rule;
|
|
106
|
+
//# sourceMappingURL=no-unlisted-inner-html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-unlisted-inner-html.js","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/no-unlisted-inner-html.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Rule } from 'eslint';
|
|
2
|
+
/**
|
|
3
|
+
* BFR-QA-006 — an Angular `@Component` that is built but used NOWHERE in the project (the folder ESLint runs in):
|
|
4
|
+
* its selector is in no comment-free template and its class is referenced in no other source in a usage position
|
|
5
|
+
* (an `import` line or an `imports` / `declarations` / `exports` entry is NOT a usage). Reported on the component
|
|
6
|
+
* class, unless it is in the committed baseline (`.dynamo/unused-components-baseline.json`). The scan and its rules:
|
|
7
|
+
* `UnusedComponent_Util`. Meant for APPS — in a library every public component is "unused" inside the package.
|
|
8
|
+
*/
|
|
9
|
+
declare const rule: Rule.RuleModule;
|
|
10
|
+
export default rule;
|
|
11
|
+
//# sourceMappingURL=no-unused-component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-unused-component.d.ts","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/no-unused-component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAO9B;;;;;;GAMG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAqEhB,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const path = tslib_1.__importStar(require("path"));
|
|
5
|
+
const ui_building_blocks_rule_context_util_1 = require("./ui-building-blocks-rule-context.util");
|
|
6
|
+
const unused_component_util_1 = require("./unused-component.util");
|
|
7
|
+
/**
|
|
8
|
+
* BFR-QA-006 — an Angular `@Component` that is built but used NOWHERE in the project (the folder ESLint runs in):
|
|
9
|
+
* its selector is in no comment-free template and its class is referenced in no other source in a usage position
|
|
10
|
+
* (an `import` line or an `imports` / `declarations` / `exports` entry is NOT a usage). Reported on the component
|
|
11
|
+
* class, unless it is in the committed baseline (`.dynamo/unused-components-baseline.json`). The scan and its rules:
|
|
12
|
+
* `UnusedComponent_Util`. Meant for APPS — in a library every public component is "unused" inside the package.
|
|
13
|
+
*/
|
|
14
|
+
const rule = {
|
|
15
|
+
meta: {
|
|
16
|
+
type: 'problem',
|
|
17
|
+
docs: {
|
|
18
|
+
description: 'An Angular component must be used somewhere (template selector, route, dialog, …) — not only built.',
|
|
19
|
+
recommended: true,
|
|
20
|
+
},
|
|
21
|
+
schema: ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.schema,
|
|
22
|
+
messages: {
|
|
23
|
+
unused: '{{className}} is built but used nowhere: its selector ({{selectors}}) is in no template and nothing '
|
|
24
|
+
+ 'routes / opens / creates it (an import or an `imports`/`declarations` entry is not a usage). Wire it in, or '
|
|
25
|
+
+ 'delete it. (BFR-QA-006)',
|
|
26
|
+
unusedCommented: '{{className}} is built but used nowhere — and a reference to it is COMMENTED OUT somewhere: a '
|
|
27
|
+
+ 'finished feature may be switched off. Wire it back in, or delete it. (BFR-QA-006)',
|
|
28
|
+
baselineUnreadable: 'The unused-components baseline {{file}} cannot be read ({{reason}}) — regenerate it with '
|
|
29
|
+
+ '`npx dynamo-unused-components-baseline`.',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
create(context) {
|
|
33
|
+
const filename = context.filename ?? '';
|
|
34
|
+
const normalized = unused_component_util_1.UnusedComponent_Util.normalize(filename);
|
|
35
|
+
if (!normalized.endsWith('.ts') || normalized.endsWith('.spec.ts') || normalized.endsWith('.stories.ts')
|
|
36
|
+
|| normalized.endsWith('.d.ts')) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
Program() {
|
|
41
|
+
const text = context.sourceCode.text ?? '';
|
|
42
|
+
if (!unused_component_util_1.UnusedComponent_Util.componentsOf(text).length) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const cwd = context.cwd ?? process.cwd();
|
|
46
|
+
const options = context.options[0] ?? {};
|
|
47
|
+
const baselinePath = path.resolve(cwd, options.baselineFile ?? unused_component_util_1.UnusedComponent_Util.defaultBaselineFile);
|
|
48
|
+
let accepted = [];
|
|
49
|
+
try {
|
|
50
|
+
accepted = unused_component_util_1.UnusedComponent_Util.readBaseline(baselinePath);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
context.report({
|
|
54
|
+
loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
55
|
+
messageId: 'baselineUnreadable',
|
|
56
|
+
data: { file: baselinePath, reason: error instanceof Error ? error.message : String(error) },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const key = unused_component_util_1.UnusedComponent_Util.normalize(path.relative(cwd, filename));
|
|
60
|
+
const findings = unused_component_util_1.UnusedComponent_Util.findUnused([{ file: key, text: text }], unused_component_util_1.UnusedComponent_Util.projectIndex(cwd).index);
|
|
61
|
+
findings
|
|
62
|
+
.filter((finding) => !accepted.includes(unused_component_util_1.UnusedComponent_Util.key(finding)))
|
|
63
|
+
.forEach((finding) => {
|
|
64
|
+
context.report({
|
|
65
|
+
loc: ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.loc(text, finding.offset),
|
|
66
|
+
messageId: finding.commentedOut ? 'unusedCommented' : 'unused',
|
|
67
|
+
data: {
|
|
68
|
+
className: finding.className,
|
|
69
|
+
selectors: finding.selectors.length ? finding.selectors.join(', ') : 'none',
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
exports.default = rule;
|
|
78
|
+
//# sourceMappingURL=no-unused-component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-unused-component.js","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/no-unused-component.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Rule } from 'eslint';
|
|
2
|
+
/**
|
|
3
|
+
* BFR-QA-004 ① — a scroll container (`overflow-(y-)auto|scroll`, or a component tag listed as a scroller in the
|
|
4
|
+
* baseline `settings.scrollerTags`) that will NOT scroll: between it and the element giving the chain its height, a
|
|
5
|
+
* column-flex item lacks `min-h-0`, so the chain grows to the content height (organizer BUG-QA-ORG-029).
|
|
6
|
+
* Reported beyond the committed per-file baseline only — today's cases pass, new ones fail.
|
|
7
|
+
* The analysis and its limits: `UiBuildingBlocks_Util.scrollFindings`.
|
|
8
|
+
*/
|
|
9
|
+
declare const rule: Rule.RuleModule;
|
|
10
|
+
export default rule;
|
|
11
|
+
//# sourceMappingURL=scroll-container-min-h-0.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-container-min-h-0.d.ts","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/scroll-container-min-h-0.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAO9B;;;;;;GAMG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAiDhB,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ui_building_blocks_rule_context_util_1 = require("./ui-building-blocks-rule-context.util");
|
|
4
|
+
const ui_building_blocks_util_1 = require("./ui-building-blocks.util");
|
|
5
|
+
/**
|
|
6
|
+
* BFR-QA-004 ① — a scroll container (`overflow-(y-)auto|scroll`, or a component tag listed as a scroller in the
|
|
7
|
+
* baseline `settings.scrollerTags`) that will NOT scroll: between it and the element giving the chain its height, a
|
|
8
|
+
* column-flex item lacks `min-h-0`, so the chain grows to the content height (organizer BUG-QA-ORG-029).
|
|
9
|
+
* Reported beyond the committed per-file baseline only — today's cases pass, new ones fail.
|
|
10
|
+
* The analysis and its limits: `UiBuildingBlocks_Util.scrollFindings`.
|
|
11
|
+
*/
|
|
12
|
+
const rule = {
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'problem',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'A scroll container must not sit under a column-flex item without `min-h-0` (it would never '
|
|
17
|
+
+ 'scroll).',
|
|
18
|
+
recommended: true,
|
|
19
|
+
},
|
|
20
|
+
schema: ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.schema,
|
|
21
|
+
messages: {
|
|
22
|
+
noMinH0: 'This <{{tag}}> scroll container will not scroll: its ancestor <{{culpritTag}}> (line {{culpritLine}}) '
|
|
23
|
+
+ 'is a column-flex item without `min-h-0`, so the chain grows to the content height. Add `min-h-0` there '
|
|
24
|
+
+ '(e.g. `class="flex-1 min-h-0"`). ({{count}} in this file, baseline {{baseline}}; BFR-QA-004)',
|
|
25
|
+
baselineUnreadable: ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.baselineUnreadableMessage,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
create(context) {
|
|
29
|
+
const filename = context.filename ?? '';
|
|
30
|
+
if (!ui_building_blocks_util_1.UiBuildingBlocks_Util.isInScope(filename)) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
Program() {
|
|
35
|
+
const text = context.sourceCode.text ?? '';
|
|
36
|
+
const baseline = ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.baseline(context);
|
|
37
|
+
const key = ui_building_blocks_util_1.UiBuildingBlocks_Util.relativeKey(filename, context.cwd ?? process.cwd());
|
|
38
|
+
const findings = ui_building_blocks_util_1.UiBuildingBlocks_Util.scrollFindings(text, filename, baseline.scrollerTags);
|
|
39
|
+
const accepted = baseline.scrollContainers[key] ?? 0;
|
|
40
|
+
// the findings BEYOND the accepted count are the new ones (the later ones in the file)
|
|
41
|
+
findings.slice(accepted).forEach((finding) => {
|
|
42
|
+
context.report({
|
|
43
|
+
loc: ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.loc(text, finding.offset),
|
|
44
|
+
messageId: 'noMinH0',
|
|
45
|
+
data: {
|
|
46
|
+
tag: finding.tag,
|
|
47
|
+
culpritTag: finding.culpritTag,
|
|
48
|
+
culpritLine: String(ui_building_blocks_rule_context_util_1.UiBuildingBlocks_RuleContext_Util.line(text, finding.culpritOffset)),
|
|
49
|
+
count: String(findings.length),
|
|
50
|
+
baseline: String(accepted),
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
exports.default = rule;
|
|
59
|
+
//# sourceMappingURL=scroll-container-min-h-0.js.map
|