@webpieces/code-rules 0.3.311 → 0.3.312

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.311",
3
+ "version": "0.3.312",
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.311"
23
+ "@webpieces/rules-config": "0.3.312"
24
24
  },
25
25
  "publishConfig": {
26
26
  "access": "public"
package/src/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export { NoDestructureValidator } from './validate-no-destructure';
12
12
  export { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';
13
13
  export { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';
14
14
  export { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';
15
+ export { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';
15
16
  export { FrameworkTagValidator } from './validate-framework-tag';
16
17
  export { RoleTagValidator } from './validate-role-tag';
17
18
  export { PrismaValidateDtosValidator } from './validate-dtos';
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateCode = exports.PrismaConverterValidator = exports.PrismaValidateDtosValidator = exports.RoleTagValidator = exports.FrameworkTagValidator = exports.NoProcessExitOutsideMainValidator = exports.NoSymbolDiTokensValidator = exports.NoDirectApiResolverValidator = exports.NoDestructureValidator = exports.NoUnmanagedExceptionsValidator = exports.CatchErrorPatternValidator = exports.NoInlineTypeLiteralsValidator = exports.RequireReturnTypeValidator = exports.MaxFileLinesValidator = exports.runNewMethods = exports.runModifiedMethods = exports.MaxMethodLinesValidator = exports.NoImplicitAnyValidator = exports.NoAnyUnknownValidator = exports.CodeValidator = void 0;
3
+ exports.validateCode = exports.PrismaConverterValidator = exports.PrismaValidateDtosValidator = exports.RoleTagValidator = exports.FrameworkTagValidator = exports.NoFunctionOutsideClassValidator = exports.NoProcessExitOutsideMainValidator = exports.NoSymbolDiTokensValidator = exports.NoDirectApiResolverValidator = exports.NoDestructureValidator = exports.NoUnmanagedExceptionsValidator = exports.CatchErrorPatternValidator = exports.NoInlineTypeLiteralsValidator = exports.RequireReturnTypeValidator = exports.MaxFileLinesValidator = exports.runNewMethods = exports.runModifiedMethods = exports.MaxMethodLinesValidator = exports.NoImplicitAnyValidator = exports.NoAnyUnknownValidator = exports.CodeValidator = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  var code_validator_1 = require("./code-validator");
6
6
  Object.defineProperty(exports, "CodeValidator", { enumerable: true, get: function () { return code_validator_1.CodeValidator; } });
@@ -31,6 +31,8 @@ var validate_no_symbol_di_tokens_1 = require("./validate-no-symbol-di-tokens");
31
31
  Object.defineProperty(exports, "NoSymbolDiTokensValidator", { enumerable: true, get: function () { return validate_no_symbol_di_tokens_1.NoSymbolDiTokensValidator; } });
32
32
  var validate_no_process_exit_outside_main_1 = require("./validate-no-process-exit-outside-main");
33
33
  Object.defineProperty(exports, "NoProcessExitOutsideMainValidator", { enumerable: true, get: function () { return validate_no_process_exit_outside_main_1.NoProcessExitOutsideMainValidator; } });
34
+ var validate_no_function_outside_class_1 = require("./validate-no-function-outside-class");
35
+ Object.defineProperty(exports, "NoFunctionOutsideClassValidator", { enumerable: true, get: function () { return validate_no_function_outside_class_1.NoFunctionOutsideClassValidator; } });
34
36
  var validate_framework_tag_1 = require("./validate-framework-tag");
35
37
  Object.defineProperty(exports, "FrameworkTagValidator", { enumerable: true, get: function () { return validate_framework_tag_1.FrameworkTagValidator; } });
36
38
  var validate_role_tag_1 = require("./validate-role-tag");
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,mDAAiE;AAAxD,+GAAA,aAAa,OAAA;AAEtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,uEAAoE;AAA3D,kIAAA,sBAAsB,OAAA;AAC/B,yEAA0F;AAAjF,oIAAA,uBAAuB,OAAA;AAAE,+HAAA,kBAAkB,OAAA;AACpD,+DAAuD;AAA9C,qHAAA,aAAa,OAAA;AACtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,iEAAqE;AAA5D,mIAAA,0BAA0B,OAAA;AACnC,uEAA2E;AAAlE,yIAAA,6BAA6B,OAAA;AACtC,+EAA4E;AAAnE,0IAAA,0BAA0B,OAAA;AACnC,uFAAoF;AAA3E,kJAAA,8BAA8B,OAAA;AACvC,qEAAmE;AAA1D,iIAAA,sBAAsB,OAAA;AAC/B,qFAAiF;AAAxE,+IAAA,4BAA4B,OAAA;AACrC,+EAA2E;AAAlE,yIAAA,yBAAyB,OAAA;AAClC,iGAA4F;AAAnF,0JAAA,iCAAiC,OAAA;AAC1C,mEAAiE;AAAxD,+HAAA,qBAAqB,OAAA;AAC9B,yDAAuD;AAA9C,qHAAA,gBAAgB,OAAA;AACzB,iDAA8D;AAArD,4HAAA,2BAA2B,OAAA;AACpC,2EAAwE;AAA/D,sIAAA,wBAAwB,OAAA;AACjC,iDAA0D;AAAjD,sIAAA,OAAO,OAAgB","sourcesContent":["export { CodeValidator, ExecutorResult } from './code-validator';\n\nexport { NoAnyUnknownValidator } from './validate-no-any-unknown';\nexport { NoImplicitAnyValidator } from './validate-no-implicit-any';\nexport { MaxMethodLinesValidator, runModifiedMethods } from './validate-modified-methods';\nexport { runNewMethods } from './validate-new-methods';\nexport { MaxFileLinesValidator } from './validate-modified-files';\nexport { RequireReturnTypeValidator } from './validate-return-types';\nexport { NoInlineTypeLiteralsValidator } from './validate-no-inline-types';\nexport { CatchErrorPatternValidator } from './validate-catch-error-pattern';\nexport { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';\nexport { NoDestructureValidator } from './validate-no-destructure';\nexport { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';\nexport { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';\nexport { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';\nexport { FrameworkTagValidator } from './validate-framework-tag';\nexport { RoleTagValidator } from './validate-role-tag';\nexport { PrismaValidateDtosValidator } from './validate-dtos';\nexport { PrismaConverterValidator } from './validate-prisma-converters';\nexport { default as validateCode } from './validate-code';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,mDAAiE;AAAxD,+GAAA,aAAa,OAAA;AAEtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,uEAAoE;AAA3D,kIAAA,sBAAsB,OAAA;AAC/B,yEAA0F;AAAjF,oIAAA,uBAAuB,OAAA;AAAE,+HAAA,kBAAkB,OAAA;AACpD,+DAAuD;AAA9C,qHAAA,aAAa,OAAA;AACtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,iEAAqE;AAA5D,mIAAA,0BAA0B,OAAA;AACnC,uEAA2E;AAAlE,yIAAA,6BAA6B,OAAA;AACtC,+EAA4E;AAAnE,0IAAA,0BAA0B,OAAA;AACnC,uFAAoF;AAA3E,kJAAA,8BAA8B,OAAA;AACvC,qEAAmE;AAA1D,iIAAA,sBAAsB,OAAA;AAC/B,qFAAiF;AAAxE,+IAAA,4BAA4B,OAAA;AACrC,+EAA2E;AAAlE,yIAAA,yBAAyB,OAAA;AAClC,iGAA4F;AAAnF,0JAAA,iCAAiC,OAAA;AAC1C,2FAAuF;AAA9E,qJAAA,+BAA+B,OAAA;AACxC,mEAAiE;AAAxD,+HAAA,qBAAqB,OAAA;AAC9B,yDAAuD;AAA9C,qHAAA,gBAAgB,OAAA;AACzB,iDAA8D;AAArD,4HAAA,2BAA2B,OAAA;AACpC,2EAAwE;AAA/D,sIAAA,wBAAwB,OAAA;AACjC,iDAA0D;AAAjD,sIAAA,OAAO,OAAgB","sourcesContent":["export { CodeValidator, ExecutorResult } from './code-validator';\n\nexport { NoAnyUnknownValidator } from './validate-no-any-unknown';\nexport { NoImplicitAnyValidator } from './validate-no-implicit-any';\nexport { MaxMethodLinesValidator, runModifiedMethods } from './validate-modified-methods';\nexport { runNewMethods } from './validate-new-methods';\nexport { MaxFileLinesValidator } from './validate-modified-files';\nexport { RequireReturnTypeValidator } from './validate-return-types';\nexport { NoInlineTypeLiteralsValidator } from './validate-no-inline-types';\nexport { CatchErrorPatternValidator } from './validate-catch-error-pattern';\nexport { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';\nexport { NoDestructureValidator } from './validate-no-destructure';\nexport { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';\nexport { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';\nexport { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';\nexport { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';\nexport { FrameworkTagValidator } from './validate-framework-tag';\nexport { RoleTagValidator } from './validate-role-tag';\nexport { PrismaValidateDtosValidator } from './validate-dtos';\nexport { PrismaConverterValidator } from './validate-prisma-converters';\nexport { default as validateCode } from './validate-code';\n"]}
@@ -17,6 +17,7 @@ const validate_no_unmanaged_exceptions_1 = require("./validate-no-unmanaged-exce
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
19
  const validate_no_process_exit_outside_main_1 = require("./validate-no-process-exit-outside-main");
20
+ const validate_no_function_outside_class_1 = require("./validate-no-function-outside-class");
20
21
  const validate_framework_tag_1 = require("./validate-framework-tag");
21
22
  const validate_role_tag_1 = require("./validate-role-tag");
22
23
  const validate_match_rules_1 = require("./validate-match-rules");
@@ -43,6 +44,7 @@ function buildValidators(config, matchRules) {
43
44
  new validate_no_direct_api_resolver_1.NoDirectApiResolverValidator(config['angular-no-direct-api-in-resolver'] ?? new rules_config_1.AngularNoDirectApiInResolverConfig()),
44
45
  new validate_no_symbol_di_tokens_1.NoSymbolDiTokensValidator(config['no-symbol-di-tokens'] ?? new rules_config_1.NoSymbolDiTokensConfig()),
45
46
  new validate_no_process_exit_outside_main_1.NoProcessExitOutsideMainValidator(config['no-process-exit-outside-main'] ?? new rules_config_1.NoProcessExitOutsideMainConfig()),
47
+ new validate_no_function_outside_class_1.NoFunctionOutsideClassValidator(config['no-function-outside-class'] ?? new rules_config_1.NoFunctionOutsideClassConfig()),
46
48
  new validate_framework_tag_1.FrameworkTagValidator(config['framework-tag'] ?? new rules_config_1.FrameworkTagConfig()),
47
49
  new validate_role_tag_1.RoleTagValidator(config['role-tag'] ?? new rules_config_1.RoleTagConfig()),
48
50
  // One validator per client-authored match-rules entry (each with its own name/mode/epoch).
@@ -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 NoProcessExitOutsideMainConfig,\n FrameworkTagConfig,\n RoleTagConfig,\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 { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';\nimport { FrameworkTagValidator } from './validate-framework-tag';\nimport { RoleTagValidator } from './validate-role-tag';\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 new NoProcessExitOutsideMainValidator(config['no-process-exit-outside-main'] ?? new NoProcessExitOutsideMainConfig()),\n new FrameworkTagValidator(config['framework-tag'] ?? new FrameworkTagConfig()),\n new RoleTagValidator(config['role-tag'] ?? new RoleTagConfig()),\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"]}
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 NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\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 { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';\nimport { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';\nimport { FrameworkTagValidator } from './validate-framework-tag';\nimport { RoleTagValidator } from './validate-role-tag';\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 new NoProcessExitOutsideMainValidator(config['no-process-exit-outside-main'] ?? new NoProcessExitOutsideMainConfig()),\n new NoFunctionOutsideClassValidator(config['no-function-outside-class'] ?? new NoFunctionOutsideClassConfig()),\n new FrameworkTagValidator(config['framework-tag'] ?? new FrameworkTagConfig()),\n new RoleTagValidator(config['role-tag'] ?? new RoleTagConfig()),\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,45 @@
1
+ /**
2
+ * Validate No Function Outside Class
3
+ *
4
+ * Flags a function CREATED OUTSIDE A CLASS at module scope:
5
+ * - a top-level `function foo()` / `export function foo()` declaration, and
6
+ * - a top-level `const foo = () => {}` / `const foo = function(){}`.
7
+ *
8
+ * WHY: webpieces DI + @DocumentDesign only work when behavior lives in injectable classes that can be
9
+ * wired into each other. A module-scope function is a dead-end the DI graph can't reach — move the
10
+ * logic onto a `@provideSingleton()` class and inject it instead.
11
+ *
12
+ * Uses the TypeScript AST so class-membership is checked precisely (not a regex heuristic): a node is a
13
+ * violation only when its parent is the SourceFile (module scope). That automatically EXEMPTS inline
14
+ * callbacks (`arr.map(x => x)`), promise handlers, nested functions inside methods, and functions
15
+ * nested in other functions — their parent is a Block/CallExpression, never the SourceFile.
16
+ *
17
+ * ALLOWED (skip — NOT violations)
18
+ * - Any function/arrow nested inside a class method, another function, or a callback.
19
+ * - Non-function top-level consts: `const SCHEMA = z.object({})`, `const MAX = 5`, object/array literals.
20
+ * - Ambient declarations (`declare function ...`) and whole `*.d.ts` files.
21
+ * - Test files (*.test.ts, *.spec.ts, __tests__/**).
22
+ * - Lines with `// webpieces-disable no-function-outside-class -- <reason>` (when disableAllowed).
23
+ *
24
+ * MODES (AST + LINE-SCOPED)
25
+ * - OFF: Skip.
26
+ * - NEW_AND_MODIFIED_CODE: Flag only on changed lines (diff hunks).
27
+ * - NEW_AND_MODIFIED_FILES: Flag every occurrence in any modified file.
28
+ *
29
+ * NOTE: intentionally NO allowedPaths — the only escapes are the inline `// webpieces-disable` comment
30
+ * and the universal epoch/branch off-switches.
31
+ */
32
+ import { NoFunctionOutsideClassConfig } from '@webpieces/rules-config';
33
+ import { CodeValidator, ExecutorResult } from './code-validator';
34
+ interface FnViolationInfo {
35
+ line: number;
36
+ column: number;
37
+ context: string;
38
+ hasDisableComment: boolean;
39
+ }
40
+ export declare function findFunctionsOutsideClassInSource(content: string, filePath: string, disableAllowed: boolean): FnViolationInfo[];
41
+ export declare class NoFunctionOutsideClassValidator extends CodeValidator<NoFunctionOutsideClassConfig> {
42
+ constructor(config: NoFunctionOutsideClassConfig);
43
+ run(workspaceRoot: string): Promise<ExecutorResult>;
44
+ }
45
+ export {};
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ /**
3
+ * Validate No Function Outside Class
4
+ *
5
+ * Flags a function CREATED OUTSIDE A CLASS at module scope:
6
+ * - a top-level `function foo()` / `export function foo()` declaration, and
7
+ * - a top-level `const foo = () => {}` / `const foo = function(){}`.
8
+ *
9
+ * WHY: webpieces DI + @DocumentDesign only work when behavior lives in injectable classes that can be
10
+ * wired into each other. A module-scope function is a dead-end the DI graph can't reach — move the
11
+ * logic onto a `@provideSingleton()` class and inject it instead.
12
+ *
13
+ * Uses the TypeScript AST so class-membership is checked precisely (not a regex heuristic): a node is a
14
+ * violation only when its parent is the SourceFile (module scope). That automatically EXEMPTS inline
15
+ * callbacks (`arr.map(x => x)`), promise handlers, nested functions inside methods, and functions
16
+ * nested in other functions — their parent is a Block/CallExpression, never the SourceFile.
17
+ *
18
+ * ALLOWED (skip — NOT violations)
19
+ * - Any function/arrow nested inside a class method, another function, or a callback.
20
+ * - Non-function top-level consts: `const SCHEMA = z.object({})`, `const MAX = 5`, object/array literals.
21
+ * - Ambient declarations (`declare function ...`) and whole `*.d.ts` files.
22
+ * - Test files (*.test.ts, *.spec.ts, __tests__/**).
23
+ * - Lines with `// webpieces-disable no-function-outside-class -- <reason>` (when disableAllowed).
24
+ *
25
+ * MODES (AST + LINE-SCOPED)
26
+ * - OFF: Skip.
27
+ * - NEW_AND_MODIFIED_CODE: Flag only on changed lines (diff hunks).
28
+ * - NEW_AND_MODIFIED_FILES: Flag every occurrence in any modified file.
29
+ *
30
+ * NOTE: intentionally NO allowedPaths — the only escapes are the inline `// webpieces-disable` comment
31
+ * and the universal epoch/branch off-switches.
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.NoFunctionOutsideClassValidator = void 0;
35
+ exports.findFunctionsOutsideClassInSource = findFunctionsOutsideClassInSource;
36
+ const tslib_1 = require("tslib");
37
+ const fs = tslib_1.__importStar(require("fs"));
38
+ const path = tslib_1.__importStar(require("path"));
39
+ const ts = tslib_1.__importStar(require("typescript"));
40
+ const rules_config_1 = require("@webpieces/rules-config");
41
+ const code_validator_1 = require("./code-validator");
42
+ const resolve_mode_1 = require("./resolve-mode");
43
+ const SHARED_MESSAGE = `Functions must live inside a class — a function created at module scope can't be injected, so
44
+ webpieces DI + @DocumentDesign can't wire it into anything.
45
+ BAD: export function computeTotal(cart: Cart): number { ... }
46
+ GOOD: @provideSingleton()
47
+ export class CartService { computeTotal(cart: Cart): number { ... } }
48
+ Then inject CartService where you need it. Inline callbacks (arr.map(x => x)), promise handlers, and
49
+ functions nested inside a method are fine — only MODULE-SCOPE function declarations and top-level
50
+ const-assigned functions are flagged.`;
51
+ function isTestFile(filePath) {
52
+ return filePath.includes('.spec.ts') || filePath.includes('.test.ts') || filePath.includes('__tests__/');
53
+ }
54
+ function isDeclarationFile(filePath) {
55
+ return filePath.endsWith('.d.ts');
56
+ }
57
+ // True for a node carrying a `declare` modifier (ambient signature — not a real definition).
58
+ function hasDeclareModifier(node) {
59
+ if (!ts.canHaveModifiers(node))
60
+ return false;
61
+ const mods = ts.getModifiers(node);
62
+ return mods?.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword) ?? false;
63
+ }
64
+ // The violation line (or the line just above it) carries the disable comment.
65
+ function hasDisableOnLine(fileLines, lineNumber) {
66
+ const current = fileLines[lineNumber - 1] ?? '';
67
+ const previous = lineNumber >= 2 ? (fileLines[lineNumber - 2] ?? '') : '';
68
+ return (0, rules_config_1.hasDisable)(current, rules_config_1.RULE_NAMES.NO_FUNCTION_OUTSIDE_CLASS)
69
+ || (0, rules_config_1.hasDisable)(previous, rules_config_1.RULE_NAMES.NO_FUNCTION_OUTSIDE_CLASS);
70
+ }
71
+ function recordViolation(node, context, fileLines, sourceFile, violations, disableAllowed) {
72
+ const startPos = node.getStart(sourceFile);
73
+ if (startPos < 0)
74
+ return;
75
+ const pos = sourceFile.getLineAndCharacterOfPosition(startPos);
76
+ const line = pos.line + 1;
77
+ const column = pos.character + 1;
78
+ const disabled = hasDisableOnLine(fileLines, line);
79
+ // When disableAllowed is false, a disable comment does NOT clear the violation.
80
+ const effectiveDisabled = disableAllowed ? disabled : false;
81
+ violations.push({ line, column, context, hasDisableComment: effectiveDisabled });
82
+ }
83
+ // Report a module-scope const whose initializer is an arrow / function-expression (a function
84
+ // assigned to a top-level const). Non-function consts are left alone.
85
+ function checkTopLevelConst(node, fileLines, sourceFile, violations, disableAllowed) {
86
+ for (const decl of node.declarationList.declarations) {
87
+ const init = decl.initializer;
88
+ if (!init)
89
+ continue;
90
+ if (!ts.isArrowFunction(init) && !ts.isFunctionExpression(init))
91
+ continue;
92
+ const name = ts.isIdentifier(decl.name) ? decl.name.text : '<fn>';
93
+ recordViolation(decl, `const ${name} = <function> at module scope (outside any class)`, fileLines, sourceFile, violations, disableAllowed);
94
+ }
95
+ }
96
+ // AST scan: a node is a violation only when its parent is the SourceFile (module scope).
97
+ function findFunctionsOutsideClassInSource(content, filePath, disableAllowed) {
98
+ if (isDeclarationFile(filePath))
99
+ return [];
100
+ const fileLines = content.split('\n');
101
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
102
+ const violations = [];
103
+ function visit(node) {
104
+ if (ts.isFunctionDeclaration(node) && ts.isSourceFile(node.parent) && !hasDeclareModifier(node)) {
105
+ const name = node.name?.text ?? '<anonymous>';
106
+ recordViolation(node, `function ${name}() declared at module scope (outside any class)`, fileLines, sourceFile, violations, disableAllowed);
107
+ }
108
+ if (ts.isVariableStatement(node) && ts.isSourceFile(node.parent) && !hasDeclareModifier(node)) {
109
+ checkTopLevelConst(node, fileLines, sourceFile, violations, disableAllowed);
110
+ }
111
+ ts.forEachChild(node, visit);
112
+ }
113
+ visit(sourceFile);
114
+ return violations;
115
+ }
116
+ function findFunctionsOutsideClassInFile(filePath, workspaceRoot, disableAllowed) {
117
+ if (isTestFile(filePath))
118
+ return [];
119
+ const fullPath = path.join(workspaceRoot, filePath);
120
+ if (!fs.existsSync(fullPath))
121
+ return [];
122
+ const content = fs.readFileSync(fullPath, 'utf-8');
123
+ return findFunctionsOutsideClassInSource(content, filePath, disableAllowed);
124
+ }
125
+ function findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed) {
126
+ const violations = [];
127
+ for (const file of changedFiles) {
128
+ const changedLines = (0, rules_config_1.getChangedLineNumbers)((0, rules_config_1.getFileDiff)(workspaceRoot, file, base, head));
129
+ if (changedLines.size === 0)
130
+ continue;
131
+ for (const v of findFunctionsOutsideClassInFile(file, workspaceRoot, disableAllowed)) {
132
+ if (disableAllowed && v.hasDisableComment)
133
+ continue;
134
+ if (!changedLines.has(v.line))
135
+ continue;
136
+ violations.push({ file, line: v.line, column: v.column, context: v.context });
137
+ }
138
+ }
139
+ return violations;
140
+ }
141
+ function findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed) {
142
+ const violations = [];
143
+ for (const file of changedFiles) {
144
+ for (const v of findFunctionsOutsideClassInFile(file, workspaceRoot, disableAllowed)) {
145
+ if (disableAllowed && v.hasDisableComment)
146
+ continue;
147
+ violations.push({ file, line: v.line, column: v.column, context: v.context });
148
+ }
149
+ }
150
+ return violations;
151
+ }
152
+ function reportViolations(violations, mode, disableAllowed) {
153
+ console.error('');
154
+ console.error('❌ Function(s) created outside a class!');
155
+ console.error('');
156
+ console.error(SHARED_MESSAGE);
157
+ console.error('');
158
+ for (const v of violations) {
159
+ console.error(` ❌ ${v.file}:${v.line}:${v.column}`);
160
+ console.error(` ${v.context}`);
161
+ }
162
+ console.error('');
163
+ console.error(disableAllowed
164
+ ? ' Escape hatch (use sparingly): // webpieces-disable no-function-outside-class -- <reason>'
165
+ : ' Escape hatch: DISABLED (disableAllowed: false)');
166
+ console.error(`\n Current mode: ${mode}\n`);
167
+ }
168
+ function resolveMode(normalMode, epoch, branchPattern) {
169
+ if (normalMode === 'OFF')
170
+ return normalMode;
171
+ const skip = (0, resolve_mode_1.shouldSkipRule)(epoch, branchPattern);
172
+ if (skip.skip) {
173
+ console.log(`\n⏭️ Skipping no-function-outside-class validation (${skip.reason})\n`);
174
+ return 'OFF';
175
+ }
176
+ return normalMode;
177
+ }
178
+ async function runValidatorImpl(options, workspaceRoot) {
179
+ const mode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch, options.ignoreRuleWhileOnBranch);
180
+ const disableAllowed = options.disableAllowed ?? true;
181
+ if (mode === 'OFF') {
182
+ console.log('\n⏭️ Skipping no-function-outside-class validation (mode: OFF)\n');
183
+ return { success: true };
184
+ }
185
+ console.log('\n📏 Validating No Function Outside Class\n');
186
+ console.log(` Mode: ${mode}`);
187
+ let base = process.env['NX_BASE'];
188
+ const head = process.env['NX_HEAD'];
189
+ if (!base) {
190
+ base = (0, rules_config_1.detectBase)(workspaceRoot) ?? undefined;
191
+ if (!base) {
192
+ console.log('\n⏭️ Skipping no-function-outside-class validation (could not detect base branch)\n');
193
+ return { success: true };
194
+ }
195
+ }
196
+ console.log(` Base: ${base}`);
197
+ console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}\n`);
198
+ const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head);
199
+ if (changedFiles.length === 0) {
200
+ console.log('✅ No TypeScript files changed');
201
+ return { success: true };
202
+ }
203
+ console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
204
+ let violations = [];
205
+ if (mode === 'NEW_AND_MODIFIED_CODE') {
206
+ violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed);
207
+ }
208
+ else if (mode === 'NEW_AND_MODIFIED_FILES') {
209
+ violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);
210
+ }
211
+ if (violations.length === 0) {
212
+ console.log('✅ No function-outside-class violations found');
213
+ return { success: true };
214
+ }
215
+ reportViolations(violations, mode, disableAllowed);
216
+ return { success: false };
217
+ }
218
+ class NoFunctionOutsideClassValidator extends code_validator_1.CodeValidator {
219
+ constructor(config) {
220
+ super(config, 'no-function-outside-class');
221
+ }
222
+ async run(workspaceRoot) {
223
+ return runValidatorImpl(this.config, workspaceRoot);
224
+ }
225
+ }
226
+ exports.NoFunctionOutsideClassValidator = NoFunctionOutsideClassValidator;
227
+ //# sourceMappingURL=validate-no-function-outside-class.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-no-function-outside-class.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-no-function-outside-class.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate No Function Outside Class\n *\n * Flags a function CREATED OUTSIDE A CLASS at module scope:\n * - a top-level `function foo()` / `export function foo()` declaration, and\n * - a top-level `const foo = () => {}` / `const foo = function(){}`.\n *\n * WHY: webpieces DI + @DocumentDesign only work when behavior lives in injectable classes that can be\n * wired into each other. A module-scope function is a dead-end the DI graph can't reach — move the\n * logic onto a `@provideSingleton()` class and inject it instead.\n *\n * Uses the TypeScript AST so class-membership is checked precisely (not a regex heuristic): a node is a\n * violation only when its parent is the SourceFile (module scope). That automatically EXEMPTS inline\n * callbacks (`arr.map(x => x)`), promise handlers, nested functions inside methods, and functions\n * nested in other functions — their parent is a Block/CallExpression, never the SourceFile.\n *\n * ALLOWED (skip — NOT violations)\n * - Any function/arrow nested inside a class method, another function, or a callback.\n * - Non-function top-level consts: `const SCHEMA = z.object({})`, `const MAX = 5`, object/array literals.\n * - Ambient declarations (`declare function ...`) and whole `*.d.ts` files.\n * - Test files (*.test.ts, *.spec.ts, __tests__/**).\n * - Lines with `// webpieces-disable no-function-outside-class -- <reason>` (when disableAllowed).\n *\n * MODES (AST + LINE-SCOPED)\n * - OFF: Skip.\n * - NEW_AND_MODIFIED_CODE: Flag only on changed lines (diff hunks).\n * - NEW_AND_MODIFIED_FILES: Flag every occurrence in any modified file.\n *\n * NOTE: intentionally NO allowedPaths — the only escapes are the inline `// webpieces-disable` comment\n * and the universal epoch/branch off-switches.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport { hasDisable, RULE_NAMES, NoFunctionOutsideClassConfig, ModifiedCodeMode, detectBase, getChangedFiles, getFileDiff, getChangedLineNumbers } from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { shouldSkipRule } from './resolve-mode';\n\nconst SHARED_MESSAGE = `Functions must live inside a class — a function created at module scope can't be injected, so\nwebpieces DI + @DocumentDesign can't wire it into anything.\n BAD: export function computeTotal(cart: Cart): number { ... }\n GOOD: @provideSingleton()\n export class CartService { computeTotal(cart: Cart): number { ... } }\nThen inject CartService where you need it. Inline callbacks (arr.map(x => x)), promise handlers, and\nfunctions nested inside a method are fine — only MODULE-SCOPE function declarations and top-level\nconst-assigned functions are flagged.`;\n\ninterface FnViolation {\n file: string;\n line: number;\n column: number;\n context: string;\n}\n\ninterface FnViolationInfo {\n line: number;\n column: number;\n context: string;\n hasDisableComment: boolean;\n}\n\nfunction isTestFile(filePath: string): boolean {\n return filePath.includes('.spec.ts') || filePath.includes('.test.ts') || filePath.includes('__tests__/');\n}\n\nfunction isDeclarationFile(filePath: string): boolean {\n return filePath.endsWith('.d.ts');\n}\n\n// True for a node carrying a `declare` modifier (ambient signature — not a real definition).\nfunction hasDeclareModifier(node: ts.Node): boolean {\n if (!ts.canHaveModifiers(node)) return false;\n const mods = ts.getModifiers(node);\n return mods?.some((m: ts.Modifier): boolean => m.kind === ts.SyntaxKind.DeclareKeyword) ?? false;\n}\n\n// The violation line (or the line just above it) carries the disable comment.\nfunction hasDisableOnLine(fileLines: string[], lineNumber: number): boolean {\n const current = fileLines[lineNumber - 1] ?? '';\n const previous = lineNumber >= 2 ? (fileLines[lineNumber - 2] ?? '') : '';\n return hasDisable(current, RULE_NAMES.NO_FUNCTION_OUTSIDE_CLASS)\n || hasDisable(previous, RULE_NAMES.NO_FUNCTION_OUTSIDE_CLASS);\n}\n\nfunction recordViolation(\n node: ts.Node,\n context: string,\n fileLines: string[],\n sourceFile: ts.SourceFile,\n violations: FnViolationInfo[],\n disableAllowed: boolean,\n): void {\n const startPos = node.getStart(sourceFile);\n if (startPos < 0) return;\n const pos = sourceFile.getLineAndCharacterOfPosition(startPos);\n const line = pos.line + 1;\n const column = pos.character + 1;\n const disabled = hasDisableOnLine(fileLines, line);\n // When disableAllowed is false, a disable comment does NOT clear the violation.\n const effectiveDisabled = disableAllowed ? disabled : false;\n violations.push({ line, column, context, hasDisableComment: effectiveDisabled });\n}\n\n// Report a module-scope const whose initializer is an arrow / function-expression (a function\n// assigned to a top-level const). Non-function consts are left alone.\nfunction checkTopLevelConst(\n node: ts.VariableStatement,\n fileLines: string[],\n sourceFile: ts.SourceFile,\n violations: FnViolationInfo[],\n disableAllowed: boolean,\n): void {\n for (const decl of node.declarationList.declarations) {\n const init = decl.initializer;\n if (!init) continue;\n if (!ts.isArrowFunction(init) && !ts.isFunctionExpression(init)) continue;\n const name = ts.isIdentifier(decl.name) ? decl.name.text : '<fn>';\n recordViolation(decl, `const ${name} = <function> at module scope (outside any class)`, fileLines, sourceFile, violations, disableAllowed);\n }\n}\n\n// AST scan: a node is a violation only when its parent is the SourceFile (module scope).\nexport function findFunctionsOutsideClassInSource(content: string, filePath: string, disableAllowed: boolean): FnViolationInfo[] {\n if (isDeclarationFile(filePath)) return [];\n const fileLines = content.split('\\n');\n const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n const violations: FnViolationInfo[] = [];\n\n function visit(node: ts.Node): void {\n if (ts.isFunctionDeclaration(node) && ts.isSourceFile(node.parent) && !hasDeclareModifier(node)) {\n const name = node.name?.text ?? '<anonymous>';\n recordViolation(node, `function ${name}() declared at module scope (outside any class)`, fileLines, sourceFile, violations, disableAllowed);\n }\n if (ts.isVariableStatement(node) && ts.isSourceFile(node.parent) && !hasDeclareModifier(node)) {\n checkTopLevelConst(node, fileLines, sourceFile, violations, disableAllowed);\n }\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return violations;\n}\n\nfunction findFunctionsOutsideClassInFile(filePath: string, workspaceRoot: string, disableAllowed: boolean): FnViolationInfo[] {\n if (isTestFile(filePath)) return [];\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n const content = fs.readFileSync(fullPath, 'utf-8');\n return findFunctionsOutsideClassInSource(content, filePath, disableAllowed);\n}\n\nfunction findViolationsForModifiedCode(workspaceRoot: string, changedFiles: string[], base: string, head: string | undefined, disableAllowed: boolean): FnViolation[] {\n const violations: FnViolation[] = [];\n for (const file of changedFiles) {\n const changedLines = getChangedLineNumbers(getFileDiff(workspaceRoot, file, base, head));\n if (changedLines.size === 0) continue;\n for (const v of findFunctionsOutsideClassInFile(file, workspaceRoot, disableAllowed)) {\n if (disableAllowed && v.hasDisableComment) continue;\n if (!changedLines.has(v.line)) continue;\n violations.push({ file, line: v.line, column: v.column, context: v.context });\n }\n }\n return violations;\n}\n\nfunction findViolationsForModifiedFiles(workspaceRoot: string, changedFiles: string[], disableAllowed: boolean): FnViolation[] {\n const violations: FnViolation[] = [];\n for (const file of changedFiles) {\n for (const v of findFunctionsOutsideClassInFile(file, workspaceRoot, disableAllowed)) {\n if (disableAllowed && v.hasDisableComment) continue;\n violations.push({ file, line: v.line, column: v.column, context: v.context });\n }\n }\n return violations;\n}\n\nfunction reportViolations(violations: FnViolation[], mode: ModifiedCodeMode, disableAllowed: boolean): void {\n console.error('');\n console.error('❌ Function(s) created outside a class!');\n console.error('');\n console.error(SHARED_MESSAGE);\n console.error('');\n for (const v of violations) {\n console.error(` ❌ ${v.file}:${v.line}:${v.column}`);\n console.error(` ${v.context}`);\n }\n console.error('');\n console.error(disableAllowed\n ? ' Escape hatch (use sparingly): // webpieces-disable no-function-outside-class -- <reason>'\n : ' Escape hatch: DISABLED (disableAllowed: false)');\n console.error(`\\n Current mode: ${mode}\\n`);\n}\n\nfunction 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-function-outside-class validation (${skip.reason})\\n`);\n return 'OFF';\n }\n return normalMode;\n}\n\nasync function runValidatorImpl(options: NoFunctionOutsideClassConfig, workspaceRoot: string): Promise<ExecutorResult> {\n const mode: ModifiedCodeMode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch, options.ignoreRuleWhileOnBranch);\n const disableAllowed = options.disableAllowed ?? true;\n\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping no-function-outside-class validation (mode: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Validating No Function Outside Class\\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-function-outside-class validation (could not detect base branch)\\n');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}\\n`);\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: FnViolation[] = [];\n if (mode === 'NEW_AND_MODIFIED_CODE') {\n violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed);\n } else if (mode === 'NEW_AND_MODIFIED_FILES') {\n violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);\n }\n\n if (violations.length === 0) {\n console.log('✅ No function-outside-class violations found');\n return { success: true };\n }\n\n reportViolations(violations, mode, disableAllowed);\n return { success: false };\n}\n\nexport class NoFunctionOutsideClassValidator extends CodeValidator<NoFunctionOutsideClassConfig> {\n constructor(config: NoFunctionOutsideClassConfig) {\n super(config, 'no-function-outside-class');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runValidatorImpl(this.config, workspaceRoot);\n }\n}\n"]}