@sun-asterisk/sunlint 1.3.47 → 1.3.48
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/output-service.js +12 -3
- package/core/scoring-service.js +12 -6
- package/core/summary-report-service.js +9 -4
- 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/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/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
|
+
}
|