@defai.digital/ax-cli 3.4.5 → 3.5.2
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/LICENSE +2 -6
- package/README.md +91 -3
- package/dist/analyzers/ast/parser.d.ts +59 -0
- package/dist/analyzers/ast/parser.js +293 -0
- package/dist/analyzers/ast/parser.js.map +1 -0
- package/dist/analyzers/ast/types.d.ts +107 -0
- package/dist/analyzers/ast/types.js +7 -0
- package/dist/analyzers/ast/types.js.map +1 -0
- package/dist/analyzers/code-smells/base-smell-detector.d.ts +30 -0
- package/dist/analyzers/code-smells/base-smell-detector.js +44 -0
- package/dist/analyzers/code-smells/base-smell-detector.js.map +1 -0
- package/dist/analyzers/code-smells/code-smell-analyzer.d.ts +30 -0
- package/dist/analyzers/code-smells/code-smell-analyzer.js +167 -0
- package/dist/analyzers/code-smells/code-smell-analyzer.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/data-clumps-detector.d.ts +11 -0
- package/dist/analyzers/code-smells/detectors/data-clumps-detector.js +66 -0
- package/dist/analyzers/code-smells/detectors/data-clumps-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/dead-code-detector.d.ts +11 -0
- package/dist/analyzers/code-smells/detectors/dead-code-detector.js +53 -0
- package/dist/analyzers/code-smells/detectors/dead-code-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.d.ts +11 -0
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js +51 -0
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/feature-envy-detector.d.ts +11 -0
- package/dist/analyzers/code-smells/detectors/feature-envy-detector.js +64 -0
- package/dist/analyzers/code-smells/detectors/feature-envy-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.d.ts +11 -0
- package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.js +56 -0
- package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/large-class-detector.d.ts +13 -0
- package/dist/analyzers/code-smells/detectors/large-class-detector.js +58 -0
- package/dist/analyzers/code-smells/detectors/large-class-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/long-method-detector.d.ts +12 -0
- package/dist/analyzers/code-smells/detectors/long-method-detector.js +52 -0
- package/dist/analyzers/code-smells/detectors/long-method-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.d.ts +12 -0
- package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.js +50 -0
- package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/magic-numbers-detector.d.ts +12 -0
- package/dist/analyzers/code-smells/detectors/magic-numbers-detector.js +54 -0
- package/dist/analyzers/code-smells/detectors/magic-numbers-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.d.ts +13 -0
- package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.js +71 -0
- package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.js.map +1 -0
- package/dist/analyzers/code-smells/index.d.ts +16 -0
- package/dist/analyzers/code-smells/index.js +19 -0
- package/dist/analyzers/code-smells/index.js.map +1 -0
- package/dist/analyzers/code-smells/types.d.ts +82 -0
- package/dist/analyzers/code-smells/types.js +30 -0
- package/dist/analyzers/code-smells/types.js.map +1 -0
- package/dist/analyzers/dependency/circular-detector.d.ts +17 -0
- package/dist/analyzers/dependency/circular-detector.js +71 -0
- package/dist/analyzers/dependency/circular-detector.js.map +1 -0
- package/dist/analyzers/dependency/coupling-calculator.d.ts +24 -0
- package/dist/analyzers/dependency/coupling-calculator.js +86 -0
- package/dist/analyzers/dependency/coupling-calculator.js.map +1 -0
- package/dist/analyzers/dependency/dependency-analyzer.d.ts +40 -0
- package/dist/analyzers/dependency/dependency-analyzer.js +214 -0
- package/dist/analyzers/dependency/dependency-analyzer.js.map +1 -0
- package/dist/analyzers/dependency/dependency-graph.d.ts +57 -0
- package/dist/analyzers/dependency/dependency-graph.js +186 -0
- package/dist/analyzers/dependency/dependency-graph.js.map +1 -0
- package/dist/analyzers/dependency/index.d.ts +8 -0
- package/dist/analyzers/dependency/index.js +8 -0
- package/dist/analyzers/dependency/index.js.map +1 -0
- package/dist/analyzers/dependency/types.d.ts +105 -0
- package/dist/analyzers/dependency/types.js +5 -0
- package/dist/analyzers/dependency/types.js.map +1 -0
- package/dist/analyzers/git/churn-calculator.d.ts +34 -0
- package/dist/analyzers/git/churn-calculator.js +214 -0
- package/dist/analyzers/git/churn-calculator.js.map +1 -0
- package/dist/analyzers/git/git-analyzer.d.ts +19 -0
- package/dist/analyzers/git/git-analyzer.js +71 -0
- package/dist/analyzers/git/git-analyzer.js.map +1 -0
- package/dist/analyzers/git/hotspot-detector.d.ts +34 -0
- package/dist/analyzers/git/hotspot-detector.js +170 -0
- package/dist/analyzers/git/hotspot-detector.js.map +1 -0
- package/dist/analyzers/git/index.d.ts +7 -0
- package/dist/analyzers/git/index.js +7 -0
- package/dist/analyzers/git/index.js.map +1 -0
- package/dist/analyzers/git/types.d.ts +88 -0
- package/dist/analyzers/git/types.js +5 -0
- package/dist/analyzers/git/types.js.map +1 -0
- package/dist/analyzers/metrics/halstead-calculator.d.ts +30 -0
- package/dist/analyzers/metrics/halstead-calculator.js +150 -0
- package/dist/analyzers/metrics/halstead-calculator.js.map +1 -0
- package/dist/analyzers/metrics/index.d.ts +9 -0
- package/dist/analyzers/metrics/index.js +9 -0
- package/dist/analyzers/metrics/index.js.map +1 -0
- package/dist/analyzers/metrics/maintainability-calculator.d.ts +17 -0
- package/dist/analyzers/metrics/maintainability-calculator.js +46 -0
- package/dist/analyzers/metrics/maintainability-calculator.js.map +1 -0
- package/dist/analyzers/metrics/metrics-analyzer.d.ts +32 -0
- package/dist/analyzers/metrics/metrics-analyzer.js +140 -0
- package/dist/analyzers/metrics/metrics-analyzer.js.map +1 -0
- package/dist/analyzers/metrics/types.d.ts +67 -0
- package/dist/analyzers/metrics/types.js +5 -0
- package/dist/analyzers/metrics/types.js.map +1 -0
- package/dist/analyzers/security/base-detector.d.ts +58 -0
- package/dist/analyzers/security/base-detector.js +104 -0
- package/dist/analyzers/security/base-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/command-injection-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/command-injection-detector.js +84 -0
- package/dist/analyzers/security/detectors/command-injection-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/hardcoded-secrets-detector.d.ts +16 -0
- package/dist/analyzers/security/detectors/hardcoded-secrets-detector.js +140 -0
- package/dist/analyzers/security/detectors/hardcoded-secrets-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/insecure-deserialization-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/insecure-deserialization-detector.js +109 -0
- package/dist/analyzers/security/detectors/insecure-deserialization-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/insecure-random-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/insecure-random-detector.js +61 -0
- package/dist/analyzers/security/detectors/insecure-random-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/path-traversal-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/path-traversal-detector.js +82 -0
- package/dist/analyzers/security/detectors/path-traversal-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/sql-injection-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/sql-injection-detector.js +88 -0
- package/dist/analyzers/security/detectors/sql-injection-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/weak-crypto-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/weak-crypto-detector.js +104 -0
- package/dist/analyzers/security/detectors/weak-crypto-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/xss-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/xss-detector.js +90 -0
- package/dist/analyzers/security/detectors/xss-detector.js.map +1 -0
- package/dist/analyzers/security/index.d.ts +16 -0
- package/dist/analyzers/security/index.js +18 -0
- package/dist/analyzers/security/index.js.map +1 -0
- package/dist/analyzers/security/security-analyzer.d.ts +38 -0
- package/dist/analyzers/security/security-analyzer.js +215 -0
- package/dist/analyzers/security/security-analyzer.js.map +1 -0
- package/dist/analyzers/security/types.d.ts +95 -0
- package/dist/analyzers/security/types.js +7 -0
- package/dist/analyzers/security/types.js.map +1 -0
- package/dist/commands/memory.js +1 -1
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/setup.js +6 -1
- package/dist/commands/setup.js.map +1 -1
- package/dist/hooks/use-enhanced-input.d.ts +0 -1
- package/dist/hooks/use-enhanced-input.js.map +1 -1
- package/dist/mcp/health.js +4 -2
- package/dist/mcp/health.js.map +1 -1
- package/dist/mcp/validation.js +12 -6
- package/dist/mcp/validation.js.map +1 -1
- package/dist/tools/analysis-tools.d.ts +73 -0
- package/dist/tools/analysis-tools.js +422 -0
- package/dist/tools/analysis-tools.js.map +1 -0
- package/dist/tools/bash.js +2 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/ui/components/chat-history.js +1 -1
- package/dist/ui/components/chat-history.js.map +1 -1
- package/dist/ui/components/chat-interface.js +3 -2
- package/dist/ui/components/chat-interface.js.map +1 -1
- package/dist/ui/components/confirmation-dialog.js +1 -1
- package/dist/ui/components/confirmation-dialog.js.map +1 -1
- package/dist/ui/components/welcome-panel.js +1 -1
- package/dist/ui/components/welcome-panel.js.map +1 -1
- package/dist/ui/hooks/use-chat-reducer.d.ts +61 -0
- package/dist/ui/hooks/use-chat-reducer.js +118 -0
- package/dist/ui/hooks/use-chat-reducer.js.map +1 -0
- package/dist/ui/hooks/use-enhanced-input.d.ts +40 -0
- package/dist/ui/hooks/use-enhanced-input.js +254 -0
- package/dist/ui/hooks/use-enhanced-input.js.map +1 -0
- package/dist/ui/hooks/use-input-handler.d.ts +46 -0
- package/dist/ui/hooks/use-input-handler.js +1434 -0
- package/dist/ui/hooks/use-input-handler.js.map +1 -0
- package/dist/ui/hooks/use-input-history.d.ts +9 -0
- package/dist/ui/hooks/use-input-history.js +117 -0
- package/dist/ui/hooks/use-input-history.js.map +1 -0
- package/dist/utils/config-loader.js +3 -3
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/parallel-analyzer.js +7 -11
- package/dist/utils/parallel-analyzer.js.map +1 -1
- package/dist/utils/paste-collapse.d.ts +46 -0
- package/dist/utils/paste-collapse.js +77 -0
- package/dist/utils/paste-collapse.js.map +1 -0
- package/dist/utils/settings-manager.js +16 -2
- package/dist/utils/settings-manager.js.map +1 -1
- package/dist/utils/streaming-analyzer.js +9 -21
- package/dist/utils/streaming-analyzer.js.map +1 -1
- package/package.json +5 -5
- package/vitest.config.ts +1 -0
- package/dist/commands/weather.d.ts +0 -8
- package/dist/commands/weather.js +0 -160
- package/dist/commands/weather.js.map +0 -1
- package/dist/grok/client.d.ts +0 -144
- package/dist/grok/client.js +0 -237
- package/dist/grok/client.js.map +0 -1
- package/dist/grok/tools.d.ts +0 -8
- package/dist/grok/tools.js +0 -318
- package/dist/grok/tools.js.map +0 -1
- package/dist/grok/types.d.ts +0 -291
- package/dist/grok/types.js +0 -127
- package/dist/grok/types.js.map +0 -1
- package/dist/tools/morph-editor.d.ts +0 -36
- package/dist/tools/morph-editor.js +0 -308
- package/dist/tools/morph-editor.js.map +0 -1
- package/dist/ui/components/session-recovery.d.ts +0 -12
- package/dist/ui/components/session-recovery.js +0 -93
- package/dist/ui/components/session-recovery.js.map +0 -1
- package/dist/utils/model-config.d.ts +0 -28
- package/dist/utils/model-config.js +0 -43
- package/dist/utils/model-config.js.map +0 -1
- package/dist/utils/tool-helpers.d.ts +0 -25
- package/dist/utils/tool-helpers.js +0 -79
- package/dist/utils/tool-helpers.js.map +0 -1
- /package/{automatosx.config.json → ax.config.json} +0 -0
- /package/{config → config-defaults}/messages.yaml +0 -0
- /package/{config → config-defaults}/models.yaml +0 -0
- /package/{config → config-defaults}/prompts.yaml +0 -0
- /package/{config → config-defaults}/settings.yaml +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Smell Detector
|
|
3
|
+
*
|
|
4
|
+
* Abstract base class for all smell detectors
|
|
5
|
+
*/
|
|
6
|
+
import { ASTParser } from '../ast/parser.js';
|
|
7
|
+
export class BaseSmellDetector {
|
|
8
|
+
type;
|
|
9
|
+
config;
|
|
10
|
+
astParser;
|
|
11
|
+
constructor(type, config) {
|
|
12
|
+
this.type = type;
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.astParser = new ASTParser();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check if detector is enabled
|
|
18
|
+
*/
|
|
19
|
+
isEnabled() {
|
|
20
|
+
return this.config.enabled;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get threshold value
|
|
24
|
+
*/
|
|
25
|
+
getThreshold(key, defaultValue) {
|
|
26
|
+
return this.config.thresholds?.[key] ?? defaultValue;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create a code smell object
|
|
30
|
+
*/
|
|
31
|
+
createSmell(filePath, startLine, endLine, message, suggestion, severity, metadata = {}) {
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
type: this.type,
|
|
34
|
+
severity,
|
|
35
|
+
filePath,
|
|
36
|
+
startLine,
|
|
37
|
+
endLine,
|
|
38
|
+
message,
|
|
39
|
+
suggestion,
|
|
40
|
+
metadata: Object.freeze(metadata),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=base-smell-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-smell-detector.js","sourceRoot":"","sources":["../../../src/analyzers/code-smells/base-smell-detector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C,MAAM,OAAgB,iBAAiB;IAInB;IACA;IAJR,SAAS,CAAY;IAE/B,YACkB,IAAe,EACf,MAAsB;QADtB,SAAI,GAAJ,IAAI,CAAW;QACf,WAAM,GAAN,MAAM,CAAgB;QAEtC,IAAI,CAAC,SAAS,GAAG,IAAI,SAAS,EAAE,CAAC;IACnC,CAAC;IAOD;;OAEG;IACO,SAAS;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;IAC7B,CAAC;IAED;;OAEG;IACO,YAAY,CAAC,GAAW,EAAE,YAAoB;QACtD,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC;IACvD,CAAC;IAED;;OAEG;IACO,WAAW,CACnB,QAAgB,EAChB,SAAiB,EACjB,OAAe,EACf,OAAe,EACf,UAAkB,EAClB,QAAuB,EACvB,WAAoC,EAAE;QAEtC,OAAO,MAAM,CAAC,MAAM,CAAC;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ;YACR,QAAQ;YACR,SAAS;YACT,OAAO;YACP,OAAO;YACP,UAAU;YACV,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Smell Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Main orchestrator for code smell detection
|
|
5
|
+
*/
|
|
6
|
+
import { type CodeSmell, type CodeSmellAnalysisResult, type CodeSmellAnalysisOptions } from './types.js';
|
|
7
|
+
export declare class CodeSmellAnalyzer {
|
|
8
|
+
private detectors;
|
|
9
|
+
constructor(options?: CodeSmellAnalysisOptions);
|
|
10
|
+
/**
|
|
11
|
+
* Initialize all detectors with configuration
|
|
12
|
+
*/
|
|
13
|
+
private initializeDetectors;
|
|
14
|
+
/**
|
|
15
|
+
* Analyze directory for code smells
|
|
16
|
+
*/
|
|
17
|
+
analyzeDirectory(directory: string, pattern?: string, options?: CodeSmellAnalysisOptions): Promise<CodeSmellAnalysisResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Analyze single file
|
|
20
|
+
*/
|
|
21
|
+
analyzeFile(filePath: string): Promise<CodeSmell[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Calculate summary statistics
|
|
24
|
+
*/
|
|
25
|
+
private calculateSummary;
|
|
26
|
+
/**
|
|
27
|
+
* Calculate code health score (0-100)
|
|
28
|
+
*/
|
|
29
|
+
private calculateHealthScore;
|
|
30
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Smell Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Main orchestrator for code smell detection
|
|
5
|
+
*/
|
|
6
|
+
import { glob } from 'glob';
|
|
7
|
+
import { SmellType, SmellSeverity } from './types.js';
|
|
8
|
+
import { LongMethodDetector } from './detectors/long-method-detector.js';
|
|
9
|
+
import { LargeClassDetector } from './detectors/large-class-detector.js';
|
|
10
|
+
import { LongParameterListDetector } from './detectors/long-parameter-list-detector.js';
|
|
11
|
+
import { MagicNumbersDetector } from './detectors/magic-numbers-detector.js';
|
|
12
|
+
import { NestedConditionalsDetector } from './detectors/nested-conditionals-detector.js';
|
|
13
|
+
import { DeadCodeDetector } from './detectors/dead-code-detector.js';
|
|
14
|
+
import { DuplicateCodeDetector } from './detectors/duplicate-code-detector.js';
|
|
15
|
+
import { FeatureEnvyDetector } from './detectors/feature-envy-detector.js';
|
|
16
|
+
import { DataClumpsDetector } from './detectors/data-clumps-detector.js';
|
|
17
|
+
import { InappropriateIntimacyDetector } from './detectors/inappropriate-intimacy-detector.js';
|
|
18
|
+
export class CodeSmellAnalyzer {
|
|
19
|
+
detectors;
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.detectors = new Map();
|
|
22
|
+
this.initializeDetectors(options);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Initialize all detectors with configuration
|
|
26
|
+
*/
|
|
27
|
+
initializeDetectors(options) {
|
|
28
|
+
const defaultConfig = { enabled: true };
|
|
29
|
+
const detectorClasses = [
|
|
30
|
+
LongMethodDetector,
|
|
31
|
+
LargeClassDetector,
|
|
32
|
+
LongParameterListDetector,
|
|
33
|
+
MagicNumbersDetector,
|
|
34
|
+
NestedConditionalsDetector,
|
|
35
|
+
DeadCodeDetector,
|
|
36
|
+
DuplicateCodeDetector,
|
|
37
|
+
FeatureEnvyDetector,
|
|
38
|
+
DataClumpsDetector,
|
|
39
|
+
InappropriateIntimacyDetector,
|
|
40
|
+
];
|
|
41
|
+
for (const DetectorClass of detectorClasses) {
|
|
42
|
+
const detector = new DetectorClass(defaultConfig);
|
|
43
|
+
const config = options.detectorConfigs?.[detector.type] ?? defaultConfig;
|
|
44
|
+
const configuredDetector = new DetectorClass(config);
|
|
45
|
+
this.detectors.set(configuredDetector.type, configuredDetector);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Analyze directory for code smells
|
|
50
|
+
*/
|
|
51
|
+
async analyzeDirectory(directory, pattern = '**/*.{ts,tsx,js,jsx}', options = {}) {
|
|
52
|
+
const timestamp = new Date();
|
|
53
|
+
// Find all files
|
|
54
|
+
const ignorePatterns = [
|
|
55
|
+
'**/node_modules/**',
|
|
56
|
+
'**/dist/**',
|
|
57
|
+
'**/build/**',
|
|
58
|
+
'**/.git/**',
|
|
59
|
+
'**/*.test.{ts,tsx,js,jsx}',
|
|
60
|
+
'**/*.spec.{ts,tsx,js,jsx}',
|
|
61
|
+
...(options.ignorePatterns || []),
|
|
62
|
+
];
|
|
63
|
+
const files = await glob(pattern, {
|
|
64
|
+
cwd: directory,
|
|
65
|
+
absolute: true,
|
|
66
|
+
nodir: true,
|
|
67
|
+
ignore: ignorePatterns,
|
|
68
|
+
});
|
|
69
|
+
// Analyze all files
|
|
70
|
+
const allSmells = [];
|
|
71
|
+
const filesWithSmells = new Set();
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
for (const detector of this.detectors.values()) {
|
|
74
|
+
try {
|
|
75
|
+
const smells = await detector.detect(file);
|
|
76
|
+
if (smells.length > 0) {
|
|
77
|
+
allSmells.push(...smells);
|
|
78
|
+
filesWithSmells.add(file);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
// Skip detector errors
|
|
83
|
+
console.error(`Error running ${detector.type} on ${file}:`, error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Calculate summary
|
|
88
|
+
const summary = this.calculateSummary(allSmells, files.length, filesWithSmells.size);
|
|
89
|
+
return Object.freeze({
|
|
90
|
+
smells: Object.freeze(allSmells),
|
|
91
|
+
summary: Object.freeze(summary),
|
|
92
|
+
timestamp,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Analyze single file
|
|
97
|
+
*/
|
|
98
|
+
async analyzeFile(filePath) {
|
|
99
|
+
const smells = [];
|
|
100
|
+
for (const detector of this.detectors.values()) {
|
|
101
|
+
try {
|
|
102
|
+
const detectedSmells = await detector.detect(filePath);
|
|
103
|
+
smells.push(...detectedSmells);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error(`Error running ${detector.type} on ${filePath}:`, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return smells;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Calculate summary statistics
|
|
113
|
+
*/
|
|
114
|
+
calculateSummary(smells, totalFiles, filesWithSmells) {
|
|
115
|
+
// Count by type
|
|
116
|
+
const smellsByType = {};
|
|
117
|
+
for (const type of Object.values(SmellType)) {
|
|
118
|
+
smellsByType[type] = 0;
|
|
119
|
+
}
|
|
120
|
+
for (const smell of smells) {
|
|
121
|
+
smellsByType[smell.type]++;
|
|
122
|
+
}
|
|
123
|
+
// Count by severity
|
|
124
|
+
const smellsBySeverity = {};
|
|
125
|
+
for (const severity of Object.values(SmellSeverity)) {
|
|
126
|
+
smellsBySeverity[severity] = 0;
|
|
127
|
+
}
|
|
128
|
+
for (const smell of smells) {
|
|
129
|
+
smellsBySeverity[smell.severity]++;
|
|
130
|
+
}
|
|
131
|
+
// Calculate health score
|
|
132
|
+
const codeHealthScore = this.calculateHealthScore(smells, totalFiles);
|
|
133
|
+
return {
|
|
134
|
+
totalSmells: smells.length,
|
|
135
|
+
smellsByType: Object.freeze(smellsByType),
|
|
136
|
+
smellsBySeverity: Object.freeze(smellsBySeverity),
|
|
137
|
+
filesAnalyzed: totalFiles,
|
|
138
|
+
filesWithSmells,
|
|
139
|
+
averageSmellsPerFile: totalFiles > 0 ? smells.length / totalFiles : 0,
|
|
140
|
+
codeHealthScore,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Calculate code health score (0-100)
|
|
145
|
+
*/
|
|
146
|
+
calculateHealthScore(smells, totalFiles) {
|
|
147
|
+
if (totalFiles === 0)
|
|
148
|
+
return 100;
|
|
149
|
+
// Weighted penalties by severity
|
|
150
|
+
const severityWeights = {
|
|
151
|
+
[SmellSeverity.LOW]: 1,
|
|
152
|
+
[SmellSeverity.MEDIUM]: 3,
|
|
153
|
+
[SmellSeverity.HIGH]: 7,
|
|
154
|
+
[SmellSeverity.CRITICAL]: 15,
|
|
155
|
+
};
|
|
156
|
+
let totalPenalty = 0;
|
|
157
|
+
for (const smell of smells) {
|
|
158
|
+
totalPenalty += severityWeights[smell.severity];
|
|
159
|
+
}
|
|
160
|
+
// Normalize penalty per file (penalty per file should reduce score)
|
|
161
|
+
const penaltyPerFile = totalPenalty / totalFiles;
|
|
162
|
+
// Score calculation: 100 - (penalty per file, capped at 100)
|
|
163
|
+
const score = Math.max(0, 100 - Math.min(100, penaltyPerFile * 2));
|
|
164
|
+
return Math.round(score);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=code-smell-analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-smell-analyzer.js","sourceRoot":"","sources":["../../../src/analyzers/code-smells/code-smell-analyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Clumps Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects groups of parameters that frequently appear together
|
|
5
|
+
*/
|
|
6
|
+
import { BaseSmellDetector } from '../base-smell-detector.js';
|
|
7
|
+
import { type CodeSmell, type DetectorConfig } from '../types.js';
|
|
8
|
+
export declare class DataClumpsDetector extends BaseSmellDetector {
|
|
9
|
+
constructor(config?: DetectorConfig);
|
|
10
|
+
detect(filePath: string): Promise<CodeSmell[]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Clumps Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects groups of parameters that frequently appear together
|
|
5
|
+
*/
|
|
6
|
+
import { BaseSmellDetector } from '../base-smell-detector.js';
|
|
7
|
+
import { SmellType, SmellSeverity } from '../types.js';
|
|
8
|
+
export class DataClumpsDetector extends BaseSmellDetector {
|
|
9
|
+
constructor(config = { enabled: true, thresholds: { minOccurrences: 3, minParams: 3 } }) {
|
|
10
|
+
super(SmellType.DATA_CLUMPS, config);
|
|
11
|
+
}
|
|
12
|
+
async detect(filePath) {
|
|
13
|
+
if (!this.isEnabled())
|
|
14
|
+
return [];
|
|
15
|
+
const smells = [];
|
|
16
|
+
const minOccurrences = this.getThreshold('minOccurrences', 3);
|
|
17
|
+
const minParams = this.getThreshold('minParams', 3);
|
|
18
|
+
try {
|
|
19
|
+
const ast = this.astParser.parseFile(filePath);
|
|
20
|
+
// Collect parameter combinations
|
|
21
|
+
const paramCombinations = new Map();
|
|
22
|
+
// Check functions
|
|
23
|
+
for (const func of ast.functions) {
|
|
24
|
+
if (func.parameters.length >= minParams) {
|
|
25
|
+
const paramNames = func.parameters.map(p => p.name).sort().join(',');
|
|
26
|
+
if (!paramCombinations.has(paramNames)) {
|
|
27
|
+
paramCombinations.set(paramNames, []);
|
|
28
|
+
}
|
|
29
|
+
paramCombinations.get(paramNames).push({
|
|
30
|
+
location: `function ${func.name}`,
|
|
31
|
+
line: func.startLine,
|
|
32
|
+
params: func.parameters.map(p => p.name),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Check methods
|
|
37
|
+
for (const cls of ast.classes) {
|
|
38
|
+
for (const method of cls.methods) {
|
|
39
|
+
if (method.parameters.length >= minParams) {
|
|
40
|
+
const paramNames = method.parameters.map(p => p.name).sort().join(',');
|
|
41
|
+
if (!paramCombinations.has(paramNames)) {
|
|
42
|
+
paramCombinations.set(paramNames, []);
|
|
43
|
+
}
|
|
44
|
+
paramCombinations.get(paramNames).push({
|
|
45
|
+
location: `${cls.name}.${method.name}`,
|
|
46
|
+
line: method.startLine,
|
|
47
|
+
params: method.parameters.map(p => p.name),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Report data clumps
|
|
53
|
+
for (const [, occurrences] of paramCombinations.entries()) {
|
|
54
|
+
if (occurrences.length >= minOccurrences) {
|
|
55
|
+
const firstOccurrence = occurrences[0];
|
|
56
|
+
smells.push(this.createSmell(filePath, firstOccurrence.line, firstOccurrence.line, `Data clump detected: Parameters (${firstOccurrence.params.join(', ')}) appear together in ${occurrences.length} locations`, `Consider creating a data class or configuration object to group these related parameters.`, occurrences.length >= 5 ? SmellSeverity.HIGH : SmellSeverity.MEDIUM, { parameters: firstOccurrence.params, occurrences: occurrences.length, locations: occurrences.map(o => o.location) }));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// Skip files that can't be parsed
|
|
62
|
+
}
|
|
63
|
+
return smells;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=data-clumps-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-clumps-detector.js","sourceRoot":"","sources":["../../../../src/analyzers/code-smells/detectors/data-clumps-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead Code Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects unused exports and variables
|
|
5
|
+
*/
|
|
6
|
+
import { BaseSmellDetector } from '../base-smell-detector.js';
|
|
7
|
+
import { type CodeSmell, type DetectorConfig } from '../types.js';
|
|
8
|
+
export declare class DeadCodeDetector extends BaseSmellDetector {
|
|
9
|
+
constructor(config?: DetectorConfig);
|
|
10
|
+
detect(filePath: string): Promise<CodeSmell[]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead Code Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects unused exports and variables
|
|
5
|
+
*/
|
|
6
|
+
import { BaseSmellDetector } from '../base-smell-detector.js';
|
|
7
|
+
import { SmellType, SmellSeverity } from '../types.js';
|
|
8
|
+
export class DeadCodeDetector extends BaseSmellDetector {
|
|
9
|
+
constructor(config = { enabled: true }) {
|
|
10
|
+
super(SmellType.DEAD_CODE, config);
|
|
11
|
+
}
|
|
12
|
+
async detect(filePath) {
|
|
13
|
+
if (!this.isEnabled())
|
|
14
|
+
return [];
|
|
15
|
+
const smells = [];
|
|
16
|
+
try {
|
|
17
|
+
const ast = this.astParser.parseFile(filePath);
|
|
18
|
+
const sourceFile = this.astParser.getSourceFile(filePath);
|
|
19
|
+
// Find all exported symbols
|
|
20
|
+
const exportedSymbols = new Set(ast.exports.map(e => e.name));
|
|
21
|
+
// Find all variable declarations
|
|
22
|
+
const variables = sourceFile.getVariableDeclarations();
|
|
23
|
+
for (const variable of variables) {
|
|
24
|
+
const name = variable.getName();
|
|
25
|
+
// Skip exported variables
|
|
26
|
+
if (exportedSymbols.has(name))
|
|
27
|
+
continue;
|
|
28
|
+
// Check if variable is referenced
|
|
29
|
+
const references = variable.findReferencesAsNodes();
|
|
30
|
+
// If only 1 reference (the declaration itself), it's unused
|
|
31
|
+
if (references.length <= 1) {
|
|
32
|
+
smells.push(this.createSmell(filePath, variable.getStartLineNumber(), variable.getEndLineNumber(), `Unused variable '${name}'`, `Remove this unused variable or export it if needed elsewhere.`, SmellSeverity.LOW, { variableName: name, references: references.length }));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Check for unused functions (not exported and not called)
|
|
36
|
+
const functions = sourceFile.getFunctions();
|
|
37
|
+
for (const func of functions) {
|
|
38
|
+
const name = func.getName();
|
|
39
|
+
if (!name || exportedSymbols.has(name))
|
|
40
|
+
continue;
|
|
41
|
+
const references = func.findReferencesAsNodes();
|
|
42
|
+
if (references.length <= 1) {
|
|
43
|
+
smells.push(this.createSmell(filePath, func.getStartLineNumber(), func.getEndLineNumber(), `Unused function '${name}'`, `Remove this unused function or export it if needed.`, SmellSeverity.MEDIUM, { functionName: name, references: references.length }));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
// Skip files that can't be parsed
|
|
49
|
+
}
|
|
50
|
+
return smells;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=dead-code-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dead-code-detector.js","sourceRoot":"","sources":["../../../../src/analyzers/code-smells/detectors/dead-code-detector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,aAAa,EAAuC,MAAM,aAAa,CAAC;AAE5F,MAAM,OAAO,gBAAiB,SAAQ,iBAAiB;IACrD,YAAY,SAAyB,EAAE,OAAO,EAAE,IAAI,EAAE;QACpD,KAAK,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAgB;QAC3B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;YAAE,OAAO,EAAE,CAAC;QAEjC,MAAM,MAAM,GAAgB,EAAE,CAAC;QAE/B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAE1D,4BAA4B;YAC5B,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAE9D,iCAAiC;YACjC,MAAM,SAAS,GAAG,UAAU,CAAC,uBAAuB,EAAE,CAAC;YAEvD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACjC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;gBAEhC,0BAA0B;gBAC1B,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAExC,kCAAkC;gBAClC,MAAM,UAAU,GAAG,QAAQ,CAAC,qBAAqB,EAAE,CAAC;gBAEpD,4DAA4D;gBAC5D,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;oBAC3B,MAAM,CAAC,IAAI,CACT,IAAI,CAAC,WAAW,CACd,QAAQ,EACR,QAAQ,CAAC,kBAAkB,EAAE,EAC7B,QAAQ,CAAC,gBAAgB,EAAE,EAC3B,oBAAoB,IAAI,GAAG,EAC3B,+DAA+D,EAC/D,aAAa,CAAC,GAAG,EACjB,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,MAAM,EAAE,CACtD,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;YAED,2DAA2D;YAC3D,MAAM,SAAS,GAAG,UAAU,CAAC,YAAY,EAAE,CAAC;YAC5C,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;gBAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC5B,IAAI,CAAC,IAAI,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAEjD,MAAM,UAAU,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBAChD,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;oBAC3B,MAAM,CAAC,IAAI,CACT,IAAI,CAAC,WAAW,CACd,QAAQ,EACR,IAAI,CAAC,kBAAkB,EAAE,EACzB,IAAI,CAAC,gBAAgB,EAAE,EACvB,oBAAoB,IAAI,GAAG,EAC3B,qDAAqD,EACrD,aAAa,CAAC,MAAM,EACpB,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,MAAM,EAAE,CACtD,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,kCAAkC;QACpC,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duplicate Code Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects similar code blocks (simplified heuristic-based detection)
|
|
5
|
+
*/
|
|
6
|
+
import { BaseSmellDetector } from '../base-smell-detector.js';
|
|
7
|
+
import { type CodeSmell, type DetectorConfig } from '../types.js';
|
|
8
|
+
export declare class DuplicateCodeDetector extends BaseSmellDetector {
|
|
9
|
+
constructor(config?: DetectorConfig);
|
|
10
|
+
detect(filePath: string): Promise<CodeSmell[]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duplicate Code Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects similar code blocks (simplified heuristic-based detection)
|
|
5
|
+
*/
|
|
6
|
+
import { BaseSmellDetector } from '../base-smell-detector.js';
|
|
7
|
+
import { SmellType, SmellSeverity } from '../types.js';
|
|
8
|
+
export class DuplicateCodeDetector extends BaseSmellDetector {
|
|
9
|
+
constructor(config = { enabled: true, thresholds: { minLines: 5 } }) {
|
|
10
|
+
super(SmellType.DUPLICATE_CODE, config);
|
|
11
|
+
}
|
|
12
|
+
async detect(filePath) {
|
|
13
|
+
if (!this.isEnabled())
|
|
14
|
+
return [];
|
|
15
|
+
const smells = [];
|
|
16
|
+
try {
|
|
17
|
+
const ast = this.astParser.parseFile(filePath);
|
|
18
|
+
// Simplified detection: Check for functions/methods with identical names (potential copy-paste)
|
|
19
|
+
const functionSignatures = new Map();
|
|
20
|
+
// Collect function signatures
|
|
21
|
+
for (const func of ast.functions) {
|
|
22
|
+
const signature = `${func.name}_${func.parameters.length}`;
|
|
23
|
+
if (!functionSignatures.has(signature)) {
|
|
24
|
+
functionSignatures.set(signature, []);
|
|
25
|
+
}
|
|
26
|
+
functionSignatures.get(signature).push({
|
|
27
|
+
name: func.name,
|
|
28
|
+
line: func.startLine,
|
|
29
|
+
params: func.parameters.length,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// Check for methods with very similar signatures across classes
|
|
33
|
+
const methodMap = new Map();
|
|
34
|
+
for (const cls of ast.classes) {
|
|
35
|
+
for (const method of cls.methods) {
|
|
36
|
+
const key = `${method.name}_${method.parameters.length}`;
|
|
37
|
+
methodMap.set(key, (methodMap.get(key) || 0) + 1);
|
|
38
|
+
// If same method appears in multiple classes, might be code duplication
|
|
39
|
+
if (methodMap.get(key) > 1) {
|
|
40
|
+
smells.push(this.createSmell(filePath, method.startLine, method.endLine, `Potential code duplication: Method '${method.name}' with ${method.parameters.length} parameters appears in multiple classes`, `Consider extracting common logic into a shared utility function or base class.`, SmellSeverity.MEDIUM, { methodName: method.name, occurrences: methodMap.get(key) }));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// Skip files that can't be parsed
|
|
47
|
+
}
|
|
48
|
+
return smells;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=duplicate-code-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-code-detector.js","sourceRoot":"","sources":["../../../../src/analyzers/code-smells/detectors/duplicate-code-detector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,aAAa,EAAuC,MAAM,aAAa,CAAC;AAE5F,MAAM,OAAO,qBAAsB,SAAQ,iBAAiB;IAC1D,YAAY,SAAyB,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;QACjF,KAAK,CAAC,SAAS,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAAgB;QAC3B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;YAAE,OAAO,EAAE,CAAC;QAEjC,MAAM,MAAM,GAAgB,EAAE,CAAC;QAE/B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YAE/C,gGAAgG;YAChG,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAiE,CAAC;YAEpG,8BAA8B;YAC9B,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;gBACjC,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;gBAC3D,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;oBACvC,kBAAkB,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;gBACxC,CAAC;gBACD,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAE,CAAC,IAAI,CAAC;oBACtC,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,IAAI,EAAE,IAAI,CAAC,SAAS;oBACpB,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM;iBAC/B,CAAC,CAAC;YACL,CAAC;YAED,gEAAgE;YAChE,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;YAC5C,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAC9B,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;oBACjC,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;oBACzD,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;oBAElD,wEAAwE;oBACxE,IAAI,SAAS,CAAC,GAAG,CAAC,GAAG,CAAE,GAAG,CAAC,EAAE,CAAC;wBAC5B,MAAM,CAAC,IAAI,CACT,IAAI,CAAC,WAAW,CACd,QAAQ,EACR,MAAM,CAAC,SAAS,EAChB,MAAM,CAAC,OAAO,EACd,uCAAuC,MAAM,CAAC,IAAI,UAAU,MAAM,CAAC,UAAU,CAAC,MAAM,yCAAyC,EAC7H,gFAAgF,EAChF,aAAa,CAAC,MAAM,EACpB,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAC7D,CACF,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,kCAAkC;QACpC,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature Envy Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects methods that use more features from other classes than their own
|
|
5
|
+
*/
|
|
6
|
+
import { BaseSmellDetector } from '../base-smell-detector.js';
|
|
7
|
+
import { type CodeSmell, type DetectorConfig } from '../types.js';
|
|
8
|
+
export declare class FeatureEnvyDetector extends BaseSmellDetector {
|
|
9
|
+
constructor(config?: DetectorConfig);
|
|
10
|
+
detect(filePath: string): Promise<CodeSmell[]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature Envy Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects methods that use more features from other classes than their own
|
|
5
|
+
*/
|
|
6
|
+
import { BaseSmellDetector } from '../base-smell-detector.js';
|
|
7
|
+
import { SmellType, SmellSeverity } from '../types.js';
|
|
8
|
+
import { SyntaxKind } from 'ts-morph';
|
|
9
|
+
export class FeatureEnvyDetector extends BaseSmellDetector {
|
|
10
|
+
constructor(config = { enabled: true, thresholds: { ratio: 0.7 } }) {
|
|
11
|
+
super(SmellType.FEATURE_ENVY, config);
|
|
12
|
+
}
|
|
13
|
+
async detect(filePath) {
|
|
14
|
+
if (!this.isEnabled())
|
|
15
|
+
return [];
|
|
16
|
+
const smells = [];
|
|
17
|
+
const envyRatio = this.getThreshold('ratio', 0.7);
|
|
18
|
+
try {
|
|
19
|
+
const sourceFile = this.astParser.getSourceFile(filePath);
|
|
20
|
+
const classes = sourceFile.getClasses();
|
|
21
|
+
for (const cls of classes) {
|
|
22
|
+
const className = cls.getName() || 'anonymous';
|
|
23
|
+
const classProperties = new Set(cls.getProperties().map(p => p.getName()));
|
|
24
|
+
for (const method of cls.getMethods()) {
|
|
25
|
+
const methodName = method.getName();
|
|
26
|
+
let ownPropertyAccess = 0;
|
|
27
|
+
let externalPropertyAccess = 0;
|
|
28
|
+
// Count property accesses
|
|
29
|
+
method.forEachDescendant((node) => {
|
|
30
|
+
if (node.getKind() === SyntaxKind.PropertyAccessExpression) {
|
|
31
|
+
const text = node.getText();
|
|
32
|
+
// Check if accessing own property
|
|
33
|
+
if (text.startsWith('this.')) {
|
|
34
|
+
const propName = text.slice(5).split('.')[0];
|
|
35
|
+
if (classProperties.has(propName)) {
|
|
36
|
+
ownPropertyAccess++;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
externalPropertyAccess++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
externalPropertyAccess++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const totalAccess = ownPropertyAccess + externalPropertyAccess;
|
|
48
|
+
if (totalAccess > 5) {
|
|
49
|
+
// Only check if significant number of accesses
|
|
50
|
+
const externalRatio = externalPropertyAccess / totalAccess;
|
|
51
|
+
if (externalRatio >= envyRatio) {
|
|
52
|
+
smells.push(this.createSmell(filePath, method.getStartLineNumber(), method.getEndLineNumber(), `Method '${className}.${methodName}' exhibits feature envy (${Math.round(externalRatio * 100)}% external access)`, `This method accesses more external properties than its own. Consider moving this logic to the class it's most interested in.`, externalRatio >= 0.9 ? SmellSeverity.HIGH : SmellSeverity.MEDIUM, { className, methodName, ownAccess: ownPropertyAccess, externalAccess: externalPropertyAccess }));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// Skip files that can't be parsed
|
|
60
|
+
}
|
|
61
|
+
return smells;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=feature-envy-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-envy-detector.js","sourceRoot":"","sources":["../../../../src/analyzers/code-smells/detectors/feature-envy-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inappropriate Intimacy Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects classes that are too tightly coupled
|
|
5
|
+
*/
|
|
6
|
+
import { BaseSmellDetector } from '../base-smell-detector.js';
|
|
7
|
+
import { type CodeSmell, type DetectorConfig } from '../types.js';
|
|
8
|
+
export declare class InappropriateIntimacyDetector extends BaseSmellDetector {
|
|
9
|
+
constructor(config?: DetectorConfig);
|
|
10
|
+
detect(filePath: string): Promise<CodeSmell[]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inappropriate Intimacy Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects classes that are too tightly coupled
|
|
5
|
+
*/
|
|
6
|
+
import { BaseSmellDetector } from '../base-smell-detector.js';
|
|
7
|
+
import { SmellType, SmellSeverity } from '../types.js';
|
|
8
|
+
import { SyntaxKind } from 'ts-morph';
|
|
9
|
+
export class InappropriateIntimacyDetector extends BaseSmellDetector {
|
|
10
|
+
constructor(config = { enabled: true, thresholds: { maxAccess: 10 } }) {
|
|
11
|
+
super(SmellType.INAPPROPRIATE_INTIMACY, config);
|
|
12
|
+
}
|
|
13
|
+
async detect(filePath) {
|
|
14
|
+
if (!this.isEnabled())
|
|
15
|
+
return [];
|
|
16
|
+
const smells = [];
|
|
17
|
+
const maxAccess = this.getThreshold('maxAccess', 10);
|
|
18
|
+
try {
|
|
19
|
+
const sourceFile = this.astParser.getSourceFile(filePath);
|
|
20
|
+
const classes = sourceFile.getClasses();
|
|
21
|
+
for (const cls of classes) {
|
|
22
|
+
const className = cls.getName() || 'anonymous';
|
|
23
|
+
const externalClassAccess = new Map();
|
|
24
|
+
// Count accesses to other classes
|
|
25
|
+
cls.forEachDescendant((node) => {
|
|
26
|
+
if (node.getKind() === SyntaxKind.PropertyAccessExpression) {
|
|
27
|
+
const text = node.getText();
|
|
28
|
+
// Skip 'this.' accesses
|
|
29
|
+
if (text.startsWith('this.'))
|
|
30
|
+
return;
|
|
31
|
+
// Extract potential class name from property access (e.g., 'otherClass.property')
|
|
32
|
+
const parts = text.split('.');
|
|
33
|
+
if (parts.length >= 2) {
|
|
34
|
+
const potentialClass = parts[0];
|
|
35
|
+
// Simple heuristic: capitalized names or common object names
|
|
36
|
+
if (potentialClass.length > 0 && /^[a-z][a-zA-Z]*$/.test(potentialClass)) {
|
|
37
|
+
externalClassAccess.set(potentialClass, (externalClassAccess.get(potentialClass) || 0) + 1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
// Report excessive coupling
|
|
43
|
+
for (const [externalClass, accessCount] of externalClassAccess.entries()) {
|
|
44
|
+
if (accessCount > maxAccess) {
|
|
45
|
+
smells.push(this.createSmell(filePath, cls.getStartLineNumber(), cls.getEndLineNumber(), `Class '${className}' is too intimate with '${externalClass}' (${accessCount} accesses)`, `Consider refactoring to reduce coupling. Move shared functionality to a common base class or use composition.`, accessCount >= 20 ? SmellSeverity.HIGH : SmellSeverity.MEDIUM, { className, targetClass: externalClass, accessCount, threshold: maxAccess }));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
// Skip files that can't be parsed
|
|
52
|
+
}
|
|
53
|
+
return smells;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=inappropriate-intimacy-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inappropriate-intimacy-detector.js","sourceRoot":"","sources":["../../../../src/analyzers/code-smells/detectors/inappropriate-intimacy-detector.ts"],"names":[],"mappings":"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"}
|