parse-hcl 0.1.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 (47) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +749 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +91 -0
  5. package/dist/index.d.ts +51 -0
  6. package/dist/index.js +74 -0
  7. package/dist/parsers/genericParser.d.ts +167 -0
  8. package/dist/parsers/genericParser.js +268 -0
  9. package/dist/parsers/localsParser.d.ts +30 -0
  10. package/dist/parsers/localsParser.js +43 -0
  11. package/dist/parsers/outputParser.d.ts +25 -0
  12. package/dist/parsers/outputParser.js +44 -0
  13. package/dist/parsers/variableParser.d.ts +62 -0
  14. package/dist/parsers/variableParser.js +249 -0
  15. package/dist/services/artifactParsers.d.ts +12 -0
  16. package/dist/services/artifactParsers.js +157 -0
  17. package/dist/services/terraformJsonParser.d.ts +16 -0
  18. package/dist/services/terraformJsonParser.js +212 -0
  19. package/dist/services/terraformParser.d.ts +91 -0
  20. package/dist/services/terraformParser.js +191 -0
  21. package/dist/types/artifacts.d.ts +210 -0
  22. package/dist/types/artifacts.js +5 -0
  23. package/dist/types/blocks.d.ts +419 -0
  24. package/dist/types/blocks.js +28 -0
  25. package/dist/utils/common/errors.d.ts +46 -0
  26. package/dist/utils/common/errors.js +54 -0
  27. package/dist/utils/common/fs.d.ts +5 -0
  28. package/dist/utils/common/fs.js +48 -0
  29. package/dist/utils/common/logger.d.ts +5 -0
  30. package/dist/utils/common/logger.js +17 -0
  31. package/dist/utils/common/valueHelpers.d.ts +4 -0
  32. package/dist/utils/common/valueHelpers.js +23 -0
  33. package/dist/utils/graph/graphBuilder.d.ts +33 -0
  34. package/dist/utils/graph/graphBuilder.js +373 -0
  35. package/dist/utils/lexer/blockScanner.d.ts +36 -0
  36. package/dist/utils/lexer/blockScanner.js +143 -0
  37. package/dist/utils/lexer/hclLexer.d.ts +119 -0
  38. package/dist/utils/lexer/hclLexer.js +525 -0
  39. package/dist/utils/parser/bodyParser.d.ts +26 -0
  40. package/dist/utils/parser/bodyParser.js +81 -0
  41. package/dist/utils/parser/valueClassifier.d.ts +21 -0
  42. package/dist/utils/parser/valueClassifier.js +434 -0
  43. package/dist/utils/serialization/serializer.d.ts +9 -0
  44. package/dist/utils/serialization/serializer.js +63 -0
  45. package/dist/utils/serialization/yaml.d.ts +1 -0
  46. package/dist/utils/serialization/yaml.js +81 -0
  47. package/package.json +66 -0
