@sun-asterisk/sunlint 1.3.34 â 1.3.35
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/core/architecture-integration.js +16 -7
- package/core/auto-performance-manager.js +1 -1
- package/core/cli-action-handler.js +92 -2
- package/core/cli-program.js +96 -138
- package/core/file-targeting-service.js +62 -4
- package/core/git-utils.js +19 -12
- package/core/github-annotate-service.js +326 -11
- package/core/html-report-generator.js +326 -731
- package/core/impact-integration.js +433 -0
- package/core/output-service.js +293 -21
- package/core/scoring-service.js +3 -2
- package/engines/arch-detect/core/analyzer.js +413 -0
- package/engines/arch-detect/core/index.js +22 -0
- package/engines/arch-detect/engine/hybrid-detector.js +176 -0
- package/engines/arch-detect/engine/index.js +24 -0
- package/engines/arch-detect/engine/rule-executor.js +228 -0
- package/engines/arch-detect/engine/score-calculator.js +214 -0
- package/engines/arch-detect/engine/violation-detector.js +616 -0
- package/engines/arch-detect/index.js +50 -0
- package/engines/arch-detect/rules/base-rule.js +187 -0
- package/engines/arch-detect/rules/index.js +35 -0
- package/engines/arch-detect/rules/layered/index.js +28 -0
- package/engines/arch-detect/rules/layered/l001-presentation-layer.js +237 -0
- package/engines/arch-detect/rules/layered/l002-business-layer.js +215 -0
- package/engines/arch-detect/rules/layered/l003-data-layer.js +229 -0
- package/engines/arch-detect/rules/layered/l004-model-layer.js +204 -0
- package/engines/arch-detect/rules/layered/l005-layer-separation.js +215 -0
- package/engines/arch-detect/rules/layered/l006-dependency-direction.js +221 -0
- package/engines/arch-detect/rules/layered/layered-rules-collection.js +445 -0
- package/engines/arch-detect/rules/modular/index.js +27 -0
- package/engines/arch-detect/rules/modular/m001-feature-modules.js +238 -0
- package/engines/arch-detect/rules/modular/m002-core-module.js +169 -0
- package/engines/arch-detect/rules/modular/m003-module-declaration.js +186 -0
- package/engines/arch-detect/rules/modular/m004-public-api.js +171 -0
- package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +220 -0
- package/engines/arch-detect/rules/modular/modular-rules-collection.js +357 -0
- package/engines/arch-detect/rules/presentation/index.js +27 -0
- package/engines/arch-detect/rules/presentation/pr001-view-layer.js +221 -0
- package/engines/arch-detect/rules/presentation/pr002-presentation-logic.js +192 -0
- package/engines/arch-detect/rules/presentation/pr004-data-binding.js +187 -0
- package/engines/arch-detect/rules/presentation/pr006-router-layer.js +185 -0
- package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +181 -0
- package/engines/arch-detect/rules/presentation/presentation-rules-collection.js +507 -0
- package/engines/arch-detect/rules/project-scanner/index.js +31 -0
- package/engines/arch-detect/rules/project-scanner/ps001-project-root.js +213 -0
- package/engines/arch-detect/rules/project-scanner/ps002-language-detection.js +192 -0
- package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +339 -0
- package/engines/arch-detect/rules/project-scanner/ps004-build-system.js +171 -0
- package/engines/arch-detect/rules/project-scanner/ps005-source-directory.js +163 -0
- package/engines/arch-detect/rules/project-scanner/ps006-test-directory.js +184 -0
- package/engines/arch-detect/rules/project-scanner/ps007-documentation.js +149 -0
- package/engines/arch-detect/rules/project-scanner/ps008-cicd-detection.js +163 -0
- package/engines/arch-detect/rules/project-scanner/ps009-code-quality.js +152 -0
- package/engines/arch-detect/rules/project-scanner/ps010-statistics.js +180 -0
- package/engines/arch-detect/rules/rule-registry.js +111 -0
- package/engines/arch-detect/types/context.types.js +60 -0
- package/engines/arch-detect/types/enums.js +161 -0
- package/engines/arch-detect/types/index.js +25 -0
- package/engines/arch-detect/types/result.types.js +7 -0
- package/engines/arch-detect/types/rule.types.js +7 -0
- package/engines/arch-detect/utils/file-scanner.js +411 -0
- package/engines/arch-detect/utils/index.js +23 -0
- package/engines/arch-detect/utils/pattern-matcher.js +328 -0
- package/engines/impact/cli.js +106 -0
- package/engines/impact/config/default-config.js +54 -0
- package/engines/impact/core/change-detector.js +258 -0
- package/engines/impact/core/detectors/database-detector.js +1317 -0
- package/engines/impact/core/detectors/endpoint-detector.js +55 -0
- package/engines/impact/core/impact-analyzer.js +124 -0
- package/engines/impact/core/report-generator.js +462 -0
- package/engines/impact/core/utils/ast-parser.js +241 -0
- package/engines/impact/core/utils/dependency-graph.js +159 -0
- package/engines/impact/core/utils/file-utils.js +116 -0
- package/engines/impact/core/utils/git-utils.js +203 -0
- package/engines/impact/core/utils/logger.js +13 -0
- package/engines/impact/core/utils/method-call-graph.js +1192 -0
- package/engines/impact/index.js +135 -0
- package/engines/impact/package.json +29 -0
- package/package.json +18 -43
- package/scripts/build-release.sh +0 -0
- package/scripts/copy-impact-analyzer.js +135 -0
- package/scripts/install.sh +0 -0
- package/scripts/manual-release.sh +0 -0
- package/scripts/pre-release-test.sh +0 -0
- package/scripts/prepare-release.sh +0 -0
- package/scripts/quick-performance-test.js +0 -0
- package/scripts/setup-github-registry.sh +0 -0
- package/scripts/trigger-release.sh +0 -0
- package/scripts/verify-install.sh +0 -0
- package/templates/combined-report.html +1418 -0
|
@@ -16,16 +16,15 @@ class ArchitectureIntegration {
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Load architecture detection module
|
|
19
|
-
* Tries bundled
|
|
19
|
+
* Tries bundled (engines/) first, then npm package, then local dev paths
|
|
20
20
|
*/
|
|
21
21
|
async loadArchitectureModule() {
|
|
22
22
|
if (this.archModule) {
|
|
23
23
|
return this.archModule;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
// Try bundled version
|
|
26
|
+
// Priority 1: Try bundled version (engines/arch-detect) - for published package
|
|
27
27
|
const bundledPath = path.join(__dirname, '..', 'engines', 'arch-detect', 'index.js');
|
|
28
|
-
|
|
29
28
|
if (fs.existsSync(bundledPath)) {
|
|
30
29
|
try {
|
|
31
30
|
this.archModule = require(bundledPath);
|
|
@@ -35,12 +34,23 @@ class ArchitectureIntegration {
|
|
|
35
34
|
return this.archModule;
|
|
36
35
|
} catch (error) {
|
|
37
36
|
if (this.options.verbose) {
|
|
38
|
-
console.log(chalk.yellow(`â ī¸ Failed to load bundled
|
|
37
|
+
console.log(chalk.yellow(`â ī¸ Failed to load bundled: ${error.message}`));
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
//
|
|
42
|
+
// Priority 2: Try npm package (workspace link) - for local development
|
|
43
|
+
try {
|
|
44
|
+
this.archModule = require('@sunlint/architecture-detection');
|
|
45
|
+
if (this.options.verbose) {
|
|
46
|
+
console.log(chalk.gray('đĻ Loaded @sunlint/architecture-detection package'));
|
|
47
|
+
}
|
|
48
|
+
return this.archModule;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
// Package not found, continue to fallback
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Priority 3: Try local development paths
|
|
44
54
|
const devPaths = [
|
|
45
55
|
path.join(__dirname, '..', '..', '..', '..', 'architecture-detection', 'dist', 'index.js'),
|
|
46
56
|
path.join(__dirname, '..', '..', '..', 'architecture-detection', 'dist', 'index.js'),
|
|
@@ -63,8 +73,7 @@ class ArchitectureIntegration {
|
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
throw new Error(
|
|
66
|
-
'Architecture detection module not found. Run "npm run build"
|
|
67
|
-
'or ensure architecture-detection is built in the parent directory.'
|
|
76
|
+
'Architecture detection module not found. Run "npm run build" in sunlint directory.'
|
|
68
77
|
);
|
|
69
78
|
}
|
|
70
79
|
|
|
@@ -76,7 +76,7 @@ class AutoPerformanceManager {
|
|
|
76
76
|
analyzeProject(options, targetFiles) {
|
|
77
77
|
const fileCount = targetFiles.length;
|
|
78
78
|
const inputPath = options.input || process.cwd();
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
// Estimate project complexity
|
|
81
81
|
const hasNodeModules = fs.existsSync(path.join(inputPath, 'node_modules'));
|
|
82
82
|
const hasPackageJson = fs.existsSync(path.join(inputPath, 'package.json'));
|
|
@@ -15,6 +15,7 @@ const OutputService = require('./output-service');
|
|
|
15
15
|
const GitUtils = require('./git-utils');
|
|
16
16
|
const FileTargetingService = require('./file-targeting-service');
|
|
17
17
|
const { ArchitectureIntegration } = require('./architecture-integration');
|
|
18
|
+
const { ImpactIntegration } = require('./impact-integration');
|
|
18
19
|
|
|
19
20
|
// Legacy orchestrator for fallback
|
|
20
21
|
// const LegacyOrchestrator = require('./legacy-analysis-orchestrator'); // Removed
|
|
@@ -83,8 +84,8 @@ class CliActionHandler {
|
|
|
83
84
|
const startTime = Date.now();
|
|
84
85
|
let results = null;
|
|
85
86
|
|
|
86
|
-
// Run code quality analysis (unless --architecture is used alone)
|
|
87
|
-
if (rulesToRun.length > 0 && !this.isArchitectureOnly()) {
|
|
87
|
+
// Run code quality analysis (unless --architecture or --impact is used alone)
|
|
88
|
+
if (rulesToRun.length > 0 && !this.isArchitectureOnly() && !this.isImpactOnly()) {
|
|
88
89
|
results = await this.runModernAnalysis(rulesToRun, targetingResult.files, config);
|
|
89
90
|
} else {
|
|
90
91
|
results = { results: [], summary: { total: 0, errors: 0, warnings: 0 } };
|
|
@@ -96,6 +97,12 @@ class CliActionHandler {
|
|
|
96
97
|
results.architecture = architectureResults;
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
// Run impact analysis if requested
|
|
101
|
+
if (this.options.impact) {
|
|
102
|
+
const impactResults = await this.runImpactAnalysis();
|
|
103
|
+
results.impact = impactResults;
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
const duration = Date.now() - startTime;
|
|
100
107
|
|
|
101
108
|
// Output results
|
|
@@ -515,6 +522,22 @@ class CliActionHandler {
|
|
|
515
522
|
*/
|
|
516
523
|
isArchitectureOnly() {
|
|
517
524
|
return this.options.architecture &&
|
|
525
|
+
!this.options.impact &&
|
|
526
|
+
!this.options.all &&
|
|
527
|
+
!this.options.rule &&
|
|
528
|
+
!this.options.rules &&
|
|
529
|
+
!this.options.quality &&
|
|
530
|
+
!this.options.security &&
|
|
531
|
+
!this.options.category;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Check if only impact analysis was requested (no code quality rules)
|
|
536
|
+
* Following Rule C006: Verb-noun naming
|
|
537
|
+
*/
|
|
538
|
+
isImpactOnly() {
|
|
539
|
+
return this.options.impact &&
|
|
540
|
+
!this.options.architecture &&
|
|
518
541
|
!this.options.all &&
|
|
519
542
|
!this.options.rule &&
|
|
520
543
|
!this.options.rules &&
|
|
@@ -554,6 +577,73 @@ class CliActionHandler {
|
|
|
554
577
|
return null;
|
|
555
578
|
}
|
|
556
579
|
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Run impact analysis using impact-analyzer module
|
|
583
|
+
* Following Rule C006: Verb-noun naming
|
|
584
|
+
*/
|
|
585
|
+
async runImpactAnalysis() {
|
|
586
|
+
if (!this.options.quiet) {
|
|
587
|
+
console.log(chalk.blue('\nđ Running impact analysis...'));
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
const integration = new ImpactIntegration({
|
|
592
|
+
...this.options,
|
|
593
|
+
impactBase: this.options.impactBase || 'HEAD~1',
|
|
594
|
+
impactReport: this.options.impactReport,
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
const projectPath = this.getProjectPath();
|
|
598
|
+
const results = await integration.analyze(projectPath);
|
|
599
|
+
|
|
600
|
+
// Display summary
|
|
601
|
+
if (!this.options.quiet && results.summary) {
|
|
602
|
+
const { summary } = results;
|
|
603
|
+
const severityColors = {
|
|
604
|
+
critical: chalk.red,
|
|
605
|
+
high: chalk.yellow,
|
|
606
|
+
medium: chalk.cyan,
|
|
607
|
+
low: chalk.green,
|
|
608
|
+
none: chalk.gray,
|
|
609
|
+
};
|
|
610
|
+
const colorFn = severityColors[summary.severity] || chalk.white;
|
|
611
|
+
|
|
612
|
+
console.log(chalk.gray(`\n Base: ${summary.baseRef}`));
|
|
613
|
+
console.log(chalk.gray(` Changes: ${summary.totalChanges} files`));
|
|
614
|
+
console.log(` Impact: ${colorFn(summary.impactScore + '/100')} (${colorFn(summary.severity.toUpperCase())})`);
|
|
615
|
+
|
|
616
|
+
if (summary.categories) {
|
|
617
|
+
const cats = Object.entries(summary.categories)
|
|
618
|
+
.filter(([, count]) => count > 0)
|
|
619
|
+
.map(([cat, count]) => `${cat}:${count}`)
|
|
620
|
+
.join(' ¡ ');
|
|
621
|
+
if (cats) {
|
|
622
|
+
console.log(chalk.gray(` Categories: ${cats}`));
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Save markdown report if requested
|
|
628
|
+
if (this.options.impactReport && results.markdownReport) {
|
|
629
|
+
const reportPath = await integration.saveReport(
|
|
630
|
+
results.markdownReport,
|
|
631
|
+
this.options.impactReport
|
|
632
|
+
);
|
|
633
|
+
if (!this.options.quiet) {
|
|
634
|
+
console.log(chalk.green(`\nđ Impact report saved: ${reportPath}`));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return results;
|
|
639
|
+
} catch (error) {
|
|
640
|
+
console.error(chalk.yellow(`â ī¸ Impact analysis failed: ${error.message}`));
|
|
641
|
+
if (this.options.verbose || this.options.debug) {
|
|
642
|
+
console.error(error.stack);
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
557
647
|
}
|
|
558
648
|
|
|
559
649
|
module.exports = CliActionHandler;
|
package/core/cli-program.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI Program Definition
|
|
3
|
-
*
|
|
3
|
+
* Defines all CLI options and help text for SunLint
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { Command } = require('commander');
|
|
@@ -11,169 +11,127 @@ function createCliProgram() {
|
|
|
11
11
|
|
|
12
12
|
program
|
|
13
13
|
.name('sunlint')
|
|
14
|
-
.description('âī¸
|
|
14
|
+
.description('âī¸ SunLint - Multi-language Static Analysis Tool | Code Quality & Security')
|
|
15
15
|
.version(version);
|
|
16
16
|
|
|
17
|
-
//
|
|
17
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
18
|
+
// Rule Selection
|
|
19
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
18
20
|
program
|
|
19
21
|
.option('-r, --rule <rule>', 'Run single rule (e.g., C019)')
|
|
20
|
-
.option('--rules <rules>', 'Run multiple rules (comma-separated
|
|
22
|
+
.option('--rules <rules>', 'Run multiple rules (comma-separated)')
|
|
21
23
|
.option('-a, --all', 'Run all available rules')
|
|
22
|
-
.option('-c, --category <category>', 'Run rules by category (quality,security,logging,naming)')
|
|
24
|
+
.option('-c, --category <category>', 'Run rules by category (quality, security, logging, naming)')
|
|
23
25
|
.option('--quality', 'Run all code quality rules')
|
|
24
26
|
.option('--security', 'Run all secure coding rules');
|
|
25
27
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
.option('--arch-patterns <patterns>', 'Target specific architecture patterns (comma-separated)')
|
|
30
|
-
.option('--arch-report', 'Generate separate architecture MD report');
|
|
31
|
-
|
|
32
|
-
// TypeScript specific options (Phase 1 focus)
|
|
33
|
-
program
|
|
34
|
-
.option('--typescript', 'Enable TypeScript-specific analysis')
|
|
35
|
-
.option('--typescript-engine <engine>', 'TypeScript analysis engine (eslint,heuristic,hybrid)', 'heuristic')
|
|
36
|
-
.option('--ensure-deps', 'Ensure ESLint dependencies are installed');
|
|
37
|
-
|
|
38
|
-
// Input/Output options (v1.x: explicit --input required)
|
|
28
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
29
|
+
// Input/Output
|
|
30
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
39
31
|
program
|
|
40
32
|
.option('-i, --input <path>', 'Input file or directory to analyze (REQUIRED)')
|
|
41
|
-
.option('-f, --format <format>', 'Output format
|
|
42
|
-
.option('-o, --output <file>', '
|
|
43
|
-
.option('--output-summary <file>', '
|
|
44
|
-
.option('--
|
|
45
|
-
.option('--config <file>', 'Configuration file path
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// File
|
|
33
|
+
.option('-f, --format <format>', 'Output format: eslint, json, summary, table', 'eslint')
|
|
34
|
+
.option('-o, --output <file>', 'Write output to file')
|
|
35
|
+
.option('--output-summary <file>', 'Write JSON summary for CI/CD')
|
|
36
|
+
.option('--output-html [file]', 'Generate HTML report (default: sunlint-report.html)')
|
|
37
|
+
.option('--config <file>', 'Configuration file path');
|
|
38
|
+
|
|
39
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
40
|
+
// File Targeting
|
|
41
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
49
42
|
program
|
|
50
43
|
.option('--include <patterns>', 'Include file patterns (comma-separated globs)')
|
|
51
44
|
.option('--exclude <patterns>', 'Exclude file patterns (comma-separated globs)')
|
|
52
|
-
.option('--languages <
|
|
53
|
-
.option('--include-tests', 'Include test files in analysis (default: true)')
|
|
45
|
+
.option('--languages <langs>', 'Target languages: typescript, dart, kotlin, swift, java')
|
|
54
46
|
.option('--exclude-tests', 'Exclude test files from analysis')
|
|
55
|
-
.option('--only-source', 'Only analyze source files (exclude tests, configs
|
|
47
|
+
.option('--only-source', 'Only analyze source files (exclude tests, configs)');
|
|
56
48
|
|
|
57
|
-
//
|
|
49
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
50
|
+
// Git Integration
|
|
51
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
58
52
|
program
|
|
59
|
-
.option('--changed-files', 'Only analyze
|
|
53
|
+
.option('--changed-files', 'Only analyze changed files (git diff)')
|
|
60
54
|
.option('--staged-files', 'Only analyze staged files (git diff --cached)')
|
|
61
|
-
.option('--diff-base <ref>', 'Compare against
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.option('--fail-on-new-violations', 'Exit with error only on new violations (not existing)');
|
|
67
|
-
|
|
68
|
-
// Performance options (SIMPLIFIED)
|
|
55
|
+
.option('--diff-base <ref>', 'Compare against git ref (e.g., origin/main)');
|
|
56
|
+
|
|
57
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
58
|
+
// GitHub Actions
|
|
59
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
69
60
|
program
|
|
70
|
-
.option('--
|
|
71
|
-
.option('--
|
|
72
|
-
.option('--performance <mode>', 'Performance mode: auto, fast, careful (default: auto)', 'auto');
|
|
61
|
+
.option('--github-annotate [mode]', 'GitHub PR annotations (modes: annotate, summary, all)')
|
|
62
|
+
.option('--upload-report [url]', 'Upload report to API (default: Sun* Coding Standards)');
|
|
73
63
|
|
|
74
|
-
//
|
|
64
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
65
|
+
// Analysis Options
|
|
66
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
67
|
+
program
|
|
68
|
+
.option('--typescript', 'Enable TypeScript-specific analysis')
|
|
69
|
+
.option('--architecture', 'Enable architecture pattern detection')
|
|
70
|
+
.option('--arch-patterns <patterns>', 'Target architecture patterns (comma-separated)')
|
|
71
|
+
.option('--arch-report', 'Generate architecture markdown report')
|
|
72
|
+
.option('--impact', 'Enable impact analysis (analyze code changes)')
|
|
73
|
+
.option('--impact-base <ref>', 'Base git ref for impact analysis (default: HEAD~1)')
|
|
74
|
+
.option('--impact-report <file>', 'Output impact report file (default: impact-report.md)')
|
|
75
|
+
.option('--engine <engine>', 'Analysis engine: auto, eslint, heuristic', 'auto')
|
|
76
|
+
.option('--eslint-integration', 'Merge with existing ESLint config')
|
|
77
|
+
.option('--no-eslint-integration', 'Disable ESLint integration');
|
|
78
|
+
|
|
79
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
80
|
+
// Performance
|
|
81
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
82
|
+
program
|
|
83
|
+
.option('--performance <mode>', 'Performance mode: auto, fast, careful', 'auto')
|
|
84
|
+
.option('--timeout <ms>', 'Analysis timeout in milliseconds', '0')
|
|
85
|
+
.option('--max-files <n>', 'Maximum files to analyze', '0')
|
|
86
|
+
.option('--max-semantic-files <n>', 'TypeScript symbol table limit (default: 1000)');
|
|
87
|
+
|
|
88
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
89
|
+
// Output Control
|
|
90
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
75
91
|
program
|
|
76
|
-
.option('--engine <engine>', 'Analysis engine (eslint,heuristic,auto)', 'auto')
|
|
77
|
-
.option('--dry-run', 'Show what would be analyzed without running')
|
|
78
92
|
.option('--verbose', 'Enable verbose logging')
|
|
79
93
|
.option('--quiet', 'Suppress non-error output')
|
|
80
|
-
.option('--debug', 'Enable debug mode')
|
|
81
94
|
.option('--ai', 'Enable AI-powered analysis')
|
|
82
|
-
.option('--no-ai', '
|
|
83
|
-
.option('--max-semantic-files <number>', 'Symbol table file limit for TypeScript analysis (default: 1000, -1 for unlimited)')
|
|
84
|
-
.option('--list-engines', 'List available analysis engines');
|
|
85
|
-
|
|
86
|
-
// ESLint Integration options
|
|
87
|
-
program
|
|
88
|
-
.option('--eslint-integration', 'Enable ESLint integration (merge with existing ESLint config)')
|
|
89
|
-
.option('--no-eslint-integration', 'Disable ESLint integration')
|
|
90
|
-
.option('--eslint-merge-rules', 'Merge SunLint and user ESLint rules (default: true)')
|
|
91
|
-
.option('--eslint-preserve-config', 'Preserve user ESLint configuration (default: true)')
|
|
92
|
-
.option('--eslint-run-after', 'Run ESLint after SunLint (instead of merged execution)');
|
|
95
|
+
.option('--no-ai', 'Disable AI analysis');
|
|
93
96
|
|
|
94
|
-
//
|
|
97
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
98
|
+
// Help Examples
|
|
99
|
+
// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
95
100
|
program.addHelpText('after', `
|
|
96
101
|
Examples:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
Engine Configuration:
|
|
132
|
-
$ sunlint --all --input=src # Use config engine setting
|
|
133
|
-
$ sunlint --all --input=src --engine=eslint # Force ESLint engine
|
|
134
|
-
$ sunlint --all --input=src --engine=heuristic # Force Heuristic engine
|
|
135
|
-
|
|
136
|
-
CI/CD Integration:
|
|
137
|
-
$ sunlint --all --changed-files --format=summary --no-ai
|
|
138
|
-
$ sunlint --all --changed-files --diff-base=origin/main --fail-on-new-violations
|
|
139
|
-
$ sunlint --all --staged-files --format=summary
|
|
140
|
-
$ sunlint --all --pr-mode --diff-base=origin/main
|
|
141
|
-
$ sunlint --all --output-summary=report.json --upload-report
|
|
142
|
-
$ sunlint --all --output-summary=report.json --upload-report=https://custom-api.com/reports
|
|
143
|
-
|
|
144
|
-
GitHub Actions Integration:
|
|
145
|
-
$ sunlint --all --input=src --github-annotate # Inline + summary + HTML artifact
|
|
146
|
-
$ sunlint --all --input=src --github-annotate=annotate # Inline comments only
|
|
147
|
-
$ sunlint --all --input=src --github-annotate=summary # Summary comment + HTML artifact
|
|
148
|
-
$ sunlint --all --input=src --github-annotate=all # All features (default)
|
|
149
|
-
$ sunlint --all --changed-files --github-annotate # With changed files only
|
|
150
|
-
|
|
151
|
-
ESLint Integration:
|
|
152
|
-
$ sunlint --typescript --eslint-integration --input=src
|
|
153
|
-
$ sunlint --all --eslint-integration --eslint-merge-rules --input=src
|
|
154
|
-
$ sunlint --all --eslint-integration --eslint-run-after --input=src
|
|
155
|
-
$ sunlint --typescript --eslint-integration --changed-files
|
|
156
|
-
|
|
157
|
-
Advanced File Targeting:
|
|
158
|
-
$ sunlint --all --include="src/**/*.ts,lib/**/*.dart" --exclude="**/*.generated.*" --input=.
|
|
159
|
-
$ sunlint --typescript --exclude="**/*.d.ts,**/*.test.*" --input=src
|
|
160
|
-
$ sunlint --languages=typescript,dart --include="src/**,packages/**" --input=.
|
|
161
|
-
$ sunlint --all --only-source --exclude-tests --languages=typescript --input=.
|
|
162
|
-
|
|
163
|
-
Large Project Optimization:
|
|
164
|
-
$ sunlint --all --input=. --max-semantic-files=500 # Conservative analysis
|
|
165
|
-
$ sunlint --all --input=. --max-semantic-files=2000 # Comprehensive analysis
|
|
166
|
-
$ sunlint --all --input=. --max-semantic-files=-1 # Unlimited (all files)
|
|
167
|
-
$ sunlint --all --input=. --max-semantic-files=0 # Disable semantic analysis
|
|
168
|
-
$ sunlint --all --changed-files --max-semantic-files=300 # Fast CI analysis
|
|
169
|
-
|
|
170
|
-
Architecture Analysis:
|
|
171
|
-
$ sunlint --all --architecture --input=src # Code quality + architecture
|
|
172
|
-
$ sunlint --architecture --input=src # Architecture only
|
|
173
|
-
$ sunlint --architecture --arch-report --input=src # Generate MD report
|
|
174
|
-
$ sunlint --architecture --arch-patterns=mvvm,layered --input=src
|
|
175
|
-
|
|
176
|
-
Sun* Engineering - Coding Standards Made Simple âī¸
|
|
102
|
+
sunlint --all --input=src # Analyze all rules
|
|
103
|
+
sunlint --rule=C019 --input=src # Single rule
|
|
104
|
+
sunlint --rules=C019,C006 --input=src # Multiple rules
|
|
105
|
+
sunlint --quality --input=src # Quality rules only
|
|
106
|
+
sunlint --security --input=src # Security rules only
|
|
107
|
+
|
|
108
|
+
Git Integration:
|
|
109
|
+
sunlint --all --changed-files --input=. # Changed files only
|
|
110
|
+
sunlint --all --staged-files --input=. # Staged files only
|
|
111
|
+
sunlint --all --diff-base=origin/main # Compare with main
|
|
112
|
+
|
|
113
|
+
GitHub Actions:
|
|
114
|
+
sunlint --all --github-annotate --input=src # PR annotations
|
|
115
|
+
sunlint --all --output-summary=report.json # JSON report for CI
|
|
116
|
+
|
|
117
|
+
Reports:
|
|
118
|
+
sunlint --all --output-html --input=src # Generate HTML report
|
|
119
|
+
sunlint --all --output-html=report.html # Custom HTML filename
|
|
120
|
+
|
|
121
|
+
Architecture:
|
|
122
|
+
sunlint --architecture --input=src # Detect patterns
|
|
123
|
+
sunlint --architecture --arch-report # Generate MD report
|
|
124
|
+
|
|
125
|
+
Impact Analysis:
|
|
126
|
+
sunlint --impact --input=src # Analyze code changes
|
|
127
|
+
sunlint --impact --impact-base=origin/main # Compare with main
|
|
128
|
+
sunlint --impact --impact-report=report.md # Custom output file
|
|
129
|
+
|
|
130
|
+
Performance:
|
|
131
|
+
sunlint --all --performance=fast --input=. # Quick scan
|
|
132
|
+
sunlint --all --max-files=500 --input=. # Limit files
|
|
133
|
+
|
|
134
|
+
âī¸ Sun* Engineering - Coding Standards Made Simple
|
|
177
135
|
`);
|
|
178
136
|
|
|
179
137
|
return program;
|
|
@@ -616,22 +616,80 @@ class FileTargetingService {
|
|
|
616
616
|
return files;
|
|
617
617
|
}
|
|
618
618
|
|
|
619
|
+
/**
|
|
620
|
+
* Directories to always skip during file collection (performance + safety)
|
|
621
|
+
* These are known heavy/problematic directories in Flutter/mobile projects
|
|
622
|
+
*/
|
|
623
|
+
static SKIP_DIRECTORIES = new Set([
|
|
624
|
+
'node_modules',
|
|
625
|
+
'.git',
|
|
626
|
+
'.dart_tool',
|
|
627
|
+
'build',
|
|
628
|
+
'Pods',
|
|
629
|
+
'.symlinks',
|
|
630
|
+
'.pub-cache',
|
|
631
|
+
'.gradle',
|
|
632
|
+
'DerivedData',
|
|
633
|
+
'__pycache__',
|
|
634
|
+
'.venv',
|
|
635
|
+
'vendor',
|
|
636
|
+
'.bundle'
|
|
637
|
+
]);
|
|
638
|
+
|
|
619
639
|
/**
|
|
620
640
|
* Collect files from directory recursively
|
|
621
641
|
* Rule C006: collectFilesFromDirectory - verb-noun naming
|
|
642
|
+
* Enhanced: Skip symlinks and known heavy directories to prevent stack overflow
|
|
622
643
|
*/
|
|
623
|
-
async collectFilesFromDirectory(dirPath) {
|
|
644
|
+
async collectFilesFromDirectory(dirPath, visitedPaths = new Set()) {
|
|
624
645
|
const files = [];
|
|
625
|
-
|
|
646
|
+
|
|
647
|
+
// Resolve real path to detect circular symlinks
|
|
648
|
+
let realPath;
|
|
649
|
+
try {
|
|
650
|
+
realPath = fs.realpathSync(dirPath);
|
|
651
|
+
} catch {
|
|
652
|
+
return files; // Cannot resolve path, skip
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Prevent infinite loops from circular symlinks
|
|
656
|
+
if (visitedPaths.has(realPath)) {
|
|
657
|
+
return files;
|
|
658
|
+
}
|
|
659
|
+
visitedPaths.add(realPath);
|
|
660
|
+
|
|
661
|
+
let entries;
|
|
662
|
+
try {
|
|
663
|
+
entries = fs.readdirSync(dirPath);
|
|
664
|
+
} catch {
|
|
665
|
+
return files; // Cannot read directory, skip
|
|
666
|
+
}
|
|
626
667
|
|
|
627
668
|
for (const entry of entries) {
|
|
669
|
+
// Skip known heavy directories early (before stat call)
|
|
670
|
+
if (FileTargetingService.SKIP_DIRECTORIES.has(entry)) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
|
|
628
674
|
const fullPath = path.join(dirPath, entry);
|
|
629
|
-
|
|
675
|
+
|
|
676
|
+
let stats;
|
|
677
|
+
try {
|
|
678
|
+
// Use lstatSync to detect symlinks without following them
|
|
679
|
+
stats = fs.lstatSync(fullPath);
|
|
680
|
+
} catch {
|
|
681
|
+
continue; // Cannot stat, skip this entry
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Skip symlinks to prevent circular references
|
|
685
|
+
if (stats.isSymbolicLink()) {
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
630
688
|
|
|
631
689
|
if (stats.isFile()) {
|
|
632
690
|
files.push(path.resolve(fullPath));
|
|
633
691
|
} else if (stats.isDirectory()) {
|
|
634
|
-
const subFiles = await this.collectFilesFromDirectory(fullPath);
|
|
692
|
+
const subFiles = await this.collectFilesFromDirectory(fullPath, visitedPaths);
|
|
635
693
|
files.push(...subFiles);
|
|
636
694
|
}
|
|
637
695
|
}
|
package/core/git-utils.js
CHANGED
|
@@ -186,13 +186,19 @@ class GitUtils {
|
|
|
186
186
|
try {
|
|
187
187
|
const { baseBranch, provider } = prContext;
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
// Professional output header
|
|
190
|
+
console.log('');
|
|
191
|
+
console.log('ââ Git Integration ââââââââââââââââââââââââââââââââââââ');
|
|
192
|
+
console.log(`â Provider: ${provider.toUpperCase()}`);
|
|
193
|
+
console.log(`â Base: ${baseBranch}`);
|
|
194
|
+
console.log('âââââââââââââââââââââââââââââââââââââââââââââââââââââââ');
|
|
190
195
|
|
|
191
196
|
// Try to find the base branch reference
|
|
192
197
|
let baseRef = this.findBaseRef(baseBranch, gitRoot);
|
|
193
198
|
|
|
194
199
|
if (!baseRef) {
|
|
195
|
-
console.log(
|
|
200
|
+
console.log(`â â Base ref not found locally`);
|
|
201
|
+
console.log(`â â Fetching origin/${baseBranch}...`);
|
|
196
202
|
|
|
197
203
|
// Try to fetch and create the ref
|
|
198
204
|
const fetchSuccess = this.ensureBaseRefExists(`origin/${baseBranch}`, gitRoot);
|
|
@@ -200,11 +206,12 @@ class GitUtils {
|
|
|
200
206
|
if (fetchSuccess) {
|
|
201
207
|
baseRef = `origin/${baseBranch}`;
|
|
202
208
|
} else {
|
|
209
|
+
console.log('âââââââââââââââââââââââââââââââââââââââââââââââââââââââ');
|
|
203
210
|
throw new Error(`Cannot find or fetch base branch: ${baseBranch}`);
|
|
204
211
|
}
|
|
205
212
|
} else {
|
|
206
213
|
// Ensure we have the latest
|
|
207
|
-
console.log(
|
|
214
|
+
console.log(`â â Base ref: ${baseRef}`);
|
|
208
215
|
this.ensureBaseRefExists(baseRef, gitRoot);
|
|
209
216
|
}
|
|
210
217
|
|
|
@@ -215,10 +222,10 @@ class GitUtils {
|
|
|
215
222
|
cwd: gitRoot,
|
|
216
223
|
encoding: 'utf8'
|
|
217
224
|
}).trim();
|
|
218
|
-
console.log(
|
|
225
|
+
console.log(`â â Merge-base: ${mergeBase.substring(0, 8)}`);
|
|
219
226
|
} catch (error) {
|
|
220
227
|
// If merge-base fails, fall back to direct comparison
|
|
221
|
-
console.
|
|
228
|
+
console.log(`â â Merge-base not found, using direct diff`);
|
|
222
229
|
mergeBase = baseRef;
|
|
223
230
|
}
|
|
224
231
|
|
|
@@ -232,7 +239,10 @@ class GitUtils {
|
|
|
232
239
|
.map(file => path.resolve(gitRoot, file))
|
|
233
240
|
.filter(file => fs.existsSync(file));
|
|
234
241
|
|
|
235
|
-
console.log(
|
|
242
|
+
console.log('âââââââââââââââââââââââââââââââââââââââââââââââââââââââ');
|
|
243
|
+
console.log(`â Changed files: ${changedFiles.length}`);
|
|
244
|
+
console.log('âââââââââââââââââââââââââââââââââââââââââââââââââââââââ');
|
|
245
|
+
console.log('');
|
|
236
246
|
|
|
237
247
|
return changedFiles;
|
|
238
248
|
} catch (error) {
|
|
@@ -290,13 +300,11 @@ class GitUtils {
|
|
|
290
300
|
|
|
291
301
|
if (remote === 'origin' || remote === 'upstream') {
|
|
292
302
|
try {
|
|
293
|
-
console.log(`âŦī¸ Fetching ${remote}/${branch}...`);
|
|
294
|
-
|
|
295
303
|
// Check if this is a shallow repository (common in GitHub Actions)
|
|
296
304
|
const isShallow = this.isShallowRepository(gitRoot);
|
|
297
305
|
|
|
298
306
|
if (isShallow) {
|
|
299
|
-
console.log(
|
|
307
|
+
console.log(`â â Shallow clone detected, deepening...`);
|
|
300
308
|
|
|
301
309
|
// For shallow clones, we need to:
|
|
302
310
|
// 1. Fetch the base branch
|
|
@@ -317,7 +325,6 @@ class GitUtils {
|
|
|
317
325
|
});
|
|
318
326
|
} catch (shallowError) {
|
|
319
327
|
// If deepen fails, try direct fetch
|
|
320
|
-
console.log(` âšī¸ Trying direct fetch...`);
|
|
321
328
|
execSync(`git fetch ${remote} ${branch}`, {
|
|
322
329
|
cwd: gitRoot,
|
|
323
330
|
stdio: 'pipe',
|
|
@@ -339,10 +346,10 @@ class GitUtils {
|
|
|
339
346
|
stdio: 'ignore'
|
|
340
347
|
});
|
|
341
348
|
|
|
342
|
-
console.log(
|
|
349
|
+
console.log(`â â Fetched ${baseRef}`);
|
|
343
350
|
return true;
|
|
344
351
|
} catch (fetchError) {
|
|
345
|
-
console.
|
|
352
|
+
console.log(`â â Failed to fetch: ${fetchError.message}`);
|
|
346
353
|
return false;
|
|
347
354
|
}
|
|
348
355
|
}
|