@sun-asterisk/sunlint 1.0.7 → 1.1.3
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/.sunlint.json +35 -0
- package/CHANGELOG.md +30 -3
- package/CONTRIBUTING.md +235 -0
- package/PROJECT_STRUCTURE.md +60 -0
- package/README.md +73 -52
- package/cli.js +1 -0
- package/config/README.md +88 -0
- package/config/defaults/ai-rules-context.json +231 -0
- package/config/engines/engines.json +49 -0
- package/config/engines/eslint-rule-mapping.json +74 -0
- package/config/eslint-rule-mapping.json +126 -0
- package/config/integrations/eslint/base.config.js +125 -0
- package/config/integrations/eslint/simple.config.js +24 -0
- package/config/presets/strict.json +0 -1
- package/config/rule-analysis-strategies.js +74 -0
- package/config/{rules-registry.json → rules/rules-registry.json} +22 -0
- package/core/analysis-orchestrator.js +383 -591
- package/core/ast-modules/README.md +103 -0
- package/core/ast-modules/base-parser.js +90 -0
- package/core/ast-modules/index.js +97 -0
- package/core/ast-modules/package.json +37 -0
- package/core/ast-modules/parsers/eslint-js-parser.js +147 -0
- package/core/ast-modules/parsers/eslint-ts-parser.js +106 -0
- package/core/ast-modules/parsers/javascript-parser.js +187 -0
- package/core/ast-modules/parsers/typescript-parser.js +187 -0
- package/core/cli-action-handler.js +271 -255
- package/core/cli-program.js +18 -4
- package/core/config-manager.js +9 -3
- package/core/config-merger.js +40 -1
- package/core/config-validator.js +2 -2
- package/core/enhanced-rules-registry.js +331 -0
- package/core/file-targeting-service.js +92 -23
- package/core/interfaces/analysis-engine.interface.js +100 -0
- package/core/multi-rule-runner.js +0 -221
- package/core/output-service.js +1 -1
- package/core/rule-mapping-service.js +1 -1
- package/core/rule-selection-service.js +10 -2
- package/docs/AI.md +163 -0
- package/docs/ARCHITECTURE.md +78 -0
- package/docs/CI-CD-GUIDE.md +315 -0
- package/docs/COMMAND-EXAMPLES.md +256 -0
- package/docs/CONFIGURATION.md +414 -0
- package/docs/DEBUG.md +86 -0
- package/docs/DEPLOYMENT-STRATEGIES.md +270 -0
- package/docs/DISTRIBUTION.md +153 -0
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
- package/docs/ESLINT_INTEGRATION.md +238 -0
- package/docs/FOLDER_STRUCTURE.md +59 -0
- package/docs/HEURISTIC_VS_AI.md +113 -0
- package/docs/README.md +32 -0
- package/docs/RELEASE_GUIDE.md +230 -0
- package/engines/eslint-engine.js +601 -0
- package/engines/heuristic-engine.js +860 -0
- package/engines/openai-engine.js +374 -0
- package/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- package/integrations/eslint/README.md +99 -0
- package/integrations/eslint/configs/.eslintrc.js +98 -0
- package/integrations/eslint/configs/eslint.config.js +133 -0
- package/integrations/eslint/configs/eslint.config.simple.js +24 -0
- package/integrations/eslint/package.json +23 -0
- package/integrations/eslint/plugin/index.js +164 -0
- package/integrations/eslint/plugin/package.json +13 -0
- package/integrations/eslint/plugin/rules/common/c002-no-duplicate-code.js +204 -0
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +246 -0
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +216 -0
- package/integrations/eslint/plugin/rules/common/c010-limit-block-nesting.js +90 -0
- package/integrations/eslint/plugin/rules/common/c013-no-dead-code.js +78 -0
- package/integrations/eslint/plugin/rules/common/c014-abstract-dependency-preferred.js +38 -0
- package/integrations/eslint/plugin/rules/common/c017-limit-constructor-logic.js +146 -0
- package/integrations/eslint/plugin/rules/common/c018-no-generic-throw.js +335 -0
- package/integrations/eslint/plugin/rules/common/c023-no-duplicate-variable-name-in-scope.js +142 -0
- package/integrations/eslint/plugin/rules/common/c029-catch-block-logging.js +115 -0
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +294 -0
- package/integrations/eslint/plugin/rules/common/c035-no-empty-catch.js +162 -0
- package/integrations/eslint/plugin/rules/common/c041-no-config-inline.js +122 -0
- package/integrations/eslint/plugin/rules/common/c042-boolean-name-prefix.js +406 -0
- package/integrations/eslint/plugin/rules/common/c043-no-console-or-print.js +300 -0
- package/integrations/eslint/plugin/rules/common/c047-no-duplicate-retry-logic.js +239 -0
- package/integrations/eslint/plugin/rules/common/c072-one-assert-per-test.js +184 -0
- package/integrations/eslint/plugin/rules/common/c075-explicit-function-return-types.js +168 -0
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +254 -0
- package/integrations/eslint/plugin/rules/security/s001-fail-securely.js +381 -0
- package/integrations/eslint/plugin/rules/security/s002-idor-check.js +945 -0
- package/integrations/eslint/plugin/rules/security/s003-no-unvalidated-redirect.js +86 -0
- package/integrations/eslint/plugin/rules/security/s007-no-plaintext-otp.js +74 -0
- package/integrations/eslint/plugin/rules/security/s013-verify-tls-connection.js +47 -0
- package/integrations/eslint/plugin/rules/security/s047-secure-random-passwords.js +108 -0
- package/integrations/eslint/plugin/rules/security/s055-verification-rest-check-the-incoming-content-type.js +143 -0
- package/integrations/eslint/plugin/rules/typescript/t002-interface-prefix-i.js +42 -0
- package/integrations/eslint/plugin/rules/typescript/t003-ts-ignore-reason.js +48 -0
- package/integrations/eslint/plugin/rules/typescript/t004-no-empty-type.js +95 -0
- package/integrations/eslint/plugin/rules/typescript/t007-no-fn-in-constructor.js +52 -0
- package/integrations/eslint/plugin/rules/typescript/t010-no-nested-union-tuple.js +48 -0
- package/integrations/eslint/plugin/rules/typescript/t019-no-this-assign.js +81 -0
- package/integrations/eslint/plugin/rules/typescript/t020-no-default-multi-export.js +127 -0
- package/integrations/eslint/plugin/rules/typescript/t021-limit-nested-generics.js +150 -0
- package/integrations/eslint/test-c041-rule.js +87 -0
- package/integrations/eslint/tsconfig.json +27 -0
- package/package.json +29 -16
- package/rules/README.md +252 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
- package/rules/common/C002_no_duplicate_code/config.json +23 -0
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
- package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
- package/rules/{C006_function_naming → common/C006_function_naming}/analyzer.js +13 -2
- package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
- package/rules/common/C013_no_dead_code/analyzer.js +206 -0
- package/rules/common/C014_dependency_injection/analyzer.js +338 -0
- package/rules/common/C017_constructor_logic/analyzer.js +314 -0
- package/rules/{C019_log_level_usage → common/C019_log_level_usage}/analyzer.js +5 -2
- package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/analyzer.js +49 -15
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
- package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
- package/rules/common/C043_no_console_or_print/analyzer.js +304 -0
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +351 -0
- package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
- package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
- package/rules/docs/C002_no_duplicate_code.md +57 -0
- package/rules/index.js +149 -0
- package/rules/migration/converter.js +385 -0
- package/rules/migration/mapping.json +164 -0
- package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
- package/rules/security/S026_json_schema_validation/config.json +27 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +263 -0
- package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
- package/rules/security/S029_csrf_protection/analyzer.js +264 -0
- package/rules/tests/C002_no_duplicate_code.test.js +50 -0
- package/rules/universal/C010/generic.js +0 -0
- package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
- package/rules/utils/ast-utils.js +191 -0
- package/rules/utils/base-analyzer.js +98 -0
- package/rules/utils/pattern-matchers.js +239 -0
- package/rules/utils/rule-helpers.js +264 -0
- package/rules/utils/severity-constants.js +93 -0
- package/scripts/build-release.sh +117 -0
- package/scripts/ci-report.js +179 -0
- package/scripts/install.sh +196 -0
- package/scripts/manual-release.sh +338 -0
- package/scripts/merge-reports.js +424 -0
- package/scripts/pre-release-test.sh +175 -0
- package/scripts/prepare-release.sh +202 -0
- package/scripts/setup-github-registry.sh +42 -0
- package/scripts/test-scripts/README.md +22 -0
- package/scripts/test-scripts/test-c041-comparison.js +114 -0
- package/scripts/test-scripts/test-c041-eslint.js +67 -0
- package/scripts/test-scripts/test-eslint-rules.js +146 -0
- package/scripts/test-scripts/test-real-world.js +44 -0
- package/scripts/test-scripts/test-rules-on-real-projects.js +86 -0
- package/scripts/trigger-release.sh +285 -0
- package/scripts/validate-rule-structure.js +148 -0
- package/scripts/verify-install.sh +82 -0
- package/config/sunlint-schema.json +0 -159
- package/config/typescript/custom-rules.js +0 -9
- package/config/typescript/package-lock.json +0 -1585
- package/config/typescript/package.json +0 -13
- package/config/typescript/security-rules/index.js +0 -90
- package/config/typescript/tsconfig.json +0 -29
- package/core/ai-analyzer.js +0 -169
- package/core/eslint-engine-service.js +0 -312
- package/core/eslint-instance-manager.js +0 -104
- package/core/eslint-integration-service.js +0 -363
- package/core/sunlint-engine-service.js +0 -23
- package/core/typescript-analyzer.js +0 -262
- package/core/typescript-engine.js +0 -313
- /package/config/{default.json → defaults/default.json} +0 -0
- /package/config/{typescript/eslint.config.js → integrations/eslint/typescript.config.js} +0 -0
- /package/config/{typescript/custom-rules-new.js → schemas/sunlint-schema.json} +0 -0
- /package/config/{typescript → testing}/test-s005-working.ts +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s005-no-origin-auth.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s006-activation-recovery-secret-not-plaintext.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s008-crypto-agility.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s009-no-insecure-crypto.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s010-no-insecure-random-in-sensitive-context.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s011-no-insecure-uuid.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s012-hardcode-secret.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s014-insecure-tls-version.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s015-insecure-tls-certificate.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s016-sensitive-query-parameter.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s017-no-sql-injection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s018-positive-input-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s019-no-raw-user-input-in-email.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s020-no-eval-dynamic-execution.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s022-output-encoding.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s023-no-json-injection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s025-server-side-input-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s026-json-schema-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s027-no-hardcoded-secrets.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s029-require-csrf-protection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s030-no-directory-browsing.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s033-require-samesite-cookie.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s034-require-host-cookie-prefix.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s035-cookie-specific-path.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s036-no-unsafe-file-include.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s037-require-anti-cache-headers.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s038-no-version-disclosure.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s039-no-session-token-in-url.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s041-require-session-invalidate-on-logout.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s042-require-periodic-reauthentication.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s043-terminate-sessions-on-password-change.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s044-require-full-session-for-sensitive-operations.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s045-anti-automation-controls.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s046-secure-notification-on-auth-change.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s048-password-credential-recovery.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s050-session-token-weak-hash.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s052-secure-random-authentication-code.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s054-verification-default-account.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s057-utc-logging.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s058-no-ssrf.js +0 -0
- /package/rules/{C006_function_naming → common/C006_function_naming}/config.json +0 -0
- /package/rules/{C019_log_level_usage → common/C019_log_level_usage}/config.json +0 -0
- /package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/config.json +0 -0
- /package/rules/{C031_validation_separation → common/C031_validation_separation}/analyzer.js +0 -0
- /package/rules/{C031_validation_separation/README.md → docs/C031_validation_separation.md} +0 -0
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heuristic Analysis Engine Plugin
|
|
3
|
+
* Following Rule C005: Single responsibility - Pattern-based analysis with AST enhancement
|
|
4
|
+
* Following Rule C014: Dependency injection - implements interface
|
|
5
|
+
* Following Rule C015: Use domain language - clear heuristic analysis terms
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const AnalysisEngineInterface = require('../core/interfaces/analysis-engine.interface');
|
|
9
|
+
const ASTModuleRegistry = require('../core/ast-modules/index');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
class HeuristicEngine extends AnalysisEngineInterface {
|
|
14
|
+
constructor() {
|
|
15
|
+
super('heuristic', '2.0', ['typescript', 'javascript', 'dart', 'swift', 'kotlin', 'java', 'python', 'go', 'rust', 'all']);
|
|
16
|
+
|
|
17
|
+
this.ruleAnalyzers = new Map();
|
|
18
|
+
this.supportedRulesList = [];
|
|
19
|
+
this.rulesRegistry = {};
|
|
20
|
+
this.astRegistry = ASTModuleRegistry;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Initialize Heuristic engine with configuration
|
|
25
|
+
* Following Rule C006: Verb-noun naming
|
|
26
|
+
* @param {Object} config - Engine configuration
|
|
27
|
+
*/
|
|
28
|
+
async initialize(config) {
|
|
29
|
+
try {
|
|
30
|
+
// Store verbosity setting
|
|
31
|
+
this.verbose = config?.verbose || false;
|
|
32
|
+
|
|
33
|
+
// Load rules registry
|
|
34
|
+
await this.loadRulesRegistry(config);
|
|
35
|
+
|
|
36
|
+
// Scan for available rule analyzers
|
|
37
|
+
await this.scanRuleAnalyzers(config);
|
|
38
|
+
|
|
39
|
+
this.initialized = true;
|
|
40
|
+
if (this.verbose) {
|
|
41
|
+
console.log(`🔍 Heuristic engine v2.0 initialized with ${this.supportedRulesList.length} rules (AST-enhanced)`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Failed to initialize Heuristic engine:', error.message);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Load rules registry configuration
|
|
52
|
+
* Following Rule C006: Verb-noun naming
|
|
53
|
+
*/
|
|
54
|
+
async loadRulesRegistry(config = {}) {
|
|
55
|
+
try {
|
|
56
|
+
const registryPath = path.resolve(__dirname, '../config/rules/rules-registry.json');
|
|
57
|
+
if (fs.existsSync(registryPath)) {
|
|
58
|
+
this.rulesRegistry = require(registryPath);
|
|
59
|
+
if (config.verbose) {
|
|
60
|
+
if (this.verbose) {
|
|
61
|
+
console.log('📋 Loaded rules registry');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
console.warn('⚠️ Rules registry not found, using minimal support');
|
|
66
|
+
this.rulesRegistry = { rules: {} };
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.warn('⚠️ Failed to load rules registry:', error.message);
|
|
70
|
+
this.rulesRegistry = { rules: {} };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Scan for available rule analyzers
|
|
76
|
+
* Following Rule C006: Verb-noun naming
|
|
77
|
+
*/
|
|
78
|
+
async scanRuleAnalyzers(config = {}) {
|
|
79
|
+
const rulesDir = path.resolve(__dirname, '../rules');
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(rulesDir)) {
|
|
82
|
+
console.warn('⚠️ Rules directory not found');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// Scan category folders (common, security, typescript, etc.)
|
|
88
|
+
const categoryFolders = fs.readdirSync(rulesDir, { withFileTypes: true })
|
|
89
|
+
.filter(dirent => dirent.isDirectory())
|
|
90
|
+
.filter(dirent => !['tests', 'docs', 'utils', 'migration'].includes(dirent.name))
|
|
91
|
+
.map(dirent => dirent.name);
|
|
92
|
+
|
|
93
|
+
for (const categoryFolder of categoryFolders) {
|
|
94
|
+
const categoryPath = path.join(rulesDir, categoryFolder);
|
|
95
|
+
|
|
96
|
+
// Scan rule folders within category
|
|
97
|
+
const ruleFolders = fs.readdirSync(categoryPath, { withFileTypes: true })
|
|
98
|
+
.filter(dirent => dirent.isDirectory())
|
|
99
|
+
.map(dirent => dirent.name);
|
|
100
|
+
|
|
101
|
+
for (const ruleFolder of ruleFolders) {
|
|
102
|
+
const ruleId = this.extractRuleIdFromFolder(ruleFolder);
|
|
103
|
+
const analyzerPath = path.join(categoryPath, ruleFolder, 'analyzer.js');
|
|
104
|
+
|
|
105
|
+
if (fs.existsSync(analyzerPath)) {
|
|
106
|
+
try {
|
|
107
|
+
// Load analyzer dynamically - handle both class and instance exports
|
|
108
|
+
const analyzerModule = require(analyzerPath);
|
|
109
|
+
const analyzer = analyzerModule.default || analyzerModule;
|
|
110
|
+
|
|
111
|
+
// Check if it's a class constructor, instance, or factory function
|
|
112
|
+
if (typeof analyzer === 'function') {
|
|
113
|
+
// It's a class constructor
|
|
114
|
+
this.ruleAnalyzers.set(ruleId, {
|
|
115
|
+
path: analyzerPath,
|
|
116
|
+
class: analyzer,
|
|
117
|
+
folder: ruleFolder,
|
|
118
|
+
category: categoryFolder,
|
|
119
|
+
type: 'class'
|
|
120
|
+
});
|
|
121
|
+
this.supportedRulesList.push(ruleId);
|
|
122
|
+
} else if (analyzer && typeof analyzer === 'object' && analyzer.analyze) {
|
|
123
|
+
// It's an analyzer instance with analyze method
|
|
124
|
+
this.ruleAnalyzers.set(ruleId, {
|
|
125
|
+
path: analyzerPath,
|
|
126
|
+
instance: analyzer,
|
|
127
|
+
folder: ruleFolder,
|
|
128
|
+
category: categoryFolder,
|
|
129
|
+
type: 'instance'
|
|
130
|
+
});
|
|
131
|
+
this.supportedRulesList.push(ruleId);
|
|
132
|
+
} else {
|
|
133
|
+
console.warn(`⚠️ Analyzer for ${ruleId} has unsupported format:`, typeof analyzer);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.warn(`⚠️ Failed to load analyzer for ${ruleId}:`, error.message);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (config.verbose) {
|
|
144
|
+
if (this.verbose) {
|
|
145
|
+
console.log(`🔍 Found ${this.supportedRulesList.length} heuristic analyzers`);
|
|
146
|
+
console.log(`🔍 Supported rules: ${this.supportedRulesList.join(', ')}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('Failed to scan rule analyzers:', error.message);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Extract rule ID from folder name
|
|
157
|
+
* Following Rule C006: Verb-noun naming
|
|
158
|
+
* @param {string} folderName - Rule folder name
|
|
159
|
+
* @returns {string} Rule ID
|
|
160
|
+
*/
|
|
161
|
+
extractRuleIdFromFolder(folderName) {
|
|
162
|
+
// Extract rule ID from patterns like "C019_log_level_usage"
|
|
163
|
+
const match = folderName.match(/^([CST]\d{3})/);
|
|
164
|
+
return match ? match[1] : folderName;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Analyze files using heuristic patterns
|
|
169
|
+
* Following Rule C006: Verb-noun naming
|
|
170
|
+
* @param {string[]} files - Files to analyze
|
|
171
|
+
* @param {Object[]} rules - Rules to apply
|
|
172
|
+
* @param {Object} options - Analysis options
|
|
173
|
+
* @returns {Promise<Object>} Analysis results
|
|
174
|
+
*/
|
|
175
|
+
async analyze(files, rules, options) {
|
|
176
|
+
if (!this.initialized) {
|
|
177
|
+
throw new Error('Heuristic engine not initialized');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (options.verbose) {
|
|
181
|
+
console.log(`🔍 [HeuristicEngine] Analyzing ${files.length} files with ${rules.length} rules`);
|
|
182
|
+
console.log(`🔍 [HeuristicEngine] Files: ${files.map(f => path.basename(f)).join(', ')}`);
|
|
183
|
+
console.log(`🔍 [HeuristicEngine] Rules: ${rules.map(r => r.id).join(', ')}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const results = {
|
|
187
|
+
results: [],
|
|
188
|
+
filesAnalyzed: files.length,
|
|
189
|
+
engine: 'heuristic',
|
|
190
|
+
metadata: {
|
|
191
|
+
rulesAnalyzed: rules.map(r => r.id),
|
|
192
|
+
analyzersUsed: []
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Group files by language for efficient processing
|
|
197
|
+
const filesByLanguage = this.groupFilesByLanguage(files);
|
|
198
|
+
|
|
199
|
+
for (const rule of rules) {
|
|
200
|
+
if (!this.isRuleSupported(rule.id)) {
|
|
201
|
+
if (options.verbose) {
|
|
202
|
+
console.warn(`⚠️ Rule ${rule.id} not supported by Heuristic engine, skipping...`);
|
|
203
|
+
}
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const ruleViolations = await this.analyzeRule(rule, filesByLanguage, options);
|
|
209
|
+
|
|
210
|
+
if (ruleViolations.length > 0) {
|
|
211
|
+
// Group violations by file
|
|
212
|
+
const violationsByFile = this.groupViolationsByFile(ruleViolations);
|
|
213
|
+
|
|
214
|
+
for (const [filePath, violations] of violationsByFile) {
|
|
215
|
+
// Find or create file result
|
|
216
|
+
let fileResult = results.results.find(r => r.file === filePath);
|
|
217
|
+
if (!fileResult) {
|
|
218
|
+
fileResult = { file: filePath, violations: [] };
|
|
219
|
+
results.results.push(fileResult);
|
|
220
|
+
}
|
|
221
|
+
fileResult.violations.push(...violations);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
results.metadata.analyzersUsed.push(rule.id);
|
|
226
|
+
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error(`❌ Failed to analyze rule ${rule.id}:`, error.message);
|
|
229
|
+
// Continue with other rules
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return results;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Analyze a specific rule across files
|
|
238
|
+
* Following Rule C006: Verb-noun naming
|
|
239
|
+
* @param {Object} rule - Rule to analyze
|
|
240
|
+
* @param {Object} filesByLanguage - Files grouped by language
|
|
241
|
+
* @param {Object} options - Analysis options
|
|
242
|
+
* @returns {Promise<Object[]>} Rule violations
|
|
243
|
+
*/
|
|
244
|
+
async analyzeRule(rule, filesByLanguage, options) {
|
|
245
|
+
const analyzerInfo = this.ruleAnalyzers.get(rule.id);
|
|
246
|
+
if (!analyzerInfo) {
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
// Get analyzer - handle both class and instance types
|
|
252
|
+
const analyzerInfo = this.ruleAnalyzers.get(rule.id);
|
|
253
|
+
let analyzer;
|
|
254
|
+
|
|
255
|
+
if (analyzerInfo.type === 'class') {
|
|
256
|
+
// Create analyzer instance from class
|
|
257
|
+
const AnalyzerClass = analyzerInfo.class;
|
|
258
|
+
try {
|
|
259
|
+
analyzer = new AnalyzerClass();
|
|
260
|
+
} catch (constructorError) {
|
|
261
|
+
throw new Error(`Failed to instantiate analyzer class: ${constructorError.message}`);
|
|
262
|
+
}
|
|
263
|
+
} else if (analyzerInfo.type === 'instance') {
|
|
264
|
+
// Use existing analyzer instance
|
|
265
|
+
analyzer = analyzerInfo.instance;
|
|
266
|
+
} else {
|
|
267
|
+
throw new Error(`Unknown analyzer type: ${analyzerInfo.type}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Verify analyzer has required methods
|
|
271
|
+
if (!analyzer.analyze || typeof analyzer.analyze !== 'function') {
|
|
272
|
+
console.warn(`⚠️ Analyzer for ${rule.id} missing analyze method`);
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const allViolations = [];
|
|
277
|
+
|
|
278
|
+
// Run analyzer for each supported language
|
|
279
|
+
const ruleLanguages = this.getRuleLanguages(rule);
|
|
280
|
+
for (const language of ruleLanguages) {
|
|
281
|
+
const languageFiles = filesByLanguage[language] || [];
|
|
282
|
+
if (languageFiles.length === 0) continue;
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
// Load rule config
|
|
286
|
+
const ruleConfig = await this.loadRuleConfig(rule.id, analyzerInfo.folder, analyzerInfo.category);
|
|
287
|
+
|
|
288
|
+
// Run analysis with AST enhancement
|
|
289
|
+
if (options.verbose) {
|
|
290
|
+
console.log(`🔧 [DEBUG] About to call runEnhancedAnalysis for rule ${rule.id}, language ${language}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const languageViolations = await this.runEnhancedAnalysis(
|
|
294
|
+
analyzer,
|
|
295
|
+
rule.id,
|
|
296
|
+
languageFiles,
|
|
297
|
+
language,
|
|
298
|
+
{ ...ruleConfig, ...options }
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
allViolations.push(...languageViolations);
|
|
302
|
+
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error(`❌ Rule ${rule.id} failed for ${language}:`, error.message);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return allViolations;
|
|
309
|
+
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error(`❌ Failed to create analyzer for rule ${rule.id}:`, error.message);
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get supported languages for a rule
|
|
318
|
+
* Following Rule C006: Verb-noun naming
|
|
319
|
+
* @param {Object} rule - Rule object
|
|
320
|
+
* @returns {string[]} Supported languages
|
|
321
|
+
*/
|
|
322
|
+
getRuleLanguages(rule) {
|
|
323
|
+
// Get from rules registry
|
|
324
|
+
const registryRule = this.rulesRegistry.rules?.[rule.id];
|
|
325
|
+
if (registryRule?.languages) {
|
|
326
|
+
return registryRule.languages;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Fallback to rule object
|
|
330
|
+
if (rule.languages) {
|
|
331
|
+
return rule.languages;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Default to common languages
|
|
335
|
+
return ['typescript', 'javascript'];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Load rule configuration
|
|
340
|
+
* Following Rule C006: Verb-noun naming
|
|
341
|
+
* @param {string} ruleId - Rule ID
|
|
342
|
+
* @param {string} ruleFolder - Rule folder name
|
|
343
|
+
* @param {string} category - Rule category (common, security, etc)
|
|
344
|
+
* @returns {Promise<Object>} Rule configuration
|
|
345
|
+
*/
|
|
346
|
+
async loadRuleConfig(ruleId, ruleFolder, category = 'common') {
|
|
347
|
+
try {
|
|
348
|
+
const configPath = path.resolve(__dirname, '../rules', category, ruleFolder, 'config.json');
|
|
349
|
+
if (fs.existsSync(configPath)) {
|
|
350
|
+
return require(configPath);
|
|
351
|
+
}
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.warn(`⚠️ Failed to load config for ${ruleId}:`, error.message);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Return minimal config
|
|
357
|
+
return {
|
|
358
|
+
ruleId,
|
|
359
|
+
name: `Rule ${ruleId}`,
|
|
360
|
+
description: `Analysis for rule ${ruleId}`,
|
|
361
|
+
severity: 'warning'
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Group files by programming language
|
|
367
|
+
* Following Rule C006: Verb-noun naming
|
|
368
|
+
* @param {string[]} files - Files to group
|
|
369
|
+
* @returns {Object} Files grouped by language
|
|
370
|
+
*/
|
|
371
|
+
groupFilesByLanguage(files) {
|
|
372
|
+
const groups = {};
|
|
373
|
+
|
|
374
|
+
for (const file of files) {
|
|
375
|
+
const language = this.detectLanguage(file);
|
|
376
|
+
if (!groups[language]) {
|
|
377
|
+
groups[language] = [];
|
|
378
|
+
}
|
|
379
|
+
groups[language].push(file);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return groups;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Detect programming language from file extension
|
|
387
|
+
* Following Rule C006: Verb-noun naming
|
|
388
|
+
* @param {string} filePath - File path
|
|
389
|
+
* @returns {string} Detected language
|
|
390
|
+
*/
|
|
391
|
+
detectLanguage(filePath) {
|
|
392
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
393
|
+
|
|
394
|
+
switch (ext) {
|
|
395
|
+
case '.ts': case '.tsx': return 'typescript';
|
|
396
|
+
case '.js': case '.jsx': return 'javascript';
|
|
397
|
+
case '.dart': return 'dart';
|
|
398
|
+
case '.swift': return 'swift';
|
|
399
|
+
case '.kt': case '.kts': return 'kotlin';
|
|
400
|
+
default: return 'unknown';
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Group violations by file path
|
|
406
|
+
* Following Rule C006: Verb-noun naming
|
|
407
|
+
* @param {Object[]} violations - Array of violations
|
|
408
|
+
* @returns {Map} Violations grouped by file
|
|
409
|
+
*/
|
|
410
|
+
groupViolationsByFile(violations) {
|
|
411
|
+
const groups = new Map();
|
|
412
|
+
|
|
413
|
+
for (const violation of violations) {
|
|
414
|
+
const filePath = violation.file || violation.filePath;
|
|
415
|
+
if (!filePath) continue;
|
|
416
|
+
|
|
417
|
+
if (!groups.has(filePath)) {
|
|
418
|
+
groups.set(filePath, []);
|
|
419
|
+
}
|
|
420
|
+
groups.get(filePath).push(violation);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return groups;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Get supported rules
|
|
428
|
+
* Following Rule C006: Verb-noun naming
|
|
429
|
+
* @returns {string[]} Supported rule IDs
|
|
430
|
+
*/
|
|
431
|
+
getSupportedRules() {
|
|
432
|
+
return this.supportedRulesList;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Run enhanced analysis with multiple strategies
|
|
437
|
+
* Automatically selects optimal analysis method per rule
|
|
438
|
+
* Following Rule C006: Verb-noun naming
|
|
439
|
+
*/
|
|
440
|
+
async runEnhancedAnalysis(analyzer, ruleId, files, language, options) {
|
|
441
|
+
// Create debug config from options
|
|
442
|
+
const debugConfig = {
|
|
443
|
+
enabled: options.debug || options.verbose || false,
|
|
444
|
+
logger: (component, message) => console.log(`🔧 [${component}] ${message}`)
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
// Debug logging based on debug flag
|
|
448
|
+
if (debugConfig.enabled) {
|
|
449
|
+
debugConfig.logger(this.constructor.name, `runEnhancedAnalysis called: rule=${ruleId}, language=${language}, files=${files.length}`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (options.verbose) {
|
|
453
|
+
console.log(`🔧 [DEBUG] runEnhancedAnalysis called: rule=${ruleId}, language=${language}, files=${files.length}`);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Load rule analysis strategy
|
|
457
|
+
const strategy = await this.getRuleAnalysisStrategy(ruleId, language);
|
|
458
|
+
|
|
459
|
+
// Debug logging based on debug flag
|
|
460
|
+
if (debugConfig.enabled) {
|
|
461
|
+
debugConfig.logger(this.constructor.name, `Rule ${ruleId}: ${strategy.primary} (approach: ${strategy.approach})`);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (options.verbose) {
|
|
465
|
+
console.log(`🔧 [Strategy] Rule ${ruleId}: ${strategy.primary} (fallback: ${strategy.fallback || 'none'})`);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
let violations = [];
|
|
469
|
+
let analysisResults = {
|
|
470
|
+
methods: [],
|
|
471
|
+
totalViolations: 0,
|
|
472
|
+
accuracy: 'unknown'
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// Execute analysis based on strategy
|
|
476
|
+
switch (strategy.approach) {
|
|
477
|
+
case 'ast-primary':
|
|
478
|
+
violations = await this.runASTPrimaryAnalysis(analyzer, ruleId, files, language, options, strategy, debugConfig);
|
|
479
|
+
break;
|
|
480
|
+
|
|
481
|
+
case 'regex-optimal':
|
|
482
|
+
violations = await this.runRegexOptimalAnalysis(analyzer, ruleId, files, language, options);
|
|
483
|
+
break;
|
|
484
|
+
|
|
485
|
+
case 'hybrid-combined':
|
|
486
|
+
violations = await this.runHybridAnalysis(analyzer, ruleId, files, language, options, strategy);
|
|
487
|
+
break;
|
|
488
|
+
|
|
489
|
+
case 'progressive-enhancement':
|
|
490
|
+
violations = await this.runProgressiveAnalysis(analyzer, ruleId, files, language, options, strategy);
|
|
491
|
+
break;
|
|
492
|
+
|
|
493
|
+
default:
|
|
494
|
+
// Fallback to existing logic
|
|
495
|
+
violations = await this.runLegacyAnalysis(analyzer, ruleId, files, language, options);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (options.verbose && violations.length > 0) {
|
|
499
|
+
console.log(`📊 [Analysis] Found ${violations.length} violations using ${strategy.approach}`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return violations;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Get optimal analysis strategy for a rule
|
|
507
|
+
*/
|
|
508
|
+
async getRuleAnalysisStrategy(ruleId, language) {
|
|
509
|
+
try {
|
|
510
|
+
const strategies = require('../config/rule-analysis-strategies');
|
|
511
|
+
|
|
512
|
+
// Check AST-preferred rules
|
|
513
|
+
if (strategies.astPreferred[ruleId]) {
|
|
514
|
+
const astAvailable = this.astRegistry.isASTSupportAvailable(language);
|
|
515
|
+
return {
|
|
516
|
+
approach: 'ast-primary',
|
|
517
|
+
primary: 'ast',
|
|
518
|
+
fallback: 'regex',
|
|
519
|
+
astAvailable,
|
|
520
|
+
config: strategies.astPreferred[ruleId]
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Check regex-optimal rules
|
|
525
|
+
if (strategies.regexOptimal[ruleId]) {
|
|
526
|
+
return {
|
|
527
|
+
approach: 'regex-optimal',
|
|
528
|
+
primary: 'regex',
|
|
529
|
+
config: strategies.regexOptimal[ruleId]
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Check hybrid rules
|
|
534
|
+
if (strategies.hybridOptimal[ruleId]) {
|
|
535
|
+
return {
|
|
536
|
+
approach: 'hybrid-combined',
|
|
537
|
+
primary: strategies.hybridOptimal[ruleId].strategy.split('-')[0],
|
|
538
|
+
config: strategies.hybridOptimal[ruleId]
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Check experimental rules
|
|
543
|
+
if (strategies.experimental[ruleId]) {
|
|
544
|
+
return {
|
|
545
|
+
approach: 'progressive-enhancement',
|
|
546
|
+
primary: 'regex',
|
|
547
|
+
fallback: 'ast',
|
|
548
|
+
config: strategies.experimental[ruleId]
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Default strategy
|
|
553
|
+
return {
|
|
554
|
+
approach: 'ast-primary',
|
|
555
|
+
primary: 'ast',
|
|
556
|
+
fallback: 'regex',
|
|
557
|
+
astAvailable: this.astRegistry.isASTSupportAvailable(language)
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
} catch (error) {
|
|
561
|
+
// Fallback if strategy config is not available
|
|
562
|
+
return {
|
|
563
|
+
approach: 'ast-primary',
|
|
564
|
+
primary: 'ast',
|
|
565
|
+
fallback: 'regex',
|
|
566
|
+
astAvailable: this.astRegistry.isASTSupportAvailable(language)
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* AST-primary analysis: Try AST first, fallback to regex
|
|
573
|
+
*/
|
|
574
|
+
async runASTPrimaryAnalysis(analyzer, ruleId, files, language, options, strategy, debugConfig) {
|
|
575
|
+
const violations = [];
|
|
576
|
+
|
|
577
|
+
if (debugConfig.enabled) {
|
|
578
|
+
debugConfig.logger(this.constructor.name, `Starting AST-primary analysis for ${ruleId}, AST available: ${strategy.astAvailable}`);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
for (const filePath of files) {
|
|
582
|
+
try {
|
|
583
|
+
const code = fs.readFileSync(filePath, 'utf8');
|
|
584
|
+
let analysisResult = null;
|
|
585
|
+
|
|
586
|
+
// Try AST analysis first if available
|
|
587
|
+
if (strategy.astAvailable) {
|
|
588
|
+
if (debugConfig.enabled) {
|
|
589
|
+
debugConfig.logger(this.constructor.name, `Attempting AST for file: ${filePath}`);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
try {
|
|
593
|
+
const astResult = await this.astRegistry.analyzeRule(ruleId, code, language, filePath);
|
|
594
|
+
if (astResult && astResult.length > 0) {
|
|
595
|
+
analysisResult = astResult.map(violation => ({
|
|
596
|
+
...violation,
|
|
597
|
+
filePath,
|
|
598
|
+
analysisMethod: 'ast',
|
|
599
|
+
confidence: strategy.config?.accuracy?.ast || 90
|
|
600
|
+
}));
|
|
601
|
+
|
|
602
|
+
if (debugConfig.enabled) {
|
|
603
|
+
debugConfig.logger(this.constructor.name, `AST found ${astResult.length} violations in ${filePath}`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
} catch (astError) {
|
|
607
|
+
if (debugConfig.enabled) {
|
|
608
|
+
debugConfig.logger(this.constructor.name, `AST failed for ${filePath}: ${astError.message}`);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (options.verbose) {
|
|
612
|
+
console.warn(`⚠️ AST analysis failed for ${filePath}, falling back to regex`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
} else {
|
|
616
|
+
if (debugConfig.enabled) {
|
|
617
|
+
debugConfig.logger(this.constructor.name, `AST not available, skipping to fallback for ${filePath}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Fallback to regex if AST failed or not available
|
|
622
|
+
if (!analysisResult) {
|
|
623
|
+
if (debugConfig.enabled) {
|
|
624
|
+
debugConfig.logger(this.constructor.name, `Using regex fallback for ${filePath}`);
|
|
625
|
+
}
|
|
626
|
+
const regexResult = await analyzer.analyze([filePath], language, options);
|
|
627
|
+
if (regexResult && regexResult.length > 0) {
|
|
628
|
+
analysisResult = regexResult.map(violation => ({
|
|
629
|
+
...violation,
|
|
630
|
+
analysisMethod: 'regex',
|
|
631
|
+
confidence: strategy.config?.accuracy?.regex || 75
|
|
632
|
+
}));
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (analysisResult) {
|
|
637
|
+
violations.push(...analysisResult);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
} catch (error) {
|
|
641
|
+
if (options.verbose) {
|
|
642
|
+
console.error(`❌ Analysis failed for ${filePath}:`, error.message);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return violations;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Regex-optimal analysis: Use regex as primary method
|
|
652
|
+
*/
|
|
653
|
+
async runRegexOptimalAnalysis(analyzer, ruleId, files, language, options) {
|
|
654
|
+
const violations = [];
|
|
655
|
+
|
|
656
|
+
const regexResult = await analyzer.analyze(files, language, options);
|
|
657
|
+
if (regexResult && regexResult.length > 0) {
|
|
658
|
+
violations.push(...regexResult.map(violation => ({
|
|
659
|
+
...violation,
|
|
660
|
+
analysisMethod: 'regex',
|
|
661
|
+
confidence: 95 // High confidence for regex-optimal rules
|
|
662
|
+
})));
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return violations;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Hybrid analysis: Combine multiple methods for best results
|
|
670
|
+
*/
|
|
671
|
+
async runHybridAnalysis(analyzer, ruleId, files, language, options, strategy) {
|
|
672
|
+
const violations = [];
|
|
673
|
+
const astViolations = [];
|
|
674
|
+
const regexViolations = [];
|
|
675
|
+
|
|
676
|
+
for (const filePath of files) {
|
|
677
|
+
try {
|
|
678
|
+
const code = fs.readFileSync(filePath, 'utf8');
|
|
679
|
+
|
|
680
|
+
// Run both AST and regex analysis
|
|
681
|
+
const analysisPromises = [];
|
|
682
|
+
|
|
683
|
+
// AST analysis
|
|
684
|
+
if (this.astRegistry.isASTSupportAvailable(language)) {
|
|
685
|
+
analysisPromises.push(
|
|
686
|
+
this.astRegistry.analyzeRule(ruleId, code, language, filePath)
|
|
687
|
+
.then(result => ({ type: 'ast', result, filePath }))
|
|
688
|
+
.catch(() => ({ type: 'ast', result: null, filePath }))
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Regex analysis
|
|
693
|
+
analysisPromises.push(
|
|
694
|
+
analyzer.analyze([filePath], language, options)
|
|
695
|
+
.then(result => ({ type: 'regex', result, filePath }))
|
|
696
|
+
.catch(() => ({ type: 'regex', result: null, filePath }))
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
const results = await Promise.all(analysisPromises);
|
|
700
|
+
|
|
701
|
+
// Process results based on strategy
|
|
702
|
+
for (const { type, result, filePath } of results) {
|
|
703
|
+
if (result && result.length > 0) {
|
|
704
|
+
const violations = result.map(violation => ({
|
|
705
|
+
...violation,
|
|
706
|
+
filePath,
|
|
707
|
+
analysisMethod: type,
|
|
708
|
+
confidence: strategy.config?.accuracy?.[type] || 85
|
|
709
|
+
}));
|
|
710
|
+
|
|
711
|
+
if (type === 'ast') {
|
|
712
|
+
astViolations.push(...violations);
|
|
713
|
+
} else {
|
|
714
|
+
regexViolations.push(...violations);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
} catch (error) {
|
|
720
|
+
if (options.verbose) {
|
|
721
|
+
console.error(`❌ Hybrid analysis failed for ${filePath}:`, error.message);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Combine results intelligently
|
|
727
|
+
const combinedViolations = this.combineHybridResults(
|
|
728
|
+
astViolations,
|
|
729
|
+
regexViolations,
|
|
730
|
+
strategy.config?.strategy || 'ast-primary-regex-fallback'
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
return combinedViolations;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Progressive enhancement: Start simple, enhance with advanced methods
|
|
738
|
+
*/
|
|
739
|
+
async runProgressiveAnalysis(analyzer, ruleId, files, language, options, strategy) {
|
|
740
|
+
const violations = [];
|
|
741
|
+
|
|
742
|
+
// Start with basic regex analysis
|
|
743
|
+
const regexResult = await analyzer.analyze(files, language, options);
|
|
744
|
+
if (regexResult) {
|
|
745
|
+
violations.push(...regexResult.map(violation => ({
|
|
746
|
+
...violation,
|
|
747
|
+
analysisMethod: 'regex',
|
|
748
|
+
confidence: 75
|
|
749
|
+
})));
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Enhance with AST if available and beneficial
|
|
753
|
+
if (this.astRegistry.isASTSupportAvailable(language) && violations.length > 0) {
|
|
754
|
+
// TODO: Implement AST enhancement for specific violation types
|
|
755
|
+
// This could involve re-analyzing files with violations for better precision
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return violations;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Legacy analysis method (for backward compatibility)
|
|
763
|
+
*/
|
|
764
|
+
async runLegacyAnalysis(analyzer, ruleId, files, language, options) {
|
|
765
|
+
const regexResult = await analyzer.analyze(files, language, options);
|
|
766
|
+
return regexResult || [];
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Intelligently combine AST and regex results
|
|
771
|
+
*/
|
|
772
|
+
combineHybridResults(astViolations, regexViolations, strategy) {
|
|
773
|
+
switch (strategy) {
|
|
774
|
+
case 'ast-primary-regex-fallback':
|
|
775
|
+
// Use AST results where available, fill gaps with regex
|
|
776
|
+
return this.mergeViolationsWithPriority(astViolations, regexViolations, 'ast');
|
|
777
|
+
|
|
778
|
+
case 'regex-primary-ast-enhancement':
|
|
779
|
+
// Use regex as base, enhance with AST insights
|
|
780
|
+
return this.mergeViolationsWithPriority(regexViolations, astViolations, 'regex');
|
|
781
|
+
|
|
782
|
+
case 'union':
|
|
783
|
+
// Combine all violations (may have duplicates)
|
|
784
|
+
return [...astViolations, ...regexViolations];
|
|
785
|
+
|
|
786
|
+
case 'intersection':
|
|
787
|
+
// Only violations found by both methods
|
|
788
|
+
return this.findIntersectionViolations(astViolations, regexViolations);
|
|
789
|
+
|
|
790
|
+
default:
|
|
791
|
+
return astViolations.length > 0 ? astViolations : regexViolations;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Merge violations with priority given to one method
|
|
797
|
+
*/
|
|
798
|
+
mergeViolationsWithPriority(primary, secondary, primaryType) {
|
|
799
|
+
const merged = [...primary];
|
|
800
|
+
const primaryLocations = new Set(
|
|
801
|
+
primary.map(v => `${v.filePath}:${v.line}:${v.column}`)
|
|
802
|
+
);
|
|
803
|
+
|
|
804
|
+
// Add secondary violations that don't conflict with primary
|
|
805
|
+
for (const violation of secondary) {
|
|
806
|
+
const location = `${violation.filePath}:${violation.line}:${violation.column}`;
|
|
807
|
+
if (!primaryLocations.has(location)) {
|
|
808
|
+
merged.push({
|
|
809
|
+
...violation,
|
|
810
|
+
isSecondary: true,
|
|
811
|
+
primaryMethod: primaryType
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return merged;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Find violations detected by both methods (high confidence)
|
|
821
|
+
*/
|
|
822
|
+
findIntersectionViolations(astViolations, regexViolations) {
|
|
823
|
+
const intersection = [];
|
|
824
|
+
|
|
825
|
+
for (const astViolation of astViolations) {
|
|
826
|
+
const matching = regexViolations.find(regexViolation =>
|
|
827
|
+
astViolation.filePath === regexViolation.filePath &&
|
|
828
|
+
Math.abs(astViolation.line - regexViolation.line) <= 2 // Allow small line differences
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
if (matching) {
|
|
832
|
+
intersection.push({
|
|
833
|
+
...astViolation,
|
|
834
|
+
analysisMethod: 'hybrid-intersection',
|
|
835
|
+
confidence: 98, // Very high confidence
|
|
836
|
+
confirmedBy: ['ast', 'regex']
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
return intersection;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Cleanup Heuristic engine resources
|
|
846
|
+
* Following Rule C006: Verb-noun naming
|
|
847
|
+
*/
|
|
848
|
+
async cleanup() {
|
|
849
|
+
// Clear analyzer cache
|
|
850
|
+
this.ruleAnalyzers.clear();
|
|
851
|
+
this.supportedRulesList = [];
|
|
852
|
+
|
|
853
|
+
await super.cleanup();
|
|
854
|
+
if (this.verbose) {
|
|
855
|
+
console.log('🔍 Heuristic engine cleanup completed');
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
module.exports = HeuristicEngine;
|