@sun-asterisk/sunlint 1.3.47 → 1.3.49
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/config/rules/rules-registry-generated.json +1717 -282
- package/core/architecture-integration.js +57 -15
- package/core/cli-action-handler.js +51 -36
- package/core/config-manager.js +6 -0
- package/core/config-merger.js +33 -0
- package/core/config-validator.js +37 -2
- package/core/file-targeting-service.js +148 -15
- package/core/init-command.js +118 -70
- package/core/output-service.js +12 -3
- package/core/project-detector.js +517 -0
- package/core/scoring-service.js +12 -6
- package/core/summary-report-service.js +9 -4
- package/core/tui-select.js +245 -0
- package/engines/arch-detect/rules/layered/l001-presentation-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l002-business-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l003-data-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l004-model-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l005-layer-separation.js +22 -2
- package/engines/arch-detect/rules/layered/l006-dependency-direction.js +8 -5
- package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +67 -29
- package/engines/arch-detect/rules/presentation/pr001-view-layer.js +16 -9
- package/engines/arch-detect/rules/presentation/pr006-router-layer.js +33 -8
- package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +35 -6
- package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +56 -10
- package/engines/impact/cli.js +54 -39
- package/engines/impact/config/default-config.js +105 -5
- package/engines/impact/core/impact-analyzer.js +12 -15
- package/engines/impact/core/utils/gitignore-parser.js +123 -0
- package/engines/impact/core/utils/method-call-graph.js +272 -87
- package/origin-rules/dart-en.md +1 -1
- package/origin-rules/go-en.md +231 -0
- package/origin-rules/php-en.md +107 -0
- package/origin-rules/python-en.md +113 -0
- package/origin-rules/ruby-en.md +607 -0
- package/package.json +1 -1
- package/scripts/copy-arch-detect.js +5 -1
- package/scripts/copy-impact-analyzer.js +5 -1
- package/scripts/generate-rules-registry.js +30 -14
- package/skill-assets/sunlint-code-quality/SKILL.md +3 -2
- package/skill-assets/sunlint-code-quality/rules/dart/C006-verb-noun-functions.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C013-no-dead-code.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C014-dependency-injection.md +92 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C017-no-constructor-logic.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C018-generic-errors.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C019-error-log-level.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C020-no-unused-imports.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C022-no-unused-variables.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C023-no-duplicate-names.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C024-centralize-constants.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C029-catch-log-root-cause.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C030-custom-error-classes.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C033-separate-data-access.md +90 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C035-error-context-logging.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C041-no-hardcoded-secrets.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C042-boolean-naming.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C052-widget-parsing.md +84 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C060-superclass-logic.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C067-no-hardcoded-config.md +108 -0
- package/skill-assets/sunlint-code-quality/rules/go/G001-explicit-error-handling.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/G002-context-first-argument.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/G003-receiver-consistency.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/go/G004-avoid-panic.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/G005-goroutine-leak-prevention.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/G006-interface-consumer-definition.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN001-gin-binding-validation.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN002-gin-error-response.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN003-graceful-shutdown.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN004-gin-route-logical-grouping.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/AGENTS.md +149 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN001-abort-after-response.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN002-request-context.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN003-bind-error-handling.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN004-dependency-injection.md +78 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN005-route-groups-middleware.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN006-http-status-codes.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN007-release-mode.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN008-struct-validation-tags.md +90 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN009-recovery-middleware.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN010-context-scope.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN011-middleware-concerns.md +92 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN012-no-log-sensitive.md +84 -0
- package/skill-assets/sunlint-code-quality/rules/java/J001-try-with-resources.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/java/J002-equals-and-hashcode.md +88 -0
- package/skill-assets/sunlint-code-quality/rules/java/J003-string-comparison.md +72 -0
- package/skill-assets/sunlint-code-quality/rules/java/J004-use-java-time.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/java/J005-no-print-stack-trace.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/java/J006-no-system-println.md +89 -0
- package/skill-assets/sunlint-code-quality/rules/java/J007-proper-logger.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/java/J008-thread-safe-singleton.md +119 -0
- package/skill-assets/sunlint-code-quality/rules/java/J009-utility-class-constructor.md +82 -0
- package/skill-assets/sunlint-code-quality/rules/java/J010-preserve-stack-trace.md +119 -0
- package/skill-assets/sunlint-code-quality/rules/java/J011-null-safe-compare.md +88 -0
- package/skill-assets/sunlint-code-quality/rules/java/J012-use-enum-collections.md +104 -0
- package/skill-assets/sunlint-code-quality/rules/java/J013-return-empty-not-null.md +102 -0
- package/skill-assets/sunlint-code-quality/rules/java/J014-hardcoded-crypto-key.md +108 -0
- package/skill-assets/sunlint-code-quality/rules/java/J015-optional-instead-of-null.md +109 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/AGENTS.md +124 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV001-form-request-validation.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV002-eager-load-no-n-plus-1.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV003-config-not-env.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV004-fillable-mass-assignment.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV005-policies-gates-authorization.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV006-queue-heavy-tasks.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV007-hash-passwords.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV008-route-model-binding.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV009-api-resources.md +72 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV010-chunk-large-datasets.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV011-db-transactions.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV012-service-layer.md +78 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV013-testing-factories.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV014-service-container.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/python/P001-mutable-default-argument.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/python/P002-specify-file-encoding.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/python/P003-context-manager-for-resources.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/python/P004-no-bare-except.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/python/P005-use-isinstance.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/python/P006-timezone-aware-datetime.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/python/P007-use-pathlib.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/python/P008-no-wildcard-import.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/python/P009-logging-lazy-format.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/python/P010-exception-chaining.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/python/P011-subprocess-check.md +59 -0
- package/skill-assets/sunlint-code-quality/rules/python/P012-requests-timeout.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/python/P013-no-global-statement.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/python/P014-no-modify-collection-while-iterating.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/python/P015-prefer-fstrings.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/AGENTS.md +121 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR001-strong-parameters.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR002-eager-load-includes.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR003-service-objects.md +99 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR004-active-job-background.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR005-pagination.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR006-find-each-batches.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR007-http-status-codes.md +76 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR008-before-action-auth.md +77 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR009-rails-credentials.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR010-scopes.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR011-counter-cache.md +59 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR012-render-json-status.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C006-verb-noun-functions.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C013-no-dead-code.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C014-dependency-injection.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C017-no-constructor-logic.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C018-generic-errors.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C019-error-log-level.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C020-no-unused-imports.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C022-no-unused-variables.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C023-no-duplicate-names.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C024-centralize-constants.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C029-catch-log-root-cause.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C030-custom-error-classes.md +77 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C033-separate-data-access.md +89 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C035-error-context-logging.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C041-no-hardcoded-secrets.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C042-boolean-naming.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C052-controller-parsing.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C060-superclass-logic.md +95 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C067-no-hardcoded-config.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S003-sql-injection.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S004-no-log-credentials.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S005-server-authorization.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S006-default-credentials.md +76 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S007-output-encoding.md +96 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S009-approved-crypto.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S010-csprng.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S011-insecure-deserialization.md +74 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S012-secrets-management.md +81 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S013-tls-connections.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S017-parameterized-queries.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S019-session-management.md +131 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S020-kvc-injection.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S025-input-validation.md +125 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S029-brute-force-protection.md +120 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S036-path-traversal.md +102 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S039-tls-certificate-validation.md +109 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S041-logout-invalidation.md +103 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S043-password-hashing.md +116 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S044-critical-changes-reauth.md +145 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S045-debug-info-exposure.md +116 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S046-unvalidated-redirect.md +140 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S051-token-expiry.md +134 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S053-jwt-validation.md +139 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S059-background-snapshot-protection.md +113 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S060-data-protection-api.md +106 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S061-jailbreak-detection.md +132 -0
|
@@ -9,8 +9,8 @@ import path from 'path';
|
|
|
9
9
|
export class MethodCallGraph {
|
|
10
10
|
constructor() {
|
|
11
11
|
this.project = null;
|
|
12
|
-
this.methodCallMap = new Map(); // method ->
|
|
13
|
-
this.methodCallsMap = new Map(); // method ->
|
|
12
|
+
this.methodCallMap = new Map(); // method -> Set(callers)
|
|
13
|
+
this.methodCallsMap = new Map(); // method -> Set(methods it calls)
|
|
14
14
|
this.methodToFile = new Map(); // method -> file path
|
|
15
15
|
this.methodToEndpoint = new Map(); // method -> endpoint info
|
|
16
16
|
this.interfaceToClass = new Map(); // interface name -> concrete class name
|
|
@@ -19,121 +19,311 @@ export class MethodCallGraph {
|
|
|
19
19
|
this.queueNameToProcessor = new Map(); // queue name -> processor class name
|
|
20
20
|
this.methodToQueueProcessor = new Map(); // method -> queue processor info
|
|
21
21
|
this.endpointToQueueNames = new Map(); // endpoint method -> [queue names]
|
|
22
|
+
this.classSet = new Set(); // All class names for O(1) lookup
|
|
23
|
+
this.typeCache = new Map(); // Cache type resolution results
|
|
24
|
+
this.dispatchPatterns = new Set(['sendcommand', 'send', 'execute', 'dispatch', 'publish', 'emit', 'trigger', 'enqueue']);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
28
|
* Initialize ts-morph project and build call graph
|
|
26
29
|
*/
|
|
27
|
-
async initialize(sourceDir, excludePaths = [], verbose = false) {
|
|
30
|
+
async initialize(sourceDir, excludePaths = [], verbose = false, gitignoreParser = null) {
|
|
28
31
|
this.verbose = verbose;
|
|
32
|
+
this.gitignoreParser = gitignoreParser;
|
|
29
33
|
|
|
34
|
+
if (this.verbose) {
|
|
35
|
+
console.log(`\n🔍 Discovering TypeScript files...`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Step 1: Find files BEFORE creating project (prevents module resolution)
|
|
39
|
+
const { glob } = await import('glob');
|
|
40
|
+
const pattern = `${sourceDir}/**/*.ts`;
|
|
41
|
+
|
|
42
|
+
// Build ignore patterns for glob
|
|
43
|
+
const ignorePatterns = [
|
|
44
|
+
'**/node_modules/**',
|
|
45
|
+
'**/dist/**',
|
|
46
|
+
'**/build/**',
|
|
47
|
+
'**/*.d.ts', // Type definitions have no runtime code
|
|
48
|
+
...excludePaths.map(p => `**/*${p}*/**`), // Convert simple paths to globs
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// Get all matching files
|
|
52
|
+
const allFiles = glob.sync(pattern, {
|
|
53
|
+
ignore: ignorePatterns,
|
|
54
|
+
absolute: true,
|
|
55
|
+
nodir: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Apply gitignore filtering if available
|
|
59
|
+
const rootDir = path.join(sourceDir, '..');
|
|
60
|
+
const filteredFiles = gitignoreParser
|
|
61
|
+
? allFiles.filter(filePath => {
|
|
62
|
+
const relativeToRoot = path.relative(rootDir, filePath);
|
|
63
|
+
return !gitignoreParser.shouldExclude(relativeToRoot);
|
|
64
|
+
})
|
|
65
|
+
: allFiles;
|
|
66
|
+
|
|
67
|
+
if (this.verbose) {
|
|
68
|
+
console.log(` Found ${allFiles.length} TypeScript files`);
|
|
69
|
+
if (gitignoreParser) {
|
|
70
|
+
console.log(` Filtered to ${filteredFiles.length} files after gitignore patterns`);
|
|
71
|
+
}
|
|
72
|
+
const excludedCount = allFiles.length - filteredFiles.length;
|
|
73
|
+
if (excludedCount > 0) {
|
|
74
|
+
console.log(` Excluded ${excludedCount} files (${Math.round(excludedCount / allFiles.length * 100)}%)`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Step 2: Create ts-morph project WITHOUT tsconfig (prevents loading node_modules)
|
|
30
79
|
this.project = new Project({
|
|
31
|
-
tsConfigFilePath: path.join(sourceDir, '../tsconfig.json'),
|
|
32
80
|
skipAddingFilesFromTsConfig: true,
|
|
81
|
+
skipLoadingLibFiles: true,
|
|
82
|
+
compilerOptions: {
|
|
83
|
+
target: 99, // ESNext
|
|
84
|
+
module: 1, // CommonJS
|
|
85
|
+
skipLibCheck: true,
|
|
86
|
+
allowJs: false,
|
|
87
|
+
noResolve: true, // Critical: Don't resolve module imports
|
|
88
|
+
}
|
|
33
89
|
});
|
|
34
90
|
|
|
35
|
-
// Add
|
|
36
|
-
this.
|
|
91
|
+
// Step 3: Add only the filtered files (no module resolution!)
|
|
92
|
+
if (this.verbose) {
|
|
93
|
+
console.log(`\n📊 Loading ${filteredFiles.length} files into ts-morph...`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.project.addSourceFilesAtPaths(filteredFiles);
|
|
97
|
+
|
|
98
|
+
const sourceFiles = this.project.getSourceFiles();
|
|
37
99
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
100
|
+
if (this.verbose) {
|
|
101
|
+
console.log(` ✅ Loaded ${sourceFiles.length} files`);
|
|
102
|
+
console.log(`\n🔬 Analyzing source code...`);
|
|
103
|
+
}
|
|
43
104
|
|
|
105
|
+
// Step 4: Single pass analysis
|
|
44
106
|
for (const sourceFile of sourceFiles) {
|
|
45
|
-
this.
|
|
46
|
-
this.extractCommandMappings(sourceFile);
|
|
47
|
-
this.extractQueueProcessorMappings(sourceFile);
|
|
107
|
+
this.analyzeFileComplete(sourceFile);
|
|
48
108
|
}
|
|
49
109
|
|
|
50
|
-
|
|
51
|
-
|
|
110
|
+
if (this.verbose) {
|
|
111
|
+
console.log(`\n✅ Analysis complete!`);
|
|
112
|
+
console.log(` Classes found: ${this.classSet.size}`);
|
|
113
|
+
console.log(` Methods found: ${this.methodToFile.size}`);
|
|
114
|
+
console.log(` Endpoints found: ${this.methodToEndpoint.size}`);
|
|
52
115
|
}
|
|
53
116
|
|
|
54
117
|
return this;
|
|
55
118
|
}
|
|
56
119
|
|
|
57
120
|
/**
|
|
58
|
-
* Analyze a single file
|
|
121
|
+
* Analyze a single file - consolidated single pass
|
|
59
122
|
*/
|
|
60
|
-
|
|
123
|
+
analyzeFileComplete(sourceFile) {
|
|
61
124
|
const filePath = sourceFile.getFilePath();
|
|
62
|
-
|
|
63
|
-
// Get all classes
|
|
64
125
|
const classes = sourceFile.getClasses();
|
|
65
126
|
|
|
66
127
|
for (const classDecl of classes) {
|
|
67
128
|
const className = classDecl.getName();
|
|
68
|
-
if (!className) continue;
|
|
129
|
+
if (!className) continue;
|
|
130
|
+
|
|
131
|
+
this.classSet.add(className);
|
|
69
132
|
|
|
70
|
-
//
|
|
133
|
+
// Extract class-level metadata
|
|
71
134
|
const classDecorators = classDecl.getDecorators();
|
|
135
|
+
|
|
136
|
+
// Check for @Processor decorator
|
|
72
137
|
const processorDecorator = classDecorators.find(d => d.getName() === 'Processor');
|
|
73
138
|
let processorQueueName = null;
|
|
74
|
-
|
|
75
139
|
if (processorDecorator) {
|
|
76
140
|
const args = processorDecorator.getArguments();
|
|
77
141
|
processorQueueName = args[0]?.getText().replace(/['"]/g, '');
|
|
142
|
+
this.queueNameToProcessor.set(processorQueueName, className);
|
|
78
143
|
}
|
|
79
144
|
|
|
80
|
-
//
|
|
81
|
-
const
|
|
145
|
+
// Check for @Command decorator
|
|
146
|
+
const commandDecorator = classDecorators.find(d => d.getName() === 'Command');
|
|
147
|
+
if (commandDecorator) {
|
|
148
|
+
const args = commandDecorator.getArguments();
|
|
149
|
+
if (args.length > 0) {
|
|
150
|
+
const text = args[0].getText();
|
|
151
|
+
const nameMatch = text.match(/name:\s*['"]([^'"]+)['"]/);
|
|
152
|
+
if (nameMatch) {
|
|
153
|
+
this.commandNameToClass.set(nameMatch[1], className);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
82
157
|
|
|
158
|
+
// Extract interface mappings from @Module decorator
|
|
159
|
+
const moduleDecorator = classDecl.getDecorator('Module');
|
|
160
|
+
if (moduleDecorator) {
|
|
161
|
+
const args = moduleDecorator.getArguments();
|
|
162
|
+
if (args.length > 0) {
|
|
163
|
+
const text = args[0].getText();
|
|
164
|
+
const providerPattern = /\{\s*provide:\s*['"](\w+)['"]\s*,\s*useClass:\s*(\w+)/g;
|
|
165
|
+
let match;
|
|
166
|
+
while ((match = providerPattern.exec(text)) !== null) {
|
|
167
|
+
this.interfaceToClass.set(match[1], match[2]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Analyze methods
|
|
173
|
+
const methods = classDecl.getMethods();
|
|
83
174
|
for (const method of methods) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
175
|
+
this.analyzeMethodComplete(method, className, filePath, processorQueueName);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Analyze a single method - consolidated single AST traversal
|
|
182
|
+
*/
|
|
183
|
+
analyzeMethodComplete(method, className, filePath, processorQueueName) {
|
|
184
|
+
const methodName = method.getName();
|
|
185
|
+
const fullMethodName = `${className}.${methodName}`;
|
|
186
|
+
|
|
187
|
+
this.methodToFile.set(fullMethodName, filePath);
|
|
188
|
+
|
|
189
|
+
// Check decorators
|
|
190
|
+
const decorators = method.getDecorators();
|
|
191
|
+
|
|
192
|
+
// Check for HTTP endpoint
|
|
193
|
+
const httpDecorator = decorators.find(d =>
|
|
194
|
+
['Get', 'Post', 'Put', 'Delete', 'Patch', 'Options', 'Head', 'All'].includes(d.getName())
|
|
195
|
+
);
|
|
196
|
+
if (httpDecorator) {
|
|
197
|
+
const decoratorName = httpDecorator.getName();
|
|
198
|
+
const args = httpDecorator.getArguments();
|
|
199
|
+
const route = args[0]?.getText().replace(/['"]/g, '') || '/';
|
|
200
|
+
this.methodToEndpoint.set(fullMethodName, {
|
|
201
|
+
method: decoratorName.toUpperCase(),
|
|
202
|
+
path: route,
|
|
203
|
+
controller: className,
|
|
204
|
+
file: filePath,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check for @Process decorator
|
|
209
|
+
const processDecorator = decorators.find(d => d.getName() === 'Process');
|
|
210
|
+
if (processDecorator && processorQueueName) {
|
|
211
|
+
const args = processDecorator.getArguments();
|
|
212
|
+
const jobName = args[0]?.getText().replace(/['"]/g, '') || '';
|
|
213
|
+
this.methodToQueueProcessor.set(fullMethodName, {
|
|
214
|
+
queueName: processorQueueName,
|
|
215
|
+
jobName: jobName,
|
|
216
|
+
processor: className,
|
|
217
|
+
method: methodName,
|
|
218
|
+
file: filePath,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Single AST traversal for all call expressions
|
|
223
|
+
const callExpressions = method.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
224
|
+
|
|
225
|
+
const commandNames = [];
|
|
226
|
+
const queueNames = [];
|
|
227
|
+
|
|
228
|
+
for (const call of callExpressions) {
|
|
229
|
+
const expression = call.getExpression();
|
|
230
|
+
|
|
231
|
+
if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
|
|
232
|
+
const calledMethodName = expression.getName();
|
|
233
|
+
const objExpression = expression.getExpression();
|
|
234
|
+
|
|
235
|
+
// Process method calls
|
|
236
|
+
const calledClassName = this.resolveClassName(objExpression, className);
|
|
237
|
+
if (calledClassName) {
|
|
238
|
+
this.recordMethodCall(fullMethodName, calledClassName, calledMethodName);
|
|
107
239
|
}
|
|
108
240
|
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
241
|
+
// Process command dispatches
|
|
242
|
+
if (this.isDispatchMethod(calledMethodName)) {
|
|
243
|
+
const args = call.getArguments();
|
|
244
|
+
if (args.length > 0) {
|
|
245
|
+
const firstArg = args[0];
|
|
246
|
+
if (firstArg.getKind() === SyntaxKind.StringLiteral) {
|
|
247
|
+
const commandName = firstArg.getText().replace(/['"]/g, '');
|
|
248
|
+
commandNames.push(commandName);
|
|
249
|
+
} else if (firstArg.getKind() === SyntaxKind.ObjectLiteralExpression) {
|
|
250
|
+
const commandName = this.extractCommandFromObject(firstArg, method);
|
|
251
|
+
if (commandName) commandNames.push(commandName);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
122
255
|
|
|
123
|
-
|
|
124
|
-
|
|
256
|
+
// Process queue dispatches
|
|
257
|
+
if (calledMethodName === 'add' && objExpression.getKind() === SyntaxKind.PropertyAccessExpression) {
|
|
258
|
+
const queuePropertyName = objExpression.getName();
|
|
259
|
+
const classDecl = method.getParent();
|
|
260
|
+
if (classDecl && classDecl.getKind() === SyntaxKind.ClassDeclaration) {
|
|
261
|
+
const constructor = classDecl.getConstructors()[0];
|
|
262
|
+
if (constructor) {
|
|
263
|
+
const queueName = this.findQueueNameFromConstructor(constructor, queuePropertyName);
|
|
264
|
+
if (queueName && !queueNames.includes(queueName)) {
|
|
265
|
+
queueNames.push(queueName);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
125
268
|
}
|
|
126
269
|
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
127
272
|
|
|
128
|
-
|
|
129
|
-
|
|
273
|
+
if (commandNames.length > 0) {
|
|
274
|
+
this.endpointToCommandNames.set(fullMethodName, commandNames);
|
|
275
|
+
}
|
|
276
|
+
if (queueNames.length > 0) {
|
|
277
|
+
this.endpointToQueueNames.set(fullMethodName, queueNames);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Resolve class name with caching and optimizations
|
|
283
|
+
*/
|
|
284
|
+
resolveClassName(objExpression, fallbackClassName) {
|
|
285
|
+
if (objExpression.getKind() === SyntaxKind.ThisKeyword) {
|
|
286
|
+
return fallbackClassName;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const cacheKey = objExpression.getText();
|
|
290
|
+
if (this.typeCache.has(cacheKey)) {
|
|
291
|
+
return this.typeCache.get(cacheKey);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const type = objExpression.getType();
|
|
295
|
+
const symbol = type.getSymbol();
|
|
296
|
+
|
|
297
|
+
if (!symbol) {
|
|
298
|
+
this.typeCache.set(cacheKey, null);
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
130
301
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
302
|
+
let calledClassName = symbol.getName();
|
|
303
|
+
|
|
304
|
+
if (this.interfaceToClass.has(calledClassName)) {
|
|
305
|
+
calledClassName = this.interfaceToClass.get(calledClassName);
|
|
306
|
+
} else if (calledClassName.startsWith('I') && calledClassName.length > 1) {
|
|
307
|
+
const possibleClassName = calledClassName.substring(1);
|
|
308
|
+
if (this.classSet.has(possibleClassName)) {
|
|
309
|
+
calledClassName = possibleClassName;
|
|
135
310
|
}
|
|
136
311
|
}
|
|
312
|
+
|
|
313
|
+
this.typeCache.set(cacheKey, calledClassName);
|
|
314
|
+
return calledClassName;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Check if method name matches dispatch patterns
|
|
319
|
+
*/
|
|
320
|
+
isDispatchMethod(methodName) {
|
|
321
|
+
const lower = methodName.toLowerCase();
|
|
322
|
+
if (this.dispatchPatterns.has(lower)) return true;
|
|
323
|
+
for (const pattern of this.dispatchPatterns) {
|
|
324
|
+
if (lower.includes(pattern)) return true;
|
|
325
|
+
}
|
|
326
|
+
return false;
|
|
137
327
|
}
|
|
138
328
|
|
|
139
329
|
/**
|
|
@@ -458,25 +648,17 @@ export class MethodCallGraph {
|
|
|
458
648
|
recordMethodCall(callerFullName, calleeClassName, calleeMethodName) {
|
|
459
649
|
const calleeFullName = `${calleeClassName}.${calleeMethodName}`;
|
|
460
650
|
|
|
461
|
-
// Add to reverse map (who calls this method)
|
|
651
|
+
// Add to reverse map (who calls this method) - use Set for O(1)
|
|
462
652
|
if (!this.methodCallMap.has(calleeFullName)) {
|
|
463
|
-
this.methodCallMap.set(calleeFullName,
|
|
653
|
+
this.methodCallMap.set(calleeFullName, new Set());
|
|
464
654
|
}
|
|
655
|
+
this.methodCallMap.get(calleeFullName).add(callerFullName);
|
|
465
656
|
|
|
466
|
-
|
|
467
|
-
if (!callers.includes(callerFullName)) {
|
|
468
|
-
callers.push(callerFullName);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Add to forward map (what this method calls)
|
|
657
|
+
// Add to forward map (what this method calls) - use Set for O(1)
|
|
472
658
|
if (!this.methodCallsMap.has(callerFullName)) {
|
|
473
|
-
this.methodCallsMap.set(callerFullName,
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const calls = this.methodCallsMap.get(callerFullName);
|
|
477
|
-
if (!calls.includes(calleeFullName)) {
|
|
478
|
-
calls.push(calleeFullName);
|
|
659
|
+
this.methodCallsMap.set(callerFullName, new Set());
|
|
479
660
|
}
|
|
661
|
+
this.methodCallsMap.get(callerFullName).add(calleeFullName);
|
|
480
662
|
}
|
|
481
663
|
|
|
482
664
|
/**
|
|
@@ -653,17 +835,20 @@ export class MethodCallGraph {
|
|
|
653
835
|
* Get direct callers of a method
|
|
654
836
|
*/
|
|
655
837
|
getCallers(methodName) {
|
|
656
|
-
|
|
838
|
+
const callers = this.methodCallMap.get(methodName);
|
|
839
|
+
return callers ? Array.from(callers) : [];
|
|
657
840
|
}
|
|
658
841
|
|
|
659
842
|
/**
|
|
660
843
|
* Find all callers of a method (recursive)
|
|
661
844
|
*/
|
|
662
|
-
findAllCallers(methodName, visited = new Set(), depth = 0) {
|
|
845
|
+
findAllCallers(methodName, visited = new Set(), depth = 0, maxDepth = 10) {
|
|
663
846
|
if (visited.has(methodName)) return [];
|
|
847
|
+
if (depth >= maxDepth) return [];
|
|
664
848
|
|
|
665
849
|
visited.add(methodName);
|
|
666
|
-
const
|
|
850
|
+
const directCallersSet = this.methodCallMap.get(methodName);
|
|
851
|
+
const directCallers = directCallersSet ? Array.from(directCallersSet) : [];
|
|
667
852
|
let allCallers = [...directCallers];
|
|
668
853
|
|
|
669
854
|
if (this.verbose && depth === 0) {
|
|
@@ -676,7 +861,7 @@ export class MethodCallGraph {
|
|
|
676
861
|
if (this.verbose && depth === 0) {
|
|
677
862
|
console.log(` Tracing up from: ${caller}`);
|
|
678
863
|
}
|
|
679
|
-
const indirectCallers = this.findAllCallers(caller, visited, depth + 1);
|
|
864
|
+
const indirectCallers = this.findAllCallers(caller, visited, depth + 1, maxDepth);
|
|
680
865
|
allCallers = allCallers.concat(indirectCallers);
|
|
681
866
|
}
|
|
682
867
|
|
package/origin-rules/dart-en.md
CHANGED
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
### 📘 Rule D008 – Avoid Long Functions
|
|
83
83
|
|
|
84
84
|
- **Objective**: Improve code readability and maintainability by limiting function length
|
|
85
|
-
- **Details**: Functions should not exceed 60 lines of effective code (excluding comments and opening/closing braces). Long functions are harder to understand, test, and maintain. They often indicate that the function is doing too much and should be broken down into smaller, more focused functions. The line count excludes blank lines, comments (both single-line // and multi-line
|
|
85
|
+
- **Details**: Functions should not exceed 60 lines of effective code (excluding comments and opening/closing braces). Long functions are harder to understand, test, and maintain. They often indicate that the function is doing too much and should be broken down into smaller, more focused functions. The line count excludes blank lines, comments (both single-line // and multi-line /\* \*/), and the opening `{` and closing `}` braces. The maximum line limit is configurable.
|
|
86
86
|
- **Applies to**: Flutter/Dart
|
|
87
87
|
- **Tools**: Custom analyzer (D008)
|
|
88
88
|
- **Principles**: CODE_QUALITY, MAINTAINABILITY, READABILITY
|