@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
|
@@ -50,6 +50,7 @@ const rule = (0, base_rule_1.createRule)('PR006', 'Router/Wireframe Layer', 'Xá
|
|
|
50
50
|
folderPatterns: ['routers', 'wireframes', 'navigation', 'routing', 'Routers', 'coordinators'],
|
|
51
51
|
filePatterns: ['*Router.*', '*Wireframe.*', '*Coordinator.*', '*Navigator.*'],
|
|
52
52
|
codePatterns: [
|
|
53
|
+
// Swift
|
|
53
54
|
{ language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.PATTERN, pattern: /protocol\s+\w+Router/ },
|
|
54
55
|
{ language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Router/ },
|
|
55
56
|
{
|
|
@@ -59,12 +60,31 @@ const rule = (0, base_rule_1.createRule)('PR006', 'Router/Wireframe Layer', 'Xá
|
|
|
59
60
|
},
|
|
60
61
|
{ language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.METHOD, pattern: /func\s+navigate/ },
|
|
61
62
|
{ language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.METHOD, pattern: /func\s+present/ },
|
|
63
|
+
// Kotlin
|
|
62
64
|
{
|
|
63
65
|
language: enums_1.Language.KOTLIN,
|
|
64
66
|
type: enums_1.CodePatternType.PATTERN,
|
|
65
67
|
pattern: /interface\s+\w+Router/,
|
|
66
68
|
},
|
|
67
69
|
{ language: enums_1.Language.KOTLIN, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Router/ },
|
|
70
|
+
// Java
|
|
71
|
+
{ language: enums_1.Language.JAVA, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Router/ },
|
|
72
|
+
// PHP
|
|
73
|
+
{
|
|
74
|
+
language: enums_1.Language.PHP,
|
|
75
|
+
type: enums_1.CodePatternType.PATTERN,
|
|
76
|
+
pattern: /Route::(get|post|put|delete|group)\s*\(/,
|
|
77
|
+
},
|
|
78
|
+
// Python
|
|
79
|
+
{ language: enums_1.Language.PYTHON, type: enums_1.CodePatternType.PATTERN, pattern: /urlpatterns\s*=/ },
|
|
80
|
+
{
|
|
81
|
+
language: enums_1.Language.PYTHON,
|
|
82
|
+
type: enums_1.CodePatternType.PATTERN,
|
|
83
|
+
pattern: /router\s*=\s*APIRouter\s*\(/,
|
|
84
|
+
},
|
|
85
|
+
// Go
|
|
86
|
+
{ language: enums_1.Language.GO, type: enums_1.CodePatternType.PATTERN, pattern: /func\s+\w+Router\b/ },
|
|
87
|
+
{ language: enums_1.Language.GO, type: enums_1.CodePatternType.PATTERN, pattern: /r\.Group\s*\(/ },
|
|
68
88
|
],
|
|
69
89
|
}, {
|
|
70
90
|
isKeyIndicator: true,
|
|
@@ -110,10 +130,10 @@ let PR006RouterLayer = (() => {
|
|
|
110
130
|
}
|
|
111
131
|
}
|
|
112
132
|
// Check for router files - match any file containing Router/Wireframe/WireFrame/Coordinator
|
|
113
|
-
const routerFiles = context.files.filter((f) => /Router\.(swift|kt|java|ts|js)$/i.test(f.fileName) ||
|
|
133
|
+
const routerFiles = context.files.filter((f) => /Router\.(swift|kt|java|ts|js|php|py|go|rb|cs)$/i.test(f.fileName) ||
|
|
114
134
|
/WireFrame\.(swift|kt|java)$/i.test(f.fileName) ||
|
|
115
135
|
/Wireframe\.(swift|kt|java)$/i.test(f.fileName) ||
|
|
116
|
-
/Coordinator\.(swift|kt|java|ts)$/i.test(f.fileName));
|
|
136
|
+
/Coordinator\.(swift|kt|java|ts|php|py|go)$/i.test(f.fileName));
|
|
117
137
|
if (routerFiles.length > 0) {
|
|
118
138
|
for (const file of routerFiles.slice(0, 5)) {
|
|
119
139
|
matches.push({
|
|
@@ -129,12 +149,17 @@ let PR006RouterLayer = (() => {
|
|
|
129
149
|
.filter((f) => !f.isTest && !f.isConfig && f.language)
|
|
130
150
|
.slice(0, 50);
|
|
131
151
|
const codePatterns = [
|
|
132
|
-
/protocol\s+\w+Router/,
|
|
133
|
-
/class\s+\w+Router/,
|
|
134
|
-
/class\s+\w+Coordinator/,
|
|
135
|
-
/func\s+navigateTo/,
|
|
136
|
-
/func\s+presentModule/,
|
|
137
|
-
/interface\s+\w+Router/,
|
|
152
|
+
/protocol\s+\w+Router/, // Swift
|
|
153
|
+
/class\s+\w+Router/, // Swift/Kotlin/Java
|
|
154
|
+
/class\s+\w+Coordinator/, // Swift
|
|
155
|
+
/func\s+navigateTo/, // Swift/Kotlin
|
|
156
|
+
/func\s+presentModule/, // Swift
|
|
157
|
+
/interface\s+\w+Router/, // Kotlin/Java
|
|
158
|
+
/Route::(get|post|put|delete|group)\s*\(/, // PHP Laravel
|
|
159
|
+
/urlpatterns\s*=/, // Python Django
|
|
160
|
+
/router\s*=\s*APIRouter\s*\(/, // Python FastAPI
|
|
161
|
+
/func\s+\w+Router\b/, // Go
|
|
162
|
+
/r\.Group\s*\(/, // Go Gin
|
|
138
163
|
];
|
|
139
164
|
for (const file of sourceFiles) {
|
|
140
165
|
try {
|
|
@@ -50,12 +50,14 @@ const rule = (0, base_rule_1.createRule)('PR007', 'Interactor Layer', 'Xác nh
|
|
|
50
50
|
folderPatterns: ['interactors', 'usecases', 'Interactors', 'UseCases'],
|
|
51
51
|
filePatterns: ['*Interactor.*', '*UseCase.*', '*BusinessLogic.*'],
|
|
52
52
|
codePatterns: [
|
|
53
|
+
// Swift
|
|
53
54
|
{
|
|
54
55
|
language: enums_1.Language.SWIFT,
|
|
55
56
|
type: enums_1.CodePatternType.PATTERN,
|
|
56
57
|
pattern: /protocol\s+\w+Interactor/,
|
|
57
58
|
},
|
|
58
59
|
{ language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Interactor/ },
|
|
60
|
+
// Kotlin
|
|
59
61
|
{
|
|
60
62
|
language: enums_1.Language.KOTLIN,
|
|
61
63
|
type: enums_1.CodePatternType.PATTERN,
|
|
@@ -67,6 +69,31 @@ const rule = (0, base_rule_1.createRule)('PR007', 'Interactor Layer', 'Xác nh
|
|
|
67
69
|
pattern: /class\s+\w+Interactor/,
|
|
68
70
|
},
|
|
69
71
|
{ language: enums_1.Language.KOTLIN, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
|
|
72
|
+
// Java
|
|
73
|
+
{ language: enums_1.Language.JAVA, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Interactor/ },
|
|
74
|
+
{ language: enums_1.Language.JAVA, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
|
|
75
|
+
// TypeScript
|
|
76
|
+
{
|
|
77
|
+
language: enums_1.Language.TYPESCRIPT,
|
|
78
|
+
type: enums_1.CodePatternType.PATTERN,
|
|
79
|
+
pattern: /class\s+\w+UseCase/,
|
|
80
|
+
},
|
|
81
|
+
// PHP
|
|
82
|
+
{ language: enums_1.Language.PHP, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
|
|
83
|
+
{ language: enums_1.Language.PHP, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Interactor/ },
|
|
84
|
+
// Python
|
|
85
|
+
{ language: enums_1.Language.PYTHON, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
|
|
86
|
+
// Go
|
|
87
|
+
{
|
|
88
|
+
language: enums_1.Language.GO,
|
|
89
|
+
type: enums_1.CodePatternType.PATTERN,
|
|
90
|
+
pattern: /type\s+\w+UseCase\s+struct/,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
language: enums_1.Language.GO,
|
|
94
|
+
type: enums_1.CodePatternType.PATTERN,
|
|
95
|
+
pattern: /type\s+\w+Interactor\s+struct/,
|
|
96
|
+
},
|
|
70
97
|
],
|
|
71
98
|
}, {
|
|
72
99
|
isKeyIndicator: true,
|
|
@@ -110,8 +137,8 @@ let PR007InteractorLayer = (() => {
|
|
|
110
137
|
}
|
|
111
138
|
}
|
|
112
139
|
// Check for interactor files
|
|
113
|
-
const interactorFiles = context.files.filter((f) => /Interactor\.(swift|kt|java|ts|js)$/i.test(f.fileName) ||
|
|
114
|
-
/UseCase\.(swift|kt|java|ts|js)$/i.test(f.fileName));
|
|
140
|
+
const interactorFiles = context.files.filter((f) => /Interactor\.(swift|kt|java|ts|js|php|py|go|rb|cs)$/i.test(f.fileName) ||
|
|
141
|
+
/UseCase\.(swift|kt|java|ts|js|php|py|go|rb|cs)$/i.test(f.fileName));
|
|
115
142
|
if (interactorFiles.length > 0) {
|
|
116
143
|
for (const file of interactorFiles.slice(0, 5)) {
|
|
117
144
|
matches.push({
|
|
@@ -127,10 +154,12 @@ let PR007InteractorLayer = (() => {
|
|
|
127
154
|
.filter((f) => !f.isTest && !f.isConfig && f.language)
|
|
128
155
|
.slice(0, 50);
|
|
129
156
|
const codePatterns = [
|
|
130
|
-
/protocol\s+\w+Interactor/,
|
|
131
|
-
/class\s+\w+Interactor/,
|
|
132
|
-
/interface\s+\w+Interactor/,
|
|
133
|
-
/class\s+\w+UseCase/,
|
|
157
|
+
/protocol\s+\w+Interactor/, // Swift
|
|
158
|
+
/class\s+\w+Interactor/, // Swift/Kotlin/Java/PHP
|
|
159
|
+
/interface\s+\w+Interactor/, // Kotlin/Java
|
|
160
|
+
/class\s+\w+UseCase/, // Kotlin/Java/TS/PHP/Python
|
|
161
|
+
/type\s+\w+UseCase\s+struct/, // Go
|
|
162
|
+
/type\s+\w+Interactor\s+struct/, // Go
|
|
134
163
|
];
|
|
135
164
|
for (const file of sourceFiles) {
|
|
136
165
|
try {
|
|
@@ -86,9 +86,20 @@ const FRAMEWORK_DETECTORS = [
|
|
|
86
86
|
dependencies: [
|
|
87
87
|
{ file: 'pom.xml', packages: ['spring-boot-starter'] },
|
|
88
88
|
{ file: 'build.gradle', packages: ['spring-boot'] },
|
|
89
|
+
{ file: 'build.gradle.kts', packages: ['spring-boot'] },
|
|
89
90
|
],
|
|
90
91
|
codePatterns: [/@SpringBootApplication/],
|
|
91
92
|
},
|
|
93
|
+
// Ktor
|
|
94
|
+
{
|
|
95
|
+
framework: enums_1.Framework.KTOR,
|
|
96
|
+
configFiles: [],
|
|
97
|
+
dependencies: [
|
|
98
|
+
{ file: 'build.gradle.kts', packages: ['io.ktor'] },
|
|
99
|
+
{ file: 'build.gradle', packages: ['io.ktor'] },
|
|
100
|
+
],
|
|
101
|
+
codePatterns: [/routing\s*\{/, /embeddedServer\s*\(/],
|
|
102
|
+
},
|
|
92
103
|
// Angular
|
|
93
104
|
{
|
|
94
105
|
framework: enums_1.Framework.ANGULAR,
|
|
@@ -133,15 +144,26 @@ const FRAMEWORK_DETECTORS = [
|
|
|
133
144
|
// Laravel
|
|
134
145
|
{
|
|
135
146
|
framework: enums_1.Framework.LARAVEL,
|
|
136
|
-
configFiles: ['artisan'],
|
|
147
|
+
configFiles: ['artisan', 'config/app.php'],
|
|
137
148
|
dependencies: [{ file: 'composer.json', packages: ['laravel/framework'] }],
|
|
138
|
-
codePatterns: [/extends\s+Controller/],
|
|
149
|
+
codePatterns: [/extends\s+Controller/, /use\s+Illuminate\\/],
|
|
150
|
+
},
|
|
151
|
+
// Symfony
|
|
152
|
+
{
|
|
153
|
+
framework: enums_1.Framework.SYMFONY,
|
|
154
|
+
configFiles: ['config/bundles.php', 'symfony.lock'],
|
|
155
|
+
dependencies: [{ file: 'composer.json', packages: ['symfony/framework-bundle'] }],
|
|
156
|
+
codePatterns: [/#\[Route\s*\(/, /extends\s+AbstractController/],
|
|
139
157
|
},
|
|
140
158
|
// Django
|
|
141
159
|
{
|
|
142
160
|
framework: enums_1.Framework.DJANGO,
|
|
143
161
|
configFiles: ['manage.py'],
|
|
144
|
-
dependencies: [
|
|
162
|
+
dependencies: [
|
|
163
|
+
{ file: 'requirements.txt', packages: ['django', 'Django'] },
|
|
164
|
+
{ file: 'pyproject.toml', packages: ['django', 'Django'] },
|
|
165
|
+
{ file: 'Pipfile', packages: ['django', 'Django'] },
|
|
166
|
+
],
|
|
145
167
|
codePatterns: [/from\s+django/, /INSTALLED_APPS/],
|
|
146
168
|
},
|
|
147
169
|
// Rails
|
|
@@ -186,15 +208,23 @@ const FRAMEWORK_DETECTORS = [
|
|
|
186
208
|
{
|
|
187
209
|
framework: enums_1.Framework.FASTAPI,
|
|
188
210
|
configFiles: [],
|
|
189
|
-
dependencies: [
|
|
211
|
+
dependencies: [
|
|
212
|
+
{ file: 'requirements.txt', packages: ['fastapi'] },
|
|
213
|
+
{ file: 'pyproject.toml', packages: ['fastapi'] },
|
|
214
|
+
{ file: 'Pipfile', packages: ['fastapi'] },
|
|
215
|
+
],
|
|
190
216
|
codePatterns: [/from\s+fastapi/, /@app\.(get|post|put|delete)\s*\(/],
|
|
191
217
|
},
|
|
192
218
|
// Flask
|
|
193
219
|
{
|
|
194
220
|
framework: enums_1.Framework.FLASK,
|
|
195
221
|
configFiles: [],
|
|
196
|
-
dependencies: [
|
|
197
|
-
|
|
222
|
+
dependencies: [
|
|
223
|
+
{ file: 'requirements.txt', packages: ['flask', 'Flask'] },
|
|
224
|
+
{ file: 'pyproject.toml', packages: ['flask', 'Flask'] },
|
|
225
|
+
{ file: 'Pipfile', packages: ['flask', 'Flask'] },
|
|
226
|
+
],
|
|
227
|
+
codePatterns: [/from\s+flask/, /Flask\s*\(\s*__name__\s*\)/],
|
|
198
228
|
},
|
|
199
229
|
// Gin (Go)
|
|
200
230
|
{
|
|
@@ -247,7 +277,7 @@ let PS003FrameworkDetection = (() => {
|
|
|
247
277
|
// Check dependencies
|
|
248
278
|
if (detector.dependencies) {
|
|
249
279
|
for (const dep of detector.dependencies) {
|
|
250
|
-
const depDetected = await this.checkDependency(projectRoot, dep.file, dep.packages);
|
|
280
|
+
const depDetected = await this.checkDependency(projectRoot, dep.file, dep.packages, context.files);
|
|
251
281
|
if (depDetected) {
|
|
252
282
|
matches.push({
|
|
253
283
|
type: enums_1.MatchType.CONFIG,
|
|
@@ -289,10 +319,26 @@ let PS003FrameworkDetection = (() => {
|
|
|
289
319
|
score,
|
|
290
320
|
};
|
|
291
321
|
}
|
|
292
|
-
async checkDependency(projectRoot, depFile, packages) {
|
|
293
|
-
// Handle glob patterns
|
|
322
|
+
async checkDependency(projectRoot, depFile, packages, files) {
|
|
323
|
+
// Handle glob patterns by searching matching files
|
|
294
324
|
if (depFile.includes('*')) {
|
|
295
|
-
|
|
325
|
+
if (!files)
|
|
326
|
+
return false;
|
|
327
|
+
const globRegex = new RegExp(`${depFile.replace(/\./g, '\\.').replace(/\*/g, '.*')}$`, 'i');
|
|
328
|
+
const matchingFiles = files.filter((f) => globRegex.test(f.fileName));
|
|
329
|
+
for (const file of matchingFiles) {
|
|
330
|
+
try {
|
|
331
|
+
const content = await file_scanner_1.FileScanner.readFileContent(file.absolutePath);
|
|
332
|
+
for (const pkg of packages) {
|
|
333
|
+
if (content.includes(pkg)) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// Skip unreadable files
|
|
340
|
+
}
|
|
341
|
+
}
|
|
296
342
|
return false;
|
|
297
343
|
}
|
|
298
344
|
const filePath = `${projectRoot}/${depFile}`;
|
package/engines/impact/cli.js
CHANGED
|
@@ -50,57 +50,72 @@ DESCRIPTION:
|
|
|
50
50
|
components in TypeScript/JavaScript projects.
|
|
51
51
|
|
|
52
52
|
OPTIONS:
|
|
53
|
-
--input=<path> Source directory to analyze
|
|
54
|
-
Default: src
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
Input & Source:
|
|
55
|
+
--input=<path> Source directory to analyze (default: src)
|
|
56
|
+
|
|
57
|
+
Git References:
|
|
58
|
+
--base=<ref> Base git reference (default: HEAD~1)
|
|
59
|
+
--head=<ref> Head git reference (default: current working dir)
|
|
60
|
+
|
|
61
|
+
Exclusions:
|
|
62
|
+
--exclude=<paths> Comma-separated paths to exclude
|
|
63
|
+
Example: --exclude=test,examples,mocks
|
|
64
|
+
--use-gitignore Use .gitignore patterns for exclusions (recommended)
|
|
65
|
+
This automatically excludes files ignored by git
|
|
66
|
+
|
|
67
|
+
Analysis Options:
|
|
68
|
+
--max-depth=<n> Maximum call chain depth (default: 3)
|
|
69
|
+
--include-tests Include test files in analysis
|
|
70
|
+
--verbose Show detailed analysis output
|
|
71
|
+
|
|
72
|
+
Output:
|
|
73
|
+
--format=<type> Output format: markdown, json, csv (default: markdown)
|
|
74
|
+
--output=<file> Output file path (default: impact-report.md)
|
|
75
|
+
|
|
76
|
+
Other:
|
|
77
|
+
--help Show this help message
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
Default: (working directory)
|
|
62
|
-
|
|
63
|
-
--exclude=<paths> Comma-separated paths to exclude
|
|
64
|
-
Default: node_modules,dist,build,specs,coverage
|
|
65
|
-
|
|
66
|
-
--output=<file> Output markdown report file
|
|
67
|
-
Default: impact-report.md
|
|
68
|
-
|
|
69
|
-
--json=<file> Output JSON report file (optional)
|
|
70
|
-
Example: --json=impact-report.json
|
|
71
|
-
|
|
72
|
-
--max-depth=<n> Maximum call graph depth
|
|
73
|
-
Default: 3
|
|
74
|
-
|
|
75
|
-
--include-tests Include test files in analysis
|
|
79
|
+
EXAMPLES:
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
Basic usage (analyze changes from last commit):
|
|
82
|
+
$ impact-analyzer --input=src --verbose
|
|
78
83
|
|
|
79
|
-
|
|
84
|
+
Use .gitignore patterns (recommended for large projects):
|
|
85
|
+
$ impact-analyzer --input=src --use-gitignore --verbose
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
Analyze specific git range:
|
|
88
|
+
$ impact-analyzer --base=main --head=feature-branch
|
|
82
89
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
impact-analyzer
|
|
90
|
+
Custom exclusions:
|
|
91
|
+
$ impact-analyzer --exclude=test,examples,fixtures
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
Include tests in analysis:
|
|
94
|
+
$ impact-analyzer --include-tests
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
Generate JSON report:
|
|
97
|
+
$ impact-analyzer --format=json --output=impact.json
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
impact-analyzer --base=HEAD~3 --verbose
|
|
99
|
+
PERFORMANCE TIPS:
|
|
95
100
|
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
⚡ Use --use-gitignore for automatic exclusion of unnecessary files
|
|
102
|
+
This can reduce analysis time by 60-90% on large projects!
|
|
103
|
+
|
|
104
|
+
⚡ Exclude test/example directories if not needed:
|
|
105
|
+
--exclude=test,tests,examples,samples,mocks
|
|
106
|
+
|
|
107
|
+
⚡ For large codebases, limit max-depth:
|
|
108
|
+
--max-depth=2
|
|
98
109
|
|
|
99
|
-
|
|
100
|
-
Architecture: .specify/plans/architecture.md
|
|
101
|
-
Coding Rules: .github/copilot-instructions.md
|
|
110
|
+
DEFAULT EXCLUSIONS (when not using --use-gitignore):
|
|
102
111
|
|
|
103
|
-
|
|
112
|
+
• node_modules, dist, build, out
|
|
113
|
+
• test files: *.test.ts, *.spec.ts
|
|
114
|
+
• examples, samples, fixtures, mocks
|
|
115
|
+
• coverage, reports
|
|
116
|
+
• .cache, .temp, .history
|
|
117
|
+
• IDE: .idea, .vscode
|
|
118
|
+
• Type definitions: *.d.ts
|
|
104
119
|
`);
|
|
105
120
|
}
|
|
106
121
|
}
|
|
@@ -3,15 +3,49 @@
|
|
|
3
3
|
* Loads and validates configuration from CLI args and defaults
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { GitignoreParser, gitignoreToExcludePaths } from '../core/utils/gitignore-parser.js';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
6
9
|
export function loadConfig(cli) {
|
|
10
|
+
const sourceDir = cli.getArg('input', 'src');
|
|
11
|
+
const rootDir = path.resolve(sourceDir, '..');
|
|
12
|
+
|
|
13
|
+
// Load patterns from .gitignore
|
|
14
|
+
const gitignorePatterns = cli.hasArg('use-gitignore')
|
|
15
|
+
? gitignoreToExcludePaths(rootDir)
|
|
16
|
+
: [];
|
|
17
|
+
|
|
18
|
+
// Custom exclude paths from CLI
|
|
19
|
+
const customExcludes = cli.getArg('exclude', '')
|
|
20
|
+
.split(',')
|
|
21
|
+
.map(p => p.trim())
|
|
22
|
+
.filter(p => p);
|
|
23
|
+
|
|
24
|
+
// Merge: gitignore + custom + defaults
|
|
25
|
+
const allExcludes = [
|
|
26
|
+
...new Set([
|
|
27
|
+
...gitignorePatterns,
|
|
28
|
+
...customExcludes,
|
|
29
|
+
...DEFAULT_EXCLUDE_PATHS,
|
|
30
|
+
])
|
|
31
|
+
];
|
|
32
|
+
|
|
7
33
|
const config = {
|
|
8
34
|
// Source directory to analyze
|
|
9
|
-
sourceDir:
|
|
35
|
+
sourceDir: sourceDir,
|
|
36
|
+
rootDir: rootDir,
|
|
10
37
|
|
|
11
38
|
// Paths to exclude from analysis
|
|
12
|
-
excludePaths:
|
|
13
|
-
|
|
14
|
-
|
|
39
|
+
excludePaths: allExcludes,
|
|
40
|
+
|
|
41
|
+
// Gitignore parser for advanced pattern matching
|
|
42
|
+
gitignoreParser: cli.hasArg('use-gitignore')
|
|
43
|
+
? (() => {
|
|
44
|
+
const parser = new GitignoreParser(rootDir);
|
|
45
|
+
parser.loadGitignore();
|
|
46
|
+
return parser;
|
|
47
|
+
})()
|
|
48
|
+
: null,
|
|
15
49
|
|
|
16
50
|
// Git references - defaults to HEAD~1 (previous commit)
|
|
17
51
|
baseRef: cli.getArg('base', 'HEAD~1'),
|
|
@@ -41,9 +75,75 @@ function validateConfig(config) {
|
|
|
41
75
|
// baseRef and headRef are optional, have sensible defaults
|
|
42
76
|
}
|
|
43
77
|
|
|
78
|
+
// Default exclude paths - comprehensive list
|
|
79
|
+
const DEFAULT_EXCLUDE_PATHS = [
|
|
80
|
+
// Build & Dependencies
|
|
81
|
+
'node_modules',
|
|
82
|
+
'dist',
|
|
83
|
+
'build',
|
|
84
|
+
'out',
|
|
85
|
+
'.turbo',
|
|
86
|
+
'.next',
|
|
87
|
+
|
|
88
|
+
// Tests (unless --include-tests)
|
|
89
|
+
'__tests__',
|
|
90
|
+
'test',
|
|
91
|
+
'tests',
|
|
92
|
+
'.test.ts',
|
|
93
|
+
'.spec.ts',
|
|
94
|
+
'.test.js',
|
|
95
|
+
'.spec.js',
|
|
96
|
+
'.test.tsx',
|
|
97
|
+
'.spec.tsx',
|
|
98
|
+
|
|
99
|
+
// Examples & Samples
|
|
100
|
+
'examples',
|
|
101
|
+
'samples',
|
|
102
|
+
'fixtures',
|
|
103
|
+
'mocks',
|
|
104
|
+
'__mocks__',
|
|
105
|
+
'project-samples',
|
|
106
|
+
|
|
107
|
+
// Coverage & Reports
|
|
108
|
+
'coverage',
|
|
109
|
+
'reports',
|
|
110
|
+
'.nyc_output',
|
|
111
|
+
|
|
112
|
+
// Cache & Temp
|
|
113
|
+
'.cache',
|
|
114
|
+
'.temp',
|
|
115
|
+
'.tmp',
|
|
116
|
+
'.history',
|
|
117
|
+
|
|
118
|
+
// Documentation Sites
|
|
119
|
+
'pages/_site',
|
|
120
|
+
'_site',
|
|
121
|
+
'.jekyll-cache',
|
|
122
|
+
'.jekyll-metadata',
|
|
123
|
+
'vendor',
|
|
124
|
+
'.bundle',
|
|
125
|
+
|
|
126
|
+
// IDE & Tools
|
|
127
|
+
'.idea',
|
|
128
|
+
'.vscode',
|
|
129
|
+
'.github',
|
|
130
|
+
'.agent',
|
|
131
|
+
'.claude',
|
|
132
|
+
'.serena',
|
|
133
|
+
|
|
134
|
+
// Config-specific
|
|
135
|
+
'specs',
|
|
136
|
+
'.sunlint-cache',
|
|
137
|
+
'origin-rules',
|
|
138
|
+
'arch-detect',
|
|
139
|
+
|
|
140
|
+
// Type definitions (no runtime code to analyze)
|
|
141
|
+
'.d.ts',
|
|
142
|
+
];
|
|
143
|
+
|
|
44
144
|
export const DEFAULT_CONFIG = {
|
|
45
145
|
sourceDir: 'src',
|
|
46
|
-
excludePaths:
|
|
146
|
+
excludePaths: DEFAULT_EXCLUDE_PATHS,
|
|
47
147
|
baseRef: 'HEAD~1', // Previous commit
|
|
48
148
|
headRef: 'HEAD', // Current commit
|
|
49
149
|
maxDepth: 3,
|
|
@@ -20,7 +20,8 @@ export class ImpactAnalyzer {
|
|
|
20
20
|
await this.methodCallGraph.initialize(
|
|
21
21
|
this.absoluteSourceDir,
|
|
22
22
|
this.config.excludePaths,
|
|
23
|
-
this.config.verbose
|
|
23
|
+
this.config.verbose,
|
|
24
|
+
this.config.gitignoreParser || null
|
|
24
25
|
);
|
|
25
26
|
|
|
26
27
|
this.endpointDetector = new EndpointDetector(this.methodCallGraph, this.config);
|
|
@@ -69,28 +70,24 @@ export class ImpactAnalyzer {
|
|
|
69
70
|
|
|
70
71
|
detectLogicImpact(changedSymbols) {
|
|
71
72
|
const directCallers = new Set();
|
|
72
|
-
const
|
|
73
|
+
const allCallers = new Set();
|
|
73
74
|
|
|
74
75
|
for (const symbol of changedSymbols) {
|
|
75
76
|
const methodName = `${symbol.className || 'global'}.${symbol.name}`;
|
|
76
|
-
const callers = this.methodCallGraph.findAllCallers(methodName);
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!callers.includes(ic)) {
|
|
84
|
-
indirectCallers.add(ic);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
});
|
|
78
|
+
const immediateCallers = this.methodCallGraph.getCallers(methodName);
|
|
79
|
+
immediateCallers.forEach(caller => directCallers.add(caller));
|
|
80
|
+
|
|
81
|
+
const recursiveCallers = this.methodCallGraph.findAllCallers(methodName);
|
|
82
|
+
recursiveCallers.forEach(caller => allCallers.add(caller));
|
|
88
83
|
}
|
|
89
84
|
|
|
85
|
+
const indirectCallers = Array.from(allCallers).filter(caller => !directCallers.has(caller));
|
|
86
|
+
|
|
90
87
|
return {
|
|
91
88
|
directCallers: Array.from(directCallers),
|
|
92
|
-
indirectCallers:
|
|
93
|
-
riskLevel: this.calculateRiskLevel(directCallers.size, indirectCallers.
|
|
89
|
+
indirectCallers: indirectCallers,
|
|
90
|
+
riskLevel: this.calculateRiskLevel(directCallers.size, indirectCallers.length),
|
|
94
91
|
};
|
|
95
92
|
}
|
|
96
93
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gitignore Parser
|
|
3
|
+
* Reads .gitignore files and converts patterns to exclude paths
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { minimatch } from 'minimatch';
|
|
9
|
+
|
|
10
|
+
export class GitignoreParser {
|
|
11
|
+
constructor(rootDir) {
|
|
12
|
+
this.rootDir = rootDir;
|
|
13
|
+
this.patterns = [];
|
|
14
|
+
this.negationPatterns = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load patterns from .gitignore file
|
|
19
|
+
*/
|
|
20
|
+
loadGitignore(gitignorePath = null) {
|
|
21
|
+
const ignoreFile = gitignorePath || path.join(this.rootDir, '.gitignore');
|
|
22
|
+
|
|
23
|
+
if (!fs.existsSync(ignoreFile)) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const content = fs.readFileSync(ignoreFile, 'utf8');
|
|
28
|
+
const lines = content.split('\n');
|
|
29
|
+
|
|
30
|
+
const patterns = [];
|
|
31
|
+
|
|
32
|
+
for (const line of lines) {
|
|
33
|
+
const trimmed = line.trim();
|
|
34
|
+
|
|
35
|
+
// Skip empty lines and comments
|
|
36
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
37
|
+
|
|
38
|
+
// Handle negation patterns (!)
|
|
39
|
+
if (trimmed.startsWith('!')) {
|
|
40
|
+
this.negationPatterns.push(trimmed.substring(1));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
patterns.push(trimmed);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.patterns = patterns;
|
|
48
|
+
return patterns;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a file path should be excluded based on gitignore patterns
|
|
53
|
+
*/
|
|
54
|
+
shouldExclude(filePath) {
|
|
55
|
+
// Normalize path separators
|
|
56
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
57
|
+
|
|
58
|
+
// Check if path matches any pattern
|
|
59
|
+
for (const pattern of this.patterns) {
|
|
60
|
+
if (this.matchesPattern(normalizedPath, pattern)) {
|
|
61
|
+
// Check if there's a negation pattern that overrides
|
|
62
|
+
const isNegated = this.negationPatterns.some(negPattern =>
|
|
63
|
+
this.matchesPattern(normalizedPath, negPattern)
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (!isNegated) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Match a file path against a gitignore pattern
|
|
77
|
+
*/
|
|
78
|
+
matchesPattern(filePath, pattern) {
|
|
79
|
+
// Handle directory-only patterns (ending with /)
|
|
80
|
+
if (pattern.endsWith('/')) {
|
|
81
|
+
const dirPattern = pattern.slice(0, -1);
|
|
82
|
+
return filePath.includes(`/${dirPattern}/`) || filePath.includes(`${dirPattern}/`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Handle patterns starting with /
|
|
86
|
+
if (pattern.startsWith('/')) {
|
|
87
|
+
return minimatch(filePath, pattern.substring(1), { matchBase: true });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Handle wildcards and glob patterns
|
|
91
|
+
if (pattern.includes('*') || pattern.includes('?') || pattern.includes('[')) {
|
|
92
|
+
return minimatch(filePath, pattern, { matchBase: true, dot: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Simple substring match for paths
|
|
96
|
+
return filePath.includes(pattern) ||
|
|
97
|
+
filePath.includes(`/${pattern}/`) ||
|
|
98
|
+
filePath.endsWith(`/${pattern}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get all patterns
|
|
103
|
+
*/
|
|
104
|
+
getPatterns() {
|
|
105
|
+
return this.patterns;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Convert gitignore patterns to simple exclude path list
|
|
111
|
+
* for backward compatibility
|
|
112
|
+
*/
|
|
113
|
+
export function gitignoreToExcludePaths(rootDir) {
|
|
114
|
+
const parser = new GitignoreParser(rootDir);
|
|
115
|
+
parser.loadGitignore();
|
|
116
|
+
|
|
117
|
+
// Extract simple path patterns (non-glob)
|
|
118
|
+
const simplePaths = parser.getPatterns().filter(p =>
|
|
119
|
+
!p.includes('*') && !p.includes('?') && !p.includes('[')
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return simplePaths.map(p => p.replace(/^\//, '').replace(/\/$/, ''));
|
|
123
|
+
}
|