@sun-asterisk/sunlint 1.0.5 → 1.0.6

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.
Files changed (66) hide show
  1. package/README.md +119 -384
  2. package/core/cli-action-handler.js +169 -4
  3. package/core/cli-program.js +20 -4
  4. package/core/config-manager.js +84 -5
  5. package/core/file-targeting-service.js +386 -0
  6. package/core/multi-rule-runner.js +9 -27
  7. package/core/output-service.js +5 -6
  8. package/docs/FILE_TARGETING_COMPARISON.md +0 -0
  9. package/examples/README.md +56 -34
  10. package/examples/basic-typescript-demo/.eslintrc.json +18 -0
  11. package/examples/basic-typescript-demo/.next/cache/eslint/.cache_1othrmo +1 -0
  12. package/examples/basic-typescript-demo/.sunlint.json +29 -0
  13. package/examples/basic-typescript-demo/eslint.config.mjs +37 -0
  14. package/examples/basic-typescript-demo/next-env.d.ts +5 -0
  15. package/examples/basic-typescript-demo/next.config.mjs +4 -0
  16. package/examples/basic-typescript-demo/package-lock.json +5656 -0
  17. package/examples/basic-typescript-demo/package.json +34 -0
  18. package/examples/basic-typescript-demo/src/app/layout.tsx +18 -0
  19. package/examples/basic-typescript-demo/src/app/page.tsx +48 -0
  20. package/examples/basic-typescript-demo/src/config.ts +14 -0
  21. package/examples/basic-typescript-demo/src/good-practices.ts +58 -0
  22. package/examples/basic-typescript-demo/src/types.generated.ts +13 -0
  23. package/examples/basic-typescript-demo/src/user.test.ts +19 -0
  24. package/examples/basic-typescript-demo/src/violations.ts +61 -0
  25. package/examples/basic-typescript-demo/test-config-priority.sh +0 -0
  26. package/examples/basic-typescript-demo/test-file-targeting.sh +0 -0
  27. package/examples/basic-typescript-demo/tsconfig.json +27 -0
  28. package/examples/enhanced-config.json +0 -0
  29. package/examples/eslint-integration-demo/.eslintrc.js +38 -0
  30. package/examples/eslint-integration-demo/.sunlint.json +42 -0
  31. package/examples/eslint-integration-demo/next-env.d.ts +5 -0
  32. package/examples/eslint-integration-demo/next.config.js +8 -0
  33. package/examples/eslint-integration-demo/package-lock.json +5740 -0
  34. package/examples/eslint-integration-demo/package.json +37 -0
  35. package/examples/eslint-integration-demo/src/api.test.ts +20 -0
  36. package/examples/eslint-integration-demo/src/conflict-test.tsx +44 -0
  37. package/examples/eslint-integration-demo/src/naming-conflicts.ts +50 -0
  38. package/examples/eslint-integration-demo/test-file-targeting.sh +0 -0
  39. package/examples/eslint-integration-demo/tsconfig.json +26 -0
  40. package/examples/file-targeting-demo/global.d.ts +11 -0
  41. package/examples/file-targeting-demo/jest.config.js +8 -0
  42. package/examples/file-targeting-demo/sample.ts +53 -0
  43. package/examples/file-targeting-demo/src/server.js +11 -0
  44. package/examples/file-targeting-demo/src/server.test.js +11 -0
  45. package/examples/file-targeting-demo/src/types.d.ts +4 -0
  46. package/examples/file-targeting-demo/src/types.generated.ts +10 -0
  47. package/examples/file-targeting-demo/user-service.test.ts +15 -0
  48. package/examples/file-targeting-demo/user-service.ts +13 -0
  49. package/examples/file-targeting-demo/utils.js +15 -0
  50. package/examples/multi-language-project/.eslintrc.json +38 -0
  51. package/examples/multi-language-project/package.json +37 -0
  52. package/examples/multi-language-project/src/sample.ts +39 -0
  53. package/examples/rule-test-fixtures/README.md +67 -0
  54. package/examples/rule-test-fixtures/rules/C006_function_naming/clean/typescript-clean.ts +64 -0
  55. package/examples/rule-test-fixtures/rules/C006_function_naming/violations/dart-violations.dart +56 -0
  56. package/examples/rule-test-fixtures/rules/C006_function_naming/violations/typescript-violations.ts +47 -0
  57. package/examples/rule-test-fixtures/rules/C019_log_level_usage/clean/typescript-clean.ts +93 -0
  58. package/examples/rule-test-fixtures/rules/C019_log_level_usage/violations/dart-violations.dart +75 -0
  59. package/examples/rule-test-fixtures/rules/C019_log_level_usage/violations/typescript-violations.ts +84 -0
  60. package/examples/rule-test-fixtures/rules/C029_catch_block_logging/clean/typescript-clean.ts +0 -0
  61. package/examples/rule-test-fixtures/rules/C029_catch_block_logging/violations/typescript-violations.ts +37 -0
  62. package/package.json +2 -1
  63. package/eslint-integration/eslint-plugin-custom/c076-one-assert-per-test.js +0 -184
  64. package/examples/.sunlint.json +0 -42
  65. package/examples/package.json +0 -33
  66. /package/{eslint-integration/node_modules/eslint-plugin-custom/package.json → docs/ENHANCED_FILE_TARGETING.md} +0 -0
