@webpieces/code-rules 0.4.411 → 0.4.413
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 +2 -2
- package/src/code-rules-config-table.js +1 -0
- package/src/code-rules-config-table.js.map +1 -1
- package/src/code-rules-engine.d.ts +4 -2
- package/src/code-rules-engine.js +7 -3
- package/src/code-rules-engine.js.map +1 -1
- package/src/validate-no-custom-css.d.ts +61 -0
- package/src/validate-no-custom-css.js +263 -0
- package/src/validate-no-custom-css.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/code-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.413",
|
|
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",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"directory": "packages/tooling/code-rules"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@webpieces/rules-config": "0.4.
|
|
23
|
+
"@webpieces/rules-config": "0.4.413",
|
|
24
24
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
25
25
|
"inversify": "7.10.4",
|
|
26
26
|
"reflect-metadata": "0.2.2"
|
|
@@ -22,6 +22,7 @@ exports.CONFIG_BINDINGS = [
|
|
|
22
22
|
[rules_config_1.NoUnmanagedExceptionsConfig, 'no-unmanaged-exceptions'],
|
|
23
23
|
[rules_config_1.AngularNoDirectApiInResolverConfig, 'angular-no-direct-api-in-resolver'],
|
|
24
24
|
[rules_config_1.NoSymbolDiTokensConfig, 'no-symbol-di-tokens'],
|
|
25
|
+
[rules_config_1.NoCustomCssConfig, 'no-custom-css'],
|
|
25
26
|
[rules_config_1.NoProcessExitOutsideMainConfig, 'no-process-exit-outside-main'],
|
|
26
27
|
[rules_config_1.NoFunctionOutsideClassConfig, 'no-function-outside-class'],
|
|
27
28
|
[rules_config_1.InjectAnnotationNotNeededForConcreteClassConfig, 'inject-annotation-not-needed-for-concrete-class'],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-rules-config-table.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/code-rules-config-table.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"code-rules-config-table.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/code-rules-config-table.ts"],"names":[],"mappings":";;;AAAA,0DAsBiC;AAKjC;;;;;GAKG;AACU,QAAA,eAAe,GAAqE;IAC7F,CAAC,mCAAoB,EAAE,kBAAkB,CAAC;IAC1C,CAAC,iCAAkB,EAAE,gBAAgB,CAAC;IACtC,CAAC,sCAAuB,EAAE,qBAAqB,CAAC;IAChD,CAAC,yCAA0B,EAAE,yBAAyB,CAAC;IACvD,CAAC,iCAAkB,EAAE,gBAAgB,CAAC;IACtC,CAAC,kCAAmB,EAAE,iBAAiB,CAAC;IACxC,CAAC,uCAAwB,EAAE,sBAAsB,CAAC;IAClD,CAAC,oCAAqB,EAAE,kBAAkB,CAAC;IAC3C,CAAC,kCAAmB,EAAE,gBAAgB,CAAC;IACvC,CAAC,sCAAuB,EAAE,qBAAqB,CAAC;IAChD,CAAC,0CAA2B,EAAE,yBAAyB,CAAC;IACxD,CAAC,iDAAkC,EAAE,mCAAmC,CAAC;IACzE,CAAC,qCAAsB,EAAE,qBAAqB,CAAC;IAC/C,CAAC,gCAAiB,EAAE,eAAe,CAAC;IACpC,CAAC,6CAA8B,EAAE,8BAA8B,CAAC;IAChE,CAAC,2CAA4B,EAAE,2BAA2B,CAAC;IAC3D,CAAC,8DAA+C,EAAE,iDAAiD,CAAC;IACpG,CAAC,iCAAkB,EAAE,eAAe,CAAC;IACrC,CAAC,4BAAa,EAAE,UAAU,CAAC;CAC9B,CAAC","sourcesContent":["import {\n BaseRuleConfig,\n WebpiecesRulesConfig,\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n CatchErrorPatternConfig,\n NoUnmanagedExceptionsConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoCustomCssConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n} from '@webpieces/rules-config';\n\n/** A rule config class usable as an inversify inject-by-type token. */\nexport type ConfigCtor = new () => BaseRuleConfig;\n\n/**\n * The 1:1 table of `[config class, webpieces.config.json rule key]`. The composition root iterates it\n * to bind each rule's config into the container (`bind(ConfigClass).toConstantValue(config[key] ??\n * new ConfigClass())`), so every `@provideSingleton` validator injects its config by type. Data only\n * — the binding loop lives inline in the bin (an inline loop, exempt from no-function-outside-class).\n */\nexport const CONFIG_BINDINGS: ReadonlyArray<readonly [ConfigCtor, keyof WebpiecesRulesConfig]> = [\n [MaxMethodLinesConfig, 'max-method-lines'],\n [MaxFileLinesConfig, 'max-file-lines'],\n [RequireReturnTypeConfig, 'require-return-type'],\n [NoInlineTypeLiteralsConfig, 'no-inline-type-literals'],\n [NoAnyUnknownConfig, 'no-any-unknown'],\n [NoImplicitAnyConfig, 'no-implicit-any'],\n [PrismaValidateDtosConfig, 'prisma-validate-dtos'],\n [PrismaConverterConfig, 'prisma-converter'],\n [NoDestructureConfig, 'no-destructure'],\n [CatchErrorPatternConfig, 'catch-error-pattern'],\n [NoUnmanagedExceptionsConfig, 'no-unmanaged-exceptions'],\n [AngularNoDirectApiInResolverConfig, 'angular-no-direct-api-in-resolver'],\n [NoSymbolDiTokensConfig, 'no-symbol-di-tokens'],\n [NoCustomCssConfig, 'no-custom-css'],\n [NoProcessExitOutsideMainConfig, 'no-process-exit-outside-main'],\n [NoFunctionOutsideClassConfig, 'no-function-outside-class'],\n [InjectAnnotationNotNeededForConcreteClassConfig, 'inject-annotation-not-needed-for-concrete-class'],\n [FrameworkTagConfig, 'framework-tag'],\n [RoleTagConfig, 'role-tag'],\n];\n"]}
|
|
@@ -15,6 +15,7 @@ import { CatchErrorPatternValidator } from './validate-catch-error-pattern';
|
|
|
15
15
|
import { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';
|
|
16
16
|
import { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';
|
|
17
17
|
import { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';
|
|
18
|
+
import { NoCustomCssValidator } from './validate-no-custom-css';
|
|
18
19
|
import { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';
|
|
19
20
|
import { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';
|
|
20
21
|
import { InjectAnnotationNotNeededForConcreteClassValidator } from './validate-inject-annotation-not-needed-for-concrete-class';
|
|
@@ -44,13 +45,14 @@ export declare class CodeRulesEngine {
|
|
|
44
45
|
private readonly noUnmanagedExceptions;
|
|
45
46
|
private readonly noDirectApiResolver;
|
|
46
47
|
private readonly noSymbolDiTokens;
|
|
48
|
+
private readonly noCustomCss;
|
|
47
49
|
private readonly noProcessExitOutsideMain;
|
|
48
50
|
private readonly noFunctionOutsideClass;
|
|
49
51
|
private readonly injectAnnotationNotNeeded;
|
|
50
52
|
private readonly frameworkTag;
|
|
51
53
|
private readonly roleTag;
|
|
52
|
-
constructor(workspace: WorkspaceRoot, reporter: RuleReporter, matchRules: MatchRulesHolder, matchChecker: MatchRulesChecker, maxMethodLines: MaxMethodLinesValidator, maxFileLines: MaxFileLinesValidator, requireReturnType: RequireReturnTypeValidator, noInlineTypeLiterals: NoInlineTypeLiteralsValidator, noAnyUnknown: NoAnyUnknownValidator, noImplicitAny: NoImplicitAnyValidator, prismaValidateDtos: PrismaValidateDtosValidator, prismaConverter: PrismaConverterValidator, noDestructure: NoDestructureValidator, catchErrorPattern: CatchErrorPatternValidator, noUnmanagedExceptions: NoUnmanagedExceptionsValidator, noDirectApiResolver: NoDirectApiResolverValidator, noSymbolDiTokens: NoSymbolDiTokensValidator, noProcessExitOutsideMain: NoProcessExitOutsideMainValidator, noFunctionOutsideClass: NoFunctionOutsideClassValidator, injectAnnotationNotNeeded: InjectAnnotationNotNeededForConcreteClassValidator, frameworkTag: FrameworkTagValidator, roleTag: RoleTagValidator);
|
|
53
|
-
/** The
|
|
54
|
+
constructor(workspace: WorkspaceRoot, reporter: RuleReporter, matchRules: MatchRulesHolder, matchChecker: MatchRulesChecker, maxMethodLines: MaxMethodLinesValidator, maxFileLines: MaxFileLinesValidator, requireReturnType: RequireReturnTypeValidator, noInlineTypeLiterals: NoInlineTypeLiteralsValidator, noAnyUnknown: NoAnyUnknownValidator, noImplicitAny: NoImplicitAnyValidator, prismaValidateDtos: PrismaValidateDtosValidator, prismaConverter: PrismaConverterValidator, noDestructure: NoDestructureValidator, catchErrorPattern: CatchErrorPatternValidator, noUnmanagedExceptions: NoUnmanagedExceptionsValidator, noDirectApiResolver: NoDirectApiResolverValidator, noSymbolDiTokens: NoSymbolDiTokensValidator, noCustomCss: NoCustomCssValidator, noProcessExitOutsideMain: NoProcessExitOutsideMainValidator, noFunctionOutsideClass: NoFunctionOutsideClassValidator, injectAnnotationNotNeeded: InjectAnnotationNotNeededForConcreteClassValidator, frameworkTag: FrameworkTagValidator, roleTag: RoleTagValidator);
|
|
55
|
+
/** The 19 injected built-in validators, in run order. */
|
|
54
56
|
private builtIns;
|
|
55
57
|
/**
|
|
56
58
|
* Every ACTIVE run: an injected built-in validator whose `shouldRun()` is true, or an injected
|
package/src/code-rules-engine.js
CHANGED
|
@@ -20,6 +20,7 @@ const validate_catch_error_pattern_1 = require("./validate-catch-error-pattern")
|
|
|
20
20
|
const validate_no_unmanaged_exceptions_1 = require("./validate-no-unmanaged-exceptions");
|
|
21
21
|
const validate_no_direct_api_resolver_1 = require("./validate-no-direct-api-resolver");
|
|
22
22
|
const validate_no_symbol_di_tokens_1 = require("./validate-no-symbol-di-tokens");
|
|
23
|
+
const validate_no_custom_css_1 = require("./validate-no-custom-css");
|
|
23
24
|
const validate_no_process_exit_outside_main_1 = require("./validate-no-process-exit-outside-main");
|
|
24
25
|
const validate_no_function_outside_class_1 = require("./validate-no-function-outside-class");
|
|
25
26
|
const validate_inject_annotation_not_needed_for_concrete_class_1 = require("./validate-inject-annotation-not-needed-for-concrete-class");
|
|
@@ -49,13 +50,14 @@ let CodeRulesEngine = class CodeRulesEngine {
|
|
|
49
50
|
noUnmanagedExceptions;
|
|
50
51
|
noDirectApiResolver;
|
|
51
52
|
noSymbolDiTokens;
|
|
53
|
+
noCustomCss;
|
|
52
54
|
noProcessExitOutsideMain;
|
|
53
55
|
noFunctionOutsideClass;
|
|
54
56
|
injectAnnotationNotNeeded;
|
|
55
57
|
frameworkTag;
|
|
56
58
|
roleTag;
|
|
57
59
|
// webpieces-disable max-lines-new-methods -- the built-in validator set is flat; each is one injected field
|
|
58
|
-
constructor(workspace, reporter, matchRules, matchChecker, maxMethodLines, maxFileLines, requireReturnType, noInlineTypeLiterals, noAnyUnknown, noImplicitAny, prismaValidateDtos, prismaConverter, noDestructure, catchErrorPattern, noUnmanagedExceptions, noDirectApiResolver, noSymbolDiTokens, noProcessExitOutsideMain, noFunctionOutsideClass, injectAnnotationNotNeeded, frameworkTag, roleTag) {
|
|
60
|
+
constructor(workspace, reporter, matchRules, matchChecker, maxMethodLines, maxFileLines, requireReturnType, noInlineTypeLiterals, noAnyUnknown, noImplicitAny, prismaValidateDtos, prismaConverter, noDestructure, catchErrorPattern, noUnmanagedExceptions, noDirectApiResolver, noSymbolDiTokens, noCustomCss, noProcessExitOutsideMain, noFunctionOutsideClass, injectAnnotationNotNeeded, frameworkTag, roleTag) {
|
|
59
61
|
this.workspace = workspace;
|
|
60
62
|
this.reporter = reporter;
|
|
61
63
|
this.matchRules = matchRules;
|
|
@@ -73,19 +75,20 @@ let CodeRulesEngine = class CodeRulesEngine {
|
|
|
73
75
|
this.noUnmanagedExceptions = noUnmanagedExceptions;
|
|
74
76
|
this.noDirectApiResolver = noDirectApiResolver;
|
|
75
77
|
this.noSymbolDiTokens = noSymbolDiTokens;
|
|
78
|
+
this.noCustomCss = noCustomCss;
|
|
76
79
|
this.noProcessExitOutsideMain = noProcessExitOutsideMain;
|
|
77
80
|
this.noFunctionOutsideClass = noFunctionOutsideClass;
|
|
78
81
|
this.injectAnnotationNotNeeded = injectAnnotationNotNeeded;
|
|
79
82
|
this.frameworkTag = frameworkTag;
|
|
80
83
|
this.roleTag = roleTag;
|
|
81
84
|
}
|
|
82
|
-
/** The
|
|
85
|
+
/** The 19 injected built-in validators, in run order. */
|
|
83
86
|
builtIns() {
|
|
84
87
|
return [
|
|
85
88
|
this.maxMethodLines, this.maxFileLines, this.requireReturnType, this.noInlineTypeLiterals,
|
|
86
89
|
this.noAnyUnknown, this.noImplicitAny, this.prismaValidateDtos, this.prismaConverter,
|
|
87
90
|
this.noDestructure, this.catchErrorPattern, this.noUnmanagedExceptions, this.noDirectApiResolver,
|
|
88
|
-
this.noSymbolDiTokens, this.noProcessExitOutsideMain, this.noFunctionOutsideClass,
|
|
91
|
+
this.noSymbolDiTokens, this.noCustomCss, this.noProcessExitOutsideMain, this.noFunctionOutsideClass,
|
|
89
92
|
this.injectAnnotationNotNeeded, this.frameworkTag, this.roleTag,
|
|
90
93
|
];
|
|
91
94
|
}
|
|
@@ -145,6 +148,7 @@ exports.CodeRulesEngine = CodeRulesEngine = tslib_1.__decorate([
|
|
|
145
148
|
validate_no_unmanaged_exceptions_1.NoUnmanagedExceptionsValidator,
|
|
146
149
|
validate_no_direct_api_resolver_1.NoDirectApiResolverValidator,
|
|
147
150
|
validate_no_symbol_di_tokens_1.NoSymbolDiTokensValidator,
|
|
151
|
+
validate_no_custom_css_1.NoCustomCssValidator,
|
|
148
152
|
validate_no_process_exit_outside_main_1.NoProcessExitOutsideMainValidator,
|
|
149
153
|
validate_no_function_outside_class_1.NoFunctionOutsideClassValidator,
|
|
150
154
|
validate_inject_annotation_not_needed_for_concrete_class_1.InjectAnnotationNotNeededForConcreteClassValidator,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-rules-engine.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/code-rules-engine.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseRuleConfig } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { CodeValidator, ExecutorResult, RuleRun } from './code-validator';\nimport { RuleReporter } from './rule-reporter';\nimport { WorkspaceRoot, MatchRulesHolder } from './code-rules-context';\nimport { MatchRulesChecker } from './validate-match-rules';\nimport { MaxMethodLinesValidator } from './validate-modified-methods';\nimport { MaxFileLinesValidator } from './validate-modified-files';\nimport { RequireReturnTypeValidator } from './validate-return-types';\nimport { NoInlineTypeLiteralsValidator } from './validate-no-inline-types';\nimport { NoAnyUnknownValidator } from './validate-no-any-unknown';\nimport { NoImplicitAnyValidator } from './validate-no-implicit-any';\nimport { PrismaValidateDtosValidator } from './validate-dtos';\nimport { PrismaConverterValidator } from './validate-prisma-converters';\nimport { NoDestructureValidator } from './validate-no-destructure';\nimport { CatchErrorPatternValidator } from './validate-catch-error-pattern';\nimport { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';\nimport { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';\nimport { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';\nimport { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';\nimport { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';\nimport { InjectAnnotationNotNeededForConcreteClassValidator } from './validate-inject-annotation-not-needed-for-concrete-class';\nimport { FrameworkTagValidator } from './validate-framework-tag';\nimport { RoleTagValidator } from './validate-role-tag';\n\n/**\n * Owns running the code-rules suite. Every built-in validator is injected as a singleton (its config\n * is bound in the container at bootstrap), so this class IS the code-rules DI DAG the design graph\n * draws. The per-entry match-rules validators are the one exception — they are config-per-instance,\n * so they are built from the injected {@link MatchRulesHolder}.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class CodeRulesEngine {\n // webpieces-disable max-lines-new-methods -- the built-in validator set is flat; each is one injected field\n constructor(\n private readonly workspace: WorkspaceRoot,\n private readonly reporter: RuleReporter,\n private readonly matchRules: MatchRulesHolder,\n private readonly matchChecker: MatchRulesChecker,\n private readonly maxMethodLines: MaxMethodLinesValidator,\n private readonly maxFileLines: MaxFileLinesValidator,\n private readonly requireReturnType: RequireReturnTypeValidator,\n private readonly noInlineTypeLiterals: NoInlineTypeLiteralsValidator,\n private readonly noAnyUnknown: NoAnyUnknownValidator,\n private readonly noImplicitAny: NoImplicitAnyValidator,\n private readonly prismaValidateDtos: PrismaValidateDtosValidator,\n private readonly prismaConverter: PrismaConverterValidator,\n private readonly noDestructure: NoDestructureValidator,\n private readonly catchErrorPattern: CatchErrorPatternValidator,\n private readonly noUnmanagedExceptions: NoUnmanagedExceptionsValidator,\n private readonly noDirectApiResolver: NoDirectApiResolverValidator,\n private readonly noSymbolDiTokens: NoSymbolDiTokensValidator,\n private readonly noProcessExitOutsideMain: NoProcessExitOutsideMainValidator,\n private readonly noFunctionOutsideClass: NoFunctionOutsideClassValidator,\n private readonly injectAnnotationNotNeeded: InjectAnnotationNotNeededForConcreteClassValidator,\n private readonly frameworkTag: FrameworkTagValidator,\n private readonly roleTag: RoleTagValidator,\n ) {}\n\n /** The 18 injected built-in validators, in run order. */\n private builtIns(): CodeValidator<BaseRuleConfig>[] {\n return [\n this.maxMethodLines, this.maxFileLines, this.requireReturnType, this.noInlineTypeLiterals,\n this.noAnyUnknown, this.noImplicitAny, this.prismaValidateDtos, this.prismaConverter,\n this.noDestructure, this.catchErrorPattern, this.noUnmanagedExceptions, this.noDirectApiResolver,\n this.noSymbolDiTokens, this.noProcessExitOutsideMain, this.noFunctionOutsideClass,\n this.injectAnnotationNotNeeded, this.frameworkTag, this.roleTag,\n ];\n }\n\n /**\n * Every ACTIVE run: an injected built-in validator whose `shouldRun()` is true, or an injected\n * match-rule check per configured entry that is active. Built as {@link RuleRun} value objects\n * (name + thunk) so NO DAG member is `new`-ed — the validators and the checker are injected.\n */\n private activeRuns(root: string): RuleRun[] {\n const runs: RuleRun[] = [];\n for (const v of this.builtIns()) {\n if (v.shouldRun()) runs.push(new RuleRun(v.name, () => v.run(root)));\n }\n for (const mr of this.matchRules.rules) {\n if (this.matchChecker.shouldRun(mr)) runs.push(new RuleRun(mr.name, () => this.matchChecker.runForConfig(mr, root)));\n }\n return runs;\n }\n\n /**\n * Run all configured code validators against the workspace. A validator runs only when\n * `shouldRun()` is true (mode not OFF and no branch/epoch escape hatch). Per-run isolation lives\n * in {@link RuleReporter} so one validator can never abort the rest.\n */\n async run(): Promise<ExecutorResult> {\n const runs = this.activeRuns(this.workspace.path);\n if (runs.length === 0) {\n console.log('\\n⏭️ Skipping all code validations (all modes: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Running Code Validations\\n');\n console.log(` Active rules: ${runs.map((r: RuleRun) => r.name).join(', ')}`);\n console.log('');\n\n const result = await this.reporter.runValidators(runs);\n console.log(result.success ? '\\n✅ All code validations passed\\n' : '\\n❌ Some code validations failed\\n');\n return result;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"code-rules-engine.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/code-rules-engine.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseRuleConfig } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { CodeValidator, ExecutorResult, RuleRun } from './code-validator';\nimport { RuleReporter } from './rule-reporter';\nimport { WorkspaceRoot, MatchRulesHolder } from './code-rules-context';\nimport { MatchRulesChecker } from './validate-match-rules';\nimport { MaxMethodLinesValidator } from './validate-modified-methods';\nimport { MaxFileLinesValidator } from './validate-modified-files';\nimport { RequireReturnTypeValidator } from './validate-return-types';\nimport { NoInlineTypeLiteralsValidator } from './validate-no-inline-types';\nimport { NoAnyUnknownValidator } from './validate-no-any-unknown';\nimport { NoImplicitAnyValidator } from './validate-no-implicit-any';\nimport { PrismaValidateDtosValidator } from './validate-dtos';\nimport { PrismaConverterValidator } from './validate-prisma-converters';\nimport { NoDestructureValidator } from './validate-no-destructure';\nimport { CatchErrorPatternValidator } from './validate-catch-error-pattern';\nimport { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';\nimport { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';\nimport { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';\nimport { NoCustomCssValidator } from './validate-no-custom-css';\nimport { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';\nimport { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';\nimport { InjectAnnotationNotNeededForConcreteClassValidator } from './validate-inject-annotation-not-needed-for-concrete-class';\nimport { FrameworkTagValidator } from './validate-framework-tag';\nimport { RoleTagValidator } from './validate-role-tag';\n\n/**\n * Owns running the code-rules suite. Every built-in validator is injected as a singleton (its config\n * is bound in the container at bootstrap), so this class IS the code-rules DI DAG the design graph\n * draws. The per-entry match-rules validators are the one exception — they are config-per-instance,\n * so they are built from the injected {@link MatchRulesHolder}.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class CodeRulesEngine {\n // webpieces-disable max-lines-new-methods -- the built-in validator set is flat; each is one injected field\n constructor(\n private readonly workspace: WorkspaceRoot,\n private readonly reporter: RuleReporter,\n private readonly matchRules: MatchRulesHolder,\n private readonly matchChecker: MatchRulesChecker,\n private readonly maxMethodLines: MaxMethodLinesValidator,\n private readonly maxFileLines: MaxFileLinesValidator,\n private readonly requireReturnType: RequireReturnTypeValidator,\n private readonly noInlineTypeLiterals: NoInlineTypeLiteralsValidator,\n private readonly noAnyUnknown: NoAnyUnknownValidator,\n private readonly noImplicitAny: NoImplicitAnyValidator,\n private readonly prismaValidateDtos: PrismaValidateDtosValidator,\n private readonly prismaConverter: PrismaConverterValidator,\n private readonly noDestructure: NoDestructureValidator,\n private readonly catchErrorPattern: CatchErrorPatternValidator,\n private readonly noUnmanagedExceptions: NoUnmanagedExceptionsValidator,\n private readonly noDirectApiResolver: NoDirectApiResolverValidator,\n private readonly noSymbolDiTokens: NoSymbolDiTokensValidator,\n private readonly noCustomCss: NoCustomCssValidator,\n private readonly noProcessExitOutsideMain: NoProcessExitOutsideMainValidator,\n private readonly noFunctionOutsideClass: NoFunctionOutsideClassValidator,\n private readonly injectAnnotationNotNeeded: InjectAnnotationNotNeededForConcreteClassValidator,\n private readonly frameworkTag: FrameworkTagValidator,\n private readonly roleTag: RoleTagValidator,\n ) {}\n\n /** The 19 injected built-in validators, in run order. */\n private builtIns(): CodeValidator<BaseRuleConfig>[] {\n return [\n this.maxMethodLines, this.maxFileLines, this.requireReturnType, this.noInlineTypeLiterals,\n this.noAnyUnknown, this.noImplicitAny, this.prismaValidateDtos, this.prismaConverter,\n this.noDestructure, this.catchErrorPattern, this.noUnmanagedExceptions, this.noDirectApiResolver,\n this.noSymbolDiTokens, this.noCustomCss, this.noProcessExitOutsideMain, this.noFunctionOutsideClass,\n this.injectAnnotationNotNeeded, this.frameworkTag, this.roleTag,\n ];\n }\n\n /**\n * Every ACTIVE run: an injected built-in validator whose `shouldRun()` is true, or an injected\n * match-rule check per configured entry that is active. Built as {@link RuleRun} value objects\n * (name + thunk) so NO DAG member is `new`-ed — the validators and the checker are injected.\n */\n private activeRuns(root: string): RuleRun[] {\n const runs: RuleRun[] = [];\n for (const v of this.builtIns()) {\n if (v.shouldRun()) runs.push(new RuleRun(v.name, () => v.run(root)));\n }\n for (const mr of this.matchRules.rules) {\n if (this.matchChecker.shouldRun(mr)) runs.push(new RuleRun(mr.name, () => this.matchChecker.runForConfig(mr, root)));\n }\n return runs;\n }\n\n /**\n * Run all configured code validators against the workspace. A validator runs only when\n * `shouldRun()` is true (mode not OFF and no branch/epoch escape hatch). Per-run isolation lives\n * in {@link RuleReporter} so one validator can never abort the rest.\n */\n async run(): Promise<ExecutorResult> {\n const runs = this.activeRuns(this.workspace.path);\n if (runs.length === 0) {\n console.log('\\n⏭️ Skipping all code validations (all modes: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Running Code Validations\\n');\n console.log(` Active rules: ${runs.map((r: RuleRun) => r.name).join(', ')}`);\n console.log('');\n\n const result = await this.reporter.runValidators(runs);\n console.log(result.success ? '\\n✅ All code validations passed\\n' : '\\n❌ Some code validations failed\\n');\n return result;\n }\n}\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate No Custom CSS Executor
|
|
3
|
+
*
|
|
4
|
+
* Enforces Tailwind-first styling in Angular sources by banning hand-written CSS on CHANGED code:
|
|
5
|
+
* - `.ts` — `styles:` / `styleUrls:` / `styleUrl:` inside an `@Component({...})` decorator
|
|
6
|
+
* (detected via the TypeScript AST, so only the real decorator triggers it).
|
|
7
|
+
* - `.html` — a static `style="…"` attribute, a `[style]` / `[style.x]` binding, or `[ngStyle]`
|
|
8
|
+
* (line/regex scan — Angular templates have no TS AST).
|
|
9
|
+
*
|
|
10
|
+
* Unlike an ESLint rule (which lints whole files), this is diff-scoped: NEW_AND_MODIFIED_CODE only
|
|
11
|
+
* flags violations on changed lines, so a legacy Angular app is never retroactively flooded — the
|
|
12
|
+
* rule bites when a file is next edited.
|
|
13
|
+
*
|
|
14
|
+
* MODES: OFF | NEW_AND_MODIFIED_CODE (changed lines) | NEW_AND_MODIFIED_FILES (all in changed files).
|
|
15
|
+
*
|
|
16
|
+
* ESCAPE HATCH (a genuinely dynamic runtime value):
|
|
17
|
+
* .ts: // webpieces-disable no-custom-css -- <reason>
|
|
18
|
+
* .html: <!-- webpieces-disable no-custom-css -- <reason> -->
|
|
19
|
+
*/
|
|
20
|
+
import { NoCustomCssConfig } from '@webpieces/rules-config';
|
|
21
|
+
import { CodeValidator, ExecutorResult } from './code-validator';
|
|
22
|
+
export declare class CssViolation {
|
|
23
|
+
readonly file: string;
|
|
24
|
+
readonly line: number;
|
|
25
|
+
readonly column: number;
|
|
26
|
+
readonly detail: string;
|
|
27
|
+
constructor(file: string, line: number, column: number, detail: string);
|
|
28
|
+
}
|
|
29
|
+
export declare class CssHit {
|
|
30
|
+
readonly line: number;
|
|
31
|
+
readonly column: number;
|
|
32
|
+
readonly detail: string;
|
|
33
|
+
readonly hasDisableComment: boolean;
|
|
34
|
+
constructor(line: number, column: number, detail: string, hasDisableComment: boolean);
|
|
35
|
+
}
|
|
36
|
+
export declare class NoCustomCssValidator extends CodeValidator<NoCustomCssConfig> {
|
|
37
|
+
constructor(config: NoCustomCssConfig);
|
|
38
|
+
run(workspaceRoot: string): Promise<ExecutorResult>;
|
|
39
|
+
/** A changed file this rule cares about: an Angular .ts/.tsx or .html, excluding test files. */
|
|
40
|
+
isRelevantFile(file: string): boolean;
|
|
41
|
+
/** All banned-CSS hits in a single file (dispatches to the .ts AST or the .html line scan). */
|
|
42
|
+
findHitsForFile(file: string, workspaceRoot: string): CssHit[];
|
|
43
|
+
/** NEW_AND_MODIFIED_CODE: only hits whose line is in the diff hunks. */
|
|
44
|
+
private findViolationsForModifiedCode;
|
|
45
|
+
/** NEW_AND_MODIFIED_FILES: all hits in any changed file. */
|
|
46
|
+
private findViolationsForModifiedFiles;
|
|
47
|
+
/** Flag styles:/styleUrls:/styleUrl: properties inside an `@Component({...})` decorator. */
|
|
48
|
+
private findCustomCssInTsFile;
|
|
49
|
+
/** Recurse the AST; every `@Component(...)` decorator contributes its banned style props. */
|
|
50
|
+
private collectTsHits;
|
|
51
|
+
/** Pull the banned style props out of a single `@Component(...)` decorator node. */
|
|
52
|
+
private collectComponentStyleProps;
|
|
53
|
+
/** Flag inline style=, [style.x]/[style], and [ngStyle] in an Angular template via a line scan. */
|
|
54
|
+
private findCustomCssInHtmlFile;
|
|
55
|
+
/** The banned-pattern label for a template line, or '' when the line is clean. */
|
|
56
|
+
private detailForTemplateLine;
|
|
57
|
+
/** True if a `webpieces-disable no-custom-css` marker sits on this line or up to 3 lines above. */
|
|
58
|
+
private hasDisableComment;
|
|
59
|
+
private resolveMode;
|
|
60
|
+
private reportViolations;
|
|
61
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Validate No Custom CSS Executor
|
|
4
|
+
*
|
|
5
|
+
* Enforces Tailwind-first styling in Angular sources by banning hand-written CSS on CHANGED code:
|
|
6
|
+
* - `.ts` — `styles:` / `styleUrls:` / `styleUrl:` inside an `@Component({...})` decorator
|
|
7
|
+
* (detected via the TypeScript AST, so only the real decorator triggers it).
|
|
8
|
+
* - `.html` — a static `style="…"` attribute, a `[style]` / `[style.x]` binding, or `[ngStyle]`
|
|
9
|
+
* (line/regex scan — Angular templates have no TS AST).
|
|
10
|
+
*
|
|
11
|
+
* Unlike an ESLint rule (which lints whole files), this is diff-scoped: NEW_AND_MODIFIED_CODE only
|
|
12
|
+
* flags violations on changed lines, so a legacy Angular app is never retroactively flooded — the
|
|
13
|
+
* rule bites when a file is next edited.
|
|
14
|
+
*
|
|
15
|
+
* MODES: OFF | NEW_AND_MODIFIED_CODE (changed lines) | NEW_AND_MODIFIED_FILES (all in changed files).
|
|
16
|
+
*
|
|
17
|
+
* ESCAPE HATCH (a genuinely dynamic runtime value):
|
|
18
|
+
* .ts: // webpieces-disable no-custom-css -- <reason>
|
|
19
|
+
* .html: <!-- webpieces-disable no-custom-css -- <reason> -->
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.NoCustomCssValidator = exports.CssHit = exports.CssViolation = void 0;
|
|
23
|
+
const tslib_1 = require("tslib");
|
|
24
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
25
|
+
const path = tslib_1.__importStar(require("path"));
|
|
26
|
+
const ts = tslib_1.__importStar(require("typescript"));
|
|
27
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
28
|
+
const code_validator_1 = require("./code-validator");
|
|
29
|
+
const inversify_1 = require("inversify");
|
|
30
|
+
const resolve_mode_1 = require("./resolve-mode");
|
|
31
|
+
// @Component decorator properties that inject hand-written CSS.
|
|
32
|
+
const STYLE_PROPS = new Set(['styles', 'styleUrls', 'styleUrl']);
|
|
33
|
+
const TEST_PATHS = [/\.test\.ts$/, /\.spec\.ts$/, /__tests__\//];
|
|
34
|
+
// Template patterns. `[style.width]="x"` is preceded by `[`, so the inline-style regex's
|
|
35
|
+
// `(^|\s)style=` boundary keeps it apart from the binding forms.
|
|
36
|
+
const RE_INLINE_STYLE = /(^|\s)style\s*=\s*["']/;
|
|
37
|
+
const RE_NG_STYLE = /\[?ngStyle\]?\s*=/;
|
|
38
|
+
const RE_STYLE_BINDING = /\[style(?:\.[\w.-]+)?\]/;
|
|
39
|
+
class CssViolation {
|
|
40
|
+
file;
|
|
41
|
+
line;
|
|
42
|
+
column;
|
|
43
|
+
detail;
|
|
44
|
+
constructor(file, line, column, detail) {
|
|
45
|
+
this.file = file;
|
|
46
|
+
this.line = line;
|
|
47
|
+
this.column = column;
|
|
48
|
+
this.detail = detail;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.CssViolation = CssViolation;
|
|
52
|
+
class CssHit {
|
|
53
|
+
line;
|
|
54
|
+
column;
|
|
55
|
+
detail;
|
|
56
|
+
hasDisableComment;
|
|
57
|
+
constructor(line, column, detail, hasDisableComment) {
|
|
58
|
+
this.line = line;
|
|
59
|
+
this.column = column;
|
|
60
|
+
this.detail = detail;
|
|
61
|
+
this.hasDisableComment = hasDisableComment;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.CssHit = CssHit;
|
|
65
|
+
let NoCustomCssValidator = class NoCustomCssValidator extends code_validator_1.CodeValidator {
|
|
66
|
+
constructor(config) {
|
|
67
|
+
super(config, 'no-custom-css');
|
|
68
|
+
}
|
|
69
|
+
async run(workspaceRoot) {
|
|
70
|
+
const opts = this.config;
|
|
71
|
+
const mode = this.resolveMode(opts.mode ?? 'OFF', opts.ignoreModifiedUntilEpoch, opts.ignoreRuleWhileOnBranch);
|
|
72
|
+
const disableAllowed = opts.disableAllowed ?? true;
|
|
73
|
+
if (mode === 'OFF') {
|
|
74
|
+
console.log('\n⏭️ Skipping no-custom-css validation (mode: OFF)\n');
|
|
75
|
+
return { success: true };
|
|
76
|
+
}
|
|
77
|
+
console.log('\n📏 Validating No Custom CSS (Tailwind-first)\n');
|
|
78
|
+
console.log(` Mode: ${mode}`);
|
|
79
|
+
let base = process.env['NX_BASE'];
|
|
80
|
+
const head = process.env['NX_HEAD'];
|
|
81
|
+
if (!base) {
|
|
82
|
+
base = (0, rules_config_1.detectBase)(workspaceRoot) ?? undefined;
|
|
83
|
+
if (!base) {
|
|
84
|
+
console.log('\n⏭️ Skipping no-custom-css validation (could not detect base branch)\n');
|
|
85
|
+
return { success: true };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
console.log(` Base: ${base}`);
|
|
89
|
+
console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}\n`);
|
|
90
|
+
// tsOnly:false so changed .html templates are included; then filter to relevant files.
|
|
91
|
+
const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head, { tsOnly: false }).filter((f) => this.isRelevantFile(f));
|
|
92
|
+
if (changedFiles.length === 0) {
|
|
93
|
+
console.log('✅ No Angular .ts/.html files changed');
|
|
94
|
+
return { success: true };
|
|
95
|
+
}
|
|
96
|
+
console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
|
|
97
|
+
const violations = mode === 'NEW_AND_MODIFIED_CODE'
|
|
98
|
+
? this.findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed)
|
|
99
|
+
: this.findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);
|
|
100
|
+
if (violations.length === 0) {
|
|
101
|
+
console.log('✅ No custom CSS found');
|
|
102
|
+
return { success: true };
|
|
103
|
+
}
|
|
104
|
+
this.reportViolations(violations, mode);
|
|
105
|
+
return { success: false };
|
|
106
|
+
}
|
|
107
|
+
/** A changed file this rule cares about: an Angular .ts/.tsx or .html, excluding test files. */
|
|
108
|
+
isRelevantFile(file) {
|
|
109
|
+
if (TEST_PATHS.some((re) => re.test(file)))
|
|
110
|
+
return false;
|
|
111
|
+
return file.endsWith('.html') || file.endsWith('.ts') || file.endsWith('.tsx');
|
|
112
|
+
}
|
|
113
|
+
/** All banned-CSS hits in a single file (dispatches to the .ts AST or the .html line scan). */
|
|
114
|
+
findHitsForFile(file, workspaceRoot) {
|
|
115
|
+
if (file.endsWith('.html'))
|
|
116
|
+
return this.findCustomCssInHtmlFile(file, workspaceRoot);
|
|
117
|
+
if (file.endsWith('.ts') || file.endsWith('.tsx'))
|
|
118
|
+
return this.findCustomCssInTsFile(file, workspaceRoot);
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
/** NEW_AND_MODIFIED_CODE: only hits whose line is in the diff hunks. */
|
|
122
|
+
findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed) {
|
|
123
|
+
const violations = [];
|
|
124
|
+
for (const file of changedFiles) {
|
|
125
|
+
const changedLines = (0, rules_config_1.getChangedLineNumbers)((0, rules_config_1.getFileDiff)(workspaceRoot, file, base, head));
|
|
126
|
+
if (changedLines.size === 0)
|
|
127
|
+
continue;
|
|
128
|
+
for (const h of this.findHitsForFile(file, workspaceRoot)) {
|
|
129
|
+
if (disableAllowed && h.hasDisableComment)
|
|
130
|
+
continue;
|
|
131
|
+
if (!changedLines.has(h.line))
|
|
132
|
+
continue;
|
|
133
|
+
violations.push(new CssViolation(file, h.line, h.column, h.detail));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return violations;
|
|
137
|
+
}
|
|
138
|
+
/** NEW_AND_MODIFIED_FILES: all hits in any changed file. */
|
|
139
|
+
findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed) {
|
|
140
|
+
const violations = [];
|
|
141
|
+
for (const file of changedFiles) {
|
|
142
|
+
for (const h of this.findHitsForFile(file, workspaceRoot)) {
|
|
143
|
+
if (disableAllowed && h.hasDisableComment)
|
|
144
|
+
continue;
|
|
145
|
+
violations.push(new CssViolation(file, h.line, h.column, h.detail));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return violations;
|
|
149
|
+
}
|
|
150
|
+
/** Flag styles:/styleUrls:/styleUrl: properties inside an `@Component({...})` decorator. */
|
|
151
|
+
findCustomCssInTsFile(filePath, workspaceRoot) {
|
|
152
|
+
const fullPath = path.join(workspaceRoot, filePath);
|
|
153
|
+
if (!fs.existsSync(fullPath))
|
|
154
|
+
return [];
|
|
155
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
156
|
+
const fileLines = content.split('\n');
|
|
157
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
158
|
+
const hits = [];
|
|
159
|
+
this.collectTsHits(sourceFile, sourceFile, fileLines, hits);
|
|
160
|
+
return hits;
|
|
161
|
+
}
|
|
162
|
+
/** Recurse the AST; every `@Component(...)` decorator contributes its banned style props. */
|
|
163
|
+
collectTsHits(node, sourceFile, fileLines, hits) {
|
|
164
|
+
if (ts.isDecorator(node))
|
|
165
|
+
this.collectComponentStyleProps(node, sourceFile, fileLines, hits);
|
|
166
|
+
ts.forEachChild(node, (child) => this.collectTsHits(child, sourceFile, fileLines, hits));
|
|
167
|
+
}
|
|
168
|
+
/** Pull the banned style props out of a single `@Component(...)` decorator node. */
|
|
169
|
+
collectComponentStyleProps(node, sourceFile, fileLines, hits) {
|
|
170
|
+
const expr = node.expression;
|
|
171
|
+
if (!ts.isCallExpression(expr) || !ts.isIdentifier(expr.expression) || expr.expression.text !== 'Component')
|
|
172
|
+
return;
|
|
173
|
+
const arg = expr.arguments[0];
|
|
174
|
+
if (!arg || !ts.isObjectLiteralExpression(arg))
|
|
175
|
+
return;
|
|
176
|
+
for (const prop of arg.properties) {
|
|
177
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
178
|
+
continue;
|
|
179
|
+
const name = prop.name.text;
|
|
180
|
+
if (!STYLE_PROPS.has(name))
|
|
181
|
+
continue;
|
|
182
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(prop.getStart(sourceFile));
|
|
183
|
+
const line = pos.line + 1;
|
|
184
|
+
hits.push(new CssHit(line, pos.character + 1, `\`${name}\` block in @Component`, this.hasDisableComment(fileLines, line)));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/** Flag inline style=, [style.x]/[style], and [ngStyle] in an Angular template via a line scan. */
|
|
188
|
+
findCustomCssInHtmlFile(filePath, workspaceRoot) {
|
|
189
|
+
const fullPath = path.join(workspaceRoot, filePath);
|
|
190
|
+
if (!fs.existsSync(fullPath))
|
|
191
|
+
return [];
|
|
192
|
+
const fileLines = fs.readFileSync(fullPath, 'utf-8').split('\n');
|
|
193
|
+
const hits = [];
|
|
194
|
+
for (let i = 0; i < fileLines.length; i++) {
|
|
195
|
+
const detail = this.detailForTemplateLine(fileLines[i] ?? '');
|
|
196
|
+
if (!detail)
|
|
197
|
+
continue;
|
|
198
|
+
const line = i + 1;
|
|
199
|
+
hits.push(new CssHit(line, 1, detail, this.hasDisableComment(fileLines, line)));
|
|
200
|
+
}
|
|
201
|
+
return hits;
|
|
202
|
+
}
|
|
203
|
+
/** The banned-pattern label for a template line, or '' when the line is clean. */
|
|
204
|
+
detailForTemplateLine(raw) {
|
|
205
|
+
if (RE_NG_STYLE.test(raw))
|
|
206
|
+
return 'inline `[ngStyle]`';
|
|
207
|
+
if (RE_STYLE_BINDING.test(raw))
|
|
208
|
+
return 'inline `[style.x]` binding';
|
|
209
|
+
if (RE_INLINE_STYLE.test(raw))
|
|
210
|
+
return 'inline `style=` attribute';
|
|
211
|
+
return '';
|
|
212
|
+
}
|
|
213
|
+
/** True if a `webpieces-disable no-custom-css` marker sits on this line or up to 3 lines above. */
|
|
214
|
+
hasDisableComment(lines, lineNumber) {
|
|
215
|
+
const start = Math.max(0, lineNumber - 4);
|
|
216
|
+
for (let i = lineNumber - 1; i >= start; i--) {
|
|
217
|
+
if ((0, rules_config_1.hasDisable)(lines[i] ?? '', rules_config_1.RULE_NAMES.NO_CUSTOM_CSS))
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
resolveMode(normalMode, epoch, branchPattern) {
|
|
223
|
+
if (normalMode === 'OFF')
|
|
224
|
+
return normalMode;
|
|
225
|
+
const skip = (0, resolve_mode_1.shouldSkipRule)(epoch, branchPattern);
|
|
226
|
+
if (skip.skip) {
|
|
227
|
+
console.log(`\n⏭️ Skipping no-custom-css validation (${skip.reason})\n`);
|
|
228
|
+
return 'OFF';
|
|
229
|
+
}
|
|
230
|
+
return normalMode;
|
|
231
|
+
}
|
|
232
|
+
reportViolations(violations, mode) {
|
|
233
|
+
console.error('');
|
|
234
|
+
console.error('❌ Custom CSS found in Angular source! Style with Tailwind utility classes instead.');
|
|
235
|
+
console.error('');
|
|
236
|
+
console.error('📚 Tailwind-first keeps styling in the template, greppable and consistent:');
|
|
237
|
+
console.error(' BAD: styles: [".card { display: flex; gap: 1rem; }"]');
|
|
238
|
+
console.error(' GOOD: class="flex gap-4"');
|
|
239
|
+
console.error(' BAD: <div style="width: 240px"> | [style.width] | [ngStyle]');
|
|
240
|
+
console.error(' GOOD: <div class="w-[240px]"> | [class.x] toggles');
|
|
241
|
+
console.error('');
|
|
242
|
+
for (const v of violations) {
|
|
243
|
+
console.error(` ❌ ${v.file}:${v.line}:${v.column}`);
|
|
244
|
+
console.error(` ${v.detail}`);
|
|
245
|
+
}
|
|
246
|
+
console.error('');
|
|
247
|
+
console.error(' To fix: delete the CSS and use Tailwind utility classes (arbitrary values like');
|
|
248
|
+
console.error(' `bg-[#fffde7]` / `grid-cols-[2fr_2fr_2fr_48px]` cover the long tail).');
|
|
249
|
+
console.error('');
|
|
250
|
+
console.error(' Escape hatch (genuinely dynamic runtime value, use sparingly):');
|
|
251
|
+
console.error(' .ts // webpieces-disable no-custom-css -- <reason>');
|
|
252
|
+
console.error(' .html <!-- webpieces-disable no-custom-css -- <reason> -->');
|
|
253
|
+
console.error('');
|
|
254
|
+
console.error(` Current mode: ${mode}`);
|
|
255
|
+
console.error('');
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
exports.NoCustomCssValidator = NoCustomCssValidator;
|
|
259
|
+
exports.NoCustomCssValidator = NoCustomCssValidator = tslib_1.__decorate([
|
|
260
|
+
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
|
|
261
|
+
tslib_1.__metadata("design:paramtypes", [rules_config_1.NoCustomCssConfig])
|
|
262
|
+
], NoCustomCssValidator);
|
|
263
|
+
//# sourceMappingURL=validate-no-custom-css.js.map
|
|
@@ -0,0 +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');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n const opts = this.config;\n const mode = this.resolveMode(opts.mode ?? 'OFF', opts.ignoreModifiedUntilEpoch, opts.ignoreRuleWhileOnBranch);\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"]}
|