@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.
Files changed (192) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/LICENSE +21 -0
  3. package/README.md +490 -0
  4. package/cli-legacy.js +355 -0
  5. package/cli.js +35 -0
  6. package/config/default.json +22 -0
  7. package/config/presets/beginner.json +36 -0
  8. package/config/presets/ci.json +46 -0
  9. package/config/presets/recommended.json +24 -0
  10. package/config/presets/strict.json +32 -0
  11. package/config/rules-registry.json +681 -0
  12. package/config/sunlint-schema.json +166 -0
  13. package/config/typescript/custom-rules-new.js +0 -0
  14. package/config/typescript/custom-rules.js +9 -0
  15. package/config/typescript/eslint.config.js +110 -0
  16. package/config/typescript/package-lock.json +1585 -0
  17. package/config/typescript/package.json +13 -0
  18. package/config/typescript/security-rules/index.js +90 -0
  19. package/config/typescript/security-rules/s005-no-origin-auth.js +95 -0
  20. package/config/typescript/security-rules/s006-activation-recovery-secret-not-plaintext.js +69 -0
  21. package/config/typescript/security-rules/s008-crypto-agility.js +62 -0
  22. package/config/typescript/security-rules/s009-no-insecure-crypto.js +103 -0
  23. package/config/typescript/security-rules/s010-no-insecure-random-in-sensitive-context.js +123 -0
  24. package/config/typescript/security-rules/s011-no-insecure-uuid.js +66 -0
  25. package/config/typescript/security-rules/s012-hardcode-secret.js +71 -0
  26. package/config/typescript/security-rules/s014-insecure-tls-version.js +50 -0
  27. package/config/typescript/security-rules/s015-insecure-tls-certificate.js +43 -0
  28. package/config/typescript/security-rules/s016-sensitive-query-parameter.js +59 -0
  29. package/config/typescript/security-rules/s017-no-sql-injection.js +193 -0
  30. package/config/typescript/security-rules/s018-positive-input-validation.js +56 -0
  31. package/config/typescript/security-rules/s019-no-raw-user-input-in-email.js +113 -0
  32. package/config/typescript/security-rules/s020-no-eval-dynamic-execution.js +89 -0
  33. package/config/typescript/security-rules/s022-output-encoding.js +78 -0
  34. package/config/typescript/security-rules/s023-no-json-injection.js +300 -0
  35. package/config/typescript/security-rules/s025-server-side-input-validation.js +217 -0
  36. package/config/typescript/security-rules/s026-json-schema-validation.js +68 -0
  37. package/config/typescript/security-rules/s027-no-hardcoded-secrets.js +80 -0
  38. package/config/typescript/security-rules/s029-require-csrf-protection.js +79 -0
  39. package/config/typescript/security-rules/s030-no-directory-browsing.js +78 -0
  40. package/config/typescript/security-rules/s033-require-samesite-cookie.js +80 -0
  41. package/config/typescript/security-rules/s034-require-host-cookie-prefix.js +77 -0
  42. package/config/typescript/security-rules/s035-cookie-specific-path.js +74 -0
  43. package/config/typescript/security-rules/s036-no-unsafe-file-include.js +68 -0
  44. package/config/typescript/security-rules/s037-require-anti-cache-headers.js +70 -0
  45. package/config/typescript/security-rules/s038-no-version-disclosure.js +74 -0
  46. package/config/typescript/security-rules/s039-no-session-token-in-url.js +63 -0
  47. package/config/typescript/security-rules/s041-require-session-invalidate-on-logout.js +211 -0
  48. package/config/typescript/security-rules/s042-require-periodic-reauthentication.js +294 -0
  49. package/config/typescript/security-rules/s043-terminate-sessions-on-password-change.js +254 -0
  50. package/config/typescript/security-rules/s044-require-full-session-for-sensitive-operations.js +292 -0
  51. package/config/typescript/security-rules/s045-anti-automation-controls.js +46 -0
  52. package/config/typescript/security-rules/s046-secure-notification-on-auth-change.js +44 -0
  53. package/config/typescript/security-rules/s048-password-credential-recovery.js +54 -0
  54. package/config/typescript/security-rules/s050-session-token-weak-hash.js +94 -0
  55. package/config/typescript/security-rules/s052-secure-random-authentication-code.js +66 -0
  56. package/config/typescript/security-rules/s054-verification-default-account.js +109 -0
  57. package/config/typescript/security-rules/s057-utc-logging.js +54 -0
  58. package/config/typescript/security-rules/s058-no-ssrf.js +73 -0
  59. package/config/typescript/test-s005-working.ts +22 -0
  60. package/config/typescript/tsconfig.json +29 -0
  61. package/core/ai-analyzer.js +169 -0
  62. package/core/analysis-orchestrator.js +705 -0
  63. package/core/cli-action-handler.js +230 -0
  64. package/core/cli-program.js +106 -0
  65. package/core/config-manager.js +396 -0
  66. package/core/config-merger.js +136 -0
  67. package/core/config-override-processor.js +74 -0
  68. package/core/config-preset-resolver.js +65 -0
  69. package/core/config-source-loader.js +152 -0
  70. package/core/config-validator.js +126 -0
  71. package/core/dependency-manager.js +105 -0
  72. package/core/eslint-engine-service.js +312 -0
  73. package/core/eslint-instance-manager.js +104 -0
  74. package/core/eslint-integration-service.js +363 -0
  75. package/core/git-utils.js +170 -0
  76. package/core/multi-rule-runner.js +239 -0
  77. package/core/output-service.js +250 -0
  78. package/core/report-generator.js +320 -0
  79. package/core/rule-mapping-service.js +309 -0
  80. package/core/rule-selection-service.js +121 -0
  81. package/core/sunlint-engine-service.js +23 -0
  82. package/core/typescript-analyzer.js +262 -0
  83. package/core/typescript-engine.js +313 -0
  84. package/docs/AI.md +163 -0
  85. package/docs/ARCHITECTURE.md +78 -0
  86. package/docs/CI-CD-GUIDE.md +315 -0
  87. package/docs/COMMAND-EXAMPLES.md +256 -0
  88. package/docs/DEBUG.md +86 -0
  89. package/docs/DISTRIBUTION.md +153 -0
  90. package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
  91. package/docs/ESLINT_INTEGRATION.md +238 -0
  92. package/docs/FOLDER_STRUCTURE.md +59 -0
  93. package/docs/HEURISTIC_VS_AI.md +113 -0
  94. package/docs/README.md +32 -0
  95. package/docs/RELEASE_GUIDE.md +230 -0
  96. package/docs/RULE-RESPONSIBILITY-MATRIX.md +204 -0
  97. package/eslint-integration/.eslintrc.js +98 -0
  98. package/eslint-integration/cli.js +35 -0
  99. package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +204 -0
  100. package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +246 -0
  101. package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +207 -0
  102. package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +90 -0
  103. package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +43 -0
  104. package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +38 -0
  105. package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +39 -0
  106. package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +335 -0
  107. package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +142 -0
  108. package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +50 -0
  109. package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +80 -0
  110. package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +294 -0
  111. package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +34 -0
  112. package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +32 -0
  113. package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +64 -0
  114. package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +406 -0
  115. package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +300 -0
  116. package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +239 -0
  117. package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +31 -0
  118. package/eslint-integration/eslint-plugin-custom/c076-one-assert-per-test.js +184 -0
  119. package/eslint-integration/eslint-plugin-custom/index.js +155 -0
  120. package/eslint-integration/eslint-plugin-custom/package.json +13 -0
  121. package/eslint-integration/eslint-plugin-custom/package.json.bak +9 -0
  122. package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +86 -0
  123. package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +95 -0
  124. package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +69 -0
  125. package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +62 -0
  126. package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +103 -0
  127. package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +123 -0
  128. package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +66 -0
  129. package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +71 -0
  130. package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +50 -0
  131. package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +43 -0
  132. package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +59 -0
  133. package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +193 -0
  134. package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +56 -0
  135. package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +113 -0
  136. package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +89 -0
  137. package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +78 -0
  138. package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +300 -0
  139. package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +217 -0
  140. package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +68 -0
  141. package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +80 -0
  142. package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +79 -0
  143. package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +78 -0
  144. package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +80 -0
  145. package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +77 -0
  146. package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +74 -0
  147. package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +68 -0
  148. package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +70 -0
  149. package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +74 -0
  150. package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +63 -0
  151. package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +211 -0
  152. package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +294 -0
  153. package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +254 -0
  154. package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +292 -0
  155. package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +46 -0
  156. package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +44 -0
  157. package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +108 -0
  158. package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +54 -0
  159. package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +94 -0
  160. package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +66 -0
  161. package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +109 -0
  162. package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +143 -0
  163. package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +54 -0
  164. package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +73 -0
  165. package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +42 -0
  166. package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +48 -0
  167. package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +160 -0
  168. package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +52 -0
  169. package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +175 -0
  170. package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +95 -0
  171. package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +48 -0
  172. package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +377 -0
  173. package/eslint-integration/eslint.config.js +125 -0
  174. package/eslint-integration/eslint.config.simple.js +24 -0
  175. package/eslint-integration/node_modules/eslint-plugin-custom/package.json +0 -0
  176. package/eslint-integration/package.json +23 -0
  177. package/eslint-integration/sample.ts +53 -0
  178. package/eslint-integration/test-s003.js +5 -0
  179. package/eslint-integration/tsconfig.json +27 -0
  180. package/examples/.github/workflows/code-quality.yml +111 -0
  181. package/examples/.sunlint.json +42 -0
  182. package/examples/README.md +47 -0
  183. package/examples/package.json +33 -0
  184. package/package.json +100 -0
  185. package/rules/C006_function_naming/analyzer.js +338 -0
  186. package/rules/C006_function_naming/config.json +86 -0
  187. package/rules/C019_log_level_usage/analyzer.js +359 -0
  188. package/rules/C019_log_level_usage/config.json +121 -0
  189. package/rules/C029_catch_block_logging/analyzer.js +339 -0
  190. package/rules/C029_catch_block_logging/config.json +59 -0
  191. package/rules/C031_validation_separation/README.md +72 -0
  192. 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;