@sun-asterisk/sunlint 1.3.16 → 1.3.18

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 (59) hide show
  1. package/config/rule-analysis-strategies.js +3 -3
  2. package/config/rules/enhanced-rules-registry.json +40 -20
  3. package/core/analysis-orchestrator.js +11 -3
  4. package/core/cli-action-handler.js +2 -2
  5. package/core/config-merger.js +28 -6
  6. package/core/constants/defaults.js +1 -1
  7. package/core/file-targeting-service.js +72 -4
  8. package/core/output-service.js +48 -13
  9. package/core/summary-report-service.js +21 -3
  10. package/engines/heuristic-engine.js +5 -0
  11. package/package.json +1 -1
  12. package/rules/common/C002_no_duplicate_code/README.md +115 -0
  13. package/rules/common/C002_no_duplicate_code/analyzer.js +615 -219
  14. package/rules/common/C002_no_duplicate_code/test-cases/api-handlers.ts +64 -0
  15. package/rules/common/C002_no_duplicate_code/test-cases/data-processor.ts +46 -0
  16. package/rules/common/C002_no_duplicate_code/test-cases/good-example.tsx +40 -0
  17. package/rules/common/C002_no_duplicate_code/test-cases/product-service.ts +57 -0
  18. package/rules/common/C002_no_duplicate_code/test-cases/user-service.ts +49 -0
  19. package/rules/common/C008/analyzer.js +40 -0
  20. package/rules/common/C008/config.json +20 -0
  21. package/rules/common/C008/ts-morph-analyzer.js +1067 -0
  22. package/rules/common/C018_no_throw_generic_error/analyzer.js +1 -1
  23. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +27 -3
  24. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +504 -162
  25. package/rules/common/C029_catch_block_logging/analyzer.js +499 -89
  26. package/rules/common/C033_separate_service_repository/README.md +131 -20
  27. package/rules/common/C033_separate_service_repository/analyzer.js +1 -1
  28. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +417 -274
  29. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +144 -254
  30. package/rules/common/C041_no_sensitive_hardcode/config.json +50 -0
  31. package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +575 -0
  32. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +96 -40
  33. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +17 -2
  34. package/rules/common/C067_no_hardcoded_config/analyzer.js +17 -16
  35. package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +3477 -659
  36. package/rules/docs/C002_no_duplicate_code.md +276 -11
  37. package/rules/index.js +5 -1
  38. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +266 -88
  39. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +805 -0
  40. package/rules/security/S010_no_insecure_encryption/README.md +78 -0
  41. package/rules/security/S010_no_insecure_encryption/analyzer.js +463 -398
  42. package/rules/security/S013_tls_enforcement/README.md +51 -0
  43. package/rules/security/S013_tls_enforcement/analyzer.js +99 -0
  44. package/rules/security/S013_tls_enforcement/config.json +41 -0
  45. package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +339 -0
  46. package/rules/security/S014_tls_version_enforcement/README.md +354 -0
  47. package/rules/security/S014_tls_version_enforcement/analyzer.js +118 -0
  48. package/rules/security/S014_tls_version_enforcement/config.json +56 -0
  49. package/rules/security/S014_tls_version_enforcement/symbol-based-analyzer.js +194 -0
  50. package/rules/security/S055_content_type_validation/analyzer.js +121 -279
  51. package/rules/security/S055_content_type_validation/symbol-based-analyzer.js +346 -0
  52. package/rules/tests/C002_no_duplicate_code.test.js +111 -22
  53. package/docs/CONSTANTS-ARCHITECTURE.md +0 -288
  54. package/docs/DEPLOYMENT-STRATEGIES.md +0 -270
  55. package/docs/ESLINT_INTEGRATION.md +0 -238
  56. package/docs/PERFORMANCE_MIGRATION_GUIDE.md +0 -368
  57. package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +0 -255
  58. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +0 -755
  59. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +0 -296
@@ -1,102 +1,690 @@
1
1
  // rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js
2
- const { SyntaxKind, Project, Node } = require('ts-morph');
2
+ const { SyntaxKind, Project, Node } = require("ts-morph");
3
3
 
4
4
  class C067SymbolBasedAnalyzer {
5
5
  constructor(semanticEngine = null) {
6
6
  this.semanticEngine = semanticEngine;
7
7
  this.verbose = false;
8
-
8
+
9
9
  // Common UI/framework strings that should be excluded
10
10
  this.UI_STRINGS = [
11
- 'checkbox', 'button', 'search', 'remove', 'submit', 'cancel', 'ok', 'close',
12
- 'Authorization', 'User-Agent', 'Content-Type', 'Accept', 'Bearer',
13
- 'ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'bottom', 'top', 'left', 'right',
14
- 'next-auth/react', '@nestjs/swagger', '@nestjs/common', 'nestjs-pino'
11
+ "checkbox",
12
+ "button",
13
+ "search",
14
+ "remove",
15
+ "submit",
16
+ "cancel",
17
+ "ok",
18
+ "close",
19
+ "Authorization",
20
+ "User-Agent",
21
+ "Content-Type",
22
+ "Accept",
23
+ "Bearer",
24
+ "ArrowDown",
25
+ "ArrowUp",
26
+ "ArrowLeft",
27
+ "ArrowRight",
28
+ "bottom",
29
+ "top",
30
+ "left",
31
+ "right",
32
+ "next-auth/react",
33
+ "@nestjs/swagger",
34
+ "@nestjs/common",
35
+ "nestjs-pino",
15
36
  ];
16
-
37
+
17
38
  // Test-related strings to exclude
18
39
  this.TEST_PATTERNS = [
19
40
  /^(test|mock|example|dummy|placeholder|fixture|stub)/i,
20
41
  /^(User \d+|Test User|Admin User)/i,
21
42
  /^(group\d+|item\d+|element\d+)/i,
22
- /^(abcdef\d+|123456|test-\w+)/i
43
+ /^(abcdef\d+|123456|test-\w+)/i,
23
44
  ];
24
-
25
- // Configuration patterns to detect - focused on environment-dependent config
45
+
46
+ // Configuration patterns to detect - based on Rule C067 requirements
26
47
  this.configPatterns = {
27
- // API endpoints and URLs - only external URLs, not internal endpoints
48
+ // API URLs and endpoints - external URLs that differ by environment
28
49
  urls: {
29
- regex: /^https?:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)([a-zA-Z0-9-]+\.[a-zA-Z]{2,}|[^\/\s]+\.[^\/\s]+)(\/[^\s]*)?$/,
50
+ regex:
51
+ /^https?:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)([a-zA-Z0-9-]+\.[a-zA-Z]{2,}|[^\/\s]+\.[^\/\s]+)(\/[^\s]*)?$/,
30
52
  exclude: [
31
- /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?/, // Local development
32
- /^https?:\/\/(example\.com|test\.com|dummy\.com)/, // Test domains
33
- /^(http|https):\/\/\$\{.+\}/ // Template URLs with variables
34
- ]
53
+ /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?/, // Local development
54
+ /^https?:\/\/(example\.com|test\.com|dummy\.com)/, // Test domains
55
+ /^(http|https):\/\/\$\{.+\}/, // Template URLs with variables
56
+ ],
35
57
  },
36
-
37
- // Environment-dependent numeric values (ports, timeouts that differ by env)
58
+
59
+ // Timeouts, retry intervals, batch sizes - environment-dependent numeric values
38
60
  environmentNumbers: {
39
- // Only consider numbers that are commonly different between environments
40
61
  isEnvironmentDependent: (value, context) => {
41
62
  const lowerContext = context.toLowerCase();
42
-
63
+
43
64
  // Business logic numbers are NOT environment config
44
65
  const businessLogicPatterns = [
45
66
  /limit|max|min|size|count|length|threshold/i,
46
67
  /page|record|item|batch|chunk|export/i,
47
68
  /width|height|margin|padding/i,
48
- /attempt|retry|step/i
69
+ /attempt|retry|step/i,
49
70
  ];
50
-
51
- if (businessLogicPatterns.some(pattern => pattern.test(context))) {
71
+
72
+ if (businessLogicPatterns.some((pattern) => pattern.test(context))) {
52
73
  return false;
53
74
  }
54
-
55
- // Very specific values that are usually business constants
75
+
76
+ // Skip common business constants
56
77
  const businessConstants = [
57
- 20000, 10000, 5000, 1000, 500, 100, 50, 20, 10, 5, // Common limits
58
- 404, 500, 200, 201, 400, 401, 403, // HTTP status codes
59
- 24, 60, 3600, 86400, // Time constants (hours, minutes, seconds)
60
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 // Simple counters
78
+ 20000,
79
+ 10000,
80
+ 5000,
81
+ 1000,
82
+ 500,
83
+ 100,
84
+ 50,
85
+ 20,
86
+ 10,
87
+ 5, // Common limits
88
+ 404,
89
+ 500,
90
+ 200,
91
+ 201,
92
+ 400,
93
+ 401,
94
+ 403, // HTTP status codes
95
+ 24,
96
+ 60,
97
+ 3600,
98
+ 86400, // Time constants (hours, minutes, seconds)
99
+ 1,
100
+ 2,
101
+ 3,
102
+ 4,
103
+ 5,
104
+ 6,
105
+ 7,
106
+ 8,
107
+ 9,
108
+ 10, // Simple counters
61
109
  ];
62
-
110
+
63
111
  if (businessConstants.includes(value)) {
64
112
  return false;
65
113
  }
66
-
67
- // Port numbers (except common ones like 80, 443, 3000, 8080)
68
- if (typeof value === 'number' && value > 1000 && value < 65536) {
69
- const commonPorts = [3000, 8000, 8080, 9000, 5000, 4200, 4000];
70
- if (!commonPorts.includes(value)) {
71
- // Check if context suggests it's a port
114
+
115
+ // Timeouts and intervals (values > 1000ms that might differ by environment)
116
+ if (typeof value === "number" && value > 1000) {
117
+ return /timeout|interval|delay|duration|retry|batch/i.test(context);
118
+ }
119
+
120
+ // Port numbers (except common development ports)
121
+ if (typeof value === "number" && value > 1000 && value < 65536) {
122
+ const commonDevPorts = [3000, 8000, 8080, 9000, 5000, 4200, 4000];
123
+ if (!commonDevPorts.includes(value)) {
72
124
  return /port|listen|bind|server/i.test(context);
73
125
  }
74
126
  }
75
-
76
- // Large timeout values that might differ by environment (> 10 seconds)
77
- if (typeof value === 'number' && value > 10000) {
78
- return /timeout|interval|delay|duration/i.test(context) &&
79
- !businessLogicPatterns.some(pattern => pattern.test(context));
80
- }
81
-
127
+
82
128
  return false;
83
- }
129
+ },
84
130
  },
85
-
131
+
86
132
  // Database and connection strings
87
133
  connections: {
88
- regex: /^(mongodb|mysql|postgres|redis|elasticsearch):\/\/|^jdbc:|^Server=|^Data Source=/i
134
+ regex: /^(mongodb|mysql|postgres|redis|elasticsearch):\/\/|^jdbc:|^Server=|^Data Source=/i,
89
135
  },
90
-
91
- // API Keys and tokens (but exclude validation messages)
136
+
137
+ // Credentials - API keys, passwords, tokens
92
138
  credentials: {
93
- keywords: ['apikey', 'api_key', 'secret_key', 'access_token', 'client_secret'],
139
+ keywords: [
140
+ "apikey",
141
+ "api_key",
142
+ "secret_key",
143
+ "access_token",
144
+ "client_secret",
145
+ "password",
146
+ "token",
147
+ "key",
148
+ ],
94
149
  exclude: [
95
- /must contain|should contain|invalid|error|message/i, // Validation messages
96
- /description|comment|note/i, // Descriptions
97
- /^[a-z\s]{10,}$/i // Long descriptive text
98
- ]
99
- }
150
+ /must contain|should contain|invalid|error|message/i, // Validation messages
151
+ /description|comment|note/i, // Descriptions
152
+ /^[a-z\s]{10,}$/i, // Long descriptive text
153
+ ],
154
+ },
155
+
156
+ // Feature flags and toggles
157
+ featureFlags: {
158
+ keywords: ["feature", "flag", "toggle", "enable", "disable", "enabled", "disabled"],
159
+ patterns: [
160
+ /^(enable|disable)[A-Z]/, // enableFeature, disableLogging
161
+ /[A-Z][a-z]+(Flag|Toggle|Enabled|Disabled)$/, // newUIFlag, debugEnabled
162
+ /^FEATURE_[A-Z_]+$/, // FEATURE_NEW_UI
163
+ /^(is|has)[A-Z][a-z]+Enabled$/, // isDebugEnabled
164
+ ],
165
+ },
166
+
167
+ // Thresholds and limits that might vary by environment
168
+ thresholds: {
169
+ keywords: ["threshold", "limit", "max", "min"],
170
+ contextPatterns: [
171
+ /memory|cpu|disk|storage/i, // Resource thresholds
172
+ /rate|request|connection/i, // Rate limiting
173
+ /pool|queue|buffer/i, // Resource pools
174
+ ],
175
+ },
176
+
177
+ // Security & Authentication - Phase 1 extension
178
+ security: {
179
+ corsOrigins: {
180
+ keywords: ["cors", "origin", "allowed"],
181
+ patterns: [
182
+ /^https?:\/\/[^\/]+$/, // URLs without paths
183
+ /\.(com|org|net|dev|staging|prod)$/i,
184
+ ],
185
+ },
186
+ sessionConfig: {
187
+ keywords: ["session", "jwt", "token", "auth", "expiry", "expire"],
188
+ timePatterns: [
189
+ /^\d+[smhd]$/, // 24h, 30m, 60s, 7d
190
+ /^\d{3,}$/, // Large numbers (seconds)
191
+ ],
192
+ },
193
+ },
194
+
195
+ // Infrastructure Config - Phase 1 extension
196
+ infrastructure: {
197
+ caching: {
198
+ keywords: ["cache", "ttl", "expire", "redis", "prefix"],
199
+ patterns: [
200
+ /^[a-zA-Z]+:[a-zA-Z]+:/, // Prefixes like "myapp:prod:"
201
+ /^\d{3,}$/, // TTL values in seconds
202
+ ],
203
+ },
204
+ logging: {
205
+ keywords: ["log", "level"],
206
+ levels: ["trace", "debug", "info", "warn", "error", "fatal"],
207
+ },
208
+ performance: {
209
+ keywords: ["worker", "thread", "concurrency", "queue", "upload", "download"],
210
+ contextPatterns: [
211
+ /worker|thread|process/i,
212
+ /concurrency|parallel|queue/i,
213
+ /upload|download|file.*size/i,
214
+ ],
215
+ },
216
+ },
217
+
218
+ // Environment-specific patterns
219
+ environments: {
220
+ names: ["production", "prod", "staging", "stage", "development", "dev", "test"],
221
+ patterns: [
222
+ /^(production|prod|staging|stage|development|dev|test)$/i,
223
+ /\.(prod|staging|dev)\./, // domain patterns
224
+ /_(prod|staging|dev)_/i, // variable patterns
225
+ ],
226
+ },
227
+
228
+ // Service dependencies
229
+ services: {
230
+ keywords: ["service", "endpoint", "host", "port"],
231
+ patterns: [
232
+ /^https?:\/\/[a-zA-Z-]+-service/, // microservice URLs
233
+ /:[0-9]{4,5}$/, // Port numbers
234
+ /service.*url|url.*service/i,
235
+ ],
236
+ },
237
+
238
+ // ============ Phase 2: Critical Configuration Patterns ============
239
+
240
+ // Database & Storage Configuration
241
+ database: {
242
+ poolConfig: {
243
+ keywords: ["pool", "connection", "max", "min", "idle"],
244
+ patterns: [
245
+ /pool.*size|max.*connections?|min.*connections?/i,
246
+ /connection.*pool/i,
247
+ /idle.*timeout|acquire.*timeout/i,
248
+ ],
249
+ },
250
+ queryConfig: {
251
+ keywords: ["query", "timeout", "retry", "transaction"],
252
+ patterns: [
253
+ /query.*timeout|statement.*timeout/i,
254
+ /transaction.*isolation|isolation.*level/i,
255
+ /read.*timeout|write.*timeout/i,
256
+ ],
257
+ },
258
+ schemaNames: {
259
+ keywords: ["table", "collection", "database", "schema", "shard", "partition"],
260
+ patterns: [
261
+ /^[a-z_]+_20\d{2}$/, // Table names with year: users_2024
262
+ /^shard_\d+$|^partition_\d+$/i, // Shard identifiers
263
+ /table.*name|collection.*name/i,
264
+ ],
265
+ },
266
+ },
267
+
268
+ // Security & Authentication (Extended)
269
+ securityExtended: {
270
+ tokenConfig: {
271
+ keywords: ["token", "jwt", "expiry", "expire", "ttl"],
272
+ patterns: [
273
+ /^\d{3,}$/, // Expiry in seconds: 7200, 86400
274
+ /^\d+[smhd]$/, // Human readable: 2h, 30m, 7d
275
+ /expir(y|e|ation)|ttl/i,
276
+ ],
277
+ },
278
+ passwordPolicy: {
279
+ keywords: ["password", "length", "complexity", "require", "min", "max"],
280
+ patterns: [
281
+ /min.*(password|length)|password.*min/i,
282
+ /password.*(complexity|requirement|policy)/i,
283
+ /must.*contain|should.*contain|require.*\d+/i,
284
+ ],
285
+ },
286
+ rateLimiting: {
287
+ keywords: ["rate", "limit", "attempt", "throttle", "max"],
288
+ patterns: [
289
+ /max.*(attempt|tries|request)|attempt.*limit/i,
290
+ /rate.*limit|throttle/i,
291
+ /request.*per.*(minute|hour|second)/i,
292
+ ],
293
+ },
294
+ encryptionConfig: {
295
+ keywords: ["encrypt", "cipher", "algorithm", "mode", "aes", "rsa"],
296
+ patterns: [
297
+ /^(AES|RSA|DES|3DES|Blowfish)-\d+(-[A-Z]+)?$/i, // AES-256-GCM
298
+ /encryption.*algorithm|cipher.*mode/i,
299
+ ],
300
+ },
301
+ oauthConfig: {
302
+ keywords: ["oauth", "scope", "grant", "client"],
303
+ patterns: [
304
+ /^(read|write|admin):[a-z_]+$/i, // OAuth scopes: read:user
305
+ /scope|grant.*type|client.*id/i,
306
+ ],
307
+ },
308
+ },
309
+
310
+ // File System & Paths
311
+ fileSystem: {
312
+ directories: {
313
+ keywords: ["dir", "directory", "path", "folder", "upload", "download", "temp"],
314
+ patterns: [
315
+ /^\/[a-z]+\/[a-z]+\//i, // Unix absolute paths: /var/www/uploads
316
+ /^[A-Z]:\\/i, // Windows paths: C:\Users\...
317
+ /upload.*dir|download.*dir|temp.*dir/i,
318
+ /^\.\/[a-z_-]+\//i, // Relative paths: ./uploads/
319
+ ],
320
+ },
321
+ fileLimits: {
322
+ keywords: ["file", "size", "limit", "max", "upload"],
323
+ patterns: [
324
+ /file.*size|max.*size|size.*limit/i,
325
+ /upload.*limit|download.*limit/i,
326
+ /^\d{6,}$/, // Large byte values: 10485760 (10MB)
327
+ ],
328
+ },
329
+ fileTypes: {
330
+ keywords: ["extension", "type", "allow", "mime", "accept"],
331
+ patterns: [
332
+ /^\.[a-z0-9]{2,4}$/i, // File extensions: .jpg, .pdf
333
+ /allowed.*(type|extension)|accept.*type/i,
334
+ /^(image|video|audio|application)\/[a-z0-9+-]+$/i, // MIME types
335
+ ],
336
+ },
337
+ logPaths: {
338
+ keywords: ["log", "path", "file"],
339
+ patterns: [/^\/var\/log\//i, /\.log$/i, /log.*file|log.*path/i],
340
+ },
341
+ },
342
+
343
+ // ============ Phase 3: Important Configuration Patterns ============
344
+
345
+ // Network & Protocol Configuration
346
+ network: {
347
+ httpConfig: {
348
+ keywords: ["method", "header", "content", "type", "accept"],
349
+ httpMethods: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
350
+ patterns: [
351
+ /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$/,
352
+ /allowed.*method|http.*method/i,
353
+ /content-type|accept|user-agent/i,
354
+ ],
355
+ },
356
+ timeouts: {
357
+ keywords: ["connect", "read", "write", "timeout", "socket"],
358
+ patterns: [
359
+ /connect.*timeout|connection.*timeout/i,
360
+ /read.*timeout|write.*timeout/i,
361
+ /socket.*timeout|idle.*timeout/i,
362
+ ],
363
+ },
364
+ bufferConfig: {
365
+ keywords: ["buffer", "size", "socket", "receive", "send"],
366
+ patterns: [/buffer.*size|socket.*buffer/i, /receive.*buffer|send.*buffer/i],
367
+ },
368
+ keepAlive: {
369
+ keywords: ["keepalive", "keep", "alive", "ping", "interval"],
370
+ patterns: [/keep.*alive|keepalive/i, /ping.*interval|heartbeat/i, /websocket.*ping/i],
371
+ },
372
+ },
373
+
374
+ // Business Rules & Limits
375
+ business: {
376
+ pricing: {
377
+ keywords: ["price", "cost", "fee", "charge", "plan"],
378
+ patterns: [
379
+ /^\d+\.\d{2}$/, // Price values: 49.99, 19.95
380
+ /price|cost|fee|charge/i,
381
+ /plan.*(price|cost)/i,
382
+ ],
383
+ },
384
+ quotas: {
385
+ keywords: ["quota", "limit", "plan", "tier", "free", "premium"],
386
+ patterns: [
387
+ /quota|limit.*per.*plan|plan.*limit/i,
388
+ /free.*plan|premium.*plan|enterprise.*plan/i,
389
+ /api.*calls.*per|request.*per.*day/i,
390
+ ],
391
+ },
392
+ discounts: {
393
+ keywords: ["discount", "promo", "coupon", "rate", "percent"],
394
+ patterns: [
395
+ /^0\.[0-9]{1,2}$/, // Decimal rates: 0.15 (15%)
396
+ /discount.*rate|promo.*code/i,
397
+ ],
398
+ },
399
+ trials: {
400
+ keywords: ["trial", "demo", "expiry", "days", "period"],
401
+ patterns: [/trial.*days|trial.*period/i, /demo.*period|expiry.*days/i],
402
+ },
403
+ },
404
+
405
+ // Monitoring & Observability
406
+ monitoring: {
407
+ metricsConfig: {
408
+ keywords: ["metric", "interval", "collect", "sample", "export"],
409
+ patterns: [/metric.*interval|collection.*interval/i, /export.*interval|push.*interval/i],
410
+ },
411
+ alertThresholds: {
412
+ keywords: ["alert", "threshold", "warn", "error", "critical"],
413
+ patterns: [
414
+ /alert.*threshold|threshold.*alert/i,
415
+ /error.*rate|error.*threshold/i,
416
+ /^0\.[0-9]{1,3}$/, // Percentage thresholds: 0.05 (5%)
417
+ ],
418
+ },
419
+ samplingRates: {
420
+ keywords: ["sample", "sampling", "rate", "trace"],
421
+ patterns: [
422
+ /sampling.*rate|sample.*rate/i,
423
+ /trace.*sampling/i,
424
+ /^0\.[0-9]{1,2}$/, // Sampling rates: 0.1 (10%)
425
+ ],
426
+ },
427
+ healthChecks: {
428
+ keywords: ["health", "check", "interval", "ping", "probe"],
429
+ patterns: [/health.*check|liveness|readiness/i, /check.*interval|probe.*interval/i],
430
+ },
431
+ },
432
+
433
+ // ============ Phase 4: Enhancement Configuration Patterns ============
434
+
435
+ // Message Queue & Event Configuration
436
+ messageQueue: {
437
+ queueConfig: {
438
+ keywords: ["queue", "size", "capacity", "max", "buffer"],
439
+ patterns: [/queue.*size|max.*queue|queue.*capacity/i, /buffer.*size.*queue/i],
440
+ },
441
+ messageTTL: {
442
+ keywords: ["message", "ttl", "expiry", "expire", "retention"],
443
+ patterns: [/message.*ttl|message.*expiry/i, /retention.*period|expire.*after/i],
444
+ },
445
+ queueNames: {
446
+ keywords: ["queue", "topic", "exchange", "dlq", "dead"],
447
+ patterns: [
448
+ /^[a-z-]+-queue$/i, // Queue names: user-events-queue
449
+ /dead.*letter|dlq|failed.*messages?/i,
450
+ /topic.*name|exchange.*name/i,
451
+ ],
452
+ },
453
+ consumerConfig: {
454
+ keywords: ["consumer", "group", "partition", "offset"],
455
+ patterns: [/consumer.*group|group.*id/i, /partition.*count|offset.*reset/i],
456
+ },
457
+ },
458
+
459
+ // Deployment & Infrastructure
460
+ deployment: {
461
+ resourceLimits: {
462
+ keywords: ["cpu", "memory", "limit", "request", "resource"],
463
+ patterns: [
464
+ /^\d+m$/i, // CPU millicores: 2000m
465
+ /^\d+[MG]i$/i, // Memory: 4Gi, 512Mi
466
+ /cpu.*limit|memory.*limit/i,
467
+ /resource.*limit|resource.*request/i,
468
+ ],
469
+ },
470
+ scalingConfig: {
471
+ keywords: ["scale", "replica", "min", "max", "threshold"],
472
+ patterns: [
473
+ /min.*replicas?|max.*replicas?/i,
474
+ /scale.*threshold|auto.*scale/i,
475
+ /horizontal.*pod.*autoscaler|hpa/i,
476
+ ],
477
+ },
478
+ regionConfig: {
479
+ keywords: ["region", "zone", "location", "deploy", "availability"],
480
+ patterns: [
481
+ /^[a-z]{2}-[a-z]+-\d+$/i, // AWS regions: us-east-1
482
+ /deploy.*region|region.*name/i,
483
+ /availability.*zone/i,
484
+ ],
485
+ },
486
+ instanceTypes: {
487
+ keywords: ["instance", "type", "machine", "node", "vm"],
488
+ patterns: [
489
+ /^[a-z]\d\.[a-z]+$/i, // AWS instance types: t3.micro
490
+ /instance.*type|machine.*type/i,
491
+ /node.*selector|node.*type/i,
492
+ ],
493
+ },
494
+ },
495
+
496
+ // Third-party Integration
497
+ integration: {
498
+ webhookURLs: {
499
+ keywords: ["webhook", "callback", "notify", "hook"],
500
+ patterns: [/webhook|callback.*url/i, /^https?:\/\/[^\/]+\/webhooks?\//i],
501
+ },
502
+ externalServices: {
503
+ keywords: ["provider", "service", "integration", "api"],
504
+ patterns: [
505
+ /^(stripe|paypal|twilio|sendgrid|slack|github)$/i, // Service names
506
+ /provider.*name|integration.*name/i,
507
+ /external.*service/i,
508
+ ],
509
+ },
510
+ apiVersions: {
511
+ keywords: ["version", "api", "v"],
512
+ patterns: [
513
+ /^v?\d{4}-\d{2}-\d{2}$/i, // Date versions: 2023-10-16
514
+ /^v\d+(\.\d+)?$/i, // Semantic versions: v1, v2.1
515
+ /api.*version/i,
516
+ ],
517
+ },
518
+ channelIds: {
519
+ keywords: ["channel", "room", "chat", "notify"],
520
+ patterns: [
521
+ /^#[a-z-]+$/i, // Slack channels: #production-alerts
522
+ /channel.*id|room.*id/i,
523
+ ],
524
+ },
525
+ },
526
+
527
+ // Localization & Formatting
528
+ localization: {
529
+ timezones: {
530
+ keywords: ["timezone", "zone", "tz"],
531
+ patterns: [
532
+ /^[A-Z][a-z]+\/[A-Z][a-z]+$/i, // IANA timezones: Asia/Tokyo
533
+ /timezone|time.*zone/i,
534
+ ],
535
+ },
536
+ dateFormats: {
537
+ keywords: ["date", "format", "pattern", "time"],
538
+ patterns: [
539
+ /^[YMDHms\-\/:\s]+$/, // Date format patterns: YYYY-MM-DD
540
+ /date.*format|time.*format/i,
541
+ /format.*string.*date/i,
542
+ ],
543
+ },
544
+ currencies: {
545
+ keywords: ["currency", "code", "symbol"],
546
+ patterns: [
547
+ /^[A-Z]{3}$/i, // Currency codes: USD, EUR, JPY
548
+ /currency.*code/i,
549
+ ],
550
+ },
551
+ locales: {
552
+ keywords: ["locale", "language", "lang", "i18n"],
553
+ patterns: [
554
+ /^[a-z]{2}-[A-Z]{2}$/i, // Locale codes: en-US, ja-JP
555
+ /locale|language.*code/i,
556
+ ],
557
+ },
558
+ numberFormats: {
559
+ keywords: ["number", "format", "decimal", "thousand", "separator"],
560
+ patterns: [/number.*format|decimal.*separator/i, /thousand.*separator/i],
561
+ },
562
+ },
563
+
564
+ // ============ Additional Critical Patterns ============
565
+
566
+ // Environment Variable Names (hardcoded)
567
+ environmentVars: {
568
+ keywords: ["process.env", "env"],
569
+ patterns: [
570
+ /^(PROD|DEV|STAGING|TEST)_[A-Z_]+$/i, // Environment-prefixed vars
571
+ /^[A-Z_]+_(PROD|DEV|STAGING|TEST)$/i,
572
+ /^(PRODUCTION|DEVELOPMENT)_/i,
573
+ ],
574
+ },
575
+
576
+ // Third-party Service IDs
577
+ thirdPartyServices: {
578
+ stripe: {
579
+ patterns: [
580
+ /^pk_(test|live)_[a-zA-Z0-9]{24,}$/, // Publishable keys
581
+ /^sk_(test|live)_[a-zA-Z0-9]{24,}$/, // Secret keys
582
+ ],
583
+ },
584
+ googleAnalytics: {
585
+ patterns: [
586
+ /^UA-\d+-\d+$/, // Universal Analytics
587
+ /^G-[A-Z0-9]{10}$/, // GA4
588
+ /^GTM-[A-Z0-9]+$/, // Google Tag Manager
589
+ ],
590
+ },
591
+ sentry: {
592
+ patterns: [/^https?:\/\/[a-f0-9]+@[^\/]+\.ingest\.sentry\.io\/\d+$/i],
593
+ },
594
+ googleMaps: {
595
+ patterns: [
596
+ /^AIzaSy[a-zA-Z0-9_-]{33}$/, // Google Maps API Key
597
+ ],
598
+ },
599
+ firebase: {
600
+ patterns: [/^[a-z0-9-]+\.firebaseapp\.com$/i, /^[a-z0-9-]+\.firebase(io|database)\.com$/i],
601
+ },
602
+ aws: {
603
+ patterns: [
604
+ /^AKIA[A-Z0-9]{16}$/, // AWS Access Key ID
605
+ /^[a-z0-9-]+\.s3\.[a-z0-9-]+\.amazonaws\.com$/i,
606
+ ],
607
+ },
608
+ social: {
609
+ patterns: [
610
+ /^\d{15,16}$/, // Facebook App ID
611
+ /^[a-zA-Z0-9]{25}$/, // Twitter Bearer Token format
612
+ ],
613
+ },
614
+ },
615
+
616
+ // IP Addresses & Hostnames
617
+ ipAddresses: {
618
+ patterns: [
619
+ /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, // IPv4
620
+ /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/, // IPv6
621
+ /^(?:[0-9a-fA-F]{1,4}:){1,7}:$/, // IPv6 shortened
622
+ ],
623
+ keywords: ["host", "ip", "address", "server"],
624
+ privateRanges: [/^10\./, /^172\.(1[6-9]|2[0-9]|3[0-1])\./, /^192\.168\./, /^127\./, /^0\.0\.0\.0$/],
625
+ },
626
+
627
+ // Internal Hostnames
628
+ hostnames: {
629
+ patterns: [
630
+ /^[a-z0-9-]+\.(internal|local|corp|lan)$/i,
631
+ /^[a-z0-9-]+-(?:master|slave|replica|primary|secondary)$/i,
632
+ /^(?:db|redis|kafka|mongo|postgres|mysql)-[a-z0-9-]+$/i,
633
+ ],
634
+ keywords: ["host", "hostname", "server", "broker", "endpoint"],
635
+ },
636
+
637
+ // Cron Job Schedules
638
+ cronSchedules: {
639
+ patterns: [
640
+ /^[\d\*\/,\-]+\s+[\d\*\/,\-]+\s+[\d\*\/,\-]+\s+[\d\*\/,\-]+\s+[\d\*\/,\-]+$/, // Standard cron
641
+ /^@(?:yearly|annually|monthly|weekly|daily|hourly|reboot)$/i, // Predefined schedules
642
+ ],
643
+ keywords: ["cron", "schedule", "interval"],
644
+ },
645
+
646
+ // Magic Numbers (Business Logic)
647
+ magicNumbers: {
648
+ // Numbers that appear in business logic contexts
649
+ keywords: ["age", "limit", "max", "min", "threshold", "rate", "tax", "fee", "discount", "commission"],
650
+ businessContexts: [
651
+ /legal.*age|minimum.*age|age.*requirement/i,
652
+ /tax.*rate|vat.*rate|commission.*rate/i,
653
+ /interest.*rate|exchange.*rate/i,
654
+ /shipping.*fee|processing.*fee|service.*fee/i,
655
+ /credit.*limit|withdrawal.*limit|transfer.*limit/i,
656
+ ],
657
+ },
658
+
659
+ // Email & SMS Templates
660
+ messageTemplates: {
661
+ email: {
662
+ keywords: ["subject", "body", "template", "email"],
663
+ patterns: [
664
+ /^[A-Z][a-zA-Z\s]{10,}$/, // English subject lines
665
+ /\{{\s*[a-z_]+\s*\}}/, // Template variables
666
+ ],
667
+ },
668
+ sms: {
669
+ keywords: ["sms", "text", "message"],
670
+ patterns: [/^Your\s+[A-Z]/i, /OTP|verification code|confirm/i],
671
+ },
672
+ },
673
+
674
+ // Version Numbers
675
+ versions: {
676
+ patterns: [
677
+ /^v?\d+\.\d+\.\d+(?:-[a-z0-9.]+)?$/i, // Semantic version: 1.2.3, v1.2.3-beta
678
+ /^v\d+$/i, // API version: v1, v2
679
+ ],
680
+ keywords: ["version", "api.*version", "min.*version", "max.*version"],
681
+ },
682
+
683
+ // Default Pagination
684
+ pagination: {
685
+ keywords: ["page", "limit", "size", "offset", "per.*page"],
686
+ defaults: [10, 20, 25, 50, 100], // Common pagination values
687
+ },
100
688
  };
101
689
  }
