typekro 0.2.1 → 0.3.0

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 (197) hide show
  1. package/README.md +4 -3
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/core/composition/imperative.d.ts.map +1 -1
  4. package/dist/core/composition/imperative.js +15 -2
  5. package/dist/core/composition/imperative.js.map +1 -1
  6. package/dist/core/composition/typekro-runtime/typekro-runtime.d.ts.map +1 -1
  7. package/dist/core/composition/typekro-runtime/typekro-runtime.js +24 -25
  8. package/dist/core/composition/typekro-runtime/typekro-runtime.js.map +1 -1
  9. package/dist/core/dependencies/type-guards.d.ts.map +1 -1
  10. package/dist/core/dependencies/type-guards.js +7 -2
  11. package/dist/core/dependencies/type-guards.js.map +1 -1
  12. package/dist/core/deployment/engine.d.ts +0 -1
  13. package/dist/core/deployment/engine.d.ts.map +1 -1
  14. package/dist/core/deployment/engine.js +0 -1
  15. package/dist/core/deployment/engine.js.map +1 -1
  16. package/dist/core/errors.d.ts +85 -0
  17. package/dist/core/errors.d.ts.map +1 -1
  18. package/dist/core/errors.js +135 -0
  19. package/dist/core/errors.js.map +1 -1
  20. package/dist/core/expressions/analyzer.d.ts +584 -0
  21. package/dist/core/expressions/analyzer.d.ts.map +1 -0
  22. package/dist/core/expressions/analyzer.js +2956 -0
  23. package/dist/core/expressions/analyzer.js.map +1 -0
  24. package/dist/core/expressions/cache.d.ts +136 -0
  25. package/dist/core/expressions/cache.d.ts.map +1 -0
  26. package/dist/core/expressions/cache.js +347 -0
  27. package/dist/core/expressions/cache.js.map +1 -0
  28. package/dist/core/expressions/cel-conversion-engine.d.ts +126 -0
  29. package/dist/core/expressions/cel-conversion-engine.d.ts.map +1 -0
  30. package/dist/core/expressions/cel-conversion-engine.js +293 -0
  31. package/dist/core/expressions/cel-conversion-engine.js.map +1 -0
  32. package/dist/core/expressions/compile-time-validation.d.ts +270 -0
  33. package/dist/core/expressions/compile-time-validation.d.ts.map +1 -0
  34. package/dist/core/expressions/compile-time-validation.js +506 -0
  35. package/dist/core/expressions/compile-time-validation.js.map +1 -0
  36. package/dist/core/expressions/composition-integration.d.ts +315 -0
  37. package/dist/core/expressions/composition-integration.d.ts.map +1 -0
  38. package/dist/core/expressions/composition-integration.js +936 -0
  39. package/dist/core/expressions/composition-integration.js.map +1 -0
  40. package/dist/core/expressions/conditional-expression-processor.d.ts +154 -0
  41. package/dist/core/expressions/conditional-expression-processor.d.ts.map +1 -0
  42. package/dist/core/expressions/conditional-expression-processor.js +479 -0
  43. package/dist/core/expressions/conditional-expression-processor.js.map +1 -0
  44. package/dist/core/expressions/conditional-integration.d.ts +133 -0
  45. package/dist/core/expressions/conditional-integration.d.ts.map +1 -0
  46. package/dist/core/expressions/conditional-integration.js +293 -0
  47. package/dist/core/expressions/conditional-integration.js.map +1 -0
  48. package/dist/core/expressions/conditional-validation.d.ts +181 -0
  49. package/dist/core/expressions/conditional-validation.d.ts.map +1 -0
  50. package/dist/core/expressions/conditional-validation.js +460 -0
  51. package/dist/core/expressions/conditional-validation.js.map +1 -0
  52. package/dist/core/expressions/context-aware-generator.d.ts +127 -0
  53. package/dist/core/expressions/context-aware-generator.d.ts.map +1 -0
  54. package/dist/core/expressions/context-aware-generator.js +500 -0
  55. package/dist/core/expressions/context-aware-generator.js.map +1 -0
  56. package/dist/core/expressions/context-detector.d.ts +148 -0
  57. package/dist/core/expressions/context-detector.d.ts.map +1 -0
  58. package/dist/core/expressions/context-detector.js +546 -0
  59. package/dist/core/expressions/context-detector.js.map +1 -0
  60. package/dist/core/expressions/context-switcher.d.ts +185 -0
  61. package/dist/core/expressions/context-switcher.d.ts.map +1 -0
  62. package/dist/core/expressions/context-switcher.js +515 -0
  63. package/dist/core/expressions/context-switcher.js.map +1 -0
  64. package/dist/core/expressions/context-validator.d.ts +176 -0
  65. package/dist/core/expressions/context-validator.d.ts.map +1 -0
  66. package/dist/core/expressions/context-validator.js +452 -0
  67. package/dist/core/expressions/context-validator.js.map +1 -0
  68. package/dist/core/expressions/custom-context-manager.d.ts +194 -0
  69. package/dist/core/expressions/custom-context-manager.d.ts.map +1 -0
  70. package/dist/core/expressions/custom-context-manager.js +390 -0
  71. package/dist/core/expressions/custom-context-manager.js.map +1 -0
  72. package/dist/core/expressions/expression-proxy.d.ts +80 -0
  73. package/dist/core/expressions/expression-proxy.d.ts.map +1 -0
  74. package/dist/core/expressions/expression-proxy.js +227 -0
  75. package/dist/core/expressions/expression-proxy.js.map +1 -0
  76. package/dist/core/expressions/factory-integration.d.ts +132 -0
  77. package/dist/core/expressions/factory-integration.d.ts.map +1 -0
  78. package/dist/core/expressions/factory-integration.js +327 -0
  79. package/dist/core/expressions/factory-integration.js.map +1 -0
  80. package/dist/core/expressions/factory-pattern-handler.d.ts +88 -0
  81. package/dist/core/expressions/factory-pattern-handler.d.ts.map +1 -0
  82. package/dist/core/expressions/factory-pattern-handler.js +336 -0
  83. package/dist/core/expressions/factory-pattern-handler.js.map +1 -0
  84. package/dist/core/expressions/field-hydration-processor.d.ts +188 -0
  85. package/dist/core/expressions/field-hydration-processor.d.ts.map +1 -0
  86. package/dist/core/expressions/field-hydration-processor.js +562 -0
  87. package/dist/core/expressions/field-hydration-processor.js.map +1 -0
  88. package/dist/core/expressions/imperative-analyzer.d.ts +21 -0
  89. package/dist/core/expressions/imperative-analyzer.d.ts.map +1 -0
  90. package/dist/core/expressions/imperative-analyzer.js +343 -0
  91. package/dist/core/expressions/imperative-analyzer.js.map +1 -0
  92. package/dist/core/expressions/index.d.ts +54 -0
  93. package/dist/core/expressions/index.d.ts.map +1 -0
  94. package/dist/core/expressions/index.js +50 -0
  95. package/dist/core/expressions/index.js.map +1 -0
  96. package/dist/core/expressions/lazy-analysis.d.ts +1128 -0
  97. package/dist/core/expressions/lazy-analysis.d.ts.map +1 -0
  98. package/dist/core/expressions/lazy-analysis.js +2443 -0
  99. package/dist/core/expressions/lazy-analysis.js.map +1 -0
  100. package/dist/core/expressions/magic-assignable-analyzer.d.ts +123 -0
  101. package/dist/core/expressions/magic-assignable-analyzer.d.ts.map +1 -0
  102. package/dist/core/expressions/magic-assignable-analyzer.js +352 -0
  103. package/dist/core/expressions/magic-assignable-analyzer.js.map +1 -0
  104. package/dist/core/expressions/magic-proxy-analyzer.d.ts +206 -0
  105. package/dist/core/expressions/magic-proxy-analyzer.d.ts.map +1 -0
  106. package/dist/core/expressions/magic-proxy-analyzer.js +639 -0
  107. package/dist/core/expressions/magic-proxy-analyzer.js.map +1 -0
  108. package/dist/core/expressions/magic-proxy-detector.d.ts +154 -0
  109. package/dist/core/expressions/magic-proxy-detector.d.ts.map +1 -0
  110. package/dist/core/expressions/magic-proxy-detector.js +242 -0
  111. package/dist/core/expressions/magic-proxy-detector.js.map +1 -0
  112. package/dist/core/expressions/migration-helpers.d.ts +133 -0
  113. package/dist/core/expressions/migration-helpers.d.ts.map +1 -0
  114. package/dist/core/expressions/migration-helpers.js +443 -0
  115. package/dist/core/expressions/migration-helpers.js.map +1 -0
  116. package/dist/core/expressions/optionality-handler.d.ts +503 -0
  117. package/dist/core/expressions/optionality-handler.d.ts.map +1 -0
  118. package/dist/core/expressions/optionality-handler.js +1306 -0
  119. package/dist/core/expressions/optionality-handler.js.map +1 -0
  120. package/dist/core/expressions/readiness-integration.d.ts +119 -0
  121. package/dist/core/expressions/readiness-integration.d.ts.map +1 -0
  122. package/dist/core/expressions/readiness-integration.js +386 -0
  123. package/dist/core/expressions/readiness-integration.js.map +1 -0
  124. package/dist/core/expressions/resource-analyzer.d.ts +486 -0
  125. package/dist/core/expressions/resource-analyzer.d.ts.map +1 -0
  126. package/dist/core/expressions/resource-analyzer.js +1086 -0
  127. package/dist/core/expressions/resource-analyzer.js.map +1 -0
  128. package/dist/core/expressions/resource-validation.d.ts +187 -0
  129. package/dist/core/expressions/resource-validation.d.ts.map +1 -0
  130. package/dist/core/expressions/resource-validation.js +552 -0
  131. package/dist/core/expressions/resource-validation.js.map +1 -0
  132. package/dist/core/expressions/runtime-error-mapper.d.ts +138 -0
  133. package/dist/core/expressions/runtime-error-mapper.d.ts.map +1 -0
  134. package/dist/core/expressions/runtime-error-mapper.js +412 -0
  135. package/dist/core/expressions/runtime-error-mapper.js.map +1 -0
  136. package/dist/core/expressions/source-map.d.ts +168 -0
  137. package/dist/core/expressions/source-map.d.ts.map +1 -0
  138. package/dist/core/expressions/source-map.js +350 -0
  139. package/dist/core/expressions/source-map.js.map +1 -0
  140. package/dist/core/expressions/status-builder-analyzer.d.ts +353 -0
  141. package/dist/core/expressions/status-builder-analyzer.d.ts.map +1 -0
  142. package/dist/core/expressions/status-builder-analyzer.js +1301 -0
  143. package/dist/core/expressions/status-builder-analyzer.js.map +1 -0
  144. package/dist/core/expressions/type-inference.d.ts +184 -0
  145. package/dist/core/expressions/type-inference.d.ts.map +1 -0
  146. package/dist/core/expressions/type-inference.js +838 -0
  147. package/dist/core/expressions/type-inference.js.map +1 -0
  148. package/dist/core/expressions/type-safety.d.ts +203 -0
  149. package/dist/core/expressions/type-safety.d.ts.map +1 -0
  150. package/dist/core/expressions/type-safety.js +442 -0
  151. package/dist/core/expressions/type-safety.js.map +1 -0
  152. package/dist/core/expressions/types.d.ts +282 -0
  153. package/dist/core/expressions/types.d.ts.map +1 -0
  154. package/dist/core/expressions/types.js +8 -0
  155. package/dist/core/expressions/types.js.map +1 -0
  156. package/dist/core/kubernetes/client-provider.js +2 -2
  157. package/dist/core/kubernetes/client-provider.js.map +1 -1
  158. package/dist/core/serialization/core.d.ts.map +1 -1
  159. package/dist/core/serialization/core.js +573 -9
  160. package/dist/core/serialization/core.js.map +1 -1
  161. package/dist/core/types/deployment.d.ts +4 -0
  162. package/dist/core/types/deployment.d.ts.map +1 -1
  163. package/dist/core/types/deployment.js.map +1 -1
  164. package/dist/core/types/index.d.ts +1 -0
  165. package/dist/core/types/index.d.ts.map +1 -1
  166. package/dist/core/types/index.js.map +1 -1
  167. package/dist/core.d.ts +1 -1
  168. package/dist/core.d.ts.map +1 -1
  169. package/dist/core.js +1 -1
  170. package/dist/core.js.map +1 -1
  171. package/dist/factories/helm/helm-release.d.ts.map +1 -1
  172. package/dist/factories/helm/helm-release.js +0 -5
  173. package/dist/factories/helm/helm-release.js.map +1 -1
  174. package/dist/factories/helm/types.d.ts +1 -1
  175. package/dist/factories/helm/types.d.ts.map +1 -1
  176. package/dist/factories/shared.d.ts.map +1 -1
  177. package/dist/factories/shared.js +21 -1
  178. package/dist/factories/shared.js.map +1 -1
  179. package/dist/factories/simple/index.d.ts +2 -2
  180. package/dist/factories/simple/index.d.ts.map +1 -1
  181. package/dist/factories/simple/workloads/deployment.d.ts +3 -3
  182. package/dist/factories/simple/workloads/deployment.d.ts.map +1 -1
  183. package/dist/factories/simple/workloads/deployment.js +37 -11
  184. package/dist/factories/simple/workloads/deployment.js.map +1 -1
  185. package/dist/index.d.ts +1 -1
  186. package/dist/index.d.ts.map +1 -1
  187. package/dist/index.js +1 -1
  188. package/dist/index.js.map +1 -1
  189. package/dist/utils/index.d.ts +1 -1
  190. package/dist/utils/index.d.ts.map +1 -1
  191. package/dist/utils/index.js +1 -1
  192. package/dist/utils/index.js.map +1 -1
  193. package/dist/utils/type-guards.d.ts +6 -0
  194. package/dist/utils/type-guards.d.ts.map +1 -1
  195. package/dist/utils/type-guards.js +25 -2
  196. package/dist/utils/type-guards.js.map +1 -1
  197. package/package.json +6 -1
