@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 CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "extends": ["recommended"],
2
+ "extends": ["@sun/sunlint/recommended"],
3
3
  "rules": {
4
4
  "C019": "warn",
5
5
  "C006": "warn",
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
- - ✅ **AST-Enhanced Analysis**: Superior accuracy with Babel/ESLint parsers
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 globally
21
+ # Install
21
22
  npm install -g @sun-asterisk/sunlint
22
23
 
23
- # Basic usage (uses config file or default patterns)
24
+ # Basic usage - works immediately!
24
25
  sunlint --all
25
26
  sunlint --rules=C019,C006
26
27
 
27
- # Explicit input specification
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 (multi-engine analysis)
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
- # Run all rules
213
- sunlint --all --input=src
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 + Git integration
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": ["@typescript-eslint/prefer-readonly", "react/forbid-foreign-prop-types"],
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", "C019", "C023", "C027", "C029", "C030", "C031", "C034", "C035", "C041", "C042", "C043", "C047", "C048", "C076", "T002", "T003", "T004", "T007", "T011", "T019", "T025", "T026"],
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.0.4",
749
- "lastUpdated": "2025-07-08",
748
+ "version": "1.1.5",
749
+ "lastUpdated": "2025-07-24",
750
750
  "totalRules": 44,
751
- "qualityRules": 4,
752
- "securityRules": 40,
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 enhancement"
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
- try {
14
- // Try to use @babel/parser (what ESLint uses internally)
15
- this.parser = require('@babel/parser');
16
- } catch (e1) {
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
- // Fallback to espree (ESLint's default parser)
19
- this.parser = require('espree');
20
- } catch (e2) {
21
- // If no parser available, this will be null
22
- this.parser = null;
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.parser.parse) {
32
- // @babel/parser or espree
37
+ if (this.parserType === 'babel') {
33
38
  return this.parser.parse(code, {
34
- ecmaVersion: 'latest',
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
- ].filter(plugin => {
47
- // Only include plugins that are supported
48
- try {
49
- return true;
50
- } catch {
51
- return false;
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
- try {
14
- // Try to use @typescript-eslint/parser
15
- this.parser = require('@typescript-eslint/parser');
16
- } catch (e1) {
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
- // Fallback to @babel/parser with TypeScript plugin
19
- this.parser = require('@babel/parser');
20
- this.parserType = 'babel';
21
- } catch (e2) {
22
- // If no parser available, this will be null
23
- this.parser = null;
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: 'module',
39
+ sourceType: 'unambiguous',
36
40
  allowImportExportEverywhere: true,
37
41
  plugins: ['typescript', 'jsx', 'decorators-legacy']
38
42
  });
39
- } else {
40
- // Use Babel parser as primary for TypeScript files
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: 'module',
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 (with warnings suppressed)
53
+ // Fallback to @typescript-eslint/parser if available
50
54
  if (this.parser.parseForESLint) {
51
- const originalWarn = console.warn;
52
- const originalError = console.error;
53
- console.warn = () => {}; // Suppress warnings temporarily
54
- console.error = () => {}; // Suppress errors temporarily
55
-
56
- try {
57
- const result = this.parser.parseForESLint(code, {
58
- ecmaVersion: 'latest',
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();