102
690
 
@@ -109,44 +697,46 @@ class C067SymbolBasedAnalyzer {
109
697
 
110
698
  async analyzeFileBasic(filePath, options = {}) {
111
699
  const violations = [];
112
-
700
+
113
701
  try {
114
702
  const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
115
703
  if (!sourceFile) {
116
704
  if (this.verbose) {
117
- console.log(`[DEBUG] 🔍 C067: File not in semantic project, trying standalone: ${filePath.split('/').pop()}`);
705
+ console.log(
706
+ `[DEBUG] 🔍 C067: File not in semantic project, trying standalone: ${filePath.split("/").pop()}`
707
+ );
118
708
  }
119
709
  // Fallback to standalone analysis if file not in semantic project
120
710
  return await this.analyzeFileStandalone(filePath, options);
121
711
  }
122
712
 
123
713
  if (this.verbose) {
124
- console.log(`[DEBUG] 🔍 C067: Analyzing hardcoded config in ${filePath.split('/').pop()}`);
714
+ console.log(`[DEBUG] 🔍 C067: Analyzing hardcoded config in ${filePath.split("/").pop()}`);
125
715
  }
126
716
 
127
717
  // Skip test files and config files themselves
128
718
  if (this.isConfigOrTestFile(filePath)) {
129
719
  if (this.verbose) {
130
- console.log(`[DEBUG] 🔍 C067: Skipping config/test file: ${filePath.split('/').pop()}`);
720
+ console.log(`[DEBUG] 🔍 C067: Skipping config/test file: ${filePath.split("/").pop()}`);
131
721
  }
132
722
  return violations;
133
723
  }
134
724
 
135
725
  // Find hardcoded configuration values
136
726
  const hardcodedConfigs = this.findHardcodedConfigs(sourceFile);
137
-
727
+
138
728
  for (const config of hardcodedConfigs) {
139
729
  violations.push({
140
- ruleId: 'C067',
730
+ ruleId: "C067",
141
731
  message: this.createMessage(config),
142
732
  filePath: filePath,
143
733
  line: config.line,
144
734
  column: config.column,
145
- severity: 'warning',
146
- category: 'configuration',
735
+ severity: "warning",
736
+ category: "configuration",
147
737
  type: config.type,
148
738
  value: config.value,
149
- suggestion: this.getSuggestion(config.type)
739
+ suggestion: this.getSuggestion(config.type),
150
740
  });
151
741
  }
152
742
 
@@ -165,67 +755,69 @@ class C067SymbolBasedAnalyzer {
165
755
 
166
756
  async analyzeFileStandalone(filePath, options = {}) {
167
757
  const violations = [];
168
-
758
+
169
759
  try {
170
760
  // Create a standalone ts-morph project for this analysis
171
761
  const project = new Project({
172
762
  compilerOptions: {
173
- target: 'ES2020',
174
- module: 'CommonJS',
763
+ target: "ES2020",
764
+ module: "CommonJS",
175
765
  allowJs: true,
176
766
  allowSyntheticDefaultImports: true,
177
767
  esModuleInterop: true,
178
768
  skipLibCheck: true,
179
- strict: false
769
+ strict: false,
180
770
  },
181
- useInMemoryFileSystem: true
771
+ useInMemoryFileSystem: true,
182
772
  });
183
773
 
184
774
  // Add the source file to the project
185
- const fs = require('fs');
186
- const path = require('path');
187
-
775
+ const fs = require("fs");
776
+ const path = require("path");
777
+
188
778
  // Check if file exists first
189
779
  if (!fs.existsSync(filePath)) {
190
780
  throw new Error(`File not found on filesystem: ${filePath}`);
191
781
  }
192
-
782
+
193
783
  // Read file content and create source file
194
- const fileContent = fs.readFileSync(filePath, 'utf8');
784
+ const fileContent = fs.readFileSync(filePath, "utf8");
195
785
  const fileName = path.basename(filePath);
196
786
  const sourceFile = project.createSourceFile(fileName, fileContent);
197
-
787
+
198
788
  if (!sourceFile) {
199
789
  throw new Error(`Source file not found: ${filePath}`);
200
790
  }
201
791
 
202
792
  if (this.verbose) {
203
- console.log(`[DEBUG] 🔍 C067: Analyzing hardcoded config in ${filePath.split('/').pop()} (standalone)`);
793
+ console.log(
794
+ `[DEBUG] 🔍 C067: Analyzing hardcoded config in ${filePath.split("/").pop()} (standalone)`
795
+ );
204
796
  }
205
797
 
206
798
  // Skip test files and config files themselves
207
799
  if (this.isConfigOrTestFile(filePath)) {
208
800
  if (this.verbose) {
209
- console.log(`[DEBUG] 🔍 C067: Skipping config/test file: ${filePath.split('/').pop()}`);
801
+ console.log(`[DEBUG] 🔍 C067: Skipping config/test file: ${filePath.split("/").pop()}`);
210
802
  }
211
803
  return violations;
212
804
  }
213
805
 
214
806
  // Find hardcoded configuration values
215
807
  const hardcodedConfigs = this.findHardcodedConfigs(sourceFile);
216
-
808
+
217
809
  for (const config of hardcodedConfigs) {
218
810
  violations.push({
219
- ruleId: 'C067',
811
+ ruleId: "C067",
220
812
  message: this.createMessage(config),
221
813
  filePath: filePath,
222
814
  line: config.line,
223
815
  column: config.column,
224
- severity: 'warning',
225
- category: 'configuration',
816
+ severity: "warning",
817
+ category: "configuration",
226
818
  type: config.type,
227
819
  value: config.value,
228
- suggestion: this.getSuggestion(config.type)
820
+ suggestion: this.getSuggestion(config.type),
229
821
  });
230
822
  }
231
823
 
@@ -255,9 +847,9 @@ class C067SymbolBasedAnalyzer {
255
847
  /\.env\./,
256
848
  /constants\.(ts|js)$/,
257
849
  /settings\.(ts|js)$/,
258
- /defaults\.(ts|js)$/
850
+ /defaults\.(ts|js)$/,
259
851
  ];
260
-
852
+
261
853
  const testPatterns = [
262
854
  /\.(test|spec)\.(ts|tsx|js|jsx)$/,
263
855
  /\/__tests__\//,
@@ -265,55 +857,65 @@ class C067SymbolBasedAnalyzer {
265
857
  /\/tests\//,
266
858
  /\.stories\.(ts|tsx|js|jsx)$/,
267
859
  /\.mock\.(ts|tsx|js|jsx)$/,
268
- /\/dummy\//, // Skip dummy data files
269
- /dummy\.(ts|js)$/, // Skip dummy files
270
- /test-fixtures\//, // Skip test fixture files
271
- /\.fixture\.(ts|js)$/, // Skip fixture files
272
- /entity\.(ts|js)$/ // Skip entity/ORM files (contain DB constraints)
860
+ /\/dummy\//, // Skip dummy data files
861
+ /dummy\.(ts|js)$/, // Skip dummy files
862
+ /test-fixtures\//, // Skip test fixture files
863
+ /\.fixture\.(ts|js)$/, // Skip fixture files
864
+ /entity\.(ts|js)$/, // Skip entity/ORM files (contain DB constraints)
273
865
  ];
274
-
275
- return configPatterns.some(pattern => pattern.test(fileName)) ||
276
- testPatterns.some(pattern => pattern.test(fileName));
866
+
867
+ return (
868
+ configPatterns.some((pattern) => pattern.test(fileName)) ||
869
+ testPatterns.some((pattern) => pattern.test(fileName))
870
+ );
277
871
  }
278
872
 
279
873
  findHardcodedConfigs(sourceFile) {
280
874
  const configs = [];
281
-
875
+
282
876
  // Traverse all nodes in the source file
283
877
  sourceFile.forEachDescendant((node) => {
284
- // Check string literals
878
+ // Check string literals for URLs, credentials, feature flags
285
879
  if (node.getKind() === SyntaxKind.StringLiteral) {
286
880
  const config = this.analyzeStringLiteral(node, sourceFile);
287
881
  if (config) {
288
882
  configs.push(config);
289
883
  }
290
884
  }
291
-
292
- // Check numeric literals
885
+
886
+ // Check numeric literals for timeouts, ports, batch sizes
293
887
  if (node.getKind() === SyntaxKind.NumericLiteral) {
294
888
  const config = this.analyzeNumericLiteral(node, sourceFile);
295
889
  if (config) {
296
890
  configs.push(config);
297
891
  }
298
892
  }
299
-
300
- // Check template literals (for URLs with variables)
893
+
894
+ // Check boolean literals for feature flags
895
+ if (node.getKind() === SyntaxKind.TrueKeyword || node.getKind() === SyntaxKind.FalseKeyword) {
896
+ const config = this.analyzeBooleanLiteral(node, sourceFile);
897
+ if (config) {
898
+ configs.push(config);
899
+ }
900
+ }
901
+
902
+ // Check template literals for URLs with embedded values
301
903
  if (node.getKind() === SyntaxKind.TemplateExpression) {
302
904
  const config = this.analyzeTemplateLiteral(node, sourceFile);
303
905
  if (config) {
304
906
  configs.push(config);
305
907
  }
306
908
  }
307
-
308
- // Check property assignments
909
+
910
+ // Check property assignments for configuration objects
309
911
  if (node.getKind() === SyntaxKind.PropertyAssignment) {
310
912
  const config = this.analyzePropertyAssignment(node, sourceFile);
311
913
  if (config) {
312
914
  configs.push(config);
313
915
  }
314
916
  }
315
-
316
- // Check variable declarations
917
+
918
+ // Check variable declarations for configuration constants
317
919
  if (node.getKind() === SyntaxKind.VariableDeclaration) {
318
920
  const config = this.analyzeVariableDeclaration(node, sourceFile);
319
921
  if (config) {
@@ -321,713 +923,2929 @@ class C067SymbolBasedAnalyzer {
321
923
  }
322
924
  }
323
925
  });
324
-
926
+
325
927
  return configs;
326
928
  }
327
929
 
328
930
  analyzeStringLiteral(node, sourceFile) {
329
931
  const value = node.getLiteralValue();
330
932
  const position = sourceFile.getLineAndColumnAtPos(node.getStart());
331
-
933
+
332
934
  // Skip short strings and common UI values
333
- if (value.length < 4) return null;
334
-
935
+ if (value.length < 3) return null;
936
+
335
937
  const parentContext = this.getParentContext(node);
336
-
938
+
337
939
  // Skip import paths and module names
338
940
  if (this.isImportPath(value, node)) return null;
339
-
941
+
340
942
  // Skip UI strings and labels
341
943
  if (this.isUIString(value)) return null;
342
-
944
+
945
+ // Skip Japanese UI text and form labels (NEW - reduces false positives)
946
+ if (this.isJapaneseUIText(value, parentContext)) return null;
947
+
948
+ // Skip form field labels (NEW - reduces false positives)
949
+ if (this.isFormFieldLabel(value, parentContext)) return null;
950
+
343
951
  // Skip test data and mocks
344
952
  if (this.isTestData(value, parentContext)) return null;
345
-
953
+
346
954
  // Skip validation messages and error messages
347
955
  if (this.isValidationMessage(value, parentContext)) return null;
348
-
956
+
349
957
  // Skip file names and descriptions
350
958
  if (this.isFileNameOrDescription(value, parentContext)) return null;
351
-
959
+
352
960
  // Skip config keys (like 'api.baseUrl', 'features.newUI', etc.)
353
961
  if (this.looksLikeConfigKey(value)) {
354
962
  return null;
355
963
  }
356
-
964
+
357
965
  // Skip if this is used in a config service call
358
- if (parentContext.includes('config.get') || parentContext.includes('config.getString') ||
359
- parentContext.includes('config.getBoolean') || parentContext.includes('config.getNumber')) {
966
+ if (
967
+ parentContext.includes("config.get") ||
968
+ parentContext.includes("config.getString") ||
969
+ parentContext.includes("config.getBoolean") ||
970
+ parentContext.includes("config.getNumber")
971
+ ) {
360
972
  return null;
361
973
  }
362
-
974
+
363
975
  // Skip if this is a property key in an object literal
364
976
  if (this.isPropertyKey(node)) {
365
977
  return null;
366
978
  }
367
-
368
- // Check for environment-dependent URLs only
979
+
980
+ // Check for API URLs and endpoints - Rule C067 requirement
369
981
  if (this.configPatterns.urls.regex.test(value)) {
370
982
  if (!this.isExcludedUrl(value, node) && this.isEnvironmentDependentUrl(value)) {
371
983
  return {
372
- type: 'url',
984
+ type: "api_url",
373
985
  value: value,
374
986
  line: position.line,
375
987
  column: position.column,
376
- node: node
988
+ node: node,
989
+ suggestion: "Move API URLs to environment variables or config files",
377
990
  };
378
991
  }
379
992
  }
380
-
381
- // Check for real credentials (not validation messages)
993
+
994
+ // Check for credentials - Rule C067 requirement
382
995
  if (this.isRealCredential(value, parentContext)) {
383
996
  return {
384
- type: 'credential',
385
- value: value,
997
+ type: "credential",
998
+ value: this.maskSensitiveValue(value),
386
999
  line: position.line,
387
1000
  column: position.column,
388
1001
  node: node,
389
- context: parentContext
1002
+ context: parentContext,
1003
+ suggestion: "Move credentials to secure environment variables or vault",
390
1004
  };
391
1005
  }
392
-
393
- // Check for connection strings
1006
+
1007
+ // Check for connection strings - Rule C067 requirement
394
1008
  if (this.configPatterns.connections.regex.test(value)) {
395
1009
  return {
396
- type: 'connection',
397
- value: value,
1010
+ type: "connection_string",
1011
+ value: this.maskSensitiveValue(value),
398
1012
  line: position.line,
399
1013
  column: position.column,
400
- node: node
1014
+ node: node,
1015
+ suggestion: "Move connection strings to environment variables",
401
1016
  };
402
1017
  }
403
-
404
- return null;
405
- }
406
1018
 
407
- analyzeNumericLiteral(node, sourceFile) {
408
- const value = node.getLiteralValue();
409
- const position = sourceFile.getLineAndColumnAtPos(node.getStart());
410
- const parentContext = this.getParentContext(node);
411
-
412
- // Only check for environment-dependent numbers
413
- if (this.configPatterns.environmentNumbers.isEnvironmentDependent(value, parentContext)) {
1019
+ // Phase 1 extensions - Security & Infrastructure
1020
+
1021
+ // Check for CORS origins
1022
+ if (this.isCorsOrigin(value, parentContext)) {
414
1023
  return {
415
- type: 'environment_config',
1024
+ type: "cors_origin",
416
1025
  value: value,
417
1026
  line: position.line,
418
1027
  column: position.column,
419
1028
  node: node,
420
- context: parentContext
1029
+ suggestion: "Move CORS origins to environment configuration",
421
1030
  };
422
1031
  }
423
-
424
- return null;
425
- }
426
-
427
- analyzeTemplateLiteral(node, sourceFile) {
428
- // For now, focus on simple template literals that might contain URLs
429
- const templateText = node.getFullText();
430
- if (templateText.includes('http://') || templateText.includes('https://')) {
431
- const position = sourceFile.getLineAndColumnAtPos(node.getStart());
432
-
433
- // Check if it's using environment variables or config
434
- if (!templateText.includes('process.env') && !templateText.includes('config.')) {
435
- return {
436
- type: 'template_url',
437
- value: templateText.trim(),
438
- line: position.line,
439
- column: position.column,
440
- node: node
441
- };
442
- }
443
- }
444
-
445
- return null;
446
- }
447
1032
 
448
- analyzePropertyAssignment(node, sourceFile) {
449
- const nameNode = node.getNameNode();
450
- const valueNode = node.getInitializer();
451
-
452
- if (!nameNode || !valueNode) return null;
453
-
454
- const propertyName = nameNode.getText();
455
- const position = sourceFile.getLineAndColumnAtPos(node.getStart());
456
-
457
- // Skip ALL field mapping objects and ORM/database entity configurations
458
- const ancestorObj = node.getParent();
459
- if (ancestorObj && Node.isObjectLiteralExpression(ancestorObj)) {
460
- const objParent = ancestorObj.getParent();
461
- if (objParent && Node.isVariableDeclaration(objParent)) {
462
- const varName = objParent.getName();
463
- // Skip field mappings, database schemas, etc.
464
- if (/mapping|map|field|column|decode|schema|entity|constraint|table/i.test(varName)) {
465
- return null;
466
- }
467
- }
468
-
469
- // Check if this looks like a table column definition or field mapping
470
- const objText = ancestorObj.getText();
471
- if (/primaryKeyConstraintName|foreignKeyConstraintName|key.*may contain/i.test(objText)) {
472
- return null; // Skip database constraint definitions
473
- }
474
- }
475
-
476
- // Skip properties that are clearly field mappings or business data
477
- const businessLogicProperties = [
478
- // Field mappings
479
- 'key', 'field', 'dataKey', 'valueKey', 'labelKey', 'sortKey',
480
- // Business logic
481
- 'endpoint', 'path', 'route', 'method',
482
- 'limit', 'pageSize', 'batchSize', 'maxResults',
483
- 'retry', 'retries', 'maxRetries', 'attempts',
484
- 'count', 'max', 'min', 'size', 'length',
485
- // UI properties
486
- 'className', 'style', 'disabled', 'readonly',
487
- // Database/ORM
488
- 'primaryKeyConstraintName', 'foreignKeyConstraintName', 'constraintName',
489
- 'tableName', 'columnName', 'schemaName'
490
- ];
491
-
492
- const lowerPropertyName = propertyName.toLowerCase();
493
- if (businessLogicProperties.some(prop => lowerPropertyName.includes(prop))) {
494
- return null; // Skip these completely
495
- }
496
-
497
- // Only check for CLEARLY environment-dependent properties
498
- const trulyEnvironmentDependentProps = [
499
- 'baseurl', 'baseURL', 'host', 'hostname', 'server', 'endpoint',
500
- 'apikey', 'api_key', 'secret_key', 'client_secret',
501
- 'database', 'connectionstring', 'dbhost', 'dbport',
502
- 'port', 'timeout', // Only when they have suspicious values
503
- 'bucket', 'region', // Cloud-specific
504
- 'clientid', 'tenantid' // OAuth-specific
505
- ];
506
-
507
- if (!trulyEnvironmentDependentProps.some(prop => lowerPropertyName.includes(prop))) {
508
- return null; // Not clearly environment-dependent
509
- }
510
-
511
- let value = null;
512
- let configType = null;
513
-
514
- if (valueNode.getKind() === SyntaxKind.StringLiteral) {
515
- value = valueNode.getLiteralValue();
516
-
517
- // Only flag URLs or clearly sensitive values
518
- if (this.configPatterns.urls.regex.test(value) && this.isEnvironmentDependentUrl(value)) {
519
- configType = 'url';
520
- } else if (this.isRealCredential(value, propertyName)) {
521
- configType = 'credential';
522
- } else {
523
- return null; // Skip other string values
524
- }
525
- } else if (valueNode.getKind() === SyntaxKind.NumericLiteral) {
526
- value = valueNode.getLiteralValue();
527
- const parentContext = this.getParentContext(node);
528
-
529
- // Only flag numbers that are clearly environment-dependent
530
- if (this.configPatterns.environmentNumbers.isEnvironmentDependent(value, parentContext)) {
531
- configType = 'environment_config';
532
- } else {
533
- return null;
534
- }
535
- } else {
536
- return null; // Skip other value types
537
- }
538
-
539
- if (configType) {
1033
+ // Check for session/JWT configuration
1034
+ if (this.isSessionConfig(value, parentContext)) {
540
1035
  return {
541
- type: configType,
1036
+ type: "session_config",
542
1037
  value: value,
543
1038
  line: position.line,
544
1039
  column: position.column,
545
1040
  node: node,
546
- propertyName: propertyName
1041
+ suggestion: "Move session configuration to environment variables",
547
1042
  };
548
1043
  }
549
-
550
- return null;
551
- }
552
1044
 
553
- analyzeVariableDeclaration(node, sourceFile) {
1045
+ // Check for cache configuration
1046
+ if (this.isCacheConfig(value, parentContext)) {
1047
+ return {
1048
+ type: "cache_config",
1049
+ value: value,
1050
+ line: position.line,
1051
+ column: position.column,
1052
+ node: node,
1053
+ suggestion: "Move cache configuration to environment settings",
1054
+ };
1055
+ }
1056
+
1057
+ // Check for log levels
1058
+ if (this.isLogLevel(value, parentContext)) {
1059
+ return {
1060
+ type: "log_level",
1061
+ value: value,
1062
+ line: position.line,
1063
+ column: position.column,
1064
+ node: node,
1065
+ suggestion: "Move log level to environment configuration",
1066
+ };
1067
+ }
1068
+
1069
+ // Check for environment names
1070
+ if (this.isEnvironmentName(value, parentContext)) {
1071
+ return {
1072
+ type: "environment_name",
1073
+ value: value,
1074
+ line: position.line,
1075
+ column: position.column,
1076
+ node: node,
1077
+ suggestion: "Avoid hardcoding environment names, use environment detection",
1078
+ };
1079
+ }
1080
+
1081
+ // Check for service dependencies
1082
+ if (this.isServiceDependency(value, parentContext)) {
1083
+ return {
1084
+ type: "service_dependency",
1085
+ value: value,
1086
+ line: position.line,
1087
+ column: position.column,
1088
+ node: node,
1089
+ suggestion: "Move service URLs to service discovery or configuration",
1090
+ };
1091
+ }
1092
+
1093
+ // ============ Phase 2: Critical String Configurations ============
1094
+
1095
+ // Check for database schema/table names
1096
+ if (this.isSchemaName(value, parentContext)) {
1097
+ return {
1098
+ type: "schema_name",
1099
+ value: value,
1100
+ line: position.line,
1101
+ column: position.column,
1102
+ node: node,
1103
+ suggestion: "Move schema names to database configuration",
1104
+ };
1105
+ }
1106
+
1107
+ // Check for encryption algorithms
1108
+ if (this.isEncryptionConfig(value, parentContext)) {
1109
+ return {
1110
+ type: "encryption_config",
1111
+ value: value,
1112
+ line: position.line,
1113
+ column: position.column,
1114
+ node: node,
1115
+ suggestion: "Move encryption configuration to security settings",
1116
+ };
1117
+ }
1118
+
1119
+ // Check for OAuth scopes
1120
+ if (this.isOAuthConfig(value, parentContext)) {
1121
+ return {
1122
+ type: "oauth_config",
1123
+ value: value,
1124
+ line: position.line,
1125
+ column: position.column,
1126
+ node: node,
1127
+ suggestion: "Move OAuth configuration to authentication settings",
1128
+ };
1129
+ }
1130
+
1131
+ // Check for directories and paths
1132
+ if (this.isDirectory(value, parentContext)) {
1133
+ return {
1134
+ type: "directory",
1135
+ value: value,
1136
+ line: position.line,
1137
+ column: position.column,
1138
+ node: node,
1139
+ suggestion: "Move directory paths to environment configuration",
1140
+ };
1141
+ }
1142
+
1143
+ // Check for file extensions and MIME types
1144
+ if (this.isFileType(value, parentContext)) {
1145
+ return {
1146
+ type: "file_type",
1147
+ value: value,
1148
+ line: position.line,
1149
+ column: position.column,
1150
+ node: node,
1151
+ suggestion: "Move file type restrictions to configuration",
1152
+ };
1153
+ }
1154
+
1155
+ // Check for log file paths
1156
+ if (this.isLogPath(value, parentContext)) {
1157
+ return {
1158
+ type: "log_path",
1159
+ value: value,
1160
+ line: position.line,
1161
+ column: position.column,
1162
+ node: node,
1163
+ suggestion: "Move log paths to logging configuration",
1164
+ };
1165
+ }
1166
+
1167
+ // ============ Phase 3: Important String Configurations ============
1168
+
1169
+ // Check for HTTP methods and headers
1170
+ if (this.isHttpConfig(value, parentContext)) {
1171
+ return {
1172
+ type: "http_config",
1173
+ value: value,
1174
+ line: position.line,
1175
+ column: position.column,
1176
+ node: node,
1177
+ suggestion: "Move HTTP configuration to network settings",
1178
+ };
1179
+ }
1180
+
1181
+ // Check for queue/topic names
1182
+ if (this.isQueueConfig(value, parentContext)) {
1183
+ return {
1184
+ type: "queue_config",
1185
+ value: value,
1186
+ line: position.line,
1187
+ column: position.column,
1188
+ node: node,
1189
+ suggestion: "Move queue names to messaging configuration",
1190
+ };
1191
+ }
1192
+
1193
+ // Check for consumer group IDs
1194
+ if (this.isConsumerConfig(value, parentContext)) {
1195
+ return {
1196
+ type: "consumer_config",
1197
+ value: value,
1198
+ line: position.line,
1199
+ column: position.column,
1200
+ node: node,
1201
+ suggestion: "Move consumer configuration to messaging settings",
1202
+ };
1203
+ }
1204
+
1205
+ // ============ Phase 4: Enhancement String Configurations ============
1206
+
1207
+ // Check for resource limits (Kubernetes style)
1208
+ if (this.isResourceLimit(value, parentContext)) {
1209
+ return {
1210
+ type: "resource_limit",
1211
+ value: value,
1212
+ line: position.line,
1213
+ column: position.column,
1214
+ node: node,
1215
+ suggestion: "Move resource limits to deployment configuration",
1216
+ };
1217
+ }
1218
+
1219
+ // Check for deployment regions
1220
+ if (this.isRegionConfig(value, parentContext)) {
1221
+ return {
1222
+ type: "region_config",
1223
+ value: value,
1224
+ line: position.line,
1225
+ column: position.column,
1226
+ node: node,
1227
+ suggestion: "Move region configuration to deployment settings",
1228
+ };
1229
+ }
1230
+
1231
+ // Check for instance types
1232
+ if (this.isInstanceType(value, parentContext)) {
1233
+ return {
1234
+ type: "instance_type",
1235
+ value: value,
1236
+ line: position.line,
1237
+ column: position.column,
1238
+ node: node,
1239
+ suggestion: "Move instance types to infrastructure configuration",
1240
+ };
1241
+ }
1242
+
1243
+ // Check for webhook URLs
1244
+ if (this.isWebhookURL(value, parentContext)) {
1245
+ return {
1246
+ type: "webhook_url",
1247
+ value: value,
1248
+ line: position.line,
1249
+ column: position.column,
1250
+ node: node,
1251
+ suggestion: "Move webhook URLs to integration configuration",
1252
+ };
1253
+ }
1254
+
1255
+ // Check for external service names
1256
+ if (this.isExternalService(value, parentContext)) {
1257
+ return {
1258
+ type: "external_service",
1259
+ value: value,
1260
+ line: position.line,
1261
+ column: position.column,
1262
+ node: node,
1263
+ suggestion: "Move service provider names to integration configuration",
1264
+ };
1265
+ }
1266
+
1267
+ // Check for API versions
1268
+ if (this.isApiVersion(value, parentContext)) {
1269
+ return {
1270
+ type: "api_version",
1271
+ value: value,
1272
+ line: position.line,
1273
+ column: position.column,
1274
+ node: node,
1275
+ suggestion: "Move API versions to integration configuration",
1276
+ };
1277
+ }
1278
+
1279
+ // Check for channel IDs (Slack, etc.)
1280
+ if (this.isChannelId(value, parentContext)) {
1281
+ return {
1282
+ type: "channel_id",
1283
+ value: value,
1284
+ line: position.line,
1285
+ column: position.column,
1286
+ node: node,
1287
+ suggestion: "Move channel IDs to notification configuration",
1288
+ };
1289
+ }
1290
+
1291
+ // Check for timezones
1292
+ if (this.isTimezone(value, parentContext)) {
1293
+ return {
1294
+ type: "timezone",
1295
+ value: value,
1296
+ line: position.line,
1297
+ column: position.column,
1298
+ node: node,
1299
+ suggestion: "Move timezone to localization configuration",
1300
+ };
1301
+ }
1302
+
1303
+ // Check for date formats
1304
+ if (this.isDateFormat(value, parentContext)) {
1305
+ return {
1306
+ type: "date_format",
1307
+ value: value,
1308
+ line: position.line,
1309
+ column: position.column,
1310
+ node: node,
1311
+ suggestion: "Move date formats to localization configuration",
1312
+ };
1313
+ }
1314
+
1315
+ // Check for currency codes
1316
+ if (this.isCurrency(value, parentContext)) {
1317
+ return {
1318
+ type: "currency",
1319
+ value: value,
1320
+ line: position.line,
1321
+ column: position.column,
1322
+ node: node,
1323
+ suggestion: "Move currency codes to localization configuration",
1324
+ };
1325
+ }
1326
+
1327
+ // Check for locale identifiers
1328
+ if (this.isLocale(value, parentContext)) {
1329
+ return {
1330
+ type: "locale",
1331
+ value: value,
1332
+ line: position.line,
1333
+ column: position.column,
1334
+ node: node,
1335
+ suggestion: "Move locale to localization configuration",
1336
+ };
1337
+ }
1338
+
1339
+ // ============ Additional Critical Configurations ============
1340
+
1341
+ // Check for hardcoded environment variable names
1342
+ if (this.isHardcodedEnvVar(value, parentContext)) {
1343
+ return {
1344
+ type: "environment_var",
1345
+ value: value,
1346
+ line: position.line,
1347
+ column: position.column,
1348
+ node: node,
1349
+ suggestion: "Use environment-agnostic variable names or config service",
1350
+ };
1351
+ }
1352
+
1353
+ // Check for third-party service IDs/keys
1354
+ if (this.isThirdPartyServiceId(value, parentContext)) {
1355
+ return {
1356
+ type: "third_party_service",
1357
+ value: this.maskSensitiveValue(value),
1358
+ line: position.line,
1359
+ column: position.column,
1360
+ node: node,
1361
+ suggestion: "Move API keys/service IDs to secret management (e.g., .env, AWS Secrets Manager)",
1362
+ };
1363
+ }
1364
+
1365
+ // Check for IP addresses
1366
+ if (this.isIPAddress(value, parentContext)) {
1367
+ return {
1368
+ type: "ip_address",
1369
+ value: value,
1370
+ line: position.line,
1371
+ column: position.column,
1372
+ node: node,
1373
+ suggestion: "Move IP addresses to configuration or service discovery",
1374
+ };
1375
+ }
1376
+
1377
+ // Check for internal hostnames
1378
+ if (this.isInternalHostname(value, parentContext)) {
1379
+ return {
1380
+ type: "hostname",
1381
+ value: value,
1382
+ line: position.line,
1383
+ column: position.column,
1384
+ node: node,
1385
+ suggestion: "Move hostnames to configuration or use service discovery",
1386
+ };
1387
+ }
1388
+
1389
+ // Check for cron schedules
1390
+ if (this.isCronSchedule(value, parentContext)) {
1391
+ return {
1392
+ type: "cron_schedule",
1393
+ value: value,
1394
+ line: position.line,
1395
+ column: position.column,
1396
+ node: node,
1397
+ suggestion: "Move cron schedules to scheduler configuration",
1398
+ };
1399
+ }
1400
+
1401
+ // Check for message templates
1402
+ if (this.isMessageTemplate(value, parentContext)) {
1403
+ return {
1404
+ type: "message_template",
1405
+ value: value.substring(0, 50) + (value.length > 50 ? "..." : ""),
1406
+ line: position.line,
1407
+ column: position.column,
1408
+ node: node,
1409
+ suggestion: "Move email/SMS templates to template management system",
1410
+ };
1411
+ }
1412
+
1413
+ // Check for version numbers
1414
+ if (this.isVersionNumber(value, parentContext)) {
1415
+ return {
1416
+ type: "version",
1417
+ value: value,
1418
+ line: position.line,
1419
+ column: position.column,
1420
+ node: node,
1421
+ suggestion: "Move version numbers to build configuration or package.json",
1422
+ };
1423
+ }
1424
+
1425
+ return null;
1426
+ }
1427
+
1428
+ analyzeBooleanLiteral(node, sourceFile) {
1429
+ const value = node.getKind() === SyntaxKind.TrueKeyword;
1430
+ const position = sourceFile.getLineAndColumnAtPos(node.getStart());
1431
+ const parentContext = this.getParentContext(node);
1432
+
1433
+ // Skip React state initial values (NEW - reduces false positives)
1434
+ // Common patterns: useState(false), const [isOpen, setIsOpen] = useState(false)
1435
+ if (/useState|useReducer|createContext/.test(parentContext)) {
1436
+ return null;
1437
+ }
1438
+
1439
+ // Skip component props default values
1440
+ if (/defaultProps|default\s*:/i.test(parentContext)) {
1441
+ return null;
1442
+ }
1443
+
1444
+ // Skip UI state management (open, close, show, hide, loading, disabled, etc.)
1445
+ const uiStatePatterns = [
1446
+ /isOpen|isClose|isShow|isHide/i,
1447
+ /isVisible|isHidden/i,
1448
+ /isLoading|isDisabled|isEnabled|isActive/i,
1449
+ /isChecked|isSelected/i,
1450
+ /hasError|hasWarning/i,
1451
+ /showModal|closeModal|openDialog/i,
1452
+ ];
1453
+
1454
+ if (uiStatePatterns.some((pattern) => pattern.test(parentContext))) {
1455
+ return null;
1456
+ }
1457
+
1458
+ // Check if this boolean is a feature flag or toggle - Rule C067 requirement
1459
+ if (this.isFeatureFlag(parentContext)) {
1460
+ return {
1461
+ type: "feature_flag",
1462
+ value: value,
1463
+ line: position.line,
1464
+ column: position.column,
1465
+ node: node,
1466
+ context: parentContext,
1467
+ suggestion: "Move feature flags to configuration management system",
1468
+ };
1469
+ }
1470
+
1471
+ return null;
1472
+ }
1473
+
1474
+ analyzeNumericLiteral(node, sourceFile) {
1475
+ const value = node.getLiteralValue();
1476
+ const position = sourceFile.getLineAndColumnAtPos(node.getStart());
1477
+ const parentContext = this.getParentContext(node);
1478
+
1479
+ // Check for timeouts and retry intervals - Rule C067 requirement
1480
+ if (this.isTimeout(value, parentContext)) {
1481
+ return {
1482
+ type: "timeout",
1483
+ value: value,
1484
+ line: position.line,
1485
+ column: position.column,
1486
+ node: node,
1487
+ context: parentContext,
1488
+ suggestion: "Move timeout values to configuration files",
1489
+ };
1490
+ }
1491
+
1492
+ // Check for retry intervals - Rule C067 requirement
1493
+ if (this.isRetryInterval(value, parentContext)) {
1494
+ return {
1495
+ type: "retry_interval",
1496
+ value: value,
1497
+ line: position.line,
1498
+ column: position.column,
1499
+ node: node,
1500
+ context: parentContext,
1501
+ suggestion: "Move retry intervals to environment configuration",
1502
+ };
1503
+ }
1504
+
1505
+ // Check for batch sizes - Rule C067 requirement
1506
+ if (this.isBatchSize(value, parentContext)) {
1507
+ return {
1508
+ type: "batch_size",
1509
+ value: value,
1510
+ line: position.line,
1511
+ column: position.column,
1512
+ node: node,
1513
+ context: parentContext,
1514
+ suggestion: "Move batch sizes to configuration management",
1515
+ };
1516
+ }
1517
+
1518
+ // Check for thresholds that might vary by environment
1519
+ if (this.isThreshold(value, parentContext)) {
1520
+ return {
1521
+ type: "threshold",
1522
+ value: value,
1523
+ line: position.line,
1524
+ column: position.column,
1525
+ node: node,
1526
+ context: parentContext,
1527
+ suggestion: "Move thresholds to environment-specific configuration",
1528
+ };
1529
+ }
1530
+
1531
+ // Phase 1 extensions - Infrastructure numeric configs
1532
+
1533
+ // Check for session/JWT expiry times
1534
+ if (this.isSessionConfig(value, parentContext)) {
1535
+ return {
1536
+ type: "session_config",
1537
+ value: value,
1538
+ line: position.line,
1539
+ column: position.column,
1540
+ node: node,
1541
+ context: parentContext,
1542
+ suggestion: "Move session timeouts to environment configuration",
1543
+ };
1544
+ }
1545
+
1546
+ // Check for cache TTL values
1547
+ if (this.isCacheConfig(value, parentContext)) {
1548
+ return {
1549
+ type: "cache_config",
1550
+ value: value,
1551
+ line: position.line,
1552
+ column: position.column,
1553
+ node: node,
1554
+ context: parentContext,
1555
+ suggestion: "Move cache TTL to configuration management",
1556
+ };
1557
+ }
1558
+
1559
+ // Check for performance configuration
1560
+ if (this.isPerformanceConfig(value, parentContext)) {
1561
+ return {
1562
+ type: "performance_config",
1563
+ value: value,
1564
+ line: position.line,
1565
+ column: position.column,
1566
+ node: node,
1567
+ context: parentContext,
1568
+ suggestion: "Move performance settings to environment configuration",
1569
+ };
1570
+ }
1571
+
1572
+ // ============ Phase 2: Critical Numeric Configurations ============
1573
+
1574
+ // Check for database pool sizes
1575
+ if (this.isDatabasePoolConfig(value, parentContext)) {
1576
+ return {
1577
+ type: "database_pool",
1578
+ value: value,
1579
+ line: position.line,
1580
+ column: position.column,
1581
+ node: node,
1582
+ context: parentContext,
1583
+ suggestion: "Move database pool configuration to environment settings",
1584
+ };
1585
+ }
1586
+
1587
+ // Check for query timeouts
1588
+ if (this.isQueryConfig(value, parentContext)) {
1589
+ return {
1590
+ type: "query_config",
1591
+ value: value,
1592
+ line: position.line,
1593
+ column: position.column,
1594
+ node: node,
1595
+ context: parentContext,
1596
+ suggestion: "Move query timeout to database configuration",
1597
+ };
1598
+ }
1599
+
1600
+ // Check for token expiry times
1601
+ if (this.isTokenConfig(value, parentContext)) {
1602
+ return {
1603
+ type: "token_config",
1604
+ value: value,
1605
+ line: position.line,
1606
+ column: position.column,
1607
+ node: node,
1608
+ context: parentContext,
1609
+ suggestion: "Move token expiry to security configuration",
1610
+ };
1611
+ }
1612
+
1613
+ // Check for password policy values
1614
+ if (this.isPasswordPolicy(value, parentContext)) {
1615
+ return {
1616
+ type: "password_policy",
1617
+ value: value,
1618
+ line: position.line,
1619
+ column: position.column,
1620
+ node: node,
1621
+ context: parentContext,
1622
+ suggestion: "Move password policy to security configuration",
1623
+ };
1624
+ }
1625
+
1626
+ // Check for rate limits
1627
+ if (this.isRateLimiting(value, parentContext)) {
1628
+ return {
1629
+ type: "rate_limiting",
1630
+ value: value,
1631
+ line: position.line,
1632
+ column: position.column,
1633
+ node: node,
1634
+ context: parentContext,
1635
+ suggestion: "Move rate limits to security configuration",
1636
+ };
1637
+ }
1638
+
1639
+ // Check for file size limits
1640
+ if (this.isFileLimit(value, parentContext)) {
1641
+ return {
1642
+ type: "file_limit",
1643
+ value: value,
1644
+ line: position.line,
1645
+ column: position.column,
1646
+ node: node,
1647
+ context: parentContext,
1648
+ suggestion: "Move file size limits to upload configuration",
1649
+ };
1650
+ }
1651
+
1652
+ // ============ Phase 3: Important Numeric Configurations ============
1653
+
1654
+ // Check for network timeouts (distinct from general timeouts)
1655
+ if (this.isNetworkTimeout(value, parentContext)) {
1656
+ return {
1657
+ type: "network_timeout",
1658
+ value: value,
1659
+ line: position.line,
1660
+ column: position.column,
1661
+ node: node,
1662
+ context: parentContext,
1663
+ suggestion: "Move network timeouts to network configuration",
1664
+ };
1665
+ }
1666
+
1667
+ // Check for buffer sizes
1668
+ if (this.isBufferConfig(value, parentContext)) {
1669
+ return {
1670
+ type: "buffer_config",
1671
+ value: value,
1672
+ line: position.line,
1673
+ column: position.column,
1674
+ node: node,
1675
+ context: parentContext,
1676
+ suggestion: "Move buffer sizes to network configuration",
1677
+ };
1678
+ }
1679
+
1680
+ // Check for keep-alive intervals
1681
+ if (this.isKeepAliveConfig(value, parentContext)) {
1682
+ return {
1683
+ type: "keepalive_config",
1684
+ value: value,
1685
+ line: position.line,
1686
+ column: position.column,
1687
+ node: node,
1688
+ context: parentContext,
1689
+ suggestion: "Move keep-alive settings to network configuration",
1690
+ };
1691
+ }
1692
+
1693
+ // Check for pricing values
1694
+ if (this.isPricing(value, parentContext)) {
1695
+ return {
1696
+ type: "pricing",
1697
+ value: value,
1698
+ line: position.line,
1699
+ column: position.column,
1700
+ node: node,
1701
+ context: parentContext,
1702
+ suggestion: "Move pricing to business configuration",
1703
+ };
1704
+ }
1705
+
1706
+ // Check for quotas
1707
+ if (this.isQuota(value, parentContext)) {
1708
+ return {
1709
+ type: "quota",
1710
+ value: value,
1711
+ line: position.line,
1712
+ column: position.column,
1713
+ node: node,
1714
+ context: parentContext,
1715
+ suggestion: "Move quotas to business configuration",
1716
+ };
1717
+ }
1718
+
1719
+ // Check for discount rates
1720
+ if (this.isDiscount(value, parentContext)) {
1721
+ return {
1722
+ type: "discount",
1723
+ value: value,
1724
+ line: position.line,
1725
+ column: position.column,
1726
+ node: node,
1727
+ context: parentContext,
1728
+ suggestion: "Move discount rates to pricing configuration",
1729
+ };
1730
+ }
1731
+
1732
+ // Check for trial periods
1733
+ if (this.isTrial(value, parentContext)) {
1734
+ return {
1735
+ type: "trial",
1736
+ value: value,
1737
+ line: position.line,
1738
+ column: position.column,
1739
+ node: node,
1740
+ context: parentContext,
1741
+ suggestion: "Move trial periods to subscription configuration",
1742
+ };
1743
+ }
1744
+
1745
+ // Check for metrics intervals
1746
+ if (this.isMetricsConfig(value, parentContext)) {
1747
+ return {
1748
+ type: "metrics_config",
1749
+ value: value,
1750
+ line: position.line,
1751
+ column: position.column,
1752
+ node: node,
1753
+ context: parentContext,
1754
+ suggestion: "Move metrics intervals to monitoring configuration",
1755
+ };
1756
+ }
1757
+
1758
+ // Check for alert thresholds
1759
+ if (this.isAlertThreshold(value, parentContext)) {
1760
+ return {
1761
+ type: "alert_threshold",
1762
+ value: value,
1763
+ line: position.line,
1764
+ column: position.column,
1765
+ node: node,
1766
+ context: parentContext,
1767
+ suggestion: "Move alert thresholds to monitoring configuration",
1768
+ };
1769
+ }
1770
+
1771
+ // Check for sampling rates
1772
+ if (this.isSamplingRate(value, parentContext)) {
1773
+ return {
1774
+ type: "sampling_rate",
1775
+ value: value,
1776
+ line: position.line,
1777
+ column: position.column,
1778
+ node: node,
1779
+ context: parentContext,
1780
+ suggestion: "Move sampling rates to observability configuration",
1781
+ };
1782
+ }
1783
+
1784
+ // Check for health check intervals
1785
+ if (this.isHealthCheck(value, parentContext)) {
1786
+ return {
1787
+ type: "health_check",
1788
+ value: value,
1789
+ line: position.line,
1790
+ column: position.column,
1791
+ node: node,
1792
+ context: parentContext,
1793
+ suggestion: "Move health check intervals to monitoring configuration",
1794
+ };
1795
+ }
1796
+
1797
+ // ============ Phase 4: Enhancement Numeric Configurations ============
1798
+
1799
+ // Check for message TTL
1800
+ if (this.isMessageTTL(value, parentContext)) {
1801
+ return {
1802
+ type: "message_ttl",
1803
+ value: value,
1804
+ line: position.line,
1805
+ column: position.column,
1806
+ node: node,
1807
+ context: parentContext,
1808
+ suggestion: "Move message TTL to messaging configuration",
1809
+ };
1810
+ }
1811
+
1812
+ // Check for scaling configuration
1813
+ if (this.isScalingConfig(value, parentContext)) {
1814
+ return {
1815
+ type: "scaling_config",
1816
+ value: value,
1817
+ line: position.line,
1818
+ column: position.column,
1819
+ node: node,
1820
+ context: parentContext,
1821
+ suggestion: "Move scaling configuration to deployment settings",
1822
+ };
1823
+ }
1824
+
1825
+ // ============ Additional Critical Numeric Configurations ============
1826
+
1827
+ // Check for magic numbers in business logic
1828
+ if (this.isMagicNumber(value, parentContext)) {
1829
+ return {
1830
+ type: "magic_number",
1831
+ value: value,
1832
+ line: position.line,
1833
+ column: position.column,
1834
+ node: node,
1835
+ context: parentContext,
1836
+ suggestion: "Move business constants to configuration or constants file",
1837
+ };
1838
+ }
1839
+
1840
+ // Check for pagination defaults
1841
+ if (this.isPaginationDefault(value, parentContext)) {
1842
+ return {
1843
+ type: "pagination_default",
1844
+ value: value,
1845
+ line: position.line,
1846
+ column: position.column,
1847
+ node: node,
1848
+ context: parentContext,
1849
+ suggestion: "Move pagination defaults to API configuration",
1850
+ };
1851
+ }
1852
+
1853
+ return null;
1854
+ }
1855
+
1856
+ analyzeTemplateLiteral(node, sourceFile) {
1857
+ // For now, focus on simple template literals that might contain URLs
1858
+ const templateText = node.getFullText();
1859
+ if (templateText.includes("http://") || templateText.includes("https://")) {
1860
+ const position = sourceFile.getLineAndColumnAtPos(node.getStart());
1861
+
1862
+ // Check if it's using environment variables or config
1863
+ if (!templateText.includes("process.env") && !templateText.includes("config.")) {
1864
+ return {
1865
+ type: "template_url",
1866
+ value: templateText.trim(),
1867
+ line: position.line,
1868
+ column: position.column,
1869
+ node: node,
1870
+ };
1871
+ }
1872
+ }
1873
+
1874
+ return null;
1875
+ }
1876
+
1877
+ analyzePropertyAssignment(node, sourceFile) {
1878
+ const nameNode = node.getNameNode();
1879
+ const valueNode = node.getInitializer();
1880
+
1881
+ if (!nameNode || !valueNode) return null;
1882
+
1883
+ const propertyName = nameNode.getText();
1884
+ const position = sourceFile.getLineAndColumnAtPos(node.getStart());
1885
+
1886
+ // Skip ALL field mapping objects and ORM/database entity configurations
1887
+ const ancestorObj = node.getParent();
1888
+ if (ancestorObj && Node.isObjectLiteralExpression(ancestorObj)) {
1889
+ const objParent = ancestorObj.getParent();
1890
+ if (objParent && Node.isVariableDeclaration(objParent)) {
1891
+ const varName = objParent.getName();
1892
+ // Skip field mappings, database schemas, etc.
1893
+ if (/mapping|map|field|column|decode|schema|entity|constraint|table/i.test(varName)) {
1894
+ return null;
1895
+ }
1896
+ }
1897
+
1898
+ // Check if this looks like a table column definition or field mapping
1899
+ const objText = ancestorObj.getText();
1900
+ if (/primaryKeyConstraintName|foreignKeyConstraintName|key.*may contain/i.test(objText)) {
1901
+ return null; // Skip database constraint definitions
1902
+ }
1903
+ }
1904
+
1905
+ // Skip properties that are clearly field mappings or business data
1906
+ const businessLogicProperties = [
1907
+ // Field mappings
1908
+ "key",
1909
+ "field",
1910
+ "dataKey",
1911
+ "valueKey",
1912
+ "labelKey",
1913
+ "sortKey",
1914
+ // Business logic
1915
+ "endpoint",
1916
+ "path",
1917
+ "route",
1918
+ "method",
1919
+ "limit",
1920
+ "pageSize",
1921
+ "batchSize",
1922
+ "maxResults",
1923
+ "retry",
1924
+ "retries",
1925
+ "maxRetries",
1926
+ "attempts",
1927
+ "count",
1928
+ "max",
1929
+ "min",
1930
+ "size",
1931
+ "length",
1932
+ // UI properties
1933
+ "className",
1934
+ "style",
1935
+ "disabled",
1936
+ "readonly",
1937
+ // Database/ORM
1938
+ "primaryKeyConstraintName",
1939
+ "foreignKeyConstraintName",
1940
+ "constraintName",
1941
+ "tableName",
1942
+ "columnName",
1943
+ "schemaName",
1944
+ ];
1945
+
1946
+ const lowerPropertyName = propertyName.toLowerCase();
1947
+ if (businessLogicProperties.some((prop) => lowerPropertyName.includes(prop))) {
1948
+ return null; // Skip these completely
1949
+ }
1950
+
1951
+ // Only check for CLEARLY environment-dependent properties
1952
+ const trulyEnvironmentDependentProps = [
1953
+ "baseurl",
1954
+ "baseURL",
1955
+ "host",
1956
+ "hostname",
1957
+ "server",
1958
+ "endpoint",
1959
+ "apikey",
1960
+ "api_key",
1961
+ "secret_key",
1962
+ "client_secret",
1963
+ "database",
1964
+ "connectionstring",
1965
+ "dbhost",
1966
+ "dbport",
1967
+ "port",
1968
+ "timeout", // Only when they have suspicious values
1969
+ "bucket",
1970
+ "region", // Cloud-specific
1971
+ "clientid",
1972
+ "tenantid", // OAuth-specific
1973
+ ];
1974
+
1975
+ if (!trulyEnvironmentDependentProps.some((prop) => lowerPropertyName.includes(prop))) {
1976
+ return null; // Not clearly environment-dependent
1977
+ }
1978
+
1979
+ let value = null;
1980
+ let configType = null;
1981
+
1982
+ if (valueNode.getKind() === SyntaxKind.StringLiteral) {
1983
+ value = valueNode.getLiteralValue();
1984
+
1985
+ // Only flag URLs or clearly sensitive values
1986
+ if (this.configPatterns.urls.regex.test(value) && this.isEnvironmentDependentUrl(value)) {
1987
+ configType = "url";
1988
+ } else if (this.isRealCredential(value, propertyName)) {
1989
+ configType = "credential";
1990
+ } else {
1991
+ return null; // Skip other string values
1992
+ }
1993
+ } else if (valueNode.getKind() === SyntaxKind.NumericLiteral) {
1994
+ value = valueNode.getLiteralValue();
1995
+ const parentContext = this.getParentContext(node);
1996
+
1997
+ // Only flag numbers that are clearly environment-dependent
1998
+ if (this.configPatterns.environmentNumbers.isEnvironmentDependent(value, parentContext)) {
1999
+ configType = "environment_config";
2000
+ } else {
2001
+ return null;
2002
+ }
2003
+ } else {
2004
+ return null; // Skip other value types
2005
+ }
2006
+
2007
+ if (configType) {
2008
+ return {
2009
+ type: configType,
2010
+ value: value,
2011
+ line: position.line,
2012
+ column: position.column,
2013
+ node: node,
2014
+ propertyName: propertyName,
2015
+ };
2016
+ }
2017
+
2018
+ return null;
2019
+ }
2020
+
2021
+ analyzeVariableDeclaration(node, sourceFile) {
554
2022
  const nameNode = node.getNameNode();
555
2023
  const initializer = node.getInitializer();
556
-
2024
+
557
2025
  if (!nameNode || !initializer) return null;
558
-
2026
+
559
2027
  const variableName = nameNode.getText();
560
2028
  const position = sourceFile.getLineAndColumnAtPos(node.getStart());
561
-
2029
+
562
2030
  // Check if variable name suggests environment-dependent configuration
563
2031
  if (this.isEnvironmentDependentProperty(variableName)) {
564
2032
  let value = null;
565
-
2033
+
566
2034
  if (initializer.getKind() === SyntaxKind.StringLiteral) {
567
2035
  value = initializer.getLiteralValue();
568
2036
  } else if (initializer.getKind() === SyntaxKind.NumericLiteral) {
569
2037
  value = initializer.getLiteralValue();
570
2038
  }
571
-
572
- if (value !== null && this.looksLikeEnvironmentConfig(variableName, value)) {
573
- return {
574
- type: 'variable_config',
575
- value: value,
576
- line: position.line,
577
- column: position.column,
578
- node: node,
579
- variableName: variableName
580
- };
2039
+
2040
+ if (value !== null && this.looksLikeEnvironmentConfig(variableName, value)) {
2041
+ return {
2042
+ type: "variable_config",
2043
+ value: value,
2044
+ line: position.line,
2045
+ column: position.column,
2046
+ node: node,
2047
+ variableName: variableName,
2048
+ };
2049
+ }
2050
+ }
2051
+
2052
+ return null;
2053
+ }
2054
+
2055
+ getParentContext(node) {
2056
+ // Get surrounding context to understand the purpose of the literal
2057
+ let parent = node.getParent();
2058
+ let context = "";
2059
+
2060
+ // Check if this is a method call argument or property access
2061
+ while (parent && context.length < 100) {
2062
+ const parentText = parent.getText();
2063
+
2064
+ // If parent is CallExpression and this node is an argument, it might be a config key
2065
+ if (parent.getKind() === SyntaxKind.CallExpression) {
2066
+ const callExpr = parent;
2067
+ const methodName = this.getMethodName(callExpr);
2068
+ if (["get", "getBoolean", "getNumber", "getArray", "getString"].includes(methodName)) {
2069
+ return `config.${methodName}()`; // This indicates it's a config key
2070
+ }
2071
+ }
2072
+
2073
+ if (parentText.length < 200) {
2074
+ context = parentText;
2075
+ break;
2076
+ }
2077
+ parent = parent.getParent();
2078
+ }
2079
+
2080
+ return context;
2081
+ }
2082
+
2083
+ getMethodName(callExpression) {
2084
+ const expression = callExpression.getExpression();
2085
+ if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
2086
+ return expression.getName();
2087
+ }
2088
+ if (expression.getKind() === SyntaxKind.Identifier) {
2089
+ return expression.getText();
2090
+ }
2091
+ return "";
2092
+ }
2093
+
2094
+ isExcludedUrl(value, node) {
2095
+ return this.configPatterns.urls.exclude.some((pattern) => pattern.test(value));
2096
+ }
2097
+
2098
+ isExcludedCredential(value, node) {
2099
+ return this.configPatterns.credentials.exclude.some((pattern) => pattern.test(value));
2100
+ }
2101
+
2102
+ containsCredentialKeyword(context) {
2103
+ const lowerContext = context.toLowerCase();
2104
+
2105
+ // Skip if this looks like a header name or property key definition
2106
+ if (context.includes("':") || context.includes('": ') || context.includes(" = ")) {
2107
+ // This might be a key-value pair where the string is the key
2108
+ return false;
2109
+ }
2110
+
2111
+ return this.configPatterns.credentials.keywords.some((keyword) => lowerContext.includes(keyword));
2112
+ }
2113
+
2114
+ looksLikeUIValue(value, context) {
2115
+ // Check if it's likely a UI-related value (like input type, label, etc.)
2116
+ const uiKeywords = ["input", "type", "field", "label", "placeholder", "text", "button"];
2117
+ const lowerContext = context.toLowerCase();
2118
+ return uiKeywords.some((keyword) => lowerContext.includes(keyword));
2119
+ }
2120
+
2121
+ looksLikeConfigKey(value) {
2122
+ // Check if it looks like a config key path (e.g., 'api.baseUrl', 'features.newUI')
2123
+ if (/^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*$/.test(value)) {
2124
+ return true;
2125
+ }
2126
+
2127
+ // Check for other config key patterns
2128
+ const configKeyPatterns = [
2129
+ /^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z]/, // dotted notation like 'api.url'
2130
+ /^[A-Z_][A-Z0-9_]*$/, // CONSTANT_CASE like 'API_URL'
2131
+ /^get[A-Z]/, // getter methods like 'getApiUrl'
2132
+ /^config\./, // config namespace
2133
+ /^settings\./, // settings namespace
2134
+ /^env\./, // env namespace
2135
+ ];
2136
+
2137
+ return configKeyPatterns.some((pattern) => pattern.test(value));
2138
+ }
2139
+
2140
+ isPropertyKey(node) {
2141
+ // Check if this string literal is used as a property key in an object literal
2142
+ const parent = node.getParent();
2143
+
2144
+ // If parent is PropertyAssignment and this node is the name, it's a property key
2145
+ if (parent && parent.getKind() === SyntaxKind.PropertyAssignment) {
2146
+ const nameNode = parent.getNameNode();
2147
+ return nameNode === node;
2148
+ }
2149
+
2150
+ return false;
2151
+ }
2152
+
2153
+ isImportPath(value, node) {
2154
+ // Check if this is likely an import path or module name
2155
+ const parent = node.getParent();
2156
+
2157
+ // Check if it's in an import statement
2158
+ let currentNode = parent;
2159
+ while (currentNode) {
2160
+ const kind = currentNode.getKind();
2161
+ if (
2162
+ kind === SyntaxKind.ImportDeclaration ||
2163
+ kind === SyntaxKind.ExportDeclaration ||
2164
+ kind === SyntaxKind.CallExpression
2165
+ ) {
2166
+ const text = currentNode.getText();
2167
+ if (text.includes("require(") || text.includes("import ") || text.includes("from ")) {
2168
+ return true;
2169
+ }
2170
+ }
2171
+ currentNode = currentNode.getParent();
2172
+ }
2173
+
2174
+ // Check for common import path patterns
2175
+ return (
2176
+ /^[@a-z][a-z0-9\-_]*\/|^[a-z][a-z0-9\-_]*$|^\.{1,2}\//.test(value) ||
2177
+ value.endsWith(".js") ||
2178
+ value.endsWith(".ts") ||
2179
+ value.endsWith(".json") ||
2180
+ value.endsWith(".css") ||
2181
+ value.endsWith(".scss") ||
2182
+ value.endsWith(".html")
2183
+ );
2184
+ }
2185
+
2186
+ isUIString(value) {
2187
+ // Check against predefined UI string patterns, but don't skip credentials
2188
+ if (
2189
+ typeof value === "string" &&
2190
+ value.length > 20 &&
2191
+ (/token|key|secret|bearer|auth/i.test(value) || /^[a-f0-9-]{30,}$/i.test(value))
2192
+ ) {
2193
+ // Don't skip potential credentials/tokens even if they contain UI keywords
2194
+ return false;
2195
+ }
2196
+
2197
+ return this.UI_STRINGS.some((pattern) => {
2198
+ if (typeof pattern === "string") {
2199
+ return value === pattern; // Exact match only, not includes
2200
+ } else {
2201
+ return pattern.test(value);
2202
+ }
2203
+ });
2204
+ }
2205
+
2206
+ isJapaneseUIText(value, context) {
2207
+ if (typeof value !== "string") return false;
2208
+
2209
+ // Check for Japanese/CJK characters
2210
+ const hasJapanese = /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf]/.test(value);
2211
+ if (!hasJapanese) return false;
2212
+
2213
+ // Japanese particles that indicate UI text
2214
+ const japaneseParticles = /[はがをにでとへのもやけだ]/;
2215
+ if (japaneseParticles.test(value)) return true;
2216
+
2217
+ // Common Japanese UI terms
2218
+ const japaneseUITerms = [
2219
+ /ID$/, // xxxID
2220
+ /名$/, // xxx名 (name)
2221
+ /用/, // xxx用 (for xxx)
2222
+ /番号/, // 番号 (number)
2223
+ /情報/, // 情報 (information)
2224
+ /設定/, // 設定 (settings)
2225
+ /画面/, // 画面 (screen)
2226
+ /ボタン/, // ボタン (button)
2227
+ /入力/, // 入力 (input)
2228
+ /選択/, // 選択 (selection)
2229
+ /一覧/, // 一覧 (list)
2230
+ /詳細/, // 詳細 (details)
2231
+ /確認/, // 確認 (confirmation)
2232
+ /登録/, // 登録 (registration)
2233
+ /更新/, // 更新 (update)
2234
+ /削除/, // 削除 (delete)
2235
+ /検索/, // 検索 (search)
2236
+ /表示/, // 表示 (display)
2237
+ /メール/, // メール (email)
2238
+ /電話/, // 電話 (phone)
2239
+ /住所/, // 住所 (address)
2240
+ /連絡/, // 連絡 (contact)
2241
+ /会社/, // 会社 (company)
2242
+ /工場/, // 工場 (factory)
2243
+ /整備/, // 整備 (maintenance)
2244
+ ];
2245
+
2246
+ if (japaneseUITerms.some((pattern) => pattern.test(value))) return true;
2247
+
2248
+ // Check if context suggests it's a label or placeholder
2249
+ const lowerContext = context.toLowerCase();
2250
+ const labelPatterns = [
2251
+ /label|placeholder|title|heading|text/i,
2252
+ /register.*label|field.*label/i,
2253
+ /header.*text|tooltip/i,
2254
+ ];
2255
+
2256
+ if (hasJapanese && labelPatterns.some((pattern) => pattern.test(context))) {
2257
+ return true;
2258
+ }
2259
+
2260
+ return false;
2261
+ }
2262
+
2263
+ isFormFieldLabel(value, context) {
2264
+ if (typeof value !== "string") return false;
2265
+
2266
+ const lowerContext = context.toLowerCase();
2267
+
2268
+ // Check if it's in a form field context
2269
+ const formFieldPatterns = [
2270
+ /register.*label|label.*register/i,
2271
+ /field.*label|label.*field/i,
2272
+ /form.*label|label.*form/i,
2273
+ /input.*label|label.*input/i,
2274
+ /placeholder|tooltip|helper.*text/i,
2275
+ /error.*message|validation.*message/i,
2276
+ /\.label\b|label:/i,
2277
+ ];
2278
+
2279
+ if (formFieldPatterns.some((pattern) => pattern.test(context))) {
2280
+ return true;
2281
+ }
2282
+
2283
+ // Check for React Hook Form's register with label
2284
+ if (context.includes("register(") && context.includes("label")) {
2285
+ return true;
2286
+ }
2287
+
2288
+ return false;
2289
+ }
2290
+
2291
+ isTestData(value, context) {
2292
+ // Check if variable name suggests it's test/mock data
2293
+ const lowerContext = context.toLowerCase();
2294
+ const testVarPatterns = [
2295
+ /mock[a-z]/i, // mockApiKey, mockUser
2296
+ /test[a-z]/i, // testUser, testKey
2297
+ /dummy[a-z]/i, // dummyData
2298
+ /sample[a-z]/i, // sampleData
2299
+ /fake[a-z]/i, // fakeKey
2300
+ /example[a-z]/i, // exampleToken
2301
+ ];
2302
+
2303
+ if (testVarPatterns.some((pattern) => pattern.test(context))) {
2304
+ return true;
2305
+ }
2306
+
2307
+ // Don't skip credentials/tokens even in dummy files if they look real
2308
+ if (
2309
+ typeof value === "string" &&
2310
+ value.length > 20 &&
2311
+ (/^sk-live|^pk-live|^prod-|^production-/i.test(value) || // Clearly production keys
2312
+ /^[a-f0-9]{32,}$/i.test(value) || // Long hex strings
2313
+ /^[A-Za-z0-9+/]{40,}={0,2}$/i.test(value)) // Base64 tokens
2314
+ ) {
2315
+ return false; // Don't skip potential real credentials
2316
+ }
2317
+
2318
+ // Check for test patterns in value - but be more restrictive
2319
+ if (this.TEST_PATTERNS.some((pattern) => pattern.test(value))) {
2320
+ // Only skip if it's clearly test data, not production dummy data
2321
+ const isInTestFile =
2322
+ /\.(test|spec)\.(ts|tsx|js|jsx)$/i.test(context) ||
2323
+ /\/__tests__\//i.test(context) ||
2324
+ /\/test\//i.test(context);
2325
+ return isInTestFile;
2326
+ }
2327
+
2328
+ // Check for test context
2329
+ const testKeywords = ["test", "spec", "mock", "fixture", "stub", "describe", "it("];
2330
+ return testKeywords.some((keyword) => lowerContext.includes(keyword));
2331
+ }
2332
+
2333
+ isValidationMessage(value, context) {
2334
+ // Skip validation/error messages
2335
+ const validationPatterns = [
2336
+ /must contain|should contain|invalid|error|required|missing/i,
2337
+ /password|username|email/i, // Common validation contexts
2338
+ /^[A-Z][a-z\s]{10,}$/, // Sentence-like messages
2339
+ /\s(at least|one|letter|uppercase|lowercase|numeric)/i,
2340
+ ];
2341
+
2342
+ return (
2343
+ validationPatterns.some((pattern) => pattern.test(value)) ||
2344
+ /message|error|validation|description/i.test(context)
2345
+ );
2346
+ }
2347
+
2348
+ isFileNameOrDescription(value, context) {
2349
+ // Skip file names and descriptions
2350
+ const filePatterns = [
2351
+ /\.(csv|json|xml|txt|md)$/i,
2352
+ /^[a-z_\-]+\.(csv|json|xml|txt)$/i,
2353
+ /description|comment|note|foreign key|identity/i,
2354
+ ];
2355
+
2356
+ return (
2357
+ filePatterns.some((pattern) => pattern.test(value)) ||
2358
+ /description|comment|note|identity|foreign|table/i.test(context)
2359
+ );
2360
+ }
2361
+
2362
+ isEnvironmentDependentUrl(value) {
2363
+ // Only flag URLs that are likely to differ between environments
2364
+ const envDependentPatterns = [
2365
+ /\.amazonaws\.com/, // AWS services
2366
+ /\.azure\.com/, // Azure services
2367
+ /\.googleapis\.com/, // Google services
2368
+ /\.herokuapp\.com/, // Heroku apps
2369
+ /\.vercel\.app/, // Vercel deployments
2370
+ /\.netlify\.app/, // Netlify deployments
2371
+ /api\./, // API endpoints
2372
+ /auth\./, // Auth services
2373
+ /\.dev|\.staging|\.prod/i, // Environment-specific domains
2374
+ /\/api\/v\d+/, // Versioned API endpoints
2375
+ /https?:\/\/[^\/]+\.com\/api/, // External API endpoints
2376
+ ];
2377
+
2378
+ // Skip localhost and development URLs
2379
+ if (/localhost|127\.0\.0\.1|0\.0\.0\.0/.test(value)) {
2380
+ return false;
2381
+ }
2382
+
2383
+ return envDependentPatterns.some((pattern) => pattern.test(value));
2384
+ }
2385
+
2386
+ isRealCredential(value, context) {
2387
+ // Check for real credentials, not validation messages
2388
+ const credentialKeywords = this.configPatterns.credentials.keywords;
2389
+ const lowerContext = context.toLowerCase();
2390
+
2391
+ // Must have credential keyword in context
2392
+ if (!credentialKeywords.some((keyword) => lowerContext.includes(keyword))) {
2393
+ return false;
2394
+ }
2395
+
2396
+ // Skip property/field names (camelCase identifiers)
2397
+ // These are field names, not actual credential values
2398
+ if (/^[a-z][a-zA-Z0-9]*$/.test(value)) {
2399
+ // Common field name patterns that are not credentials
2400
+ const fieldNamePatterns = [
2401
+ /^(is|has|can|should)[A-Z]/, // isJoinFPL, hasAccess
2402
+ /Flg$/i, // loginFlg, deleteFlg
2403
+ /Id$/i, // userId, accountId
2404
+ /Key$/i, // sortKey, primaryKey
2405
+ /Name$/i, // userName, keyName
2406
+ /Code$/i, // errorCode, statusCode
2407
+ /Type$/i, // tokenType, keyType
2408
+ /Status$/i, // loginStatus
2409
+ /^[a-z]+[A-Z][a-z]+$/, // General camelCase
2410
+ ];
2411
+
2412
+ if (fieldNamePatterns.some((pattern) => pattern.test(value))) {
2413
+ return false;
2414
+ }
2415
+ }
2416
+
2417
+ // Skip URL paths (routes)
2418
+ if (/^\/[a-z-/]*$/.test(value)) {
2419
+ return false; // /login, /verify-login, etc.
2420
+ }
2421
+
2422
+ // Skip Japanese UI text and labels
2423
+ if (this.isJapaneseUIText(value, context)) {
2424
+ return false;
2425
+ }
2426
+
2427
+ // Skip form field labels
2428
+ if (this.isFormFieldLabel(value, context)) {
2429
+ return false;
2430
+ }
2431
+
2432
+ // Skip if it's excluded (validation messages, etc.)
2433
+ if (this.configPatterns.credentials.exclude.some((pattern) => pattern.test(value))) {
2434
+ return false;
2435
+ }
2436
+
2437
+ // Skip validation messages and descriptions
2438
+ if (this.isValidationMessage(value, context)) {
2439
+ return false;
2440
+ }
2441
+
2442
+ // Real credentials are usually:
2443
+ // - Random strings with mix of chars: abc123XYZ789
2444
+ // - Long alphanumeric: sk-1234567890abcdef
2445
+ // - Base64 encoded: YWJjZGVmZ2hpams=
2446
+ // - UUID format: 550e8400-e29b-41d4-a716-446655440000
2447
+ const looksLikeRealCredential =
2448
+ /^[a-zA-Z0-9+/]{32,}={0,2}$/.test(value) || // Base64
2449
+ /^[a-f0-9]{32,}$/.test(value) || // Hex
2450
+ /^[a-z]{2,4}-[a-zA-Z0-9]{20,}$/.test(value) || // Prefixed keys: sk-..., pk-...
2451
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); // UUID
2452
+
2453
+ // Must be reasonably long and look like an actual credential value
2454
+ return value.length >= 16 && looksLikeRealCredential && !this.looksLikeUIValue(value, context);
2455
+ }
2456
+
2457
+ isEnvironmentDependentProperty(propertyName) {
2458
+ // Skip UI/framework related property names
2459
+ const uiPropertyPatterns = [
2460
+ /^key[A-Z]/, // keyXxx (UI field keys)
2461
+ /^field[A-Z]/, // fieldXxx
2462
+ /^prop[A-Z]/, // propXxx
2463
+ /^data[A-Z]/, // dataXxx
2464
+ /CheckDisplay/, // UI display control keys
2465
+ /InputPossible/, // UI input control keys
2466
+ /Flag$/, // UI flags
2467
+ /Class$/, // CSS classes
2468
+ /^(disabled|readonly|active)Class$/i, // UI state classes
2469
+ ];
2470
+
2471
+ if (uiPropertyPatterns.some((pattern) => pattern.test(propertyName))) {
2472
+ return false;
2473
+ }
2474
+
2475
+ // Properties that are likely to differ between environments
2476
+ const envDependentProps = [
2477
+ "baseurl",
2478
+ "baseURL",
2479
+ "host",
2480
+ "hostname",
2481
+ "server",
2482
+ "apikey",
2483
+ "api_key",
2484
+ "secret",
2485
+ "token",
2486
+ "password",
2487
+ "credential",
2488
+ "database",
2489
+ "db",
2490
+ "connection",
2491
+ "connectionstring",
2492
+ "timeout", // Only long timeouts
2493
+ "port", // Only non-standard ports
2494
+ "authorization",
2495
+ "auth",
2496
+ "authentication", // Auth headers and codes
2497
+ "apptoken",
2498
+ "devicetoken",
2499
+ "accesstoken",
2500
+ "refreshtoken", // App tokens
2501
+ "code",
2502
+ "hash",
2503
+ "signature",
2504
+ "key", // Various security values
2505
+ "clientsecret",
2506
+ "clientid",
2507
+ "sessionkey", // OAuth and session
2508
+ "requestid",
2509
+ "sessionid",
2510
+ "transactionid",
2511
+ "otp", // Request/session tracking
2512
+ ];
2513
+
2514
+ const lowerName = propertyName.toLowerCase();
2515
+ return envDependentProps.some((prop) => lowerName.includes(prop));
2516
+ }
2517
+
2518
+ looksLikeEnvironmentConfig(propertyName, value) {
2519
+ // Check if this property/value combination looks like environment config
2520
+ const lowerPropertyName = propertyName.toLowerCase();
2521
+
2522
+ if (typeof value === "string") {
2523
+ // Skip test data (common test passwords, etc.)
2524
+ const testDataPatterns = [
2525
+ /^(password123|test123|admin123|user123|wrongpassword|testpassword)$/i,
2526
+ /^(test|mock|dummy|sample|example)/i,
2527
+ /^\/(api|mock|test)/, // Test API paths
2528
+ /^[a-z]+\d+$/i, // Simple test values like 'user1', 'test2'
2529
+ ];
2530
+
2531
+ // Check if variable name suggests it's test/mock data
2532
+ const isMockVariable = /^(mock|test|dummy|sample|fake|example)[A-Z]/i.test(propertyName);
2533
+ if (isMockVariable) {
2534
+ return false; // Skip mock variables even if they contain credentials
2535
+ }
2536
+
2537
+ // Don't skip common test patterns if they appear in credential contexts
2538
+ const isCredentialContext = /token|key|secret|auth|otp|code|password|credential/i.test(propertyName);
2539
+
2540
+ if (!isCredentialContext && testDataPatterns.some((pattern) => pattern.test(value))) {
2541
+ return false;
2542
+ }
2543
+
2544
+ // Skip object property paths and field names
2545
+ const propertyPathPatterns = [
2546
+ /^[a-zA-Z][a-zA-Z0-9]*(\[[0-9]+\])?\.[a-zA-Z][a-zA-Z0-9]*$/, // obj[0].prop, obj.prop
2547
+ /^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*$/, // obj.prop.subprop
2548
+ /^[a-zA-Z][a-zA-Z0-9]*(\[[0-9]+\])+$/, // obj[0], obj[0][1]
2549
+ /^(key|field|prop|data)[A-Z]/, // keyXxx, fieldXxx, propXxx, dataXxx
2550
+ /CheckDisplay|InputPossible|Flag$/i, // Common UI field patterns
2551
+ /^exflg|^flg|Support$/i, // Business logic flags
2552
+ ];
2553
+
2554
+ if (propertyPathPatterns.some((pattern) => pattern.test(value))) {
2555
+ return false;
2556
+ }
2557
+
2558
+ // Skip CSS classes and UI constants
2559
+ const uiPatterns = [
2560
+ /^bg-|text-|cursor-|border-|flex-|grid-/, // CSS classes
2561
+ /^(disabled|readonly|active|inactive)$/i, // UI states
2562
+ /class$/i, // className values
2563
+ ];
2564
+
2565
+ if (uiPatterns.some((pattern) => pattern.test(value))) {
2566
+ return false;
2567
+ }
2568
+
2569
+ // Skip internal system identifiers (queue names, service names, route names)
2570
+ const systemIdentifierPatterns = [
2571
+ /-queue$/i, // Queue names
2572
+ /-task$/i, // Task names
2573
+ /-activity$/i, // Activity names
2574
+ /-service$/i, // Service names
2575
+ /-worker$/i, // Worker names
2576
+ /^[A-Z_]+_QUEUE$/, // CONSTANT_QUEUE names
2577
+ /^[A-Z_]+_TASK$/, // CONSTANT_TASK names
2578
+ /^(register|login|logout|reset-password|verify|update)$/i, // Route names
2579
+ /password|token/i && /invalid|expired|attempts|exceeded/i, // Error messages
2580
+ ];
2581
+
2582
+ if (systemIdentifierPatterns.some((pattern) => pattern.test(value))) {
2583
+ return false;
2584
+ }
2585
+
2586
+ // Skip error messages and validation messages
2587
+ const messagePatterns = [
2588
+ /invalid|expired|exceeded|failed|error|success/i,
2589
+ /attempts|required|missing|not found/i,
2590
+ /^[A-Z][a-z\s]{10,}$/, // Sentence-like messages
2591
+ /は|が|を|に|で|と/, // Japanese particles (UI text)
2592
+ /情報|画面|ボタン|入力/, // Japanese UI terms
2593
+ ];
2594
+
2595
+ if (messagePatterns.some((pattern) => pattern.test(value))) {
2596
+ return false;
2597
+ }
2598
+
2599
+ // URLs are environment-dependent
2600
+ if (this.configPatterns.urls.regex.test(value)) {
2601
+ return this.isEnvironmentDependentUrl(value);
2602
+ }
2603
+
2604
+ // Credentials - but exclude test data
2605
+ if (
2606
+ lowerPropertyName.includes("key") ||
2607
+ lowerPropertyName.includes("secret") ||
2608
+ lowerPropertyName.includes("token") ||
2609
+ lowerPropertyName.includes("password")
2610
+ ) {
2611
+ return value.length > 10; // Real secrets are usually longer
2612
+ }
2613
+
2614
+ // Skip short endpoint names or simple strings
2615
+ if (value.length < 10 && !value.includes(".") && !value.includes("/")) {
2616
+ return false;
581
2617
  }
582
2618
  }
583
-
584
- return null;
2619
+
2620
+ if (typeof value === "number") {
2621
+ // Only flag environment-dependent numbers
2622
+ return this.configPatterns.environmentNumbers.isEnvironmentDependent(value, propertyName);
2623
+ }
2624
+
2625
+ return true;
2626
+ }
2627
+
2628
+ isCommonConstant(value) {
2629
+ // Common constants that are usually OK to hardcode
2630
+ const commonConstants = [100, 200, 300, 400, 500, 1000, 2000, 3000, 5000, 8080, 3000];
2631
+ return commonConstants.includes(value);
2632
+ }
2633
+
2634
+ isConfigProperty(propertyName) {
2635
+ const configProps = [
2636
+ "url",
2637
+ "endpoint",
2638
+ "baseurl",
2639
+ "apiurl",
2640
+ "host",
2641
+ "port",
2642
+ "timeout",
2643
+ "interval",
2644
+ "delay",
2645
+ "retry",
2646
+ "retries",
2647
+ "username",
2648
+ "password",
2649
+ "apikey",
2650
+ "secret",
2651
+ "token",
2652
+ "database",
2653
+ "connection",
2654
+ "connectionstring",
2655
+ "maxsize",
2656
+ "batchsize",
2657
+ "pagesize",
2658
+ "limit",
2659
+ ];
2660
+
2661
+ const lowerName = propertyName.toLowerCase();
2662
+ return configProps.some((prop) => lowerName.includes(prop));
2663
+ }
2664
+
2665
+ isConfigVariable(variableName) {
2666
+ const configVars = [
2667
+ "api",
2668
+ "url",
2669
+ "endpoint",
2670
+ "host",
2671
+ "port",
2672
+ "timeout",
2673
+ "interval",
2674
+ "delay",
2675
+ "retry",
2676
+ "config",
2677
+ "setting",
2678
+ "constant",
2679
+ ];
2680
+
2681
+ const lowerName = variableName.toLowerCase();
2682
+ return configVars.some((var_) => lowerName.includes(var_));
2683
+ }
2684
+
2685
+ looksLikeHardcodedConfig(name, value) {
2686
+ // Skip obvious constants and UI values
2687
+ if (typeof value === "string") {
2688
+ if (value.length < 3) return false;
2689
+ if (/^(ok|yes|no|true|false|success|error|info|warn)$/i.test(value)) return false;
2690
+ }
2691
+
2692
+ if (typeof value === "number") {
2693
+ if (this.isCommonConstant(value)) return false;
2694
+ }
2695
+
2696
+ return true;
2697
+ }
2698
+
2699
+ // Helper methods for specific configuration types
2700
+ isTimeout(value, context) {
2701
+ const lowerContext = context.toLowerCase();
2702
+ return typeof value === "number" && value > 1000 && /timeout|delay|wait|duration/i.test(context);
2703
+ }
2704
+
2705
+ isRetryInterval(value, context) {
2706
+ const lowerContext = context.toLowerCase();
2707
+ return typeof value === "number" && value > 100 && /retry|interval|backoff|attempt/i.test(context);
2708
+ }
2709
+
2710
+ // Phase 1 extensions - Security & Infrastructure
2711
+ isCorsOrigin(value, context) {
2712
+ if (typeof value !== "string") return false;
2713
+ const lowerContext = context.toLowerCase();
2714
+
2715
+ // Check if it's a URL pattern that matches CORS origin
2716
+ const matchesPattern = this.configPatterns.security.corsOrigins.patterns.some((pattern) =>
2717
+ pattern.test(value)
2718
+ );
2719
+
2720
+ if (!matchesPattern) return false;
2721
+
2722
+ // If context contains CORS-related keywords, it's definitely a CORS origin
2723
+ if (this.configPatterns.security.corsOrigins.keywords.some((keyword) => lowerContext.includes(keyword))) {
2724
+ return true;
2725
+ }
2726
+
2727
+ // Also check variable name patterns for CORS/allowed origins
2728
+ if (/cors|allowed.*origin|origin.*allowed/i.test(context)) {
2729
+ return true;
2730
+ }
2731
+
2732
+ return false;
2733
+ }
2734
+
2735
+ isSessionConfig(value, context) {
2736
+ const lowerContext = context.toLowerCase();
2737
+
2738
+ // Check keywords
2739
+ if (
2740
+ !this.configPatterns.security.sessionConfig.keywords.some((keyword) => lowerContext.includes(keyword))
2741
+ ) {
2742
+ return false;
2743
+ }
2744
+
2745
+ // Check for time patterns
2746
+ if (typeof value === "string") {
2747
+ return this.configPatterns.security.sessionConfig.timePatterns.some((pattern) => pattern.test(value));
2748
+ }
2749
+
2750
+ // Check for numeric values (seconds)
2751
+ if (typeof value === "number" && value > 300) {
2752
+ // > 5 minutes
2753
+ return /session|jwt|token|expiry|expire/i.test(context);
2754
+ }
2755
+
2756
+ return false;
2757
+ }
2758
+
2759
+ isCacheConfig(value, context) {
2760
+ const lowerContext = context.toLowerCase();
2761
+
2762
+ if (
2763
+ !this.configPatterns.infrastructure.caching.keywords.some((keyword) => lowerContext.includes(keyword))
2764
+ ) {
2765
+ return false;
2766
+ }
2767
+
2768
+ if (typeof value === "string") {
2769
+ // Check for Redis prefixes
2770
+ return this.configPatterns.infrastructure.caching.patterns.some((pattern) => pattern.test(value));
2771
+ }
2772
+
2773
+ if (typeof value === "number" && value > 60) {
2774
+ // TTL > 1 minute
2775
+ return /cache|ttl|expire/i.test(context);
2776
+ }
2777
+
2778
+ return false;
2779
+ }
2780
+
2781
+ isLogLevel(value, context) {
2782
+ if (typeof value !== "string") return false;
2783
+ const lowerContext = context.toLowerCase();
2784
+
2785
+ // Check if value is a valid log level
2786
+ const isValidLevel = this.configPatterns.infrastructure.logging.levels.includes(value.toLowerCase());
2787
+ if (!isValidLevel) return false;
2788
+
2789
+ // Check if context suggests it's a log level configuration
2790
+ return /log.*level|level.*log|^log_level$|^loglevel$/i.test(context);
2791
+ }
2792
+
2793
+ isPerformanceConfig(value, context) {
2794
+ const lowerContext = context.toLowerCase();
2795
+
2796
+ if (typeof value !== "number" || value <= 1) {
2797
+ return false;
2798
+ }
2799
+
2800
+ // Check for performance-related keywords in context
2801
+ const hasKeyword = this.configPatterns.infrastructure.performance.keywords.some((keyword) =>
2802
+ lowerContext.includes(keyword)
2803
+ );
2804
+
2805
+ if (!hasKeyword) return false;
2806
+
2807
+ // Check for specific performance patterns
2808
+ return this.configPatterns.infrastructure.performance.contextPatterns.some((pattern) =>
2809
+ pattern.test(context)
2810
+ );
2811
+ }
2812
+
2813
+ isEnvironmentName(value, context) {
2814
+ if (typeof value !== "string") return false;
2815
+ const lowerContext = context.toLowerCase();
2816
+
2817
+ // Check if value is a known environment name
2818
+ const isEnvName =
2819
+ this.configPatterns.environments.names.includes(value.toLowerCase()) ||
2820
+ this.configPatterns.environments.patterns.some((pattern) => pattern.test(value));
2821
+
2822
+ if (!isEnvName) return false;
2823
+
2824
+ // Check if context suggests it's environment configuration
2825
+ return /environment|env|node_env|current.*env/i.test(context);
2826
+ }
2827
+
2828
+ isServiceDependency(value, context) {
2829
+ if (typeof value !== "string") return false;
2830
+ const lowerContext = context.toLowerCase();
2831
+
2832
+ return (
2833
+ this.configPatterns.services.keywords.some((keyword) => lowerContext.includes(keyword)) &&
2834
+ this.configPatterns.services.patterns.some((pattern) => pattern.test(value))
2835
+ );
2836
+ }
2837
+
2838
+ isBatchSize(value, context) {
2839
+ const lowerContext = context.toLowerCase();
2840
+
2841
+ // Skip UI/display related sizes
2842
+ if (/ui|display|view|page.*size|page.*limit|default.*page/i.test(context)) {
2843
+ return false;
2844
+ }
2845
+
2846
+ // Skip small values that are likely UI constants (pagination, etc.)
2847
+ if (typeof value === "number" && value <= 50 && /page|size|limit/i.test(context)) {
2848
+ return false;
2849
+ }
2850
+
2851
+ // Only flag larger batch sizes for processing
2852
+ return (
2853
+ typeof value === "number" &&
2854
+ value > 100 &&
2855
+ /batch|chunk|buffer|pool|queue|processing/i.test(context) &&
2856
+ !/ui|display|view|page/i.test(context)
2857
+ );
2858
+ }
2859
+
2860
+ isThreshold(value, context) {
2861
+ const lowerContext = context.toLowerCase();
2862
+ return (
2863
+ typeof value === "number" &&
2864
+ value > 0 &&
2865
+ this.configPatterns.thresholds.contextPatterns.some((pattern) => pattern.test(context))
2866
+ );
2867
+ }
2868
+
2869
+ isFeatureFlag(context) {
2870
+ const lowerContext = context.toLowerCase();
2871
+ return (
2872
+ this.configPatterns.featureFlags.keywords.some((keyword) => lowerContext.includes(keyword)) ||
2873
+ this.configPatterns.featureFlags.patterns.some((pattern) => pattern.test(context))
2874
+ );
2875
+ }
2876
+
2877
+ // ============ Phase 2: Critical Configuration Helpers ============
2878
+
2879
+ // Database & Storage Configuration helpers
2880
+ isDatabasePoolConfig(value, context) {
2881
+ const lowerContext = context.toLowerCase();
2882
+ if (typeof value !== "number" || value <= 0) return false;
2883
+
2884
+ return this.configPatterns.database.poolConfig.patterns.some((pattern) => pattern.test(context));
2885
+ }
2886
+
2887
+ isQueryConfig(value, context) {
2888
+ const lowerContext = context.toLowerCase();
2889
+
2890
+ if (typeof value === "number" && value > 1000) {
2891
+ return this.configPatterns.database.queryConfig.patterns.some((pattern) => pattern.test(context));
2892
+ }
2893
+
2894
+ if (typeof value === "string") {
2895
+ return this.configPatterns.database.queryConfig.patterns.some((pattern) => pattern.test(context));
2896
+ }
2897
+
2898
+ return false;
2899
+ }
2900
+
2901
+ isSchemaName(value, context) {
2902
+ if (typeof value !== "string") return false;
2903
+ const lowerContext = context.toLowerCase();
2904
+
2905
+ // Check if it matches schema name patterns
2906
+ const matchesPattern = this.configPatterns.database.schemaNames.patterns.some((pattern) =>
2907
+ pattern.test(value)
2908
+ );
2909
+ if (!matchesPattern) return false;
2910
+
2911
+ // Check if context suggests it's a schema/table name
2912
+ return this.configPatterns.database.schemaNames.keywords.some((keyword) =>
2913
+ lowerContext.includes(keyword)
2914
+ );
2915
+ }
2916
+
2917
+ // Security & Authentication (Extended) helpers
2918
+ isTokenConfig(value, context) {
2919
+ const lowerContext = context.toLowerCase();
2920
+
2921
+ if (
2922
+ !this.configPatterns.securityExtended.tokenConfig.keywords.some((keyword) =>
2923
+ lowerContext.includes(keyword)
2924
+ )
2925
+ ) {
2926
+ return false;
2927
+ }
2928
+
2929
+ if (typeof value === "string") {
2930
+ return this.configPatterns.securityExtended.tokenConfig.patterns.some((pattern) => pattern.test(value));
2931
+ }
2932
+
2933
+ if (typeof value === "number" && value > 300) {
2934
+ return /expir|ttl|token|jwt/i.test(context);
2935
+ }
2936
+
2937
+ return false;
585
2938
  }
586
2939
 
587
- getParentContext(node) {
588
- // Get surrounding context to understand the purpose of the literal
589
- let parent = node.getParent();
590
- let context = '';
591
-
592
- // Check if this is a method call argument or property access
593
- while (parent && context.length < 100) {
594
- const parentText = parent.getText();
595
-
596
- // If parent is CallExpression and this node is an argument, it might be a config key
597
- if (parent.getKind() === SyntaxKind.CallExpression) {
598
- const callExpr = parent;
599
- const methodName = this.getMethodName(callExpr);
600
- if (['get', 'getBoolean', 'getNumber', 'getArray', 'getString'].includes(methodName)) {
601
- return `config.${methodName}()`; // This indicates it's a config key
602
- }
603
- }
604
-
605
- if (parentText.length < 200) {
606
- context = parentText;
607
- break;
2940
+ isPasswordPolicy(value, context) {
2941
+ const lowerContext = context.toLowerCase();
2942
+
2943
+ if (typeof value === "number" && value > 0 && value < 128) {
2944
+ return this.configPatterns.securityExtended.passwordPolicy.patterns.some((pattern) =>
2945
+ pattern.test(context)
2946
+ );
2947
+ }
2948
+
2949
+ if (typeof value === "string") {
2950
+ return this.configPatterns.securityExtended.passwordPolicy.patterns.some((pattern) =>
2951
+ pattern.test(value)
2952
+ );
2953
+ }
2954
+
2955
+ return false;
2956
+ }
2957
+
2958
+ isRateLimiting(value, context) {
2959
+ const lowerContext = context.toLowerCase();
2960
+
2961
+ if (typeof value !== "number" || value <= 0) return false;
2962
+
2963
+ return this.configPatterns.securityExtended.rateLimiting.patterns.some((pattern) =>
2964
+ pattern.test(context)
2965
+ );
2966
+ }
2967
+
2968
+ isEncryptionConfig(value, context) {
2969
+ if (typeof value !== "string") return false;
2970
+ const lowerContext = context.toLowerCase();
2971
+
2972
+ const matchesPattern = this.configPatterns.securityExtended.encryptionConfig.patterns.some((pattern) =>
2973
+ pattern.test(value)
2974
+ );
2975
+ if (!matchesPattern) return false;
2976
+
2977
+ return this.configPatterns.securityExtended.encryptionConfig.keywords.some((keyword) =>
2978
+ lowerContext.includes(keyword)
2979
+ );
2980
+ }
2981
+
2982
+ isOAuthConfig(value, context) {
2983
+ if (typeof value !== "string") return false;
2984
+ const lowerContext = context.toLowerCase();
2985
+
2986
+ const matchesPattern = this.configPatterns.securityExtended.oauthConfig.patterns.some((pattern) =>
2987
+ pattern.test(value)
2988
+ );
2989
+ if (!matchesPattern) return false;
2990
+
2991
+ return this.configPatterns.securityExtended.oauthConfig.keywords.some((keyword) =>
2992
+ lowerContext.includes(keyword)
2993
+ );
2994
+ }
2995
+
2996
+ // File System & Paths helpers
2997
+ isDirectory(value, context) {
2998
+ if (typeof value !== "string") return false;
2999
+ const lowerContext = context.toLowerCase();
3000
+
3001
+ const matchesPattern = this.configPatterns.fileSystem.directories.patterns.some((pattern) =>
3002
+ pattern.test(value)
3003
+ );
3004
+ if (!matchesPattern) return false;
3005
+
3006
+ return this.configPatterns.fileSystem.directories.keywords.some((keyword) =>
3007
+ lowerContext.includes(keyword)
3008
+ );
3009
+ }
3010
+
3011
+ isFileLimit(value, context) {
3012
+ const lowerContext = context.toLowerCase();
3013
+
3014
+ if (typeof value !== "number" || value <= 1000) return false;
3015
+
3016
+ return this.configPatterns.fileSystem.fileLimits.patterns.some((pattern) => pattern.test(context));
3017
+ }
3018
+
3019
+ isFileType(value, context) {
3020
+ if (typeof value !== "string") return false;
3021
+ const lowerContext = context.toLowerCase();
3022
+
3023
+ const matchesPattern = this.configPatterns.fileSystem.fileTypes.patterns.some((pattern) =>
3024
+ pattern.test(value)
3025
+ );
3026
+ if (!matchesPattern) return false;
3027
+
3028
+ return this.configPatterns.fileSystem.fileTypes.keywords.some((keyword) =>
3029
+ lowerContext.includes(keyword)
3030
+ );
3031
+ }
3032
+
3033
+ isLogPath(value, context) {
3034
+ if (typeof value !== "string") return false;
3035
+ const lowerContext = context.toLowerCase();
3036
+
3037
+ return this.configPatterns.fileSystem.logPaths.patterns.some((pattern) => pattern.test(value));
3038
+ }
3039
+
3040
+ // ============ Phase 3: Important Configuration Helpers ============
3041
+
3042
+ // Network & Protocol Configuration helpers
3043
+ isHttpConfig(value, context) {
3044
+ if (typeof value !== "string") return false;
3045
+ const lowerContext = context.toLowerCase();
3046
+
3047
+ // Check if it's an HTTP method
3048
+ if (this.configPatterns.network.httpConfig.httpMethods.includes(value)) {
3049
+ return /method|allowed|http/i.test(context);
3050
+ }
3051
+
3052
+ // Check other HTTP patterns
3053
+ return this.configPatterns.network.httpConfig.patterns.some((pattern) => pattern.test(value));
3054
+ }
3055
+
3056
+ isNetworkTimeout(value, context) {
3057
+ const lowerContext = context.toLowerCase();
3058
+
3059
+ if (typeof value !== "number" || value <= 100) return false;
3060
+
3061
+ return this.configPatterns.network.timeouts.patterns.some((pattern) => pattern.test(context));
3062
+ }
3063
+
3064
+ isBufferConfig(value, context) {
3065
+ const lowerContext = context.toLowerCase();
3066
+
3067
+ if (typeof value !== "number" || value <= 1024) return false;
3068
+
3069
+ return this.configPatterns.network.bufferConfig.patterns.some((pattern) => pattern.test(context));
3070
+ }
3071
+
3072
+ isKeepAliveConfig(value, context) {
3073
+ const lowerContext = context.toLowerCase();
3074
+
3075
+ if (typeof value === "boolean") {
3076
+ return /keepalive|keep.*alive/i.test(context);
3077
+ }
3078
+
3079
+ if (typeof value === "number" && value > 0) {
3080
+ return this.configPatterns.network.keepAlive.patterns.some((pattern) => pattern.test(context));
3081
+ }
3082
+
3083
+ return false;
3084
+ }
3085
+
3086
+ // Business Rules & Limits helpers
3087
+ isPricing(value, context) {
3088
+ const lowerContext = context.toLowerCase();
3089
+
3090
+ if (typeof value === "number" && value > 0) {
3091
+ // Check for price pattern (e.g., 49.99)
3092
+ if (this.configPatterns.business.pricing.patterns.some((pattern) => pattern.test(value.toString()))) {
3093
+ return this.configPatterns.business.pricing.keywords.some((keyword) =>
3094
+ lowerContext.includes(keyword)
3095
+ );
608
3096
  }
609
- parent = parent.getParent();
610
3097
  }
611
-
612
- return context;
3098
+
3099
+ return false;
3100
+ }
3101
+
3102
+ isQuota(value, context) {
3103
+ const lowerContext = context.toLowerCase();
3104
+
3105
+ if (typeof value !== "number" || value <= 0) return false;
3106
+
3107
+ return this.configPatterns.business.quotas.patterns.some((pattern) => pattern.test(context));
3108
+ }
3109
+
3110
+ isDiscount(value, context) {
3111
+ const lowerContext = context.toLowerCase();
3112
+
3113
+ if (typeof value !== "number") return false;
3114
+
3115
+ // Check for decimal rate patterns
3116
+ if (this.configPatterns.business.discounts.patterns.some((pattern) => pattern.test(value.toString()))) {
3117
+ return this.configPatterns.business.discounts.keywords.some((keyword) =>
3118
+ lowerContext.includes(keyword)
3119
+ );
3120
+ }
3121
+
3122
+ return false;
3123
+ }
3124
+
3125
+ isTrial(value, context) {
3126
+ const lowerContext = context.toLowerCase();
3127
+
3128
+ if (typeof value !== "number" || value <= 0) return false;
3129
+
3130
+ return this.configPatterns.business.trials.patterns.some((pattern) => pattern.test(context));
3131
+ }
3132
+
3133
+ // Monitoring & Observability helpers
3134
+ isMetricsConfig(value, context) {
3135
+ const lowerContext = context.toLowerCase();
3136
+
3137
+ if (typeof value !== "number" || value <= 0) return false;
3138
+
3139
+ return this.configPatterns.monitoring.metricsConfig.patterns.some((pattern) => pattern.test(context));
3140
+ }
3141
+
3142
+ isAlertThreshold(value, context) {
3143
+ const lowerContext = context.toLowerCase();
3144
+
3145
+ if (typeof value !== "number") return false;
3146
+
3147
+ return this.configPatterns.monitoring.alertThresholds.patterns.some((pattern) => pattern.test(context));
3148
+ }
3149
+
3150
+ isSamplingRate(value, context) {
3151
+ const lowerContext = context.toLowerCase();
3152
+
3153
+ if (typeof value !== "number" || value < 0 || value > 1) return false;
3154
+
3155
+ return this.configPatterns.monitoring.samplingRates.patterns.some((pattern) => pattern.test(context));
3156
+ }
3157
+
3158
+ isHealthCheck(value, context) {
3159
+ const lowerContext = context.toLowerCase();
3160
+
3161
+ if (typeof value !== "number" || value <= 0) return false;
3162
+
3163
+ return this.configPatterns.monitoring.healthChecks.patterns.some((pattern) => pattern.test(context));
3164
+ }
3165
+
3166
+ // ============ Phase 4: Enhancement Configuration Helpers ============
3167
+
3168
+ // Message Queue & Event Configuration helpers
3169
+ isQueueConfig(value, context) {
3170
+ const lowerContext = context.toLowerCase();
3171
+
3172
+ if (typeof value === "number" && value > 0) {
3173
+ return this.configPatterns.messageQueue.queueConfig.patterns.some((pattern) => pattern.test(context));
3174
+ }
3175
+
3176
+ if (typeof value === "string") {
3177
+ return (
3178
+ this.configPatterns.messageQueue.queueNames.patterns.some((pattern) => pattern.test(value)) &&
3179
+ this.configPatterns.messageQueue.queueNames.keywords.some((keyword) => lowerContext.includes(keyword))
3180
+ );
3181
+ }
3182
+
3183
+ return false;
3184
+ }
3185
+
3186
+ isMessageTTL(value, context) {
3187
+ const lowerContext = context.toLowerCase();
3188
+
3189
+ if (typeof value !== "number" || value <= 0) return false;
3190
+
3191
+ return this.configPatterns.messageQueue.messageTTL.patterns.some((pattern) => pattern.test(context));
3192
+ }
3193
+
3194
+ isConsumerConfig(value, context) {
3195
+ if (typeof value !== "string") return false;
3196
+ const lowerContext = context.toLowerCase();
3197
+
3198
+ // Skip CSS classes (Tailwind, Bootstrap, etc.)
3199
+ // CSS classes typically have: spaces, dashes, colons, brackets
3200
+ const cssIndicators = [
3201
+ /\s+(flex|grid|block|inline|hidden|visible)/, // Layout utilities
3202
+ /\s+(text|bg|border|rounded|shadow|p-|m-|w-|h-)/, // Common CSS utilities
3203
+ /:\w+/, // Pseudo-classes like :hover, data-[...]
3204
+ /\[[\w-]+\]/, // Attribute selectors
3205
+ /(sm|md|lg|xl|2xl):/, // Responsive breakpoints
3206
+ ];
3207
+
3208
+ if (cssIndicators.some((pattern) => pattern.test(value))) {
3209
+ return false;
3210
+ }
3211
+
3212
+ // Skip className/class attributes
3213
+ if (/className|class\s*=/.test(context)) {
3214
+ return false;
3215
+ }
3216
+
3217
+ // Only detect if context has consumer/group/queue keywords
3218
+ const hasConsumerContext = /consumer|queue|group|topic|subscription/i.test(context);
3219
+ if (!hasConsumerContext) return false;
3220
+
3221
+ return this.configPatterns.messageQueue.consumerConfig.patterns.some((pattern) => pattern.test(context));
3222
+ }
3223
+
3224
+ // Deployment & Infrastructure helpers
3225
+ isResourceLimit(value, context) {
3226
+ if (typeof value !== "string") return false;
3227
+ const lowerContext = context.toLowerCase();
3228
+
3229
+ const matchesPattern = this.configPatterns.deployment.resourceLimits.patterns.some((pattern) =>
3230
+ pattern.test(value)
3231
+ );
3232
+ if (!matchesPattern) return false;
3233
+
3234
+ return this.configPatterns.deployment.resourceLimits.keywords.some((keyword) =>
3235
+ lowerContext.includes(keyword)
3236
+ );
3237
+ }
3238
+
3239
+ isScalingConfig(value, context) {
3240
+ const lowerContext = context.toLowerCase();
3241
+
3242
+ if (typeof value !== "number" || value <= 0) return false;
3243
+
3244
+ return this.configPatterns.deployment.scalingConfig.patterns.some((pattern) => pattern.test(context));
3245
+ }
3246
+
3247
+ isRegionConfig(value, context) {
3248
+ if (typeof value !== "string") return false;
3249
+ const lowerContext = context.toLowerCase();
3250
+
3251
+ const matchesPattern = this.configPatterns.deployment.regionConfig.patterns.some((pattern) =>
3252
+ pattern.test(value)
3253
+ );
3254
+ if (!matchesPattern) return false;
3255
+
3256
+ return this.configPatterns.deployment.regionConfig.keywords.some((keyword) =>
3257
+ lowerContext.includes(keyword)
3258
+ );
3259
+ }
3260
+
3261
+ isInstanceType(value, context) {
3262
+ if (typeof value !== "string") return false;
3263
+ const lowerContext = context.toLowerCase();
3264
+
3265
+ const matchesPattern = this.configPatterns.deployment.instanceTypes.patterns.some((pattern) =>
3266
+ pattern.test(value)
3267
+ );
3268
+ if (!matchesPattern) return false;
3269
+
3270
+ return this.configPatterns.deployment.instanceTypes.keywords.some((keyword) =>
3271
+ lowerContext.includes(keyword)
3272
+ );
3273
+ }
3274
+
3275
+ // Third-party Integration helpers
3276
+ isWebhookURL(value, context) {
3277
+ if (typeof value !== "string") return false;
3278
+ const lowerContext = context.toLowerCase();
3279
+
3280
+ // Skip UI text/error messages - they're not webhook URLs!
3281
+ const uiTextIndicators = [
3282
+ /failed|error|success|warning/i,
3283
+ /please|try again|invalid/i,
3284
+ /\s{2,}/, // Multiple spaces (typical in messages)
3285
+ value.length > 100, // Long text is likely a message
3286
+ ];
3287
+
3288
+ if (uiTextIndicators.some((pattern) => (typeof pattern === "boolean" ? pattern : pattern.test(value)))) {
3289
+ return false;
3290
+ }
3291
+
3292
+ // Skip single words that aren't paths
3293
+ if (!/[\/\.]/.test(value) && value.length < 20) {
3294
+ // Single word like "Callback" is not a webhook URL
3295
+ return false;
3296
+ }
3297
+
3298
+ return (
3299
+ this.configPatterns.integration.webhookURLs.patterns.some((pattern) => pattern.test(value)) ||
3300
+ this.configPatterns.integration.webhookURLs.keywords.some((keyword) => lowerContext.includes(keyword))
3301
+ );
3302
+ }
3303
+
3304
+ isExternalService(value, context) {
3305
+ if (typeof value !== "string") return false;
3306
+ const lowerContext = context.toLowerCase();
3307
+
3308
+ const matchesPattern = this.configPatterns.integration.externalServices.patterns.some((pattern) =>
3309
+ pattern.test(value)
3310
+ );
3311
+ if (!matchesPattern) return false;
3312
+
3313
+ return this.configPatterns.integration.externalServices.keywords.some((keyword) =>
3314
+ lowerContext.includes(keyword)
3315
+ );
3316
+ }
3317
+
3318
+ isApiVersion(value, context) {
3319
+ if (typeof value !== "string") return false;
3320
+ const lowerContext = context.toLowerCase();
3321
+
3322
+ const matchesPattern = this.configPatterns.integration.apiVersions.patterns.some((pattern) =>
3323
+ pattern.test(value)
3324
+ );
3325
+ if (!matchesPattern) return false;
3326
+
3327
+ return this.configPatterns.integration.apiVersions.keywords.some((keyword) =>
3328
+ lowerContext.includes(keyword)
3329
+ );
3330
+ }
3331
+
3332
+ isChannelId(value, context) {
3333
+ if (typeof value !== "string") return false;
3334
+ const lowerContext = context.toLowerCase();
3335
+
3336
+ const matchesPattern = this.configPatterns.integration.channelIds.patterns.some((pattern) =>
3337
+ pattern.test(value)
3338
+ );
3339
+ if (!matchesPattern) return false;
3340
+
3341
+ return this.configPatterns.integration.channelIds.keywords.some((keyword) =>
3342
+ lowerContext.includes(keyword)
3343
+ );
3344
+ }
3345
+
3346
+ // Localization & Formatting helpers
3347
+ isTimezone(value, context) {
3348
+ if (typeof value !== "string") return false;
3349
+ const lowerContext = context.toLowerCase();
3350
+
3351
+ const matchesPattern = this.configPatterns.localization.timezones.patterns.some((pattern) =>
3352
+ pattern.test(value)
3353
+ );
3354
+ if (!matchesPattern) return false;
3355
+
3356
+ return this.configPatterns.localization.timezones.keywords.some((keyword) =>
3357
+ lowerContext.includes(keyword)
3358
+ );
613
3359
  }
614
3360
 
615
- getMethodName(callExpression) {
616
- const expression = callExpression.getExpression();
617
- if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
618
- return expression.getName();
619
- }
620
- if (expression.getKind() === SyntaxKind.Identifier) {
621
- return expression.getText();
622
- }
623
- return '';
624
- }
3361
+ isDateFormat(value, context) {
3362
+ if (typeof value !== "string") return false;
3363
+ const lowerContext = context.toLowerCase();
625
3364
 
626
- isExcludedUrl(value, node) {
627
- return this.configPatterns.urls.exclude.some(pattern => pattern.test(value));
3365
+ const matchesPattern = this.configPatterns.localization.dateFormats.patterns.some((pattern) =>
3366
+ pattern.test(value)
3367
+ );
3368
+ if (!matchesPattern) return false;
3369
+
3370
+ return this.configPatterns.localization.dateFormats.keywords.some((keyword) =>
3371
+ lowerContext.includes(keyword)
3372
+ );
628
3373
  }
629
3374
 
630
- isExcludedCredential(value, node) {
631
- return this.configPatterns.credentials.exclude.some(pattern => pattern.test(value));
3375
+ isCurrency(value, context) {
3376
+ if (typeof value !== "string") return false;
3377
+ const lowerContext = context.toLowerCase();
3378
+
3379
+ const matchesPattern = this.configPatterns.localization.currencies.patterns.some((pattern) =>
3380
+ pattern.test(value)
3381
+ );
3382
+ if (!matchesPattern) return false;
3383
+
3384
+ return this.configPatterns.localization.currencies.keywords.some((keyword) =>
3385
+ lowerContext.includes(keyword)
3386
+ );
632
3387
  }
633
3388
 
634
- containsCredentialKeyword(context) {
3389
+ isLocale(value, context) {
3390
+ if (typeof value !== "string") return false;
635
3391
  const lowerContext = context.toLowerCase();
636
-
637
- // Skip if this looks like a header name or property key definition
638
- if (context.includes("':") || context.includes('": ') || context.includes(' = ')) {
639
- // This might be a key-value pair where the string is the key
640
- return false;
641
- }
642
-
643
- return this.configPatterns.credentials.keywords.some(keyword =>
3392
+
3393
+ const matchesPattern = this.configPatterns.localization.locales.patterns.some((pattern) =>
3394
+ pattern.test(value)
3395
+ );
3396
+ if (!matchesPattern) return false;
3397
+
3398
+ return this.configPatterns.localization.locales.keywords.some((keyword) =>
644
3399
  lowerContext.includes(keyword)
645
3400
  );
646
3401
  }
647
3402
 
648
- looksLikeUIValue(value, context) {
649
- // Check if it's likely a UI-related value (like input type, label, etc.)
650
- const uiKeywords = ['input', 'type', 'field', 'label', 'placeholder', 'text', 'button'];
3403
+ isNumberFormat(value, context) {
3404
+ if (typeof value !== "string") return false;
651
3405
  const lowerContext = context.toLowerCase();
652
- return uiKeywords.some(keyword => lowerContext.includes(keyword));
3406
+
3407
+ return this.configPatterns.localization.numberFormats.patterns.some((pattern) => pattern.test(context));
653
3408
  }
654
3409
 
655
- looksLikeConfigKey(value) {
656
- // Check if it looks like a config key path (e.g., 'api.baseUrl', 'features.newUI')
657
- if (/^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*$/.test(value)) {
3410
+ // ============ Additional Critical Configuration Helpers ============
3411
+
3412
+ // Environment Variable Names (hardcoded)
3413
+ isHardcodedEnvVar(value, context) {
3414
+ if (typeof value !== "string") return false;
3415
+
3416
+ // Check if it's accessing process.env with hardcoded environment-specific name
3417
+ // Look for patterns like: process.env.PROD_API_URL, process.env['DEV_DATABASE']
3418
+ if (!context.includes("process.env")) return false;
3419
+
3420
+ // The value should be the environment variable name itself
3421
+ return this.configPatterns.environmentVars.patterns.some((pattern) => pattern.test(value));
3422
+ }
3423
+
3424
+ // Third-party Service IDs
3425
+ isThirdPartyServiceId(value, context) {
3426
+ if (typeof value !== "string") return false;
3427
+
3428
+ const { thirdPartyServices } = this.configPatterns;
3429
+
3430
+ // Check Stripe keys
3431
+ if (thirdPartyServices.stripe.patterns.some((p) => p.test(value))) {
658
3432
  return true;
659
3433
  }
660
-
661
- // Check for other config key patterns
662
- const configKeyPatterns = [
663
- /^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z]/, // dotted notation like 'api.url'
664
- /^[A-Z_][A-Z0-9_]*$/, // CONSTANT_CASE like 'API_URL'
665
- /^get[A-Z]/, // getter methods like 'getApiUrl'
666
- /^config\./, // config namespace
667
- /^settings\./, // settings namespace
668
- /^env\./ // env namespace
669
- ];
670
-
671
- return configKeyPatterns.some(pattern => pattern.test(value));
672
- }
673
3434
 
674
- isPropertyKey(node) {
675
- // Check if this string literal is used as a property key in an object literal
676
- const parent = node.getParent();
677
-
678
- // If parent is PropertyAssignment and this node is the name, it's a property key
679
- if (parent && parent.getKind() === SyntaxKind.PropertyAssignment) {
680
- const nameNode = parent.getNameNode();
681
- return nameNode === node;
3435
+ // Check Google Analytics IDs
3436
+ if (thirdPartyServices.googleAnalytics.patterns.some((p) => p.test(value))) {
3437
+ return true;
682
3438
  }
683
-
684
- return false;
685
- }
686
3439
 
687
- isImportPath(value, node) {
688
- // Check if this is likely an import path or module name
689
- const parent = node.getParent();
690
-
691
- // Check if it's in an import statement
692
- let currentNode = parent;
693
- while (currentNode) {
694
- const kind = currentNode.getKind();
695
- if (kind === SyntaxKind.ImportDeclaration ||
696
- kind === SyntaxKind.ExportDeclaration ||
697
- kind === SyntaxKind.CallExpression) {
698
- const text = currentNode.getText();
699
- if (text.includes('require(') || text.includes('import ') || text.includes('from ')) {
700
- return true;
701
- }
702
- }
703
- currentNode = currentNode.getParent();
3440
+ // Check Sentry DSN
3441
+ if (thirdPartyServices.sentry.patterns.some((p) => p.test(value))) {
3442
+ return true;
704
3443
  }
705
-
706
- // Check for common import path patterns
707
- return /^[@a-z][a-z0-9\-_]*\/|^[a-z][a-z0-9\-_]*$|^\.{1,2}\//.test(value) ||
708
- value.endsWith('.js') || value.endsWith('.ts') ||
709
- value.endsWith('.json') || value.endsWith('.css') ||
710
- value.endsWith('.scss') || value.endsWith('.html');
711
- }
712
3444
 
713
- isUIString(value) {
714
- // Check against predefined UI string patterns, but don't skip credentials
715
- if (typeof value === 'string' && value.length > 20 &&
716
- (/token|key|secret|bearer|auth/i.test(value) || /^[a-f0-9-]{30,}$/i.test(value))) {
717
- // Don't skip potential credentials/tokens even if they contain UI keywords
718
- return false;
3445
+ // Check Google Maps API Key
3446
+ if (thirdPartyServices.googleMaps.patterns.some((p) => p.test(value))) {
3447
+ return true;
719
3448
  }
720
-
721
- return this.UI_STRINGS.some(pattern => {
722
- if (typeof pattern === 'string') {
723
- return value === pattern; // Exact match only, not includes
724
- } else {
725
- return pattern.test(value);
726
- }
727
- });
728
- }
729
3449
 
730
- isTestData(value, context) {
731
- // Don't skip credentials/tokens even in dummy files
732
- if (typeof value === 'string' && value.length > 15 &&
733
- (/token|key|secret|auth|bearer|jwt/i.test(value) ||
734
- /^[a-f0-9-]{20,}$/i.test(value) || // Hex tokens
735
- /^[A-Za-z0-9_-]{20,}$/i.test(value))) { // Base64-like tokens
736
- return false; // Don't skip potential credentials
737
- }
738
-
739
- // Check for test patterns in value - but be more restrictive
740
- if (this.TEST_PATTERNS.some(pattern => pattern.test(value))) {
741
- // Only skip if it's clearly test data, not production dummy data
742
- const isInTestFile = /\.(test|spec)\.(ts|tsx|js|jsx)$/i.test(context) ||
743
- /\/__tests__\//i.test(context) ||
744
- /\/test\//i.test(context);
745
- return isInTestFile;
3450
+ // Check Firebase
3451
+ if (thirdPartyServices.firebase.patterns.some((p) => p.test(value))) {
3452
+ return true;
746
3453
  }
747
-
748
- // Check for test context
749
- const lowerContext = context.toLowerCase();
750
- const testKeywords = ['test', 'spec', 'mock', 'fixture', 'stub', 'describe', 'it('];
751
- return testKeywords.some(keyword => lowerContext.includes(keyword));
752
- }
753
3454
 
754
- isValidationMessage(value, context) {
755
- // Skip validation/error messages
756
- const validationPatterns = [
757
- /must contain|should contain|invalid|error|required|missing/i,
758
- /password|username|email/i, // Common validation contexts
759
- /^[A-Z][a-z\s]{10,}$/, // Sentence-like messages
760
- /\s(at least|one|letter|uppercase|lowercase|numeric)/i
761
- ];
762
-
763
- return validationPatterns.some(pattern => pattern.test(value)) ||
764
- /message|error|validation|description/i.test(context);
3455
+ // Check AWS
3456
+ if (thirdPartyServices.aws.patterns.some((p) => p.test(value))) {
3457
+ return true;
3458
+ }
3459
+
3460
+ return false;
765
3461
  }
766
3462
 
767
- isFileNameOrDescription(value, context) {
768
- // Skip file names and descriptions
769
- const filePatterns = [
770
- /\.(csv|json|xml|txt|md)$/i,
771
- /^[a-z_\-]+\.(csv|json|xml|txt)$/i,
772
- /description|comment|note|foreign key|identity/i
773
- ];
774
-
775
- return filePatterns.some(pattern => pattern.test(value)) ||
776
- /description|comment|note|identity|foreign|table/i.test(context);
3463
+ // IP Addresses
3464
+ isIPAddress(value, context) {
3465
+ if (typeof value !== "string") return false;
3466
+
3467
+ const { ipAddresses } = this.configPatterns;
3468
+
3469
+ // Check if it matches IP pattern
3470
+ const isIP = ipAddresses.patterns.some((pattern) => pattern.test(value));
3471
+ if (!isIP) return false;
3472
+
3473
+ // Skip localhost and common development IPs
3474
+ if (ipAddresses.privateRanges.some((range) => range.test(value))) {
3475
+ // Only flag private IPs if in server/host context
3476
+ return ipAddresses.keywords.some((keyword) => context.toLowerCase().includes(keyword));
3477
+ }
3478
+
3479
+ return true;
777
3480
  }
778
3481
 
779
- isEnvironmentDependentUrl(value) {
780
- // Only flag URLs that are likely to differ between environments
781
- const envDependentPatterns = [
782
- /\.amazonaws\.com/, // AWS services
783
- /\.azure\.com/, // Azure services
784
- /\.googleapis\.com/, // Google services
785
- /api\./, // API endpoints
786
- /\.dev|\.staging|\.prod/i // Environment-specific domains
787
- ];
788
-
789
- return envDependentPatterns.some(pattern => pattern.test(value));
3482
+ // Internal Hostnames
3483
+ isInternalHostname(value, context) {
3484
+ if (typeof value !== "string") return false;
3485
+ const lowerContext = context.toLowerCase();
3486
+
3487
+ const matchesPattern = this.configPatterns.hostnames.patterns.some((pattern) => pattern.test(value));
3488
+ if (!matchesPattern) return false;
3489
+
3490
+ return this.configPatterns.hostnames.keywords.some((keyword) => lowerContext.includes(keyword));
790
3491
  }
791
3492
 
792
- isRealCredential(value, context) {
793
- // Check for real credentials, not validation messages
794
- const credentialKeywords = this.configPatterns.credentials.keywords;
3493
+ // Cron Schedules
3494
+ isCronSchedule(value, context) {
3495
+ if (typeof value !== "string") return false;
795
3496
  const lowerContext = context.toLowerCase();
796
-
797
- // Must have credential keyword in context
798
- if (!credentialKeywords.some(keyword => lowerContext.includes(keyword))) {
799
- return false;
800
- }
801
-
802
- // Skip if it's excluded (validation messages, etc.)
803
- if (this.configPatterns.credentials.exclude.some(pattern => pattern.test(value))) {
3497
+
3498
+ const matchesPattern = this.configPatterns.cronSchedules.patterns.some((pattern) => pattern.test(value));
3499
+ if (!matchesPattern) return false;
3500
+
3501
+ return this.configPatterns.cronSchedules.keywords.some((keyword) => lowerContext.includes(keyword));
3502
+ }
3503
+
3504
+ // Magic Numbers in Business Logic
3505
+ isMagicNumber(value, context) {
3506
+ if (typeof value !== "number") return false;
3507
+
3508
+ // Skip common constants
3509
+ const commonConstants = [0, 1, 2];
3510
+ if (commonConstants.includes(value)) return false;
3511
+
3512
+ // Skip HTTP status codes (200, 404, etc.)
3513
+ if (value >= 100 && value < 600 && Number.isInteger(value)) {
3514
+ // But allow decimal values like 0.1 (tax rate), 0.08 (VAT)
804
3515
  return false;
805
3516
  }
806
-
807
- // Skip validation messages and descriptions
808
- if (this.isValidationMessage(value, context)) {
3517
+
3518
+ const lowerContext = context.toLowerCase();
3519
+
3520
+ // Check if it's in business logic context with specific keywords
3521
+ const hasKeyword = this.configPatterns.magicNumbers.keywords.some((keyword) =>
3522
+ lowerContext.includes(keyword)
3523
+ );
3524
+
3525
+ if (!hasKeyword) return false;
3526
+
3527
+ // Additional check: look for business context patterns
3528
+ const hasBusinessContext = this.configPatterns.magicNumbers.businessContexts.some((pattern) =>
3529
+ pattern.test(context)
3530
+ );
3531
+
3532
+ return hasBusinessContext;
3533
+ }
3534
+
3535
+ // Email & SMS Templates
3536
+ isMessageTemplate(value, context) {
3537
+ if (typeof value !== "string") return false;
3538
+ if (value.length < 10) return false;
3539
+
3540
+ const lowerValue = value.toLowerCase();
3541
+ const lowerContext = context.toLowerCase();
3542
+
3543
+ // Skip error codes/enums (like "OAuthAccountNotLinked", "AccessDenied")
3544
+ // These are constants, not templates!
3545
+ if (/^[A-Z][a-zA-Z]+$/.test(value) && !value.includes(" ") && value.length < 30) {
809
3546
  return false;
810
3547
  }
811
-
812
- // Must be reasonably long and not look like UI text
813
- return value.length >= 6 && !this.looksLikeUIValue(value, context);
814
- }
815
3548
 
816
- isEnvironmentDependentProperty(propertyName) {
817
- // Skip UI/framework related property names
818
- const uiPropertyPatterns = [
819
- /^key[A-Z]/, // keyXxx (UI field keys)
820
- /^field[A-Z]/, // fieldXxx
821
- /^prop[A-Z]/, // propXxx
822
- /^data[A-Z]/, // dataXxx
823
- /CheckDisplay/, // UI display control keys
824
- /InputPossible/, // UI input control keys
825
- /Flag$/, // UI flags
826
- /Class$/, // CSS classes
827
- /^(disabled|readonly|active)Class$/i // UI state classes
828
- ];
829
-
830
- if (uiPropertyPatterns.some(pattern => pattern.test(propertyName))) {
3549
+ // Skip short strings without template markers
3550
+ if (value.length < 20 && !value.includes("{{") && !value.includes("${")) {
831
3551
  return false;
832
3552
  }
833
-
834
- // Properties that are likely to differ between environments
835
- const envDependentProps = [
836
- 'baseurl', 'baseURL', 'host', 'hostname', 'server',
837
- 'apikey', 'api_key', 'secret', 'token', 'password', 'credential',
838
- 'database', 'db', 'connection', 'connectionstring',
839
- 'timeout', // Only long timeouts
840
- 'port', // Only non-standard ports
841
- 'authorization', 'auth', 'authentication', // Auth headers and codes
842
- 'apptoken', 'devicetoken', 'accesstoken', 'refreshtoken', // App tokens
843
- 'code', 'hash', 'signature', 'key', // Various security values
844
- 'clientsecret', 'clientid', 'sessionkey', // OAuth and session
845
- 'requestid', 'sessionid', 'transactionid', 'otp' // Request/session tracking
846
- ];
847
-
848
- const lowerName = propertyName.toLowerCase();
849
- return envDependentProps.some(prop => lowerName.includes(prop));
850
- }
851
3553
 
852
- looksLikeEnvironmentConfig(propertyName, value) {
853
- // Check if this property/value combination looks like environment config
854
- const lowerPropertyName = propertyName.toLowerCase();
855
-
856
- if (typeof value === 'string') {
857
- // Skip test data (common test passwords, etc.)
858
- const testDataPatterns = [
859
- /^(password123|test123|admin123|user123|wrongpassword|testpassword)$/i,
860
- /^(test|mock|dummy|sample|example)/i,
861
- /^\/(api|mock|test)/, // Test API paths
862
- /^[a-z]+\d+$/i // Simple test values like 'user1', 'test2'
863
- ];
864
-
865
- // Don't skip common test patterns if they appear in credential contexts
866
- const isCredentialContext = /token|key|secret|auth|otp|code|password|credential/i.test(propertyName);
867
-
868
- if (!isCredentialContext && testDataPatterns.some(pattern => pattern.test(value))) {
869
- return false;
870
- }
871
-
872
- // Skip object property paths and field names
873
- const propertyPathPatterns = [
874
- /^[a-zA-Z][a-zA-Z0-9]*(\[[0-9]+\])?\.[a-zA-Z][a-zA-Z0-9]*$/, // obj[0].prop, obj.prop
875
- /^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*$/, // obj.prop.subprop
876
- /^[a-zA-Z][a-zA-Z0-9]*(\[[0-9]+\])+$/, // obj[0], obj[0][1]
877
- /^(key|field|prop|data)[A-Z]/, // keyXxx, fieldXxx, propXxx, dataXxx
878
- /CheckDisplay|InputPossible|Flag$/i, // Common UI field patterns
879
- /^exflg|^flg|Support$/i, // Business logic flags
880
- ];
881
-
882
- if (propertyPathPatterns.some(pattern => pattern.test(value))) {
883
- return false;
884
- }
885
-
886
- // Skip CSS classes and UI constants
887
- const uiPatterns = [
888
- /^bg-|text-|cursor-|border-|flex-|grid-/, // CSS classes
889
- /^(disabled|readonly|active|inactive)$/i, // UI states
890
- /class$/i // className values
891
- ];
892
-
893
- if (uiPatterns.some(pattern => pattern.test(value))) {
894
- return false;
895
- }
896
-
897
- // Skip internal system identifiers (queue names, service names, route names)
898
- const systemIdentifierPatterns = [
899
- /-queue$/i, // Queue names
900
- /-task$/i, // Task names
901
- /-activity$/i, // Activity names
902
- /-service$/i, // Service names
903
- /-worker$/i, // Worker names
904
- /^[A-Z_]+_QUEUE$/, // CONSTANT_QUEUE names
905
- /^[A-Z_]+_TASK$/, // CONSTANT_TASK names
906
- /^(register|login|logout|reset-password|verify|update)$/i, // Route names
907
- /password|token/i && /invalid|expired|attempts|exceeded/i // Error messages
908
- ];
909
-
910
- if (systemIdentifierPatterns.some(pattern => pattern.test(value))) {
911
- return false;
912
- }
913
-
914
- // Skip error messages and validation messages
915
- const messagePatterns = [
916
- /invalid|expired|exceeded|failed|error|success/i,
917
- /attempts|required|missing|not found/i,
918
- /^[A-Z][a-z\s]{10,}$/, // Sentence-like messages
919
- /は|が|を|に|で|と/, // Japanese particles (UI text)
920
- /情報|画面|ボタン|入力/ // Japanese UI terms
921
- ];
922
-
923
- if (messagePatterns.some(pattern => pattern.test(value))) {
924
- return false;
925
- }
926
-
927
- // URLs are environment-dependent
928
- if (this.configPatterns.urls.regex.test(value)) {
929
- return this.isEnvironmentDependentUrl(value);
930
- }
931
-
932
- // Credentials - but exclude test data
933
- if (lowerPropertyName.includes('key') || lowerPropertyName.includes('secret') ||
934
- lowerPropertyName.includes('token') || lowerPropertyName.includes('password')) {
935
- return value.length > 10; // Real secrets are usually longer
936
- }
937
-
938
- // Skip short endpoint names or simple strings
939
- if (value.length < 10 && !value.includes('.') && !value.includes('/')) {
940
- return false;
941
- }
3554
+ // Email subject/body - check both context AND value for keywords
3555
+ const emailKeywordInContext = this.configPatterns.messageTemplates.email.keywords.some((k) =>
3556
+ lowerContext.includes(k)
3557
+ );
3558
+ const emailKeywordInValue = ["welcome", "verify", "password", "reset", "order", "confirm"].some((k) =>
3559
+ lowerValue.includes(k)
3560
+ );
3561
+
3562
+ if (emailKeywordInContext || emailKeywordInValue) {
3563
+ return this.configPatterns.messageTemplates.email.patterns.some((p) => p.test(value));
942
3564
  }
943
-
944
- if (typeof value === 'number') {
945
- // Only flag environment-dependent numbers
946
- return this.configPatterns.environmentNumbers.isEnvironmentDependent(value, propertyName);
3565
+
3566
+ // SMS text - check for OTP, code, verification keywords
3567
+ const smsKeywordInContext = this.configPatterns.messageTemplates.sms.keywords.some((k) =>
3568
+ lowerContext.includes(k)
3569
+ );
3570
+ const smsKeywordInValue = ["otp", "code", "verification", "verify"].some((k) => lowerValue.includes(k));
3571
+
3572
+ if (smsKeywordInContext || smsKeywordInValue) {
3573
+ return this.configPatterns.messageTemplates.sms.patterns.some((p) => p.test(value));
947
3574
  }
948
-
949
- return true;
950
- }
951
3575
 
952
- isCommonConstant(value) {
953
- // Common constants that are usually OK to hardcode
954
- const commonConstants = [100, 200, 300, 400, 500, 1000, 2000, 3000, 5000, 8080, 3000];
955
- return commonConstants.includes(value);
3576
+ return false;
956
3577
  }
957
3578
 
958
- isConfigProperty(propertyName) {
959
- const configProps = [
960
- 'url', 'endpoint', 'baseurl', 'apiurl', 'host', 'port',
961
- 'timeout', 'interval', 'delay', 'retry', 'retries',
962
- 'username', 'password', 'apikey', 'secret', 'token',
963
- 'database', 'connection', 'connectionstring',
964
- 'maxsize', 'batchsize', 'pagesize', 'limit'
965
- ];
966
-
967
- const lowerName = propertyName.toLowerCase();
968
- return configProps.some(prop => lowerName.includes(prop));
3579
+ // Version Numbers
3580
+ isVersionNumber(value, context) {
3581
+ if (typeof value !== "string") return false;
3582
+ const lowerContext = context.toLowerCase();
3583
+
3584
+ const matchesPattern = this.configPatterns.versions.patterns.some((pattern) => pattern.test(value));
3585
+ if (!matchesPattern) return false;
3586
+
3587
+ return this.configPatterns.versions.keywords.some((keyword) => lowerContext.includes(keyword));
969
3588
  }
970
3589
 
971
- isConfigVariable(variableName) {
972
- const configVars = [
973
- 'api', 'url', 'endpoint', 'host', 'port',
974
- 'timeout', 'interval', 'delay', 'retry',
975
- 'config', 'setting', 'constant'
976
- ];
977
-
978
- const lowerName = variableName.toLowerCase();
979
- return configVars.some(var_ => lowerName.includes(var_));
3590
+ // Default Pagination
3591
+ isPaginationDefault(value, context) {
3592
+ if (typeof value !== "number") return false;
3593
+ const lowerContext = context.toLowerCase();
3594
+
3595
+ // Must be a common pagination value
3596
+ if (!this.configPatterns.pagination.defaults.includes(value)) return false;
3597
+
3598
+ // Must be in pagination context
3599
+ return this.configPatterns.pagination.keywords.some((keyword) => lowerContext.includes(keyword));
980
3600
  }
981
3601
 
982
- looksLikeHardcodedConfig(name, value) {
983
- // Skip obvious constants and UI values
984
- if (typeof value === 'string') {
985
- if (value.length < 3) return false;
986
- if (/^(ok|yes|no|true|false|success|error|info|warn)$/i.test(value)) return false;
987
- }
988
-
989
- if (typeof value === 'number') {
990
- if (this.isCommonConstant(value)) return false;
3602
+ maskSensitiveValue(value) {
3603
+ if (typeof value !== "string" || value.length <= 6) {
3604
+ return value;
991
3605
  }
992
-
993
- return true;
3606
+
3607
+ // Mask sensitive information but show some characters for context
3608
+ const start = value.substring(0, 3);
3609
+ const end = value.substring(value.length - 3);
3610
+ const masked = "*".repeat(Math.min(value.length - 6, 10));
3611
+ return `${start}${masked}${end}`;
994
3612
  }
995
3613
 
996
3614
  createMessage(config) {
997
- const baseMessage = 'Environment-dependent configuration should not be hardcoded.';
998
-
3615
+ const baseMessage = "Configuration should not be hardcoded in source code.";
3616
+
999
3617
  switch (config.type) {
1000
- case 'url':
1001
- return `${baseMessage} External URL '${config.value}' should be loaded from environment variables or configuration files.`;
1002
- case 'credential':
1003
- return `${baseMessage} Credential value '${config.value}' should be loaded from secure environment variables.`;
1004
- case 'environment_config':
1005
- return `${baseMessage} Environment-dependent value ${config.value} should be configurable via environment variables or config files.`;
1006
- case 'connection':
1007
- return `${baseMessage} Connection string should be loaded from environment variables.`;
1008
- case 'property_config':
1009
- return `${baseMessage} Property '${config.propertyName}' may contain environment-dependent value '${config.value}'.`;
1010
- case 'variable_config':
1011
- return `${baseMessage} Variable '${config.variableName}' may contain environment-dependent value '${config.value}'.`;
1012
- case 'config_key':
1013
- return `${baseMessage} Configuration key '${config.value}' should not be hardcoded.`;
3618
+ case "api_url":
3619
+ return `${baseMessage} API URL '${config.value}' should be managed through environment variables or configuration files.`;
3620
+ case "credential":
3621
+ return `${baseMessage} Credential '${config.value}' must be stored in secure environment variables or a secrets vault.`;
3622
+ case "connection_string":
3623
+ return `${baseMessage} Database connection string should be loaded from environment variables.`;
3624
+ case "timeout":
3625
+ return `${baseMessage} Timeout value ${config.value}ms may need to differ between environments.`;
3626
+ case "retry_interval":
3627
+ return `${baseMessage} Retry interval ${config.value}ms should be configurable per environment.`;
3628
+ case "batch_size":
3629
+ return `${baseMessage} Batch size ${config.value} may vary between development and production environments.`;
3630
+ case "threshold":
3631
+ return `${baseMessage} Threshold value ${config.value} should be environment-configurable.`;
3632
+ case "feature_flag":
3633
+ return `${baseMessage} Feature flag should be managed through a feature flag system or configuration.`;
3634
+ case "cors_origin":
3635
+ return `${baseMessage} CORS origin '${config.value}' should be loaded from environment configuration.`;
3636
+ case "session_config":
3637
+ return `${baseMessage} Session configuration '${config.value}' should be environment-dependent.`;
3638
+ case "cache_config":
3639
+ return `${baseMessage} Cache configuration '${config.value}' should be managed through environment settings.`;
3640
+ case "log_level":
3641
+ return `${baseMessage} Log level '${config.value}' should be configurable per environment.`;
3642
+ case "environment_name":
3643
+ return `${baseMessage} Environment name '${config.value}' should not be hardcoded, use environment detection.`;
3644
+ case "service_dependency":
3645
+ return `${baseMessage} Service URL '${config.value}' should use service discovery or configuration management.`;
3646
+ case "performance_config":
3647
+ return `${baseMessage} Performance setting ${config.value} should be tuned per environment capacity.`;
3648
+
3649
+ // Phase 2: Critical configurations
3650
+ case "database_pool":
3651
+ return `${baseMessage} Database pool size ${config.value} should be optimized per environment capacity.`;
3652
+ case "query_config":
3653
+ return `${baseMessage} Query configuration '${config.value}' should be environment-dependent.`;
3654
+ case "schema_name":
3655
+ return `${baseMessage} Database schema/table name '${config.value}' should be managed through configuration.`;
3656
+ case "token_config":
3657
+ return `${baseMessage} Token configuration '${config.value}' should be externalized for security.`;
3658
+ case "password_policy":
3659
+ return `${baseMessage} Password policy setting ${config.value} should be configurable per environment.`;
3660
+ case "rate_limiting":
3661
+ return `${baseMessage} Rate limit ${config.value} should vary by environment and tier.`;
3662
+ case "encryption_config":
3663
+ return `${baseMessage} Encryption algorithm '${config.value}' should be configurable for compliance requirements.`;
3664
+ case "oauth_config":
3665
+ return `${baseMessage} OAuth scope '${config.value}' should be managed through configuration.`;
3666
+ case "directory":
3667
+ return `${baseMessage} Directory path '${config.value}' should be environment-specific.`;
3668
+ case "file_limit":
3669
+ return `${baseMessage} File size limit ${config.value} bytes should be configurable per environment.`;
3670
+ case "file_type":
3671
+ return `${baseMessage} File type restriction '${config.value}' should be managed in configuration.`;
3672
+ case "log_path":
3673
+ return `${baseMessage} Log file path '${config.value}' should be environment-specific.`;
3674
+
3675
+ // Phase 3: Important configurations
3676
+ case "http_config":
3677
+ return `${baseMessage} HTTP configuration '${config.value}' should be externalized.`;
3678
+ case "network_timeout":
3679
+ return `${baseMessage} Network timeout ${config.value}ms should be tuned per environment.`;
3680
+ case "buffer_config":
3681
+ return `${baseMessage} Buffer size ${config.value} bytes should be configurable.`;
3682
+ case "keepalive_config":
3683
+ return `${baseMessage} Keep-alive setting ${config.value} should be environment-dependent.`;
3684
+ case "pricing":
3685
+ return `${baseMessage} Price ${config.value} should be managed in pricing configuration system.`;
3686
+ case "quota":
3687
+ return `${baseMessage} Quota limit ${config.value} should be configurable per plan/tier.`;
3688
+ case "discount":
3689
+ return `${baseMessage} Discount rate ${config.value} should be managed in pricing configuration.`;
3690
+ case "trial":
3691
+ return `${baseMessage} Trial period ${config.value} days should be configurable.`;
3692
+ case "metrics_config":
3693
+ return `${baseMessage} Metrics interval ${config.value} should be environment-dependent.`;
3694
+ case "alert_threshold":
3695
+ return `${baseMessage} Alert threshold ${config.value} should be tuned per environment.`;
3696
+ case "sampling_rate":
3697
+ return `${baseMessage} Sampling rate ${config.value} should be configurable for observability tuning.`;
3698
+ case "health_check":
3699
+ return `${baseMessage} Health check interval ${config.value} should be environment-specific.`;
3700
+
3701
+ // Phase 4: Enhancement configurations
3702
+ case "queue_config":
3703
+ return `${baseMessage} Queue configuration '${config.value}' should be externalized.`;
3704
+ case "message_ttl":
3705
+ return `${baseMessage} Message TTL ${config.value} should be configurable per environment.`;
3706
+ case "consumer_config":
3707
+ return `${baseMessage} Consumer configuration '${config.value}' should be managed externally.`;
3708
+ case "resource_limit":
3709
+ return `${baseMessage} Resource limit '${config.value}' should be environment-specific.`;
3710
+ case "scaling_config":
3711
+ return `${baseMessage} Scaling configuration ${config.value} should be tuned per environment.`;
3712
+ case "region_config":
3713
+ return `${baseMessage} Deployment region '${config.value}' should be determined at deployment time.`;
3714
+ case "instance_type":
3715
+ return `${baseMessage} Instance type '${config.value}' should be configurable per environment.`;
3716
+ case "webhook_url":
3717
+ return `${baseMessage} Webhook URL '${config.value}' should be environment-specific.`;
3718
+ case "external_service":
3719
+ return `${baseMessage} External service name '${config.value}' should be managed in configuration.`;
3720
+ case "api_version":
3721
+ return `${baseMessage} API version '${config.value}' should be configurable for version management.`;
3722
+ case "channel_id":
3723
+ return `${baseMessage} Channel ID '${config.value}' should be environment-dependent.`;
3724
+ case "timezone":
3725
+ return `${baseMessage} Timezone '${config.value}' should be user-configurable or environment-specific.`;
3726
+ case "date_format":
3727
+ return `${baseMessage} Date format '${config.value}' should be locale-configurable.`;
3728
+ case "currency":
3729
+ return `${baseMessage} Currency code '${config.value}' should be region-configurable.`;
3730
+ case "locale":
3731
+ return `${baseMessage} Locale '${config.value}' should be user-configurable.`;
3732
+ case "number_format":
3733
+ return `${baseMessage} Number format should be locale-configurable.`;
3734
+
3735
+ // Additional critical configurations
3736
+ case "environment_var":
3737
+ return `${baseMessage} Environment variable name '${config.value}' is hardcoded and environment-specific.`;
3738
+ case "third_party_service":
3739
+ return `${baseMessage} Third-party service ID/key '${config.value}' is hardcoded (security risk).`;
3740
+ case "ip_address":
3741
+ return `${baseMessage} IP address '${config.value}' is hardcoded.`;
3742
+ case "hostname":
3743
+ return `${baseMessage} Internal hostname '${config.value}' is hardcoded.`;
3744
+ case "cron_schedule":
3745
+ return `${baseMessage} Cron schedule '${config.value}' is hardcoded.`;
3746
+ case "magic_number":
3747
+ return `${baseMessage} Magic number '${config.value}' in business logic should be a named constant.`;
3748
+ case "message_template":
3749
+ return `${baseMessage} Message template is hardcoded (should be in template system).`;
3750
+ case "version":
3751
+ return `${baseMessage} Version number '${config.value}' is hardcoded.`;
3752
+ case "pagination_default":
3753
+ return `${baseMessage} Pagination default '${config.value}' is hardcoded.`;
3754
+
3755
+ case "property_config":
3756
+ return `${baseMessage} Property '${config.propertyName}' contains value '${config.value}' that may differ between environments.`;
3757
+ case "variable_config":
3758
+ return `${baseMessage} Variable '${config.variableName}' contains value '${config.value}' that should be configurable.`;
1014
3759
  default:
1015
- return `${baseMessage} Value '${config.value}' may differ between environments.`;
3760
+ return `${baseMessage} Value '${config.value}' may need to differ between development, staging, and production environments.`;
1016
3761
  }
1017
3762
  }
1018
3763
 
1019
3764
  getSuggestion(type) {
1020
3765
  const suggestions = {
1021
- 'url': 'Use process.env.API_URL or config.get("api.url")',
1022
- 'credential': 'Use process.env.SECRET_KEY or secure vault',
1023
- 'environment_config': 'Move to environment variables or config service',
1024
- 'connection': 'Use process.env.DATABASE_URL',
1025
- 'property_config': 'Consider if this differs between dev/staging/production',
1026
- 'variable_config': 'Use environment variables if this differs between environments',
1027
- 'config_key': 'Use constants or enums for configuration keys'
3766
+ api_url: 'Use process.env.API_BASE_URL or config.get("api.baseUrl")',
3767
+ credential: "Use process.env.API_KEY or secure vault (e.g., AWS Secrets Manager, Azure Key Vault)",
3768
+ connection_string: "Use process.env.DATABASE_URL or connection configuration",
3769
+ timeout: 'Use process.env.REQUEST_TIMEOUT or config.get("timeouts.request")',
3770
+ retry_interval: 'Use process.env.RETRY_INTERVAL or config.get("retry.interval")',
3771
+ batch_size: 'Use process.env.BATCH_SIZE or config.get("processing.batchSize")',
3772
+ threshold: 'Use process.env.MEMORY_THRESHOLD or config.get("limits.memory")',
3773
+ feature_flag:
3774
+ 'Use feature flag service (e.g., LaunchDarkly, ConfigCat) or config.get("features.enabled")',
3775
+ cors_origin: 'Use process.env.CORS_ORIGINS or config.get("security.corsOrigins")',
3776
+ session_config: 'Use process.env.JWT_EXPIRY or config.get("auth.sessionTimeout")',
3777
+ cache_config: 'Use process.env.CACHE_TTL or config.get("cache.defaultTtl")',
3778
+ log_level: 'Use process.env.LOG_LEVEL or config.get("logging.level")',
3779
+ environment_name: "Use process.env.NODE_ENV or runtime environment detection",
3780
+ service_dependency: 'Use service discovery or config.get("services.userService.url")',
3781
+ performance_config: 'Use process.env.WORKER_THREADS or config.get("performance.workers")',
3782
+
3783
+ // Phase 2: Critical configurations
3784
+ database_pool: 'Use process.env.DB_POOL_SIZE or config.get("database.poolSize")',
3785
+ query_config: 'Use process.env.QUERY_TIMEOUT or config.get("database.queryTimeout")',
3786
+ schema_name: 'Use process.env.TABLE_PREFIX or config.get("database.schemaName")',
3787
+ token_config: 'Use process.env.TOKEN_EXPIRY or config.get("auth.tokenExpiry")',
3788
+ password_policy: 'Use process.env.MIN_PASSWORD_LENGTH or config.get("security.passwordPolicy")',
3789
+ rate_limiting: 'Use process.env.RATE_LIMIT or config.get("security.rateLimit")',
3790
+ encryption_config: 'Use process.env.ENCRYPTION_ALGORITHM or config.get("security.encryption")',
3791
+ oauth_config: 'Use process.env.OAUTH_SCOPES or config.get("oauth.scopes")',
3792
+ directory: 'Use process.env.UPLOAD_DIR or config.get("storage.uploadDirectory")',
3793
+ file_limit: 'Use process.env.MAX_FILE_SIZE or config.get("upload.maxFileSize")',
3794
+ file_type: 'Use process.env.ALLOWED_FILE_TYPES or config.get("upload.allowedTypes")',
3795
+ log_path: 'Use process.env.LOG_PATH or config.get("logging.filePath")',
3796
+
3797
+ // Phase 3: Important configurations
3798
+ http_config: 'Use process.env.ALLOWED_METHODS or config.get("http.allowedMethods")',
3799
+ network_timeout: 'Use process.env.CONNECT_TIMEOUT or config.get("network.timeout")',
3800
+ buffer_config: 'Use process.env.BUFFER_SIZE or config.get("network.bufferSize")',
3801
+ keepalive_config: 'Use process.env.KEEPALIVE_INTERVAL or config.get("network.keepAlive")',
3802
+ pricing: 'Use pricing service or config.get("pricing.plans")',
3803
+ quota: 'Use process.env.API_QUOTA or config.get("limits.quotas")',
3804
+ discount: 'Use pricing service or config.get("pricing.discounts")',
3805
+ trial: 'Use process.env.TRIAL_DAYS or config.get("subscription.trialPeriod")',
3806
+ metrics_config: 'Use process.env.METRICS_INTERVAL or config.get("monitoring.metricsInterval")',
3807
+ alert_threshold: 'Use process.env.ALERT_THRESHOLD or config.get("monitoring.alertThresholds")',
3808
+ sampling_rate: 'Use process.env.TRACE_SAMPLING_RATE or config.get("observability.samplingRate")',
3809
+ health_check: 'Use process.env.HEALTH_CHECK_INTERVAL or config.get("monitoring.healthCheck")',
3810
+
3811
+ // Phase 4: Enhancement configurations
3812
+ queue_config: 'Use process.env.QUEUE_SIZE or config.get("messaging.queueSize")',
3813
+ message_ttl: 'Use process.env.MESSAGE_TTL or config.get("messaging.messageTtl")',
3814
+ consumer_config: 'Use process.env.CONSUMER_GROUP or config.get("messaging.consumerGroup")',
3815
+ resource_limit: 'Use Kubernetes limits or config.get("deployment.resources")',
3816
+ scaling_config: 'Use process.env.MIN_REPLICAS or config.get("deployment.autoscaling")',
3817
+ region_config: 'Use deployment configuration or config.get("infrastructure.region")',
3818
+ instance_type: 'Use deployment configuration or config.get("infrastructure.instanceType")',
3819
+ webhook_url: 'Use process.env.WEBHOOK_URL or config.get("integrations.webhookUrl")',
3820
+ external_service: 'Use process.env.PAYMENT_PROVIDER or config.get("integrations.provider")',
3821
+ api_version: 'Use process.env.API_VERSION or config.get("integrations.apiVersion")',
3822
+ channel_id: 'Use process.env.SLACK_CHANNEL or config.get("notifications.channel")',
3823
+ timezone: 'Use process.env.TZ or config.get("localization.timezone")',
3824
+ date_format: 'Use config.get("localization.dateFormat") or user preferences',
3825
+ currency: 'Use config.get("localization.currency") or user/region preferences',
3826
+ locale: 'Use process.env.LOCALE or config.get("localization.locale")',
3827
+ number_format: 'Use config.get("localization.numberFormat") or locale-based formatting',
3828
+
3829
+ // Additional critical configurations
3830
+ environment_var: "Use environment-agnostic names with NODE_ENV detection or config service",
3831
+ third_party_service: "Use AWS Secrets Manager, Azure Key Vault, or .env with git-ignored secrets",
3832
+ ip_address: 'Use service discovery (Consul, Kubernetes DNS) or config.get("services.host")',
3833
+ hostname: 'Use service discovery or config.get("services.internalHostname")',
3834
+ cron_schedule: 'Use process.env.CRON_SCHEDULE or config.get("scheduler.cron")',
3835
+ magic_number: "Define as const BUSINESS_CONSTANT = value in constants file",
3836
+ message_template: 'Use template management (i18n, email service) or config.get("templates.email")',
3837
+ version: "Use package.json version or build-time injection (webpack.DefinePlugin)",
3838
+ pagination_default: 'Use config.get("api.defaultPageSize") or API configuration',
3839
+
3840
+ property_config: "Move to centralized configuration (config.ts, application.properties, or .env)",
3841
+ variable_config: "Use environment variables or configuration service",
3842
+ config_key: "Define configuration keys as constants in a dedicated config module",
1028
3843
  };
1029
-
1030
- return suggestions[type] || 'Consider if this value should differ between dev/staging/production environments';
3844
+
3845
+ return (
3846
+ suggestions[type] ||
3847
+ "Move to environment variables, config files, or centralized configuration management"
3848
+ );
1031
3849
  }
1032
3850
  }
1033
3851