@sun-asterisk/sunlint 1.0.5
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/CHANGELOG.md +202 -0
- package/LICENSE +21 -0
- package/README.md +490 -0
- package/cli-legacy.js +355 -0
- package/cli.js +35 -0
- package/config/default.json +22 -0
- package/config/presets/beginner.json +36 -0
- package/config/presets/ci.json +46 -0
- package/config/presets/recommended.json +24 -0
- package/config/presets/strict.json +32 -0
- package/config/rules-registry.json +681 -0
- package/config/sunlint-schema.json +166 -0
- package/config/typescript/custom-rules-new.js +0 -0
- package/config/typescript/custom-rules.js +9 -0
- package/config/typescript/eslint.config.js +110 -0
- package/config/typescript/package-lock.json +1585 -0
- package/config/typescript/package.json +13 -0
- package/config/typescript/security-rules/index.js +90 -0
- package/config/typescript/security-rules/s005-no-origin-auth.js +95 -0
- package/config/typescript/security-rules/s006-activation-recovery-secret-not-plaintext.js +69 -0
- package/config/typescript/security-rules/s008-crypto-agility.js +62 -0
- package/config/typescript/security-rules/s009-no-insecure-crypto.js +103 -0
- package/config/typescript/security-rules/s010-no-insecure-random-in-sensitive-context.js +123 -0
- package/config/typescript/security-rules/s011-no-insecure-uuid.js +66 -0
- package/config/typescript/security-rules/s012-hardcode-secret.js +71 -0
- package/config/typescript/security-rules/s014-insecure-tls-version.js +50 -0
- package/config/typescript/security-rules/s015-insecure-tls-certificate.js +43 -0
- package/config/typescript/security-rules/s016-sensitive-query-parameter.js +59 -0
- package/config/typescript/security-rules/s017-no-sql-injection.js +193 -0
- package/config/typescript/security-rules/s018-positive-input-validation.js +56 -0
- package/config/typescript/security-rules/s019-no-raw-user-input-in-email.js +113 -0
- package/config/typescript/security-rules/s020-no-eval-dynamic-execution.js +89 -0
- package/config/typescript/security-rules/s022-output-encoding.js +78 -0
- package/config/typescript/security-rules/s023-no-json-injection.js +300 -0
- package/config/typescript/security-rules/s025-server-side-input-validation.js +217 -0
- package/config/typescript/security-rules/s026-json-schema-validation.js +68 -0
- package/config/typescript/security-rules/s027-no-hardcoded-secrets.js +80 -0
- package/config/typescript/security-rules/s029-require-csrf-protection.js +79 -0
- package/config/typescript/security-rules/s030-no-directory-browsing.js +78 -0
- package/config/typescript/security-rules/s033-require-samesite-cookie.js +80 -0
- package/config/typescript/security-rules/s034-require-host-cookie-prefix.js +77 -0
- package/config/typescript/security-rules/s035-cookie-specific-path.js +74 -0
- package/config/typescript/security-rules/s036-no-unsafe-file-include.js +68 -0
- package/config/typescript/security-rules/s037-require-anti-cache-headers.js +70 -0
- package/config/typescript/security-rules/s038-no-version-disclosure.js +74 -0
- package/config/typescript/security-rules/s039-no-session-token-in-url.js +63 -0
- package/config/typescript/security-rules/s041-require-session-invalidate-on-logout.js +211 -0
- package/config/typescript/security-rules/s042-require-periodic-reauthentication.js +294 -0
- package/config/typescript/security-rules/s043-terminate-sessions-on-password-change.js +254 -0
- package/config/typescript/security-rules/s044-require-full-session-for-sensitive-operations.js +292 -0
- package/config/typescript/security-rules/s045-anti-automation-controls.js +46 -0
- package/config/typescript/security-rules/s046-secure-notification-on-auth-change.js +44 -0
- package/config/typescript/security-rules/s048-password-credential-recovery.js +54 -0
- package/config/typescript/security-rules/s050-session-token-weak-hash.js +94 -0
- package/config/typescript/security-rules/s052-secure-random-authentication-code.js +66 -0
- package/config/typescript/security-rules/s054-verification-default-account.js +109 -0
- package/config/typescript/security-rules/s057-utc-logging.js +54 -0
- package/config/typescript/security-rules/s058-no-ssrf.js +73 -0
- package/config/typescript/test-s005-working.ts +22 -0
- package/config/typescript/tsconfig.json +29 -0
- package/core/ai-analyzer.js +169 -0
- package/core/analysis-orchestrator.js +705 -0
- package/core/cli-action-handler.js +230 -0
- package/core/cli-program.js +106 -0
- package/core/config-manager.js +396 -0
- package/core/config-merger.js +136 -0
- package/core/config-override-processor.js +74 -0
- package/core/config-preset-resolver.js +65 -0
- package/core/config-source-loader.js +152 -0
- package/core/config-validator.js +126 -0
- package/core/dependency-manager.js +105 -0
- package/core/eslint-engine-service.js +312 -0
- package/core/eslint-instance-manager.js +104 -0
- package/core/eslint-integration-service.js +363 -0
- package/core/git-utils.js +170 -0
- package/core/multi-rule-runner.js +239 -0
- package/core/output-service.js +250 -0
- package/core/report-generator.js +320 -0
- package/core/rule-mapping-service.js +309 -0
- package/core/rule-selection-service.js +121 -0
- package/core/sunlint-engine-service.js +23 -0
- package/core/typescript-analyzer.js +262 -0
- package/core/typescript-engine.js +313 -0
- 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/DEBUG.md +86 -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/docs/RULE-RESPONSIBILITY-MATRIX.md +204 -0
- package/eslint-integration/.eslintrc.js +98 -0
- package/eslint-integration/cli.js +35 -0
- package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +204 -0
- package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +246 -0
- package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +207 -0
- package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +90 -0
- package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +43 -0
- package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +38 -0
- package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +39 -0
- package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +335 -0
- package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +142 -0
- package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +50 -0
- package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +80 -0
- package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +294 -0
- package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +34 -0
- package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +32 -0
- package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +64 -0
- package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +406 -0
- package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +300 -0
- package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +239 -0
- package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +31 -0
- package/eslint-integration/eslint-plugin-custom/c076-one-assert-per-test.js +184 -0
- package/eslint-integration/eslint-plugin-custom/index.js +155 -0
- package/eslint-integration/eslint-plugin-custom/package.json +13 -0
- package/eslint-integration/eslint-plugin-custom/package.json.bak +9 -0
- package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +86 -0
- package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +95 -0
- package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +69 -0
- package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +62 -0
- package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +103 -0
- package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +123 -0
- package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +66 -0
- package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +71 -0
- package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +50 -0
- package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +43 -0
- package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +59 -0
- package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +193 -0
- package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +56 -0
- package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +113 -0
- package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +89 -0
- package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +78 -0
- package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +300 -0
- package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +217 -0
- package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +68 -0
- package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +80 -0
- package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +79 -0
- package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +78 -0
- package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +80 -0
- package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +77 -0
- package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +74 -0
- package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +68 -0
- package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +70 -0
- package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +74 -0
- package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +63 -0
- package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +211 -0
- package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +294 -0
- package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +254 -0
- package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +292 -0
- package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +46 -0
- package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +44 -0
- package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +108 -0
- package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +54 -0
- package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +94 -0
- package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +66 -0
- package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +109 -0
- package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +143 -0
- package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +54 -0
- package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +73 -0
- package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +42 -0
- package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +48 -0
- package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +160 -0
- package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +52 -0
- package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +175 -0
- package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +95 -0
- package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +48 -0
- package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +377 -0
- package/eslint-integration/eslint.config.js +125 -0
- package/eslint-integration/eslint.config.simple.js +24 -0
- package/eslint-integration/node_modules/eslint-plugin-custom/package.json +0 -0
- package/eslint-integration/package.json +23 -0
- package/eslint-integration/sample.ts +53 -0
- package/eslint-integration/test-s003.js +5 -0
- package/eslint-integration/tsconfig.json +27 -0
- package/examples/.github/workflows/code-quality.yml +111 -0
- package/examples/.sunlint.json +42 -0
- package/examples/README.md +47 -0
- package/examples/package.json +33 -0
- package/package.json +100 -0
- package/rules/C006_function_naming/analyzer.js +338 -0
- package/rules/C006_function_naming/config.json +86 -0
- package/rules/C019_log_level_usage/analyzer.js +359 -0
- package/rules/C019_log_level_usage/config.json +121 -0
- package/rules/C029_catch_block_logging/analyzer.js +339 -0
- package/rules/C029_catch_block_logging/config.json +59 -0
- package/rules/C031_validation_separation/README.md +72 -0
- package/rules/C031_validation_separation/analyzer.js +186 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analysis Orchestrator
|
|
3
|
+
* Following Rule C005: Single responsibility - orchestrate analysis
|
|
4
|
+
* Following Rule C014: Dependency Injection - inject engines
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const MultiRuleRunner = require('./multi-rule-runner');
|
|
9
|
+
const TypeScriptEngine = require('./typescript-engine');
|
|
10
|
+
const RuleMappingService = require('./rule-mapping-service');
|
|
11
|
+
const ESLintIntegrationService = require('./eslint-integration-service');
|
|
12
|
+
|
|
13
|
+
class AnalysisOrchestrator {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.multiRuleRunner = null;
|
|
16
|
+
this.typeScriptEngine = null;
|
|
17
|
+
this.ruleMappingService = new RuleMappingService();
|
|
18
|
+
this.eslintIntegrationService = new ESLintIntegrationService();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async runAnalysis(rulesToRun, options, config = {}) {
|
|
22
|
+
try {
|
|
23
|
+
// Check for ESLint integration mode
|
|
24
|
+
if (this.shouldUseESLintIntegration(options, config)) {
|
|
25
|
+
return await this.runESLintIntegratedAnalysis(rulesToRun, options, config);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check AI configuration and provide feedback
|
|
29
|
+
if (options.ai === true) {
|
|
30
|
+
this.checkAIConfiguration(rulesToRun, config);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check if user explicitly requests TypeScript engine
|
|
34
|
+
if (options.typescript || options.typescriptEngine === 'eslint') {
|
|
35
|
+
// Force all rules through TypeScript engine when --typescript flag is used
|
|
36
|
+
return await this.runTypeScriptAnalysis(rulesToRun, options, config);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Auto-detect based on rule analyzer
|
|
40
|
+
const eslintRules = rulesToRun.filter(rule =>
|
|
41
|
+
rule.analyzer === 'eslint' ||
|
|
42
|
+
rule.analyzer === 'typescript' // Legacy typescript analyzer routes to eslint
|
|
43
|
+
);
|
|
44
|
+
const customRules = rulesToRun.filter(rule =>
|
|
45
|
+
rule.analyzer &&
|
|
46
|
+
rule.analyzer !== 'eslint' &&
|
|
47
|
+
rule.analyzer !== 'typescript'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Debug logging for rule routing
|
|
51
|
+
if (!options.quiet && options.format !== 'json') {
|
|
52
|
+
console.log(chalk.cyan(`🔍 Rule routing: ${eslintRules.length} ESLint rules, ${customRules.length} custom rules`));
|
|
53
|
+
if (eslintRules.length > 0) {
|
|
54
|
+
console.log(chalk.cyan(` ESLint rules: ${eslintRules.map(r => r.id).join(', ')}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If we have ESLint rules, run TypeScript analysis
|
|
59
|
+
if (eslintRules.length > 0) {
|
|
60
|
+
return await this.runTypeScriptAnalysis(eslintRules, options, config);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// For custom analyzer rules, use multi-rule runner
|
|
64
|
+
if (customRules.length > 0) {
|
|
65
|
+
// Initialize multi-rule runner if available
|
|
66
|
+
if (!this.multiRuleRunner) {
|
|
67
|
+
try {
|
|
68
|
+
this.multiRuleRunner = new MultiRuleRunner(config, options);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.log(chalk.yellow('⚠️ Multi-rule runner not available, using basic analysis'));
|
|
71
|
+
return this.runBasicAnalysis(rulesToRun, options, config);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Run analysis using existing multi-rule runner
|
|
76
|
+
return await this.multiRuleRunner.runRules(customRules, options.input, options);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Fallback to basic analysis
|
|
80
|
+
return this.runBasicAnalysis(rulesToRun, options, config);
|
|
81
|
+
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.log(chalk.yellow('⚠️ Falling back to basic analysis'));
|
|
84
|
+
return this.runBasicAnalysis(rulesToRun, options, config);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async runBasicAnalysis(rulesToRun, options, config = {}) {
|
|
89
|
+
console.log(chalk.blue(`🔄 Analyzing ${rulesToRun.length} rules...`));
|
|
90
|
+
|
|
91
|
+
// Basic mock analysis for now
|
|
92
|
+
const results = {
|
|
93
|
+
results: [],
|
|
94
|
+
filesAnalyzed: 1,
|
|
95
|
+
engine: 'sunlint-basic'
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// For demonstration, create some mock violations for C006
|
|
99
|
+
if (rulesToRun.some(rule => rule.id === 'C006')) {
|
|
100
|
+
results.results.push({
|
|
101
|
+
file: options.input,
|
|
102
|
+
violations: [
|
|
103
|
+
{
|
|
104
|
+
ruleId: 'C006',
|
|
105
|
+
severity: 'warning',
|
|
106
|
+
message: 'Function name should follow verb-noun pattern',
|
|
107
|
+
line: 1,
|
|
108
|
+
column: 1
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return results;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Run TypeScript analysis using ESLint
|
|
119
|
+
* Following Rule C005: Single responsibility
|
|
120
|
+
*/
|
|
121
|
+
async runTypeScriptAnalysis(rulesToRun, options, config = {}) {
|
|
122
|
+
try {
|
|
123
|
+
// Initialize TypeScript engine
|
|
124
|
+
if (!this.typeScriptEngine) {
|
|
125
|
+
this.typeScriptEngine = new TypeScriptEngine();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check and install dependencies if needed
|
|
129
|
+
if (options.ensureDeps) {
|
|
130
|
+
await this.ensureTypeScriptDependencies();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Configure rules dynamically (important for security rules)
|
|
134
|
+
const selectedRuleIds = rulesToRun.map(rule => rule.id);
|
|
135
|
+
await this.typeScriptEngine.configureRules(selectedRuleIds, options);
|
|
136
|
+
|
|
137
|
+
// Filter rules that can be handled by ESLint
|
|
138
|
+
const eslintSupportedRules = rulesToRun.filter(rule =>
|
|
139
|
+
this.ruleMappingService.isSunLintRuleSupportedByEslint(rule.id)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (eslintSupportedRules.length === 0) {
|
|
143
|
+
if (!options.quiet && options.format !== 'json') {
|
|
144
|
+
console.log(chalk.yellow('⚠️ No ESLint-supported rules found, falling back to basic analysis'));
|
|
145
|
+
}
|
|
146
|
+
return this.runBasicAnalysis(rulesToRun, options, config);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!options.quiet && options.format !== 'json') {
|
|
150
|
+
console.log(chalk.blue(`🔄 Running TypeScript analysis with ${eslintSupportedRules.length} rules...`));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Run ESLint analysis
|
|
154
|
+
const inputPaths = options.isFileList ? options.input : [options.input];
|
|
155
|
+
const eslintResults = await this.typeScriptEngine.runAnalysis(inputPaths, options);
|
|
156
|
+
|
|
157
|
+
// Convert results to SunLint format
|
|
158
|
+
const sunlintResults = this.typeScriptEngine.convertToSunLintFormat(
|
|
159
|
+
eslintResults,
|
|
160
|
+
this.ruleMappingService.eslintToSunlintMapping || {},
|
|
161
|
+
options
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Filter results to only include requested rules
|
|
165
|
+
const filteredResults = this.filterResultsByRules(sunlintResults, eslintSupportedRules);
|
|
166
|
+
|
|
167
|
+
return filteredResults;
|
|
168
|
+
|
|
169
|
+
} catch (error) {
|
|
170
|
+
// Following Rule C035: Log full error information
|
|
171
|
+
console.error(chalk.red('❌ TypeScript analysis failed:'), {
|
|
172
|
+
message: error.message,
|
|
173
|
+
rules: rulesToRun.map(r => r.id),
|
|
174
|
+
options,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
console.log(chalk.yellow('⚠️ Falling back to basic analysis'));
|
|
178
|
+
return this.runBasicAnalysis(rulesToRun, options, config);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Ensure TypeScript dependencies are installed
|
|
184
|
+
* Following Rule C006: Verb-noun naming
|
|
185
|
+
*/
|
|
186
|
+
async ensureTypeScriptDependencies() {
|
|
187
|
+
const deps = await this.typeScriptEngine.checkDependencies();
|
|
188
|
+
|
|
189
|
+
if (!deps.nodeModulesExists) {
|
|
190
|
+
console.log(chalk.blue('📦 Installing TypeScript dependencies...'));
|
|
191
|
+
await this.typeScriptEngine.installDependencies();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Filter results by requested rules
|
|
197
|
+
* Following Rule C012: Query - pure function
|
|
198
|
+
*/
|
|
199
|
+
filterResultsByRules(results, requestedRules) {
|
|
200
|
+
const requestedRuleIds = new Set(requestedRules.map(r => r.id));
|
|
201
|
+
|
|
202
|
+
const filteredResults = {
|
|
203
|
+
...results,
|
|
204
|
+
results: results.results.map(fileResult => ({
|
|
205
|
+
...fileResult,
|
|
206
|
+
violations: fileResult.violations.filter(violation =>
|
|
207
|
+
requestedRuleIds.has(violation.ruleId)
|
|
208
|
+
)
|
|
209
|
+
})).filter(fileResult => fileResult.violations.length > 0)
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Recalculate total violations
|
|
213
|
+
filteredResults.totalViolations = filteredResults.results.reduce(
|
|
214
|
+
(total, fileResult) => total + fileResult.violations.length,
|
|
215
|
+
0
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
return filteredResults;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Check AI configuration and provide user feedback
|
|
223
|
+
* Following Rule C006: Verb-noun naming - checkAIConfiguration
|
|
224
|
+
*/
|
|
225
|
+
checkAIConfiguration(rulesToRun, config) {
|
|
226
|
+
const chalk = require('chalk');
|
|
227
|
+
const hasApiKey = process.env.OPENAI_API_KEY || (config.ai && config.ai.apiKey);
|
|
228
|
+
|
|
229
|
+
// Check which rules support AI
|
|
230
|
+
const aiSupportedRules = ['C019', 'C029']; // Rules that have AI analyzer support
|
|
231
|
+
const aiCapableRules = rulesToRun.filter(rule => aiSupportedRules.includes(rule.id));
|
|
232
|
+
const nonAiRules = rulesToRun.filter(rule => !aiSupportedRules.includes(rule.id));
|
|
233
|
+
|
|
234
|
+
if (nonAiRules.length > 0) {
|
|
235
|
+
console.log(chalk.yellow(`⚠️ AI analysis not supported for: ${nonAiRules.map(r => r.id).join(', ')}`));
|
|
236
|
+
console.log(chalk.gray(` These rules will use pattern-based analysis`));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (aiCapableRules.length > 0) {
|
|
240
|
+
if (!hasApiKey) {
|
|
241
|
+
console.log(chalk.yellow(`⚠️ OPENAI_API_KEY not set for AI-capable rules: ${aiCapableRules.map(r => r.id).join(', ')}`));
|
|
242
|
+
console.log(chalk.gray(` Falling back to pattern-based analysis`));
|
|
243
|
+
console.log(chalk.gray(` To enable AI analysis: export OPENAI_API_KEY="your-api-key"`));
|
|
244
|
+
} else {
|
|
245
|
+
console.log(chalk.green(`🤖 AI analysis enabled for: ${aiCapableRules.map(r => r.id).join(', ')}`));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Check if ESLint integration should be used
|
|
252
|
+
*/
|
|
253
|
+
shouldUseESLintIntegration(options, config) {
|
|
254
|
+
return (
|
|
255
|
+
options.eslintIntegration ||
|
|
256
|
+
config.eslintIntegration?.enabled ||
|
|
257
|
+
options.typescript // Auto-enable for TypeScript analysis
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Run analysis with ESLint integration (merged config)
|
|
263
|
+
*/
|
|
264
|
+
async runESLintIntegratedAnalysis(rulesToRun, options, config) {
|
|
265
|
+
try {
|
|
266
|
+
if (!options.quiet) {
|
|
267
|
+
console.log(chalk.blue('🔗 Running integrated analysis (SunLint + ESLint)'));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Determine file list
|
|
271
|
+
const filePaths = this.getFileList(options);
|
|
272
|
+
|
|
273
|
+
// First, run SunLint custom rules
|
|
274
|
+
const sunlintResults = await this.runStandardAnalysis(rulesToRun, options, config);
|
|
275
|
+
|
|
276
|
+
// Then, run integrated ESLint analysis
|
|
277
|
+
const eslintResults = await this.eslintIntegrationService.runIntegratedAnalysis(
|
|
278
|
+
filePaths,
|
|
279
|
+
config,
|
|
280
|
+
{
|
|
281
|
+
fix: options.fix,
|
|
282
|
+
quiet: true, // Suppress ESLint service logging
|
|
283
|
+
verbose: options.verbose
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Merge SunLint and ESLint results
|
|
288
|
+
if (!options.quiet) {
|
|
289
|
+
console.log('Debug: SunLint results structure:', JSON.stringify(sunlintResults, null, 2).substring(0, 300));
|
|
290
|
+
console.log('Debug: ESLint results structure:', JSON.stringify(eslintResults, null, 2).substring(0, 300));
|
|
291
|
+
}
|
|
292
|
+
const mergedResults = this.mergeAnalysisResults(sunlintResults, eslintResults, options);
|
|
293
|
+
|
|
294
|
+
// Log integration summary with merged results
|
|
295
|
+
if (!options.quiet) {
|
|
296
|
+
this.eslintIntegrationService.logIntegrationSummary(
|
|
297
|
+
eslintResults.categorized || { sunlint: [], user: [], combined: [] },
|
|
298
|
+
true,
|
|
299
|
+
mergedResults
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Add metadata
|
|
304
|
+
mergedResults.engine = 'sunlint-eslint-integrated';
|
|
305
|
+
mergedResults.integration = {
|
|
306
|
+
totalRules: eslintResults.totalRules,
|
|
307
|
+
sunlintRules: mergedResults.sources.sunlint,
|
|
308
|
+
userRules: mergedResults.sources.eslint,
|
|
309
|
+
categorized: eslintResults.categorized
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// Convert merged results back to ESLint-compatible format for output
|
|
313
|
+
const finalResults = this.convertMergedResultsToESLintFormat(mergedResults);
|
|
314
|
+
|
|
315
|
+
// Update metadata for proper display
|
|
316
|
+
finalResults.metadata = {
|
|
317
|
+
...finalResults.metadata,
|
|
318
|
+
totalViolations: finalResults.violationCount,
|
|
319
|
+
totalFiles: finalResults.totalFiles
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
return finalResults;
|
|
323
|
+
|
|
324
|
+
} catch (error) {
|
|
325
|
+
if (!options.quiet) {
|
|
326
|
+
console.log(chalk.yellow(`⚠️ ESLint integration failed: ${error.message}`));
|
|
327
|
+
console.log(chalk.yellow(' Falling back to standard SunLint analysis'));
|
|
328
|
+
}
|
|
329
|
+
return this.runStandardAnalysis(rulesToRun, options, config);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Get file list based on options
|
|
335
|
+
*/
|
|
336
|
+
getFileList(options) {
|
|
337
|
+
if (options.isFileList && Array.isArray(options.input)) {
|
|
338
|
+
return options.input;
|
|
339
|
+
} else if (typeof options.input === 'string') {
|
|
340
|
+
// Convert single path to array
|
|
341
|
+
return [options.input];
|
|
342
|
+
} else {
|
|
343
|
+
return ['src/**/*.{ts,tsx,js,jsx}'];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Convert ESLint results to SunLint format
|
|
349
|
+
*/
|
|
350
|
+
convertESLintResultsToSunLintFormat(eslintResults, options) {
|
|
351
|
+
const results = {
|
|
352
|
+
results: [],
|
|
353
|
+
filesAnalyzed: eslintResults.results.length,
|
|
354
|
+
engine: 'eslint-integrated'
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
eslintResults.results.forEach(fileResult => {
|
|
358
|
+
if (fileResult.messages && fileResult.messages.length > 0) {
|
|
359
|
+
const violations = fileResult.messages.map(message => ({
|
|
360
|
+
ruleId: message.ruleId,
|
|
361
|
+
severity: this.mapESLintSeverityToSunLint(message.severity),
|
|
362
|
+
message: message.message,
|
|
363
|
+
line: message.line,
|
|
364
|
+
column: message.column,
|
|
365
|
+
source: this.eslintIntegrationService.isSunLintRule(message.ruleId) ? 'sunlint' : 'user-eslint'
|
|
366
|
+
}));
|
|
367
|
+
|
|
368
|
+
results.results.push({
|
|
369
|
+
filePath: fileResult.filePath,
|
|
370
|
+
violations: violations
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
return results;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Map ESLint severity to SunLint severity
|
|
380
|
+
*/
|
|
381
|
+
mapESLintSeverityToSunLint(eslintSeverity) {
|
|
382
|
+
switch (eslintSeverity) {
|
|
383
|
+
case 1: return 'warning';
|
|
384
|
+
case 2: return 'error';
|
|
385
|
+
default: return 'info';
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Merge SunLint custom rules results with ESLint results with conflict resolution
|
|
391
|
+
*/
|
|
392
|
+
mergeAnalysisResults(sunlintResults, eslintResults, options) {
|
|
393
|
+
// Convert ESLint results to SunLint format
|
|
394
|
+
const eslintFormatted = this.convertESLintResultsToSunLintFormat(eslintResults, options);
|
|
395
|
+
|
|
396
|
+
// Create violation maps for conflict detection
|
|
397
|
+
const sunlintViolations = new Map();
|
|
398
|
+
const eslintViolations = new Map();
|
|
399
|
+
const mergedViolations = [];
|
|
400
|
+
|
|
401
|
+
// Extract SunLint violations from results format (ESLint-compatible)
|
|
402
|
+
const sunlintResults_violations = this.extractViolationsFromResults(sunlintResults);
|
|
403
|
+
|
|
404
|
+
// Process SunLint violations first (higher priority)
|
|
405
|
+
if (sunlintResults_violations && sunlintResults_violations.length > 0) {
|
|
406
|
+
sunlintResults_violations.forEach(violation => {
|
|
407
|
+
const key = `${violation.file}:${violation.line}:${violation.column}`;
|
|
408
|
+
sunlintViolations.set(key, {
|
|
409
|
+
...violation,
|
|
410
|
+
source: 'sunlint',
|
|
411
|
+
priority: 1
|
|
412
|
+
});
|
|
413
|
+
mergedViolations.push({
|
|
414
|
+
...violation,
|
|
415
|
+
source: 'sunlint'
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Extract ESLint violations
|
|
421
|
+
const eslintResults_violations = this.extractViolationsFromResults(eslintFormatted);
|
|
422
|
+
|
|
423
|
+
// Process ESLint violations with conflict resolution
|
|
424
|
+
if (eslintResults_violations && eslintResults_violations.length > 0) {
|
|
425
|
+
eslintResults_violations.forEach(violation => {
|
|
426
|
+
const key = `${violation.file}:${violation.line}:${violation.column}`;
|
|
427
|
+
|
|
428
|
+
// Check for conflicts
|
|
429
|
+
if (sunlintViolations.has(key)) {
|
|
430
|
+
const sunlintViolation = sunlintViolations.get(key);
|
|
431
|
+
|
|
432
|
+
// Check if it's the same rule concept (e.g., both about console.log)
|
|
433
|
+
const isSameRule = this.isConflictingRule(sunlintViolation.ruleId, violation.ruleId);
|
|
434
|
+
|
|
435
|
+
if (isSameRule) {
|
|
436
|
+
// SunLint takes priority, add ESLint as additional info
|
|
437
|
+
const mergedViolation = mergedViolations.find(v =>
|
|
438
|
+
v.file === violation.file && v.line === violation.line && v.column === violation.column
|
|
439
|
+
);
|
|
440
|
+
if (mergedViolation) {
|
|
441
|
+
mergedViolation.additionalInfo = {
|
|
442
|
+
eslintRule: violation.ruleId,
|
|
443
|
+
eslintMessage: violation.message
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
447
|
+
// Different rules on same line, keep both
|
|
448
|
+
mergedViolations.push({
|
|
449
|
+
...violation,
|
|
450
|
+
source: 'eslint'
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
// No conflict, add ESLint violation
|
|
455
|
+
eslintViolations.set(key, violation);
|
|
456
|
+
mergedViolations.push({
|
|
457
|
+
...violation,
|
|
458
|
+
source: 'eslint'
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Create combined results in ESLint format
|
|
465
|
+
const mergedResults = {
|
|
466
|
+
...eslintFormatted,
|
|
467
|
+
violations: mergedViolations,
|
|
468
|
+
violationCount: mergedViolations.length,
|
|
469
|
+
totalFiles: Math.max(sunlintResults.totalFiles || 0, eslintFormatted.totalFiles || 0),
|
|
470
|
+
analysisTime: (sunlintResults.analysisTime || 0) + (eslintFormatted.analysisTime || 0),
|
|
471
|
+
sources: {
|
|
472
|
+
sunlint: sunlintViolations.size,
|
|
473
|
+
eslint: eslintViolations.size,
|
|
474
|
+
conflicts: mergedViolations.filter(v => v.additionalInfo).length
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
return mergedResults;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Extract violations from results in consistent format
|
|
483
|
+
*/
|
|
484
|
+
extractViolationsFromResults(results) {
|
|
485
|
+
const violations = [];
|
|
486
|
+
|
|
487
|
+
if (results && results.results) {
|
|
488
|
+
// Handle different result structures
|
|
489
|
+
results.results.forEach(result => {
|
|
490
|
+
// SunLint format: result.violations[]
|
|
491
|
+
if (result.violations && Array.isArray(result.violations)) {
|
|
492
|
+
result.violations.forEach(violation => {
|
|
493
|
+
violations.push({
|
|
494
|
+
file: violation.file,
|
|
495
|
+
line: violation.line,
|
|
496
|
+
column: violation.column || 1,
|
|
497
|
+
ruleId: violation.ruleId,
|
|
498
|
+
severity: violation.severity || 'warning',
|
|
499
|
+
message: violation.message
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
// ESLint format: result.messages[]
|
|
504
|
+
else if (result.messages && Array.isArray(result.messages)) {
|
|
505
|
+
result.messages.forEach(message => {
|
|
506
|
+
violations.push({
|
|
507
|
+
file: result.filePath,
|
|
508
|
+
line: message.line,
|
|
509
|
+
column: message.column,
|
|
510
|
+
ruleId: message.ruleId,
|
|
511
|
+
severity: this.mapESLintSeverityToString(message.severity),
|
|
512
|
+
message: message.message
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
} else if (Array.isArray(results)) {
|
|
518
|
+
// Direct array format
|
|
519
|
+
results.forEach(fileResult => {
|
|
520
|
+
if (fileResult.messages && fileResult.messages.length > 0) {
|
|
521
|
+
fileResult.messages.forEach(message => {
|
|
522
|
+
violations.push({
|
|
523
|
+
file: fileResult.filePath,
|
|
524
|
+
line: message.line,
|
|
525
|
+
column: message.column,
|
|
526
|
+
ruleId: message.ruleId,
|
|
527
|
+
severity: this.mapESLintSeverityToString(message.severity),
|
|
528
|
+
message: message.message
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return violations;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Map ESLint severity numbers to strings
|
|
540
|
+
*/
|
|
541
|
+
mapESLintSeverityToString(severity) {
|
|
542
|
+
switch (severity) {
|
|
543
|
+
case 1: return 'warning';
|
|
544
|
+
case 2: return 'error';
|
|
545
|
+
default: return 'info';
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Check if two rules are conflicting (same concept)
|
|
551
|
+
*/
|
|
552
|
+
isConflictingRule(sunlintRule, eslintRule) {
|
|
553
|
+
const ruleMapping = {
|
|
554
|
+
'C019': ['no-console'], // Console logging rules
|
|
555
|
+
'C045': ['no-console'], // Console logging rules
|
|
556
|
+
'C006': [], // Function naming (SunLint specific)
|
|
557
|
+
'C032': [] // Logging info (SunLint specific)
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
return ruleMapping[sunlintRule] && ruleMapping[sunlintRule].includes(eslintRule);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Run standard SunLint analysis (fallback)
|
|
565
|
+
*/
|
|
566
|
+
async runStandardAnalysis(rulesToRun, options, config) {
|
|
567
|
+
// Check AI configuration and provide feedback
|
|
568
|
+
if (options.ai === true) {
|
|
569
|
+
this.checkAIConfiguration(rulesToRun, config);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Check if user explicitly requests TypeScript engine
|
|
573
|
+
if (options.typescript || options.typescriptEngine === 'eslint') {
|
|
574
|
+
// Force all rules through TypeScript engine when --typescript flag is used
|
|
575
|
+
return await this.runTypeScriptAnalysis(rulesToRun, options, config);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Auto-detect based on rule analyzer
|
|
579
|
+
const eslintRules = rulesToRun.filter(rule =>
|
|
580
|
+
rule.analyzer === 'eslint' ||
|
|
581
|
+
rule.analyzer === 'typescript' // Legacy typescript analyzer routes to eslint
|
|
582
|
+
);
|
|
583
|
+
const customRules = rulesToRun.filter(rule =>
|
|
584
|
+
rule.analyzer &&
|
|
585
|
+
rule.analyzer !== 'eslint' &&
|
|
586
|
+
rule.analyzer !== 'typescript'
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
// Debug logging for rule routing
|
|
590
|
+
if (!options.quiet && options.format !== 'json') {
|
|
591
|
+
console.log(chalk.cyan(`🔍 Rule routing: ${eslintRules.length} ESLint rules, ${customRules.length} custom rules`));
|
|
592
|
+
if (eslintRules.length > 0) {
|
|
593
|
+
console.log(chalk.cyan(` ESLint rules: ${eslintRules.map(r => r.id).join(', ')}`));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Run ESLint rules if any
|
|
598
|
+
let allResults = { results: [], filesAnalyzed: 0, engine: 'combined' };
|
|
599
|
+
|
|
600
|
+
if (eslintRules.length > 0) {
|
|
601
|
+
const eslintResults = await this.runTypeScriptAnalysis(eslintRules, options, config);
|
|
602
|
+
allResults.results.push(...eslintResults.results);
|
|
603
|
+
allResults.filesAnalyzed = Math.max(allResults.filesAnalyzed, eslintResults.filesAnalyzed);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Run custom rules if any
|
|
607
|
+
if (customRules.length > 0) {
|
|
608
|
+
try {
|
|
609
|
+
if (!this.multiRuleRunner) {
|
|
610
|
+
const MultiRuleRunner = require('./multi-rule-runner');
|
|
611
|
+
if (MultiRuleRunner) {
|
|
612
|
+
this.multiRuleRunner = new MultiRuleRunner();
|
|
613
|
+
} else {
|
|
614
|
+
console.log(chalk.yellow('⚠️ Multi-rule runner not available, using basic analysis'));
|
|
615
|
+
return this.runBasicAnalysis(rulesToRun, options, config);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Run analysis using existing multi-rule runner
|
|
620
|
+
const customResults = await this.multiRuleRunner.runRules(customRules, options.input, options);
|
|
621
|
+
allResults.results.push(...customResults.results);
|
|
622
|
+
allResults.filesAnalyzed = Math.max(allResults.filesAnalyzed, customResults.filesAnalyzed);
|
|
623
|
+
} catch (error) {
|
|
624
|
+
console.log(chalk.yellow('⚠️ Custom rule analysis failed, continuing with ESLint results only'));
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return allResults.results.length > 0 ? allResults : this.runBasicAnalysis(rulesToRun, options, config);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Rule C006: Name method with verb-noun - createMockSummary
|
|
633
|
+
*/
|
|
634
|
+
createMockSummary() {
|
|
635
|
+
return {
|
|
636
|
+
totalViolations: 1,
|
|
637
|
+
filesAnalyzed: 1,
|
|
638
|
+
rulesSummary: {
|
|
639
|
+
'C006': 1
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Convert merged results back to ESLint-compatible format for output
|
|
646
|
+
*/
|
|
647
|
+
convertMergedResultsToESLintFormat(mergedResults) {
|
|
648
|
+
const fileMap = new Map();
|
|
649
|
+
|
|
650
|
+
// Group violations by file
|
|
651
|
+
if (mergedResults.violations) {
|
|
652
|
+
mergedResults.violations.forEach(violation => {
|
|
653
|
+
if (!fileMap.has(violation.file)) {
|
|
654
|
+
fileMap.set(violation.file, {
|
|
655
|
+
filePath: violation.file,
|
|
656
|
+
messages: [],
|
|
657
|
+
suppressedMessages: [],
|
|
658
|
+
errorCount: 0,
|
|
659
|
+
warningCount: 0,
|
|
660
|
+
fatalErrorCount: 0,
|
|
661
|
+
fixableErrorCount: 0,
|
|
662
|
+
fixableWarningCount: 0
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const fileResult = fileMap.get(violation.file);
|
|
667
|
+
const message = {
|
|
668
|
+
ruleId: violation.ruleId,
|
|
669
|
+
severity: violation.severity === 'error' ? 2 : 1,
|
|
670
|
+
message: violation.message,
|
|
671
|
+
line: violation.line || 1,
|
|
672
|
+
column: violation.column || 1,
|
|
673
|
+
nodeType: null,
|
|
674
|
+
messageId: null,
|
|
675
|
+
endLine: null,
|
|
676
|
+
endColumn: null,
|
|
677
|
+
source: violation.source || 'unknown'
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// Add additional info for conflicts
|
|
681
|
+
if (violation.additionalInfo) {
|
|
682
|
+
message.message += ` (ESLint: ${violation.additionalInfo.eslintRule})`;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
fileResult.messages.push(message);
|
|
686
|
+
|
|
687
|
+
// Update counts
|
|
688
|
+
if (violation.severity === 'error') {
|
|
689
|
+
fileResult.errorCount++;
|
|
690
|
+
} else {
|
|
691
|
+
fileResult.warningCount++;
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return {
|
|
697
|
+
...mergedResults,
|
|
698
|
+
results: Array.from(fileMap.values()),
|
|
699
|
+
violationCount: mergedResults.violations ? mergedResults.violations.length : 0,
|
|
700
|
+
totalFiles: fileMap.size
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
module.exports = AnalysisOrchestrator;
|