@webpieces/code-rules 0.3.232 → 0.3.234

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.3.232",
3
+ "version": "0.3.234",
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.3.232"
23
+ "@webpieces/rules-config": "0.3.234"
24
24
  },
25
25
  "publishConfig": {
26
26
  "access": "public"
@@ -16,6 +16,7 @@ const validate_catch_error_pattern_1 = require("./validate-catch-error-pattern")
16
16
  const validate_no_unmanaged_exceptions_1 = require("./validate-no-unmanaged-exceptions");
17
17
  const validate_no_direct_api_resolver_1 = require("./validate-no-direct-api-resolver");
18
18
  const validate_no_symbol_di_tokens_1 = require("./validate-no-symbol-di-tokens");
19
+ const validate_match_rules_1 = require("./validate-match-rules");
19
20
  /**
20
21
  * Build every code validator from the typed WebpiecesRulesConfig. Each validator is
21
22
  * constructed with its own `*Config` (the genuinely-consumed config class), so the
@@ -23,7 +24,7 @@ const validate_no_symbol_di_tokens_1 = require("./validate-no-symbol-di-tokens")
23
24
  * config instance — `loadAndValidate` already validates that configured rules
24
25
  * carry an explicit `mode`, so in practice every key is present.
25
26
  */
26
- function buildValidators(config) {
27
+ function buildValidators(config, matchRules) {
27
28
  return [
28
29
  new validate_modified_methods_1.MaxMethodLinesValidator(config['max-method-lines'] ?? new rules_config_1.MaxMethodLinesConfig()),
29
30
  new validate_modified_files_1.MaxFileLinesValidator(config['max-file-lines'] ?? new rules_config_1.MaxFileLinesConfig()),
@@ -38,6 +39,8 @@ function buildValidators(config) {
38
39
  new validate_no_unmanaged_exceptions_1.NoUnmanagedExceptionsValidator(config['no-unmanaged-exceptions'] ?? new rules_config_1.NoUnmanagedExceptionsConfig()),
39
40
  new validate_no_direct_api_resolver_1.NoDirectApiResolverValidator(config['angular-no-direct-api-in-resolver'] ?? new rules_config_1.AngularNoDirectApiInResolverConfig()),
40
41
  new validate_no_symbol_di_tokens_1.NoSymbolDiTokensValidator(config['no-symbol-di-tokens'] ?? new rules_config_1.NoSymbolDiTokensConfig()),
42
+ // One validator per client-authored match-rules entry (each with its own name/mode/epoch).
43
+ ...matchRules.map((mr) => new validate_match_rules_1.MatchRulesValidator(mr)),
41
44
  ];
42
45
  }
43
46
  /**
@@ -55,7 +58,7 @@ async function runValidator(workspaceRoot) {
55
58
  return { success: false };
56
59
  }
57
60
  console.log(`\nšŸ“„ Loaded config: ${loaded.configPath}`);
58
- const validators = buildValidators(loaded.rulesConfig);
61
+ const validators = buildValidators(loaded.rulesConfig, loaded.matchRules);
59
62
  const active = validators.filter((v) => v.shouldRun());
60
63
  if (active.length === 0) {
61
64
  console.log('\nā­ļø Skipping all code validations (all modes: OFF)\n');
@@ -1 +1 @@
1
- {"version":3,"file":"validate-code.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-code.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n loadAndValidate,\n WebpiecesRulesConfig,\n BaseRuleConfig,\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} from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { runValidators } from './rule-reporter';\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';\n\nexport { ExecutorResult } from './code-validator';\n\n/**\n * Build every code validator from the typed WebpiecesRulesConfig. Each validator is\n * constructed with its own `*Config` (the genuinely-consumed config class), so the\n * config types are exercised at compile time. Missing rule keys fall back to an empty\n * config instance — `loadAndValidate` already validates that configured rules\n * carry an explicit `mode`, so in practice every key is present.\n */\nfunction buildValidators(config: WebpiecesRulesConfig): CodeValidator<BaseRuleConfig>[] {\n return [\n new MaxMethodLinesValidator(config['max-method-lines'] ?? new MaxMethodLinesConfig()),\n new MaxFileLinesValidator(config['max-file-lines'] ?? new MaxFileLinesConfig()),\n new RequireReturnTypeValidator(config['require-return-type'] ?? new RequireReturnTypeConfig()),\n new NoInlineTypeLiteralsValidator(config['no-inline-type-literals'] ?? new NoInlineTypeLiteralsConfig()),\n new NoAnyUnknownValidator(config['no-any-unknown'] ?? new NoAnyUnknownConfig()),\n new NoImplicitAnyValidator(config['no-implicit-any'] ?? new NoImplicitAnyConfig()),\n new PrismaValidateDtosValidator(config['prisma-validate-dtos'] ?? new PrismaValidateDtosConfig()),\n new PrismaConverterValidator(config['prisma-converter'] ?? new PrismaConverterConfig()),\n new NoDestructureValidator(config['no-destructure'] ?? new NoDestructureConfig()),\n new CatchErrorPatternValidator(config['catch-error-pattern'] ?? new CatchErrorPatternConfig()),\n new NoUnmanagedExceptionsValidator(config['no-unmanaged-exceptions'] ?? new NoUnmanagedExceptionsConfig()),\n new NoDirectApiResolverValidator(config['angular-no-direct-api-in-resolver'] ?? new AngularNoDirectApiInResolverConfig()),\n new NoSymbolDiTokensValidator(config['no-symbol-di-tokens'] ?? new NoSymbolDiTokensConfig()),\n ];\n}\n\n/**\n * Run all configured code validators against the workspace.\n *\n * Owns config loading: callers just pass the workspace root. Config comes from\n * webpieces.config.json (loaded via @webpieces/rules-config so ai-hooks and this\n * executor agree on every rule's enabled/mode/options). A validator runs only when\n * `shouldRun()` is true (i.e. its mode is not OFF and no branch/epoch escape hatch matches).\n */\nexport default async function runValidator(workspaceRoot: string): Promise<ExecutorResult> {\n const loaded = loadAndValidate(workspaceRoot);\n if (loaded.configPath === null) {\n console.error('\\nāŒ No webpieces.config.json found at workspace root (or any ancestor).\\n');\n return { success: false };\n }\n\n console.log(`\\nšŸ“„ Loaded config: ${loaded.configPath}`);\n\n const validators = buildValidators(loaded.rulesConfig);\n const active = validators.filter((v: CodeValidator<BaseRuleConfig>) => v.shouldRun());\n\n if (active.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: ${active.map((v: CodeValidator<BaseRuleConfig>) => v.name).join(', ')}`);\n console.log('');\n\n // Per-validator isolation lives in runValidators: a bug in one validator no longer aborts the\n // rest, and a thrown RuleFailError is reported like any other failure.\n const result = await runValidators(active, workspaceRoot);\n const allSuccess = result.success;\n\n console.log(allSuccess ? '\\nāœ… All code validations passed\\n' : '\\nāŒ Some code validations failed\\n');\n return { success: allSuccess };\n}\n"]}
1
+ {"version":3,"file":"validate-code.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-code.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n loadAndValidate,\n WebpiecesRulesConfig,\n BaseRuleConfig,\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} from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { runValidators } from './rule-reporter';\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 { MatchRulesValidator } from './validate-match-rules';\nimport { MatchRuleConfig } from '@webpieces/rules-config';\n\nexport { ExecutorResult } from './code-validator';\n\n/**\n * Build every code validator from the typed WebpiecesRulesConfig. Each validator is\n * constructed with its own `*Config` (the genuinely-consumed config class), so the\n * config types are exercised at compile time. Missing rule keys fall back to an empty\n * config instance — `loadAndValidate` already validates that configured rules\n * carry an explicit `mode`, so in practice every key is present.\n */\nfunction buildValidators(\n config: WebpiecesRulesConfig,\n matchRules: readonly MatchRuleConfig[],\n): CodeValidator<BaseRuleConfig>[] {\n return [\n new MaxMethodLinesValidator(config['max-method-lines'] ?? new MaxMethodLinesConfig()),\n new MaxFileLinesValidator(config['max-file-lines'] ?? new MaxFileLinesConfig()),\n new RequireReturnTypeValidator(config['require-return-type'] ?? new RequireReturnTypeConfig()),\n new NoInlineTypeLiteralsValidator(config['no-inline-type-literals'] ?? new NoInlineTypeLiteralsConfig()),\n new NoAnyUnknownValidator(config['no-any-unknown'] ?? new NoAnyUnknownConfig()),\n new NoImplicitAnyValidator(config['no-implicit-any'] ?? new NoImplicitAnyConfig()),\n new PrismaValidateDtosValidator(config['prisma-validate-dtos'] ?? new PrismaValidateDtosConfig()),\n new PrismaConverterValidator(config['prisma-converter'] ?? new PrismaConverterConfig()),\n new NoDestructureValidator(config['no-destructure'] ?? new NoDestructureConfig()),\n new CatchErrorPatternValidator(config['catch-error-pattern'] ?? new CatchErrorPatternConfig()),\n new NoUnmanagedExceptionsValidator(config['no-unmanaged-exceptions'] ?? new NoUnmanagedExceptionsConfig()),\n new NoDirectApiResolverValidator(config['angular-no-direct-api-in-resolver'] ?? new AngularNoDirectApiInResolverConfig()),\n new NoSymbolDiTokensValidator(config['no-symbol-di-tokens'] ?? new NoSymbolDiTokensConfig()),\n // One validator per client-authored match-rules entry (each with its own name/mode/epoch).\n ...matchRules.map((mr: MatchRuleConfig) => new MatchRulesValidator(mr)),\n ];\n}\n\n/**\n * Run all configured code validators against the workspace.\n *\n * Owns config loading: callers just pass the workspace root. Config comes from\n * webpieces.config.json (loaded via @webpieces/rules-config so ai-hooks and this\n * executor agree on every rule's enabled/mode/options). A validator runs only when\n * `shouldRun()` is true (i.e. its mode is not OFF and no branch/epoch escape hatch matches).\n */\nexport default async function runValidator(workspaceRoot: string): Promise<ExecutorResult> {\n const loaded = loadAndValidate(workspaceRoot);\n if (loaded.configPath === null) {\n console.error('\\nāŒ No webpieces.config.json found at workspace root (or any ancestor).\\n');\n return { success: false };\n }\n\n console.log(`\\nšŸ“„ Loaded config: ${loaded.configPath}`);\n\n const validators = buildValidators(loaded.rulesConfig, loaded.matchRules);\n const active = validators.filter((v: CodeValidator<BaseRuleConfig>) => v.shouldRun());\n\n if (active.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: ${active.map((v: CodeValidator<BaseRuleConfig>) => v.name).join(', ')}`);\n console.log('');\n\n // Per-validator isolation lives in runValidators: a bug in one validator no longer aborts the\n // rest, and a thrown RuleFailError is reported like any other failure.\n const result = await runValidators(active, workspaceRoot);\n const allSuccess = result.success;\n\n console.log(allSuccess ? '\\nāœ… All code validations passed\\n' : '\\nāŒ Some code validations failed\\n');\n return { success: allSuccess };\n}\n"]}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Validate a single `match-rules` entry (a client-authored content guard) at build time.
3
+ *
4
+ * One MatchRulesValidator is constructed PER entry of the `match-rules` array (see validate-code.ts),
5
+ * so `shouldRun()` / mode / epoch apply per entry and the report shows the entry name. The regex
6
+ * matching + allowedPath/test-file exemption is shared with the ai-hook engine via
7
+ * findMatchRuleViolations; this validator adds diff-scoping (mode), the `// webpieces-disable <name>`
8
+ * filter, and the console report (renderMatchRuleMessage — the same text the ai-hook FixHint renders).
9
+ */
10
+ import { MatchRuleConfig } from '@webpieces/rules-config';
11
+ import { CodeValidator, ExecutorResult } from './code-validator';
12
+ export declare class MatchViolationInfo {
13
+ readonly line: number;
14
+ readonly context: string;
15
+ readonly hasDisableComment: boolean;
16
+ constructor(line: number, context: string, hasDisableComment: boolean);
17
+ }
18
+ export declare function findViolationsInFile(filePath: string, workspaceRoot: string, config: MatchRuleConfig): MatchViolationInfo[];
19
+ export declare class MatchRulesValidator extends CodeValidator<MatchRuleConfig> {
20
+ constructor(config: MatchRuleConfig);
21
+ run(workspaceRoot: string): Promise<ExecutorResult>;
22
+ }
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ /**
3
+ * Validate a single `match-rules` entry (a client-authored content guard) at build time.
4
+ *
5
+ * One MatchRulesValidator is constructed PER entry of the `match-rules` array (see validate-code.ts),
6
+ * so `shouldRun()` / mode / epoch apply per entry and the report shows the entry name. The regex
7
+ * matching + allowedPath/test-file exemption is shared with the ai-hook engine via
8
+ * findMatchRuleViolations; this validator adds diff-scoping (mode), the `// webpieces-disable <name>`
9
+ * filter, and the console report (renderMatchRuleMessage — the same text the ai-hook FixHint renders).
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MatchRulesValidator = exports.MatchViolationInfo = void 0;
13
+ exports.findViolationsInFile = findViolationsInFile;
14
+ const tslib_1 = require("tslib");
15
+ const fs = tslib_1.__importStar(require("fs"));
16
+ const path = tslib_1.__importStar(require("path"));
17
+ const rules_config_1 = require("@webpieces/rules-config");
18
+ const code_validator_1 = require("./code-validator");
19
+ const resolve_mode_1 = require("./resolve-mode");
20
+ class MatchViolation {
21
+ file;
22
+ line;
23
+ context;
24
+ constructor(file, line, context) {
25
+ this.file = file;
26
+ this.line = line;
27
+ this.context = context;
28
+ }
29
+ }
30
+ class MatchViolationInfo {
31
+ line;
32
+ context;
33
+ hasDisableComment;
34
+ constructor(line, context, hasDisableComment) {
35
+ this.line = line;
36
+ this.context = context;
37
+ this.hasDisableComment = hasDisableComment;
38
+ }
39
+ }
40
+ exports.MatchViolationInfo = MatchViolationInfo;
41
+ function findViolationsInFile(filePath, workspaceRoot, config) {
42
+ const fullPath = path.join(workspaceRoot, filePath);
43
+ if (!fs.existsSync(fullPath))
44
+ return [];
45
+ const content = fs.readFileSync(fullPath, 'utf-8');
46
+ const lines = content.split('\n');
47
+ // Shared engine applies allowedPaths + test-file exemption and regex matching.
48
+ const hits = (0, rules_config_1.findMatchRuleViolations)(lines, filePath, config);
49
+ const disableAllowed = config.disableAllowed ?? true;
50
+ return hits.map((hit) => {
51
+ const line = lines[hit.line - 1] ?? '';
52
+ const prevLine = hit.line > 1 ? (lines[hit.line - 2] ?? '') : '';
53
+ const disabled = (0, rules_config_1.hasDisable)(line, config.name) || (0, rules_config_1.hasDisable)(prevLine, config.name);
54
+ return new MatchViolationInfo(hit.line, hit.context, disableAllowed && disabled);
55
+ });
56
+ }
57
+ function findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, config) {
58
+ const violations = [];
59
+ for (const file of changedFiles) {
60
+ const diff = (0, rules_config_1.getFileDiff)(workspaceRoot, file, base, head);
61
+ const changedLines = (0, rules_config_1.getChangedLineNumbers)(diff);
62
+ if (changedLines.size === 0)
63
+ continue;
64
+ for (const v of findViolationsInFile(file, workspaceRoot, config)) {
65
+ if (v.hasDisableComment)
66
+ continue;
67
+ if (!changedLines.has(v.line))
68
+ continue;
69
+ violations.push(new MatchViolation(file, v.line, v.context));
70
+ }
71
+ }
72
+ return violations;
73
+ }
74
+ function findViolationsForModifiedFiles(workspaceRoot, changedFiles, config) {
75
+ const violations = [];
76
+ for (const file of changedFiles) {
77
+ for (const v of findViolationsInFile(file, workspaceRoot, config)) {
78
+ if (v.hasDisableComment)
79
+ continue;
80
+ violations.push(new MatchViolation(file, v.line, v.context));
81
+ }
82
+ }
83
+ return violations;
84
+ }
85
+ function reportViolations(config, violations, mode) {
86
+ console.error('');
87
+ console.error(`āŒ [${config.name}] matched disallowed pattern(s)!`);
88
+ console.error('');
89
+ console.error((0, rules_config_1.renderMatchRuleMessage)(config));
90
+ console.error('');
91
+ for (const v of violations) {
92
+ console.error(` āŒ ${v.file}:${v.line}`);
93
+ console.error(` ${v.context}`);
94
+ }
95
+ console.error('');
96
+ console.error(` Current mode: ${mode}`);
97
+ console.error('');
98
+ }
99
+ function resolveMode(config) {
100
+ const normalMode = config.mode ?? 'OFF';
101
+ if (normalMode === 'OFF')
102
+ return normalMode;
103
+ const skip = (0, resolve_mode_1.shouldSkipRule)(config.ignoreModifiedUntilEpoch, config.ignoreRuleWhileOnBranch);
104
+ if (skip.skip) {
105
+ console.log(`\nā­ļø Skipping ${config.name} validation (${skip.reason})`);
106
+ console.log('');
107
+ return 'OFF';
108
+ }
109
+ return normalMode;
110
+ }
111
+ async function runValidatorImpl(config, workspaceRoot) {
112
+ const mode = resolveMode(config);
113
+ if (mode === 'OFF') {
114
+ console.log(`\nā­ļø Skipping ${config.name} validation (mode: OFF)`);
115
+ console.log('');
116
+ return { success: true };
117
+ }
118
+ console.log(`\nšŸ“ Validating match-rule: ${config.name}\n`);
119
+ console.log(` Mode: ${mode}`);
120
+ let base = process.env['NX_BASE'];
121
+ const head = process.env['NX_HEAD'];
122
+ if (!base) {
123
+ base = (0, rules_config_1.detectBase)(workspaceRoot) ?? undefined;
124
+ if (!base) {
125
+ console.log(`\nā­ļø Skipping ${config.name} validation (could not detect base branch)`);
126
+ console.log('');
127
+ return { success: true };
128
+ }
129
+ }
130
+ console.log(` Base: ${base}`);
131
+ console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
132
+ console.log('');
133
+ const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head);
134
+ if (changedFiles.length === 0) {
135
+ console.log('āœ… No TypeScript files changed');
136
+ return { success: true };
137
+ }
138
+ console.log(`šŸ“‚ Checking ${changedFiles.length} changed file(s)...`);
139
+ let violations = [];
140
+ if (mode === 'NEW_AND_MODIFIED_CODE') {
141
+ violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, config);
142
+ }
143
+ else if (mode === 'NEW_AND_MODIFIED_FILES') {
144
+ violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, config);
145
+ }
146
+ if (violations.length === 0) {
147
+ console.log(`āœ… No ${config.name} violations found`);
148
+ return { success: true };
149
+ }
150
+ reportViolations(config, violations, mode);
151
+ return { success: false };
152
+ }
153
+ class MatchRulesValidator extends code_validator_1.CodeValidator {
154
+ constructor(config) {
155
+ super(config, config.name);
156
+ }
157
+ async run(workspaceRoot) {
158
+ return runValidatorImpl(this.config, workspaceRoot);
159
+ }
160
+ }
161
+ exports.MatchRulesValidator = MatchRulesValidator;
162
+ //# sourceMappingURL=validate-match-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-match-rules.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-match-rules.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate a single `match-rules` entry (a client-authored content guard) at build time.\n *\n * One MatchRulesValidator is constructed PER entry of the `match-rules` array (see validate-code.ts),\n * so `shouldRun()` / mode / epoch apply per entry and the report shows the entry name. The regex\n * matching + allowedPath/test-file exemption is shared with the ai-hook engine via\n * findMatchRuleViolations; this validator adds diff-scoping (mode), the `// webpieces-disable <name>`\n * filter, and the console report (renderMatchRuleMessage — the same text the ai-hook FixHint renders).\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n hasDisable,\n MatchRuleConfig,\n MatchRuleViolation,\n findMatchRuleViolations,\n renderMatchRuleMessage,\n ModifiedCodeMode,\n detectBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n} from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { shouldSkipRule } from './resolve-mode';\n\nclass MatchViolation {\n readonly file: string;\n readonly line: number;\n readonly context: string;\n\n constructor(file: string, line: number, context: string) {\n this.file = file;\n this.line = line;\n this.context = context;\n }\n}\n\nexport class MatchViolationInfo {\n readonly line: number;\n readonly context: string;\n readonly hasDisableComment: boolean;\n\n constructor(line: number, context: string, hasDisableComment: boolean) {\n this.line = line;\n this.context = context;\n this.hasDisableComment = hasDisableComment;\n }\n}\n\nexport function findViolationsInFile(\n filePath: string,\n workspaceRoot: string,\n config: MatchRuleConfig,\n): MatchViolationInfo[] {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const lines = content.split('\\n');\n\n // Shared engine applies allowedPaths + test-file exemption and regex matching.\n const hits = findMatchRuleViolations(lines, filePath, config);\n const disableAllowed = config.disableAllowed ?? true;\n\n return hits.map((hit: MatchRuleViolation) => {\n const line = lines[hit.line - 1] ?? '';\n const prevLine = hit.line > 1 ? (lines[hit.line - 2] ?? '') : '';\n const disabled = hasDisable(line, config.name) || hasDisable(prevLine, config.name);\n return new MatchViolationInfo(hit.line, hit.context, disableAllowed && disabled);\n });\n}\n\nfunction findViolationsForModifiedCode(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n head: string | undefined,\n config: MatchRuleConfig,\n): MatchViolation[] {\n const violations: MatchViolation[] = [];\n for (const file of changedFiles) {\n const diff = getFileDiff(workspaceRoot, file, base, head);\n const changedLines = getChangedLineNumbers(diff);\n if (changedLines.size === 0) continue;\n\n for (const v of findViolationsInFile(file, workspaceRoot, config)) {\n if (v.hasDisableComment) continue;\n if (!changedLines.has(v.line)) continue;\n violations.push(new MatchViolation(file, v.line, v.context));\n }\n }\n return violations;\n}\n\nfunction findViolationsForModifiedFiles(\n workspaceRoot: string,\n changedFiles: string[],\n config: MatchRuleConfig,\n): MatchViolation[] {\n const violations: MatchViolation[] = [];\n for (const file of changedFiles) {\n for (const v of findViolationsInFile(file, workspaceRoot, config)) {\n if (v.hasDisableComment) continue;\n violations.push(new MatchViolation(file, v.line, v.context));\n }\n }\n return violations;\n}\n\nfunction reportViolations(config: MatchRuleConfig, violations: MatchViolation[], mode: ModifiedCodeMode): void {\n console.error('');\n console.error(`āŒ [${config.name}] matched disallowed pattern(s)!`);\n console.error('');\n console.error(renderMatchRuleMessage(config));\n console.error('');\n for (const v of violations) {\n console.error(` āŒ ${v.file}:${v.line}`);\n console.error(` ${v.context}`);\n }\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n}\n\nfunction resolveMode(config: MatchRuleConfig): ModifiedCodeMode {\n const normalMode: ModifiedCodeMode = config.mode ?? 'OFF';\n if (normalMode === 'OFF') return normalMode;\n const skip = shouldSkipRule(config.ignoreModifiedUntilEpoch, config.ignoreRuleWhileOnBranch);\n if (skip.skip) {\n console.log(`\\nā­ļø Skipping ${config.name} validation (${skip.reason})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\nasync function runValidatorImpl(config: MatchRuleConfig, workspaceRoot: string): Promise<ExecutorResult> {\n const mode = resolveMode(config);\n if (mode === 'OFF') {\n console.log(`\\nā­ļø Skipping ${config.name} validation (mode: OFF)`);\n console.log('');\n return { success: true };\n }\n\n console.log(`\\nšŸ“ Validating match-rule: ${config.name}\\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 ${config.name} validation (could not detect base branch)`);\n console.log('');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log('');\n\n const changedFiles = getChangedFiles(workspaceRoot, base, head);\n if (changedFiles.length === 0) {\n console.log('āœ… No TypeScript files changed');\n return { success: true };\n }\n\n console.log(`šŸ“‚ Checking ${changedFiles.length} changed file(s)...`);\n\n let violations: MatchViolation[] = [];\n if (mode === 'NEW_AND_MODIFIED_CODE') {\n violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, config);\n } else if (mode === 'NEW_AND_MODIFIED_FILES') {\n violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, config);\n }\n\n if (violations.length === 0) {\n console.log(`āœ… No ${config.name} violations found`);\n return { success: true };\n }\n\n reportViolations(config, violations, mode);\n return { success: false };\n}\n\nexport class MatchRulesValidator extends CodeValidator<MatchRuleConfig> {\n constructor(config: MatchRuleConfig) {\n super(config, config.name);\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runValidatorImpl(this.config, workspaceRoot);\n }\n}\n"]}