@sun-asterisk/sunlint 1.0.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/CHANGELOG.md +202 -0
- package/LICENSE +21 -0
- package/README.md +490 -0
- package/cli-legacy.js +355 -0
- package/cli.js +35 -0
- package/config/default.json +22 -0
- package/config/presets/beginner.json +36 -0
- package/config/presets/ci.json +46 -0
- package/config/presets/recommended.json +24 -0
- package/config/presets/strict.json +32 -0
- package/config/rules-registry.json +681 -0
- package/config/sunlint-schema.json +166 -0
- package/config/typescript/custom-rules-new.js +0 -0
- package/config/typescript/custom-rules.js +9 -0
- package/config/typescript/eslint.config.js +110 -0
- package/config/typescript/package-lock.json +1585 -0
- package/config/typescript/package.json +13 -0
- package/config/typescript/security-rules/index.js +90 -0
- package/config/typescript/security-rules/s005-no-origin-auth.js +95 -0
- package/config/typescript/security-rules/s006-activation-recovery-secret-not-plaintext.js +69 -0
- package/config/typescript/security-rules/s008-crypto-agility.js +62 -0
- package/config/typescript/security-rules/s009-no-insecure-crypto.js +103 -0
- package/config/typescript/security-rules/s010-no-insecure-random-in-sensitive-context.js +123 -0
- package/config/typescript/security-rules/s011-no-insecure-uuid.js +66 -0
- package/config/typescript/security-rules/s012-hardcode-secret.js +71 -0
- package/config/typescript/security-rules/s014-insecure-tls-version.js +50 -0
- package/config/typescript/security-rules/s015-insecure-tls-certificate.js +43 -0
- package/config/typescript/security-rules/s016-sensitive-query-parameter.js +59 -0
- package/config/typescript/security-rules/s017-no-sql-injection.js +193 -0
- package/config/typescript/security-rules/s018-positive-input-validation.js +56 -0
- package/config/typescript/security-rules/s019-no-raw-user-input-in-email.js +113 -0
- package/config/typescript/security-rules/s020-no-eval-dynamic-execution.js +89 -0
- package/config/typescript/security-rules/s022-output-encoding.js +78 -0
- package/config/typescript/security-rules/s023-no-json-injection.js +300 -0
- package/config/typescript/security-rules/s025-server-side-input-validation.js +217 -0
- package/config/typescript/security-rules/s026-json-schema-validation.js +68 -0
- package/config/typescript/security-rules/s027-no-hardcoded-secrets.js +80 -0
- package/config/typescript/security-rules/s029-require-csrf-protection.js +79 -0
- package/config/typescript/security-rules/s030-no-directory-browsing.js +78 -0
- package/config/typescript/security-rules/s033-require-samesite-cookie.js +80 -0
- package/config/typescript/security-rules/s034-require-host-cookie-prefix.js +77 -0
- package/config/typescript/security-rules/s035-cookie-specific-path.js +74 -0
- package/config/typescript/security-rules/s036-no-unsafe-file-include.js +68 -0
- package/config/typescript/security-rules/s037-require-anti-cache-headers.js +70 -0
- package/config/typescript/security-rules/s038-no-version-disclosure.js +74 -0
- package/config/typescript/security-rules/s039-no-session-token-in-url.js +63 -0
- package/config/typescript/security-rules/s041-require-session-invalidate-on-logout.js +211 -0
- package/config/typescript/security-rules/s042-require-periodic-reauthentication.js +294 -0
- package/config/typescript/security-rules/s043-terminate-sessions-on-password-change.js +254 -0
- package/config/typescript/security-rules/s044-require-full-session-for-sensitive-operations.js +292 -0
- package/config/typescript/security-rules/s045-anti-automation-controls.js +46 -0
- package/config/typescript/security-rules/s046-secure-notification-on-auth-change.js +44 -0
- package/config/typescript/security-rules/s048-password-credential-recovery.js +54 -0
- package/config/typescript/security-rules/s050-session-token-weak-hash.js +94 -0
- package/config/typescript/security-rules/s052-secure-random-authentication-code.js +66 -0
- package/config/typescript/security-rules/s054-verification-default-account.js +109 -0
- package/config/typescript/security-rules/s057-utc-logging.js +54 -0
- package/config/typescript/security-rules/s058-no-ssrf.js +73 -0
- package/config/typescript/test-s005-working.ts +22 -0
- package/config/typescript/tsconfig.json +29 -0
- package/core/ai-analyzer.js +169 -0
- package/core/analysis-orchestrator.js +705 -0
- package/core/cli-action-handler.js +230 -0
- package/core/cli-program.js +106 -0
- package/core/config-manager.js +396 -0
- package/core/config-merger.js +136 -0
- package/core/config-override-processor.js +74 -0
- package/core/config-preset-resolver.js +65 -0
- package/core/config-source-loader.js +152 -0
- package/core/config-validator.js +126 -0
- package/core/dependency-manager.js +105 -0
- package/core/eslint-engine-service.js +312 -0
- package/core/eslint-instance-manager.js +104 -0
- package/core/eslint-integration-service.js +363 -0
- package/core/git-utils.js +170 -0
- package/core/multi-rule-runner.js +239 -0
- package/core/output-service.js +250 -0
- package/core/report-generator.js +320 -0
- package/core/rule-mapping-service.js +309 -0
- package/core/rule-selection-service.js +121 -0
- package/core/sunlint-engine-service.js +23 -0
- package/core/typescript-analyzer.js +262 -0
- package/core/typescript-engine.js +313 -0
- package/docs/AI.md +163 -0
- package/docs/ARCHITECTURE.md +78 -0
- package/docs/CI-CD-GUIDE.md +315 -0
- package/docs/COMMAND-EXAMPLES.md +256 -0
- package/docs/DEBUG.md +86 -0
- package/docs/DISTRIBUTION.md +153 -0
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
- package/docs/ESLINT_INTEGRATION.md +238 -0
- package/docs/FOLDER_STRUCTURE.md +59 -0
- package/docs/HEURISTIC_VS_AI.md +113 -0
- package/docs/README.md +32 -0
- package/docs/RELEASE_GUIDE.md +230 -0
- package/docs/RULE-RESPONSIBILITY-MATRIX.md +204 -0
- package/eslint-integration/.eslintrc.js +98 -0
- package/eslint-integration/cli.js +35 -0
- package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +204 -0
- package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +246 -0
- package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +207 -0
- package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +90 -0
- package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +43 -0
- package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +38 -0
- package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +39 -0
- package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +335 -0
- package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +142 -0
- package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +50 -0
- package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +80 -0
- package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +294 -0
- package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +34 -0
- package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +32 -0
- package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +64 -0
- package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +406 -0
- package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +300 -0
- package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +239 -0
- package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +31 -0
- package/eslint-integration/eslint-plugin-custom/c076-one-assert-per-test.js +184 -0
- package/eslint-integration/eslint-plugin-custom/index.js +155 -0
- package/eslint-integration/eslint-plugin-custom/package.json +13 -0
- package/eslint-integration/eslint-plugin-custom/package.json.bak +9 -0
- package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +86 -0
- package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +95 -0
- package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +69 -0
- package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +62 -0
- package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +103 -0
- package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +123 -0
- package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +66 -0
- package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +71 -0
- package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +50 -0
- package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +43 -0
- package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +59 -0
- package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +193 -0
- package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +56 -0
- package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +113 -0
- package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +89 -0
- package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +78 -0
- package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +300 -0
- package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +217 -0
- package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +68 -0
- package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +80 -0
- package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +79 -0
- package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +78 -0
- package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +80 -0
- package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +77 -0
- package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +74 -0
- package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +68 -0
- package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +70 -0
- package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +74 -0
- package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +63 -0
- package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +211 -0
- package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +294 -0
- package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +254 -0
- package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +292 -0
- package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +46 -0
- package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +44 -0
- package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +108 -0
- package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +54 -0
- package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +94 -0
- package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +66 -0
- package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +109 -0
- package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +143 -0
- package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +54 -0
- package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +73 -0
- package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +42 -0
- package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +48 -0
- package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +160 -0
- package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +52 -0
- package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +175 -0
- package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +95 -0
- package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +48 -0
- package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +377 -0
- package/eslint-integration/eslint.config.js +125 -0
- package/eslint-integration/eslint.config.simple.js +24 -0
- package/eslint-integration/node_modules/eslint-plugin-custom/package.json +0 -0
- package/eslint-integration/package.json +23 -0
- package/eslint-integration/sample.ts +53 -0
- package/eslint-integration/test-s003.js +5 -0
- package/eslint-integration/tsconfig.json +27 -0
- package/examples/.github/workflows/code-quality.yml +111 -0
- package/examples/.sunlint.json +42 -0
- package/examples/README.md +47 -0
- package/examples/package.json +33 -0
- package/package.json +100 -0
- package/rules/C006_function_naming/analyzer.js +338 -0
- package/rules/C006_function_naming/config.json +86 -0
- package/rules/C019_log_level_usage/analyzer.js +359 -0
- package/rules/C019_log_level_usage/config.json +121 -0
- package/rules/C029_catch_block_logging/analyzer.js +339 -0
- package/rules/C029_catch_block_logging/config.json +59 -0
- package/rules/C031_validation_separation/README.md +72 -0
- package/rules/C031_validation_separation/analyzer.js +186 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
// Rule C014: Dependency injection instead of direct instantiation
|
|
7
|
+
const ConfigSourceLoader = require('./config-source-loader');
|
|
8
|
+
const ConfigPresetResolver = require('./config-preset-resolver');
|
|
9
|
+
const ConfigMerger = require('./config-merger');
|
|
10
|
+
const ConfigValidator = require('./config-validator');
|
|
11
|
+
const ConfigOverrideProcessor = require('./config-override-processor');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Main configuration manager - orchestrates config loading process
|
|
15
|
+
* Rule C005: Single responsibility - orchestrates other config services
|
|
16
|
+
* Rule C015: Domain language - ConfigManager as main coordinator
|
|
17
|
+
* Rule C014: Uses dependency injection for all services
|
|
18
|
+
*/
|
|
19
|
+
class ConfigManager {
|
|
20
|
+
constructor() {
|
|
21
|
+
// Rule C014: Dependency injection
|
|
22
|
+
this.sourceLoader = new ConfigSourceLoader();
|
|
23
|
+
this.presetResolver = new ConfigPresetResolver();
|
|
24
|
+
this.merger = new ConfigMerger();
|
|
25
|
+
this.validator = new ConfigValidator();
|
|
26
|
+
this.overrideProcessor = new ConfigOverrideProcessor();
|
|
27
|
+
|
|
28
|
+
this.defaultConfig = {
|
|
29
|
+
rules: {},
|
|
30
|
+
categories: {},
|
|
31
|
+
languages: ['typescript', 'dart'],
|
|
32
|
+
include: ['**/*.ts', '**/*.tsx', '**/*.dart', '**/*.js', '**/*.jsx'],
|
|
33
|
+
exclude: ['**/node_modules/**', '**/build/**', '**/dist/**'],
|
|
34
|
+
ignorePatterns: [],
|
|
35
|
+
overrides: [],
|
|
36
|
+
env: {},
|
|
37
|
+
parserOptions: {},
|
|
38
|
+
// ESLint Integration Configuration
|
|
39
|
+
eslintIntegration: {
|
|
40
|
+
enabled: false,
|
|
41
|
+
mergeRules: true,
|
|
42
|
+
preserveUserConfig: true,
|
|
43
|
+
runAfterSunLint: false
|
|
44
|
+
},
|
|
45
|
+
output: {
|
|
46
|
+
format: 'eslint',
|
|
47
|
+
console: true,
|
|
48
|
+
summary: true
|
|
49
|
+
},
|
|
50
|
+
ai: {
|
|
51
|
+
enabled: false,
|
|
52
|
+
fallbackToPattern: true,
|
|
53
|
+
provider: 'openai',
|
|
54
|
+
model: 'gpt-4o-mini'
|
|
55
|
+
},
|
|
56
|
+
performance: {
|
|
57
|
+
maxConcurrentRules: 5,
|
|
58
|
+
timeoutMs: 30000,
|
|
59
|
+
cacheEnabled: true,
|
|
60
|
+
cacheLocation: '.sunlint-cache/'
|
|
61
|
+
},
|
|
62
|
+
reporting: {
|
|
63
|
+
includeContext: true,
|
|
64
|
+
showFixSuggestions: true,
|
|
65
|
+
groupByFile: true,
|
|
66
|
+
sortBy: 'severity',
|
|
67
|
+
showProgress: true,
|
|
68
|
+
exitOnError: false
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Rule C006: loadConfiguration - verb-noun naming
|
|
75
|
+
* Rule C005: Single responsibility - orchestrates config loading
|
|
76
|
+
* Rule C012: Command method - loads and returns config
|
|
77
|
+
*/
|
|
78
|
+
async loadConfiguration(configPath, cliOptions = {}) {
|
|
79
|
+
// 1. Start with built-in defaults
|
|
80
|
+
let config = { ...this.defaultConfig };
|
|
81
|
+
|
|
82
|
+
// 2. Environment variables
|
|
83
|
+
config = this.merger.applyEnvironmentVariables(config);
|
|
84
|
+
|
|
85
|
+
// 3. Global config (~/.sunlint.json)
|
|
86
|
+
const globalConfig = this.sourceLoader.loadGlobalConfiguration(os.homedir(), cliOptions.verbose);
|
|
87
|
+
if (globalConfig) {
|
|
88
|
+
config = this.merger.mergeConfigurations(config, globalConfig.config);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 4. Auto-discover project config if not explicitly provided
|
|
92
|
+
let resolvedConfigPath = configPath;
|
|
93
|
+
if (!configPath || configPath === '.sunlint.json') {
|
|
94
|
+
const discoveredConfig = this.findConfigFile(cliOptions.input || process.cwd());
|
|
95
|
+
if (discoveredConfig) {
|
|
96
|
+
resolvedConfigPath = discoveredConfig;
|
|
97
|
+
if (cliOptions.verbose) {
|
|
98
|
+
console.log(chalk.gray(`🔍 Auto-discovered config: ${discoveredConfig}`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 5. Load project config (explicit or discovered)
|
|
104
|
+
let projectConfig = null;
|
|
105
|
+
if (resolvedConfigPath && fs.existsSync(resolvedConfigPath)) {
|
|
106
|
+
if (resolvedConfigPath.endsWith('package.json')) {
|
|
107
|
+
// Load from package.json sunlint field
|
|
108
|
+
const pkg = JSON.parse(fs.readFileSync(resolvedConfigPath, 'utf8'));
|
|
109
|
+
if (pkg.sunlint) {
|
|
110
|
+
projectConfig = {
|
|
111
|
+
config: pkg.sunlint,
|
|
112
|
+
path: resolvedConfigPath,
|
|
113
|
+
dir: path.dirname(resolvedConfigPath)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
// Load from dedicated config file
|
|
118
|
+
projectConfig = this.sourceLoader.loadConfiguration(resolvedConfigPath, cliOptions.verbose);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (projectConfig) {
|
|
122
|
+
config = this.merger.mergeConfigurations(config, projectConfig.config);
|
|
123
|
+
if (cliOptions.verbose) {
|
|
124
|
+
console.log(chalk.gray(`📄 Loaded project config: ${projectConfig.path}`));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 6. Load ignore patterns (.sunlintignore)
|
|
130
|
+
const ignorePatterns = this.sourceLoader.loadIgnorePatterns(
|
|
131
|
+
projectConfig?.dir || process.cwd(),
|
|
132
|
+
cliOptions.verbose
|
|
133
|
+
);
|
|
134
|
+
config.ignorePatterns = [...(config.ignorePatterns || []), ...ignorePatterns];
|
|
135
|
+
config = this.merger.processIgnorePatterns(config);
|
|
136
|
+
|
|
137
|
+
// 7. Apply CLI overrides (highest priority)
|
|
138
|
+
config = this.merger.applyCLIOverrides(config, cliOptions);
|
|
139
|
+
|
|
140
|
+
// 8. Resolve extends
|
|
141
|
+
config = await this.resolveExtends(config);
|
|
142
|
+
|
|
143
|
+
// 9. Validate config
|
|
144
|
+
this.validator.validateConfiguration(config);
|
|
145
|
+
|
|
146
|
+
return config;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
mergeConfigs(base, override) {
|
|
150
|
+
const merged = { ...base };
|
|
151
|
+
|
|
152
|
+
for (const [key, value] of Object.entries(override)) {
|
|
153
|
+
if (key === 'rules' && typeof value === 'object') {
|
|
154
|
+
merged.rules = { ...merged.rules, ...value };
|
|
155
|
+
} else if (key === 'categories' && typeof value === 'object') {
|
|
156
|
+
merged.categories = { ...merged.categories, ...value };
|
|
157
|
+
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
158
|
+
merged[key] = { ...merged[key], ...value };
|
|
159
|
+
} else {
|
|
160
|
+
merged[key] = value;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return merged;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
applyCLIOverrides(config, options) {
|
|
168
|
+
const overrides = { ...config };
|
|
169
|
+
|
|
170
|
+
// Languages override
|
|
171
|
+
if (options.languages) {
|
|
172
|
+
overrides.languages = options.languages.split(',').map(l => l.trim());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Output format override
|
|
176
|
+
if (options.format) {
|
|
177
|
+
overrides.output = { ...overrides.output, format: options.format };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// AI override
|
|
181
|
+
if (options.ai === true) {
|
|
182
|
+
overrides.ai = { ...overrides.ai, enabled: true };
|
|
183
|
+
}
|
|
184
|
+
if (options.ai === false) {
|
|
185
|
+
overrides.ai = { ...overrides.ai, enabled: false };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Performance overrides
|
|
189
|
+
if (options.maxConcurrent) {
|
|
190
|
+
overrides.performance = {
|
|
191
|
+
...overrides.performance,
|
|
192
|
+
maxConcurrentRules: parseInt(options.maxConcurrent)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (options.timeout) {
|
|
197
|
+
overrides.performance = {
|
|
198
|
+
...overrides.performance,
|
|
199
|
+
timeoutMs: parseInt(options.timeout)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Cache override
|
|
204
|
+
if (options.cache === false) {
|
|
205
|
+
overrides.performance = {
|
|
206
|
+
...overrides.performance,
|
|
207
|
+
cacheEnabled: false
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return overrides;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Rule C006: resolveExtends - verb-noun naming
|
|
216
|
+
* Rule C005: Single responsibility - only handles extends resolution
|
|
217
|
+
*/
|
|
218
|
+
async resolveExtends(config) {
|
|
219
|
+
if (!config.extends) {
|
|
220
|
+
return config;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const extends_ = Array.isArray(config.extends) ? config.extends : [config.extends];
|
|
224
|
+
let resolvedConfig = { ...config };
|
|
225
|
+
|
|
226
|
+
for (const extendPath of extends_) {
|
|
227
|
+
try {
|
|
228
|
+
// Check if it's a preset
|
|
229
|
+
if (extendPath.startsWith('@sun/sunlint/')) {
|
|
230
|
+
const presetConfig = await this.presetResolver.loadPresetConfiguration(extendPath);
|
|
231
|
+
resolvedConfig = this.merger.mergeConfigurations(presetConfig, resolvedConfig);
|
|
232
|
+
} else {
|
|
233
|
+
const extendedConfig = await this.loadExtendedConfig(extendPath);
|
|
234
|
+
resolvedConfig = this.merger.mergeConfigurations(extendedConfig, resolvedConfig);
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.error(chalk.yellow(`⚠️ Failed to extend config '${extendPath}':`), error.message);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Remove extends to avoid circular references
|
|
242
|
+
delete resolvedConfig.extends;
|
|
243
|
+
|
|
244
|
+
return resolvedConfig;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Rule C006: loadExtendedConfig - verb-noun naming
|
|
249
|
+
*/
|
|
250
|
+
async loadExtendedConfig(extendPath) {
|
|
251
|
+
if (extendPath.startsWith('@sun/sunlint/')) {
|
|
252
|
+
// Load preset from rules registry
|
|
253
|
+
const presetName = extendPath.replace('@sun/sunlint/', '');
|
|
254
|
+
const rulesRegistry = require('../config/rules-registry.json');
|
|
255
|
+
return this.presetResolver.resolvePresetFromRegistry(presetName, rulesRegistry);
|
|
256
|
+
} else {
|
|
257
|
+
// Load from file path
|
|
258
|
+
const configPath = path.resolve(extendPath);
|
|
259
|
+
if (fs.existsSync(configPath)) {
|
|
260
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
261
|
+
} else {
|
|
262
|
+
throw new Error(`Config file not found: ${configPath}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Rule C006: applyFileOverrides - verb-noun naming
|
|
269
|
+
* Rule C014: Delegate to override processor
|
|
270
|
+
*/
|
|
271
|
+
applyFileOverrides(config, filePath) {
|
|
272
|
+
return this.overrideProcessor.applyFileOverrides(config, filePath);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Rule C006: getEffectiveRuleConfiguration - verb-noun naming
|
|
277
|
+
* Rule C014: Delegate to validator
|
|
278
|
+
*/
|
|
279
|
+
getEffectiveRuleConfiguration(ruleId, config) {
|
|
280
|
+
const rulesRegistry = require('../config/rules-registry.json');
|
|
281
|
+
return this.validator.getEffectiveRuleConfiguration(ruleId, config, rulesRegistry);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Rule C006: normalizeRuleValue - verb-noun naming
|
|
286
|
+
* Rule C014: Delegate to validator
|
|
287
|
+
*/
|
|
288
|
+
normalizeRuleValue(value) {
|
|
289
|
+
return this.validator.normalizeRuleValue(value);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Find configuration file using discovery hierarchy
|
|
294
|
+
* Following Rule C005: Single responsibility - only handle config discovery
|
|
295
|
+
* @param {string} startPath - Starting directory for config search
|
|
296
|
+
* @returns {string|null} Path to config file or null if not found
|
|
297
|
+
*/
|
|
298
|
+
findConfigFile(startPath = process.cwd()) {
|
|
299
|
+
const configNames = [
|
|
300
|
+
'.sunlint.json',
|
|
301
|
+
'.sunlint.js',
|
|
302
|
+
'sunlint.config.js',
|
|
303
|
+
'sunlint.config.json'
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
let currentPath = path.resolve(startPath);
|
|
307
|
+
|
|
308
|
+
// Traverse up directory tree
|
|
309
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
310
|
+
for (const configName of configNames) {
|
|
311
|
+
const configPath = path.join(currentPath, configName);
|
|
312
|
+
if (fs.existsSync(configPath)) {
|
|
313
|
+
return configPath;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
currentPath = path.dirname(currentPath);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Check for package.json with sunlint field
|
|
320
|
+
const packageConfigPath = this.findPackageConfig(startPath);
|
|
321
|
+
if (packageConfigPath) {
|
|
322
|
+
return packageConfigPath;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Find package.json with sunlint configuration
|
|
330
|
+
* @param {string} startPath - Starting directory
|
|
331
|
+
* @returns {string|null} Path to package.json or null
|
|
332
|
+
*/
|
|
333
|
+
findPackageConfig(startPath = process.cwd()) {
|
|
334
|
+
let currentPath = path.resolve(startPath);
|
|
335
|
+
|
|
336
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
337
|
+
const packagePath = path.join(currentPath, 'package.json');
|
|
338
|
+
if (fs.existsSync(packagePath)) {
|
|
339
|
+
try {
|
|
340
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
341
|
+
if (pkg.sunlint) {
|
|
342
|
+
return packagePath;
|
|
343
|
+
}
|
|
344
|
+
} catch (error) {
|
|
345
|
+
// Continue searching if package.json is invalid
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
currentPath = path.dirname(currentPath);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Find project root directory (where package.json exists)
|
|
356
|
+
* @param {string} startPath - Starting directory
|
|
357
|
+
* @returns {string} Project root path or startPath if not found
|
|
358
|
+
*/
|
|
359
|
+
findProjectRoot(startPath = process.cwd()) {
|
|
360
|
+
let currentPath = path.resolve(startPath);
|
|
361
|
+
|
|
362
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
363
|
+
const packagePath = path.join(currentPath, 'package.json');
|
|
364
|
+
if (fs.existsSync(packagePath)) {
|
|
365
|
+
return currentPath;
|
|
366
|
+
}
|
|
367
|
+
currentPath = path.dirname(currentPath);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return startPath;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Legacy method names for backward compatibility
|
|
374
|
+
// Rule C006: Maintaining existing API while delegating to new services
|
|
375
|
+
async loadConfig(configPath, cliOptions) {
|
|
376
|
+
return this.loadConfiguration(configPath, cliOptions);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
mergeConfigs(base, override) {
|
|
380
|
+
return this.merger.mergeConfigurations(base, override);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
applyCLIOverrides(config, options) {
|
|
384
|
+
return this.merger.applyCLIOverrides(config, options);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
applyOverrides(config, filePath) {
|
|
388
|
+
return this.overrideProcessor.applyFileOverrides(config, filePath);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
validateConfig(config) {
|
|
392
|
+
return this.validator.validateConfiguration(config);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = ConfigManager;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles configuration merging and CLI overrides
|
|
3
|
+
* Rule C005: Single responsibility - chỉ merge và override config
|
|
4
|
+
* Rule C015: Domain language - ConfigMerger
|
|
5
|
+
*/
|
|
6
|
+
class ConfigMerger {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Rule C006: mergeConfigurations - verb-noun naming
|
|
10
|
+
* Rule C012: Pure function - no side effects, clear input/output
|
|
11
|
+
*/
|
|
12
|
+
mergeConfigurations(base, override) {
|
|
13
|
+
const merged = { ...base };
|
|
14
|
+
|
|
15
|
+
for (const [key, value] of Object.entries(override)) {
|
|
16
|
+
if (key === 'rules' && typeof value === 'object') {
|
|
17
|
+
merged.rules = { ...merged.rules, ...value };
|
|
18
|
+
} else if (key === 'categories' && typeof value === 'object') {
|
|
19
|
+
merged.categories = { ...merged.categories, ...value };
|
|
20
|
+
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
21
|
+
merged[key] = { ...merged[key], ...value };
|
|
22
|
+
} else {
|
|
23
|
+
merged[key] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return merged;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Rule C006: applyCLIOverrides - verb-noun naming
|
|
32
|
+
* Rule C012: Pure function - no side effects
|
|
33
|
+
*/
|
|
34
|
+
applyCLIOverrides(config, options) {
|
|
35
|
+
const overrides = { ...config };
|
|
36
|
+
|
|
37
|
+
// Languages override
|
|
38
|
+
if (options.languages) {
|
|
39
|
+
overrides.languages = options.languages.split(',').map(l => l.trim());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Output format override
|
|
43
|
+
if (options.format) {
|
|
44
|
+
overrides.output = { ...overrides.output, format: options.format };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// AI overrides
|
|
48
|
+
overrides.ai = this.applyAIOverrides(overrides.ai, options);
|
|
49
|
+
|
|
50
|
+
// Performance overrides
|
|
51
|
+
overrides.performance = this.applyPerformanceOverrides(overrides.performance, options);
|
|
52
|
+
|
|
53
|
+
return overrides;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Rule C006: applyAIOverrides - verb-noun naming
|
|
58
|
+
* Rule C005: Extracted for single responsibility
|
|
59
|
+
*/
|
|
60
|
+
applyAIOverrides(aiConfig, options) {
|
|
61
|
+
const ai = { ...aiConfig };
|
|
62
|
+
|
|
63
|
+
if (options.ai === true) {
|
|
64
|
+
ai.enabled = true;
|
|
65
|
+
}
|
|
66
|
+
if (options.ai === false) {
|
|
67
|
+
ai.enabled = false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return ai;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Rule C006: applyPerformanceOverrides - verb-noun naming
|
|
75
|
+
* Rule C005: Extracted for single responsibility
|
|
76
|
+
*/
|
|
77
|
+
applyPerformanceOverrides(performanceConfig, options) {
|
|
78
|
+
const performance = { ...performanceConfig };
|
|
79
|
+
|
|
80
|
+
if (options.maxConcurrent) {
|
|
81
|
+
performance.maxConcurrentRules = parseInt(options.maxConcurrent);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (options.timeout) {
|
|
85
|
+
performance.timeoutMs = parseInt(options.timeout);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (options.cache === false) {
|
|
89
|
+
performance.cacheEnabled = false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return performance;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Rule C006: applyEnvironmentVariables - verb-noun naming
|
|
97
|
+
*/
|
|
98
|
+
applyEnvironmentVariables(config) {
|
|
99
|
+
const updatedConfig = { ...config };
|
|
100
|
+
|
|
101
|
+
// SUNLINT_RULES environment variable
|
|
102
|
+
if (process.env.SUNLINT_RULES) {
|
|
103
|
+
const envRules = {};
|
|
104
|
+
process.env.SUNLINT_RULES.split(',').forEach(rule => {
|
|
105
|
+
const [ruleId, severity] = rule.trim().split(':');
|
|
106
|
+
envRules[ruleId] = severity || 'error';
|
|
107
|
+
});
|
|
108
|
+
updatedConfig.rules = { ...updatedConfig.rules, ...envRules };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// SUNLINT_AI_ENABLED environment variable
|
|
112
|
+
if (process.env.SUNLINT_AI_ENABLED) {
|
|
113
|
+
updatedConfig.ai = updatedConfig.ai || {};
|
|
114
|
+
updatedConfig.ai.enabled = process.env.SUNLINT_AI_ENABLED === 'true';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// SUNLINT_LANGUAGES environment variable
|
|
118
|
+
if (process.env.SUNLINT_LANGUAGES) {
|
|
119
|
+
updatedConfig.languages = process.env.SUNLINT_LANGUAGES.split(',').map(l => l.trim());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return updatedConfig;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Rule C006: processIgnorePatterns - verb-noun naming
|
|
127
|
+
*/
|
|
128
|
+
processIgnorePatterns(config) {
|
|
129
|
+
if (config.ignorePatterns && config.ignorePatterns.length > 0) {
|
|
130
|
+
config.exclude = [...new Set([...config.exclude, ...config.ignorePatterns])];
|
|
131
|
+
}
|
|
132
|
+
return config;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = ConfigMerger;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles configuration file overrides based on file patterns
|
|
3
|
+
* Rule C005: Single responsibility - chỉ xử lý overrides theo file patterns
|
|
4
|
+
* Rule C015: Domain language - ConfigOverrideProcessor
|
|
5
|
+
*/
|
|
6
|
+
class ConfigOverrideProcessor {
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
// Rule C014: Dependency injection for minimatch
|
|
10
|
+
this.minimatch = require('minimatch');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Rule C006: applyFileOverrides - verb-noun naming
|
|
15
|
+
* Rule C012: Pure function with clear input/output
|
|
16
|
+
*/
|
|
17
|
+
applyFileOverrides(config, filePath) {
|
|
18
|
+
if (!config.overrides || config.overrides.length === 0) {
|
|
19
|
+
return config;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let fileConfig = { ...config };
|
|
23
|
+
|
|
24
|
+
for (const override of config.overrides) {
|
|
25
|
+
if (this.shouldApplyOverride(override, filePath)) {
|
|
26
|
+
fileConfig = this.applyOverride(fileConfig, override);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return fileConfig;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Rule C006: shouldApplyOverride - verb-noun naming
|
|
35
|
+
* Rule C005: Single responsibility check
|
|
36
|
+
* Rule C012: Pure function - query operation
|
|
37
|
+
*/
|
|
38
|
+
shouldApplyOverride(override, filePath) {
|
|
39
|
+
const { files } = override;
|
|
40
|
+
|
|
41
|
+
if (!files || !Array.isArray(files)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return files.some(pattern => this.minimatch(filePath, pattern));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Rule C006: applyOverride - verb-noun naming
|
|
50
|
+
* Rule C005: Single responsibility application
|
|
51
|
+
*/
|
|
52
|
+
applyOverride(fileConfig, override) {
|
|
53
|
+
const { files, rules, ...otherSettings } = override;
|
|
54
|
+
const updatedConfig = { ...fileConfig };
|
|
55
|
+
|
|
56
|
+
// Apply rule overrides
|
|
57
|
+
if (rules) {
|
|
58
|
+
updatedConfig.rules = { ...updatedConfig.rules, ...rules };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Apply other setting overrides
|
|
62
|
+
for (const [key, value] of Object.entries(otherSettings)) {
|
|
63
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
64
|
+
updatedConfig[key] = { ...updatedConfig[key], ...value };
|
|
65
|
+
} else {
|
|
66
|
+
updatedConfig[key] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return updatedConfig;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = ConfigOverrideProcessor;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handles loading and resolving configuration presets
|
|
6
|
+
* Rule C005: Single responsibility - chỉ xử lý presets
|
|
7
|
+
* Rule C015: Domain language - ConfigPresetResolver
|
|
8
|
+
*/
|
|
9
|
+
class ConfigPresetResolver {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.presetMap = {
|
|
12
|
+
'@sun/sunlint/recommended': 'config/presets/recommended.json',
|
|
13
|
+
'@sun/sunlint/strict': 'config/presets/strict.json',
|
|
14
|
+
'@sun/sunlint/beginner': 'config/presets/beginner.json',
|
|
15
|
+
'@sun/sunlint/ci': 'config/presets/ci.json'
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Rule C006: loadPresetConfiguration - verb-noun naming
|
|
21
|
+
*/
|
|
22
|
+
async loadPresetConfiguration(presetName) {
|
|
23
|
+
const presetPath = this.presetMap[presetName];
|
|
24
|
+
if (!presetPath) {
|
|
25
|
+
throw new Error(`Unknown preset: ${presetName}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const fullPath = path.join(__dirname, '..', presetPath);
|
|
29
|
+
if (!fs.existsSync(fullPath)) {
|
|
30
|
+
throw new Error(`Preset file not found: ${fullPath}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const presetConfig = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
|
|
35
|
+
return presetConfig;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new Error(`Failed to load preset ${presetName}: ${error.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Rule C006: resolvePresetFromRegistry - verb-noun naming
|
|
43
|
+
*/
|
|
44
|
+
resolvePresetFromRegistry(presetName, rulesRegistry) {
|
|
45
|
+
const preset = rulesRegistry.presets[presetName];
|
|
46
|
+
if (!preset) {
|
|
47
|
+
throw new Error(`Preset '${presetName}' not found`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
rules: preset.rules,
|
|
52
|
+
output: { format: 'eslint', console: true, summary: true },
|
|
53
|
+
performance: { maxConcurrentRules: 5, timeoutMs: 30000 }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Rule C006: checkPresetExists - verb-noun naming
|
|
59
|
+
*/
|
|
60
|
+
checkPresetExists(presetName) {
|
|
61
|
+
return this.presetMap.hasOwnProperty(presetName);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = ConfigPresetResolver;
|