@@ -0,0 +1,2956 @@
1
+ /**
2
+ * JavaScript to CEL Expression Analyzer
3
+ *
4
+ * This module provides the core functionality for detecting KubernetesRef objects
5
+ * in JavaScript expressions and converting them to appropriate CEL expressions.
6
+ *
7
+ * The analyzer works with TypeKro's magic proxy system where schema.spec.name and
8
+ * resources.database.status.podIP return KubernetesRef objects at runtime.
9
+ */
10
+ import * as esprima from 'esprima';
11
+ import * as estraverse from 'estraverse';
12
+ import { containsKubernetesRefs, extractResourceReferences, isKubernetesRef } from '../../utils/type-guards.js';
13
+ import { CEL_EXPRESSION_BRAND, KUBERNETES_REF_BRAND } from '../constants/brands.js';
14
+ import { ConversionError } from '../errors.js';
15
+ import { SourceMapUtils } from './source-map.js';
16
+ import { ExpressionCache } from './cache.js';
17
+ import { handleExpressionWithFactoryPattern } from './factory-pattern-handler.js';
18
+ import { ExpressionTypeValidator, TypeRegistry, TypeSafetyUtils } from './type-safety.js';
19
+ import { CelTypeInferenceEngine } from './type-inference.js';
20
+ import { ResourceReferenceValidator } from './resource-validation.js';
21
+ import { CompileTimeTypeChecker } from './compile-time-validation.js';
22
+ /**
23
+ * Main analyzer class for JavaScript to CEL expression conversion
24
+ */
25
+ export class JavaScriptToCelAnalyzer {
26
+ cache;
27
+ typeValidator = new ExpressionTypeValidator();
28
+ enableMetrics;
29
+ constructor(cacheOptions) {
30
+ this.cache = new ExpressionCache(cacheOptions);
31
+ this.enableMetrics = cacheOptions?.enableMetrics ?? true;
32
+ }
33
+ typeInferenceEngine = new CelTypeInferenceEngine();
34
+ resourceValidator = new ResourceReferenceValidator();
35
+ compileTimeChecker = new CompileTimeTypeChecker();
36
+ /**
37
+ * Analyze any expression type and convert to CEL if needed
38
+ */
39
+ analyzeExpression(expression, context) {
40
+ // Handle different expression types
41
+ if (typeof expression === 'string') {
42
+ return this.analyzeStringExpression(expression, context);
43
+ }
44
+ // Handle KubernetesRef objects directly
45
+ if (expression && typeof expression === 'object' && expression[KUBERNETES_REF_BRAND]) {
46
+ return this.analyzeKubernetesRefObject(expression, context);
47
+ }
48
+ // Handle other objects
49
+ if (typeof expression === 'object' && expression !== null) {
50
+ return this.analyzeObjectExpression(expression, context);
51
+ }
52
+ // Handle primitives
53
+ return this.analyzePrimitiveExpression(expression, context);
54
+ }
55
+ /**
56
+ * Analyze a JavaScript string expression and convert to CEL if it contains KubernetesRef objects
57
+ */
58
+ analyzeStringExpression(expression, context) {
59
+ // Check cache first
60
+ const cached = this.cache.get(expression, context);
61
+ if (cached)
62
+ return cached;
63
+ try {
64
+ // Handle modern JavaScript syntax that esprima 4.0.1 doesn't support
65
+ const preprocessedExpression = this.preprocessModernSyntax(expression);
66
+ // Parse JavaScript expression to AST with location tracking
67
+ // Try parsing as expression first, then fall back to wrapping in parentheses
68
+ let ast;
69
+ let exprNode;
70
+ try {
71
+ // Try parsing as a standalone expression first
72
+ ast = esprima.parseScript(preprocessedExpression, {
73
+ ecmaVersion: 2020,
74
+ sourceType: 'script',
75
+ loc: true,
76
+ range: true
77
+ });
78
+ exprNode = ast.body[0];
79
+ // If it's not an expression statement, we need to wrap it
80
+ if (exprNode.type !== 'ExpressionStatement') {
81
+ throw new Error('Not an expression statement');
82
+ }
83
+ exprNode = exprNode.expression;
84
+ }
85
+ catch (firstError) {
86
+ // Fall back to wrapping in parentheses
87
+ try {
88
+ ast = esprima.parseScript(`(${preprocessedExpression})`, {
89
+ ecmaVersion: 2020,
90
+ sourceType: 'script',
91
+ loc: true,
92
+ range: true
93
+ });
94
+ exprNode = ast.body[0].expression;
95
+ }
96
+ catch (_secondError) {
97
+ // Use the original error message from the first attempt
98
+ throw firstError;
99
+ }
100
+ }
101
+ // Create source location from AST
102
+ const sourceLocation = SourceMapUtils.createSourceLocation(exprNode.loc, expression);
103
+ // Initialize dependencies array if not provided
104
+ if (!context.dependencies) {
105
+ context.dependencies = [];
106
+ }
107
+ // Convert to CEL with source tracking (this will extract dependencies through AST analysis)
108
+ const celExpression = this.convertASTNodeWithSourceTracking(exprNode, context, expression, sourceLocation);
109
+ // Add source mapping entry
110
+ const sourceMapEntries = [];
111
+ if (context.sourceMap) {
112
+ const _mappingId = context.sourceMap.addMapping(expression, celExpression.expression, sourceLocation, context.type, {
113
+ expressionType: SourceMapUtils.determineExpressionType(exprNode.type),
114
+ kubernetesRefs: SourceMapUtils.extractKubernetesRefPaths(celExpression.expression),
115
+ dependencies: context.dependencies?.map(dep => `${dep.resourceId}.${dep.fieldPath}`) || [],
116
+ conversionNotes: [`Converted from ${exprNode.type} AST node`]
117
+ });
118
+ sourceMapEntries.push(...context.sourceMap.getEntries());
119
+ }
120
+ // Perform type validation and inference if enabled
121
+ let typeValidation;
122
+ let inferredType;
123
+ let resourceValidation;
124
+ if (context.strictTypeChecking !== false && context.typeRegistry) {
125
+ const availableTypes = context.typeRegistry.getAvailableTypes();
126
+ typeValidation = this.typeValidator.validateExpression(expression, availableTypes, context.expectedType);
127
+ inferredType = typeValidation.resultType;
128
+ // Also perform CEL type inference
129
+ const celTypeInference = this.inferCelExpressionType(celExpression, context);
130
+ if (celTypeInference.success && !inferredType) {
131
+ inferredType = celTypeInference.resultType;
132
+ }
133
+ }
134
+ // Validate resource references if enabled
135
+ if (context.validateResourceReferences !== false && context.dependencies) {
136
+ resourceValidation = this.validateResourceReferences(context.dependencies, context.availableReferences, context.schemaProxy, context.validationContext);
137
+ }
138
+ // Perform compile-time type checking if enabled
139
+ let compileTimeValidation;
140
+ if (context.compileTimeTypeChecking !== false && context.compileTimeContext) {
141
+ compileTimeValidation = this.performCompileTimeValidation(expression, context.compileTimeContext);
142
+ }
143
+ // Only treat compile-time and type validation errors as critical
144
+ // Resource validation errors should be warnings, not critical errors
145
+ const hasCompileTimeErrors = compileTimeValidation && !compileTimeValidation.valid;
146
+ const hasTypeValidationErrors = typeValidation && !typeValidation.valid;
147
+ // Collect only critical validation errors that should affect validity
148
+ const criticalErrors = [];
149
+ if (compileTimeValidation?.errors) {
150
+ criticalErrors.push(...compileTimeValidation.errors);
151
+ }
152
+ if (typeValidation?.errors) {
153
+ criticalErrors.push(...typeValidation.errors);
154
+ }
155
+ // Resource validation errors are treated as warnings, not critical errors
156
+ // Aggregate warnings from all validation results
157
+ const aggregatedWarnings = [];
158
+ // Add resource validation warnings and errors (treat errors as warnings)
159
+ if (resourceValidation) {
160
+ for (const rv of resourceValidation) {
161
+ // Add warnings
162
+ for (const warning of rv.warnings) {
163
+ const warningObj = {
164
+ message: warning.message,
165
+ type: warning.warningType
166
+ };
167
+ if (rv.suggestions.length > 0) {
168
+ warningObj.suggestion = rv.suggestions.join('; ');
169
+ }
170
+ aggregatedWarnings.push(warningObj);
171
+ }
172
+ // Add errors as warnings (resource validation errors shouldn't fail the entire expression)
173
+ if (rv.errors) {
174
+ for (const error of rv.errors) {
175
+ const warningObj = {
176
+ message: error instanceof Error ? error.message : String(error),
177
+ type: 'resource_validation'
178
+ };
179
+ if (rv.suggestions.length > 0) {
180
+ warningObj.suggestion = rv.suggestions.join('; ');
181
+ }
182
+ aggregatedWarnings.push(warningObj);
183
+ }
184
+ }
185
+ }
186
+ }
187
+ // Add type validation warnings (if any)
188
+ if (typeValidation?.warnings) {
189
+ for (const warning of typeValidation.warnings) {
190
+ aggregatedWarnings.push({
191
+ message: warning.message,
192
+ type: 'type_validation'
193
+ // No suggestion property for TypeValidationWarning
194
+ });
195
+ }
196
+ }
197
+ // Add compile-time validation warnings (if any)
198
+ if (compileTimeValidation?.warnings) {
199
+ for (const warning of compileTimeValidation.warnings) {
200
+ aggregatedWarnings.push({
201
+ message: warning.message,
202
+ type: 'compile_time'
203
+ // No suggestion property for CompileTimeWarning
204
+ });
205
+ }
206
+ }
207
+ const result = {
208
+ valid: celExpression !== null && !hasCompileTimeErrors && !hasTypeValidationErrors,
209
+ celExpression,
210
+ dependencies: context.dependencies || [],
211
+ sourceMap: sourceMapEntries,
212
+ errors: criticalErrors,
213
+ requiresConversion: (context.dependencies || []).length > 0, // Only requires conversion if there are KubernetesRef dependencies
214
+ typeValidation,
215
+ inferredType,
216
+ resourceValidation,
217
+ compileTimeValidation,
218
+ warnings: aggregatedWarnings
219
+ };
220
+ // Cache the result
221
+ this.cache.set(expression, context, result);
222
+ return result;
223
+ }
224
+ catch (error) {
225
+ // If parsing fails, try to handle it as a special case
226
+ const specialCaseResult = this.handleSpecialCases(expression, context);
227
+ if (specialCaseResult) {
228
+ // Only cache successful special case results
229
+ if (specialCaseResult.valid) {
230
+ this.cache.set(expression, context, specialCaseResult);
231
+ }
232
+ return specialCaseResult;
233
+ }
234
+ // Create detailed error with source location
235
+ const sourceLocation = { line: 1, column: 1, length: expression.length };
236
+ const conversionError = ConversionError.forParsingFailure(expression, error instanceof Error ? error.message : String(error), sourceLocation, error instanceof Error ? error : undefined);
237
+ const errorResult = {
238
+ valid: false,
239
+ celExpression: null,
240
+ dependencies: [],
241
+ sourceMap: [],
242
+ errors: [conversionError],
243
+ requiresConversion: false,
244
+ warnings: []
245
+ };
246
+ // Don't cache error results to allow retry
247
+ return errorResult;
248
+ }
249
+ }
250
+ /**
251
+ * Analyze a KubernetesRef object directly
252
+ */
253
+ analyzeKubernetesRefObject(ref, context) {
254
+ // Use the proper CEL path format
255
+ const resourceId = ref.resourceId === '__schema__' ? 'schema' : ref.resourceId;
256
+ const celPath = `${resourceId}.${ref.fieldPath}`;
257
+ // Add to dependencies
258
+ if (!context.dependencies) {
259
+ context.dependencies = [];
260
+ }
261
+ context.dependencies.push(ref);
262
+ return {
263
+ valid: true,
264
+ celExpression: {
265
+ [CEL_EXPRESSION_BRAND]: true,
266
+ expression: celPath,
267
+ _type: ref._type
268
+ },
269
+ dependencies: [ref],
270
+ sourceMap: [],
271
+ errors: [],
272
+ warnings: [],
273
+ requiresConversion: true
274
+ };
275
+ }
276
+ /**
277
+ * Analyze object expression by examining its structure
278
+ */
279
+ analyzeObjectExpression(obj, context) {
280
+ const kubernetesRefs = [];
281
+ // Recursively examine object properties for KubernetesRef objects
282
+ this.extractKubernetesRefsFromObject(obj, kubernetesRefs, '');
283
+ // Add to dependencies
284
+ if (!context.dependencies) {
285
+ context.dependencies = [];
286
+ }
287
+ context.dependencies.push(...kubernetesRefs);
288
+ return {
289
+ valid: true,
290
+ celExpression: null, // Objects don't convert to single CEL expressions
291
+ dependencies: kubernetesRefs,
292
+ sourceMap: [],
293
+ errors: [],
294
+ warnings: [],
295
+ requiresConversion: kubernetesRefs.length > 0
296
+ };
297
+ }
298
+ /**
299
+ * Analyze primitive expression (no KubernetesRef objects)
300
+ */
301
+ analyzePrimitiveExpression(_value, _context) {
302
+ return {
303
+ valid: true,
304
+ celExpression: null,
305
+ dependencies: [],
306
+ sourceMap: [],
307
+ errors: [],
308
+ warnings: [],
309
+ requiresConversion: false
310
+ };
311
+ }
312
+ /**
313
+ * Extract KubernetesRef objects from object structure
314
+ */
315
+ extractKubernetesRefsFromObject(obj, refs, path) {
316
+ if (!obj || typeof obj !== 'object')
317
+ return;
318
+ // Check if this object is a KubernetesRef
319
+ if (obj[KUBERNETES_REF_BRAND]) {
320
+ refs.push(obj);
321
+ return;
322
+ }
323
+ // Recursively check properties
324
+ for (const [key, value] of Object.entries(obj)) {
325
+ const newPath = path ? `${path}.${key}` : key;
326
+ this.extractKubernetesRefsFromObject(value, refs, newPath);
327
+ }
328
+ }
329
+ /**
330
+ * NEW: Analyze expressions that may contain KubernetesRef objects from magic proxy system
331
+ * This is the key method that detects when JavaScript expressions contain KubernetesRef objects
332
+ */
333
+ analyzeExpressionWithRefs(expression, // Could be a JavaScript expression or contain KubernetesRef objects
334
+ context) {
335
+ try {
336
+ // First check if this is a static value (no KubernetesRef objects)
337
+ if (this.isStaticValue(expression)) {
338
+ // Static values don't need conversion - preserve them as-is for performance
339
+ return this.createStaticValueResult(expression);
340
+ }
341
+ // Check if the expression contains KubernetesRef objects
342
+ if (!containsKubernetesRefs(expression)) {
343
+ // No KubernetesRef objects found - return as-is (no conversion needed)
344
+ return this.createStaticValueResult(expression);
345
+ }
346
+ // Expression contains KubernetesRef objects - needs conversion
347
+ if (typeof expression === 'string') {
348
+ // String expression - parse and convert
349
+ return this.analyzeExpression(expression, context);
350
+ }
351
+ if (typeof expression === 'function') {
352
+ // Function expression - analyze function body
353
+ return this.analyzeFunction(expression, context);
354
+ }
355
+ // Direct KubernetesRef object
356
+ if (isKubernetesRef(expression)) {
357
+ return this.convertKubernetesRefToResult(expression, context);
358
+ }
359
+ // Template literal or complex expression
360
+ if (this.isTemplateLiteral(expression)) {
361
+ return this.analyzeTemplateLiteral(expression, context);
362
+ }
363
+ // Complex object/array containing KubernetesRef objects
364
+ return this.analyzeComplexValue(expression, context);
365
+ }
366
+ catch (error) {
367
+ return {
368
+ valid: false,
369
+ celExpression: null,
370
+ dependencies: [],
371
+ sourceMap: [],
372
+ errors: [new ConversionError(`Failed to analyze expression with refs: ${error instanceof Error ? error.message : String(error)}`, String(expression), 'javascript')],
373
+ requiresConversion: false,
374
+ warnings: []
375
+ };
376
+ }
377
+ }
378
+ /**
379
+ * Convert an AST node to CEL expression
380
+ */
381
+ convertASTNode(node, context) {
382
+ switch (node.type) {
383
+ case 'BinaryExpression':
384
+ return this.convertBinaryExpression(node, context);
385
+ case 'MemberExpression':
386
+ return this.convertMemberExpression(node, context);
387
+ case 'ConditionalExpression':
388
+ return this.convertConditionalExpression(node, context);
389
+ case 'LogicalExpression':
390
+ return this.convertLogicalExpression(node, context);
391
+ case 'ChainExpression':
392
+ return this.convertOptionalChaining(node, context);
393
+ case 'TemplateLiteral':
394
+ return this.convertTemplateLiteral(node, context);
395
+ case 'Literal':
396
+ return this.convertLiteral(node, context);
397
+ case 'CallExpression':
398
+ return this.convertCallExpression(node, context);
399
+ case 'ArrayExpression':
400
+ return this.convertArrayExpression(node, context);
401
+ case 'Identifier':
402
+ return this.convertIdentifier(node, context);
403
+ case 'UnaryExpression':
404
+ return this.convertUnaryExpression(node, context);
405
+ default:
406
+ throw new Error(`Unsupported expression type: ${node.type}`);
407
+ }
408
+ }
409
+ /**
410
+ * Convert an AST node to CEL expression with source location tracking
411
+ */
412
+ convertASTNodeWithSourceTracking(node, context, originalExpression, sourceLocation) {
413
+ try {
414
+ const celExpression = this.convertASTNode(node, context);
415
+ // Add source mapping if builder is available
416
+ if (context.sourceMap) {
417
+ context.sourceMap.addMapping(originalExpression, celExpression.expression, sourceLocation, context.type, {
418
+ expressionType: SourceMapUtils.determineExpressionType(node.type),
419
+ kubernetesRefs: SourceMapUtils.extractKubernetesRefPaths(celExpression.expression),
420
+ dependencies: context.dependencies?.map(dep => `${dep.resourceId}.${dep.fieldPath}`) || [],
421
+ conversionNotes: [`Converted ${node.type} at line ${sourceLocation.line}, column ${sourceLocation.column}`]
422
+ });
423
+ }
424
+ return celExpression;
425
+ }
426
+ catch (_error) {
427
+ // Create detailed conversion error with source location
428
+ const conversionError = ConversionError.forUnsupportedSyntax(originalExpression, node.type, sourceLocation, [`The ${node.type} syntax is not supported in this context`]);
429
+ throw conversionError;
430
+ }
431
+ }
432
+ /**
433
+ * Analyze a function for JavaScript expressions containing KubernetesRef objects
434
+ */
435
+ analyzeFunction(fn, _context) {
436
+ try {
437
+ // Parse function to AST
438
+ const ast = esprima.parseScript(fn.toString());
439
+ // Find return statement
440
+ const returnStatement = this.findReturnStatement(ast);
441
+ if (!returnStatement) {
442
+ throw new Error('Function must have a return statement for analysis');
443
+ }
444
+ // For now, return a placeholder result
445
+ return {
446
+ valid: false,
447
+ celExpression: {
448
+ expression: '/* TODO: Analyze function body */',
449
+ _type: undefined
450
+ },
451
+ dependencies: [],
452
+ sourceMap: [],
453
+ errors: [],
454
+ warnings: [],
455
+ requiresConversion: true
456
+ };
457
+ }
458
+ catch (error) {
459
+ return {
460
+ valid: false,
461
+ celExpression: null,
462
+ dependencies: [],
463
+ sourceMap: [],
464
+ errors: [new ConversionError(error instanceof Error ? error.message : String(error), fn.toString(), 'function-call')],
465
+ warnings: [],
466
+ requiresConversion: false
467
+ };
468
+ }
469
+ }
470
+ /**
471
+ * Convert a single KubernetesRef to a conversion result
472
+ */
473
+ convertKubernetesRefToResult(ref, context) {
474
+ try {
475
+ // Use the dedicated KubernetesRef to CEL conversion method
476
+ const celExpression = this.convertKubernetesRefToCel(ref, context);
477
+ const originalExpression = `${ref.resourceId}.${ref.fieldPath}`;
478
+ // Create source location for the KubernetesRef
479
+ const sourceLocation = {
480
+ line: 1,
481
+ column: 1,
482
+ length: originalExpression.length
483
+ };
484
+ // Add source mapping
485
+ const sourceMapEntries = [];
486
+ if (context.sourceMap) {
487
+ context.sourceMap.addMapping(originalExpression, celExpression.expression, sourceLocation, context.type, {
488
+ expressionType: 'member-access',
489
+ kubernetesRefs: [originalExpression],
490
+ dependencies: [`${ref.resourceId}.${ref.fieldPath}`],
491
+ conversionNotes: ['Direct KubernetesRef to CEL conversion']
492
+ });
493
+ sourceMapEntries.push(...context.sourceMap.getEntries());
494
+ }
495
+ return {
496
+ valid: true,
497
+ celExpression,
498
+ dependencies: [ref],
499
+ sourceMap: sourceMapEntries,
500
+ errors: [],
501
+ warnings: [],
502
+ requiresConversion: true
503
+ };
504
+ }
505
+ catch (_error) {
506
+ const originalExpression = `${ref.resourceId}.${ref.fieldPath}`;
507
+ const sourceLocation = { line: 1, column: 1, length: originalExpression.length };
508
+ const conversionError = ConversionError.forKubernetesRefResolution(originalExpression, originalExpression, Object.keys(context.availableReferences || {}), sourceLocation);
509
+ return {
510
+ valid: false,
511
+ celExpression: null,
512
+ dependencies: [ref],
513
+ sourceMap: [],
514
+ errors: [conversionError],
515
+ warnings: [],
516
+ requiresConversion: true
517
+ };
518
+ }
519
+ }
520
+ /**
521
+ * Analyze complex values (objects/arrays) that may contain KubernetesRef objects
522
+ */
523
+ analyzeComplexValue(value, context) {
524
+ const dependencies = [];
525
+ const errors = [];
526
+ try {
527
+ // Recursively find all KubernetesRef objects
528
+ this.extractKubernetesRefs(value, dependencies);
529
+ if (dependencies.length === 0) {
530
+ return {
531
+ valid: false,
532
+ celExpression: null,
533
+ dependencies: [],
534
+ sourceMap: [],
535
+ errors: [],
536
+ warnings: [],
537
+ requiresConversion: false
538
+ };
539
+ }
540
+ // For complex values, we'll need to analyze the structure
541
+ // This is a placeholder implementation - will be expanded in later tasks
542
+ const originalExpression = JSON.stringify(value, null, 2);
543
+ const celExpression = {
544
+ [CEL_EXPRESSION_BRAND]: true,
545
+ expression: `/* TODO: Convert complex value with ${dependencies.length} references */`,
546
+ _type: undefined
547
+ };
548
+ // Create source location for the complex value
549
+ const sourceLocation = {
550
+ line: 1,
551
+ column: 1,
552
+ length: originalExpression.length
553
+ };
554
+ // Add source mapping
555
+ const sourceMapEntries = [];
556
+ if (context.sourceMap) {
557
+ context.sourceMap.addMapping(originalExpression, celExpression.expression, sourceLocation, context.type, {
558
+ expressionType: 'javascript',
559
+ kubernetesRefs: dependencies.map(dep => `${dep.resourceId}.${dep.fieldPath}`),
560
+ dependencies: dependencies.map(dep => `${dep.resourceId}.${dep.fieldPath}`),
561
+ conversionNotes: [`Complex value with ${dependencies.length} KubernetesRef objects`]
562
+ });
563
+ sourceMapEntries.push(...context.sourceMap.getEntries());
564
+ }
565
+ return {
566
+ valid: true,
567
+ celExpression,
568
+ dependencies,
569
+ sourceMap: sourceMapEntries,
570
+ errors,
571
+ warnings: [],
572
+ requiresConversion: true
573
+ };
574
+ }
575
+ catch (error) {
576
+ const originalExpression = JSON.stringify(value);
577
+ const sourceLocation = { line: 1, column: 1, length: originalExpression.length };
578
+ const conversionError = ConversionError.forParsingFailure(originalExpression, error instanceof Error ? error.message : String(error), sourceLocation, error instanceof Error ? error : undefined);
579
+ errors.push(conversionError);
580
+ return {
581
+ valid: false,
582
+ celExpression: null,
583
+ dependencies,
584
+ sourceMap: [],
585
+ errors,
586
+ warnings: [],
587
+ requiresConversion: true
588
+ };
589
+ }
590
+ }
591
+ /**
592
+ * Generate CEL expression from KubernetesRef based on context
593
+ * This handles the core KubernetesRef to CEL field path conversion (resourceId.fieldPath)
594
+ */
595
+ generateCelFromKubernetesRef(ref, context) {
596
+ // Validate the KubernetesRef
597
+ if (!ref.resourceId || !ref.fieldPath) {
598
+ throw new Error(`Invalid KubernetesRef: missing resourceId or fieldPath`);
599
+ }
600
+ // Generate appropriate CEL expression based on factory type and resource type
601
+ if (context.factoryType === 'kro') {
602
+ // For Kro factory, generate CEL expressions for runtime evaluation by Kro controller
603
+ if (ref.resourceId === '__schema__') {
604
+ // Schema references: schema.spec.name, schema.status.ready
605
+ return `schema.${ref.fieldPath}`;
606
+ }
607
+ else {
608
+ // Resource references: resources.database.status.podIP
609
+ return `resources.${ref.resourceId}.${ref.fieldPath}`;
610
+ }
611
+ }
612
+ else {
613
+ // For direct factory, generate CEL expressions that will be resolved at deployment time
614
+ // The direct factory will resolve these before deployment
615
+ if (ref.resourceId === '__schema__') {
616
+ // Schema references are resolved from the schema proxy
617
+ return `schema.${ref.fieldPath}`;
618
+ }
619
+ else {
620
+ // Resource references are resolved from the available resources
621
+ return `resources.${ref.resourceId}.${ref.fieldPath}`;
622
+ }
623
+ }
624
+ }
625
+ /**
626
+ * Convert a KubernetesRef directly to a CEL expression
627
+ * This is the main method for KubernetesRef to CEL field path conversion
628
+ */
629
+ convertKubernetesRefToCel(ref, context) {
630
+ try {
631
+ // Validate KubernetesRef types if type checking is enabled
632
+ if (context.strictTypeChecking !== false && context.typeRegistry) {
633
+ const validation = this.typeValidator.validateKubernetesRef(ref, context.availableReferences, context.schemaProxy);
634
+ if (!validation.valid) {
635
+ throw new ConversionError(`KubernetesRef type validation failed: ${validation.errors.map(e => e.message).join(', ')}`, `${ref.resourceId}.${ref.fieldPath}`, 'member-access');
636
+ }
637
+ }
638
+ const expression = this.generateCelFromKubernetesRef(ref, context);
639
+ // Track this KubernetesRef as a dependency
640
+ if (context.dependencies) {
641
+ context.dependencies.push(ref);
642
+ }
643
+ return {
644
+ [CEL_EXPRESSION_BRAND]: true,
645
+ expression,
646
+ _type: ref._type
647
+ };
648
+ }
649
+ catch (error) {
650
+ throw new ConversionError(`Failed to convert KubernetesRef to CEL: ${error instanceof Error ? error.message : String(error)}`, `${ref.resourceId}.${ref.fieldPath}`, 'member-access');
651
+ }
652
+ }
653
+ /**
654
+ * Find return statement in AST
655
+ */
656
+ findReturnStatement(ast) {
657
+ let returnStatement = null;
658
+ estraverse.traverse(ast, {
659
+ enter: (node) => {
660
+ if (node.type === 'ReturnStatement') {
661
+ returnStatement = node;
662
+ return estraverse.VisitorOption.Break;
663
+ }
664
+ return undefined; // Continue traversal
665
+ }
666
+ });
667
+ return returnStatement;
668
+ }
669
+ /**
670
+ * Check if a value is a template literal expression
671
+ * This checks for JavaScript template literal syntax in runtime values
672
+ */
673
+ isTemplateLiteral(value) {
674
+ // Check if it's a string that looks like a template literal
675
+ if (typeof value === 'string') {
676
+ // Look for template literal patterns like `text ${expression} more text`
677
+ return value.includes('${') && value.includes('}');
678
+ }
679
+ // Check if it's an object that represents a template literal structure
680
+ if (value && typeof value === 'object' && value.type === 'TemplateLiteral') {
681
+ return true;
682
+ }
683
+ return false;
684
+ }
685
+ /**
686
+ * Analyze template literal expressions containing KubernetesRef objects
687
+ * This handles runtime template literal values that contain KubernetesRef interpolations
688
+ */
689
+ analyzeTemplateLiteral(expression, context) {
690
+ try {
691
+ const dependencies = extractResourceReferences(expression);
692
+ const originalExpression = String(expression);
693
+ let celExpression;
694
+ if (typeof expression === 'string') {
695
+ // Handle string-based template literals
696
+ // For now, preserve the template literal structure
697
+ celExpression = {
698
+ [CEL_EXPRESSION_BRAND]: true,
699
+ expression: expression, // Keep the ${} syntax for CEL
700
+ _type: 'string'
701
+ };
702
+ }
703
+ else {
704
+ // Handle structured template literal objects
705
+ // This would be used when we have parsed template literal AST nodes
706
+ celExpression = {
707
+ [CEL_EXPRESSION_BRAND]: true,
708
+ expression: '/* Complex template literal */',
709
+ _type: 'string'
710
+ };
711
+ }
712
+ // Create source location for the template literal
713
+ const sourceLocation = {
714
+ line: 1,
715
+ column: 1,
716
+ length: originalExpression.length
717
+ };
718
+ // Add source mapping
719
+ const sourceMapEntries = [];
720
+ if (context.sourceMap) {
721
+ context.sourceMap.addMapping(originalExpression, celExpression.expression, sourceLocation, context.type, {
722
+ expressionType: 'template-literal',
723
+ kubernetesRefs: dependencies.map(dep => `${dep.resourceId}.${dep.fieldPath}`),
724
+ dependencies: dependencies.map(dep => `${dep.resourceId}.${dep.fieldPath}`),
725
+ conversionNotes: ['Template literal with KubernetesRef interpolations']
726
+ });
727
+ sourceMapEntries.push(...context.sourceMap.getEntries());
728
+ }
729
+ return {
730
+ valid: true,
731
+ celExpression,
732
+ dependencies,
733
+ sourceMap: sourceMapEntries,
734
+ errors: [],
735
+ warnings: [],
736
+ requiresConversion: true
737
+ };
738
+ }
739
+ catch (error) {
740
+ const originalExpression = String(expression);
741
+ const sourceLocation = { line: 1, column: 1, length: originalExpression.length };
742
+ const conversionError = ConversionError.forTemplateLiteral(originalExpression, [originalExpression], 0, sourceLocation, error instanceof Error ? error : undefined);
743
+ return {
744
+ valid: false,
745
+ celExpression: null,
746
+ dependencies: [],
747
+ sourceMap: [],
748
+ errors: [conversionError],
749
+ warnings: [],
750
+ requiresConversion: true
751
+ };
752
+ }
753
+ }
754
+ /**
755
+ * Extract all KubernetesRef objects from a complex value
756
+ */
757
+ extractKubernetesRefs(value, refs) {
758
+ const extractedRefs = extractResourceReferences(value);
759
+ refs.push(...extractedRefs);
760
+ }
761
+ /**
762
+ * Handle special cases for expressions that can't be parsed normally
763
+ */
764
+ handleSpecialCases(expression, context) {
765
+ // Handle expressions with both optional chaining and nullish coalescing
766
+ if (expression.includes('?.') && expression.includes('??')) {
767
+ return this.handleMixedOptionalAndNullishExpression(expression, context);
768
+ }
769
+ // Handle optional chaining expressions
770
+ if (expression.includes('?.')) {
771
+ return this.handleOptionalChainingExpression(expression, context);
772
+ }
773
+ // Handle nullish coalescing expressions
774
+ if (expression.includes('??')) {
775
+ return this.handleNullishCoalescingExpression(expression, context);
776
+ }
777
+ // Handle simple resource references that might not parse as valid JavaScript
778
+ if (expression.match(/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$/)) {
779
+ // This looks like a simple property access path
780
+ try {
781
+ // Initialize dependencies array if not provided
782
+ if (!context.dependencies) {
783
+ context.dependencies = [];
784
+ }
785
+ // Extract dependencies from the expression
786
+ this.extractDependenciesFromExpression(expression, context);
787
+ const celExpression = {
788
+ [CEL_EXPRESSION_BRAND]: true,
789
+ expression: expression,
790
+ _type: undefined
791
+ };
792
+ const sourceLocation = { line: 1, column: 1, length: expression.length };
793
+ const sourceMapEntries = [];
794
+ if (context.sourceMap) {
795
+ context.sourceMap.addMapping(expression, expression, sourceLocation, context.type, {
796
+ expressionType: 'member-access',
797
+ kubernetesRefs: this.extractResourceReferencesFromExpression(expression),
798
+ dependencies: context.dependencies?.map(dep => `${dep.resourceId}.${dep.fieldPath}`) || [],
799
+ conversionNotes: ['Simple property access path']
800
+ });
801
+ sourceMapEntries.push(...context.sourceMap.getEntries());
802
+ }
803
+ // Perform resource validation if enabled
804
+ let resourceValidation;
805
+ if (context.validateResourceReferences !== false && context.dependencies && context.dependencies.length > 0) {
806
+ resourceValidation = this.validateResourceReferences(context.dependencies, context.availableReferences, context.schemaProxy, context.validationContext);
807
+ }
808
+ // Extract errors from resource validation
809
+ const errors = [];
810
+ if (resourceValidation) {
811
+ for (const validation of resourceValidation) {
812
+ for (const error of validation.errors) {
813
+ errors.push(new ConversionError(error.message, expression, 'member-access'));
814
+ }
815
+ }
816
+ }
817
+ return {
818
+ valid: errors.length === 0,
819
+ celExpression,
820
+ dependencies: context.dependencies || [],
821
+ sourceMap: sourceMapEntries,
822
+ errors,
823
+ warnings: [],
824
+ requiresConversion: true,
825
+ resourceValidation
826
+ };
827
+ }
828
+ catch (_error) {
829
+ // Fall through to return null
830
+ }
831
+ }
832
+ return null;
833
+ }
834
+ /**
835
+ * Handle optional chaining expressions that can't be parsed by esprima
836
+ */
837
+ handleOptionalChainingExpression(expression, context) {
838
+ try {
839
+ // First, validate that the expression is syntactically valid JavaScript
840
+ // even if esprima doesn't support optional chaining
841
+ // We'll do a basic syntax check by removing the optional chaining and parsing
842
+ const withoutOptionalChaining = expression
843
+ .replace(/\?\.\[/g, '[') // Replace ?.[ with [
844
+ .replace(/\?\.\(/g, '(') // Replace ?.( with (
845
+ .replace(/\?\./g, '.'); // Replace ?. with .
846
+ try {
847
+ // Try to parse the expression without optional chaining to validate basic syntax
848
+ esprima.parseScript(`(${withoutOptionalChaining})`);
849
+ }
850
+ catch (syntaxError) {
851
+ // If it fails to parse even without optional chaining, it has syntax errors
852
+ throw new ConversionError(`Invalid JavaScript syntax in optional chaining expression: ${syntaxError instanceof Error ? syntaxError.message : String(syntaxError)}`, expression, 'optional-chaining');
853
+ }
854
+ // Convert optional chaining to CEL-compatible syntax
855
+ // deployment?.status?.readyReplicas -> deployment?.status?.readyReplicas
856
+ const celExpression = {
857
+ [CEL_EXPRESSION_BRAND]: true,
858
+ expression: expression, // Keep the ?. syntax as CEL supports it
859
+ _type: undefined
860
+ };
861
+ const sourceLocation = { line: 1, column: 1, length: expression.length };
862
+ const sourceMapEntries = [];
863
+ // Extract dependencies from the optional chaining expression
864
+ const dependencies = this.extractDependenciesFromExpressionString(expression, context);
865
+ if (context.sourceMap) {
866
+ context.sourceMap.addMapping(expression, expression, sourceLocation, context.type, {
867
+ expressionType: 'optional-chaining',
868
+ kubernetesRefs: this.extractResourceReferencesFromExpression(expression),
869
+ dependencies: dependencies.map(dep => `${dep.resourceId}.${dep.fieldPath}`),
870
+ conversionNotes: ['Optional chaining expression']
871
+ });
872
+ sourceMapEntries.push(...context.sourceMap.getEntries());
873
+ }
874
+ return {
875
+ valid: true,
876
+ celExpression,
877
+ dependencies,
878
+ sourceMap: sourceMapEntries,
879
+ errors: [],
880
+ warnings: [],
881
+ requiresConversion: true
882
+ };
883
+ }
884
+ catch (error) {
885
+ return {
886
+ valid: false,
887
+ celExpression: null,
888
+ dependencies: [],
889
+ sourceMap: [],
890
+ errors: [new ConversionError(`Failed to handle optional chaining: ${error instanceof Error ? error.message : String(error)}`, expression, 'optional-chaining')],
891
+ warnings: [],
892
+ requiresConversion: true
893
+ };
894
+ }
895
+ }
896
+ /**
897
+ * Handle expressions with both optional chaining and nullish coalescing
898
+ */
899
+ handleMixedOptionalAndNullishExpression(expression, context) {
900
+ try {
901
+ // For mixed expressions, we'll convert them to a CEL expression that handles both
902
+ // Optional chaining and nullish coalescing together
903
+ // Example: deployment.status?.readyReplicas ?? deployment.spec?.replicas ?? 1
904
+ // Becomes: deployment.status?.readyReplicas != null ? deployment.status?.readyReplicas : (deployment.spec?.replicas != null ? deployment.spec?.replicas : 1)
905
+ // Split by nullish coalescing operator
906
+ const parts = expression.split('??').map(part => part.trim());
907
+ if (parts.length < 2) {
908
+ throw new Error('Invalid mixed expression');
909
+ }
910
+ // Build nested conditional expression from right to left
911
+ let celExpression = parts[parts.length - 1] || ''; // Start with the last part (fallback)
912
+ for (let i = parts.length - 2; i >= 0; i--) {
913
+ const part = parts[i];
914
+ celExpression = `${part} != null ? ${part} : ${celExpression}`;
915
+ }
916
+ const result = {
917
+ [CEL_EXPRESSION_BRAND]: true,
918
+ expression: celExpression,
919
+ _type: undefined
920
+ };
921
+ // Extract dependencies from the mixed expression
922
+ const dependencies = this.extractDependenciesFromExpressionString(expression, context);
923
+ const sourceLocation = { line: 1, column: 1, length: expression.length };
924
+ const sourceMapEntries = [];
925
+ if (context.sourceMap) {
926
+ context.sourceMap.addMapping(expression, result.expression, sourceLocation, context.type, {
927
+ expressionType: 'optional-chaining',
928
+ kubernetesRefs: this.extractResourceReferencesFromExpression(expression),
929
+ dependencies: dependencies.map(dep => `${dep.resourceId}.${dep.fieldPath}`),
930
+ conversionNotes: ['Mixed optional chaining and nullish coalescing converted to nested conditionals']
931
+ });
932
+ sourceMapEntries.push(...context.sourceMap.getEntries());
933
+ }
934
+ return {
935
+ valid: true,
936
+ celExpression: result,
937
+ dependencies,
938
+ sourceMap: sourceMapEntries,
939
+ errors: [],
940
+ warnings: [],
941
+ requiresConversion: true
942
+ };
943
+ }
944
+ catch (error) {
945
+ return {
946
+ valid: false,
947
+ celExpression: null,
948
+ dependencies: [],
949
+ sourceMap: [],
950
+ errors: [new ConversionError(`Failed to handle mixed optional chaining and nullish coalescing: ${error instanceof Error ? error.message : String(error)}`, expression, 'optional-chaining')],
951
+ warnings: [],
952
+ requiresConversion: true
953
+ };
954
+ }
955
+ }
956
+ /**
957
+ * Handle nullish coalescing expressions that can't be parsed by esprima
958
+ */
959
+ handleNullishCoalescingExpression(expression, context) {
960
+ try {
961
+ // Convert nullish coalescing to CEL-compatible syntax
962
+ // deployment.status.readyReplicas ?? 0 -> deployment.status.readyReplicas != null ? deployment.status.readyReplicas : 0
963
+ const parts = expression.split('??').map(part => part.trim());
964
+ if (parts.length !== 2) {
965
+ throw new Error('Invalid nullish coalescing expression');
966
+ }
967
+ const [left, right] = parts;
968
+ const celExpression = {
969
+ [CEL_EXPRESSION_BRAND]: true,
970
+ expression: `${left} != null ? ${left} : ${right}`,
971
+ _type: undefined
972
+ };
973
+ const sourceLocation = { line: 1, column: 1, length: expression.length };
974
+ const sourceMapEntries = [];
975
+ if (context.sourceMap) {
976
+ context.sourceMap.addMapping(expression, celExpression.expression, sourceLocation, context.type, {
977
+ expressionType: 'nullish-coalescing',
978
+ kubernetesRefs: this.extractResourceReferencesFromExpression(expression),
979
+ dependencies: this.extractResourceReferencesFromExpression(expression),
980
+ conversionNotes: ['Nullish coalescing converted to conditional']
981
+ });
982
+ sourceMapEntries.push(...context.sourceMap.getEntries());
983
+ }
984
+ // Extract dependencies from the nullish coalescing expression
985
+ const dependencies = this.extractDependenciesFromExpressionString(expression, context);
986
+ return {
987
+ valid: true,
988
+ celExpression,
989
+ dependencies,
990
+ sourceMap: sourceMapEntries,
991
+ errors: [],
992
+ warnings: [],
993
+ requiresConversion: true
994
+ };
995
+ }
996
+ catch (error) {
997
+ return {
998
+ valid: false,
999
+ celExpression: null,
1000
+ dependencies: [],
1001
+ sourceMap: [],
1002
+ errors: [new ConversionError(`Failed to handle nullish coalescing: ${error instanceof Error ? error.message : String(error)}`, expression, 'nullish-coalescing')],
1003
+ warnings: [],
1004
+ requiresConversion: true
1005
+ };
1006
+ }
1007
+ }
1008
+ /**
1009
+ * Extract resource references from expression string
1010
+ */
1011
+ extractResourceReferencesFromExpression(expression) {
1012
+ const refs = [];
1013
+ // Look for patterns like deployment.status.readyReplicas or service?.status?.loadBalancer
1014
+ const resourcePattern = /([a-zA-Z_][a-zA-Z0-9_]*)\??\.([a-zA-Z_][a-zA-Z0-9_]*(?:\??\.?[a-zA-Z_][a-zA-Z0-9_]*)*)/g;
1015
+ let match = resourcePattern.exec(expression);
1016
+ while (match !== null) {
1017
+ refs.push(match[0].replace(/\?/g, '')); // Remove optional chaining operators for reference tracking
1018
+ match = resourcePattern.exec(expression);
1019
+ }
1020
+ return refs;
1021
+ }
1022
+ /**
1023
+ * Check if an expression is a resource reference
1024
+ */
1025
+ isResourceReference(expression) {
1026
+ // Check for explicit resource/schema prefixes
1027
+ if (expression.includes('resources.') || expression.includes('schema.')) {
1028
+ return true;
1029
+ }
1030
+ // Check if it starts with a known resource name (for direct references like deployment.status.field)
1031
+ const parts = expression.split('.');
1032
+ if (parts.length >= 2) {
1033
+ const resourceName = parts[0];
1034
+ // This is a heuristic - if it looks like a resource reference pattern
1035
+ return !!(resourceName && /^[a-zA-Z][a-zA-Z0-9-]*$/.test(resourceName) &&
1036
+ (parts[1] === 'status' || parts[1] === 'spec' || parts[1] === 'metadata'));
1037
+ }
1038
+ return false;
1039
+ }
1040
+ /**
1041
+ * Setup type registry from analysis context
1042
+ */
1043
+ setupTypeRegistry(context) {
1044
+ const registry = new TypeRegistry();
1045
+ // Register resource types
1046
+ for (const [resourceId, resource] of Object.entries(context.availableReferences)) {
1047
+ const resourceType = TypeSafetyUtils.fromEnhancedType(resource);
1048
+ registry.registerResourceType(resourceId, resourceType);
1049
+ }
1050
+ // Register basic types
1051
+ registry.registerType('string', { typeName: 'string', optional: false, nullable: false });
1052
+ registry.registerType('number', { typeName: 'number', optional: false, nullable: false });
1053
+ registry.registerType('boolean', { typeName: 'boolean', optional: false, nullable: false });
1054
+ registry.registerType('null', { typeName: 'null', optional: false, nullable: true });
1055
+ registry.registerType('undefined', { typeName: 'undefined', optional: true, nullable: false });
1056
+ return registry;
1057
+ }
1058
+ /**
1059
+ * Validate expression compatibility with target context
1060
+ */
1061
+ validateExpressionCompatibility(expression, context) {
1062
+ const registry = context.typeRegistry || this.setupTypeRegistry(context);
1063
+ const availableTypes = registry.getAvailableTypes();
1064
+ return this.typeValidator.validateExpression(expression, availableTypes, context.expectedType);
1065
+ }
1066
+ /**
1067
+ * Infer the type of a CEL expression
1068
+ */
1069
+ inferCelExpressionType(celExpression, context) {
1070
+ const inferenceContext = {
1071
+ availableResources: context.availableReferences,
1072
+ ...(context.schemaProxy && { schemaProxy: context.schemaProxy }),
1073
+ factoryType: context.factoryType
1074
+ };
1075
+ return this.typeInferenceEngine.inferType(celExpression, inferenceContext);
1076
+ }
1077
+ /**
1078
+ * Infer types for multiple CEL expressions
1079
+ */
1080
+ inferCelExpressionTypes(celExpressions, context) {
1081
+ const inferenceContext = {
1082
+ availableResources: context.availableReferences,
1083
+ ...(context.schemaProxy && { schemaProxy: context.schemaProxy }),
1084
+ factoryType: context.factoryType
1085
+ };
1086
+ return this.typeInferenceEngine.inferTypes(celExpressions, inferenceContext);
1087
+ }
1088
+ /**
1089
+ * Validate type compatibility between JavaScript and CEL expressions
1090
+ */
1091
+ validateJavaScriptToCelTypeCompatibility(jsExpression, celExpression, context) {
1092
+ // Get JavaScript expression type
1093
+ const jsValidation = this.validateExpressionCompatibility(jsExpression, context);
1094
+ if (!jsValidation.resultType) {
1095
+ return jsValidation;
1096
+ }
1097
+ // Get CEL expression type
1098
+ const celInference = this.inferCelExpressionType(celExpression, context);
1099
+ if (!celInference.success) {
1100
+ return {
1101
+ valid: false,
1102
+ errors: celInference.errors.map(e => ({
1103
+ message: e.message,
1104
+ expression: e.celExpression,
1105
+ expectedType: { typeName: 'unknown', optional: false, nullable: false },
1106
+ actualType: { typeName: 'unknown', optional: false, nullable: false }
1107
+ })),
1108
+ warnings: [],
1109
+ suggestions: []
1110
+ };
1111
+ }
1112
+ // Validate compatibility
1113
+ return this.typeInferenceEngine.validateTypeCompatibility(jsValidation.resultType, celInference.resultType);
1114
+ }
1115
+ /**
1116
+ * Validate resource references in KubernetesRef objects
1117
+ */
1118
+ validateResourceReferences(refs, availableResources, schemaProxy, validationContext) {
1119
+ return this.resourceValidator.validateKubernetesRefs(refs, availableResources, schemaProxy, validationContext);
1120
+ }
1121
+ /**
1122
+ * Validate a single resource reference
1123
+ */
1124
+ validateResourceReference(ref, availableResources, schemaProxy, validationContext) {
1125
+ return this.resourceValidator.validateKubernetesRef(ref, availableResources, schemaProxy, validationContext);
1126
+ }
1127
+ /**
1128
+ * Validate a reference chain for type safety and circular dependencies
1129
+ */
1130
+ validateReferenceChain(refs, availableResources, schemaProxy) {
1131
+ return this.resourceValidator.validateReferenceChain(refs, availableResources, schemaProxy);
1132
+ }
1133
+ /**
1134
+ * Get comprehensive validation report for an expression
1135
+ */
1136
+ getValidationReport(expression, context) {
1137
+ const conversionResult = this.analyzeExpression(expression, context);
1138
+ return {
1139
+ expression,
1140
+ conversionResult,
1141
+ ...(conversionResult.typeValidation && { typeValidation: conversionResult.typeValidation }),
1142
+ ...(conversionResult.resourceValidation && { resourceValidation: conversionResult.resourceValidation }),
1143
+ summary: this.createValidationSummary(conversionResult)
1144
+ };
1145
+ }
1146
+ /**
1147
+ * Perform compile-time type checking
1148
+ */
1149
+ performCompileTimeValidation(expression, context) {
1150
+ return this.compileTimeChecker.validateExpressionCompatibility(expression, context);
1151
+ }
1152
+ /**
1153
+ * Validate compile-time compatibility for multiple expressions
1154
+ */
1155
+ performCompileTimeValidationBatch(expressions, context) {
1156
+ return this.compileTimeChecker.validateExpressionsCompatibility(expressions, context);
1157
+ }
1158
+ /**
1159
+ * Validate KubernetesRef compile-time compatibility
1160
+ */
1161
+ validateKubernetesRefCompileTimeCompatibility(ref, context) {
1162
+ if (!context.compileTimeContext) {
1163
+ throw new Error('Compile-time context required for KubernetesRef validation');
1164
+ }
1165
+ const usageContext = {
1166
+ availableResources: context.availableReferences,
1167
+ ...(context.schemaProxy && { schemaProxy: context.schemaProxy }),
1168
+ usageType: 'property-access',
1169
+ ...(context.expectedType && {
1170
+ expectedResultType: {
1171
+ typeName: context.expectedType.typeName,
1172
+ isUnion: false,
1173
+ isGeneric: false,
1174
+ optional: context.expectedType.optional,
1175
+ nullable: context.expectedType.nullable,
1176
+ undefinable: context.expectedType.optional
1177
+ }
1178
+ })
1179
+ };
1180
+ return this.compileTimeChecker.validateKubernetesRefCompatibility(ref, usageContext, context.compileTimeContext);
1181
+ }
1182
+ /**
1183
+ * Get comprehensive compile-time validation report
1184
+ */
1185
+ getCompileTimeValidationReport(expression, context) {
1186
+ if (!context.compileTimeContext) {
1187
+ return null;
1188
+ }
1189
+ return this.performCompileTimeValidation(expression, context.compileTimeContext);
1190
+ }
1191
+ /**
1192
+ * Create a validation summary from conversion results
1193
+ */
1194
+ createValidationSummary(result) {
1195
+ const totalErrors = result.errors.length +
1196
+ (result.typeValidation?.errors.length || 0) +
1197
+ (result.resourceValidation?.reduce((sum, rv) => sum + rv.errors.length, 0) || 0) +
1198
+ (result.compileTimeValidation?.errors.length || 0);
1199
+ const totalWarnings = (result.typeValidation?.warnings.length || 0) +
1200
+ (result.resourceValidation?.reduce((sum, rv) => sum + rv.warnings.length, 0) || 0) +
1201
+ (result.compileTimeValidation?.warnings.length || 0);
1202
+ return {
1203
+ valid: result.celExpression !== null && totalErrors === 0,
1204
+ totalErrors,
1205
+ totalWarnings,
1206
+ requiresConversion: result.requiresConversion,
1207
+ hasTypeIssues: (result.typeValidation?.errors.length || 0) > 0,
1208
+ hasResourceIssues: (result.resourceValidation?.some(rv => !rv.valid)) || false,
1209
+ hasCompileTimeIssues: (result.compileTimeValidation && !result.compileTimeValidation.valid) || false,
1210
+ confidence: this.calculateOverallConfidence(result)
1211
+ };
1212
+ }
1213
+ /**
1214
+ * Extract dependencies from JavaScript expression string and return them
1215
+ */
1216
+ extractDependenciesFromExpressionString(expression, context) {
1217
+ const dependencies = [];
1218
+ // Look for direct resource references (deployment.status.field)
1219
+ if (context.availableReferences) {
1220
+ for (const [resourceKey, _resource] of Object.entries(context.availableReferences)) {
1221
+ const resourcePattern = new RegExp(`\\b${resourceKey}\\.([a-zA-Z0-9_.?\\[\\]]+)`, 'g');
1222
+ const matches = expression.match(resourcePattern);
1223
+ if (matches) {
1224
+ for (const match of matches) {
1225
+ const fieldPath = match.substring(resourceKey.length + 1)
1226
+ .replace(/\?\./g, '.') // Remove optional chaining
1227
+ .replace(/\?\[/g, '['); // Remove optional array access
1228
+ const ref = {
1229
+ [KUBERNETES_REF_BRAND]: true,
1230
+ resourceId: resourceKey,
1231
+ fieldPath,
1232
+ _type: this.inferTypeFromFieldPath(fieldPath)
1233
+ };
1234
+ // Only add if not already present
1235
+ if (!dependencies.some(dep => dep.resourceId === resourceKey && dep.fieldPath === fieldPath)) {
1236
+ dependencies.push(ref);
1237
+ }
1238
+ }
1239
+ }
1240
+ }
1241
+ }
1242
+ // Look for schema references (schema.spec.field)
1243
+ const schemaPattern = /\bschema\.[a-zA-Z0-9_.?[\]?]+/g;
1244
+ const schemaMatches = expression.match(schemaPattern);
1245
+ if (schemaMatches) {
1246
+ for (const match of schemaMatches) {
1247
+ const fieldPath = match.replace('schema.', '')
1248
+ .replace(/\?\./g, '.') // Remove optional chaining
1249
+ .replace(/\?\[/g, '['); // Remove optional array access
1250
+ const ref = {
1251
+ [KUBERNETES_REF_BRAND]: true,
1252
+ resourceId: '__schema__',
1253
+ fieldPath,
1254
+ _type: this.inferTypeFromFieldPath(fieldPath)
1255
+ };
1256
+ // Only add if not already present
1257
+ if (!dependencies.some(dep => dep.resourceId === '__schema__' && dep.fieldPath === fieldPath)) {
1258
+ dependencies.push(ref);
1259
+ }
1260
+ }
1261
+ }
1262
+ return dependencies;
1263
+ }
1264
+ /**
1265
+ * Extract dependencies from JavaScript expression
1266
+ */
1267
+ extractDependenciesFromExpression(expression, context) {
1268
+ if (!context.dependencies) {
1269
+ context.dependencies = [];
1270
+ }
1271
+ // Look for resource references (resources.name.field)
1272
+ const resourceMatches = expression.match(/resources\.(\w+)\.([a-zA-Z0-9_.]+)/g);
1273
+ if (resourceMatches) {
1274
+ for (const match of resourceMatches) {
1275
+ const parts = match.split('.');
1276
+ if (parts.length >= 3) {
1277
+ const resourceId = parts[1];
1278
+ const fieldPath = parts.slice(2).join('.');
1279
+ const ref = {
1280
+ [KUBERNETES_REF_BRAND]: true,
1281
+ resourceId,
1282
+ fieldPath,
1283
+ _type: 'unknown'
1284
+ };
1285
+ // Only add if not already present
1286
+ if (!context.dependencies.some(dep => dep.resourceId === resourceId && dep.fieldPath === fieldPath)) {
1287
+ context.dependencies.push(ref);
1288
+ }
1289
+ }
1290
+ }
1291
+ }
1292
+ // Look for schema references (schema.spec.field)
1293
+ const schemaMatches = expression.match(/schema\.([a-zA-Z0-9_.]+)/g);
1294
+ if (schemaMatches) {
1295
+ for (const match of schemaMatches) {
1296
+ const fieldPath = match.replace('schema.', '');
1297
+ const ref = {
1298
+ [KUBERNETES_REF_BRAND]: true,
1299
+ resourceId: '__schema__',
1300
+ fieldPath,
1301
+ _type: 'unknown'
1302
+ };
1303
+ // Only add if not already present
1304
+ if (!context.dependencies.some(dep => dep.resourceId === '__schema__' && dep.fieldPath === fieldPath)) {
1305
+ context.dependencies.push(ref);
1306
+ }
1307
+ }
1308
+ }
1309
+ // Look for direct resource references with various patterns
1310
+ // This handles patterns like:
1311
+ // - "deployment.status.readyReplicas"
1312
+ // - "deployment.status['readyReplicas']"
1313
+ // - "deployment.status?.readyReplicas"
1314
+ // - "deployment.status.conditions[0].type"
1315
+ const directResourcePatterns = [
1316
+ // Standard dot notation: deployment.status.readyReplicas
1317
+ /\b([a-zA-Z_][a-zA-Z0-9_]*)\.(status|spec|metadata)\.([a-zA-Z0-9_.[\]]+)/g,
1318
+ // Computed property access: deployment.status["readyReplicas"]
1319
+ /\b([a-zA-Z_][a-zA-Z0-9_]*)\.(status|spec|metadata)\["([^"]+)"\]/g,
1320
+ // Computed property access with single quotes: deployment.status['readyReplicas']
1321
+ /\b([a-zA-Z_][a-zA-Z0-9_]*)\.(status|spec|metadata)\['([^']+)'\]/g,
1322
+ // Optional chaining: deployment.status?.readyReplicas
1323
+ /\b([a-zA-Z_][a-zA-Z0-9_]*)\.(status|spec|metadata)\?\?\.([a-zA-Z0-9_.[\]?]+)/g,
1324
+ // Mixed patterns: deployment.status.conditions[0].type
1325
+ /\b([a-zA-Z_][a-zA-Z0-9_]*)\.(status|spec|metadata)\.([a-zA-Z0-9_.[\]?]+)/g
1326
+ ];
1327
+ for (const pattern of directResourcePatterns) {
1328
+ let match;
1329
+ pattern.lastIndex = 0; // Reset regex state
1330
+ match = pattern.exec(expression);
1331
+ while (match !== null) {
1332
+ const fullMatch = match[0];
1333
+ const resourceId = match[1];
1334
+ const baseField = match[2]; // status, spec, or metadata
1335
+ const remainingPath = match[3];
1336
+ let fieldPath = baseField;
1337
+ // Handle different patterns
1338
+ if (remainingPath) {
1339
+ // For computed property access patterns, the remainingPath is the property name
1340
+ if (pattern.source.includes('\\["') || pattern.source.includes("\\'")) {
1341
+ fieldPath = `${baseField}.${remainingPath}`;
1342
+ }
1343
+ else {
1344
+ fieldPath = `${baseField}.${remainingPath}`;
1345
+ }
1346
+ }
1347
+ else {
1348
+ // For computed property access, we need to extract the property name differently
1349
+ const computedMatch = fullMatch.match(/\.(status|spec|metadata)\["([^"]+)"\]/) ||
1350
+ fullMatch.match(/\.(status|spec|metadata)\['([^']+)'\]/);
1351
+ if (computedMatch) {
1352
+ fieldPath = `${computedMatch[1]}.${computedMatch[2]}`;
1353
+ }
1354
+ }
1355
+ // Clean up field path
1356
+ fieldPath = fieldPath?.replace(/\?\?/g, '').replace(/\?/g, '') || '';
1357
+ fieldPath = fieldPath.replace(/\["([^"]+)"\]/g, '.$1');
1358
+ fieldPath = fieldPath.replace(/\['([^']+)'\]/g, '.$1');
1359
+ fieldPath = fieldPath.replace(/\[(\d+)\]/g, '[$1]'); // Keep array indices
1360
+ // Check if this resource exists in available references or add it anyway
1361
+ const shouldAdd = !context.availableReferences ||
1362
+ (resourceId ? context.availableReferences[resourceId] : null) ||
1363
+ true; // Add all for now, let validation handle it later
1364
+ if (shouldAdd) {
1365
+ const ref = {
1366
+ [KUBERNETES_REF_BRAND]: true,
1367
+ resourceId,
1368
+ fieldPath,
1369
+ _type: 'unknown'
1370
+ };
1371
+ // Only add if not already present
1372
+ if (!context.dependencies.some(dep => dep.resourceId === resourceId && dep.fieldPath === fieldPath)) {
1373
+ context.dependencies.push(ref);
1374
+ }
1375
+ }
1376
+ // Get next match
1377
+ match = pattern.exec(expression);
1378
+ }
1379
+ }
1380
+ // Look for template literal interpolations
1381
+ // This handles patterns like: `http://${service.status.loadBalancer.ingress[0].ip}`
1382
+ const templateLiteralMatches = expression.match(/\$\{([^}]+)\}/g);
1383
+ if (templateLiteralMatches) {
1384
+ for (const match of templateLiteralMatches) {
1385
+ const innerExpression = match.slice(2, -1); // Remove ${ and }
1386
+ // Recursively extract dependencies from the inner expression
1387
+ this.extractDependenciesFromExpression(innerExpression, context);
1388
+ }
1389
+ }
1390
+ }
1391
+ /**
1392
+ * Calculate overall confidence score
1393
+ */
1394
+ calculateOverallConfidence(result) {
1395
+ let confidence = 1.0;
1396
+ // Reduce confidence for errors
1397
+ if (result.errors.length > 0) {
1398
+ confidence *= 0.1;
1399
+ }
1400
+ // Reduce confidence for type validation issues
1401
+ if (result.typeValidation && !result.typeValidation.valid) {
1402
+ confidence *= 0.5;
1403
+ }
1404
+ // Reduce confidence for resource validation issues
1405
+ if (result.resourceValidation) {
1406
+ const invalidResources = result.resourceValidation.filter(rv => !rv.valid).length;
1407
+ if (invalidResources > 0) {
1408
+ confidence *= Math.max(0.1, 1 - (invalidResources * 0.3));
1409
+ }
1410
+ }
1411
+ // Reduce confidence for compile-time validation issues
1412
+ if (result.compileTimeValidation && !result.compileTimeValidation.valid) {
1413
+ confidence *= 0.3;
1414
+ }
1415
+ return Math.max(0, Math.min(1, confidence));
1416
+ }
1417
+ /**
1418
+ * Convert binary expressions (>, <, ==, !=, &&, ||) with KubernetesRef operand handling
1419
+ */
1420
+ convertBinaryExpression(node, context) {
1421
+ // Convert operands with proper precedence handling
1422
+ const left = this.handleComplexExpression(node.left, context, node.operator);
1423
+ const right = this.handleComplexExpression(node.right, context, node.operator);
1424
+ // Map JavaScript operators to CEL operators
1425
+ const operator = this.mapOperatorToCel(node.operator);
1426
+ // Generate CEL expression with proper precedence
1427
+ const leftExpr = this.addParenthesesIfNeeded(left.expression, node.operator, true);
1428
+ const rightExpr = this.addParenthesesIfNeeded(right.expression, node.operator, false);
1429
+ const expression = `${leftExpr} ${operator} ${rightExpr}`;
1430
+ return {
1431
+ [CEL_EXPRESSION_BRAND]: true,
1432
+ expression,
1433
+ _type: undefined
1434
+ };
1435
+ }
1436
+ /**
1437
+ * Convert member expressions (object.property, object['property']) and array access (array[0], array[index])
1438
+ */
1439
+ convertMemberExpression(node, context) {
1440
+ // Handle optional member expressions (obj?.prop)
1441
+ if (node.optional) {
1442
+ return this.convertOptionalMemberExpression(node, context);
1443
+ }
1444
+ // Handle computed member access (array[index] or object['key'])
1445
+ if (node.computed) {
1446
+ return this.convertArrayAccess(node, context);
1447
+ }
1448
+ // Check if the object is a complex expression (like a method call result)
1449
+ if (node.object.type === 'CallExpression' || node.object.type === 'MemberExpression' && this.isComplexExpression(node.object)) {
1450
+ // Convert the object expression first
1451
+ const objectExpr = this.convertASTNode(node.object, context);
1452
+ const propertyName = node.property.name;
1453
+ // Create a member access on the result of the complex expression
1454
+ const expression = `${objectExpr.expression}.${propertyName}`;
1455
+ return {
1456
+ [CEL_EXPRESSION_BRAND]: true,
1457
+ expression,
1458
+ _type: undefined
1459
+ };
1460
+ }
1461
+ // Try to extract the full member path for simple cases
1462
+ let path;
1463
+ try {
1464
+ path = this.extractMemberPath(node);
1465
+ }
1466
+ catch (_error) {
1467
+ // If path extraction fails, fall back to converting the object and property separately
1468
+ const objectExpr = this.convertASTNode(node.object, context);
1469
+ const propertyName = node.property.name;
1470
+ const expression = `${objectExpr.expression}.${propertyName}`;
1471
+ return {
1472
+ [CEL_EXPRESSION_BRAND]: true,
1473
+ expression,
1474
+ _type: undefined
1475
+ };
1476
+ }
1477
+ // Check if this is a resource reference
1478
+ if (context.availableReferences) {
1479
+ for (const [resourceKey, resource] of Object.entries(context.availableReferences)) {
1480
+ if (path.startsWith(`resources.${resourceKey}.`) || path.startsWith(`${resourceKey}.`) || path === resourceKey) {
1481
+ let fieldPath;
1482
+ if (path === resourceKey) {
1483
+ // Direct resource reference
1484
+ fieldPath = '';
1485
+ }
1486
+ else if (path.startsWith('resources.')) {
1487
+ fieldPath = path.substring(`resources.${resourceKey}.`.length);
1488
+ }
1489
+ else {
1490
+ fieldPath = path.substring(`${resourceKey}.`.length);
1491
+ }
1492
+ return this.getResourceFieldReference(resource, resourceKey, fieldPath, context);
1493
+ }
1494
+ }
1495
+ }
1496
+ // Handle schema references
1497
+ if (path.startsWith('schema.')) {
1498
+ return this.getSchemaFieldReference(path, context);
1499
+ }
1500
+ // Handle unknown resources - this should be an error in strict mode
1501
+ const parts = path.split('.');
1502
+ if (parts.length >= 2) {
1503
+ let resourceName;
1504
+ let fieldPath;
1505
+ // Check if this is a resources.* prefixed expression
1506
+ if (parts[0] === 'resources' && parts.length >= 3) {
1507
+ resourceName = parts[1] || ''; // The actual resource name after "resources."
1508
+ fieldPath = parts.slice(2).join('.'); // The field path after the resource name
1509
+ }
1510
+ else {
1511
+ resourceName = parts[0] || '';
1512
+ fieldPath = parts.slice(1).join('.');
1513
+ }
1514
+ // For strict validation contexts, check if resource should be available
1515
+ // For now, we'll be lenient and allow unknown resources with warnings
1516
+ // Create a placeholder KubernetesRef for the unknown resource
1517
+ const unknownRef = {
1518
+ [KUBERNETES_REF_BRAND]: true,
1519
+ resourceId: resourceName,
1520
+ fieldPath: fieldPath,
1521
+ _type: this.inferTypeFromFieldPath(fieldPath)
1522
+ };
1523
+ // Add to dependencies
1524
+ if (context.dependencies) {
1525
+ context.dependencies.push(unknownRef);
1526
+ }
1527
+ // Generate CEL expression for unknown resource
1528
+ const expression = `resources.${resourceName}.${fieldPath}`;
1529
+ return {
1530
+ [CEL_EXPRESSION_BRAND]: true,
1531
+ expression,
1532
+ _type: undefined
1533
+ };
1534
+ }
1535
+ throw new Error(`Unable to resolve member expression: ${path}`);
1536
+ }
1537
+ /**
1538
+ * Convert conditional expressions (condition ? true : false)
1539
+ * Handles ternary operators with proper CEL syntax and KubernetesRef support
1540
+ */
1541
+ convertConditionalExpression(node, context) {
1542
+ const test = this.handleComplexExpression(node.test, context, '?');
1543
+ const consequent = this.handleComplexExpression(node.consequent, context, '?');
1544
+ const alternate = this.handleComplexExpression(node.alternate, context, '?');
1545
+ // Ensure the test condition is properly formatted for CEL
1546
+ let testExpression = test.expression;
1547
+ // If the test is a resource reference or optional chaining, ensure it's properly evaluated as boolean
1548
+ if (this.isResourceReference(testExpression) || testExpression.includes('?')) {
1549
+ // For resource references, we need to check if they exist and are truthy
1550
+ testExpression = this.convertToBooleanTest(testExpression);
1551
+ }
1552
+ // Add parentheses to operands if needed for precedence
1553
+ const testExpr = this.addParenthesesIfNeeded(testExpression, '?', true);
1554
+ const consequentExpr = this.addParenthesesIfNeeded(consequent.expression, '?', false);
1555
+ const alternateExpr = this.addParenthesesIfNeeded(alternate.expression, '?', false);
1556
+ const expression = `${testExpr} ? ${consequentExpr} : ${alternateExpr}`;
1557
+ return {
1558
+ [CEL_EXPRESSION_BRAND]: true,
1559
+ expression,
1560
+ _type: undefined
1561
+ };
1562
+ }
1563
+ /**
1564
+ * Convert an expression to a proper boolean test for CEL conditionals
1565
+ */
1566
+ convertToBooleanTest(expression) {
1567
+ // If it's already a comparison or boolean expression, use as-is
1568
+ if (this.isBooleanExpression(expression)) {
1569
+ return expression;
1570
+ }
1571
+ // For resource references and other values, check for truthiness
1572
+ // This handles JavaScript's truthy/falsy semantics in CEL
1573
+ return `${expression} != null && ${expression} != "" && ${expression} != false && ${expression} != 0`;
1574
+ }
1575
+ /**
1576
+ * Check if an expression is already a boolean expression
1577
+ */
1578
+ isBooleanExpression(expression) {
1579
+ // Check for comparison operators
1580
+ const comparisonOperators = ['==', '!=', '>', '<', '>=', '<=', '&&', '||'];
1581
+ return comparisonOperators.some(op => expression.includes(` ${op} `));
1582
+ }
1583
+ /**
1584
+ * Add parentheses to expression if needed for proper precedence
1585
+ */
1586
+ addParenthesesIfNeeded(expression, parentOperator, isLeftOperand) {
1587
+ // If no parent operator, no parentheses needed
1588
+ if (!parentOperator) {
1589
+ return expression;
1590
+ }
1591
+ // Get the precedence of operators in the expression
1592
+ const expressionOperator = this.getMainOperator(expression);
1593
+ if (!expressionOperator) {
1594
+ return expression; // No operator found, likely a simple expression
1595
+ }
1596
+ const parentPrecedence = this.getOperatorPrecedence(parentOperator);
1597
+ const expressionPrecedence = this.getOperatorPrecedence(expressionOperator);
1598
+ // Add parentheses if expression has lower precedence than parent
1599
+ // or if it's a right operand with equal precedence (for left-associative operators)
1600
+ if (expressionPrecedence < parentPrecedence ||
1601
+ (expressionPrecedence === parentPrecedence && !isLeftOperand && this.isLeftAssociative(parentOperator))) {
1602
+ return `(${expression})`;
1603
+ }
1604
+ return expression;
1605
+ }
1606
+ /**
1607
+ * Get the main operator in an expression (the one with lowest precedence)
1608
+ */
1609
+ getMainOperator(expression) {
1610
+ // This is a simplified implementation - in a full parser, we'd need to handle
1611
+ // nested expressions properly. For now, we'll look for operators outside of parentheses.
1612
+ const operators = ['||', '&&', '==', '!=', '<=', '>=', '<', '>', '+', '-', '*', '/', '%'];
1613
+ let depth = 0;
1614
+ let mainOperator = null;
1615
+ let lowestPrecedence = Infinity;
1616
+ for (let i = 0; i < expression.length; i++) {
1617
+ const char = expression[i];
1618
+ if (char === '(') {
1619
+ depth++;
1620
+ }
1621
+ else if (char === ')') {
1622
+ depth--;
1623
+ }
1624
+ else if (depth === 0) {
1625
+ // Check for operators at the top level
1626
+ for (const op of operators) {
1627
+ if (expression.substring(i, i + op.length) === op) {
1628
+ const precedence = this.getOperatorPrecedence(op);
1629
+ if (precedence <= lowestPrecedence) {
1630
+ lowestPrecedence = precedence;
1631
+ mainOperator = op;
1632
+ }
1633
+ i += op.length - 1; // Skip the operator
1634
+ break;
1635
+ }
1636
+ }
1637
+ }
1638
+ }
1639
+ return mainOperator;
1640
+ }
1641
+ /**
1642
+ * Get operator precedence (lower number = lower precedence)
1643
+ */
1644
+ getOperatorPrecedence(operator) {
1645
+ const precedence = {
1646
+ '||': 1,
1647
+ '&&': 2,
1648
+ '==': 3,
1649
+ '!=': 3,
1650
+ '<': 4,
1651
+ '<=': 4,
1652
+ '>': 4,
1653
+ '>=': 4,
1654
+ '+': 5,
1655
+ '-': 5,
1656
+ '*': 6,
1657
+ '/': 6,
1658
+ '%': 6,
1659
+ '??': 1, // Same as ||
1660
+ '?': 0 // Ternary has lowest precedence
1661
+ };
1662
+ return precedence[operator] ?? 10; // Unknown operators get high precedence
1663
+ }
1664
+ /**
1665
+ * Check if an operator is left-associative
1666
+ */
1667
+ isLeftAssociative(operator) {
1668
+ // Most operators are left-associative, ternary is right-associative
1669
+ return operator !== '?';
1670
+ }
1671
+ /**
1672
+ * Handle complex nested expressions with proper precedence
1673
+ */
1674
+ handleComplexExpression(node, context, parentOperator) {
1675
+ const result = this.convertASTNode(node, context);
1676
+ // Add parentheses if needed for precedence
1677
+ const expressionWithParens = this.addParenthesesIfNeeded(result.expression, parentOperator);
1678
+ return {
1679
+ [CEL_EXPRESSION_BRAND]: true,
1680
+ expression: expressionWithParens,
1681
+ _type: result._type
1682
+ };
1683
+ }
1684
+ /**
1685
+ * Convert logical expressions (&&, ||, ??)
1686
+ * Handles logical OR fallback conversion (value || default) and nullish coalescing (value ?? default)
1687
+ */
1688
+ convertLogicalExpression(node, context) {
1689
+ const left = this.convertASTNode(node.left, context);
1690
+ const right = this.convertASTNode(node.right, context);
1691
+ if (node.operator === '||') {
1692
+ return this.convertLogicalOrFallback(left, right, context);
1693
+ }
1694
+ if (node.operator === '&&') {
1695
+ return this.convertLogicalAnd(left, right, context);
1696
+ }
1697
+ if (node.operator === '??') {
1698
+ return this.convertNullishCoalescing(left, right, context);
1699
+ }
1700
+ // For other logical operators, use direct mapping
1701
+ const operator = this.mapOperatorToCel(node.operator);
1702
+ const expression = `${left.expression} ${operator} ${right.expression}`;
1703
+ return {
1704
+ [CEL_EXPRESSION_BRAND]: true,
1705
+ expression,
1706
+ _type: undefined
1707
+ };
1708
+ }
1709
+ /**
1710
+ * Convert logical OR fallback (value || default) to appropriate CEL conditionals
1711
+ */
1712
+ convertLogicalOrFallback(left, right, _context) {
1713
+ // Add parentheses to operands if they contain lower precedence operators
1714
+ const leftExpr = this.addParenthesesIfNeeded(left.expression, '||', true);
1715
+ const rightExpr = this.addParenthesesIfNeeded(right.expression, '||', false);
1716
+ // For resource references and optional chaining, we can use a simpler null check
1717
+ if (this.isResourceReference(left.expression) || left.expression.includes('?')) {
1718
+ // For resource references, primarily check for null/undefined
1719
+ const expression = `${leftExpr} != null ? ${leftExpr} : ${rightExpr}`;
1720
+ return {
1721
+ [CEL_EXPRESSION_BRAND]: true,
1722
+ expression,
1723
+ _type: undefined
1724
+ };
1725
+ }
1726
+ // For general expressions, check for all falsy values
1727
+ // This handles JavaScript's truthy/falsy semantics in CEL
1728
+ const expression = `${leftExpr} != null && ${leftExpr} != "" && ${leftExpr} != false && ${leftExpr} != 0 ? ${leftExpr} : ${rightExpr}`;
1729
+ return {
1730
+ [CEL_EXPRESSION_BRAND]: true,
1731
+ expression,
1732
+ _type: undefined
1733
+ };
1734
+ }
1735
+ /**
1736
+ * Convert logical AND (value && other) to CEL conditional
1737
+ */
1738
+ convertLogicalAnd(left, right, _context) {
1739
+ // Add parentheses to operands if they contain lower precedence operators
1740
+ const leftExpr = this.addParenthesesIfNeeded(left.expression, '&&', true);
1741
+ const rightExpr = this.addParenthesesIfNeeded(right.expression, '&&', false);
1742
+ // For resource references, primarily check for null/undefined
1743
+ if (this.isResourceReference(left.expression) || left.expression.includes('?')) {
1744
+ const expression = `${leftExpr} != null ? ${rightExpr} : ${leftExpr}`;
1745
+ return {
1746
+ [CEL_EXPRESSION_BRAND]: true,
1747
+ expression,
1748
+ _type: undefined
1749
+ };
1750
+ }
1751
+ // For general expressions, check for all truthy values
1752
+ const expression = `${leftExpr} != null && ${leftExpr} != "" && ${leftExpr} != false && ${leftExpr} != 0 ? ${rightExpr} : ${leftExpr}`;
1753
+ return {
1754
+ [CEL_EXPRESSION_BRAND]: true,
1755
+ expression,
1756
+ _type: undefined
1757
+ };
1758
+ }
1759
+ /**
1760
+ * Convert nullish coalescing (value ?? default) to CEL null-checking expressions
1761
+ * Only checks for null and undefined, not other falsy values like || does
1762
+ */
1763
+ convertNullishCoalescing(left, right, _context) {
1764
+ // Add parentheses to operands if they contain lower precedence operators
1765
+ const leftExpr = this.addParenthesesIfNeeded(left.expression, '??', true);
1766
+ const rightExpr = this.addParenthesesIfNeeded(right.expression, '??', false);
1767
+ // Nullish coalescing only checks for null and undefined, not other falsy values
1768
+ // This is more precise than || which checks for all falsy values
1769
+ const expression = `${leftExpr} != null ? ${leftExpr} : ${rightExpr}`;
1770
+ return {
1771
+ [CEL_EXPRESSION_BRAND]: true,
1772
+ expression,
1773
+ _type: undefined
1774
+ };
1775
+ }
1776
+ /**
1777
+ * Preprocess modern JavaScript syntax for older esprima parser
1778
+ */
1779
+ preprocessModernSyntax(expression) {
1780
+ const processed = expression;
1781
+ // Handle optional chaining (?.) - convert to special handling
1782
+ if (processed.includes('?.')) {
1783
+ // Mark for special handling but don't throw error
1784
+ // Keep as-is for now, let the parser handle it
1785
+ }
1786
+ // Handle nullish coalescing (??) - convert to special handling
1787
+ if (processed.includes('??')) {
1788
+ // Mark for special handling but don't throw error
1789
+ // Keep as-is for now, let the parser handle it
1790
+ }
1791
+ return processed;
1792
+ }
1793
+ /**
1794
+ * Convert optional chaining expressions (obj?.prop?.field) to Kro conditional CEL
1795
+ * Uses Kro's ? operator for null-safe property access
1796
+ */
1797
+ convertOptionalChaining(node, context) {
1798
+ // ChainExpression wraps the actual optional expression
1799
+ const expression = node.expression;
1800
+ if (expression.type === 'MemberExpression' && expression.optional) {
1801
+ return this.convertOptionalMemberExpression(expression, context);
1802
+ }
1803
+ if (expression.type === 'CallExpression' && expression.optional) {
1804
+ return this.convertOptionalCallExpression(expression, context);
1805
+ }
1806
+ // If it's not actually optional, convert the inner expression normally
1807
+ return this.convertASTNode(expression, context);
1808
+ }
1809
+ /**
1810
+ * Convert optional member expressions (obj?.prop, obj?.prop?.field)
1811
+ */
1812
+ convertOptionalMemberExpression(node, context) {
1813
+ // Build the optional chain by recursively processing the object
1814
+ const objectExpr = this.convertASTNode(node.object, context);
1815
+ let propertyAccess;
1816
+ if (node.computed) {
1817
+ // Handle obj?.[key] syntax
1818
+ const property = this.convertASTNode(node.property, context);
1819
+ propertyAccess = `[${property.expression}]`;
1820
+ }
1821
+ else {
1822
+ // Handle obj?.prop syntax
1823
+ propertyAccess = `.${node.property.name}`;
1824
+ }
1825
+ // Use Kro's ? operator for null-safe access
1826
+ // The ? operator in Kro CEL provides null-safe property access
1827
+ const expression = `${objectExpr.expression}?${propertyAccess}`;
1828
+ return {
1829
+ [CEL_EXPRESSION_BRAND]: true,
1830
+ expression,
1831
+ _type: undefined
1832
+ };
1833
+ }
1834
+ /**
1835
+ * Convert optional call expressions (obj?.method?.())
1836
+ */
1837
+ convertOptionalCallExpression(node, context) {
1838
+ // Convert the callee with optional chaining
1839
+ const callee = this.convertASTNode(node.callee, context);
1840
+ // Convert arguments
1841
+ const args = node.arguments.map((arg) => this.convertASTNode(arg, context).expression).join(', ');
1842
+ // Use Kro's ? operator for null-safe method calls
1843
+ const expression = `${callee.expression}?(${args})`;
1844
+ return {
1845
+ [CEL_EXPRESSION_BRAND]: true,
1846
+ expression,
1847
+ _type: undefined
1848
+ };
1849
+ }
1850
+ /**
1851
+ * Convert template literals with KubernetesRef interpolation
1852
+ * Handles expressions like `http://${database.status.podIP}:5432/db`
1853
+ */
1854
+ convertTemplateLiteral(node, context) {
1855
+ let result = '';
1856
+ const _dependencies = [];
1857
+ // Process each part of the template literal
1858
+ for (let i = 0; i < node.quasis.length; i++) {
1859
+ // Add the literal string part
1860
+ const literalPart = node.quasis[i].value.cooked;
1861
+ result += literalPart;
1862
+ // Add the interpolated expression if it exists
1863
+ if (i < node.expressions.length) {
1864
+ const expr = this.convertASTNode(node.expressions[i], context);
1865
+ // For template literals, we need to wrap expressions in ${}
1866
+ result += `\${${expr.expression}}`;
1867
+ // Track dependencies from the interpolated expression
1868
+ // Note: We'd need to extract dependencies from the expression
1869
+ // For now, we'll handle this in a future enhancement
1870
+ }
1871
+ }
1872
+ return {
1873
+ [CEL_EXPRESSION_BRAND]: true,
1874
+ expression: result,
1875
+ _type: 'string' // Template literals always produce strings
1876
+ };
1877
+ }
1878
+ /**
1879
+ * Convert literal values (strings, numbers, booleans - no KubernetesRef objects)
1880
+ * This preserves literal values exactly as they are, without any KubernetesRef processing
1881
+ */
1882
+ convertLiteral(node, _context) {
1883
+ let literalValue;
1884
+ if (typeof node.value === 'string') {
1885
+ // Preserve string literals with proper quoting for CEL
1886
+ literalValue = `"${node.value.replace(/"/g, '\\"')}"`;
1887
+ }
1888
+ else if (typeof node.value === 'number') {
1889
+ // Preserve numeric literals as-is
1890
+ literalValue = String(node.value);
1891
+ }
1892
+ else if (typeof node.value === 'boolean') {
1893
+ // Preserve boolean literals as-is
1894
+ literalValue = String(node.value);
1895
+ }
1896
+ else if (node.value === null) {
1897
+ // Preserve null literals
1898
+ literalValue = 'null';
1899
+ }
1900
+ else if (node.value === undefined) {
1901
+ // Handle undefined (though this shouldn't appear in valid JS literals)
1902
+ literalValue = 'null';
1903
+ }
1904
+ else {
1905
+ // For any other literal types, convert to string
1906
+ literalValue = `"${String(node.value).replace(/"/g, '\\"')}"`;
1907
+ }
1908
+ return {
1909
+ [CEL_EXPRESSION_BRAND]: true,
1910
+ expression: literalValue,
1911
+ _type: typeof node.value
1912
+ };
1913
+ }
1914
+ /**
1915
+ * Convert call expressions (method calls and global functions)
1916
+ */
1917
+ convertCallExpression(node, context) {
1918
+ // Handle global functions and Math methods
1919
+ if (node.callee.type === 'Identifier') {
1920
+ const functionName = node.callee.name;
1921
+ return this.convertGlobalFunction(functionName, node.arguments, context);
1922
+ }
1923
+ // Handle Math.* functions
1924
+ if (node.callee.type === 'MemberExpression' &&
1925
+ node.callee.object.type === 'Identifier' &&
1926
+ node.callee.object.name === 'Math') {
1927
+ const mathMethod = node.callee.property.name;
1928
+ return this.convertMathFunction(mathMethod, node.arguments, context);
1929
+ }
1930
+ // Handle common JavaScript methods that can be converted to CEL
1931
+ if (node.callee.type === 'MemberExpression') {
1932
+ const object = this.convertASTNode(node.callee.object, context);
1933
+ const methodName = node.callee.property.name;
1934
+ switch (methodName) {
1935
+ case 'find':
1936
+ return this.convertArrayFind(object, node.arguments, context);
1937
+ case 'filter':
1938
+ return this.convertArrayFilter(object, node.arguments, context);
1939
+ case 'map':
1940
+ return this.convertArrayMap(object, node.arguments, context);
1941
+ case 'includes':
1942
+ return this.convertStringIncludes(object, node.arguments, context);
1943
+ case 'some':
1944
+ return this.convertArraySome(object, node.arguments, context);
1945
+ case 'every':
1946
+ return this.convertArrayEvery(object, node.arguments, context);
1947
+ case 'startsWith':
1948
+ return this.convertStringStartsWith(object, node.arguments, context);
1949
+ case 'endsWith':
1950
+ return this.convertStringEndsWith(object, node.arguments, context);
1951
+ case 'toLowerCase':
1952
+ return this.convertStringToLowerCase(object, node.arguments, context);
1953
+ case 'toUpperCase':
1954
+ return this.convertStringToUpperCase(object, node.arguments, context);
1955
+ case 'trim':
1956
+ return this.convertStringTrim(object, node.arguments, context);
1957
+ case 'substring':
1958
+ return this.convertStringSubstring(object, node.arguments, context);
1959
+ case 'slice':
1960
+ return this.convertStringSlice(object, node.arguments, context);
1961
+ case 'split':
1962
+ return this.convertStringSplit(object, node.arguments, context);
1963
+ case 'join':
1964
+ return this.convertArrayJoin(object, node.arguments, context);
1965
+ case 'flatMap':
1966
+ return this.convertArrayFlatMap(object, node.arguments, context);
1967
+ case 'length':
1968
+ return this.convertLengthProperty(object, context);
1969
+ case 'padStart':
1970
+ return this.convertStringPadStart(object, node.arguments, context);
1971
+ case 'padEnd':
1972
+ return this.convertStringPadEnd(object, node.arguments, context);
1973
+ case 'repeat':
1974
+ return this.convertStringRepeat(object, node.arguments, context);
1975
+ case 'replace':
1976
+ return this.convertStringReplace(object, node.arguments, context);
1977
+ case 'indexOf':
1978
+ return this.convertStringIndexOf(object, node.arguments, context);
1979
+ case 'lastIndexOf':
1980
+ return this.convertStringLastIndexOf(object, node.arguments, context);
1981
+ default:
1982
+ throw new Error(`Unsupported method call: ${methodName}`);
1983
+ }
1984
+ }
1985
+ throw new Error(`Unsupported call expression`);
1986
+ }
1987
+ /**
1988
+ * Convert global functions like Number(), String(), Boolean()
1989
+ */
1990
+ convertGlobalFunction(functionName, args, context) {
1991
+ const convertedArgs = args.map(arg => this.convertASTNode(arg, context));
1992
+ switch (functionName) {
1993
+ case 'Number':
1994
+ if (args.length === 1) {
1995
+ return {
1996
+ [CEL_EXPRESSION_BRAND]: true,
1997
+ expression: `double(${convertedArgs[0]?.expression || 'null'})`,
1998
+ _type: 'number'
1999
+ };
2000
+ }
2001
+ break;
2002
+ case 'String':
2003
+ if (args.length === 1) {
2004
+ return {
2005
+ [CEL_EXPRESSION_BRAND]: true,
2006
+ expression: `string(${convertedArgs[0]?.expression || 'null'})`,
2007
+ _type: 'string'
2008
+ };
2009
+ }
2010
+ break;
2011
+ case 'Boolean':
2012
+ if (args.length === 1) {
2013
+ return {
2014
+ [CEL_EXPRESSION_BRAND]: true,
2015
+ expression: `bool(${convertedArgs[0]?.expression || 'null'})`,
2016
+ _type: 'boolean'
2017
+ };
2018
+ }
2019
+ break;
2020
+ case 'parseInt':
2021
+ if (args.length >= 1) {
2022
+ return {
2023
+ [CEL_EXPRESSION_BRAND]: true,
2024
+ expression: `int(${convertedArgs[0]?.expression || 'null'})`,
2025
+ _type: 'number'
2026
+ };
2027
+ }
2028
+ break;
2029
+ case 'parseFloat':
2030
+ if (args.length >= 1) {
2031
+ return {
2032
+ [CEL_EXPRESSION_BRAND]: true,
2033
+ expression: `double(${convertedArgs[0]?.expression || 'null'})`,
2034
+ _type: 'number'
2035
+ };
2036
+ }
2037
+ break;
2038
+ }
2039
+ throw new Error(`Unsupported global function: ${functionName}`);
2040
+ }
2041
+ /**
2042
+ * Convert Math functions like Math.min(), Math.max(), Math.abs()
2043
+ */
2044
+ convertMathFunction(mathMethod, args, context) {
2045
+ const convertedArgs = args.map(arg => this.convertASTNode(arg, context));
2046
+ switch (mathMethod) {
2047
+ case 'min':
2048
+ if (args.length >= 2) {
2049
+ // CEL doesn't have a direct min function, so we'll use a conditional approach
2050
+ // For now, we'll use a simple approach for 2 arguments
2051
+ if (args.length === 2) {
2052
+ return {
2053
+ [CEL_EXPRESSION_BRAND]: true,
2054
+ expression: `${convertedArgs[0]?.expression || 'null'} < ${convertedArgs[1]?.expression || 'null'} ? ${convertedArgs[0]?.expression || 'null'} : ${convertedArgs[1]?.expression || 'null'}`,
2055
+ _type: 'number'
2056
+ };
2057
+ }
2058
+ else {
2059
+ // For more than 2 arguments, we'll create a nested conditional
2060
+ let expression = convertedArgs[0]?.expression || 'null';
2061
+ for (let i = 1; i < convertedArgs.length; i++) {
2062
+ expression = `${expression} < ${convertedArgs[i]?.expression || 'null'} ? ${expression} : ${convertedArgs[i]?.expression || 'null'}`;
2063
+ }
2064
+ return {
2065
+ [CEL_EXPRESSION_BRAND]: true,
2066
+ expression,
2067
+ _type: 'number'
2068
+ };
2069
+ }
2070
+ }
2071
+ break;
2072
+ case 'max':
2073
+ if (args.length >= 2) {
2074
+ if (args.length === 2) {
2075
+ return {
2076
+ [CEL_EXPRESSION_BRAND]: true,
2077
+ expression: `${convertedArgs[0]?.expression || 'null'} > ${convertedArgs[1]?.expression || 'null'} ? ${convertedArgs[0]?.expression || 'null'} : ${convertedArgs[1]?.expression || 'null'}`,
2078
+ _type: 'number'
2079
+ };
2080
+ }
2081
+ else {
2082
+ let expression = convertedArgs[0]?.expression || 'null';
2083
+ for (let i = 1; i < convertedArgs.length; i++) {
2084
+ expression = `${expression} > ${convertedArgs[i]?.expression || 'null'} ? ${expression} : ${convertedArgs[i]?.expression || 'null'}`;
2085
+ }
2086
+ return {
2087
+ [CEL_EXPRESSION_BRAND]: true,
2088
+ expression,
2089
+ _type: 'number'
2090
+ };
2091
+ }
2092
+ }
2093
+ break;
2094
+ case 'abs':
2095
+ if (args.length === 1) {
2096
+ return {
2097
+ [CEL_EXPRESSION_BRAND]: true,
2098
+ expression: `${convertedArgs[0]?.expression || 'null'} < 0 ? -${convertedArgs[0]?.expression || 'null'} : ${convertedArgs[0]?.expression || 'null'}`,
2099
+ _type: 'number'
2100
+ };
2101
+ }
2102
+ break;
2103
+ case 'floor':
2104
+ if (args.length === 1) {
2105
+ return {
2106
+ [CEL_EXPRESSION_BRAND]: true,
2107
+ expression: `int(${convertedArgs[0]?.expression || 'null'})`,
2108
+ _type: 'number'
2109
+ };
2110
+ }
2111
+ break;
2112
+ case 'ceil':
2113
+ if (args.length === 1) {
2114
+ return {
2115
+ [CEL_EXPRESSION_BRAND]: true,
2116
+ expression: `int(${convertedArgs[0]?.expression || 'null'} + 0.999999)`,
2117
+ _type: 'number'
2118
+ };
2119
+ }
2120
+ break;
2121
+ case 'round':
2122
+ if (args.length === 1) {
2123
+ return {
2124
+ [CEL_EXPRESSION_BRAND]: true,
2125
+ expression: `int(${convertedArgs[0]?.expression || 'null'} + 0.5)`,
2126
+ _type: 'number'
2127
+ };
2128
+ }
2129
+ break;
2130
+ }
2131
+ throw new Error(`Unsupported Math function: ${mathMethod}`);
2132
+ }
2133
+ /**
2134
+ * Convert unary expressions like !x, +x, -x, !!x
2135
+ */
2136
+ convertUnaryExpression(node, context) {
2137
+ const operand = this.convertASTNode(node.argument, context);
2138
+ switch (node.operator) {
2139
+ case '!':
2140
+ return {
2141
+ [CEL_EXPRESSION_BRAND]: true,
2142
+ expression: `!${operand.expression}`,
2143
+ _type: 'boolean'
2144
+ };
2145
+ case '+':
2146
+ return {
2147
+ [CEL_EXPRESSION_BRAND]: true,
2148
+ expression: `double(${operand.expression})`,
2149
+ _type: 'number'
2150
+ };
2151
+ case '-':
2152
+ return {
2153
+ [CEL_EXPRESSION_BRAND]: true,
2154
+ expression: `-${operand.expression}`,
2155
+ _type: 'number'
2156
+ };
2157
+ case 'typeof':
2158
+ return {
2159
+ [CEL_EXPRESSION_BRAND]: true,
2160
+ expression: `type(${operand.expression})`,
2161
+ _type: 'string'
2162
+ };
2163
+ default:
2164
+ throw new Error(`Unsupported unary operator: ${node.operator}`);
2165
+ }
2166
+ }
2167
+ /**
2168
+ * Convert array expressions
2169
+ */
2170
+ convertArrayExpression(node, context) {
2171
+ const elements = node.elements.map((element) => {
2172
+ if (element === null)
2173
+ return 'null';
2174
+ return this.convertASTNode(element, context).expression;
2175
+ });
2176
+ const expression = `[${elements.join(', ')}]`;
2177
+ return {
2178
+ [CEL_EXPRESSION_BRAND]: true,
2179
+ expression,
2180
+ _type: undefined
2181
+ };
2182
+ }
2183
+ /**
2184
+ * Convert identifier expressions
2185
+ */
2186
+ convertIdentifier(node, context) {
2187
+ // For identifiers, we need to check if they refer to available references
2188
+ const name = node.name;
2189
+ // Check if this is a resource reference
2190
+ if (context.availableReferences?.[name]) {
2191
+ // This is a direct resource reference
2192
+ return {
2193
+ [CEL_EXPRESSION_BRAND]: true,
2194
+ expression: `resources.${name}`,
2195
+ _type: undefined
2196
+ };
2197
+ }
2198
+ // Check if this is a schema reference
2199
+ if (name === 'schema') {
2200
+ return {
2201
+ [CEL_EXPRESSION_BRAND]: true,
2202
+ expression: 'schema',
2203
+ _type: undefined
2204
+ };
2205
+ }
2206
+ // For other identifiers, return as-is (might be local variables in complex expressions)
2207
+ return {
2208
+ [CEL_EXPRESSION_BRAND]: true,
2209
+ expression: name,
2210
+ _type: undefined
2211
+ };
2212
+ }
2213
+ /**
2214
+ * Map JavaScript operators to CEL operators
2215
+ */
2216
+ mapOperatorToCel(operator) {
2217
+ const mapping = {
2218
+ '===': '==',
2219
+ '!==': '!=',
2220
+ '&&': '&&',
2221
+ '||': '||',
2222
+ '>': '>',
2223
+ '<': '<',
2224
+ '>=': '>=',
2225
+ '<=': '<=',
2226
+ '==': '==',
2227
+ '!=': '!='
2228
+ };
2229
+ return mapping[operator] || operator;
2230
+ }
2231
+ /**
2232
+ * Extract member path from AST node
2233
+ */
2234
+ extractMemberPath(node) {
2235
+ if (node.type === 'Identifier') {
2236
+ return node.name;
2237
+ }
2238
+ if (node.type === 'MemberExpression') {
2239
+ const object = this.extractMemberPath(node.object);
2240
+ if (node.computed) {
2241
+ // For computed access like array[0] or object['key'], we need special handling
2242
+ // This is used for path extraction, so we'll represent it differently
2243
+ const property = this.getSourceText(node.property);
2244
+ const optionalMarker = node.optional ? '?.' : '';
2245
+ return `${object}${optionalMarker}[${property}]`;
2246
+ }
2247
+ else {
2248
+ // For regular property access like object.property or object?.property
2249
+ const property = node.property.name;
2250
+ const optionalMarker = node.optional ? '?.' : '.';
2251
+ return `${object}${optionalMarker}${property}`;
2252
+ }
2253
+ }
2254
+ if (node.type === 'ChainExpression') {
2255
+ // Handle ChainExpression wrapper for optional chaining
2256
+ return this.extractMemberPath(node.expression);
2257
+ }
2258
+ throw new Error(`Cannot extract path from node type: ${node.type}`);
2259
+ }
2260
+ /**
2261
+ * Check if a node represents a complex expression that can't be handled as a simple path
2262
+ */
2263
+ isComplexExpression(node) {
2264
+ if (node.type === 'CallExpression') {
2265
+ return true;
2266
+ }
2267
+ if (node.type === 'MemberExpression') {
2268
+ // Recursively check if the object is complex
2269
+ return this.isComplexExpression(node.object);
2270
+ }
2271
+ return false;
2272
+ }
2273
+ /**
2274
+ * Get source text from AST node (placeholder implementation)
2275
+ */
2276
+ getSourceText(node) {
2277
+ // For now, return a placeholder - this would need access to original source
2278
+ if (node.type === 'Literal') {
2279
+ return String(node.value);
2280
+ }
2281
+ return '<expression>';
2282
+ }
2283
+ /**
2284
+ * Generate CEL expression for resource field reference
2285
+ */
2286
+ getResourceFieldReference(_resource, resourceKey, fieldPath, context) {
2287
+ // Generate CEL expression for resource field reference using the correct format
2288
+ // This should match the format used by getInnerCelPath
2289
+ const expression = `${resourceKey}.${fieldPath}`;
2290
+ // Create a KubernetesRef object and add it to dependencies
2291
+ const ref = {
2292
+ [KUBERNETES_REF_BRAND]: true,
2293
+ resourceId: resourceKey,
2294
+ fieldPath,
2295
+ _type: this.inferTypeFromFieldPath(fieldPath)
2296
+ };
2297
+ if (!context.dependencies) {
2298
+ context.dependencies = [];
2299
+ }
2300
+ context.dependencies.push(ref);
2301
+ return {
2302
+ [CEL_EXPRESSION_BRAND]: true,
2303
+ expression,
2304
+ _type: undefined
2305
+ };
2306
+ }
2307
+ /**
2308
+ * Generate CEL expression for schema field reference
2309
+ */
2310
+ getSchemaFieldReference(path, context) {
2311
+ // Create a KubernetesRef object for schema reference and add it to dependencies
2312
+ const fieldPath = path.substring('schema.'.length);
2313
+ const ref = {
2314
+ [KUBERNETES_REF_BRAND]: true,
2315
+ resourceId: '__schema__',
2316
+ fieldPath,
2317
+ _type: this.inferTypeFromFieldPath(fieldPath)
2318
+ };
2319
+ if (!context.dependencies) {
2320
+ context.dependencies = [];
2321
+ }
2322
+ context.dependencies.push(ref);
2323
+ // Generate CEL expression for schema field reference
2324
+ return {
2325
+ [CEL_EXPRESSION_BRAND]: true,
2326
+ expression: path,
2327
+ _type: undefined
2328
+ };
2329
+ }
2330
+ /**
2331
+ * Convert array.find() method calls
2332
+ */
2333
+ convertArrayFind(object, args, context) {
2334
+ if (args.length !== 1) {
2335
+ throw new Error('Array.find() requires exactly one argument');
2336
+ }
2337
+ // For simple property comparisons like c => c.type === "Available", we can convert to CEL
2338
+ const arg = args[0];
2339
+ if (arg.type === 'ArrowFunctionExpression' && arg.body.type === 'BinaryExpression') {
2340
+ const param = arg.params[0].name;
2341
+ const binaryExpr = arg.body;
2342
+ // Handle the left side (should be a member expression like c.type)
2343
+ let leftExpr;
2344
+ if (binaryExpr.left.type === 'MemberExpression' && binaryExpr.left.object.name === param) {
2345
+ // Simple case: c.type
2346
+ leftExpr = `${param}.${binaryExpr.left.property.name}`;
2347
+ }
2348
+ else {
2349
+ // More complex case - try to convert but replace parameter references
2350
+ try {
2351
+ const leftResult = this.convertASTNode(binaryExpr.left, context);
2352
+ leftExpr = leftResult.expression.replace(new RegExp(`\\b${param}\\b`, 'g'), param);
2353
+ }
2354
+ catch {
2355
+ leftExpr = `${param}.property`;
2356
+ }
2357
+ }
2358
+ // Handle the right side (usually a literal)
2359
+ let rightExpr;
2360
+ try {
2361
+ const rightResult = this.convertASTNode(binaryExpr.right, context);
2362
+ rightExpr = rightResult.expression;
2363
+ }
2364
+ catch {
2365
+ rightExpr = 'value';
2366
+ }
2367
+ const operator = this.convertBinaryOperator(binaryExpr.operator);
2368
+ const expression = `${object.expression}.filter(${param}, ${leftExpr} ${operator} ${rightExpr})[0]`;
2369
+ return {
2370
+ [CEL_EXPRESSION_BRAND]: true,
2371
+ expression,
2372
+ _type: undefined
2373
+ };
2374
+ }
2375
+ // For now, create a placeholder for complex find operations
2376
+ const expression = `${object.expression}.filter(/* TODO: convert find predicate */)[0]`;
2377
+ return {
2378
+ [CEL_EXPRESSION_BRAND]: true,
2379
+ expression,
2380
+ _type: undefined
2381
+ };
2382
+ }
2383
+ /**
2384
+ * Convert array.filter() method calls
2385
+ */
2386
+ convertArrayFilter(object, args, context) {
2387
+ if (args.length !== 1) {
2388
+ throw new Error('Array.filter() requires exactly one argument');
2389
+ }
2390
+ // For simple property access like i => i.ip, we can convert to CEL
2391
+ const arg = args[0];
2392
+ if (arg.type === 'ArrowFunctionExpression') {
2393
+ const param = arg.params[0].name;
2394
+ if (arg.body.type === 'MemberExpression') {
2395
+ // Simple property access: i => i.ip
2396
+ const property = arg.body.property.name;
2397
+ const expression = `${object.expression}.filter(${param}, has(${param}.${property}) && ${param}.${property} != null)`;
2398
+ return {
2399
+ [CEL_EXPRESSION_BRAND]: true,
2400
+ expression,
2401
+ _type: undefined
2402
+ };
2403
+ }
2404
+ else if (arg.body.type === 'BinaryExpression') {
2405
+ // Binary comparison: i => i.type === "Available"
2406
+ const left = this.convertASTNode(arg.body.left, context);
2407
+ const operator = this.convertBinaryOperator(arg.body.operator);
2408
+ const right = this.convertASTNode(arg.body.right, context);
2409
+ // Replace parameter references with the iteration variable
2410
+ const leftExpr = left.expression.replace(new RegExp(`\\b${param}\\b`, 'g'), param);
2411
+ const rightExpr = right.expression;
2412
+ const expression = `${object.expression}.filter(${param}, ${leftExpr} ${operator} ${rightExpr})`;
2413
+ return {
2414
+ [CEL_EXPRESSION_BRAND]: true,
2415
+ expression,
2416
+ _type: undefined
2417
+ };
2418
+ }
2419
+ }
2420
+ // For now, create a placeholder for complex filter operations
2421
+ const expression = `${object.expression}.filter(/* TODO: convert filter predicate */)`;
2422
+ return {
2423
+ [CEL_EXPRESSION_BRAND]: true,
2424
+ expression,
2425
+ _type: undefined
2426
+ };
2427
+ }
2428
+ /**
2429
+ * Convert string.includes() method calls
2430
+ */
2431
+ convertStringIncludes(object, args, context) {
2432
+ if (args.length !== 1) {
2433
+ throw new Error('String.includes() requires exactly one argument');
2434
+ }
2435
+ const searchValue = this.convertASTNode(args[0], context);
2436
+ const expression = `${object.expression}.contains(${searchValue.expression})`;
2437
+ return {
2438
+ [CEL_EXPRESSION_BRAND]: true,
2439
+ expression,
2440
+ _type: undefined
2441
+ };
2442
+ }
2443
+ /**
2444
+ * Convert array.map() method calls
2445
+ */
2446
+ convertArrayMap(object, args, _context) {
2447
+ if (args.length !== 1) {
2448
+ throw new Error('Array.map() requires exactly one argument');
2449
+ }
2450
+ // For simple property access like c => c.name, we can convert to CEL
2451
+ const arg = args[0];
2452
+ if (arg.type === 'ArrowFunctionExpression' && arg.body.type === 'MemberExpression') {
2453
+ const param = arg.params[0].name;
2454
+ const property = arg.body.property.name;
2455
+ const expression = `${object.expression}.map(${param}, ${param}.${property})`;
2456
+ return {
2457
+ [CEL_EXPRESSION_BRAND]: true,
2458
+ expression,
2459
+ _type: undefined
2460
+ };
2461
+ }
2462
+ // For now, create a placeholder for complex map operations
2463
+ const expression = `${object.expression}.map(/* TODO: convert map predicate */)`;
2464
+ return {
2465
+ [CEL_EXPRESSION_BRAND]: true,
2466
+ expression,
2467
+ _type: undefined
2468
+ };
2469
+ }
2470
+ /**
2471
+ * Convert array.some() method calls
2472
+ */
2473
+ convertArraySome(object, args, _context) {
2474
+ if (args.length !== 1) {
2475
+ throw new Error('Array.some() requires exactly one argument');
2476
+ }
2477
+ // For now, create a placeholder - full implementation would need lambda support
2478
+ const expression = `${object.expression}.exists(/* TODO: convert predicate */)`;
2479
+ return {
2480
+ [CEL_EXPRESSION_BRAND]: true,
2481
+ expression,
2482
+ _type: undefined
2483
+ };
2484
+ }
2485
+ /**
2486
+ * Convert array.every() method calls
2487
+ */
2488
+ convertArrayEvery(object, args, _context) {
2489
+ if (args.length !== 1) {
2490
+ throw new Error('Array.every() requires exactly one argument');
2491
+ }
2492
+ // For now, create a placeholder - full implementation would need lambda support
2493
+ const expression = `${object.expression}.all(/* TODO: convert predicate */)`;
2494
+ return {
2495
+ [CEL_EXPRESSION_BRAND]: true,
2496
+ expression,
2497
+ _type: undefined
2498
+ };
2499
+ }
2500
+ /**
2501
+ * Convert string.startsWith() method calls
2502
+ */
2503
+ convertStringStartsWith(object, args, context) {
2504
+ if (args.length !== 1) {
2505
+ throw new Error('String.startsWith() requires exactly one argument');
2506
+ }
2507
+ const searchValue = this.convertASTNode(args[0], context);
2508
+ const expression = `${object.expression}.startsWith(${searchValue.expression})`;
2509
+ return {
2510
+ [CEL_EXPRESSION_BRAND]: true,
2511
+ expression,
2512
+ _type: undefined
2513
+ };
2514
+ }
2515
+ /**
2516
+ * Convert string.endsWith() method calls
2517
+ */
2518
+ convertStringEndsWith(object, args, context) {
2519
+ if (args.length !== 1) {
2520
+ throw new Error('String.endsWith() requires exactly one argument');
2521
+ }
2522
+ const searchValue = this.convertASTNode(args[0], context);
2523
+ const expression = `${object.expression}.endsWith(${searchValue.expression})`;
2524
+ return {
2525
+ [CEL_EXPRESSION_BRAND]: true,
2526
+ expression,
2527
+ _type: undefined
2528
+ };
2529
+ }
2530
+ /**
2531
+ * Convert string.toLowerCase() method calls
2532
+ */
2533
+ convertStringToLowerCase(object, args, _context) {
2534
+ if (args.length !== 0) {
2535
+ throw new Error('String.toLowerCase() requires no arguments');
2536
+ }
2537
+ const expression = `${object.expression}.lowerAscii()`;
2538
+ return {
2539
+ [CEL_EXPRESSION_BRAND]: true,
2540
+ expression,
2541
+ _type: undefined
2542
+ };
2543
+ }
2544
+ /**
2545
+ * Convert string.toUpperCase() method calls
2546
+ */
2547
+ convertStringToUpperCase(object, args, _context) {
2548
+ if (args.length !== 0) {
2549
+ throw new Error('String.toUpperCase() requires no arguments');
2550
+ }
2551
+ const expression = `${object.expression}.upperAscii()`;
2552
+ return {
2553
+ [CEL_EXPRESSION_BRAND]: true,
2554
+ expression,
2555
+ _type: undefined
2556
+ };
2557
+ }
2558
+ /**
2559
+ * Convert string.trim() method calls
2560
+ */
2561
+ convertStringTrim(object, args, _context) {
2562
+ if (args.length !== 0) {
2563
+ throw new Error('String.trim() requires no arguments');
2564
+ }
2565
+ // CEL doesn't have a direct trim function, so we'll use a placeholder
2566
+ const expression = `${object.expression}.trim()`;
2567
+ return {
2568
+ [CEL_EXPRESSION_BRAND]: true,
2569
+ expression,
2570
+ _type: undefined
2571
+ };
2572
+ }
2573
+ /**
2574
+ * Convert string.substring() method calls
2575
+ */
2576
+ convertStringSubstring(object, args, context) {
2577
+ if (args.length < 1 || args.length > 2) {
2578
+ throw new Error('String.substring() requires 1 or 2 arguments');
2579
+ }
2580
+ const startIndex = this.convertASTNode(args[0], context);
2581
+ if (args.length === 1) {
2582
+ const expression = `${object.expression}.substring(${startIndex.expression})`;
2583
+ return {
2584
+ [CEL_EXPRESSION_BRAND]: true,
2585
+ expression,
2586
+ _type: undefined
2587
+ };
2588
+ }
2589
+ else {
2590
+ const endIndex = this.convertASTNode(args[1], context);
2591
+ const expression = `${object.expression}.substring(${startIndex.expression}, ${endIndex.expression})`;
2592
+ return {
2593
+ [CEL_EXPRESSION_BRAND]: true,
2594
+ expression,
2595
+ _type: undefined
2596
+ };
2597
+ }
2598
+ }
2599
+ /**
2600
+ * Convert string.slice() method calls
2601
+ */
2602
+ convertStringSlice(object, args, context) {
2603
+ if (args.length < 1 || args.length > 2) {
2604
+ throw new Error('String.slice() requires 1 or 2 arguments');
2605
+ }
2606
+ const startIndex = this.convertASTNode(args[0], context);
2607
+ if (args.length === 1) {
2608
+ const expression = `${object.expression}.substring(${startIndex.expression})`;
2609
+ return {
2610
+ [CEL_EXPRESSION_BRAND]: true,
2611
+ expression,
2612
+ _type: undefined
2613
+ };
2614
+ }
2615
+ else {
2616
+ const endIndex = this.convertASTNode(args[1], context);
2617
+ const expression = `${object.expression}.substring(${startIndex.expression}, ${endIndex.expression})`;
2618
+ return {
2619
+ [CEL_EXPRESSION_BRAND]: true,
2620
+ expression,
2621
+ _type: undefined
2622
+ };
2623
+ }
2624
+ }
2625
+ /**
2626
+ * Convert string.split() method calls
2627
+ */
2628
+ convertStringSplit(object, args, context) {
2629
+ if (args.length !== 1) {
2630
+ throw new Error('String.split() requires exactly one argument');
2631
+ }
2632
+ const separator = this.convertASTNode(args[0], context);
2633
+ const expression = `${object.expression}.split(${separator.expression})`;
2634
+ return {
2635
+ [CEL_EXPRESSION_BRAND]: true,
2636
+ expression,
2637
+ _type: undefined
2638
+ };
2639
+ }
2640
+ /**
2641
+ * Convert array.join() method calls
2642
+ */
2643
+ convertArrayJoin(object, args, context) {
2644
+ if (args.length !== 1) {
2645
+ throw new Error('Array.join() requires exactly one argument');
2646
+ }
2647
+ const separator = this.convertASTNode(args[0], context);
2648
+ const expression = `${object.expression}.join(${separator.expression})`;
2649
+ return {
2650
+ [CEL_EXPRESSION_BRAND]: true,
2651
+ expression,
2652
+ _type: undefined
2653
+ };
2654
+ }
2655
+ /**
2656
+ * Convert array.flatMap() method calls
2657
+ */
2658
+ convertArrayFlatMap(object, args, _context) {
2659
+ if (args.length !== 1) {
2660
+ throw new Error('Array.flatMap() requires exactly one argument');
2661
+ }
2662
+ const arg = args[0];
2663
+ // Handle arrow function: arr.flatMap(x => x.items)
2664
+ if (arg.type === 'ArrowFunctionExpression') {
2665
+ const param = arg.params[0].name;
2666
+ if (arg.body.type === 'MemberExpression') {
2667
+ // Simple property access: x => x.items
2668
+ const property = arg.body.property.name;
2669
+ const expression = `${object.expression}.map(${param}, ${param}.${property}).flatten()`;
2670
+ return {
2671
+ [CEL_EXPRESSION_BRAND]: true,
2672
+ expression,
2673
+ _type: undefined
2674
+ };
2675
+ }
2676
+ }
2677
+ throw new Error('Unsupported flatMap expression');
2678
+ }
2679
+ /**
2680
+ * Convert .length property access
2681
+ */
2682
+ convertLengthProperty(object, _context) {
2683
+ const expression = `size(${object.expression})`;
2684
+ return {
2685
+ [CEL_EXPRESSION_BRAND]: true,
2686
+ expression,
2687
+ _type: undefined
2688
+ };
2689
+ }
2690
+ /**
2691
+ * Convert string.padStart() method calls
2692
+ */
2693
+ convertStringPadStart(object, args, context) {
2694
+ if (args.length < 1 || args.length > 2) {
2695
+ throw new Error('String.padStart() requires 1 or 2 arguments');
2696
+ }
2697
+ const targetLength = this.convertASTNode(args[0], context);
2698
+ const padString = args.length > 1 ? this.convertASTNode(args[1], context) : { expression: '" "' };
2699
+ // CEL doesn't have padStart, so we'll simulate it
2700
+ const expression = `size(${object.expression}) >= ${targetLength.expression} ? ${object.expression} : (${padString.expression}.repeat(${targetLength.expression} - size(${object.expression})) + ${object.expression})`;
2701
+ return {
2702
+ [CEL_EXPRESSION_BRAND]: true,
2703
+ expression,
2704
+ _type: 'string'
2705
+ };
2706
+ }
2707
+ /**
2708
+ * Convert string.padEnd() method calls
2709
+ */
2710
+ convertStringPadEnd(object, args, context) {
2711
+ if (args.length < 1 || args.length > 2) {
2712
+ throw new Error('String.padEnd() requires 1 or 2 arguments');
2713
+ }
2714
+ const targetLength = this.convertASTNode(args[0], context);
2715
+ const padString = args.length > 1 ? this.convertASTNode(args[1], context) : { expression: '" "' };
2716
+ // CEL doesn't have padEnd, so we'll simulate it
2717
+ const expression = `size(${object.expression}) >= ${targetLength.expression} ? ${object.expression} : (${object.expression} + ${padString.expression}.repeat(${targetLength.expression} - size(${object.expression})))`;
2718
+ return {
2719
+ [CEL_EXPRESSION_BRAND]: true,
2720
+ expression,
2721
+ _type: 'string'
2722
+ };
2723
+ }
2724
+ /**
2725
+ * Convert string.repeat() method calls
2726
+ */
2727
+ convertStringRepeat(object, args, context) {
2728
+ if (args.length !== 1) {
2729
+ throw new Error('String.repeat() requires exactly one argument');
2730
+ }
2731
+ const count = this.convertASTNode(args[0], context);
2732
+ // CEL doesn't have repeat, so we'll use a simple approach for small counts
2733
+ const expression = `${object.expression}.repeat(${count.expression})`;
2734
+ return {
2735
+ [CEL_EXPRESSION_BRAND]: true,
2736
+ expression,
2737
+ _type: 'string'
2738
+ };
2739
+ }
2740
+ /**
2741
+ * Convert string.replace() method calls
2742
+ */
2743
+ convertStringReplace(object, args, context) {
2744
+ if (args.length !== 2) {
2745
+ throw new Error('String.replace() requires exactly two arguments');
2746
+ }
2747
+ const searchValue = this.convertASTNode(args[0], context);
2748
+ const replaceValue = this.convertASTNode(args[1], context);
2749
+ // CEL doesn't have replace, so we'll use a simple substitution
2750
+ const expression = `${object.expression}.replace(${searchValue.expression}, ${replaceValue.expression})`;
2751
+ return {
2752
+ [CEL_EXPRESSION_BRAND]: true,
2753
+ expression,
2754
+ _type: 'string'
2755
+ };
2756
+ }
2757
+ /**
2758
+ * Convert string.indexOf() method calls
2759
+ */
2760
+ convertStringIndexOf(object, args, context) {
2761
+ if (args.length !== 1) {
2762
+ throw new Error('String.indexOf() requires exactly one argument');
2763
+ }
2764
+ const searchValue = this.convertASTNode(args[0], context);
2765
+ // CEL doesn't have indexOf, so we'll use a conditional approach
2766
+ const expression = `${object.expression}.contains(${searchValue.expression}) ? 0 : -1`;
2767
+ return {
2768
+ [CEL_EXPRESSION_BRAND]: true,
2769
+ expression,
2770
+ _type: 'number'
2771
+ };
2772
+ }
2773
+ /**
2774
+ * Convert string.lastIndexOf() method calls
2775
+ */
2776
+ convertStringLastIndexOf(object, args, context) {
2777
+ if (args.length !== 1) {
2778
+ throw new Error('String.lastIndexOf() requires exactly one argument');
2779
+ }
2780
+ const searchValue = this.convertASTNode(args[0], context);
2781
+ // CEL doesn't have lastIndexOf, so we'll use a conditional approach
2782
+ const expression = `${object.expression}.contains(${searchValue.expression}) ? size(${object.expression}) - size(${searchValue.expression}) : -1`;
2783
+ return {
2784
+ [CEL_EXPRESSION_BRAND]: true,
2785
+ expression,
2786
+ _type: 'number'
2787
+ };
2788
+ }
2789
+ /**
2790
+ * Infer type from field path based on common Kubernetes patterns
2791
+ */
2792
+ inferTypeFromFieldPath(fieldPath) {
2793
+ // Common patterns for type inference
2794
+ if (fieldPath.includes('replicas') || fieldPath.includes('count') || fieldPath.includes('port')) {
2795
+ return 0; // number
2796
+ }
2797
+ if (fieldPath.includes('ready') || fieldPath.includes('available') || fieldPath.includes('enabled')) {
2798
+ return false; // boolean
2799
+ }
2800
+ if (fieldPath.includes('name') || fieldPath.includes('image') || fieldPath.includes('namespace')) {
2801
+ return ''; // string
2802
+ }
2803
+ if (fieldPath.includes('labels') || fieldPath.includes('annotations')) {
2804
+ return {}; // object
2805
+ }
2806
+ if (fieldPath.includes('conditions') || fieldPath.includes('ingress') || fieldPath.includes('containers')) {
2807
+ return []; // array
2808
+ }
2809
+ // Default to string for unknown fields
2810
+ return '';
2811
+ }
2812
+ /**
2813
+ * Convert binary operators to CEL equivalents
2814
+ */
2815
+ convertBinaryOperator(operator) {
2816
+ const operatorMap = {
2817
+ '===': '==',
2818
+ '!==': '!=',
2819
+ '==': '==',
2820
+ '!=': '!=',
2821
+ '<': '<',
2822
+ '<=': '<=',
2823
+ '>': '>',
2824
+ '>=': '>=',
2825
+ '+': '+',
2826
+ '-': '-',
2827
+ '*': '*',
2828
+ '/': '/',
2829
+ '%': '%'
2830
+ };
2831
+ const celOperator = operatorMap[operator];
2832
+ if (!celOperator) {
2833
+ throw new Error(`Unsupported binary operator: ${operator}`);
2834
+ }
2835
+ return celOperator;
2836
+ }
2837
+ /**
2838
+ * Convert array access expressions with KubernetesRef support (array[0], array[index])
2839
+ */
2840
+ convertArrayAccess(node, context) {
2841
+ // Convert the object being accessed (could be a KubernetesRef)
2842
+ const object = this.convertASTNode(node.object, context);
2843
+ // Convert the index/key expression
2844
+ const property = this.convertASTNode(node.property, context);
2845
+ // Generate CEL array access expression
2846
+ const expression = `${object.expression}[${property.expression}]`;
2847
+ return {
2848
+ [CEL_EXPRESSION_BRAND]: true,
2849
+ expression,
2850
+ _type: undefined
2851
+ };
2852
+ }
2853
+ /**
2854
+ * Check if a value is a static literal that doesn't need conversion
2855
+ * Static values (no KubernetesRef objects) should be preserved as-is for performance
2856
+ */
2857
+ isStaticValue(value) {
2858
+ // Primitive values are always static
2859
+ if (value === null || value === undefined)
2860
+ return true;
2861
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
2862
+ return true;
2863
+ }
2864
+ // Check if it's a KubernetesRef (not static)
2865
+ if (isKubernetesRef(value))
2866
+ return false;
2867
+ // Check if it contains KubernetesRef objects (not static)
2868
+ if (containsKubernetesRefs(value))
2869
+ return false;
2870
+ // Arrays and objects need recursive checking
2871
+ if (Array.isArray(value)) {
2872
+ return value.every(item => this.isStaticValue(item));
2873
+ }
2874
+ if (value && typeof value === 'object') {
2875
+ return Object.values(value).every(val => this.isStaticValue(val));
2876
+ }
2877
+ // Default to static for other types
2878
+ return true;
2879
+ }
2880
+ /**
2881
+ * Create a result for static values that don't require conversion
2882
+ */
2883
+ createStaticValueResult(_value) {
2884
+ return {
2885
+ valid: true,
2886
+ celExpression: null, // No CEL expression needed for static values
2887
+ dependencies: [],
2888
+ sourceMap: [],
2889
+ errors: [],
2890
+ warnings: [],
2891
+ requiresConversion: false // Key: static values don't need conversion
2892
+ };
2893
+ }
2894
+ /**
2895
+ * Analyze expression using factory pattern aware handling
2896
+ *
2897
+ * This method integrates with the factory pattern handler to provide
2898
+ * appropriate expression processing based on the deployment strategy.
2899
+ */
2900
+ analyzeExpressionWithFactoryPattern(expression, context) {
2901
+ try {
2902
+ // Use the factory pattern handler for initial processing
2903
+ const factoryResult = handleExpressionWithFactoryPattern(expression, context);
2904
+ // If the factory handler processed it successfully, return the result
2905
+ if (factoryResult.valid && factoryResult.celExpression) {
2906
+ return factoryResult;
2907
+ }
2908
+ // If the factory handler determined no conversion is needed, return as-is
2909
+ if (!factoryResult.requiresConversion) {
2910
+ return factoryResult;
2911
+ }
2912
+ // If the factory handler couldn't process it, fall back to the main analyzer
2913
+ if (typeof expression === 'string') {
2914
+ return this.analyzeExpression(expression, context);
2915
+ }
2916
+ // For non-string expressions that need conversion, return the factory result
2917
+ return factoryResult;
2918
+ }
2919
+ catch (error) {
2920
+ return {
2921
+ valid: false,
2922
+ celExpression: null,
2923
+ dependencies: [],
2924
+ sourceMap: [],
2925
+ errors: [new ConversionError(`Factory pattern expression analysis failed: ${error instanceof Error ? error.message : String(error)}`, String(expression), 'javascript')],
2926
+ warnings: [],
2927
+ requiresConversion: true
2928
+ };
2929
+ }
2930
+ }
2931
+ /**
2932
+ * Get cache statistics for performance monitoring
2933
+ */
2934
+ getCacheStats() {
2935
+ return this.cache.getStats();
2936
+ }
2937
+ /**
2938
+ * Clear all caches
2939
+ */
2940
+ clearCache() {
2941
+ this.cache.clear();
2942
+ }
2943
+ /**
2944
+ * Force cleanup of expired cache entries
2945
+ */
2946
+ cleanupCache() {
2947
+ return this.cache.cleanup();
2948
+ }
2949
+ /**
2950
+ * Destroy analyzer and cleanup resources
2951
+ */
2952
+ destroy() {
2953
+ this.cache.destroy();
2954
+ }
2955
+ }
2956
+ //# sourceMappingURL=analyzer.js.map