@webpieces/code-rules 0.4.671 → 0.4.673
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/code-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.673",
|
|
4
4
|
"description": "Standalone code validation rules extracted from architecture-validators, no Nx dependency required",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"directory": "packages/tooling/code-rules"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@webpieces/rules-config": "0.4.
|
|
19
|
+
"@webpieces/rules-config": "0.4.673",
|
|
20
20
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
21
21
|
"inversify": "7.10.4",
|
|
22
22
|
"reflect-metadata": "0.2.2"
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
* ESCAPE HATCH (a genuinely dynamic runtime value):
|
|
17
17
|
* .ts: // webpieces-disable no-custom-css -- <reason>
|
|
18
18
|
* .html: <!-- webpieces-disable no-custom-css -- <reason> -->
|
|
19
|
+
*
|
|
20
|
+
* PATH EXEMPTION: `allowGlobs` in the rule's webpieces.config.json entry (a vendored kit copied verbatim
|
|
21
|
+
* with its own CSS, a generated artifact). It is applied by the shared `NoCustomCssScope`, the SAME class
|
|
22
|
+
* the edit-time hook rule consults — so the editor and CI agree on which paths the rule looks at.
|
|
19
23
|
*/
|
|
20
24
|
import { NoCustomCssConfig } from '@webpieces/rules-config';
|
|
21
25
|
import { CodeValidator, ExecutorResult } from './code-validator';
|
|
@@ -34,9 +38,17 @@ export declare class CssHit {
|
|
|
34
38
|
constructor(line: number, column: number, detail: string, hasDisableComment: boolean);
|
|
35
39
|
}
|
|
36
40
|
export declare class NoCustomCssValidator extends CodeValidator<NoCustomCssConfig> {
|
|
41
|
+
private readonly pathScope;
|
|
37
42
|
constructor(config: NoCustomCssConfig);
|
|
38
43
|
run(workspaceRoot: string): Promise<ExecutorResult>;
|
|
39
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* A changed file this rule cares about: an Angular .ts/.tsx or .html that the shared
|
|
46
|
+
* {@link NoCustomCssScope} does not exempt (a test source, or a configured `allowGlobs` match).
|
|
47
|
+
*
|
|
48
|
+
* This is the ONE place the changed-file set is narrowed, which is what makes the exemption
|
|
49
|
+
* impossible to apply in one mode and forget in the other — both `NEW_AND_MODIFIED_CODE` and
|
|
50
|
+
* `NEW_AND_MODIFIED_FILES` run over the list this predicate produced.
|
|
51
|
+
*/
|
|
40
52
|
isRelevantFile(file: string): boolean;
|
|
41
53
|
/** All banned-CSS hits in a single file (dispatches to the .ts AST or the .html line scan). */
|
|
42
54
|
findHitsForFile(file: string, workspaceRoot: string): CssHit[];
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
* ESCAPE HATCH (a genuinely dynamic runtime value):
|
|
18
18
|
* .ts: // webpieces-disable no-custom-css -- <reason>
|
|
19
19
|
* .html: <!-- webpieces-disable no-custom-css -- <reason> -->
|
|
20
|
+
*
|
|
21
|
+
* PATH EXEMPTION: `allowGlobs` in the rule's webpieces.config.json entry (a vendored kit copied verbatim
|
|
22
|
+
* with its own CSS, a generated artifact). It is applied by the shared `NoCustomCssScope`, the SAME class
|
|
23
|
+
* the edit-time hook rule consults — so the editor and CI agree on which paths the rule looks at.
|
|
20
24
|
*/
|
|
21
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
26
|
exports.NoCustomCssValidator = exports.CssHit = exports.CssViolation = void 0;
|
|
@@ -30,7 +34,6 @@ const inversify_1 = require("inversify");
|
|
|
30
34
|
const resolve_mode_1 = require("./resolve-mode");
|
|
31
35
|
// @Component decorator properties that inject hand-written CSS.
|
|
32
36
|
const STYLE_PROPS = new Set(['styles', 'styleUrls', 'styleUrl']);
|
|
33
|
-
const TEST_PATHS = [/\.test\.ts$/, /\.spec\.ts$/, /__tests__\//];
|
|
34
37
|
// Template patterns. `[style.width]="x"` is preceded by `[`, so the inline-style regex's
|
|
35
38
|
// `(^|\s)style=` boundary keeps it apart from the binding forms.
|
|
36
39
|
const RE_INLINE_STYLE = /(^|\s)style\s*=\s*["']/;
|
|
@@ -63,8 +66,12 @@ class CssHit {
|
|
|
63
66
|
}
|
|
64
67
|
exports.CssHit = CssHit;
|
|
65
68
|
let NoCustomCssValidator = class NoCustomCssValidator extends code_validator_1.CodeValidator {
|
|
69
|
+
// The SAME exemption the edit-time hook rule applies per file — one class in @webpieces/rules-config,
|
|
70
|
+
// so `allowGlobs` cannot be honoured by the editor and ignored here (which is what it used to do).
|
|
71
|
+
pathScope;
|
|
66
72
|
constructor(config) {
|
|
67
73
|
super(config, 'no-custom-css', 'no-custom-css');
|
|
74
|
+
this.pathScope = new rules_config_1.NoCustomCssScope(config);
|
|
68
75
|
}
|
|
69
76
|
async run(workspaceRoot) {
|
|
70
77
|
const opts = this.config;
|
|
@@ -104,9 +111,16 @@ let NoCustomCssValidator = class NoCustomCssValidator extends code_validator_1.C
|
|
|
104
111
|
this.reportViolations(violations, mode);
|
|
105
112
|
return { success: false };
|
|
106
113
|
}
|
|
107
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* A changed file this rule cares about: an Angular .ts/.tsx or .html that the shared
|
|
116
|
+
* {@link NoCustomCssScope} does not exempt (a test source, or a configured `allowGlobs` match).
|
|
117
|
+
*
|
|
118
|
+
* This is the ONE place the changed-file set is narrowed, which is what makes the exemption
|
|
119
|
+
* impossible to apply in one mode and forget in the other — both `NEW_AND_MODIFIED_CODE` and
|
|
120
|
+
* `NEW_AND_MODIFIED_FILES` run over the list this predicate produced.
|
|
121
|
+
*/
|
|
108
122
|
isRelevantFile(file) {
|
|
109
|
-
if (
|
|
123
|
+
if (this.pathScope.isExempt(file))
|
|
110
124
|
return false;
|
|
111
125
|
return file.endsWith('.html') || file.endsWith('.ts') || file.endsWith('.tsx');
|
|
112
126
|
}
|
|
@@ -247,6 +261,10 @@ let NoCustomCssValidator = class NoCustomCssValidator extends code_validator_1.C
|
|
|
247
261
|
console.error(' To fix: delete the CSS and use Tailwind utility classes (arbitrary values like');
|
|
248
262
|
console.error(' `bg-[#fffde7]` / `grid-cols-[2fr_2fr_2fr_48px]` cover the long tail).');
|
|
249
263
|
console.error('');
|
|
264
|
+
console.error(' Whole tree not yours to restyle (a vendored kit, a generated artifact)?');
|
|
265
|
+
console.error(' Add a glob to no-custom-css.allowGlobs in webpieces.config.json — it exempts the');
|
|
266
|
+
console.error(' path here and in the editor hook alike.');
|
|
267
|
+
console.error('');
|
|
250
268
|
console.error(' Escape hatch (genuinely dynamic runtime value, use sparingly):');
|
|
251
269
|
console.error(' .ts // webpieces-disable no-custom-css -- <reason>');
|
|
252
270
|
console.error(' .html <!-- webpieces-disable no-custom-css -- <reason> -->');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-no-custom-css.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-no-custom-css.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate No Custom CSS Executor\n *\n * Enforces Tailwind-first styling in Angular sources by banning hand-written CSS on CHANGED code:\n * - `.ts` — `styles:` / `styleUrls:` / `styleUrl:` inside an `@Component({...})` decorator\n * (detected via the TypeScript AST, so only the real decorator triggers it).\n * - `.html` — a static `style=\"…\"` attribute, a `[style]` / `[style.x]` binding, or `[ngStyle]`\n * (line/regex scan — Angular templates have no TS AST).\n *\n * Unlike an ESLint rule (which lints whole files), this is diff-scoped: NEW_AND_MODIFIED_CODE only\n * flags violations on changed lines, so a legacy Angular app is never retroactively flooded — the\n * rule bites when a file is next edited.\n *\n * MODES: OFF | NEW_AND_MODIFIED_CODE (changed lines) | NEW_AND_MODIFIED_FILES (all in changed files).\n *\n * ESCAPE HATCH (a genuinely dynamic runtime value):\n * .ts: // webpieces-disable no-custom-css -- <reason>\n * .html: <!-- webpieces-disable no-custom-css -- <reason> -->\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport {\n hasDisable,\n RULE_NAMES,\n NoCustomCssConfig,\n ModifiedCodeMode,\n detectBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n} from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { shouldSkipRule } from './resolve-mode';\n\n// @Component decorator properties that inject hand-written CSS.\nconst STYLE_PROPS = new Set(['styles', 'styleUrls', 'styleUrl']);\nconst TEST_PATHS: RegExp[] = [/\\.test\\.ts$/, /\\.spec\\.ts$/, /__tests__\\//];\n\n// Template patterns. `[style.width]=\"x\"` is preceded by `[`, so the inline-style regex's\n// `(^|\\s)style=` boundary keeps it apart from the binding forms.\nconst RE_INLINE_STYLE = /(^|\\s)style\\s*=\\s*[\"']/;\nconst RE_NG_STYLE = /\\[?ngStyle\\]?\\s*=/;\nconst RE_STYLE_BINDING = /\\[style(?:\\.[\\w.-]+)?\\]/;\n\nexport class CssViolation {\n constructor(\n readonly file: string,\n readonly line: number,\n readonly column: number,\n readonly detail: string,\n ) {}\n}\n\nexport class CssHit {\n constructor(\n readonly line: number,\n readonly column: number,\n readonly detail: string,\n readonly hasDisableComment: boolean,\n ) {}\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class NoCustomCssValidator extends CodeValidator<NoCustomCssConfig> {\n constructor(config: NoCustomCssConfig) {\n super(config, 'no-custom-css', 'no-custom-css');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n const opts = this.config;\n const mode = this.resolveMode(opts.mode ?? 'OFF', opts.turnOffRuleUntilEpoch, opts.turnOffRuleWhileOnBranch ?? undefined);\n const disableAllowed = opts.disableAllowed ?? true;\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping no-custom-css validation (mode: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Validating No Custom CSS (Tailwind-first)\\n');\n console.log(` Mode: ${mode}`);\n\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n if (!base) {\n console.log('\\n⏭️ Skipping no-custom-css validation (could not detect base branch)\\n');\n return { success: true };\n }\n }\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}\\n`);\n\n // tsOnly:false so changed .html templates are included; then filter to relevant files.\n const changedFiles = getChangedFiles(workspaceRoot, base, head, { tsOnly: false }).filter((f: string) => this.isRelevantFile(f));\n if (changedFiles.length === 0) {\n console.log('✅ No Angular .ts/.html files changed');\n return { success: true };\n }\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n const violations =\n mode === 'NEW_AND_MODIFIED_CODE'\n ? this.findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed)\n : this.findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);\n\n if (violations.length === 0) {\n console.log('✅ No custom CSS found');\n return { success: true };\n }\n this.reportViolations(violations, mode);\n return { success: false };\n }\n\n /** A changed file this rule cares about: an Angular .ts/.tsx or .html, excluding test files. */\n isRelevantFile(file: string): boolean {\n if (TEST_PATHS.some((re: RegExp) => re.test(file))) return false;\n return file.endsWith('.html') || file.endsWith('.ts') || file.endsWith('.tsx');\n }\n\n /** All banned-CSS hits in a single file (dispatches to the .ts AST or the .html line scan). */\n findHitsForFile(file: string, workspaceRoot: string): CssHit[] {\n if (file.endsWith('.html')) return this.findCustomCssInHtmlFile(file, workspaceRoot);\n if (file.endsWith('.ts') || file.endsWith('.tsx')) return this.findCustomCssInTsFile(file, workspaceRoot);\n return [];\n }\n\n /** NEW_AND_MODIFIED_CODE: only hits whose line is in the diff hunks. */\n private findViolationsForModifiedCode(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n head: string | undefined,\n disableAllowed: boolean,\n ): CssViolation[] {\n const violations: CssViolation[] = [];\n for (const file of changedFiles) {\n const changedLines = getChangedLineNumbers(getFileDiff(workspaceRoot, file, base, head));\n if (changedLines.size === 0) continue;\n for (const h of this.findHitsForFile(file, workspaceRoot)) {\n if (disableAllowed && h.hasDisableComment) continue;\n if (!changedLines.has(h.line)) continue;\n violations.push(new CssViolation(file, h.line, h.column, h.detail));\n }\n }\n return violations;\n }\n\n /** NEW_AND_MODIFIED_FILES: all hits in any changed file. */\n private findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: string[], disableAllowed: boolean): CssViolation[] {\n const violations: CssViolation[] = [];\n for (const file of changedFiles) {\n for (const h of this.findHitsForFile(file, workspaceRoot)) {\n if (disableAllowed && h.hasDisableComment) continue;\n violations.push(new CssViolation(file, h.line, h.column, h.detail));\n }\n }\n return violations;\n }\n\n /** Flag styles:/styleUrls:/styleUrl: properties inside an `@Component({...})` decorator. */\n private findCustomCssInTsFile(filePath: string, workspaceRoot: string): CssHit[] {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n const content = fs.readFileSync(fullPath, 'utf-8');\n const fileLines = content.split('\\n');\n const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n const hits: CssHit[] = [];\n this.collectTsHits(sourceFile, sourceFile, fileLines, hits);\n return hits;\n }\n\n /** Recurse the AST; every `@Component(...)` decorator contributes its banned style props. */\n private collectTsHits(node: ts.Node, sourceFile: ts.SourceFile, fileLines: string[], hits: CssHit[]): void {\n if (ts.isDecorator(node)) this.collectComponentStyleProps(node, sourceFile, fileLines, hits);\n ts.forEachChild(node, (child: ts.Node) => this.collectTsHits(child, sourceFile, fileLines, hits));\n }\n\n /** Pull the banned style props out of a single `@Component(...)` decorator node. */\n private collectComponentStyleProps(node: ts.Decorator, sourceFile: ts.SourceFile, fileLines: string[], hits: CssHit[]): void {\n const expr = node.expression;\n if (!ts.isCallExpression(expr) || !ts.isIdentifier(expr.expression) || expr.expression.text !== 'Component') return;\n const arg = expr.arguments[0];\n if (!arg || !ts.isObjectLiteralExpression(arg)) return;\n for (const prop of arg.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n const name = prop.name.text;\n if (!STYLE_PROPS.has(name)) continue;\n const pos = sourceFile.getLineAndCharacterOfPosition(prop.getStart(sourceFile));\n const line = pos.line + 1;\n hits.push(new CssHit(line, pos.character + 1, `\\`${name}\\` block in @Component`, this.hasDisableComment(fileLines, line)));\n }\n }\n\n /** Flag inline style=, [style.x]/[style], and [ngStyle] in an Angular template via a line scan. */\n private findCustomCssInHtmlFile(filePath: string, workspaceRoot: string): CssHit[] {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n const fileLines = fs.readFileSync(fullPath, 'utf-8').split('\\n');\n const hits: CssHit[] = [];\n for (let i = 0; i < fileLines.length; i++) {\n const detail = this.detailForTemplateLine(fileLines[i] ?? '');\n if (!detail) continue;\n const line = i + 1;\n hits.push(new CssHit(line, 1, detail, this.hasDisableComment(fileLines, line)));\n }\n return hits;\n }\n\n /** The banned-pattern label for a template line, or '' when the line is clean. */\n private detailForTemplateLine(raw: string): string {\n if (RE_NG_STYLE.test(raw)) return 'inline `[ngStyle]`';\n if (RE_STYLE_BINDING.test(raw)) return 'inline `[style.x]` binding';\n if (RE_INLINE_STYLE.test(raw)) return 'inline `style=` attribute';\n return '';\n }\n\n /** True if a `webpieces-disable no-custom-css` marker sits on this line or up to 3 lines above. */\n private hasDisableComment(lines: string[], lineNumber: number): boolean {\n const start = Math.max(0, lineNumber - 4);\n for (let i = lineNumber - 1; i >= start; i--) {\n if (hasDisable(lines[i] ?? '', RULE_NAMES.NO_CUSTOM_CSS)) return true;\n }\n return false;\n }\n\n private resolveMode(normalMode: ModifiedCodeMode, epoch: number | undefined, branchPattern: string | undefined): ModifiedCodeMode {\n if (normalMode === 'OFF') return normalMode;\n const skip = shouldSkipRule(epoch, branchPattern);\n if (skip.skip) {\n console.log(`\\n⏭️ Skipping no-custom-css validation (${skip.reason})\\n`);\n return 'OFF';\n }\n return normalMode;\n }\n\n private reportViolations(violations: CssViolation[], mode: ModifiedCodeMode): void {\n console.error('');\n console.error('❌ Custom CSS found in Angular source! Style with Tailwind utility classes instead.');\n console.error('');\n console.error('📚 Tailwind-first keeps styling in the template, greppable and consistent:');\n console.error(' BAD: styles: [\".card { display: flex; gap: 1rem; }\"]');\n console.error(' GOOD: class=\"flex gap-4\"');\n console.error(' BAD: <div style=\"width: 240px\"> | [style.width] | [ngStyle]');\n console.error(' GOOD: <div class=\"w-[240px]\"> | [class.x] toggles');\n console.error('');\n for (const v of violations) {\n console.error(` ❌ ${v.file}:${v.line}:${v.column}`);\n console.error(` ${v.detail}`);\n }\n console.error('');\n console.error(' To fix: delete the CSS and use Tailwind utility classes (arbitrary values like');\n console.error(' `bg-[#fffde7]` / `grid-cols-[2fr_2fr_2fr_48px]` cover the long tail).');\n console.error('');\n console.error(' Escape hatch (genuinely dynamic runtime value, use sparingly):');\n console.error(' .ts // webpieces-disable no-custom-css -- <reason>');\n console.error(' .html <!-- webpieces-disable no-custom-css -- <reason> -->');\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"validate-no-custom-css.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-no-custom-css.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;;;;AAEH,+CAAyB;AACzB,mDAA6B;AAC7B,uDAAiC;AACjC,0DAUiC;AACjC,qDAAiE;AACjE,yCAA2D;AAC3D,iDAAgD;AAEhD,gEAAgE;AAChE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;AAEjE,yFAAyF;AACzF,iEAAiE;AACjE,MAAM,eAAe,GAAG,wBAAwB,CAAC;AACjD,MAAM,WAAW,GAAG,mBAAmB,CAAC;AACxC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAEnD,MAAa,YAAY;IAER;IACA;IACA;IACA;IAJb,YACa,IAAY,EACZ,IAAY,EACZ,MAAc,EACd,MAAc;QAHd,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAQ;QACZ,WAAM,GAAN,MAAM,CAAQ;QACd,WAAM,GAAN,MAAM,CAAQ;IACxB,CAAC;CACP;AAPD,oCAOC;AAED,MAAa,MAAM;IAEF;IACA;IACA;IACA;IAJb,YACa,IAAY,EACZ,MAAc,EACd,MAAc,EACd,iBAA0B;QAH1B,SAAI,GAAJ,IAAI,CAAQ;QACZ,WAAM,GAAN,MAAM,CAAQ;QACd,WAAM,GAAN,MAAM,CAAQ;QACd,sBAAiB,GAAjB,iBAAiB,CAAS;IACpC,CAAC;CACP;AAPD,wBAOC;AAGM,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,8BAAgC;IACtE,sGAAsG;IACtG,mGAAmG;IAClF,SAAS,CAAmB;IAE7C,YAAY,MAAyB;QACjC,KAAK,CAAC,MAAM,EAAE,eAAe,EAAE,eAAe,CAAC,CAAC;QAChD,IAAI,CAAC,SAAS,GAAG,IAAI,+BAAgB,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,aAAqB;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,EAAE,IAAI,CAAC,qBAAqB,EAAE,IAAI,CAAC,wBAAwB,IAAI,SAAS,CAAC,CAAC;QAC1H,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC;QACnD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;YACrE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;QAChE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAEhC,IAAI,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,EAAE,CAAC;YACR,IAAI,GAAG,IAAA,yBAAU,EAAC,aAAa,CAAC,IAAI,SAAS,CAAC;YAC9C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACR,OAAO,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;gBACxF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YAC7B,CAAC;QACL,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,6CAA6C,IAAI,CAAC,CAAC;QAEnF,uFAAuF;QACvF,MAAM,YAAY,GAAG,IAAA,8BAAe,EAAC,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;QACjI,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;YACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,eAAe,YAAY,CAAC,MAAM,qBAAqB,CAAC,CAAC;QAErE,MAAM,UAAU,GACZ,IAAI,KAAK,uBAAuB;YAC5B,CAAC,CAAC,IAAI,CAAC,6BAA6B,CAAC,aAAa,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC;YAC7F,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,aAAa,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;QAE3F,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;YACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QACD,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACxC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED;;;;;;;OAOG;IACH,cAAc,CAAC,IAAY;QACvB,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAChD,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnF,CAAC;IAED,+FAA+F;IAC/F,eAAe,CAAC,IAAY,EAAE,aAAqB;QAC/C,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC,uBAAuB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QACrF,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QAC1G,OAAO,EAAE,CAAC;IACd,CAAC;IAED,wEAAwE;IAChE,6BAA6B,CACjC,aAAqB,EACrB,YAAsB,EACtB,IAAY,EACZ,IAAwB,EACxB,cAAuB;QAEvB,MAAM,UAAU,GAAmB,EAAE,CAAC;QACtC,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YAC9B,MAAM,YAAY,GAAG,IAAA,oCAAqB,EAAC,IAAA,0BAAW,EAAC,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;YACzF,IAAI,YAAY,CAAC,IAAI,KAAK,CAAC;gBAAE,SAAS;YACtC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC;gBACxD,IAAI,cAAc,IAAI,CAAC,CAAC,iBAAiB;oBAAE,SAAS;gBACpD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;oBAAE,SAAS;gBACxC,UAAU,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;YACxE,CAAC;QACL,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;IAED,4DAA4D;IACpD,8BAA8B,CAAC,aAAqB,EAAE,YAAsB,EAAE,cAAuB;QACzG,MAAM,UAAU,GAAmB,EAAE,CAAC;QACtC,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YAC9B,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC;gBACxD,IAAI,cAAc,IAAI,CAAC,CAAC,iBAAiB;oBAAE,SAAS;gBACpD,UAAU,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;YACxE,CAAC;QACL,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;IAED,4FAA4F;IACpF,qBAAqB,CAAC,QAAgB,EAAE,aAAqB;QACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QACpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,UAAU,GAAG,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACxF,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,6FAA6F;IACrF,aAAa,CAAC,IAAa,EAAE,UAAyB,EAAE,SAAmB,EAAE,IAAc;QAC/F,IAAI,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC;YAAE,IAAI,CAAC,0BAA0B,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QAC7F,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;IACtG,CAAC;IAED,oFAAoF;IAC5E,0BAA0B,CAAC,IAAkB,EAAE,UAAyB,EAAE,SAAmB,EAAE,IAAc;QACjH,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;QAC7B,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,WAAW;YAAE,OAAO;QACpH,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,yBAAyB,CAAC,GAAG,CAAC;YAAE,OAAO;QACvD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YAChC,IAAI,CAAC,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC5E,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAC5B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YACrC,MAAM,GAAG,GAAG,UAAU,CAAC,6BAA6B,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;YAChF,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,SAAS,GAAG,CAAC,EAAE,KAAK,IAAI,wBAAwB,EAAE,IAAI,CAAC,iBAAiB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/H,CAAC;IACL,CAAC;IAED,mGAAmG;IAC3F,uBAAuB,CAAC,QAAgB,EAAE,aAAqB;QACnE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QACpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,EAAE,CAAC;QACxC,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjE,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAC9D,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YACnB,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,iBAAiB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACpF,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,kFAAkF;IAC1E,qBAAqB,CAAC,GAAW;QACrC,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,oBAAoB,CAAC;QACvD,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,4BAA4B,CAAC;QACpE,IAAI,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,2BAA2B,CAAC;QAClE,OAAO,EAAE,CAAC;IACd,CAAC;IAED,mGAAmG;IAC3F,iBAAiB,CAAC,KAAe,EAAE,UAAkB;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC;QAC1C,KAAK,IAAI,CAAC,GAAG,UAAU,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,IAAI,IAAA,yBAAU,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,yBAAU,CAAC,aAAa,CAAC;gBAAE,OAAO,IAAI,CAAC;QAC1E,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,WAAW,CAAC,UAA4B,EAAE,KAAyB,EAAE,aAAiC;QAC1G,IAAI,UAAU,KAAK,KAAK;YAAE,OAAO,UAAU,CAAC;QAC5C,MAAM,IAAI,GAAG,IAAA,6BAAc,EAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QAClD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,4CAA4C,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC;YAC1E,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;IAEO,gBAAgB,CAAC,UAA0B,EAAE,IAAsB;QACvE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,oFAAoF,CAAC,CAAC;QACpG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,4EAA4E,CAAC,CAAC;QAC5F,OAAO,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;QAC3E,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC9C,OAAO,CAAC,KAAK,CAAC,sEAAsE,CAAC,CAAC;QACtF,OAAO,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAC;QAC5E,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YACrD,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,mFAAmF,CAAC,CAAC;QACnG,OAAO,CAAC,KAAK,CAAC,0EAA0E,CAAC,CAAC;QAC1F,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,4EAA4E,CAAC,CAAC;QAC5F,OAAO,CAAC,KAAK,CAAC,qFAAqF,CAAC,CAAC;QACrG,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC5D,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAC;QACnF,OAAO,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;QACzE,OAAO,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;QAC/E,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACtB,CAAC;CACJ,CAAA;AArNY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAMjB,gCAAiB;GAL5B,oBAAoB,CAqNhC","sourcesContent":["/**\n * Validate No Custom CSS Executor\n *\n * Enforces Tailwind-first styling in Angular sources by banning hand-written CSS on CHANGED code:\n * - `.ts` — `styles:` / `styleUrls:` / `styleUrl:` inside an `@Component({...})` decorator\n * (detected via the TypeScript AST, so only the real decorator triggers it).\n * - `.html` — a static `style=\"…\"` attribute, a `[style]` / `[style.x]` binding, or `[ngStyle]`\n * (line/regex scan — Angular templates have no TS AST).\n *\n * Unlike an ESLint rule (which lints whole files), this is diff-scoped: NEW_AND_MODIFIED_CODE only\n * flags violations on changed lines, so a legacy Angular app is never retroactively flooded — the\n * rule bites when a file is next edited.\n *\n * MODES: OFF | NEW_AND_MODIFIED_CODE (changed lines) | NEW_AND_MODIFIED_FILES (all in changed files).\n *\n * ESCAPE HATCH (a genuinely dynamic runtime value):\n * .ts: // webpieces-disable no-custom-css -- <reason>\n * .html: <!-- webpieces-disable no-custom-css -- <reason> -->\n *\n * PATH EXEMPTION: `allowGlobs` in the rule's webpieces.config.json entry (a vendored kit copied verbatim\n * with its own CSS, a generated artifact). It is applied by the shared `NoCustomCssScope`, the SAME class\n * the edit-time hook rule consults — so the editor and CI agree on which paths the rule looks at.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport {\n hasDisable,\n RULE_NAMES,\n NoCustomCssConfig,\n NoCustomCssScope,\n ModifiedCodeMode,\n detectBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n} from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { shouldSkipRule } from './resolve-mode';\n\n// @Component decorator properties that inject hand-written CSS.\nconst STYLE_PROPS = new Set(['styles', 'styleUrls', 'styleUrl']);\n\n// Template patterns. `[style.width]=\"x\"` is preceded by `[`, so the inline-style regex's\n// `(^|\\s)style=` boundary keeps it apart from the binding forms.\nconst RE_INLINE_STYLE = /(^|\\s)style\\s*=\\s*[\"']/;\nconst RE_NG_STYLE = /\\[?ngStyle\\]?\\s*=/;\nconst RE_STYLE_BINDING = /\\[style(?:\\.[\\w.-]+)?\\]/;\n\nexport class CssViolation {\n constructor(\n readonly file: string,\n readonly line: number,\n readonly column: number,\n readonly detail: string,\n ) {}\n}\n\nexport class CssHit {\n constructor(\n readonly line: number,\n readonly column: number,\n readonly detail: string,\n readonly hasDisableComment: boolean,\n ) {}\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class NoCustomCssValidator extends CodeValidator<NoCustomCssConfig> {\n // The SAME exemption the edit-time hook rule applies per file — one class in @webpieces/rules-config,\n // so `allowGlobs` cannot be honoured by the editor and ignored here (which is what it used to do).\n private readonly pathScope: NoCustomCssScope;\n\n constructor(config: NoCustomCssConfig) {\n super(config, 'no-custom-css', 'no-custom-css');\n this.pathScope = new NoCustomCssScope(config);\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n const opts = this.config;\n const mode = this.resolveMode(opts.mode ?? 'OFF', opts.turnOffRuleUntilEpoch, opts.turnOffRuleWhileOnBranch ?? undefined);\n const disableAllowed = opts.disableAllowed ?? true;\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping no-custom-css validation (mode: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Validating No Custom CSS (Tailwind-first)\\n');\n console.log(` Mode: ${mode}`);\n\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n if (!base) {\n console.log('\\n⏭️ Skipping no-custom-css validation (could not detect base branch)\\n');\n return { success: true };\n }\n }\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}\\n`);\n\n // tsOnly:false so changed .html templates are included; then filter to relevant files.\n const changedFiles = getChangedFiles(workspaceRoot, base, head, { tsOnly: false }).filter((f: string) => this.isRelevantFile(f));\n if (changedFiles.length === 0) {\n console.log('✅ No Angular .ts/.html files changed');\n return { success: true };\n }\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n const violations =\n mode === 'NEW_AND_MODIFIED_CODE'\n ? this.findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed)\n : this.findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);\n\n if (violations.length === 0) {\n console.log('✅ No custom CSS found');\n return { success: true };\n }\n this.reportViolations(violations, mode);\n return { success: false };\n }\n\n /**\n * A changed file this rule cares about: an Angular .ts/.tsx or .html that the shared\n * {@link NoCustomCssScope} does not exempt (a test source, or a configured `allowGlobs` match).\n *\n * This is the ONE place the changed-file set is narrowed, which is what makes the exemption\n * impossible to apply in one mode and forget in the other — both `NEW_AND_MODIFIED_CODE` and\n * `NEW_AND_MODIFIED_FILES` run over the list this predicate produced.\n */\n isRelevantFile(file: string): boolean {\n if (this.pathScope.isExempt(file)) return false;\n return file.endsWith('.html') || file.endsWith('.ts') || file.endsWith('.tsx');\n }\n\n /** All banned-CSS hits in a single file (dispatches to the .ts AST or the .html line scan). */\n findHitsForFile(file: string, workspaceRoot: string): CssHit[] {\n if (file.endsWith('.html')) return this.findCustomCssInHtmlFile(file, workspaceRoot);\n if (file.endsWith('.ts') || file.endsWith('.tsx')) return this.findCustomCssInTsFile(file, workspaceRoot);\n return [];\n }\n\n /** NEW_AND_MODIFIED_CODE: only hits whose line is in the diff hunks. */\n private findViolationsForModifiedCode(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n head: string | undefined,\n disableAllowed: boolean,\n ): CssViolation[] {\n const violations: CssViolation[] = [];\n for (const file of changedFiles) {\n const changedLines = getChangedLineNumbers(getFileDiff(workspaceRoot, file, base, head));\n if (changedLines.size === 0) continue;\n for (const h of this.findHitsForFile(file, workspaceRoot)) {\n if (disableAllowed && h.hasDisableComment) continue;\n if (!changedLines.has(h.line)) continue;\n violations.push(new CssViolation(file, h.line, h.column, h.detail));\n }\n }\n return violations;\n }\n\n /** NEW_AND_MODIFIED_FILES: all hits in any changed file. */\n private findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: string[], disableAllowed: boolean): CssViolation[] {\n const violations: CssViolation[] = [];\n for (const file of changedFiles) {\n for (const h of this.findHitsForFile(file, workspaceRoot)) {\n if (disableAllowed && h.hasDisableComment) continue;\n violations.push(new CssViolation(file, h.line, h.column, h.detail));\n }\n }\n return violations;\n }\n\n /** Flag styles:/styleUrls:/styleUrl: properties inside an `@Component({...})` decorator. */\n private findCustomCssInTsFile(filePath: string, workspaceRoot: string): CssHit[] {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n const content = fs.readFileSync(fullPath, 'utf-8');\n const fileLines = content.split('\\n');\n const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n const hits: CssHit[] = [];\n this.collectTsHits(sourceFile, sourceFile, fileLines, hits);\n return hits;\n }\n\n /** Recurse the AST; every `@Component(...)` decorator contributes its banned style props. */\n private collectTsHits(node: ts.Node, sourceFile: ts.SourceFile, fileLines: string[], hits: CssHit[]): void {\n if (ts.isDecorator(node)) this.collectComponentStyleProps(node, sourceFile, fileLines, hits);\n ts.forEachChild(node, (child: ts.Node) => this.collectTsHits(child, sourceFile, fileLines, hits));\n }\n\n /** Pull the banned style props out of a single `@Component(...)` decorator node. */\n private collectComponentStyleProps(node: ts.Decorator, sourceFile: ts.SourceFile, fileLines: string[], hits: CssHit[]): void {\n const expr = node.expression;\n if (!ts.isCallExpression(expr) || !ts.isIdentifier(expr.expression) || expr.expression.text !== 'Component') return;\n const arg = expr.arguments[0];\n if (!arg || !ts.isObjectLiteralExpression(arg)) return;\n for (const prop of arg.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n const name = prop.name.text;\n if (!STYLE_PROPS.has(name)) continue;\n const pos = sourceFile.getLineAndCharacterOfPosition(prop.getStart(sourceFile));\n const line = pos.line + 1;\n hits.push(new CssHit(line, pos.character + 1, `\\`${name}\\` block in @Component`, this.hasDisableComment(fileLines, line)));\n }\n }\n\n /** Flag inline style=, [style.x]/[style], and [ngStyle] in an Angular template via a line scan. */\n private findCustomCssInHtmlFile(filePath: string, workspaceRoot: string): CssHit[] {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n const fileLines = fs.readFileSync(fullPath, 'utf-8').split('\\n');\n const hits: CssHit[] = [];\n for (let i = 0; i < fileLines.length; i++) {\n const detail = this.detailForTemplateLine(fileLines[i] ?? '');\n if (!detail) continue;\n const line = i + 1;\n hits.push(new CssHit(line, 1, detail, this.hasDisableComment(fileLines, line)));\n }\n return hits;\n }\n\n /** The banned-pattern label for a template line, or '' when the line is clean. */\n private detailForTemplateLine(raw: string): string {\n if (RE_NG_STYLE.test(raw)) return 'inline `[ngStyle]`';\n if (RE_STYLE_BINDING.test(raw)) return 'inline `[style.x]` binding';\n if (RE_INLINE_STYLE.test(raw)) return 'inline `style=` attribute';\n return '';\n }\n\n /** True if a `webpieces-disable no-custom-css` marker sits on this line or up to 3 lines above. */\n private hasDisableComment(lines: string[], lineNumber: number): boolean {\n const start = Math.max(0, lineNumber - 4);\n for (let i = lineNumber - 1; i >= start; i--) {\n if (hasDisable(lines[i] ?? '', RULE_NAMES.NO_CUSTOM_CSS)) return true;\n }\n return false;\n }\n\n private resolveMode(normalMode: ModifiedCodeMode, epoch: number | undefined, branchPattern: string | undefined): ModifiedCodeMode {\n if (normalMode === 'OFF') return normalMode;\n const skip = shouldSkipRule(epoch, branchPattern);\n if (skip.skip) {\n console.log(`\\n⏭️ Skipping no-custom-css validation (${skip.reason})\\n`);\n return 'OFF';\n }\n return normalMode;\n }\n\n private reportViolations(violations: CssViolation[], mode: ModifiedCodeMode): void {\n console.error('');\n console.error('❌ Custom CSS found in Angular source! Style with Tailwind utility classes instead.');\n console.error('');\n console.error('📚 Tailwind-first keeps styling in the template, greppable and consistent:');\n console.error(' BAD: styles: [\".card { display: flex; gap: 1rem; }\"]');\n console.error(' GOOD: class=\"flex gap-4\"');\n console.error(' BAD: <div style=\"width: 240px\"> | [style.width] | [ngStyle]');\n console.error(' GOOD: <div class=\"w-[240px]\"> | [class.x] toggles');\n console.error('');\n for (const v of violations) {\n console.error(` ❌ ${v.file}:${v.line}:${v.column}`);\n console.error(` ${v.detail}`);\n }\n console.error('');\n console.error(' To fix: delete the CSS and use Tailwind utility classes (arbitrary values like');\n console.error(' `bg-[#fffde7]` / `grid-cols-[2fr_2fr_2fr_48px]` cover the long tail).');\n console.error('');\n console.error(' Whole tree not yours to restyle (a vendored kit, a generated artifact)?');\n console.error(' Add a glob to no-custom-css.allowGlobs in webpieces.config.json — it exempts the');\n console.error(' path here and in the editor hook alike.');\n console.error('');\n console.error(' Escape hatch (genuinely dynamic runtime value, use sparingly):');\n console.error(' .ts // webpieces-disable no-custom-css -- <reason>');\n console.error(' .html <!-- webpieces-disable no-custom-css -- <reason> -->');\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n }\n}\n"]}
|