@@ -10,6 +10,7 @@ const RuleSelectionService = require('./rule-selection-service');
10
10
  const AnalysisOrchestrator = require('./analysis-orchestrator');
11
11
  const OutputService = require('./output-service');
12
12
  const GitUtils = require('./git-utils');
13
+ const FileTargetingService = require('./file-targeting-service');
13
14
 
14
15
  class CliActionHandler {
15
16
  constructor(options = {}) {
@@ -18,6 +19,7 @@ class CliActionHandler {
18
19
  this.ruleSelectionService = new RuleSelectionService();
19
20
  this.analysisOrchestrator = new AnalysisOrchestrator();
20
21
  this.outputService = new OutputService(options);
22
+ this.fileTargetingService = new FileTargetingService();
21
23
  }
22
24
 
23
25
  async execute() {
@@ -45,8 +47,19 @@ class CliActionHandler {
45
47
  return;
46
48
  }
47
49
 
50
+ // Apply enhanced file targeting
51
+ const targetingResult = await this.applyFileTargeting(config);
52
+ if (targetingResult.files.length === 0) {
53
+ console.log(chalk.yellow('⚠️ No files to analyze after applying filters'));
54
+ this.displayTargetingStats(targetingResult.stats);
55
+ return;
56
+ }
57
+
58
+ // Update options with filtered files
59
+ this.options.targetFiles = targetingResult.files;
60
+
48
61
  // Display analysis info
49
- this.displayAnalysisInfo(rulesToRun);
62
+ this.displayAnalysisInfo(rulesToRun, targetingResult);
50
63
 
51
64
  // Run analysis
52
65
  const startTime = Date.now();
@@ -121,7 +134,11 @@ class CliActionHandler {
121
134
  return await this.configManager.loadConfig(this.options.config, this.options);
122
135
  } catch (error) {
123
136
  console.log(chalk.yellow('⚠️ Using default configuration'));
124
- return { rules: {} };
137
+ // Return default config instead of empty object
138
+ if (!this.configManager) {
139
+ this.configManager = new ConfigManager();
140
+ }
141
+ return this.configManager.defaultConfig;
125
142
  }
126
143
  }
127
144
 
@@ -192,30 +209,178 @@ class CliActionHandler {
192
209
  }
193
210
  }
194
211
 
212
+ /**
213
+ * Apply enhanced file targeting logic
214
+ * Rule C006: applyFileTargeting - verb-noun naming
215
+ */
216
+ async applyFileTargeting(config) {
217
+ // Debug config
218
+ if (this.options.debug) {
219
+ console.log('🐛 Debug applyFileTargeting config:', JSON.stringify(config.testPatterns, null, 2));
220
+ }
221
+
222
+ // Prepare CLI options for file targeting
223
+ const targetingOptions = {
224
+ include: this.options.include ? this.options.include.split(',') : null,
225
+ exclude: this.options.exclude ? this.options.exclude.split(',') : null,
226
+ languages: this.options.languages,
227
+ excludeTests: this.options.excludeTests,
228
+ includeTests: this.options.includeTests,
229
+ onlySource: this.options.onlySource
230
+ };
231
+
232
+ if (this.options.debug) {
233
+ console.log('🐛 Debug targetingOptions:', targetingOptions);
234
+ }
235
+
236
+ // Determine input paths
237
+ let inputPaths = [this.options.input];
238
+
239
+ // Handle git-based file filtering
240
+ if (this.options.changedFiles || this.options.stagedFiles || this.options.since || this.options.prMode) {
241
+ inputPaths = this.getGitFilteredFiles();
242
+ }
243
+
244
+ // Apply file targeting
245
+ if (this.options.debug) {
246
+ console.log('🐛 Calling fileTargetingService.getTargetFiles with inputPaths:', inputPaths);
247
+ }
248
+
249
+ return await this.fileTargetingService.getTargetFiles(inputPaths, config, targetingOptions);
250
+ }
251
+
252
+ /**
253
+ * Get files based on git filtering
254
+ * Rule C006: getGitFilteredFiles - verb-noun naming
255
+ */
256
+ getGitFilteredFiles() {
257
+ try {
258
+ let files = [];
259
+
260
+ if (this.options.changedFiles) {
261
+ const diffBase = this.options.diffBase || GitUtils.getPRDiffBase('main');
262
+ files = GitUtils.getChangedFiles(diffBase);
263
+ } else if (this.options.stagedFiles) {
264
+ files = GitUtils.getStagedFiles();
265
+ } else if (this.options.since) {
266
+ files = GitUtils.getChangedFilesSince(this.options.since);
267
+ } else if (this.options.prMode) {
268
+ const diffBase = this.options.diffBase || GitUtils.getPRDiffBase('main');
269
+ files = GitUtils.getChangedFiles(diffBase);
270
+ }
271
+
272
+ if (files.length === 0) {
273
+ console.log(chalk.yellow('⚠️ No changed files found'));
274
+ return [];
275
+ }
276
+
277
+ if (this.options.verbose) {
278
+ console.log(chalk.gray(`🔍 Found ${files.length} changed files`));
279
+ }
280
+
281
+ return files.map(file => require('path').resolve(file));
282
+ } catch (error) {
283
+ console.error(chalk.red('❌ Git operation failed:'), error.message);
284
+ if (this.options.debug) {
285
+ console.error(error.stack);
286
+ }
287
+ return [this.options.input]; // Fallback to regular input
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Display file targeting statistics
293
+ * Rule C006: displayTargetingStats - verb-noun naming
294
+ */
295
+ displayTargetingStats(stats) {
296
+ if (this.options.quiet || this.options.format === 'json') {
297
+ return;
298
+ }
299
+
300
+ console.log(chalk.blue('\n📊 File Targeting Results:'));
301
+ console.log(chalk.gray(` Total files: ${stats.totalFiles}`));
302
+
303
+ if (Object.keys(stats.byLanguage).length > 0) {
304
+ console.log(chalk.gray(' By language:'));
305
+ for (const [language, count] of Object.entries(stats.byLanguage)) {
306
+ if (count > 0) {
307
+ console.log(chalk.gray(` ${language}: ${count} files`));
308
+ }
309
+ }
310
+ }
311
+
312
+ console.log(chalk.gray(' By category:'));
313
+ console.log(chalk.gray(` Source: ${stats.byCategory.source} files`));
314
+ console.log(chalk.gray(` Test: ${stats.byCategory.test} files`));
315
+ console.log(chalk.gray(` Config: ${stats.byCategory.config} files`));
316
+ console.log(chalk.gray(` Other: ${stats.byCategory.other} files`));
317
+ }
318
+
195
319
  async showDryRunPreview(config) {
196
320
  console.log(chalk.yellow('🔍 Dry run mode - Analysis preview:'));
321
+
322
+ // Apply file targeting to show which files would be analyzed
323
+ const targetingResult = await this.applyFileTargeting(config);
197
324
  const rulesToRun = await this.ruleSelectionService.selectRules(config, this.options);
198
325
 
199
326
  console.log(chalk.blue('📋 Sun Lint Analysis Preview:'));
200
327
  console.log(chalk.gray(`Input: ${this.options.input}`));
201
328
  console.log(chalk.gray(`Format: ${this.options.format}`));
202
329
  console.log(chalk.gray(`Rules to run: ${rulesToRun.length}`));
330
+ console.log(chalk.gray(`Files to analyze: ${targetingResult.files.length}`));
331
+
332
+ // Show file targeting details
333
+ if (this.options.verbose || targetingResult.files.length <= 10) {
334
+ console.log(chalk.blue('\n📁 Target Files:'));
335
+ targetingResult.files.forEach(file => {
336
+ const relativePath = require('path').relative(process.cwd(), file);
337
+ console.log(` ${chalk.gray(relativePath)}`);
338
+ });
339
+ }
340
+
341
+ // Show targeting stats
342
+ this.displayTargetingStats(targetingResult.stats);
203
343
 
204
344
  if (rulesToRun.length > 0) {
205
- console.log(chalk.blue('📋 Selected Rules:'));
345
+ console.log(chalk.blue('\n📋 Selected Rules:'));
206
346
  rulesToRun.forEach(rule => {
207
347
  console.log(` ${chalk.cyan(rule.id)}: ${rule.name} (${rule.severity})`);
208
348
  });
209
349
  }
350
+
351
+ // Show CLI file targeting options if provided
352
+ if (this.options.include || this.options.exclude || this.options.languages) {
353
+ console.log(chalk.blue('\n🎯 File Targeting Options:'));
354
+ if (this.options.include) {
355
+ console.log(` Include: ${chalk.green(this.options.include)}`);
356
+ }
357
+ if (this.options.exclude) {
358
+ console.log(` Exclude: ${chalk.red(this.options.exclude)}`);
359
+ }
360
+ if (this.options.languages) {
361
+ console.log(` Languages: ${chalk.cyan(this.options.languages)}`);
362
+ }
363
+ if (this.options.excludeTests) {
364
+ console.log(` Exclude tests: ${chalk.yellow('true')}`);
365
+ }
366
+ if (this.options.onlySource) {
367
+ console.log(` Only source: ${chalk.yellow('true')}`);
368
+ }
369
+ }
210
370
  }
211
371
 
212
- displayAnalysisInfo(rulesToRun) {
372
+ displayAnalysisInfo(rulesToRun, targetingResult) {
213
373
  if (this.options.quiet || this.options.format === 'json') return;
214
374
 
215
375
  console.log(chalk.green(`🚀 Running ${rulesToRun.length} rules on: ${this.options.input}`));
216
376
 
217
377
  const ruleIds = rulesToRun.map(r => r.id).join(', ');
218
378
  console.log(chalk.blue(` 📋 Rules: ${ruleIds}`));
379
+
380
+ // Display file targeting stats if available
381
+ if (targetingResult && targetingResult.stats) {
382
+ this.displayTargetingStats(targetingResult.stats);
383
+ }
219
384
  }
220
385
 
221
386
  handleExit(results) {
@@ -36,6 +36,15 @@ function createCliProgram() {
36
36
  .option('-o, --output <file>', 'Output file path')
37
37
  .option('--config <file>', 'Configuration file path', '.sunlint.json');
38
38
 
39
+ // File targeting options
40
+ program
41
+ .option('--include <patterns>', 'Include file patterns (comma-separated globs)')
42
+ .option('--exclude <patterns>', 'Exclude file patterns (comma-separated globs)')
43
+ .option('--languages <languages>', 'Target specific languages (comma-separated: typescript,dart,kotlin)')
44
+ .option('--include-tests', 'Include test files in analysis (default: true)')
45
+ .option('--exclude-tests', 'Exclude test files from analysis')
46
+ .option('--only-source', 'Only analyze source files (exclude tests, configs, etc.)');
47
+
39
48
  // CI/CD and Git integration options
40
49
  program
41
50
  .option('--changed-files', 'Only analyze files changed in current branch (git diff)')
@@ -74,6 +83,12 @@ Examples:
74
83
  $ sunlint --security --input=src
75
84
  $ sunlint --category=logging --input=src
76
85
 
86
+ File Targeting:
87
+ $ sunlint --all --include="src/**/*.ts" --exclude="**/*.test.*" --input=.
88
+ $ sunlint --all --languages=typescript,dart --input=src
89
+ $ sunlint --typescript --exclude-tests --input=src
90
+ $ sunlint --all --only-source --include="src/**,lib/**" --input=.
91
+
77
92
  TypeScript Analysis (Phase 1):
78
93
  $ sunlint --typescript --input=src
79
94
  $ sunlint --rule=C006 --typescript --input=src
@@ -92,10 +107,11 @@ ESLint Integration:
92
107
  $ sunlint --all --eslint-integration --eslint-run-after --input=src
93
108
  $ sunlint --typescript --eslint-integration --changed-files
94
109
 
95
- AI-powered Analysis:
96
- $ sunlint --rule=C019 --input=src --ai
97
- $ sunlint --all --input=src --ai
98
- $ sunlint --quality --input=src --no-ai
110
+ Advanced File Targeting:
111
+ $ sunlint --all --include="src/**/*.ts,lib/**/*.dart" --exclude="**/*.generated.*" --input=.
112
+ $ sunlint --typescript --exclude="**/*.d.ts,**/*.test.*" --input=src
113
+ $ sunlint --languages=typescript,dart --include="src/**,packages/**" --input=.
114
+ $ sunlint --all --only-source --exclude-tests --languages=typescript --input=.
99
115
 
100
116
  Sun* Engineering - Coding Standards Made Simple ☀️
101
117
  `);
@@ -28,11 +28,90 @@ class ConfigManager {
28
28
  this.defaultConfig = {
29
29
  rules: {},
30
30
  categories: {},
31
- languages: ['typescript', 'dart'],
32
- include: ['**/*.ts', '**/*.tsx', '**/*.dart', '**/*.js', '**/*.jsx'],
33
- exclude: ['**/node_modules/**', '**/build/**', '**/dist/**'],
31
+
32
+ // Enhanced language-specific configuration
33
+ languages: {
34
+ typescript: {
35
+ include: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
36
+ exclude: ['**/*.d.ts', '**/*.test.ts', '**/*.spec.ts'],
37
+ parser: 'typescript'
38
+ },
39
+ javascript: {
40
+ include: ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs'],
41
+ exclude: ['**/*.min.js', '**/*.bundle.js'],
42
+ parser: 'espree'
43
+ },
44
+ dart: {
45
+ include: ['**/*.dart'],
46
+ exclude: ['**/*.g.dart', '**/*.freezed.dart', '**/*.mocks.dart'],
47
+ parser: 'dart'
48
+ },
49
+ kotlin: {
50
+ include: ['**/*.kt', '**/*.kts'],
51
+ exclude: ['**/build/**', '**/generated/**'],
52
+ parser: 'kotlin'
53
+ }
54
+ },
55
+
56
+ // Global file patterns (cross-language)
57
+ include: [
58
+ 'src/**',
59
+ 'lib/**',
60
+ 'app/**',
61
+ 'packages/**'
62
+ ],
63
+
64
+ exclude: [
65
+ 'node_modules/**',
66
+ 'dist/**',
67
+ 'build/**',
68
+ 'coverage/**',
69
+ '.git/**',
70
+ '**/*.min.*',
71
+ '**/*.bundle.*',
72
+ '**/generated/**',
73
+ '**/*.generated.*',
74
+ '.next/**',
75
+ '.nuxt/**',
76
+ 'vendor/**'
77
+ ],
78
+
79
+ // Test file patterns with specific rules
80
+ testPatterns: {
81
+ include: ['**/*.test.*', '**/*.spec.*', '**/test/**', '**/tests/**', '**/__tests__/**'],
82
+ rules: {
83
+ 'C006': 'off', // Function naming less strict in tests
84
+ 'C019': 'warn' // Log level still important in tests
85
+ }
86
+ },
87
+
88
+ // Legacy support (backward compatibility)
34
89
  ignorePatterns: [],
35
- overrides: [],
90
+
91
+ // Rule-specific overrides for different contexts
92
+ overrides: [
93
+ {
94
+ files: ['**/*.d.ts'],
95
+ rules: {
96
+ 'C006': 'off',
97
+ 'C007': 'off'
98
+ }
99
+ },
100
+ {
101
+ files: ['**/migrations/**', '**/seeds/**'],
102
+ rules: {
103
+ 'C031': 'off' // Validation separation not needed in migrations
104
+ }
105
+ },
106
+ {
107
+ files: ['**/config/**', '**/*.config.*'],
108
+ rules: {
109
+ 'C006': 'warn', // Config files may have different naming
110
+ 'C015': 'off' // Domain language not strict in config
111
+ }
112
+ }
113
+ ],
114
+
36
115
  env: {},
37
116
  parserOptions: {},
38
117
  // ESLint Integration Configuration
@@ -115,7 +194,7 @@ class ConfigManager {
115
194
  }
116
195
  } else {
117
196
  // Load from dedicated config file
118
- projectConfig = this.sourceLoader.loadConfiguration(resolvedConfigPath, cliOptions.verbose);
197
+ projectConfig = this.sourceLoader.loadSpecificConfigFile(resolvedConfigPath, cliOptions.verbose);
119
198
  }
120
199
 
121
200
  if (projectConfig) {