@sun-asterisk/sunlint 1.1.3 → 1.1.5
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/.sunlint.json +1 -1
- package/README.md +77 -10
- package/config/eslint-rule-mapping.json +1 -1
- package/config/rules/rules-registry.json +10 -8
- package/core/ast-modules/parsers/eslint-js-parser.js +27 -21
- package/core/ast-modules/parsers/eslint-ts-parser.js +28 -36
- package/core/dependency-checker.js +125 -0
- package/core/smart-installer.js +164 -0
- package/docs/DEPENDENCIES.md +90 -0
- package/docs/FUTURE_PACKAGES.md +83 -0
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +112 -0
- package/docs/PRODUCTION_SIZE_IMPACT.md +183 -0
- package/engines/eslint-engine.js +11 -2
- package/engines/heuristic-engine.js +4 -0
- package/package.json +33 -10
- package/integrations/eslint/test-c041-rule.js +0 -87
package/.sunlint.json
CHANGED
package/README.md
CHANGED
|
@@ -8,29 +8,30 @@ Sun Lint is a universal coding standards checker providing comprehensive code qu
|
|
|
8
8
|
|
|
9
9
|
### **✨ Key Features**
|
|
10
10
|
- ✅ **97+ Coding Rules**: Quality (30), Security (47), TypeScript-specific
|
|
11
|
-
- ✅ **
|
|
11
|
+
- ✅ **Built-in AST Analysis**: JavaScript/TypeScript parsing out of the box
|
|
12
12
|
- ✅ **Multi-Engine Architecture**: Heuristic + ESLint + OpenAI integration
|
|
13
13
|
- ✅ **Git Integration**: `--changed-files`, `--staged-files`, `--pr-mode`
|
|
14
14
|
- ✅ **TypeScript Support**: Native TypeScript 5.8+ analysis
|
|
15
|
+
- ✅ **Zero Config**: Works immediately after `npm install`
|
|
15
16
|
- ✅ **CI/CD Ready**: Baseline comparison, fail-on-new-violations
|
|
16
17
|
- ✅ **Advanced File Targeting**: Include/exclude patterns, language filtering
|
|
17
18
|
|
|
18
19
|
### **🚀 Quick Start**
|
|
19
20
|
```bash
|
|
20
|
-
# Install
|
|
21
|
+
# Install
|
|
21
22
|
npm install -g @sun-asterisk/sunlint
|
|
22
23
|
|
|
23
|
-
# Basic usage
|
|
24
|
+
# Basic usage - works immediately!
|
|
24
25
|
sunlint --all
|
|
25
26
|
sunlint --rules=C019,C006
|
|
26
27
|
|
|
27
|
-
#
|
|
28
|
+
# With input specification
|
|
28
29
|
sunlint --all --input=src
|
|
29
30
|
sunlint --rules=C019,C006 --input=src
|
|
30
31
|
sunlint --quality --input=src
|
|
31
32
|
sunlint --security --input=src
|
|
32
33
|
|
|
33
|
-
# ESLint integration (
|
|
34
|
+
# ESLint integration (requires eslint dependency)
|
|
34
35
|
sunlint --rules=C010,C006 --eslint-integration --input=src
|
|
35
36
|
|
|
36
37
|
# Git integration
|
|
@@ -48,12 +49,66 @@ sunlint --version
|
|
|
48
49
|
### **Project Installation**
|
|
49
50
|
```bash
|
|
50
51
|
npm install --save-dev @sun-asterisk/sunlint
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**✅ Works immediately** with JavaScript analysis using built-in AST parsers (`@babel/parser`, `espree`)
|
|
55
|
+
|
|
56
|
+
### **Enhanced TypeScript Support**
|
|
57
|
+
For advanced TypeScript analysis with ESLint integration:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install --save-dev @sun-asterisk/sunlint eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin typescript
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### **What's Included by Default**
|
|
64
|
+
- ✅ **JavaScript Analysis**: High-accuracy AST analysis out of the box
|
|
65
|
+
- ✅ **Basic TypeScript**: Works with built-in Babel parser
|
|
66
|
+
- ✅ **97+ Rules**: All quality and security rules available
|
|
67
|
+
- ✅ **Heuristic Engine**: Pattern-based analysis for all languages
|
|
68
|
+
|
|
69
|
+
### **Optional Dependencies (Install as needed)**
|
|
70
|
+
```bash
|
|
71
|
+
# For ESLint engine integration
|
|
72
|
+
npm install eslint --save-dev
|
|
73
|
+
|
|
74
|
+
# For enhanced TypeScript analysis
|
|
75
|
+
npm install @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
|
|
76
|
+
|
|
77
|
+
# For TypeScript compiler integration
|
|
78
|
+
npm install typescript --save-dev
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Quick setup for TypeScript projects:**
|
|
82
|
+
```bash
|
|
83
|
+
npm install --save-dev @sun-asterisk/sunlint eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin typescript
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
> 💡 **Note**: SunLint gracefully handles missing dependencies. Install only what your project needs. See [docs/DEPENDENCIES.md](docs/DEPENDENCIES.md) for detailed guidance.
|
|
51
87
|
|
|
52
88
|
# Package.json scripts
|
|
89
|
+
```json
|
|
53
90
|
{
|
|
54
91
|
"scripts": {
|
|
55
92
|
"lint": "sunlint --all --input=src",
|
|
56
|
-
"lint:changed": "sunlint --all --changed-files"
|
|
93
|
+
"lint:changed": "sunlint --all --changed-files",
|
|
94
|
+
"lint:typescript": "sunlint --all --input=src",
|
|
95
|
+
"lint:eslint-integration": "sunlint --all --eslint-integration --input=src"
|
|
96
|
+
},
|
|
97
|
+
"devDependencies": {
|
|
98
|
+
"@sun-asterisk/sunlint": "^1.2.0"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**For TypeScript projects, add:**
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"devDependencies": {
|
|
107
|
+
"@sun-asterisk/sunlint": "^1.2.0",
|
|
108
|
+
"eslint": "^8.50.0",
|
|
109
|
+
"@typescript-eslint/parser": "^7.2.0",
|
|
110
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
111
|
+
"typescript": "^5.0.0"
|
|
57
112
|
}
|
|
58
113
|
}
|
|
59
114
|
```
|
|
@@ -209,16 +264,28 @@ Complete reference with all available options:
|
|
|
209
264
|
|
|
210
265
|
### **Development**
|
|
211
266
|
```bash
|
|
212
|
-
#
|
|
213
|
-
|
|
267
|
+
# Quick start - works immediately
|
|
268
|
+
npm install --save-dev @sun-asterisk/sunlint
|
|
269
|
+
npx sunlint --all --input=src
|
|
214
270
|
|
|
215
271
|
# Check specific rules
|
|
216
272
|
sunlint --rules=C019,S005 --input=src
|
|
217
273
|
|
|
218
|
-
# ESLint
|
|
274
|
+
# ESLint integration (requires eslint dependency)
|
|
275
|
+
npm install --save-dev eslint
|
|
219
276
|
sunlint --all --eslint-integration --changed-files
|
|
220
277
|
```
|
|
221
278
|
|
|
279
|
+
### **TypeScript Projects**
|
|
280
|
+
```bash
|
|
281
|
+
# Enhanced TypeScript setup
|
|
282
|
+
npm install --save-dev @sun-asterisk/sunlint eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin typescript
|
|
283
|
+
|
|
284
|
+
# Full TypeScript analysis
|
|
285
|
+
sunlint --all --input=src
|
|
286
|
+
sunlint --all --eslint-integration --input=src
|
|
287
|
+
```
|
|
288
|
+
|
|
222
289
|
### **CI/CD**
|
|
223
290
|
```bash
|
|
224
291
|
# Full project scan
|
|
@@ -227,7 +294,7 @@ sunlint --all --input=. --format=json --output=report.json
|
|
|
227
294
|
# PR validation
|
|
228
295
|
sunlint --all --changed-files --fail-on-new-violations
|
|
229
296
|
|
|
230
|
-
# Pre-commit hook
|
|
297
|
+
# Pre-commit hook
|
|
231
298
|
sunlint --all --staged-files --format=summary
|
|
232
299
|
```
|
|
233
300
|
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"R001": ["react/no-this-in-sfc", "no-param-reassign", "react/function-component-definition", "react/forbid-component-props"],
|
|
105
105
|
"R002": ["react-hooks/rules-of-hooks", "react-hooks/exhaustive-deps", "react/no-did-mount-set-state", "react/no-did-update-set-state"],
|
|
106
106
|
"R003": ["react/no-direct-mutation-state", "react/jsx-no-constructed-context-values", "react/forbid-dom-props"],
|
|
107
|
-
"R004": ["
|
|
107
|
+
"R004": ["no-param-reassign", "react/forbid-foreign-prop-types"],
|
|
108
108
|
"R005": ["react/jsx-no-bind"],
|
|
109
109
|
"R006": ["react/jsx-pascal-case", "react/jsx-uses-react", "react/jsx-uses-vars"],
|
|
110
110
|
"R007": ["react-hooks/rules-of-hooks"],
|
|
@@ -651,13 +651,13 @@
|
|
|
651
651
|
"quality": {
|
|
652
652
|
"name": "Code Quality",
|
|
653
653
|
"description": "Rules for code quality improvement",
|
|
654
|
-
"rules": ["C002", "C003", "C006", "C010", "C013", "C014", "C017", "C018", "
|
|
654
|
+
"rules": ["C002", "C003", "C006", "C010", "C013", "C014", "C017", "C018", "C023", "C029", "C030", "C035", "C041", "C042", "C043", "C047", "C072", "C075", "C076", "T002", "T003", "T004", "T007", "T010", "T019", "T020", "T021", "R001", "R002", "R003", "R004", "R005", "R006"],
|
|
655
655
|
"severity": "warning"
|
|
656
656
|
},
|
|
657
657
|
"security": {
|
|
658
658
|
"name": "Security",
|
|
659
659
|
"description": "Rules for security best practices",
|
|
660
|
-
"rules": ["S003", "S005", "S006", "S008", "S009", "S010", "S011", "S012", "S014", "S015", "S016", "S017", "S018", "S019", "S020", "S022", "S023", "S025", "S026", "S027", "S029", "S030", "S033", "S034", "S035", "S036", "S037", "S038", "S039", "S041", "S042", "S043", "S044", "S045", "S046", "S047", "S048", "S050", "S052", "S054", "S055", "S057", "S058"],
|
|
660
|
+
"rules": ["S001", "S002", "S003", "S005", "S006", "S007", "S008", "S009", "S010", "S011", "S012", "S013", "S014", "S015", "S016", "S017", "S018", "S019", "S020", "S022", "S023", "S025", "S026", "S027", "S029", "S030", "S033", "S034", "S035", "S036", "S037", "S038", "S039", "S041", "S042", "S043", "S044", "S045", "S046", "S047", "S048", "S050", "S052", "S054", "S055", "S057", "S058"],
|
|
661
661
|
"severity": "error"
|
|
662
662
|
},
|
|
663
663
|
"logging": {
|
|
@@ -745,19 +745,21 @@
|
|
|
745
745
|
}
|
|
746
746
|
},
|
|
747
747
|
"metadata": {
|
|
748
|
-
"version": "1.
|
|
749
|
-
"lastUpdated": "2025-07-
|
|
748
|
+
"version": "1.1.5",
|
|
749
|
+
"lastUpdated": "2025-07-24",
|
|
750
750
|
"totalRules": 44,
|
|
751
|
-
"qualityRules":
|
|
752
|
-
"securityRules":
|
|
751
|
+
"qualityRules": 33,
|
|
752
|
+
"securityRules": 47,
|
|
753
753
|
"stableRules": 43,
|
|
754
754
|
"experimentalRules": 1,
|
|
755
755
|
"supportedLanguages": 4,
|
|
756
756
|
"features": [
|
|
757
757
|
"Security rules integration",
|
|
758
|
-
"Category-based rule filtering",
|
|
758
|
+
"Category-based rule filtering",
|
|
759
759
|
"Dynamic rule configuration",
|
|
760
|
-
"ESLint integration
|
|
760
|
+
"ESLint 9.x integration",
|
|
761
|
+
"React rules integration",
|
|
762
|
+
"Memory leak fixes"
|
|
761
763
|
]
|
|
762
764
|
}
|
|
763
765
|
}
|
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ESLint-based JavaScript Parser
|
|
3
3
|
* Uses the same AST infrastructure as ESLint for reliable parsing
|
|
4
|
+
* Gracefully handles missing peer dependencies
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
class ESLintJavaScriptParser {
|
|
7
8
|
constructor() {
|
|
8
9
|
this.parser = null;
|
|
10
|
+
this.parserType = null;
|
|
9
11
|
this.initParser();
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
initParser() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// Try to dynamically load available parsers
|
|
16
|
+
const parsers = [
|
|
17
|
+
{ name: '@babel/parser', type: 'babel' },
|
|
18
|
+
{ name: 'espree', type: 'espree' }
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
for (const { name, type } of parsers) {
|
|
17
22
|
try {
|
|
18
|
-
|
|
19
|
-
this.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
this.parser = require(name);
|
|
24
|
+
this.parserType = type;
|
|
25
|
+
break;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
// Continue to next parser
|
|
28
|
+
continue;
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
31
|
}
|
|
@@ -28,11 +34,9 @@ class ESLintJavaScriptParser {
|
|
|
28
34
|
if (!this.parser) return null;
|
|
29
35
|
|
|
30
36
|
try {
|
|
31
|
-
if (this.
|
|
32
|
-
// @babel/parser or espree
|
|
37
|
+
if (this.parserType === 'babel') {
|
|
33
38
|
return this.parser.parse(code, {
|
|
34
|
-
|
|
35
|
-
sourceType: 'module',
|
|
39
|
+
sourceType: 'unambiguous',
|
|
36
40
|
allowImportExportEverywhere: true,
|
|
37
41
|
allowReturnOutsideFunction: true,
|
|
38
42
|
plugins: [
|
|
@@ -43,14 +47,16 @@ class ESLintJavaScriptParser {
|
|
|
43
47
|
'objectRestSpread',
|
|
44
48
|
'optionalChaining',
|
|
45
49
|
'nullishCoalescingOperator'
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
]
|
|
51
|
+
});
|
|
52
|
+
} else if (this.parserType === 'espree') {
|
|
53
|
+
return this.parser.parse(code, {
|
|
54
|
+
ecmaVersion: 'latest',
|
|
55
|
+
sourceType: 'module',
|
|
56
|
+
ecmaFeatures: {
|
|
57
|
+
jsx: true,
|
|
58
|
+
globalReturn: true
|
|
59
|
+
}
|
|
54
60
|
});
|
|
55
61
|
}
|
|
56
62
|
} catch (error) {
|
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ESLint-based TypeScript Parser
|
|
3
3
|
* Uses @typescript-eslint/parser for TypeScript AST analysis
|
|
4
|
+
* Gracefully handles missing peer dependencies
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
class ESLintTypeScriptParser {
|
|
7
8
|
constructor() {
|
|
8
9
|
this.parser = null;
|
|
10
|
+
this.parserType = null;
|
|
9
11
|
this.initParser();
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
initParser() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// Try to dynamically load available TypeScript parsers
|
|
16
|
+
const parsers = [
|
|
17
|
+
{ name: '@typescript-eslint/parser', type: 'typescript-eslint' },
|
|
18
|
+
{ name: '@babel/parser', type: 'babel' }
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
for (const { name, type } of parsers) {
|
|
17
22
|
try {
|
|
18
|
-
|
|
19
|
-
this.
|
|
20
|
-
|
|
21
|
-
} catch (
|
|
22
|
-
//
|
|
23
|
-
|
|
23
|
+
this.parser = require(name);
|
|
24
|
+
this.parserType = type;
|
|
25
|
+
break;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
// Continue to next parser
|
|
28
|
+
continue;
|
|
24
29
|
}
|
|
25
30
|
}
|
|
26
31
|
}
|
|
@@ -29,45 +34,32 @@ class ESLintTypeScriptParser {
|
|
|
29
34
|
if (!this.parser) return null;
|
|
30
35
|
|
|
31
36
|
try {
|
|
32
|
-
// Try Babel parser first for better TypeScript 5.x support
|
|
33
37
|
if (this.parserType === 'babel') {
|
|
34
38
|
return this.parser.parse(code, {
|
|
35
|
-
sourceType: '
|
|
39
|
+
sourceType: 'unambiguous',
|
|
36
40
|
allowImportExportEverywhere: true,
|
|
37
41
|
plugins: ['typescript', 'jsx', 'decorators-legacy']
|
|
38
42
|
});
|
|
39
|
-
} else {
|
|
40
|
-
//
|
|
43
|
+
} else if (this.parserType === 'typescript-eslint') {
|
|
44
|
+
// First try @babel/parser for better compatibility
|
|
41
45
|
try {
|
|
42
46
|
const babel = require('@babel/parser');
|
|
43
47
|
return babel.parse(code, {
|
|
44
|
-
sourceType: '
|
|
48
|
+
sourceType: 'unambiguous',
|
|
45
49
|
allowImportExportEverywhere: true,
|
|
46
50
|
plugins: ['typescript', 'jsx', 'decorators-legacy', 'classProperties']
|
|
47
51
|
});
|
|
48
52
|
} catch (babelError) {
|
|
49
|
-
// Fallback to @typescript-eslint/parser
|
|
53
|
+
// Fallback to @typescript-eslint/parser if available
|
|
50
54
|
if (this.parser.parseForESLint) {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
sourceType: 'module',
|
|
60
|
-
ecmaFeatures: {
|
|
61
|
-
jsx: true
|
|
62
|
-
},
|
|
63
|
-
errorOnUnknownASTType: false // Don't error on unknown AST types
|
|
64
|
-
});
|
|
65
|
-
return result.ast;
|
|
66
|
-
} finally {
|
|
67
|
-
// Always restore console methods
|
|
68
|
-
console.warn = originalWarn;
|
|
69
|
-
console.error = originalError;
|
|
70
|
-
}
|
|
55
|
+
const result = this.parser.parseForESLint(code, {
|
|
56
|
+
ecmaVersion: 'latest',
|
|
57
|
+
sourceType: 'module',
|
|
58
|
+
ecmaFeatures: {
|
|
59
|
+
jsx: true
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return result.ast;
|
|
71
63
|
}
|
|
72
64
|
}
|
|
73
65
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Checker
|
|
3
|
+
* Checks for optional peer dependencies and provides helpful messages
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class DependencyChecker {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.checkedDependencies = new Set();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if a dependency is available
|
|
13
|
+
*/
|
|
14
|
+
isDependencyAvailable(packageName) {
|
|
15
|
+
try {
|
|
16
|
+
require.resolve(packageName);
|
|
17
|
+
return true;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check for ESLint engine dependencies
|
|
25
|
+
*/
|
|
26
|
+
checkESLintDependencies() {
|
|
27
|
+
const dependencies = {
|
|
28
|
+
'eslint': { required: false, description: 'ESLint engine support' },
|
|
29
|
+
'@typescript-eslint/eslint-plugin': { required: false, description: 'TypeScript ESLint rules' },
|
|
30
|
+
'@typescript-eslint/parser': { required: false, description: 'TypeScript ESLint parsing' },
|
|
31
|
+
'typescript': { required: false, description: 'TypeScript compiler' }
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const missing = [];
|
|
35
|
+
const available = [];
|
|
36
|
+
|
|
37
|
+
for (const [pkg, info] of Object.entries(dependencies)) {
|
|
38
|
+
if (this.isDependencyAvailable(pkg)) {
|
|
39
|
+
available.push(pkg);
|
|
40
|
+
} else {
|
|
41
|
+
missing.push({ pkg, ...info });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { available, missing };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check for AST parsing dependencies
|
|
50
|
+
*/
|
|
51
|
+
checkASTDependencies() {
|
|
52
|
+
const dependencies = {
|
|
53
|
+
'@babel/parser': { required: false, description: 'JavaScript AST parsing' },
|
|
54
|
+
'espree': { required: false, description: 'ECMAScript AST parsing' }
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const missing = [];
|
|
58
|
+
const available = [];
|
|
59
|
+
|
|
60
|
+
for (const [pkg, info] of Object.entries(dependencies)) {
|
|
61
|
+
if (this.isDependencyAvailable(pkg)) {
|
|
62
|
+
available.push(pkg);
|
|
63
|
+
} else {
|
|
64
|
+
missing.push({ pkg, ...info });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { available, missing };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Show installation instructions for missing dependencies
|
|
73
|
+
*/
|
|
74
|
+
showInstallationInstructions(missing, context = 'general') {
|
|
75
|
+
if (missing.length === 0) return;
|
|
76
|
+
|
|
77
|
+
console.log('\n📦 Optional dependencies not found:');
|
|
78
|
+
|
|
79
|
+
for (const { pkg, description } of missing) {
|
|
80
|
+
console.log(` • ${pkg} - ${description}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const packages = missing.map(m => m.pkg).join(' ');
|
|
84
|
+
|
|
85
|
+
if (context === 'eslint') {
|
|
86
|
+
console.log('\n💡 To enable ESLint engine features, install:');
|
|
87
|
+
console.log(` npm install ${packages}`);
|
|
88
|
+
console.log('\n Or add to your project dependencies if you already have them.');
|
|
89
|
+
} else if (context === 'ast') {
|
|
90
|
+
console.log('\n💡 To enable AST analysis features, install:');
|
|
91
|
+
console.log(` npm install ${packages}`);
|
|
92
|
+
} else {
|
|
93
|
+
console.log('\n💡 To enable full functionality, install:');
|
|
94
|
+
console.log(` npm install ${packages}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log('\n SunLint will continue using heuristic analysis.\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check and notify about missing dependencies (only once per session)
|
|
102
|
+
*/
|
|
103
|
+
checkAndNotify(type = 'all') {
|
|
104
|
+
const key = `checked_${type}`;
|
|
105
|
+
if (this.checkedDependencies.has(key)) return;
|
|
106
|
+
|
|
107
|
+
this.checkedDependencies.add(key);
|
|
108
|
+
|
|
109
|
+
if (type === 'eslint' || type === 'all') {
|
|
110
|
+
const { missing } = this.checkESLintDependencies();
|
|
111
|
+
if (missing.length > 0) {
|
|
112
|
+
this.showInstallationInstructions(missing, 'eslint');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (type === 'ast' || type === 'all') {
|
|
117
|
+
const { missing } = this.checkASTDependencies();
|
|
118
|
+
if (missing.length > 0) {
|
|
119
|
+
this.showInstallationInstructions(missing, 'ast');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = new DependencyChecker();
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Dependency Auto-Installer
|
|
3
|
+
* Automatically installs missing peer dependencies when SunLint runs
|
|
4
|
+
* Future: Will support package flavor recommendations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
class SmartInstaller {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.installedInSession = new Set();
|
|
14
|
+
this.packageFlavors = {
|
|
15
|
+
'typescript': '@sun-asterisk/sunlint-typescript',
|
|
16
|
+
'dart': '@sun-asterisk/sunlint-dart',
|
|
17
|
+
'python': '@sun-asterisk/sunlint-python',
|
|
18
|
+
'go': '@sun-asterisk/sunlint-go',
|
|
19
|
+
'full': '@sun-asterisk/sunlint-full'
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Detect project type and recommend appropriate packages
|
|
25
|
+
*/
|
|
26
|
+
detectProjectType(projectRoot) {
|
|
27
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
28
|
+
if (!fs.existsSync(packageJsonPath)) return ['basic'];
|
|
29
|
+
|
|
30
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
31
|
+
const types = [];
|
|
32
|
+
|
|
33
|
+
// Check for TypeScript
|
|
34
|
+
if (packageJson.devDependencies?.typescript ||
|
|
35
|
+
packageJson.dependencies?.typescript ||
|
|
36
|
+
fs.existsSync(path.join(projectRoot, 'tsconfig.json'))) {
|
|
37
|
+
types.push('typescript');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check for other languages (future)
|
|
41
|
+
if (fs.existsSync(path.join(projectRoot, 'pubspec.yaml'))) {
|
|
42
|
+
types.push('dart');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(path.join(projectRoot, 'requirements.txt')) ||
|
|
46
|
+
fs.existsSync(path.join(projectRoot, 'pyproject.toml'))) {
|
|
47
|
+
types.push('python');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(path.join(projectRoot, 'go.mod'))) {
|
|
51
|
+
types.push('go');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return types.length > 0 ? types : ['basic'];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Recommend optimal package flavors instead of individual dependencies
|
|
59
|
+
*/
|
|
60
|
+
recommendPackageFlavors(missingDeps, projectTypes) {
|
|
61
|
+
const recommendations = [];
|
|
62
|
+
|
|
63
|
+
// If missing TypeScript deps and it's a TS project
|
|
64
|
+
if (missingDeps.some(d => d.pkg.includes('@typescript-eslint')) &&
|
|
65
|
+
projectTypes.includes('typescript')) {
|
|
66
|
+
recommendations.push({
|
|
67
|
+
package: '@sun-asterisk/sunlint-typescript',
|
|
68
|
+
reason: 'Complete TypeScript analysis support',
|
|
69
|
+
replaces: missingDeps.filter(d => d.pkg.includes('typescript')).map(d => d.pkg)
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// If missing many ESLint deps, suggest full package
|
|
74
|
+
if (missingDeps.length >= 3 && missingDeps.some(d => d.pkg === 'eslint')) {
|
|
75
|
+
recommendations.push({
|
|
76
|
+
package: '@sun-asterisk/sunlint-full',
|
|
77
|
+
reason: 'Complete ESLint integration with all features',
|
|
78
|
+
replaces: missingDeps.map(d => d.pkg)
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return recommendations;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if we're in a project that can install dependencies
|
|
87
|
+
*/
|
|
88
|
+
canAutoInstall() {
|
|
89
|
+
// Check if package.json exists in current or parent directories
|
|
90
|
+
let currentDir = process.cwd();
|
|
91
|
+
const root = path.parse(currentDir).root;
|
|
92
|
+
|
|
93
|
+
while (currentDir !== root) {
|
|
94
|
+
if (fs.existsSync(path.join(currentDir, 'package.json'))) {
|
|
95
|
+
return currentDir;
|
|
96
|
+
}
|
|
97
|
+
currentDir = path.dirname(currentDir);
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Auto-install missing dependencies with user confirmation
|
|
104
|
+
*/
|
|
105
|
+
async autoInstallMissing(missingDeps, context = 'analysis') {
|
|
106
|
+
const projectRoot = this.canAutoInstall();
|
|
107
|
+
if (!projectRoot) {
|
|
108
|
+
this.showManualInstallInstructions(missingDeps);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`\n🔍 SunLint needs these dependencies for enhanced ${context}:`);
|
|
113
|
+
missingDeps.forEach(dep => console.log(` • ${dep.pkg} - ${dep.description}`));
|
|
114
|
+
|
|
115
|
+
const packages = missingDeps.map(d => d.pkg).join(' ');
|
|
116
|
+
|
|
117
|
+
console.log(`\n💡 Install command:`);
|
|
118
|
+
console.log(` npm install ${packages} --save-dev`);
|
|
119
|
+
|
|
120
|
+
// In CI environments, don't auto-install but show clear message
|
|
121
|
+
if (process.env.CI || process.env.NODE_ENV === 'test') {
|
|
122
|
+
console.log('\n⚠️ CI Environment: Add dependencies to package.json for consistent builds');
|
|
123
|
+
console.log(' SunLint will continue with available features\n');
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Interactive prompt for auto-install
|
|
128
|
+
const shouldAutoInstall = process.env.SUNLINT_AUTO_INSTALL === 'true';
|
|
129
|
+
|
|
130
|
+
if (shouldAutoInstall) {
|
|
131
|
+
try {
|
|
132
|
+
console.log('\n📦 Auto-installing dependencies...');
|
|
133
|
+
execSync(`npm install ${packages} --save-dev`, {
|
|
134
|
+
cwd: projectRoot,
|
|
135
|
+
stdio: 'pipe' // Less noisy
|
|
136
|
+
});
|
|
137
|
+
console.log('✅ Dependencies installed successfully!');
|
|
138
|
+
console.log('🔄 Re-running analysis with full features...\n');
|
|
139
|
+
return true;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.log('❌ Auto-install failed, continuing with available features');
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
console.log('\n🔄 Continuing with available features...');
|
|
146
|
+
console.log('💡 Set SUNLINT_AUTO_INSTALL=true to enable automatic installation\n');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Show manual installation instructions
|
|
154
|
+
*/
|
|
155
|
+
showManualInstallInstructions(missingDeps) {
|
|
156
|
+
console.log('\n📦 To enable full functionality, install:');
|
|
157
|
+
const packages = missingDeps.map(d => d.pkg).join(' ');
|
|
158
|
+
console.log(` npm install ${packages} --save-dev`);
|
|
159
|
+
console.log('\n💡 Or set SUNLINT_AUTO_INSTALL=true for automatic installation');
|
|
160
|
+
console.log(' SunLint will continue with heuristic analysis.\n');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = new SmartInstaller();
|