@sun-asterisk/sunlint 1.0.5 → 1.0.7
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/CHANGELOG.md +108 -169
- package/README.md +124 -383
- package/config/presets/beginner.json +1 -1
- package/config/presets/ci.json +3 -2
- package/config/presets/recommended.json +1 -1
- package/config/presets/strict.json +3 -2
- package/config/rules-registry.json +60 -0
- package/config/sunlint-schema.json +0 -7
- package/config/typescript/eslint.config.js +4 -0
- package/core/cli-action-handler.js +169 -4
- package/core/cli-program.js +20 -4
- package/core/config-manager.js +91 -11
- package/core/config-merger.js +12 -0
- package/core/file-targeting-service.js +381 -0
- package/core/multi-rule-runner.js +9 -27
- package/core/output-service.js +5 -6
- package/core/rule-mapping-service.js +8 -0
- package/package.json +3 -5
- package/cli-legacy.js +0 -355
- package/docs/AI.md +0 -163
- package/docs/ARCHITECTURE.md +0 -78
- package/docs/CI-CD-GUIDE.md +0 -315
- package/docs/COMMAND-EXAMPLES.md +0 -256
- package/docs/DEBUG.md +0 -86
- package/docs/DISTRIBUTION.md +0 -153
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
- package/docs/ESLINT_INTEGRATION.md +0 -238
- package/docs/FOLDER_STRUCTURE.md +0 -59
- package/docs/HEURISTIC_VS_AI.md +0 -113
- package/docs/README.md +0 -32
- package/docs/RELEASE_GUIDE.md +0 -230
- package/docs/RULE-RESPONSIBILITY-MATRIX.md +0 -204
- package/eslint-integration/.eslintrc.js +0 -98
- package/eslint-integration/cli.js +0 -35
- package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +0 -204
- package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +0 -246
- package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +0 -207
- package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +0 -90
- package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +0 -43
- package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +0 -38
- package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +0 -39
- package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +0 -335
- package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +0 -142
- package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +0 -50
- package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +0 -80
- package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +0 -294
- package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +0 -34
- package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +0 -32
- package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +0 -64
- package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +0 -406
- package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +0 -300
- package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +0 -239
- package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +0 -31
- package/eslint-integration/eslint-plugin-custom/c076-one-assert-per-test.js +0 -184
- package/eslint-integration/eslint-plugin-custom/index.js +0 -155
- package/eslint-integration/eslint-plugin-custom/package.json +0 -13
- package/eslint-integration/eslint-plugin-custom/package.json.bak +0 -9
- package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +0 -86
- package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +0 -95
- package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +0 -69
- package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +0 -62
- package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +0 -103
- package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +0 -123
- package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +0 -66
- package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +0 -71
- package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +0 -50
- package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +0 -43
- package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +0 -59
- package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +0 -193
- package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +0 -56
- package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +0 -113
- package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +0 -89
- package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +0 -78
- package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +0 -300
- package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +0 -217
- package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +0 -68
- package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +0 -80
- package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +0 -79
- package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +0 -78
- package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +0 -80
- package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +0 -77
- package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +0 -74
- package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +0 -68
- package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +0 -70
- package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +0 -74
- package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +0 -63
- package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +0 -211
- package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +0 -294
- package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +0 -254
- package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +0 -292
- package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +0 -46
- package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +0 -44
- package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +0 -108
- package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +0 -54
- package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +0 -94
- package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +0 -66
- package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +0 -109
- package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +0 -143
- package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +0 -54
- package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +0 -73
- package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +0 -42
- package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +0 -48
- package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +0 -160
- package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +0 -52
- package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +0 -175
- package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +0 -95
- package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +0 -48
- package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +0 -377
- package/eslint-integration/eslint.config.js +0 -125
- package/eslint-integration/eslint.config.simple.js +0 -24
- package/eslint-integration/node_modules/eslint-plugin-custom/package.json +0 -0
- package/eslint-integration/package.json +0 -23
- package/eslint-integration/sample.ts +0 -53
- package/eslint-integration/test-s003.js +0 -5
- package/eslint-integration/tsconfig.json +0 -27
- package/examples/.github/workflows/code-quality.yml +0 -111
- package/examples/.sunlint.json +0 -42
- package/examples/README.md +0 -47
- package/examples/package.json +0 -33
|
@@ -48,6 +48,42 @@
|
|
|
48
48
|
"status": "experimental",
|
|
49
49
|
"tags": ["validation", "separation", "architecture"]
|
|
50
50
|
},
|
|
51
|
+
"C076": {
|
|
52
|
+
"name": "One Assert Per Test",
|
|
53
|
+
"description": "Each test should assert only one behavior (Single Assert Rule)",
|
|
54
|
+
"category": "testing",
|
|
55
|
+
"severity": "warning",
|
|
56
|
+
"languages": ["typescript", "javascript"],
|
|
57
|
+
"analyzer": "eslint",
|
|
58
|
+
"eslintRule": "custom/c076",
|
|
59
|
+
"version": "1.0.0",
|
|
60
|
+
"status": "stable",
|
|
61
|
+
"tags": ["testing", "unit-test", "assertion"]
|
|
62
|
+
},
|
|
63
|
+
"S001": {
|
|
64
|
+
"name": "Fail Securely",
|
|
65
|
+
"description": "Verify that if there is an error in access control, the system fails securely",
|
|
66
|
+
"category": "security",
|
|
67
|
+
"severity": "error",
|
|
68
|
+
"languages": ["typescript", "javascript"],
|
|
69
|
+
"analyzer": "eslint",
|
|
70
|
+
"eslintRule": "custom/typescript_s001",
|
|
71
|
+
"version": "1.0.0",
|
|
72
|
+
"status": "stable",
|
|
73
|
+
"tags": ["security", "access-control", "fail-safe"]
|
|
74
|
+
},
|
|
75
|
+
"S002": {
|
|
76
|
+
"name": "IDOR Check",
|
|
77
|
+
"description": "Insecure Direct Object Reference prevention",
|
|
78
|
+
"category": "security",
|
|
79
|
+
"severity": "error",
|
|
80
|
+
"languages": ["typescript", "javascript"],
|
|
81
|
+
"analyzer": "eslint",
|
|
82
|
+
"eslintRule": "custom/typescript_s002",
|
|
83
|
+
"version": "1.0.0",
|
|
84
|
+
"status": "stable",
|
|
85
|
+
"tags": ["security", "idor", "access-control"]
|
|
86
|
+
},
|
|
51
87
|
"S003": {
|
|
52
88
|
"name": "No Unvalidated Redirect",
|
|
53
89
|
"description": "Prevent unvalidated redirects and forwards",
|
|
@@ -84,6 +120,18 @@
|
|
|
84
120
|
"status": "stable",
|
|
85
121
|
"tags": ["security", "secrets", "encryption"]
|
|
86
122
|
},
|
|
123
|
+
"S007": {
|
|
124
|
+
"name": "No Plaintext OTP",
|
|
125
|
+
"description": "One-Time Passwords must not be stored in plaintext",
|
|
126
|
+
"category": "security",
|
|
127
|
+
"severity": "error",
|
|
128
|
+
"languages": ["typescript", "javascript"],
|
|
129
|
+
"analyzer": "eslint",
|
|
130
|
+
"eslintRule": "custom/typescript_s007",
|
|
131
|
+
"version": "1.0.0",
|
|
132
|
+
"status": "stable",
|
|
133
|
+
"tags": ["security", "otp", "encryption"]
|
|
134
|
+
},
|
|
87
135
|
"S008": {
|
|
88
136
|
"name": "Crypto Agility",
|
|
89
137
|
"description": "Ensure cryptographic agility and algorithm flexibility",
|
|
@@ -144,6 +192,18 @@
|
|
|
144
192
|
"status": "stable",
|
|
145
193
|
"tags": ["security", "secrets", "hardcoded"]
|
|
146
194
|
},
|
|
195
|
+
"S013": {
|
|
196
|
+
"name": "Verify TLS Connection",
|
|
197
|
+
"description": "Verify that TLS connections are properly established and validated",
|
|
198
|
+
"category": "security",
|
|
199
|
+
"severity": "error",
|
|
200
|
+
"languages": ["typescript", "javascript"],
|
|
201
|
+
"analyzer": "eslint",
|
|
202
|
+
"eslintRule": "custom/typescript_s013",
|
|
203
|
+
"version": "1.0.0",
|
|
204
|
+
"status": "stable",
|
|
205
|
+
"tags": ["security", "tls", "connection"]
|
|
206
|
+
},
|
|
147
207
|
"S014": {
|
|
148
208
|
"name": "Insecure TLS Version",
|
|
149
209
|
"description": "Prevent usage of insecure TLS versions",
|
|
@@ -100,13 +100,6 @@
|
|
|
100
100
|
"type": "string"
|
|
101
101
|
}
|
|
102
102
|
},
|
|
103
|
-
"ignorePatterns": {
|
|
104
|
-
"description": "Patterns to ignore (alias for exclude)",
|
|
105
|
-
"type": "array",
|
|
106
|
-
"items": {
|
|
107
|
-
"type": "string"
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
103
|
"maxConcurrent": {
|
|
111
104
|
"description": "Maximum number of concurrent rule executions",
|
|
112
105
|
"type": "integer",
|
|
@@ -62,14 +62,18 @@ module.exports = [
|
|
|
62
62
|
'custom/t026': 'warn',
|
|
63
63
|
|
|
64
64
|
// Enable all security rules as warnings by default
|
|
65
|
+
'custom/typescript_s001': 'warn',
|
|
66
|
+
'custom/typescript_s002': 'warn',
|
|
65
67
|
'custom/typescript_s003': 'warn',
|
|
66
68
|
'custom/typescript_s005': 'warn',
|
|
67
69
|
'custom/typescript_s006': 'warn',
|
|
70
|
+
'custom/typescript_s007': 'warn',
|
|
68
71
|
'custom/typescript_s008': 'warn',
|
|
69
72
|
'custom/typescript_s009': 'warn',
|
|
70
73
|
'custom/typescript_s010': 'warn',
|
|
71
74
|
'custom/typescript_s011': 'warn',
|
|
72
75
|
'custom/typescript_s012': 'warn',
|
|
76
|
+
'custom/typescript_s013': 'warn',
|
|
73
77
|
'custom/typescript_s014': 'warn',
|
|
74
78
|
'custom/typescript_s015': 'warn',
|
|
75
79
|
'custom/typescript_s016': 'warn',
|
|
@@ -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
|
-
|
|
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) {
|
package/core/cli-program.js
CHANGED
|
@@ -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
|
-
|
|
96
|
-
$ sunlint --
|
|
97
|
-
$ sunlint --
|
|
98
|
-
$ sunlint --
|
|
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
|
`);
|
package/core/config-manager.js
CHANGED
|
@@ -28,11 +28,87 @@ class ConfigManager {
|
|
|
28
28
|
this.defaultConfig = {
|
|
29
29
|
rules: {},
|
|
30
30
|
categories: {},
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
// Rule-specific overrides for different contexts
|
|
89
|
+
overrides: [
|
|
90
|
+
{
|
|
91
|
+
files: ['**/*.d.ts'],
|
|
92
|
+
rules: {
|
|
93
|
+
'C006': 'off',
|
|
94
|
+
'C007': 'off'
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
files: ['**/migrations/**', '**/seeds/**'],
|
|
99
|
+
rules: {
|
|
100
|
+
'C031': 'off' // Validation separation not needed in migrations
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
files: ['**/config/**', '**/*.config.*'],
|
|
105
|
+
rules: {
|
|
106
|
+
'C006': 'warn', // Config files may have different naming
|
|
107
|
+
'C015': 'off' // Domain language not strict in config
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
|
|
36
112
|
env: {},
|
|
37
113
|
parserOptions: {},
|
|
38
114
|
// ESLint Integration Configuration
|
|
@@ -115,7 +191,7 @@ class ConfigManager {
|
|
|
115
191
|
}
|
|
116
192
|
} else {
|
|
117
193
|
// Load from dedicated config file
|
|
118
|
-
projectConfig = this.sourceLoader.
|
|
194
|
+
projectConfig = this.sourceLoader.loadSpecificConfigFile(resolvedConfigPath, cliOptions.verbose);
|
|
119
195
|
}
|
|
120
196
|
|
|
121
197
|
if (projectConfig) {
|
|
@@ -126,21 +202,25 @@ class ConfigManager {
|
|
|
126
202
|
}
|
|
127
203
|
}
|
|
128
204
|
|
|
129
|
-
// 6. Load ignore patterns (.sunlintignore)
|
|
205
|
+
// 6. Load ignore patterns (.sunlintignore) and merge into exclude
|
|
130
206
|
const ignorePatterns = this.sourceLoader.loadIgnorePatterns(
|
|
131
207
|
projectConfig?.dir || process.cwd(),
|
|
132
208
|
cliOptions.verbose
|
|
133
209
|
);
|
|
134
|
-
|
|
210
|
+
if (ignorePatterns.length > 0) {
|
|
211
|
+
config.exclude = [...new Set([...(config.exclude || []), ...ignorePatterns])];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 7. Process any deprecated ignorePatterns in config
|
|
135
215
|
config = this.merger.processIgnorePatterns(config);
|
|
136
216
|
|
|
137
|
-
//
|
|
217
|
+
// 8. Apply CLI overrides (highest priority)
|
|
138
218
|
config = this.merger.applyCLIOverrides(config, cliOptions);
|
|
139
219
|
|
|
140
|
-
//
|
|
220
|
+
// 9. Resolve extends
|
|
141
221
|
config = await this.resolveExtends(config);
|
|
142
222
|
|
|
143
|
-
//
|
|
223
|
+
// 10. Validate config
|
|
144
224
|
this.validator.validateConfiguration(config);
|
|
145
225
|
|
|
146
226
|
return config;
|
package/core/config-merger.js
CHANGED
|
@@ -124,10 +124,22 @@ class ConfigMerger {
|
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
126
|
* Rule C006: processIgnorePatterns - verb-noun naming
|
|
127
|
+
* Convert deprecated ignorePatterns to exclude for backward compatibility
|
|
127
128
|
*/
|
|
128
129
|
processIgnorePatterns(config) {
|
|
129
130
|
if (config.ignorePatterns && config.ignorePatterns.length > 0) {
|
|
131
|
+
console.warn('⚠️ DEPRECATED: "ignorePatterns" is deprecated. Please use "exclude" instead.');
|
|
132
|
+
|
|
133
|
+
// Initialize exclude if it doesn't exist
|
|
134
|
+
if (!config.exclude) {
|
|
135
|
+
config.exclude = [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Merge ignorePatterns into exclude and remove duplicates
|
|
130
139
|
config.exclude = [...new Set([...config.exclude, ...config.ignorePatterns])];
|
|
140
|
+
|
|
141
|
+
// Remove the deprecated property
|
|
142
|
+
delete config.ignorePatterns;
|
|
131
143
|
}
|
|
132
144
|
return config;
|
|
133
145
|
}
|