@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,152 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+
5
+ /**
6
+ * Handles loading configuration from various sources (files, environment, etc.)
7
+ * Rule C005: Single responsibility - chỉ load config từ sources
8
+ * Rule C015: Domain language - ConfigSourceLoader
9
+ */
10
+ class ConfigSourceLoader {
11
+ constructor() {
12
+ this.configNames = [
13
+ '.sunlint.json',
14
+ '.sunlint.js',
15
+ 'sunlint.config.json',
16
+ 'sunlint.config.js'
17
+ ];
18
+ }
19
+
20
+ /**
21
+ * Rule C006: loadGlobalConfiguration - verb-noun naming
22
+ */
23
+ loadGlobalConfiguration(homePath, verbose = false) {
24
+ const globalConfigPath = path.join(homePath, '.sunlint.json');
25
+ if (!fs.existsSync(globalConfigPath)) {
26
+ return null;
27
+ }
28
+
29
+ try {
30
+ const globalConfig = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
31
+ if (verbose) {
32
+ console.log(chalk.gray(`🌍 Loaded global config: ${globalConfigPath}`));
33
+ }
34
+ return { config: globalConfig, path: globalConfigPath };
35
+ } catch (error) {
36
+ console.warn(chalk.yellow(`⚠️ Failed to load global config: ${error.message}`));
37
+ return null;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Rule C006: findProjectConfiguration - verb-noun naming
43
+ */
44
+ findProjectConfiguration(startDir) {
45
+ let currentDir = path.resolve(startDir);
46
+ const rootDir = path.parse(currentDir).root;
47
+
48
+ while (currentDir !== rootDir) {
49
+ // Check for SunLint config files
50
+ for (const configName of this.configNames) {
51
+ const configPath = path.join(currentDir, configName);
52
+ if (fs.existsSync(configPath)) {
53
+ try {
54
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
55
+ return { path: configPath, config, dir: currentDir };
56
+ } catch (error) {
57
+ console.warn(chalk.yellow(`⚠️ Invalid config file ${configPath}: ${error.message}`));
58
+ }
59
+ }
60
+ }
61
+
62
+ // Check package.json for sunlint config
63
+ const packageResult = this.loadPackageJsonConfig(currentDir);
64
+ if (packageResult) {
65
+ return packageResult;
66
+ }
67
+
68
+ currentDir = path.dirname(currentDir);
69
+ }
70
+
71
+ return null;
72
+ }
73
+
74
+ /**
75
+ * Rule C006: loadPackageJsonConfig - verb-noun naming
76
+ * Rule C005: Extracted method for single responsibility
77
+ */
78
+ loadPackageJsonConfig(directory) {
79
+ const packageJsonPath = path.join(directory, 'package.json');
80
+ if (!fs.existsSync(packageJsonPath)) {
81
+ return null;
82
+ }
83
+
84
+ try {
85
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
86
+ if (packageJson.sunlint) {
87
+ return { path: packageJsonPath, config: packageJson.sunlint, dir: directory };
88
+ }
89
+ } catch (error) {
90
+ // Ignore package.json parse errors
91
+ }
92
+
93
+ return null;
94
+ }
95
+
96
+ /**
97
+ * Rule C006: loadSpecificConfigFile - verb-noun naming
98
+ */
99
+ loadSpecificConfigFile(configPath, verbose = false) {
100
+ if (!configPath || !fs.existsSync(configPath)) {
101
+ return null;
102
+ }
103
+
104
+ try {
105
+ const fileConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
106
+ if (verbose) {
107
+ console.log(chalk.gray(`📄 Loaded config from: ${configPath}`));
108
+ }
109
+ return { config: fileConfig, path: configPath };
110
+ } catch (error) {
111
+ console.error(chalk.red(`❌ Failed to load config from ${configPath}:`), error.message);
112
+ return null;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Rule C006: loadIgnorePatterns - verb-noun naming
118
+ */
119
+ loadIgnorePatterns(projectDir, verbose = false) {
120
+ const ignoreFiles = ['.sunlintignore', '.eslintignore', '.gitignore'];
121
+ const ignorePatterns = [];
122
+
123
+ for (const ignoreFile of ignoreFiles) {
124
+ const ignorePath = path.join(projectDir, ignoreFile);
125
+ if (fs.existsSync(ignorePath)) {
126
+ try {
127
+ const ignoreContent = fs.readFileSync(ignorePath, 'utf8');
128
+ const patterns = ignoreContent
129
+ .split('\n')
130
+ .map(line => line.trim())
131
+ .filter(line => line && !line.startsWith('#'))
132
+ .filter(line => !ignorePatterns.includes(line));
133
+
134
+ ignorePatterns.push(...patterns);
135
+
136
+ if (verbose) {
137
+ console.log(chalk.gray(`📋 Loaded ignore patterns from: ${ignorePath} (${patterns.length} patterns)`));
138
+ }
139
+
140
+ // Only use the first ignore file found
141
+ break;
142
+ } catch (error) {
143
+ console.warn(chalk.yellow(`⚠️ Failed to load ignore file ${ignorePath}: ${error.message}`));
144
+ }
145
+ }
146
+ }
147
+
148
+ return [...new Set(ignorePatterns)]; // Remove duplicates
149
+ }
150
+ }
151
+
152
+ module.exports = ConfigSourceLoader;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Handles configuration validation and rule value normalization
3
+ * Rule C005: Single responsibility - chỉ validation và normalization
4
+ * Rule C015: Domain language - ConfigValidator
5
+ * Rule C031: Validation logic tách riêng
6
+ */
7
+ class ConfigValidator {
8
+
9
+ constructor() {
10
+ this.validFormats = ['eslint', 'json', 'summary', 'table'];
11
+ this.validRuleValues = ['error', 'warning', 'info', 'warn', 'off', true, false, 0, 1, 2];
12
+ this.ruleValueMapping = {
13
+ 0: 'off',
14
+ 1: 'warning',
15
+ 2: 'error',
16
+ 'warn': 'warning',
17
+ true: 'warning',
18
+ false: 'off'
19
+ };
20
+ }
21
+
22
+ /**
23
+ * Rule C006: validateConfiguration - verb-noun naming
24
+ * Rule C031: Main validation method
25
+ */
26
+ validateConfiguration(config) {
27
+ this.validateRulesSection(config.rules);
28
+ this.validateLanguagesSection(config.languages);
29
+ this.validateIncludeExcludePatterns(config.include, config.exclude);
30
+ this.validateOutputFormat(config.output);
31
+ this.validateRuleValues(config.rules);
32
+ }
33
+
34
+ /**
35
+ * Rule C006: validateRulesSection - verb-noun naming
36
+ * Rule C031: Specific validation logic
37
+ */
38
+ validateRulesSection(rules) {
39
+ if (rules && typeof rules !== 'object') {
40
+ throw new Error('Config error: rules must be an object');
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Rule C006: validateLanguagesSection - verb-noun naming
46
+ * Rule C031: Specific validation logic
47
+ */
48
+ validateLanguagesSection(languages) {
49
+ if (languages && !Array.isArray(languages)) {
50
+ throw new Error('Config error: languages must be an array');
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Rule C006: validateIncludeExcludePatterns - verb-noun naming
56
+ * Rule C031: Specific validation logic
57
+ */
58
+ validateIncludeExcludePatterns(include, exclude) {
59
+ if (include && !Array.isArray(include)) {
60
+ throw new Error('Config error: include must be an array');
61
+ }
62
+
63
+ if (exclude && !Array.isArray(exclude)) {
64
+ throw new Error('Config error: exclude must be an array');
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Rule C006: validateOutputFormat - verb-noun naming
70
+ * Rule C031: Specific validation logic
71
+ */
72
+ validateOutputFormat(output) {
73
+ if (output && output.format && !this.validFormats.includes(output.format)) {
74
+ throw new Error(`Config error: invalid output format '${output.format}'. Valid formats: ${this.validFormats.join(', ')}`);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Rule C006: validateRuleValues - verb-noun naming
80
+ * Rule C031: Specific validation logic
81
+ */
82
+ validateRuleValues(rules) {
83
+ if (!rules) return;
84
+
85
+ for (const [ruleId, ruleValue] of Object.entries(rules)) {
86
+ if (!this.validRuleValues.includes(ruleValue)) {
87
+ throw new Error(`Config error: invalid value '${ruleValue}' for rule '${ruleId}'. Valid values: ${this.validRuleValues.join(', ')}`);
88
+ }
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Rule C006: normalizeRuleValue - verb-noun naming
94
+ * Rule C012: Pure function - no side effects
95
+ */
96
+ normalizeRuleValue(value) {
97
+ return this.ruleValueMapping[value] || value;
98
+ }
99
+
100
+ /**
101
+ * Rule C006: getEffectiveRuleConfiguration - verb-noun naming
102
+ * Rule C014: Accept rulesRegistry as dependency injection
103
+ */
104
+ getEffectiveRuleConfiguration(ruleId, config, rulesRegistry) {
105
+ // Check direct rule configuration
106
+ if (config.rules && config.rules[ruleId] !== undefined) {
107
+ return this.normalizeRuleValue(config.rules[ruleId]);
108
+ }
109
+
110
+ // Check category configuration
111
+ const rule = rulesRegistry.rules[ruleId];
112
+
113
+ if (rule && config.categories && config.categories[rule.category] !== undefined) {
114
+ return this.normalizeRuleValue(config.categories[rule.category]);
115
+ }
116
+
117
+ // Use rule default
118
+ if (rule) {
119
+ return this.normalizeRuleValue(rule.severity);
120
+ }
121
+
122
+ return 'off';
123
+ }
124
+ }
125
+
126
+ module.exports = ConfigValidator;
@@ -0,0 +1,105 @@
1
+ const chalk = require('chalk');
2
+ const { execSync } = require('child_process');
3
+
4
+ /**
5
+ * Handles dependency validation and installation
6
+ * Rule C005: Single responsibility - only dependency management
7
+ * Rule C015: Domain language - DependencyManager
8
+ * Rule C032: No external API calls in constructor
9
+ */
10
+ class DependencyManager {
11
+ constructor() {
12
+ this.requiredDependencies = [
13
+ '@typescript-eslint/parser',
14
+ '@typescript-eslint/eslint-plugin',
15
+ 'eslint'
16
+ ];
17
+ }
18
+
19
+ /**
20
+ * Rule C006: checkDependenciesAvailable - verb-noun naming
21
+ * Rule C012: Query method - checks without side effects
22
+ */
23
+ async checkDependenciesAvailable() {
24
+ const missingDeps = [];
25
+
26
+ for (const dep of this.requiredDependencies) {
27
+ try {
28
+ require.resolve(dep);
29
+ } catch (error) {
30
+ missingDeps.push(dep);
31
+ }
32
+ }
33
+
34
+ return {
35
+ allAvailable: missingDeps.length === 0,
36
+ missing: missingDeps
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Rule C006: installMissingDependencies - verb-noun naming
42
+ * Rule C012: Command method - performs installation
43
+ */
44
+ async installMissingDependencies() {
45
+ const { allAvailable, missing } = await this.checkDependenciesAvailable();
46
+
47
+ if (allAvailable) {
48
+ console.log(chalk.green('✅ All TypeScript dependencies are available'));
49
+ return true;
50
+ }
51
+
52
+ console.log(chalk.yellow(`⚠️ Missing dependencies: ${missing.join(', ')}`));
53
+ console.log(chalk.blue('📦 Installing missing dependencies...'));
54
+
55
+ try {
56
+ const installCommand = `npm install ${missing.join(' ')}`;
57
+ execSync(installCommand, { stdio: 'inherit', cwd: process.cwd() });
58
+
59
+ console.log(chalk.green('✅ Dependencies installed successfully'));
60
+ return true;
61
+ } catch (error) {
62
+ console.error(chalk.red('❌ Failed to install dependencies:'), error.message);
63
+ console.log(chalk.yellow('💡 Please install manually:'));
64
+ console.log(chalk.gray(` npm install ${missing.join(' ')}`));
65
+ return false;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Rule C006: validateDependencyVersions - verb-noun naming
71
+ * Rule C012: Query method
72
+ */
73
+ validateDependencyVersions() {
74
+ const versions = {};
75
+
76
+ for (const dep of this.requiredDependencies) {
77
+ try {
78
+ const packagePath = require.resolve(`${dep}/package.json`);
79
+ const packageInfo = require(packagePath);
80
+ versions[dep] = packageInfo.version;
81
+ } catch (error) {
82
+ versions[dep] = 'not found';
83
+ }
84
+ }
85
+
86
+ return versions;
87
+ }
88
+
89
+ /**
90
+ * Rule C006: logDependencyStatus - verb-noun naming
91
+ */
92
+ logDependencyStatus() {
93
+ const versions = this.validateDependencyVersions();
94
+
95
+ console.log(chalk.blue('📦 TypeScript Dependencies:'));
96
+ for (const [dep, version] of Object.entries(versions)) {
97
+ const status = version === 'not found'
98
+ ? chalk.red('❌ Not found')
99
+ : chalk.green(`✅ v${version}`);
100
+ console.log(` ${dep}: ${status}`);
101
+ }
102
+ }
103
+ }
104
+
105
+ module.exports = DependencyManager;
@@ -0,0 +1,312 @@
1
+ /**
2
+ * ESLint Engine Service
3
+ * Handles ESLint integration for TypeScript analysis
4
+ * Following Rule C005: Single responsibility - only handle ESLint execution
5
+ * Following Rule C014: Dependency injection for configuration
6
+ */
7
+
8
+ const { execSync } = require('child_process');
9
+ const path = require('path');
10
+ const fs = require('fs');
11
+ const chalk = require('chalk');
12
+
13
+ class ESLintEngineService {
14
+ constructor() {
15
+ this.eslintConfigPath = path.join(__dirname, '../config/typescript/eslint.config.js');
16
+ this.eslintPackagePath = path.join(__dirname, '../config/typescript');
17
+ }
18
+
19
+ /**
20
+ * Query: Check if ESLint is available
21
+ */
22
+ isAvailable() {
23
+ try {
24
+ // Check if config exists
25
+ if (!fs.existsSync(this.eslintConfigPath)) {
26
+ return false;
27
+ }
28
+
29
+ // Check if ESLint is available
30
+ execSync('npx eslint --version', {
31
+ stdio: 'ignore',
32
+ cwd: this.eslintPackagePath
33
+ });
34
+ return true;
35
+ } catch (error) {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Command: Ensure ESLint dependencies are installed
42
+ */
43
+ async ensureDependencies() {
44
+ try {
45
+ console.log(chalk.blue('🔧 Ensuring ESLint dependencies...'));
46
+
47
+ const packageJsonPath = path.join(this.eslintPackagePath, 'package.json');
48
+ if (fs.existsSync(packageJsonPath)) {
49
+ execSync('npm install', {
50
+ cwd: this.eslintPackagePath,
51
+ stdio: 'inherit'
52
+ });
53
+ }
54
+
55
+ console.log(chalk.green('✅ ESLint dependencies ready'));
56
+ } catch (error) {
57
+ throw new Error(`Failed to ensure ESLint dependencies: ${error.message}`);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Command: Run ESLint analysis
63
+ */
64
+ async runAnalysis(rulesToRun, options) {
65
+ try {
66
+ // Map SunLint rules to ESLint rules
67
+ const eslintRules = this.mapSunLintRulesToESLint(rulesToRun);
68
+
69
+ // Build ESLint command
70
+ const command = this.buildESLintCommand(eslintRules, options);
71
+
72
+ if (options.debug) {
73
+ console.log(chalk.yellow('ESLint command:'), command);
74
+ }
75
+
76
+ // Execute ESLint
77
+ const output = execSync(command, {
78
+ cwd: this.eslintPackagePath,
79
+ encoding: 'utf-8'
80
+ });
81
+
82
+ // Parse results
83
+ return this.parseESLintOutput(output, options);
84
+
85
+ } catch (error) {
86
+ // ESLint might exit with non-zero code when violations are found
87
+ if (error.stdout) {
88
+ return this.parseESLintOutput(error.stdout, options);
89
+ }
90
+ throw new Error(`ESLint execution failed: ${error.message}`);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Query: Map SunLint rule IDs to ESLint rule IDs
96
+ */
97
+ mapSunLintRulesToESLint(rules) {
98
+ const ruleMapping = {
99
+ // Quality rules (C-rules)
100
+ 'C002': 'custom/c002',
101
+ 'C003': 'custom/c003',
102
+ 'C006': 'custom/c006',
103
+ 'C010': 'custom/c010',
104
+ 'C013': 'custom/c013',
105
+ 'C014': 'custom/c014',
106
+ 'C017': 'custom/c017',
107
+ 'C018': 'custom/c018',
108
+ 'C023': 'custom/c023',
109
+ 'C027': 'custom/c027',
110
+ 'C029': 'custom/c029',
111
+ 'C030': 'custom/c030',
112
+ 'C034': 'custom/c034',
113
+ 'C035': 'custom/c035',
114
+ 'C041': 'custom/c041',
115
+ 'C042': 'custom/c042',
116
+ 'C043': 'custom/c043',
117
+ 'C047': 'custom/c047',
118
+ 'C048': 'custom/c048',
119
+
120
+ // Security rules (S-rules)
121
+ 'S005': 'custom/typescript_s005',
122
+ 'S006': 'custom/typescript_s006',
123
+ 'S008': 'custom/typescript_s008',
124
+ 'S009': 'custom/typescript_s009',
125
+ 'S010': 'custom/typescript_s010',
126
+ 'S011': 'custom/typescript_s011',
127
+ 'S012': 'custom/typescript_s012',
128
+ 'S014': 'custom/typescript_s014',
129
+ 'S015': 'custom/typescript_s015',
130
+ 'S016': 'custom/typescript_s016',
131
+ 'S017': 'custom/typescript_s017',
132
+ 'S018': 'custom/typescript_s018',
133
+ 'S019': 'custom/typescript_s019',
134
+ 'S020': 'custom/typescript_s020',
135
+ 'S022': 'custom/typescript_s022',
136
+ 'S023': 'custom/typescript_s023',
137
+ 'S025': 'custom/typescript_s025',
138
+ 'S026': 'custom/typescript_s026',
139
+ 'S027': 'custom/typescript_s027',
140
+ 'S029': 'custom/typescript_s029',
141
+ 'S030': 'custom/typescript_s030',
142
+ 'S033': 'custom/typescript_s033',
143
+ 'S034': 'custom/typescript_s034',
144
+ 'S035': 'custom/typescript_s035',
145
+ 'S036': 'custom/typescript_s036',
146
+ 'S037': 'custom/typescript_s037',
147
+ 'S038': 'custom/typescript_s038',
148
+ 'S039': 'custom/typescript_s039',
149
+ 'S041': 'custom/typescript_s041',
150
+ 'S042': 'custom/typescript_s042',
151
+ 'S043': 'custom/typescript_s043',
152
+ 'S044': 'custom/typescript_s044',
153
+ 'S045': 'custom/typescript_s045',
154
+ 'S046': 'custom/typescript_s046',
155
+ 'S048': 'custom/typescript_s048',
156
+ 'S050': 'custom/typescript_s050',
157
+ 'S052': 'custom/typescript_s052',
158
+ 'S054': 'custom/typescript_s054',
159
+ 'S057': 'custom/typescript_s057',
160
+ 'S058': 'custom/typescript_s058'
161
+ };
162
+
163
+ return rules
164
+ .filter(rule => ruleMapping[rule.id])
165
+ .map(rule => ruleMapping[rule.id]);
166
+ }
167
+
168
+ /**
169
+ * Command: Build ESLint command
170
+ */
171
+ buildESLintCommand(eslintRules, options) {
172
+ const parts = [
173
+ 'npx eslint',
174
+ `--config ${this.eslintConfigPath}`,
175
+ '--format json',
176
+ `"${options.input}"`
177
+ ];
178
+
179
+ // Add specific rules if provided
180
+ if (eslintRules.length > 0) {
181
+ const rulesFlag = eslintRules.map(rule => `${rule}:error`).join(' ');
182
+ parts.push(`--rule "${rulesFlag}"`);
183
+ }
184
+
185
+ // Add file extensions
186
+ parts.push('--ext .ts,.tsx,.js,.jsx');
187
+
188
+ return parts.join(' ');
189
+ }
190
+
191
+ /**
192
+ * Query: Parse ESLint JSON output to SunLint format
193
+ */
194
+ parseESLintOutput(output, options) {
195
+ try {
196
+ const eslintResults = JSON.parse(output);
197
+
198
+ const results = {
199
+ results: [],
200
+ filesAnalyzed: eslintResults.length,
201
+ engine: 'eslint'
202
+ };
203
+
204
+ eslintResults.forEach(file => {
205
+ if (file.messages.length > 0) {
206
+ const violations = file.messages.map(msg => ({
207
+ ruleId: this.mapESLintRuleToSunLint(msg.ruleId),
208
+ severity: this.mapESLintSeverity(msg.severity),
209
+ message: msg.message,
210
+ line: msg.line,
211
+ column: msg.column,
212
+ file: file.filePath
213
+ }));
214
+
215
+ results.results.push({
216
+ file: file.filePath,
217
+ violations: violations
218
+ });
219
+ }
220
+ });
221
+
222
+ return results;
223
+ } catch (error) {
224
+ throw new Error(`Failed to parse ESLint output: ${error.message}`);
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Query: Map ESLint rule ID back to SunLint rule ID
230
+ */
231
+ mapESLintRuleToSunLint(eslintRuleId) {
232
+ const reverseMapping = {
233
+ // Quality rules (C-rules)
234
+ 'custom/c002': 'C002',
235
+ 'custom/c003': 'C003',
236
+ 'custom/c006': 'C006',
237
+ 'custom/c010': 'C010',
238
+ 'custom/c013': 'C013',
239
+ 'custom/c014': 'C014',
240
+ 'custom/c017': 'C017',
241
+ 'custom/c018': 'C018',
242
+ 'custom/c023': 'C023',
243
+ 'custom/c027': 'C027',
244
+ 'custom/c029': 'C029',
245
+ 'custom/c030': 'C030',
246
+ 'custom/c034': 'C034',
247
+ 'custom/c035': 'C035',
248
+ 'custom/c041': 'C041',
249
+ 'custom/c042': 'C042',
250
+ 'custom/c043': 'C043',
251
+ 'custom/c047': 'C047',
252
+ 'custom/c048': 'C048',
253
+
254
+ // Security rules (S-rules)
255
+ 'custom/typescript_s005': 'S005',
256
+ 'custom/typescript_s006': 'S006',
257
+ 'custom/typescript_s008': 'S008',
258
+ 'custom/typescript_s009': 'S009',
259
+ 'custom/typescript_s010': 'S010',
260
+ 'custom/typescript_s011': 'S011',
261
+ 'custom/typescript_s012': 'S012',
262
+ 'custom/typescript_s014': 'S014',
263
+ 'custom/typescript_s015': 'S015',
264
+ 'custom/typescript_s016': 'S016',
265
+ 'custom/typescript_s017': 'S017',
266
+ 'custom/typescript_s018': 'S018',
267
+ 'custom/typescript_s019': 'S019',
268
+ 'custom/typescript_s020': 'S020',
269
+ 'custom/typescript_s022': 'S022',
270
+ 'custom/typescript_s023': 'S023',
271
+ 'custom/typescript_s025': 'S025',
272
+ 'custom/typescript_s026': 'S026',
273
+ 'custom/typescript_s027': 'S027',
274
+ 'custom/typescript_s029': 'S029',
275
+ 'custom/typescript_s030': 'S030',
276
+ 'custom/typescript_s033': 'S033',
277
+ 'custom/typescript_s034': 'S034',
278
+ 'custom/typescript_s035': 'S035',
279
+ 'custom/typescript_s036': 'S036',
280
+ 'custom/typescript_s037': 'S037',
281
+ 'custom/typescript_s038': 'S038',
282
+ 'custom/typescript_s039': 'S039',
283
+ 'custom/typescript_s041': 'S041',
284
+ 'custom/typescript_s042': 'S042',
285
+ 'custom/typescript_s043': 'S043',
286
+ 'custom/typescript_s044': 'S044',
287
+ 'custom/typescript_s045': 'S045',
288
+ 'custom/typescript_s046': 'S046',
289
+ 'custom/typescript_s048': 'S048',
290
+ 'custom/typescript_s050': 'S050',
291
+ 'custom/typescript_s052': 'S052',
292
+ 'custom/typescript_s054': 'S054',
293
+ 'custom/typescript_s057': 'S057',
294
+ 'custom/typescript_s058': 'S058'
295
+ };
296
+
297
+ return reverseMapping[eslintRuleId] || eslintRuleId;
298
+ }
299
+
300
+ /**
301
+ * Query: Map ESLint severity to SunLint severity
302
+ */
303
+ mapESLintSeverity(eslintSeverity) {
304
+ switch (eslintSeverity) {
305
+ case 1: return 'warning';
306
+ case 2: return 'error';
307
+ default: return 'info';
308
+ }
309
+ }
310
+ }
311
+
312
+ module.exports = ESLintEngineService;