@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,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Selection Service
|
|
3
|
+
* Following Rule C005: Single responsibility - only handle rule selection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const RuleMappingService = require('./rule-mapping-service');
|
|
8
|
+
|
|
9
|
+
class RuleSelectionService {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.rulesRegistry = null;
|
|
12
|
+
this.ruleMappingService = new RuleMappingService();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async selectRules(config, options) {
|
|
16
|
+
// Load rules registry
|
|
17
|
+
try {
|
|
18
|
+
this.rulesRegistry = require('../config/rules-registry.json');
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.log(chalk.yellow('⚠️ Rules registry not found, using minimal rule set'));
|
|
21
|
+
this.rulesRegistry = this.getMinimalRuleSet();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const allRules = config.rules || {};
|
|
25
|
+
let selectedRules = [];
|
|
26
|
+
|
|
27
|
+
// Determine rule selection strategy
|
|
28
|
+
if (options.rule) {
|
|
29
|
+
selectedRules = [options.rule];
|
|
30
|
+
} else if (options.rules) {
|
|
31
|
+
selectedRules = options.rules.split(',').map(r => r.trim());
|
|
32
|
+
} else if (options.all) {
|
|
33
|
+
// For --all, use all supported rules from both registry and ESLint integration
|
|
34
|
+
const registryRules = Object.keys(this.rulesRegistry.rules || {});
|
|
35
|
+
const eslintSupportedRules = this.ruleMappingService.getSupportedSunLintRules();
|
|
36
|
+
|
|
37
|
+
// Combine and deduplicate
|
|
38
|
+
selectedRules = [...new Set([...registryRules, ...eslintSupportedRules])];
|
|
39
|
+
|
|
40
|
+
if (options.verbose) {
|
|
41
|
+
console.log(chalk.blue(`📋 Found ${selectedRules.length} total rules (${registryRules.length} registry + ${eslintSupportedRules.length} ESLint)`));
|
|
42
|
+
}
|
|
43
|
+
} else if (options.category) {
|
|
44
|
+
const categoryRules = this.getRulesByCategory(options.category);
|
|
45
|
+
selectedRules = categoryRules;
|
|
46
|
+
} else {
|
|
47
|
+
// Default: use config rules or minimal set
|
|
48
|
+
selectedRules = Object.keys(allRules).filter(ruleId =>
|
|
49
|
+
allRules[ruleId] !== 'off' && allRules[ruleId] !== false
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (selectedRules.length === 0) {
|
|
53
|
+
selectedRules = ['C006', 'C019']; // Default minimal set
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Convert to rule objects
|
|
58
|
+
return selectedRules.map(ruleId => ({
|
|
59
|
+
id: ruleId,
|
|
60
|
+
name: this.getRuleName(ruleId),
|
|
61
|
+
severity: 'warning',
|
|
62
|
+
...((this.rulesRegistry.rules && this.rulesRegistry.rules[ruleId]) || {})
|
|
63
|
+
})).filter(rule => rule.id);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getMinimalRuleSet() {
|
|
67
|
+
return {
|
|
68
|
+
rules: {
|
|
69
|
+
'C006': {
|
|
70
|
+
name: 'Function Naming Convention',
|
|
71
|
+
description: 'Function names should follow verb-noun pattern',
|
|
72
|
+
category: 'naming',
|
|
73
|
+
severity: 'warning'
|
|
74
|
+
},
|
|
75
|
+
'C019': {
|
|
76
|
+
name: 'Log Level Usage',
|
|
77
|
+
description: 'Use appropriate log levels',
|
|
78
|
+
category: 'logging',
|
|
79
|
+
severity: 'warning'
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getRulesByCategory(category) {
|
|
86
|
+
// Get rules from registry if available
|
|
87
|
+
if (this.rulesRegistry && this.rulesRegistry.categories && this.rulesRegistry.categories[category]) {
|
|
88
|
+
return this.rulesRegistry.categories[category].rules;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Fallback to hardcoded mapping
|
|
92
|
+
const categoryMap = {
|
|
93
|
+
'quality': [
|
|
94
|
+
'C002', 'C003', 'C006', 'C010', 'C013', 'C014', 'C017', 'C018', 'C019',
|
|
95
|
+
'C023', 'C027', 'C029', 'C030', 'C031', 'C034', 'C035', 'C041', 'C042',
|
|
96
|
+
'C043', 'C047', 'C048', 'C076', 'T002', 'T003', 'T004', 'T007', 'T011',
|
|
97
|
+
'T019', 'T025', 'T026'
|
|
98
|
+
],
|
|
99
|
+
'security': ['S003', 'S005', 'S006', 'S008', 'S009', 'S010', 'S011', 'S012', 'S014', 'S015', 'S016', 'S017', 'S018', 'S019', 'S020', 'S022', 'S023', 'S025', 'S026', 'S027', 'S029', 'S030', 'S033', 'S034', 'S035', 'S036', 'S037', 'S038', 'S039', 'S041', 'S042', 'S043', 'S044', 'S045', 'S046', 'S047', 'S048', 'S050', 'S052', 'S054', 'S055', 'S057', 'S058'],
|
|
100
|
+
'naming': ['C006'],
|
|
101
|
+
'logging': ['C019', 'S057'],
|
|
102
|
+
'validation': ['C031', 'S018', 'S025', 'S026']
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return categoryMap[category] || [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getRuleName(ruleId) {
|
|
109
|
+
const ruleNames = {
|
|
110
|
+
'C006': 'Function Naming Convention',
|
|
111
|
+
'C019': 'Log Level Usage',
|
|
112
|
+
'C002': 'No Duplicate Code',
|
|
113
|
+
'C003': 'No Vague Abbreviations',
|
|
114
|
+
'C029': 'Catch Block Logging'
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return ruleNames[ruleId] || `Rule ${ruleId}`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = RuleSelectionService;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SunLint Engine Service
|
|
3
|
+
* Handles native SunLint analysis
|
|
4
|
+
* Following Rule C005: Single responsibility - only handle SunLint execution
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const MultiRuleRunner = require('./multi-rule-runner');
|
|
8
|
+
|
|
9
|
+
class SunLintEngineService {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.multiRuleRunner = new MultiRuleRunner();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Command: Run SunLint analysis
|
|
16
|
+
*/
|
|
17
|
+
async runAnalysis(rulesToRun, options) {
|
|
18
|
+
// Use existing multi-rule runner
|
|
19
|
+
return await this.multiRuleRunner.runRules(rulesToRun, options.input, options);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = SunLintEngineService;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handles TypeScript file analysis using ESLint
|
|
7
|
+
* Rule C005: Single responsibility - only file analysis
|
|
8
|
+
* Rule C015: Domain language - TypescriptAnalyzer
|
|
9
|
+
* Rule C033: Separates analysis logic from data queries
|
|
10
|
+
*/
|
|
11
|
+
class TypescriptAnalyzer {
|
|
12
|
+
constructor(eslintInstanceManager) {
|
|
13
|
+
// Rule C014: Dependency injection
|
|
14
|
+
this.eslintInstanceManager = eslintInstanceManager;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Rule C006: analyzeTypeScriptFiles - verb-noun naming
|
|
19
|
+
* Rule C012: Command method - performs analysis
|
|
20
|
+
* Rule C033: Analysis logic separated from data queries
|
|
21
|
+
*/
|
|
22
|
+
async analyzeTypeScriptFiles(filePaths, options = {}) {
|
|
23
|
+
// Expand directories to actual files
|
|
24
|
+
const expandedFiles = this.expandDirectories(filePaths);
|
|
25
|
+
|
|
26
|
+
if (expandedFiles.length === 0) {
|
|
27
|
+
console.warn(chalk.yellow('⚠️ No TypeScript/JavaScript files found'));
|
|
28
|
+
return this.formatAnalysisResults([], options);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (options.verbose) {
|
|
32
|
+
console.log(chalk.blue(`📁 Found ${expandedFiles.length} files to analyze`));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const eslint = this.eslintInstanceManager.getEslintInstance();
|
|
36
|
+
const results = [];
|
|
37
|
+
|
|
38
|
+
for (const filePath of expandedFiles) {
|
|
39
|
+
try {
|
|
40
|
+
const analysisResult = await this.analyzeFile(eslint, filePath, options);
|
|
41
|
+
if (analysisResult) {
|
|
42
|
+
results.push(analysisResult);
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(chalk.red(`❌ Error analyzing ${filePath}:`), error.message);
|
|
46
|
+
|
|
47
|
+
// Add error result
|
|
48
|
+
results.push({
|
|
49
|
+
filePath: filePath,
|
|
50
|
+
messages: [{
|
|
51
|
+
ruleId: null,
|
|
52
|
+
severity: 2,
|
|
53
|
+
message: `Analysis failed: ${error.message}`,
|
|
54
|
+
line: 1,
|
|
55
|
+
column: 1
|
|
56
|
+
}],
|
|
57
|
+
errorCount: 1,
|
|
58
|
+
warningCount: 0,
|
|
59
|
+
fixableErrorCount: 0,
|
|
60
|
+
fixableWarningCount: 0
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return this.formatAnalysisResults(results, options);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Rule C006: analyzeFile - verb-noun naming
|
|
70
|
+
* Rule C005: Single responsibility - analyze one file
|
|
71
|
+
*/
|
|
72
|
+
async analyzeFile(eslint, filePath, options) {
|
|
73
|
+
if (!this.validateFilePath(filePath)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (options.verbose) {
|
|
78
|
+
console.log(chalk.gray(`🔍 Analyzing: ${filePath}`));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const results = await eslint.lintFiles([filePath]);
|
|
83
|
+
const result = results[0]; // ESLint returns array, we need first result
|
|
84
|
+
|
|
85
|
+
// Ensure filePath is preserved in result
|
|
86
|
+
if (result && !result.filePath) {
|
|
87
|
+
result.filePath = filePath;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw new Error(`ESLint analysis failed for ${filePath}: ${error.message}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Rule C006: validateFilePath - verb-noun naming
|
|
98
|
+
* Rule C031: Validation logic separated
|
|
99
|
+
*/
|
|
100
|
+
validateFilePath(filePath) {
|
|
101
|
+
if (!fs.existsSync(filePath)) {
|
|
102
|
+
console.warn(chalk.yellow(`⚠️ File not found: ${filePath}`));
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check if it's a directory
|
|
107
|
+
const stats = fs.statSync(filePath);
|
|
108
|
+
if (stats.isDirectory()) {
|
|
109
|
+
return true; // Directories are valid, will be expanded later
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Check file extension for files
|
|
113
|
+
const ext = path.extname(filePath);
|
|
114
|
+
const supportedExtensions = ['.ts', '.tsx', '.js', '.jsx'];
|
|
115
|
+
|
|
116
|
+
if (!supportedExtensions.includes(ext)) {
|
|
117
|
+
console.warn(chalk.yellow(`⚠️ Unsupported file type: ${filePath}`));
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Rule C006: formatAnalysisResults - verb-noun naming
|
|
126
|
+
* Rule C005: Single responsibility - format results
|
|
127
|
+
*/
|
|
128
|
+
formatAnalysisResults(results, options) {
|
|
129
|
+
const totalFiles = results.length;
|
|
130
|
+
const totalErrors = results.reduce((sum, result) => sum + (result.errorCount || 0), 0);
|
|
131
|
+
const totalWarnings = results.reduce((sum, result) => sum + (result.warningCount || 0), 0);
|
|
132
|
+
|
|
133
|
+
const summary = {
|
|
134
|
+
totalFiles,
|
|
135
|
+
totalErrors,
|
|
136
|
+
totalWarnings,
|
|
137
|
+
results: results.filter(result => result.messages && result.messages.length > 0)
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (options.verbose) {
|
|
141
|
+
console.log(chalk.blue(`📊 Analysis Summary: ${totalFiles} files, ${totalErrors} errors, ${totalWarnings} warnings`));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return summary;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Rule C006: extractProblemsFromResults - verb-noun naming
|
|
149
|
+
* Rule C012: Query method - extracts data without side effects
|
|
150
|
+
*/
|
|
151
|
+
extractProblemsFromResults(results) {
|
|
152
|
+
const problems = [];
|
|
153
|
+
|
|
154
|
+
for (const result of results.results || []) {
|
|
155
|
+
for (const message of result.messages || []) {
|
|
156
|
+
problems.push({
|
|
157
|
+
filePath: result.filePath,
|
|
158
|
+
ruleId: message.ruleId,
|
|
159
|
+
severity: message.severity,
|
|
160
|
+
message: message.message,
|
|
161
|
+
line: message.line,
|
|
162
|
+
column: message.column,
|
|
163
|
+
endLine: message.endLine,
|
|
164
|
+
endColumn: message.endColumn
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return problems;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Rule C006: filterResultsByRules - verb-noun naming
|
|
174
|
+
* Rule C012: Query method - filters without modifying original
|
|
175
|
+
*/
|
|
176
|
+
filterResultsByRules(results, ruleIds) {
|
|
177
|
+
if (!ruleIds || ruleIds.length === 0) {
|
|
178
|
+
return results;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const filteredResults = {
|
|
182
|
+
...results,
|
|
183
|
+
results: results.results.map(result => ({
|
|
184
|
+
...result,
|
|
185
|
+
messages: result.messages.filter(message =>
|
|
186
|
+
ruleIds.includes(message.ruleId)
|
|
187
|
+
)
|
|
188
|
+
})).filter(result => result.messages.length > 0)
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Recalculate counts
|
|
192
|
+
filteredResults.totalErrors = filteredResults.results.reduce(
|
|
193
|
+
(sum, result) => sum + result.messages.filter(m => m.severity === 2).length,
|
|
194
|
+
0
|
|
195
|
+
);
|
|
196
|
+
filteredResults.totalWarnings = filteredResults.results.reduce(
|
|
197
|
+
(sum, result) => sum + result.messages.filter(m => m.severity === 1).length,
|
|
198
|
+
0
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return filteredResults;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Rule C006: expandDirectories - verb-noun naming
|
|
206
|
+
* Rule C005: Single responsibility - expand directories to files
|
|
207
|
+
*/
|
|
208
|
+
expandDirectories(filePaths) {
|
|
209
|
+
const expandedFiles = [];
|
|
210
|
+
const supportedExtensions = ['.ts', '.tsx', '.js', '.jsx'];
|
|
211
|
+
|
|
212
|
+
for (const filePath of filePaths) {
|
|
213
|
+
if (!fs.existsSync(filePath)) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const stats = fs.statSync(filePath);
|
|
218
|
+
if (stats.isDirectory()) {
|
|
219
|
+
// Recursively find TypeScript/JavaScript files
|
|
220
|
+
const files = this.findFilesRecursively(filePath, supportedExtensions);
|
|
221
|
+
expandedFiles.push(...files);
|
|
222
|
+
} else if (stats.isFile() && supportedExtensions.includes(path.extname(filePath))) {
|
|
223
|
+
expandedFiles.push(filePath);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return expandedFiles;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Rule C006: findFilesRecursively - verb-noun naming
|
|
232
|
+
* Rule C005: Single responsibility - recursive file finding
|
|
233
|
+
*/
|
|
234
|
+
findFilesRecursively(dir, extensions) {
|
|
235
|
+
const files = [];
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
239
|
+
|
|
240
|
+
for (const entry of entries) {
|
|
241
|
+
const fullPath = path.join(dir, entry.name);
|
|
242
|
+
|
|
243
|
+
// Skip node_modules and other common ignore patterns
|
|
244
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (entry.isDirectory()) {
|
|
249
|
+
files.push(...this.findFilesRecursively(fullPath, extensions));
|
|
250
|
+
} else if (entry.isFile() && extensions.includes(path.extname(entry.name))) {
|
|
251
|
+
files.push(fullPath);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.warn(chalk.yellow(`⚠️ Cannot read directory: ${dir}`));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return files;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = TypescriptAnalyzer;
|