@@ -0,0 +1,525 @@
1
+ "use strict";
2
+ /**
3
+ * Shared lexer utilities for HCL parsing.
4
+ * Provides common functions for tokenization and string handling.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.isQuote = isQuote;
8
+ exports.isEscaped = isEscaped;
9
+ exports.skipWhitespaceAndComments = skipWhitespaceAndComments;
10
+ exports.skipString = skipString;
11
+ exports.skipHeredoc = skipHeredoc;
12
+ exports.findMatchingBrace = findMatchingBrace;
13
+ exports.findMatchingBracket = findMatchingBracket;
14
+ exports.readIdentifier = readIdentifier;
15
+ exports.readDottedIdentifier = readDottedIdentifier;
16
+ exports.readQuotedString = readQuotedString;
17
+ exports.readValue = readValue;
18
+ exports.splitArrayElements = splitArrayElements;
19
+ exports.splitObjectEntries = splitObjectEntries;
20
+ /**
21
+ * Checks if a character is a quote character (single or double).
22
+ * @param char - The character to check
23
+ * @returns True if the character is a quote
24
+ */
25
+ function isQuote(char) {
26
+ return char === '"' || char === "'";
27
+ }
28
+ /**
29
+ * Checks if a character at a given position is escaped by counting preceding backslashes.
30
+ * Handles consecutive backslashes correctly (e.g., \\\\ is two escaped backslashes).
31
+ * @param text - The source text
32
+ * @param index - The index of the character to check
33
+ * @returns True if the character is escaped
34
+ */
35
+ function isEscaped(text, index) {
36
+ let backslashCount = 0;
37
+ let pos = index - 1;
38
+ while (pos >= 0 && text[pos] === '\\') {
39
+ backslashCount++;
40
+ pos--;
41
+ }
42
+ return backslashCount % 2 === 1;
43
+ }
44
+ /**
45
+ * Skips whitespace and comments (line and block comments).
46
+ * Handles "//", block comments, and "#" style comments.
47
+ * @param text - The source text
48
+ * @param start - The starting index
49
+ * @returns The index of the next non-whitespace, non-comment character
50
+ */
51
+ function skipWhitespaceAndComments(text, start) {
52
+ let index = start;
53
+ const length = text.length;
54
+ while (index < length) {
55
+ const char = text[index];
56
+ const next = text[index + 1];
57
+ // Skip whitespace
58
+ if (/\s/.test(char)) {
59
+ index++;
60
+ continue;
61
+ }
62
+ // Skip block comments /* ... */
63
+ if (char === '/' && next === '*') {
64
+ const end = text.indexOf('*/', index + 2);
65
+ index = end === -1 ? length : end + 2;
66
+ continue;
67
+ }
68
+ // Skip line comments // ...
69
+ if (char === '/' && next === '/') {
70
+ const end = text.indexOf('\n', index + 2);
71
+ index = end === -1 ? length : end + 1;
72
+ continue;
73
+ }
74
+ // Skip hash comments # ...
75
+ if (char === '#') {
76
+ const end = text.indexOf('\n', index + 1);
77
+ index = end === -1 ? length : end + 1;
78
+ continue;
79
+ }
80
+ break;
81
+ }
82
+ return index;
83
+ }
84
+ /**
85
+ * Skips a quoted string, handling escape sequences correctly.
86
+ * @param text - The source text
87
+ * @param start - The index of the opening quote
88
+ * @returns The index after the closing quote
89
+ */
90
+ function skipString(text, start) {
91
+ const quote = text[start];
92
+ let index = start + 1;
93
+ const length = text.length;
94
+ while (index < length) {
95
+ const char = text[index];
96
+ if (char === quote && !isEscaped(text, index)) {
97
+ return index + 1;
98
+ }
99
+ index++;
100
+ }
101
+ return length;
102
+ }
103
+ /**
104
+ * Skips a heredoc string (<<EOF or <<-EOF style).
105
+ * @param text - The source text
106
+ * @param start - The index of the first '<'
107
+ * @returns The index after the heredoc terminator
108
+ */
109
+ function skipHeredoc(text, start) {
110
+ const markerMatch = text.slice(start).match(/^<<-?\s*"?([A-Za-z0-9_]+)"?/);
111
+ if (!markerMatch) {
112
+ return start + 2;
113
+ }
114
+ const marker = markerMatch[1];
115
+ const afterMarker = start + markerMatch[0].length;
116
+ const terminatorIndex = text.indexOf(`\n${marker}`, afterMarker);
117
+ if (terminatorIndex === -1) {
118
+ return text.length;
119
+ }
120
+ const endOfTerminator = text.indexOf('\n', terminatorIndex + marker.length + 1);
121
+ return endOfTerminator === -1 ? text.length : endOfTerminator + 1;
122
+ }
123
+ /**
124
+ * Finds the matching closing brace for an opening brace.
125
+ * Handles nested braces, strings, comments, and heredocs.
126
+ * @param content - The source text
127
+ * @param startIndex - The index of the opening brace
128
+ * @returns The index of the matching closing brace, or -1 if not found
129
+ */
130
+ function findMatchingBrace(content, startIndex) {
131
+ let depth = 0;
132
+ let index = startIndex;
133
+ const length = content.length;
134
+ while (index < length) {
135
+ const char = content[index];
136
+ const next = content[index + 1];
137
+ // Skip quoted strings
138
+ if (isQuote(char)) {
139
+ index = skipString(content, index);
140
+ continue;
141
+ }
142
+ // Skip block comments
143
+ if (char === '/' && next === '*') {
144
+ const end = content.indexOf('*/', index + 2);
145
+ index = end === -1 ? length : end + 2;
146
+ continue;
147
+ }
148
+ // Skip line comments
149
+ if (char === '/' && next === '/') {
150
+ const end = content.indexOf('\n', index + 2);
151
+ index = end === -1 ? length : end + 1;
152
+ continue;
153
+ }
154
+ // Skip hash comments
155
+ if (char === '#') {
156
+ const end = content.indexOf('\n', index + 1);
157
+ index = end === -1 ? length : end + 1;
158
+ continue;
159
+ }
160
+ // Skip heredocs
161
+ if (char === '<' && next === '<') {
162
+ index = skipHeredoc(content, index);
163
+ continue;
164
+ }
165
+ // Track brace depth
166
+ if (char === '{') {
167
+ depth++;
168
+ }
169
+ else if (char === '}') {
170
+ depth--;
171
+ if (depth === 0) {
172
+ return index;
173
+ }
174
+ }
175
+ index++;
176
+ }
177
+ return -1;
178
+ }
179
+ /**
180
+ * Finds the matching closing bracket for an opening bracket.
181
+ * Handles nested brackets of all types [], {}, ().
182
+ * @param content - The source text
183
+ * @param startIndex - The index of the opening bracket
184
+ * @param openChar - The opening bracket character
185
+ * @param closeChar - The closing bracket character
186
+ * @returns The index of the matching closing bracket, or -1 if not found
187
+ */
188
+ function findMatchingBracket(content, startIndex, openChar, closeChar) {
189
+ let depth = 0;
190
+ let index = startIndex;
191
+ const length = content.length;
192
+ while (index < length) {
193
+ const char = content[index];
194
+ const next = content[index + 1];
195
+ // Skip quoted strings
196
+ if (isQuote(char)) {
197
+ index = skipString(content, index);
198
+ continue;
199
+ }
200
+ // Skip block comments
201
+ if (char === '/' && next === '*') {
202
+ const end = content.indexOf('*/', index + 2);
203
+ index = end === -1 ? length : end + 2;
204
+ continue;
205
+ }
206
+ // Skip line comments
207
+ if (char === '/' && next === '/') {
208
+ const end = content.indexOf('\n', index + 2);
209
+ index = end === -1 ? length : end + 1;
210
+ continue;
211
+ }
212
+ // Skip hash comments
213
+ if (char === '#') {
214
+ const end = content.indexOf('\n', index + 1);
215
+ index = end === -1 ? length : end + 1;
216
+ continue;
217
+ }
218
+ // Track bracket depth
219
+ if (char === openChar) {
220
+ depth++;
221
+ }
222
+ else if (char === closeChar) {
223
+ depth--;
224
+ if (depth === 0) {
225
+ return index;
226
+ }
227
+ }
228
+ index++;
229
+ }
230
+ return -1;
231
+ }
232
+ /**
233
+ * Reads an identifier from the source text.
234
+ * Identifiers start with a letter or underscore, followed by letters, digits, underscores, or hyphens.
235
+ * @param text - The source text
236
+ * @param start - The starting index
237
+ * @returns The identifier string, or empty string if no identifier found
238
+ */
239
+ function readIdentifier(text, start) {
240
+ const match = text.slice(start).match(/^[A-Za-z_][\w-]*/);
241
+ return match ? match[0] : '';
242
+ }
243
+ /**
244
+ * Reads an identifier that may contain dots (for attribute access).
245
+ * @param text - The source text
246
+ * @param start - The starting index
247
+ * @returns The identifier string, or empty string if no identifier found
248
+ */
249
+ function readDottedIdentifier(text, start) {
250
+ const match = text.slice(start).match(/^[\w.-]+/);
251
+ return match ? match[0] : '';
252
+ }
253
+ /**
254
+ * Reads a quoted string and returns its unescaped content.
255
+ * @param text - The source text
256
+ * @param start - The index of the opening quote
257
+ * @returns The unquoted string and the index after the closing quote
258
+ */
259
+ function readQuotedString(text, start) {
260
+ const quote = text[start];
261
+ let index = start + 1;
262
+ let value = '';
263
+ const length = text.length;
264
+ while (index < length) {
265
+ const char = text[index];
266
+ if (char === quote && !isEscaped(text, index)) {
267
+ return { text: value, end: index + 1 };
268
+ }
269
+ // Handle escape sequences
270
+ if (char === '\\' && index + 1 < length) {
271
+ const nextChar = text[index + 1];
272
+ if (nextChar === quote || nextChar === '\\' || nextChar === 'n' || nextChar === 't' || nextChar === 'r') {
273
+ switch (nextChar) {
274
+ case 'n':
275
+ value += '\n';
276
+ break;
277
+ case 't':
278
+ value += '\t';
279
+ break;
280
+ case 'r':
281
+ value += '\r';
282
+ break;
283
+ default:
284
+ value += nextChar;
285
+ }
286
+ index += 2;
287
+ continue;
288
+ }
289
+ }
290
+ value += char;
291
+ index++;
292
+ }
293
+ return { text: value, end: length };
294
+ }
295
+ /**
296
+ * Reads a value from HCL source (handles multiline values in brackets).
297
+ * @param text - The source text
298
+ * @param start - The starting index (after the '=' sign)
299
+ * @returns The raw value text and the index after the value
300
+ */
301
+ function readValue(text, start) {
302
+ let index = skipWhitespaceAndComments(text, start);
303
+ const valueStart = index;
304
+ const length = text.length;
305
+ // Handle heredocs
306
+ if (text.slice(index, index + 2) === '<<') {
307
+ const newlineIndex = text.indexOf('\n', index);
308
+ const firstLine = newlineIndex === -1 ? text.slice(index) : text.slice(index, newlineIndex);
309
+ const markerMatch = firstLine.match(/^<<-?\s*"?([A-Za-z0-9_]+)"?/);
310
+ if (markerMatch) {
311
+ const marker = markerMatch[1];
312
+ const terminatorIndex = text.indexOf(`\n${marker}`, newlineIndex);
313
+ if (terminatorIndex !== -1) {
314
+ const endOfTerminator = text.indexOf('\n', terminatorIndex + marker.length + 1);
315
+ const endIndex = endOfTerminator === -1 ? length : endOfTerminator;
316
+ return { raw: text.slice(valueStart, endIndex).trim(), end: endIndex };
317
+ }
318
+ }
319
+ }
320
+ // Track bracket depth for multiline values
321
+ let depth = 0;
322
+ let inString = false;
323
+ let stringChar = null;
324
+ while (index < length) {
325
+ const char = text[index];
326
+ const next = text[index + 1];
327
+ if (!inString) {
328
+ // Enter string
329
+ if (isQuote(char)) {
330
+ inString = true;
331
+ stringChar = char;
332
+ index++;
333
+ continue;
334
+ }
335
+ // Skip block comments
336
+ if (char === '/' && next === '*') {
337
+ const end = text.indexOf('*/', index + 2);
338
+ index = end === -1 ? length : end + 2;
339
+ continue;
340
+ }
341
+ // Skip line comments
342
+ if (char === '/' && next === '/') {
343
+ const end = text.indexOf('\n', index + 2);
344
+ index = end === -1 ? length : end + 1;
345
+ continue;
346
+ }
347
+ // Track brackets
348
+ if (char === '{' || char === '[' || char === '(') {
349
+ depth++;
350
+ }
351
+ else if (char === '}' || char === ']' || char === ')') {
352
+ depth = Math.max(depth - 1, 0);
353
+ }
354
+ // End of value (newline at depth 0)
355
+ if ((char === '\n' || char === '\r') && depth === 0) {
356
+ break;
357
+ }
358
+ }
359
+ else {
360
+ // Exit string
361
+ if (char === stringChar && !isEscaped(text, index)) {
362
+ inString = false;
363
+ stringChar = null;
364
+ }
365
+ }
366
+ index++;
367
+ }
368
+ return { raw: text.slice(valueStart, index).trim(), end: index };
369
+ }
370
+ /**
371
+ * Splits an array literal into its elements.
372
+ * Handles nested arrays, objects, and strings correctly.
373
+ * @param raw - The raw array string including brackets
374
+ * @returns Array of raw element strings
375
+ */
376
+ function splitArrayElements(raw) {
377
+ const inner = raw.slice(1, -1).trim();
378
+ if (!inner) {
379
+ return [];
380
+ }
381
+ const elements = [];
382
+ let current = '';
383
+ let depth = 0;
384
+ let inString = false;
385
+ let stringChar = null;
386
+ for (let i = 0; i < inner.length; i++) {
387
+ const char = inner[i];
388
+ if (!inString) {
389
+ if (isQuote(char)) {
390
+ inString = true;
391
+ stringChar = char;
392
+ current += char;
393
+ continue;
394
+ }
395
+ if (char === '{' || char === '[' || char === '(') {
396
+ depth++;
397
+ current += char;
398
+ continue;
399
+ }
400
+ if (char === '}' || char === ']' || char === ')') {
401
+ depth--;
402
+ current += char;
403
+ continue;
404
+ }
405
+ if (char === ',' && depth === 0) {
406
+ const trimmed = current.trim();
407
+ if (trimmed) {
408
+ elements.push(trimmed);
409
+ }
410
+ current = '';
411
+ continue;
412
+ }
413
+ current += char;
414
+ }
415
+ else {
416
+ current += char;
417
+ if (char === stringChar && !isEscaped(inner, i)) {
418
+ inString = false;
419
+ stringChar = null;
420
+ }
421
+ }
422
+ }
423
+ const trimmed = current.trim();
424
+ if (trimmed) {
425
+ elements.push(trimmed);
426
+ }
427
+ return elements;
428
+ }
429
+ /**
430
+ * Splits an object literal into key-value pairs.
431
+ * Handles nested objects, arrays, and strings correctly.
432
+ * @param raw - The raw object string including braces
433
+ * @returns Array of [key, value] tuples
434
+ */
435
+ function splitObjectEntries(raw) {
436
+ const inner = raw.slice(1, -1).trim();
437
+ if (!inner) {
438
+ return [];
439
+ }
440
+ const entries = [];
441
+ let index = 0;
442
+ const length = inner.length;
443
+ while (index < length) {
444
+ index = skipWhitespaceAndComments(inner, index);
445
+ if (index >= length)
446
+ break;
447
+ // Read key (can be identifier or quoted string)
448
+ let key;
449
+ if (isQuote(inner[index])) {
450
+ const result = readQuotedString(inner, index);
451
+ key = result.text;
452
+ index = result.end;
453
+ }
454
+ else {
455
+ key = readDottedIdentifier(inner, index);
456
+ index += key.length;
457
+ }
458
+ if (!key) {
459
+ index++;
460
+ continue;
461
+ }
462
+ index = skipWhitespaceAndComments(inner, index);
463
+ // Expect = or :
464
+ if (inner[index] === '=' || inner[index] === ':') {
465
+ index++;
466
+ }
467
+ // Read value
468
+ const valueResult = readObjectValue(inner, index);
469
+ entries.push([key, valueResult.raw]);
470
+ index = valueResult.end;
471
+ // Skip comma if present
472
+ index = skipWhitespaceAndComments(inner, index);
473
+ if (inner[index] === ',') {
474
+ index++;
475
+ }
476
+ }
477
+ return entries;
478
+ }
479
+ /**
480
+ * Reads a value within an object (stops at comma or closing brace).
481
+ */
482
+ function readObjectValue(text, start) {
483
+ let index = skipWhitespaceAndComments(text, start);
484
+ const valueStart = index;
485
+ const length = text.length;
486
+ let depth = 0;
487
+ let inString = false;
488
+ let stringChar = null;
489
+ while (index < length) {
490
+ const char = text[index];
491
+ if (!inString) {
492
+ if (isQuote(char)) {
493
+ inString = true;
494
+ stringChar = char;
495
+ index++;
496
+ continue;
497
+ }
498
+ if (char === '{' || char === '[' || char === '(') {
499
+ depth++;
500
+ index++;
501
+ continue;
502
+ }
503
+ if (char === '}' || char === ']' || char === ')') {
504
+ if (depth === 0) {
505
+ break;
506
+ }
507
+ depth--;
508
+ index++;
509
+ continue;
510
+ }
511
+ if ((char === ',' || char === '\n') && depth === 0) {
512
+ break;
513
+ }
514
+ index++;
515
+ }
516
+ else {
517
+ if (char === stringChar && !isEscaped(text, index)) {
518
+ inString = false;
519
+ stringChar = null;
520
+ }
521
+ index++;
522
+ }
523
+ }
524
+ return { raw: text.slice(valueStart, index).trim(), end: index };
525
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Parser for HCL block bodies.
3
+ * Extracts attributes (key = value pairs) and nested blocks from block content.
4
+ */
5
+ import { ParsedBody } from '../../types/blocks';
6
+ /**
7
+ * Parses an HCL block body into structured attributes and nested blocks.
8
+ *
9
+ * @param body - The raw block body content (without outer braces)
10
+ * @returns ParsedBody containing attributes and nested blocks
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const body = `
15
+ * name = "example"
16
+ * count = 5
17
+ * tags {
18
+ * env = "prod"
19
+ * }
20
+ * `;
21
+ * const parsed = parseBlockBody(body);
22
+ * // parsed.attributes: { name: Value, count: Value }
23
+ * // parsed.blocks: [{ type: 'tags', ... }]
24
+ * ```
25
+ */
26
+ export declare function parseBlockBody(body: string): ParsedBody;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Parser for HCL block bodies.
4
+ * Extracts attributes (key = value pairs) and nested blocks from block content.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.parseBlockBody = parseBlockBody;
8
+ const valueClassifier_1 = require("./valueClassifier");
9
+ const hclLexer_1 = require("../lexer/hclLexer");
10
+ /**
11
+ * Parses an HCL block body into structured attributes and nested blocks.
12
+ *
13
+ * @param body - The raw block body content (without outer braces)
14
+ * @returns ParsedBody containing attributes and nested blocks
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const body = `
19
+ * name = "example"
20
+ * count = 5
21
+ * tags {
22
+ * env = "prod"
23
+ * }
24
+ * `;
25
+ * const parsed = parseBlockBody(body);
26
+ * // parsed.attributes: { name: Value, count: Value }
27
+ * // parsed.blocks: [{ type: 'tags', ... }]
28
+ * ```
29
+ */
30
+ function parseBlockBody(body) {
31
+ const attributes = {};
32
+ const blocks = [];
33
+ let index = 0;
34
+ const length = body.length;
35
+ while (index < length) {
36
+ index = (0, hclLexer_1.skipWhitespaceAndComments)(body, index);
37
+ if (index >= length)
38
+ break;
39
+ const identifierStart = index;
40
+ const identifier = (0, hclLexer_1.readDottedIdentifier)(body, index);
41
+ if (!identifier) {
42
+ index++;
43
+ continue;
44
+ }
45
+ index += identifier.length;
46
+ index = (0, hclLexer_1.skipWhitespaceAndComments)(body, index);
47
+ // Check for attribute assignment (key = value)
48
+ if (body[index] === '=') {
49
+ const { raw, end } = (0, hclLexer_1.readValue)(body, index + 1);
50
+ attributes[identifier] = (0, valueClassifier_1.classifyValue)(raw);
51
+ index = end;
52
+ continue;
53
+ }
54
+ // Check for nested block with labels
55
+ const labels = [];
56
+ while ((0, hclLexer_1.isQuote)(body[index])) {
57
+ const { text, end } = (0, hclLexer_1.readQuotedString)(body, index);
58
+ labels.push(text);
59
+ index = (0, hclLexer_1.skipWhitespaceAndComments)(body, end);
60
+ }
61
+ // Check for nested block opening brace
62
+ if (body[index] === '{') {
63
+ const closeIndex = (0, hclLexer_1.findMatchingBrace)(body, index);
64
+ const innerBody = body.slice(index + 1, closeIndex);
65
+ const parsed = parseBlockBody(innerBody);
66
+ const raw = body.slice(identifierStart, closeIndex + 1);
67
+ blocks.push({
68
+ type: identifier,
69
+ labels,
70
+ attributes: parsed.attributes,
71
+ blocks: parsed.blocks,
72
+ raw
73
+ });
74
+ index = closeIndex + 1;
75
+ continue;
76
+ }
77
+ // Not recognized, skip character and continue
78
+ index++;
79
+ }
80
+ return { attributes, blocks };
81
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Value classifier for HCL expressions.
3
+ * Classifies raw value strings into typed Value structures and extracts references.
4
+ */
5
+ import { Value } from '../../types/blocks';
6
+ /**
7
+ * Classifies a raw HCL value string into a typed Value structure.
8
+ * Supports literals, quoted strings, arrays, objects, and expressions.
9
+ *
10
+ * @param raw - The raw value string to classify
11
+ * @returns The classified Value with type information and extracted references
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * classifyValue('true') // LiteralValue { type: 'literal', value: true }
16
+ * classifyValue('"hello"') // LiteralValue { type: 'literal', value: 'hello' }
17
+ * classifyValue('var.region') // ExpressionValue with variable reference
18
+ * classifyValue('[1, 2, 3]') // ArrayValue with parsed elements
19
+ * ```
20
+ */
21
+ export declare function classifyValue(raw: string): Value;