@futdevpro/dynamo-eslint 1.20.18 → 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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-container-min-h-0.js","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/scroll-container-min-h-0.ts"],"names":[],"mappings":";;AAGA,iGAA2F;AAE3F,uEAAkE;AAElE;;;;;;GAMG;AACH,MAAM,IAAI,GAAoB;IAC5B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,6FAA6F;kBACtG,UAAU;YACd,WAAW,EAAE,IAAI;SAClB;QACD,MAAM,EAAE,wEAAiC,CAAC,MAAM;QAChD,QAAQ,EAAE;YACR,OAAO,EAAE,wGAAwG;kBAC7G,yGAAyG;kBACzG,8FAA8F;YAClG,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,QAAQ,GAAwC,wEAAiC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAC1G,MAAM,GAAG,GAAW,+CAAqB,CAAC,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;gBAC9F,MAAM,QAAQ,GACZ,+CAAqB,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;gBAC9E,MAAM,QAAQ,GAAW,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAE7D,uFAAuF;gBACvF,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,OAAiD,EAAQ,EAAE;oBAC3F,OAAO,CAAC,MAAM,CAAC;wBACb,GAAG,EAAE,wEAAiC,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC;wBAChE,SAAS,EAAE,SAAS;wBACpB,IAAI,EAAE;4BACJ,GAAG,EAAE,OAAO,CAAC,GAAG;4BAChB,UAAU,EAAE,OAAO,CAAC,UAAU;4BAC9B,WAAW,EAAE,MAAM,CAAC,wEAAiC,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;4BACxF,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;4BAC9B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;yBAC3B;qBACF,CAAC,CAAC;gBACL,CAAC,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,kBAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The committed BFR-QA-004 baseline (`.dynamo/ui-building-blocks-baseline.json`), as the rules read it. */
|
|
2
|
+
export interface UiBuildingBlocks_Baseline_Interface {
|
|
3
|
+
/** Component tags that ARE scroll containers (their scrolling lives in their own template / CSS). */
|
|
4
|
+
scrollerTags: string[];
|
|
5
|
+
/** Accepted scroll containers without a `min-h-0` chain, per file (relative, forward slashes). */
|
|
6
|
+
scrollContainers: Record<string, number>;
|
|
7
|
+
/** Accepted modules (relative folder, e.g. `src/app/_modules/tasks`) with a loading state but no failed branch. */
|
|
8
|
+
modulesWithoutLoadFailedBranch: string[];
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=ui-building-blocks-baseline.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-building-blocks-baseline.interface.d.ts","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/ui-building-blocks-baseline.interface.ts"],"names":[],"mappings":"AAAA,4GAA4G;AAC5G,MAAM,WAAW,mCAAmC;IAClD,qGAAqG;IACrG,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,kGAAkG;IAClG,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,mHAAmH;IACnH,8BAA8B,EAAE,MAAM,EAAE,CAAC;CAC1C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-building-blocks-baseline.interface.js","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/ui-building-blocks-baseline.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** One element of a template, as the BFR-QA-004 scanner sees it (enough structure for the scroll-chain check). */
|
|
2
|
+
export interface UiBuildingBlocks_Element_Interface {
|
|
3
|
+
/** Lower-case tag name. */
|
|
4
|
+
tag: string;
|
|
5
|
+
/** Class names: static `class`, `[class.x]` bindings and quoted `[ngClass]` keys (a conditional class counts). */
|
|
6
|
+
classes: string[];
|
|
7
|
+
/** A height / max-height is set inline (`style`, `[style.height]`, `[ngStyle]`). */
|
|
8
|
+
heightStyle: boolean;
|
|
9
|
+
/** Offset of the `<` in the SOURCE file (inline templates are shifted to their place in the `.ts`). */
|
|
10
|
+
offset: number;
|
|
11
|
+
/** The enclosing element inside the same template (`undefined` at the template root). */
|
|
12
|
+
parent: UiBuildingBlocks_Element_Interface | undefined;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=ui-building-blocks-element.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-building-blocks-element.interface.d.ts","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/ui-building-blocks-element.interface.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAClH,MAAM,WAAW,kCAAkC;IACjD,2BAA2B;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,kHAAkH;IAClH,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oFAAoF;IACpF,WAAW,EAAE,OAAO,CAAC;IACrB,uGAAuG;IACvG,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,MAAM,EAAE,kCAAkC,GAAG,SAAS,CAAC;CACxD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-building-blocks-element.interface.js","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/ui-building-blocks-element.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Rule } from 'eslint';
|
|
2
|
+
import { UiBuildingBlocks_Baseline_Interface } from './ui-building-blocks-baseline.interface';
|
|
3
|
+
/** An ESLint source range. */
|
|
4
|
+
interface UiBuildingBlocks_Loc_Interface {
|
|
5
|
+
start: {
|
|
6
|
+
line: number;
|
|
7
|
+
column: number;
|
|
8
|
+
};
|
|
9
|
+
end: {
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Rule plumbing shared by `scroll-container-min-h-0` and `module-load-failed-branch` (one baseline, one reader). */
|
|
15
|
+
export declare class UiBuildingBlocks_RuleContext_Util {
|
|
16
|
+
/** The JSON schema of the shared options (`baselineFile`, relative to the ESLint working directory). */
|
|
17
|
+
static readonly schema: Rule.RuleMetaData['schema'];
|
|
18
|
+
/** The message shown when the baseline cannot be read (never a silent pass). */
|
|
19
|
+
static readonly baselineUnreadableMessage: string;
|
|
20
|
+
/** Reads the baseline; a broken one is REPORTED on the file and treated as empty. */
|
|
21
|
+
static baseline(context: Rule.RuleContext): UiBuildingBlocks_Baseline_Interface;
|
|
22
|
+
/** Offset → ESLint location (1-based line, 0-based column). */
|
|
23
|
+
static loc(text: string, offset: number): UiBuildingBlocks_Loc_Interface;
|
|
24
|
+
/** Offset → 1-based line. */
|
|
25
|
+
static line(text: string, offset: number): number;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=ui-building-blocks-rule-context.util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-building-blocks-rule-context.util.d.ts","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/ui-building-blocks-rule-context.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAG9B,OAAO,EAAE,mCAAmC,EAAE,MAAM,yCAAyC,CAAC;AAG9F,8BAA8B;AAC9B,UAAU,8BAA8B;IACtC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC;AAED,qHAAqH;AACrH,qBAAa,iCAAiC;IAE5C,wGAAwG;IACxG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAMjD;IAEF,gFAAgF;IAChF,MAAM,CAAC,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CACiC;IAElF,qFAAqF;IACrF,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,GAAG,mCAAmC;IAkB/E,+DAA+D;IAC/D,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,8BAA8B;IAQxE,6BAA6B;IAC7B,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;CAGlD"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UiBuildingBlocks_RuleContext_Util = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const path = tslib_1.__importStar(require("path"));
|
|
6
|
+
const ui_building_blocks_util_1 = require("./ui-building-blocks.util");
|
|
7
|
+
/** Rule plumbing shared by `scroll-container-min-h-0` and `module-load-failed-branch` (one baseline, one reader). */
|
|
8
|
+
class UiBuildingBlocks_RuleContext_Util {
|
|
9
|
+
/** The JSON schema of the shared options (`baselineFile`, relative to the ESLint working directory). */
|
|
10
|
+
static schema = [
|
|
11
|
+
{
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: { baselineFile: { type: 'string' } },
|
|
14
|
+
additionalProperties: false,
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
/** The message shown when the baseline cannot be read (never a silent pass). */
|
|
18
|
+
static baselineUnreadableMessage = 'The UI-building-blocks baseline {{file}} cannot be read '
|
|
19
|
+
+ '({{reason}}) — regenerate it with `npx dynamo-ui-building-blocks-baseline`.';
|
|
20
|
+
/** Reads the baseline; a broken one is REPORTED on the file and treated as empty. */
|
|
21
|
+
static baseline(context) {
|
|
22
|
+
const options = context.options[0] ?? {};
|
|
23
|
+
const cwd = context.cwd ?? process.cwd();
|
|
24
|
+
const file = path.resolve(cwd, options.baselineFile ?? ui_building_blocks_util_1.UiBuildingBlocks_Util.defaultBaselineFile);
|
|
25
|
+
try {
|
|
26
|
+
return ui_building_blocks_util_1.UiBuildingBlocks_Util.readBaseline(file);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
context.report({
|
|
30
|
+
loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
31
|
+
messageId: 'baselineUnreadable',
|
|
32
|
+
data: { file: file, reason: error instanceof Error ? error.message : String(error) },
|
|
33
|
+
});
|
|
34
|
+
return { scrollerTags: [], scrollContainers: {}, modulesWithoutLoadFailedBranch: [] };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Offset → ESLint location (1-based line, 0-based column). */
|
|
38
|
+
static loc(text, offset) {
|
|
39
|
+
const before = text.slice(0, offset);
|
|
40
|
+
const line = before.split('\n').length;
|
|
41
|
+
const column = offset - (before.lastIndexOf('\n') + 1);
|
|
42
|
+
return { start: { line: line, column: column }, end: { line: line, column: column + 1 } };
|
|
43
|
+
}
|
|
44
|
+
/** Offset → 1-based line. */
|
|
45
|
+
static line(text, offset) {
|
|
46
|
+
return text.slice(0, offset).split('\n').length;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.UiBuildingBlocks_RuleContext_Util = UiBuildingBlocks_RuleContext_Util;
|
|
50
|
+
//# sourceMappingURL=ui-building-blocks-rule-context.util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-building-blocks-rule-context.util.js","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/ui-building-blocks-rule-context.util.ts"],"names":[],"mappings":";;;;AACA,mDAA6B;AAG7B,uEAAkE;AAQlE,qHAAqH;AACrH,MAAa,iCAAiC;IAE5C,wGAAwG;IACxG,MAAM,CAAU,MAAM,GAAgC;QACpD;YACE,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;YAChD,oBAAoB,EAAE,KAAK;SAC5B;KACF,CAAC;IAEF,gFAAgF;IAChF,MAAM,CAAU,yBAAyB,GAAW,0DAA0D;UAC1G,6EAA6E,CAAC;IAElF,qFAAqF;IACrF,MAAM,CAAC,QAAQ,CAAC,OAAyB;QACvC,MAAM,OAAO,GAA8B,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACpE,MAAM,GAAG,GAAW,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACjD,MAAM,IAAI,GAAW,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,YAAY,IAAI,+CAAqB,CAAC,mBAAmB,CAAC,CAAC;QAE1G,IAAI,CAAC;YACH,OAAO,+CAAqB,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,OAAO,CAAC,MAAM,CAAC;gBACb,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE;gBACnE,SAAS,EAAE,oBAAoB;gBAC/B,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;aACrF,CAAC,CAAC;YAEH,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,8BAA8B,EAAE,EAAE,EAAE,CAAC;QACxF,CAAC;IACH,CAAC;IAED,+DAA+D;IAC/D,MAAM,CAAC,GAAG,CAAC,IAAY,EAAE,MAAc;QACrC,MAAM,MAAM,GAAW,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAW,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QAC/C,MAAM,MAAM,GAAW,MAAM,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAE/D,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,EAAE,EAAE,CAAC;IAC5F,CAAC;IAED,6BAA6B;IAC7B,MAAM,CAAC,IAAI,CAAC,IAAY,EAAE,MAAc;QACtC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IAClD,CAAC;;AA9CH,8EA+CC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** A scroll container that will not scroll: a column-flex ancestor lets the chain grow to the content height. */
|
|
2
|
+
export interface UiBuildingBlocks_ScrollFinding_Interface {
|
|
3
|
+
/** Offset of the scroll container's `<` in the source file. */
|
|
4
|
+
offset: number;
|
|
5
|
+
/** Tag of the scroll container. */
|
|
6
|
+
tag: string;
|
|
7
|
+
/** Offset of the nearest ancestor that is a column-flex item without `min-h-0`. */
|
|
8
|
+
culpritOffset: number;
|
|
9
|
+
/** Tag of that ancestor. */
|
|
10
|
+
culpritTag: string;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=ui-building-blocks-scroll-finding.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-building-blocks-scroll-finding.interface.d.ts","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/ui-building-blocks-scroll-finding.interface.ts"],"names":[],"mappings":"AAAA,iHAAiH;AACjH,MAAM,WAAW,wCAAwC;IACvD,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,mFAAmF;IACnF,aAAa,EAAE,MAAM,CAAC;IACtB,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-building-blocks-scroll-finding.interface.js","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/ui-building-blocks-scroll-finding.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { UiBuildingBlocks_Baseline_Interface } from './ui-building-blocks-baseline.interface';
|
|
2
|
+
import { UiBuildingBlocks_Element_Interface } from './ui-building-blocks-element.interface';
|
|
3
|
+
import { UiBuildingBlocks_ScrollFinding_Interface } from './ui-building-blocks-scroll-finding.interface';
|
|
4
|
+
/** A template inside a source file: its text and where it starts in the file. */
|
|
5
|
+
interface UiBuildingBlocks_Template_Interface {
|
|
6
|
+
text: string;
|
|
7
|
+
offset: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* BFR-QA-004 — the ONE scanner behind the `scroll-container-min-h-0` and `module-load-failed-branch` rules AND the
|
|
11
|
+
* `dynamo-ui-building-blocks-baseline` generator, so the baseline counts exactly what the rules count.
|
|
12
|
+
*
|
|
13
|
+
* ① Scroll container that will not scroll: a flex item's default `min-height: auto` keeps it at least as tall as its
|
|
14
|
+
* content. A scroll container (`overflow-(y-)auto|scroll`) under a COLUMN-flex item without `min-h-0` therefore sits
|
|
15
|
+
* in a chain that grows to the content height — `scrollHeight ≈ clientHeight`, it never scrolls (organizer
|
|
16
|
+
* BUG-QA-ORG-029). The check walks the scroll container's ancestors inside the template up to the element that
|
|
17
|
+
* gives the chain its height (`h-full`, a fixed height); a column-flex item on the way without `min-h-0` /
|
|
18
|
+
* `overflow-*` is the culprit. NOT a finding: a scroll container with its own fixed height (`h-64`, `max-h-96`,
|
|
19
|
+
* `h-screen`, inline height); an outer scroll container on the way (that one scrolls); an `absolute` / `fixed`
|
|
20
|
+
* ancestor (out of the flex flow); a component host without classes in the template (its `host` / `:host` styles
|
|
21
|
+
* are not visible here — mirrored: `<app-list class="flex-1">` IS checked, its flex role is written here); no
|
|
22
|
+
* height source in the template at all (then the host's parents decide).
|
|
23
|
+
* Static limit: the chain is followed inside ONE template (a component host's parents live in another one).
|
|
24
|
+
* ② Module without a load-failed branch: a module (`…/_modules/<name>/`) whose templates show a LOADING state but none
|
|
25
|
+
* of which shows a FAILED / error state — "empty", "loading" and "failed" are three different states
|
|
26
|
+
* (BUG-QA-ORG-032/-034); without the third the screen looks empty when loading failed.
|
|
27
|
+
*
|
|
28
|
+
* Templates = `.html` files and inline `template:` literals of `.ts` files; comments and interpolations are ignored.
|
|
29
|
+
*/
|
|
30
|
+
export declare class UiBuildingBlocks_Util {
|
|
31
|
+
/** The default location of the committed per-project baseline (relative to the ESLint working directory). */
|
|
32
|
+
static readonly defaultBaselineFile: string;
|
|
33
|
+
/** The rule ids the baseline belongs to. */
|
|
34
|
+
static readonly ruleIds: string[];
|
|
35
|
+
/**
|
|
36
|
+
* A loading state in a template. Deliberately NOT a bare spinner / progress bar (status indicators, determinate
|
|
37
|
+
* progress) nor `pending` (counters) — both measured as false positives on the fleet.
|
|
38
|
+
*/
|
|
39
|
+
static readonly loadingPattern: RegExp;
|
|
40
|
+
/** A failed / error state in a template (`errors` — a list OF errors — is not one). */
|
|
41
|
+
static readonly failedPattern: RegExp;
|
|
42
|
+
private static readonly voidTags;
|
|
43
|
+
private static readonly scrollClasses;
|
|
44
|
+
/** Classes that stop a flex item from growing with its content (min-height 0 or a non-visible overflow). */
|
|
45
|
+
private static readonly shrinkClasses;
|
|
46
|
+
/** Classes that give an element its own height (not relative to a growing parent). */
|
|
47
|
+
private static readonly fixedHeightClasses;
|
|
48
|
+
/** Baseline cache: path → { stamp (mtime + size: two writes in one mtime tick still differ), baseline }. */
|
|
49
|
+
private static readonly baselineCache;
|
|
50
|
+
/** Module cache: folder → { fingerprint, has a failed branch }. */
|
|
51
|
+
private static readonly moduleCache;
|
|
52
|
+
/** Whether a file is in scope (templates and component sources; tests and declarations excluded). */
|
|
53
|
+
static isInScope(filename: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The templates of a source file (`.html` = the whole file; `.ts` = its inline `template:` literals — backtick AND
|
|
56
|
+
* single / double quoted: a one-line `template: '<app-x />'` is a real template, measured on the fleet).
|
|
57
|
+
*/
|
|
58
|
+
static templates(text: string, filename: string): UiBuildingBlocks_Template_Interface[];
|
|
59
|
+
/** The elements of every template of a file, each with its in-template parent. */
|
|
60
|
+
static elements(text: string, filename: string): UiBuildingBlocks_Element_Interface[];
|
|
61
|
+
/** ① The scroll containers of a file that sit in a chain growing to the content height. */
|
|
62
|
+
static scrollFindings(text: string, filename: string, scrollerTags: string[]): UiBuildingBlocks_ScrollFinding_Interface[];
|
|
63
|
+
/** ② Offsets of the loading states in a file's templates (comments / interpolations ignored). */
|
|
64
|
+
static loadingOffsets(text: string, filename: string): number[];
|
|
65
|
+
/** ② Whether a file's templates show a failed / error state. */
|
|
66
|
+
static hasFailedBranch(text: string, filename: string): boolean;
|
|
67
|
+
/** ② The module folder of a file: the path up to the FIRST `_modules/<name>` segment (`undefined` = not in one). */
|
|
68
|
+
static moduleDir(filename: string): string | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* ② Whether any template of a module shows a failed state. `current` = the file being linted with its IN-EDITOR
|
|
71
|
+
* text (it may differ from the disk). Cached per module until a file of it changes.
|
|
72
|
+
*/
|
|
73
|
+
static moduleHasFailedBranch(moduleDir: string, current: {
|
|
74
|
+
filename: string;
|
|
75
|
+
text: string;
|
|
76
|
+
}): boolean;
|
|
77
|
+
/** ② The template-bearing files of a module folder (recursive, tests excluded). */
|
|
78
|
+
static moduleFiles(moduleDir: string): string[];
|
|
79
|
+
/** A path in baseline form: forward slashes, relative to `cwd` when inside it. */
|
|
80
|
+
static relativeKey(filename: string, cwd: string): string;
|
|
81
|
+
/** Reads the committed baseline (missing file = empty baseline; a broken file THROWS — never a silent pass). */
|
|
82
|
+
static readBaseline(baselinePath: string): UiBuildingBlocks_Baseline_Interface;
|
|
83
|
+
/** Forward slashes. */
|
|
84
|
+
static normalize(value: string): string;
|
|
85
|
+
/** A change stamp of a file: mtime AND size (a rewrite inside one mtime tick still changes the size). */
|
|
86
|
+
private static stamp;
|
|
87
|
+
/** Class names without variant prefixes (`md:overflow-auto` → `overflow-auto`). */
|
|
88
|
+
private static bases;
|
|
89
|
+
private static isScroller;
|
|
90
|
+
/** The element that bounds the chain: its own height, or `h-full` / `max-h-full` (the height of its parent). */
|
|
91
|
+
private static givesHeight;
|
|
92
|
+
private static hasFixedHeight;
|
|
93
|
+
private static isColumnFlex;
|
|
94
|
+
/** Static `class`, `[class.x]` bindings and the quoted keys of `[ngClass]`. */
|
|
95
|
+
private static classesOf;
|
|
96
|
+
/** Comments → spaces (offsets and line breaks preserved). */
|
|
97
|
+
private static blankComments;
|
|
98
|
+
/** Comments AND `{{ … }}` interpolations → spaces, so a `<` in an expression is never read as a tag. */
|
|
99
|
+
private static blankNonMarkup;
|
|
100
|
+
}
|
|
101
|
+
export {};
|
|
102
|
+
//# sourceMappingURL=ui-building-blocks.util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-building-blocks.util.d.ts","sourceRoot":"","sources":["../../../../src/plugin/rules/angular/ui-building-blocks.util.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UiBuildingBlocks_Util = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
/**
|
|
8
|
+
* BFR-QA-004 — the ONE scanner behind the `scroll-container-min-h-0` and `module-load-failed-branch` rules AND the
|
|
9
|
+
* `dynamo-ui-building-blocks-baseline` generator, so the baseline counts exactly what the rules count.
|
|
10
|
+
*
|
|
11
|
+
* ① Scroll container that will not scroll: a flex item's default `min-height: auto` keeps it at least as tall as its
|
|
12
|
+
* content. A scroll container (`overflow-(y-)auto|scroll`) under a COLUMN-flex item without `min-h-0` therefore sits
|
|
13
|
+
* in a chain that grows to the content height — `scrollHeight ≈ clientHeight`, it never scrolls (organizer
|
|
14
|
+
* BUG-QA-ORG-029). The check walks the scroll container's ancestors inside the template up to the element that
|
|
15
|
+
* gives the chain its height (`h-full`, a fixed height); a column-flex item on the way without `min-h-0` /
|
|
16
|
+
* `overflow-*` is the culprit. NOT a finding: a scroll container with its own fixed height (`h-64`, `max-h-96`,
|
|
17
|
+
* `h-screen`, inline height); an outer scroll container on the way (that one scrolls); an `absolute` / `fixed`
|
|
18
|
+
* ancestor (out of the flex flow); a component host without classes in the template (its `host` / `:host` styles
|
|
19
|
+
* are not visible here — mirrored: `<app-list class="flex-1">` IS checked, its flex role is written here); no
|
|
20
|
+
* height source in the template at all (then the host's parents decide).
|
|
21
|
+
* Static limit: the chain is followed inside ONE template (a component host's parents live in another one).
|
|
22
|
+
* ② Module without a load-failed branch: a module (`…/_modules/<name>/`) whose templates show a LOADING state but none
|
|
23
|
+
* of which shows a FAILED / error state — "empty", "loading" and "failed" are three different states
|
|
24
|
+
* (BUG-QA-ORG-032/-034); without the third the screen looks empty when loading failed.
|
|
25
|
+
*
|
|
26
|
+
* Templates = `.html` files and inline `template:` literals of `.ts` files; comments and interpolations are ignored.
|
|
27
|
+
*/
|
|
28
|
+
class UiBuildingBlocks_Util {
|
|
29
|
+
/** The default location of the committed per-project baseline (relative to the ESLint working directory). */
|
|
30
|
+
static defaultBaselineFile = '.dynamo/ui-building-blocks-baseline.json';
|
|
31
|
+
/** The rule ids the baseline belongs to. */
|
|
32
|
+
static ruleIds = [
|
|
33
|
+
'@futdevpro/dynamo/scroll-container-min-h-0',
|
|
34
|
+
'@futdevpro/dynamo/module-load-failed-branch',
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* A loading state in a template. Deliberately NOT a bare spinner / progress bar (status indicators, determinate
|
|
38
|
+
* progress) nor `pending` (counters) — both measured as false positives on the fleet.
|
|
39
|
+
*/
|
|
40
|
+
static loadingPattern = new RegExp('inProgress_\\$|loading-skeleton|'
|
|
41
|
+
+ '@(?:if|else if)\\s*\\([^)]*(?:[lL]oading|inProgress)|'
|
|
42
|
+
+ '\\*ngIf\\s*=\\s*"[^"]*(?:[lL]oading|inProgress)');
|
|
43
|
+
/** A failed / error state in a template (`errors` — a list OF errors — is not one). */
|
|
44
|
+
static failedPattern = new RegExp('@(?:if|else if)\\s*\\([^)]*(?:[eE]rror|[fF]ail)(?!s\\b)|'
|
|
45
|
+
+ '\\*ngIf\\s*=\\s*"[^"]*(?:[eE]rror|[fF]ail)(?!s\\b)|'
|
|
46
|
+
+ '<[a-z][\\w-]*(?:load-?failed|load-error|error-state)\\b|'
|
|
47
|
+
+ 'data-testid="[^"]*load[a-z-]*(?:error|failed)');
|
|
48
|
+
static voidTags = new Set([
|
|
49
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr',
|
|
50
|
+
]);
|
|
51
|
+
static scrollClasses = new Set([
|
|
52
|
+
'overflow-auto', 'overflow-y-auto', 'overflow-scroll', 'overflow-y-scroll',
|
|
53
|
+
]);
|
|
54
|
+
/** Classes that stop a flex item from growing with its content (min-height 0 or a non-visible overflow). */
|
|
55
|
+
static shrinkClasses = /^(?:min-h-0|min-h-\[0(?:px)?\]|overflow-(?:y-)?(?:auto|scroll|hidden|clip))$/;
|
|
56
|
+
/** Classes that give an element its own height (not relative to a growing parent). */
|
|
57
|
+
static fixedHeightClasses = /^(?:h|max-h)-(?!full$|auto$|fit$|min$|max$|0$)[\w.[\]()%/-]+$/;
|
|
58
|
+
/** Baseline cache: path → { stamp (mtime + size: two writes in one mtime tick still differ), baseline }. */
|
|
59
|
+
static baselineCache = new Map();
|
|
60
|
+
/** Module cache: folder → { fingerprint, has a failed branch }. */
|
|
61
|
+
static moduleCache = new Map();
|
|
62
|
+
/** Whether a file is in scope (templates and component sources; tests and declarations excluded). */
|
|
63
|
+
static isInScope(filename) {
|
|
64
|
+
const normalized = UiBuildingBlocks_Util.normalize(filename);
|
|
65
|
+
return (normalized.endsWith('.html') || normalized.endsWith('.ts'))
|
|
66
|
+
&& !normalized.endsWith('.spec.ts')
|
|
67
|
+
&& !normalized.endsWith('.d.ts');
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The templates of a source file (`.html` = the whole file; `.ts` = its inline `template:` literals — backtick AND
|
|
71
|
+
* single / double quoted: a one-line `template: '<app-x />'` is a real template, measured on the fleet).
|
|
72
|
+
*/
|
|
73
|
+
static templates(text, filename) {
|
|
74
|
+
if (filename.endsWith('.html')) {
|
|
75
|
+
return [{ text: text, offset: 0 }];
|
|
76
|
+
}
|
|
77
|
+
const result = [];
|
|
78
|
+
const regex = /\btemplate\s*:\s*(?:`((?:\\[\s\S]|[^`\\])*)`|'((?:\\.|[^'\\\n])*)'|"((?:\\.|[^"\\\n])*)")/g;
|
|
79
|
+
let match = regex.exec(text);
|
|
80
|
+
while (match !== null) {
|
|
81
|
+
const body = match[1] ?? match[2] ?? match[3] ?? '';
|
|
82
|
+
result.push({ text: body, offset: match.index + match[0].length - body.length - 1 });
|
|
83
|
+
match = regex.exec(text);
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
/** The elements of every template of a file, each with its in-template parent. */
|
|
88
|
+
static elements(text, filename) {
|
|
89
|
+
const result = [];
|
|
90
|
+
UiBuildingBlocks_Util.templates(text, filename).forEach((template) => {
|
|
91
|
+
const code = UiBuildingBlocks_Util.blankNonMarkup(template.text);
|
|
92
|
+
const stack = [];
|
|
93
|
+
const tagRegex = /<(\/?)([a-zA-Z][\w.:-]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/g;
|
|
94
|
+
let match = tagRegex.exec(code);
|
|
95
|
+
while (match !== null) {
|
|
96
|
+
const tag = match[2].toLowerCase();
|
|
97
|
+
if (match[1]) {
|
|
98
|
+
// closing tag: pop to the matching open element (tolerant of unclosed children)
|
|
99
|
+
const index = stack.map((e) => e.tag).lastIndexOf(tag);
|
|
100
|
+
if (index >= 0) {
|
|
101
|
+
stack.length = index;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
const attributes = match[3];
|
|
106
|
+
const element = {
|
|
107
|
+
tag: tag,
|
|
108
|
+
classes: UiBuildingBlocks_Util.classesOf(attributes),
|
|
109
|
+
heightStyle: /(?:^|\s)style\s*=\s*["'][^"']*\bheight\s*:|\[style\.(?:max-)?height|\[ngStyle\][^>]*height/i
|
|
110
|
+
.test(attributes),
|
|
111
|
+
offset: template.offset + match.index,
|
|
112
|
+
parent: stack[stack.length - 1],
|
|
113
|
+
};
|
|
114
|
+
result.push(element);
|
|
115
|
+
if (!attributes.trim().endsWith('/') && !UiBuildingBlocks_Util.voidTags.has(tag)) {
|
|
116
|
+
stack.push(element);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
match = tagRegex.exec(code);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
/** ① The scroll containers of a file that sit in a chain growing to the content height. */
|
|
125
|
+
static scrollFindings(text, filename, scrollerTags) {
|
|
126
|
+
const tags = new Set(scrollerTags.map((t) => t.toLowerCase()));
|
|
127
|
+
const findings = [];
|
|
128
|
+
UiBuildingBlocks_Util.elements(text, filename).forEach((element) => {
|
|
129
|
+
if (!UiBuildingBlocks_Util.isScroller(element, tags) || UiBuildingBlocks_Util.hasFixedHeight(element)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// walk up to the element that gives the chain its height; a column-flex item on the way must be able to shrink
|
|
133
|
+
let culprit;
|
|
134
|
+
let ancestor = element.parent;
|
|
135
|
+
while (ancestor) {
|
|
136
|
+
if (UiBuildingBlocks_Util.isScroller(ancestor, tags)) {
|
|
137
|
+
// an outer scroll container takes over: the page still scrolls, just not in the inner one
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (UiBuildingBlocks_Util.givesHeight(ancestor)) {
|
|
141
|
+
if (culprit) {
|
|
142
|
+
findings.push({
|
|
143
|
+
offset: element.offset, tag: element.tag, culpritOffset: culprit.offset, culpritTag: culprit.tag,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const bases = UiBuildingBlocks_Util.bases(ancestor);
|
|
149
|
+
if (bases.includes('absolute') || bases.includes('fixed')) {
|
|
150
|
+
// out of the flex flow: its height comes from the containing block, not from this chain
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const isColumnFlexItem = !!ancestor.parent && UiBuildingBlocks_Util.isColumnFlex(ancestor.parent);
|
|
154
|
+
const canShrink = bases.some((c) => UiBuildingBlocks_Util.shrinkClasses.test(c));
|
|
155
|
+
// a component host without classes in this template is sized by its own `host` / `:host` — not visible here
|
|
156
|
+
const sizedHere = !ancestor.tag.includes('-') || ancestor.classes.length > 0;
|
|
157
|
+
if (isColumnFlexItem && !canShrink && sizedHere && !culprit) {
|
|
158
|
+
culprit = ancestor;
|
|
159
|
+
}
|
|
160
|
+
ancestor = ancestor.parent;
|
|
161
|
+
}
|
|
162
|
+
// no height source inside the template: the chain is decided by the host's parents — not decidable here
|
|
163
|
+
});
|
|
164
|
+
return findings;
|
|
165
|
+
}
|
|
166
|
+
/** ② Offsets of the loading states in a file's templates (comments / interpolations ignored). */
|
|
167
|
+
static loadingOffsets(text, filename) {
|
|
168
|
+
const result = [];
|
|
169
|
+
UiBuildingBlocks_Util.templates(text, filename).forEach((template) => {
|
|
170
|
+
const code = UiBuildingBlocks_Util.blankComments(template.text);
|
|
171
|
+
const regex = new RegExp(UiBuildingBlocks_Util.loadingPattern.source, 'g');
|
|
172
|
+
let match = regex.exec(code);
|
|
173
|
+
while (match !== null) {
|
|
174
|
+
result.push(template.offset + match.index);
|
|
175
|
+
match = regex.exec(code);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
/** ② Whether a file's templates show a failed / error state. */
|
|
181
|
+
static hasFailedBranch(text, filename) {
|
|
182
|
+
return UiBuildingBlocks_Util.templates(text, filename).some((template) => UiBuildingBlocks_Util.failedPattern.test(UiBuildingBlocks_Util.blankComments(template.text)));
|
|
183
|
+
}
|
|
184
|
+
/** ② The module folder of a file: the path up to the FIRST `_modules/<name>` segment (`undefined` = not in one). */
|
|
185
|
+
static moduleDir(filename) {
|
|
186
|
+
const normalized = UiBuildingBlocks_Util.normalize(filename);
|
|
187
|
+
const match = /^(.*?\/_modules\/[^/]+)\//.exec(normalized);
|
|
188
|
+
return match ? match[1] : undefined;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* ② Whether any template of a module shows a failed state. `current` = the file being linted with its IN-EDITOR
|
|
192
|
+
* text (it may differ from the disk). Cached per module until a file of it changes.
|
|
193
|
+
*/
|
|
194
|
+
static moduleHasFailedBranch(moduleDir, current) {
|
|
195
|
+
if (UiBuildingBlocks_Util.hasFailedBranch(current.text, current.filename)) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
const currentFile = UiBuildingBlocks_Util.normalize(current.filename).toLowerCase();
|
|
199
|
+
const files = UiBuildingBlocks_Util.moduleFiles(moduleDir)
|
|
200
|
+
.filter((f) => UiBuildingBlocks_Util.normalize(f).toLowerCase() !== currentFile);
|
|
201
|
+
const fingerprint = files.map((f) => f + ':' + UiBuildingBlocks_Util.stamp(f)).join('|');
|
|
202
|
+
const cached = UiBuildingBlocks_Util.moduleCache.get(moduleDir + '|' + currentFile);
|
|
203
|
+
if (cached?.fingerprint === fingerprint) {
|
|
204
|
+
return cached.hasFailedBranch;
|
|
205
|
+
}
|
|
206
|
+
const hasFailedBranch = files.some((f) => UiBuildingBlocks_Util.hasFailedBranch((0, fs_1.readFileSync)(f, 'utf8'), f));
|
|
207
|
+
UiBuildingBlocks_Util.moduleCache.set(moduleDir + '|' + currentFile, { fingerprint: fingerprint, hasFailedBranch: hasFailedBranch });
|
|
208
|
+
return hasFailedBranch;
|
|
209
|
+
}
|
|
210
|
+
/** ② The template-bearing files of a module folder (recursive, tests excluded). */
|
|
211
|
+
static moduleFiles(moduleDir) {
|
|
212
|
+
const result = [];
|
|
213
|
+
const walk = (dir) => {
|
|
214
|
+
(0, fs_1.readdirSync)(dir, { withFileTypes: true }).forEach((entry) => {
|
|
215
|
+
const full = path.join(dir, entry.name);
|
|
216
|
+
if (entry.isDirectory()) {
|
|
217
|
+
if (entry.name !== 'node_modules') {
|
|
218
|
+
walk(full);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else if (UiBuildingBlocks_Util.isInScope(full)) {
|
|
222
|
+
result.push(full);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
if ((0, fs_1.existsSync)(moduleDir)) {
|
|
227
|
+
walk(moduleDir);
|
|
228
|
+
}
|
|
229
|
+
return result.sort();
|
|
230
|
+
}
|
|
231
|
+
/** A path in baseline form: forward slashes, relative to `cwd` when inside it. */
|
|
232
|
+
static relativeKey(filename, cwd) {
|
|
233
|
+
const file = UiBuildingBlocks_Util.normalize(filename);
|
|
234
|
+
const root = UiBuildingBlocks_Util.normalize(cwd).replace(/\/$/, '');
|
|
235
|
+
return file.toLowerCase().startsWith(root.toLowerCase() + '/') ? file.slice(root.length + 1) : file;
|
|
236
|
+
}
|
|
237
|
+
/** Reads the committed baseline (missing file = empty baseline; a broken file THROWS — never a silent pass). */
|
|
238
|
+
static readBaseline(baselinePath) {
|
|
239
|
+
if (!(0, fs_1.existsSync)(baselinePath)) {
|
|
240
|
+
return { scrollerTags: [], scrollContainers: {}, modulesWithoutLoadFailedBranch: [] };
|
|
241
|
+
}
|
|
242
|
+
const stamp = UiBuildingBlocks_Util.stamp(baselinePath);
|
|
243
|
+
const cached = UiBuildingBlocks_Util.baselineCache.get(baselinePath);
|
|
244
|
+
if (cached?.stamp === stamp) {
|
|
245
|
+
return cached.baseline;
|
|
246
|
+
}
|
|
247
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(baselinePath, 'utf8'));
|
|
248
|
+
const field = (name) => (typeof parsed === 'object' && parsed !== null ? Reflect.get(parsed, name) : undefined);
|
|
249
|
+
const settings = field('settings');
|
|
250
|
+
const scrollContainers = field('scrollContainers');
|
|
251
|
+
const modules = field('modulesWithoutLoadFailedBranch');
|
|
252
|
+
if (!scrollContainers || typeof scrollContainers !== 'object' || !Array.isArray(modules)) {
|
|
253
|
+
throw new Error(`UI-building-blocks baseline ${baselinePath} has no "scrollContainers" object / `
|
|
254
|
+
+ '"modulesWithoutLoadFailedBranch" array — regenerate it with `npx dynamo-ui-building-blocks-baseline`.');
|
|
255
|
+
}
|
|
256
|
+
const tags = settings && typeof settings === 'object' ? Reflect.get(settings, 'scrollerTags') : undefined;
|
|
257
|
+
const baseline = {
|
|
258
|
+
scrollerTags: Array.isArray(tags) ? tags.filter((t) => typeof t === 'string') : [],
|
|
259
|
+
scrollContainers: {},
|
|
260
|
+
modulesWithoutLoadFailedBranch: modules.filter((m) => typeof m === 'string')
|
|
261
|
+
.map((m) => UiBuildingBlocks_Util.normalize(m)),
|
|
262
|
+
};
|
|
263
|
+
Object.entries(scrollContainers).forEach(([key, value]) => {
|
|
264
|
+
baseline.scrollContainers[UiBuildingBlocks_Util.normalize(key)] =
|
|
265
|
+
typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : 0;
|
|
266
|
+
});
|
|
267
|
+
UiBuildingBlocks_Util.baselineCache.set(baselinePath, { stamp: stamp, baseline: baseline });
|
|
268
|
+
return baseline;
|
|
269
|
+
}
|
|
270
|
+
/** Forward slashes. */
|
|
271
|
+
static normalize(value) {
|
|
272
|
+
return value.replace(/\\/g, '/');
|
|
273
|
+
}
|
|
274
|
+
/** A change stamp of a file: mtime AND size (a rewrite inside one mtime tick still changes the size). */
|
|
275
|
+
static stamp(file) {
|
|
276
|
+
const stat = (0, fs_1.statSync)(file);
|
|
277
|
+
return stat.mtimeMs + ':' + stat.size;
|
|
278
|
+
}
|
|
279
|
+
/** Class names without variant prefixes (`md:overflow-auto` → `overflow-auto`). */
|
|
280
|
+
static bases(element) {
|
|
281
|
+
return element.classes.map((c) => c.slice(c.lastIndexOf(':') + 1));
|
|
282
|
+
}
|
|
283
|
+
static isScroller(element, tags) {
|
|
284
|
+
return tags.has(element.tag)
|
|
285
|
+
|| UiBuildingBlocks_Util.bases(element).some((c) => UiBuildingBlocks_Util.scrollClasses.has(c));
|
|
286
|
+
}
|
|
287
|
+
/** The element that bounds the chain: its own height, or `h-full` / `max-h-full` (the height of its parent). */
|
|
288
|
+
static givesHeight(element) {
|
|
289
|
+
return UiBuildingBlocks_Util.hasFixedHeight(element)
|
|
290
|
+
|| UiBuildingBlocks_Util.bases(element).some((c) => c === 'h-full' || c === 'max-h-full' || c === 'inset-0' || c === 'inset-y-0');
|
|
291
|
+
}
|
|
292
|
+
static hasFixedHeight(element) {
|
|
293
|
+
return element.heightStyle
|
|
294
|
+
|| UiBuildingBlocks_Util.bases(element).some((c) => UiBuildingBlocks_Util.fixedHeightClasses.test(c));
|
|
295
|
+
}
|
|
296
|
+
static isColumnFlex(element) {
|
|
297
|
+
const bases = UiBuildingBlocks_Util.bases(element);
|
|
298
|
+
return (bases.includes('flex') || bases.includes('inline-flex'))
|
|
299
|
+
&& (bases.includes('flex-col') || bases.includes('flex-col-reverse'));
|
|
300
|
+
}
|
|
301
|
+
/** Static `class`, `[class.x]` bindings and the quoted keys of `[ngClass]`. */
|
|
302
|
+
static classesOf(attributes) {
|
|
303
|
+
const result = [];
|
|
304
|
+
const staticClass = /(?:^|\s)class\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(attributes);
|
|
305
|
+
if (staticClass) {
|
|
306
|
+
result.push(...(staticClass[1] ?? staticClass[2]).split(/\s+/).filter(Boolean));
|
|
307
|
+
}
|
|
308
|
+
const bindingRegex = /\[class\.([^\]=\s]+)\]/g;
|
|
309
|
+
let binding = bindingRegex.exec(attributes);
|
|
310
|
+
while (binding !== null) {
|
|
311
|
+
result.push(binding[1]);
|
|
312
|
+
binding = bindingRegex.exec(attributes);
|
|
313
|
+
}
|
|
314
|
+
const ngClass = /\[ngClass\]\s*=\s*"([^"]*)"/.exec(attributes);
|
|
315
|
+
if (ngClass) {
|
|
316
|
+
(ngClass[1].match(/'[^']*'/g) ?? []).forEach((quoted) => {
|
|
317
|
+
result.push(...quoted.slice(1, -1).split(/\s+/).filter(Boolean));
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
/** Comments → spaces (offsets and line breaks preserved). */
|
|
323
|
+
static blankComments(text) {
|
|
324
|
+
return text.replace(/<!--[\s\S]*?-->/g, (match) => match.replace(/[^\n]/g, ' '));
|
|
325
|
+
}
|
|
326
|
+
/** Comments AND `{{ … }}` interpolations → spaces, so a `<` in an expression is never read as a tag. */
|
|
327
|
+
static blankNonMarkup(text) {
|
|
328
|
+
/** An expression becomes spaces — offsets and line breaks stay where they were. */
|
|
329
|
+
const blank = (match) => match.replace(/[^\n]/g, ' ');
|
|
330
|
+
return UiBuildingBlocks_Util.blankComments(text)
|
|
331
|
+
.replace(/\{\{[\s\S]*?\}\}/g, blank)
|
|
332
|
+
// control-flow conditions (`@if (count<limit) {`), one level of nested parentheses
|
|
333
|
+
.replace(/@\w+(?:\s+if)?\s*\((?:[^()]|\([^()]*\))*\)/g, blank);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
exports.UiBuildingBlocks_Util = UiBuildingBlocks_Util;
|
|
337
|
+
//# sourceMappingURL=ui-building-blocks.util.js.map
|