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,373 @@
1
+ "use strict";
2
+ /**
3
+ * Dependency graph builder for Terraform documents.
4
+ * Creates a directed graph of dependencies between Terraform elements.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.buildDependencyGraph = buildDependencyGraph;
8
+ exports.createExport = createExport;
9
+ /** Current graph export version */
10
+ const GRAPH_VERSION = '1.0.0';
11
+ /**
12
+ * Builds a dependency graph from a parsed Terraform document.
13
+ * Analyzes all blocks and their references to construct nodes and edges.
14
+ *
15
+ * @param document - The parsed Terraform document
16
+ * @returns A complete dependency graph with nodes, edges, and orphan references
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const parser = new TerraformParser();
21
+ * const doc = parser.parseFile('main.tf');
22
+ * const graph = buildDependencyGraph(doc);
23
+ *
24
+ * // Visualize dependencies
25
+ * for (const edge of graph.edges) {
26
+ * console.log(`${edge.from} -> ${edge.to}`);
27
+ * }
28
+ * ```
29
+ */
30
+ function buildDependencyGraph(document) {
31
+ const nodes = new Map();
32
+ const edges = [];
33
+ const orphanReferences = [];
34
+ const edgeKeys = new Set();
35
+ // First pass: populate all declared nodes
36
+ populateNodes(document, nodes);
37
+ /**
38
+ * Adds edges from a source node to all referenced targets.
39
+ */
40
+ const addEdges = (fromNode, refs, source) => {
41
+ if (!fromNode || refs.length === 0) {
42
+ return;
43
+ }
44
+ for (const ref of refs) {
45
+ const target = ensureTargetNode(ref, nodes);
46
+ if (!target) {
47
+ orphanReferences.push(ref);
48
+ continue;
49
+ }
50
+ // Deduplicate edges
51
+ const key = `${fromNode.id}->${target.id}:${JSON.stringify(ref)}`;
52
+ if (edgeKeys.has(key)) {
53
+ continue;
54
+ }
55
+ edgeKeys.add(key);
56
+ edges.push({
57
+ from: fromNode.id,
58
+ to: target.id,
59
+ reference: ref,
60
+ source
61
+ });
62
+ }
63
+ };
64
+ // Process terraform settings
65
+ for (const block of document.terraform) {
66
+ const node = nodes.get(nodeId('terraform', 'settings'));
67
+ addEdges(node, referencesFromAttributes(block.properties), block.source);
68
+ }
69
+ // Process providers
70
+ for (const provider of document.provider) {
71
+ const node = nodes.get(nodeId('provider', provider.name, provider.alias));
72
+ addEdges(node, referencesFromAttributes(provider.properties), provider.source);
73
+ }
74
+ // Process variables (references in default values)
75
+ for (const variable of document.variable) {
76
+ const node = nodes.get(nodeId('variable', variable.name));
77
+ addEdges(node, referencesFromValue(variable.default), variable.source);
78
+ }
79
+ // Process outputs
80
+ for (const output of document.output) {
81
+ const node = nodes.get(nodeId('output', output.name));
82
+ addEdges(node, referencesFromValue(output.value), output.source);
83
+ }
84
+ // Process modules
85
+ for (const module of document.module) {
86
+ const node = nodes.get(nodeId('module', module.name));
87
+ addEdges(node, referencesFromAttributes(module.properties), module.source);
88
+ }
89
+ // Process resources
90
+ for (const resource of document.resource) {
91
+ const node = nodes.get(nodeId('resource', resource.type, resource.name));
92
+ addEdges(node, referencesFromAttributes(resource.properties), resource.source);
93
+ addEdges(node, referencesFromAttributes(resource.meta), resource.source);
94
+ // Process dynamic blocks
95
+ for (const dyn of resource.dynamic_blocks) {
96
+ addEdges(node, referencesFromValue(dyn.for_each), resource.source);
97
+ addEdges(node, referencesFromAttributes(dyn.content), resource.source);
98
+ }
99
+ // Process nested blocks
100
+ addEdges(node, referencesFromNestedBlocks(resource.blocks), resource.source);
101
+ }
102
+ // Process data sources
103
+ for (const data of document.data) {
104
+ const node = nodes.get(nodeId('data', data.dataType, data.name));
105
+ addEdges(node, referencesFromAttributes(data.properties), data.source);
106
+ addEdges(node, referencesFromNestedBlocks(data.blocks), data.source);
107
+ }
108
+ // Process locals
109
+ for (const local of document.locals) {
110
+ const node = nodes.get(nodeId('locals', local.name));
111
+ addEdges(node, referencesFromValue(local.value), local.source);
112
+ }
113
+ // Process other blocks (moved/import/check/terraform_data/unknown)
114
+ const otherBlocks = [
115
+ ...document.moved,
116
+ ...document.import,
117
+ ...document.check,
118
+ ...document.terraform_data,
119
+ ...document.unknown
120
+ ];
121
+ for (const block of otherBlocks) {
122
+ // These blocks don't have nodes, but we track their references
123
+ const allRefs = [
124
+ ...referencesFromAttributes(block.properties),
125
+ ...referencesFromNestedBlocks(block.blocks)
126
+ ];
127
+ // Add edges from the block type as a pseudo-node
128
+ const blockNode = nodes.get(nodeId(block.type, block.labels[0] || 'default'));
129
+ if (!blockNode) {
130
+ // Create a node for this block
131
+ const newNode = {
132
+ id: nodeId(block.type, block.labels[0] || 'default'),
133
+ kind: block.type,
134
+ name: block.labels[0] || 'default',
135
+ source: block.source
136
+ };
137
+ nodes.set(newNode.id, newNode);
138
+ addEdges(newNode, allRefs, block.source);
139
+ }
140
+ else {
141
+ addEdges(blockNode, allRefs, block.source);
142
+ }
143
+ }
144
+ return {
145
+ nodes: Array.from(nodes.values()),
146
+ edges,
147
+ orphanReferences
148
+ };
149
+ }
150
+ /**
151
+ * Creates a complete export containing the document and its dependency graph.
152
+ *
153
+ * @param document - The parsed Terraform document
154
+ * @returns TerraformExport with version, document, and graph
155
+ */
156
+ function createExport(document) {
157
+ return {
158
+ version: GRAPH_VERSION,
159
+ document,
160
+ graph: buildDependencyGraph(document)
161
+ };
162
+ }
163
+ /**
164
+ * Populates the node map with all declared elements from the document.
165
+ */
166
+ function populateNodes(document, nodes) {
167
+ const addNode = (node) => {
168
+ if (!nodes.has(node.id)) {
169
+ nodes.set(node.id, node);
170
+ }
171
+ };
172
+ // Always add terraform settings node
173
+ addNode({
174
+ id: nodeId('terraform', 'settings'),
175
+ kind: 'terraform',
176
+ name: 'settings'
177
+ });
178
+ // Add provider nodes
179
+ for (const provider of document.provider) {
180
+ addNode({
181
+ id: nodeId('provider', provider.name, provider.alias),
182
+ kind: 'provider',
183
+ name: provider.alias || provider.name,
184
+ type: provider.name,
185
+ source: provider.source
186
+ });
187
+ }
188
+ // Add variable nodes
189
+ for (const variable of document.variable) {
190
+ addNode({
191
+ id: nodeId('variable', variable.name),
192
+ kind: 'variable',
193
+ name: variable.name,
194
+ source: variable.source
195
+ });
196
+ }
197
+ // Add output nodes
198
+ for (const output of document.output) {
199
+ addNode({
200
+ id: nodeId('output', output.name),
201
+ kind: 'output',
202
+ name: output.name,
203
+ source: output.source
204
+ });
205
+ }
206
+ // Add module nodes
207
+ for (const module of document.module) {
208
+ addNode({
209
+ id: nodeId('module', module.name),
210
+ kind: 'module',
211
+ name: module.name,
212
+ source: module.source
213
+ });
214
+ }
215
+ // Add resource nodes
216
+ for (const resource of document.resource) {
217
+ addNode({
218
+ id: nodeId('resource', resource.type, resource.name),
219
+ kind: 'resource',
220
+ name: resource.name,
221
+ type: resource.type,
222
+ source: resource.source
223
+ });
224
+ }
225
+ // Add data source nodes
226
+ for (const data of document.data) {
227
+ addNode({
228
+ id: nodeId('data', data.dataType, data.name),
229
+ kind: 'data',
230
+ name: data.name,
231
+ type: data.dataType,
232
+ source: data.source
233
+ });
234
+ }
235
+ // Add local value nodes
236
+ for (const local of document.locals) {
237
+ addNode({
238
+ id: nodeId('locals', local.name),
239
+ kind: 'locals',
240
+ name: local.name,
241
+ source: local.source
242
+ });
243
+ }
244
+ }
245
+ /**
246
+ * Extracts all references from a record of attributes.
247
+ */
248
+ function referencesFromAttributes(attributes) {
249
+ if (!attributes) {
250
+ return [];
251
+ }
252
+ return Object.values(attributes).flatMap((value) => referencesFromValue(value));
253
+ }
254
+ /**
255
+ * Recursively extracts references from nested blocks.
256
+ */
257
+ function referencesFromNestedBlocks(blocks) {
258
+ const refs = [];
259
+ for (const block of blocks) {
260
+ refs.push(...referencesFromAttributes(block.attributes));
261
+ refs.push(...referencesFromNestedBlocks(block.blocks));
262
+ }
263
+ return refs;
264
+ }
265
+ /**
266
+ * Extracts all references from a value (recursively for arrays and objects).
267
+ */
268
+ function referencesFromValue(value) {
269
+ if (!value) {
270
+ return [];
271
+ }
272
+ const direct = value.references ?? [];
273
+ if (value.type === 'object' && value.value) {
274
+ return [...direct, ...referencesFromAttributes(value.value)];
275
+ }
276
+ if (value.type === 'array' && Array.isArray(value.value)) {
277
+ return [
278
+ ...direct,
279
+ ...value.value.flatMap((item) => referencesFromValue(item))
280
+ ];
281
+ }
282
+ return direct;
283
+ }
284
+ /**
285
+ * Creates a node ID from kind and name components.
286
+ */
287
+ function nodeId(kind, primary, secondary) {
288
+ return [kind, primary, secondary].filter(Boolean).join('.');
289
+ }
290
+ /**
291
+ * Ensures a target node exists for a reference, creating a placeholder if needed.
292
+ */
293
+ function ensureTargetNode(ref, nodes) {
294
+ const existing = nodes.get(referenceToId(ref));
295
+ if (existing) {
296
+ return existing;
297
+ }
298
+ const placeholder = referenceToNode(ref);
299
+ if (!placeholder) {
300
+ return undefined;
301
+ }
302
+ nodes.set(placeholder.id, placeholder);
303
+ return placeholder;
304
+ }
305
+ /**
306
+ * Converts a reference to its corresponding node ID.
307
+ */
308
+ function referenceToId(ref) {
309
+ switch (ref.kind) {
310
+ case 'variable':
311
+ return nodeId('variable', ref.name);
312
+ case 'local':
313
+ return nodeId('locals', ref.name);
314
+ case 'module_output':
315
+ return nodeId('module_output', ref.module, ref.name);
316
+ case 'data':
317
+ return nodeId('data', ref.data_type, ref.name);
318
+ case 'resource':
319
+ return nodeId('resource', ref.resource_type, ref.name);
320
+ case 'path':
321
+ return nodeId('path', ref.name);
322
+ case 'each':
323
+ return nodeId('each', ref.property);
324
+ case 'count':
325
+ return nodeId('count', ref.property);
326
+ case 'self':
327
+ return nodeId('self', ref.attribute);
328
+ default:
329
+ return '';
330
+ }
331
+ }
332
+ /**
333
+ * Converts a reference to a placeholder node.
334
+ */
335
+ function referenceToNode(ref) {
336
+ switch (ref.kind) {
337
+ case 'variable':
338
+ return { id: referenceToId(ref), kind: 'variable', name: ref.name };
339
+ case 'local':
340
+ return { id: referenceToId(ref), kind: 'locals', name: ref.name };
341
+ case 'module_output':
342
+ return {
343
+ id: referenceToId(ref),
344
+ kind: 'module_output',
345
+ name: ref.name,
346
+ type: ref.module
347
+ };
348
+ case 'data':
349
+ return {
350
+ id: referenceToId(ref),
351
+ kind: 'data',
352
+ name: ref.name,
353
+ type: ref.data_type
354
+ };
355
+ case 'resource':
356
+ return {
357
+ id: referenceToId(ref),
358
+ kind: 'resource',
359
+ name: ref.name,
360
+ type: ref.resource_type
361
+ };
362
+ case 'path':
363
+ return { id: referenceToId(ref), kind: 'path', name: ref.name };
364
+ case 'each':
365
+ return { id: referenceToId(ref), kind: 'each', name: ref.property };
366
+ case 'count':
367
+ return { id: referenceToId(ref), kind: 'count', name: ref.property };
368
+ case 'self':
369
+ return { id: referenceToId(ref), kind: 'self', name: ref.attribute };
370
+ default:
371
+ return { id: `external.${JSON.stringify(ref)}`, kind: 'external', name: 'external' };
372
+ }
373
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Scanner for identifying top-level HCL blocks in Terraform configuration files.
3
+ * Handles block detection, label extraction, and body isolation.
4
+ */
5
+ import { HclBlock } from '../../types/blocks';
6
+ /**
7
+ * Options for block scanning.
8
+ */
9
+ export interface ScanOptions {
10
+ /** Whether to throw ParseError on syntax errors (default: false, logs warning instead) */
11
+ strict?: boolean;
12
+ }
13
+ /**
14
+ * Scanner for extracting top-level HCL blocks from Terraform files.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const scanner = new BlockScanner();
19
+ * const blocks = scanner.scan(hclContent, 'main.tf');
20
+ * for (const block of blocks) {
21
+ * console.log(`Found ${block.kind} block: ${block.labels.join('.')}`);
22
+ * }
23
+ * ```
24
+ */
25
+ export declare class BlockScanner {
26
+ /**
27
+ * Scans HCL content and extracts all top-level blocks.
28
+ *
29
+ * @param content - The HCL source content to scan
30
+ * @param source - The source file path (for error reporting)
31
+ * @param options - Scanning options
32
+ * @returns Array of parsed HCL blocks
33
+ * @throws {ParseError} If strict mode is enabled and syntax errors are found
34
+ */
35
+ scan(content: string, source: string, options?: ScanOptions): HclBlock[];
36
+ }
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ /**
3
+ * Scanner for identifying top-level HCL blocks in Terraform configuration files.
4
+ * Handles block detection, label extraction, and body isolation.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.BlockScanner = void 0;
8
+ const errors_1 = require("../common/errors");
9
+ const hclLexer_1 = require("./hclLexer");
10
+ const logger_1 = require("../common/logger");
11
+ /**
12
+ * Set of known Terraform block types.
13
+ * Unknown block types are categorized as 'unknown'.
14
+ */
15
+ const KNOWN_BLOCKS = new Set([
16
+ 'terraform',
17
+ 'locals',
18
+ 'provider',
19
+ 'variable',
20
+ 'output',
21
+ 'module',
22
+ 'resource',
23
+ 'data',
24
+ 'moved',
25
+ 'import',
26
+ 'check',
27
+ 'terraform_data'
28
+ ]);
29
+ /**
30
+ * Scanner for extracting top-level HCL blocks from Terraform files.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const scanner = new BlockScanner();
35
+ * const blocks = scanner.scan(hclContent, 'main.tf');
36
+ * for (const block of blocks) {
37
+ * console.log(`Found ${block.kind} block: ${block.labels.join('.')}`);
38
+ * }
39
+ * ```
40
+ */
41
+ class BlockScanner {
42
+ /**
43
+ * Scans HCL content and extracts all top-level blocks.
44
+ *
45
+ * @param content - The HCL source content to scan
46
+ * @param source - The source file path (for error reporting)
47
+ * @param options - Scanning options
48
+ * @returns Array of parsed HCL blocks
49
+ * @throws {ParseError} If strict mode is enabled and syntax errors are found
50
+ */
51
+ scan(content, source, options) {
52
+ const blocks = [];
53
+ const length = content.length;
54
+ let index = 0;
55
+ const strict = options?.strict ?? false;
56
+ while (index < length) {
57
+ index = (0, hclLexer_1.skipWhitespaceAndComments)(content, index);
58
+ // Skip standalone strings (not part of block headers)
59
+ if ((0, hclLexer_1.isQuote)(content[index])) {
60
+ index = (0, hclLexer_1.skipString)(content, index);
61
+ continue;
62
+ }
63
+ const identifierStart = index;
64
+ const keyword = (0, hclLexer_1.readIdentifier)(content, index);
65
+ if (!keyword) {
66
+ index++;
67
+ continue;
68
+ }
69
+ index += keyword.length;
70
+ index = (0, hclLexer_1.skipWhitespaceAndComments)(content, index);
71
+ // Read block labels (quoted strings)
72
+ const labels = [];
73
+ while ((0, hclLexer_1.isQuote)(content[index])) {
74
+ const { text, end } = (0, hclLexer_1.readQuotedString)(content, index);
75
+ labels.push(text);
76
+ index = (0, hclLexer_1.skipWhitespaceAndComments)(content, end);
77
+ }
78
+ // Check for opening brace
79
+ if (content[index] !== '{') {
80
+ // Not a block header, continue searching
81
+ index = identifierStart + keyword.length;
82
+ continue;
83
+ }
84
+ const braceIndex = index;
85
+ const endIndex = (0, hclLexer_1.findMatchingBrace)(content, braceIndex);
86
+ if (endIndex === -1) {
87
+ const location = (0, errors_1.offsetToLocation)(content, braceIndex);
88
+ const message = `Unclosed block '${keyword}': missing closing '}'`;
89
+ if (strict) {
90
+ throw new errors_1.ParseError(message, source, location);
91
+ }
92
+ logger_1.logger.warn(`${message} in ${source}:${location.line}:${location.column}`);
93
+ break;
94
+ }
95
+ const raw = normalizeRaw(content.slice(identifierStart, endIndex + 1));
96
+ const body = content.slice(braceIndex + 1, endIndex);
97
+ const kind = (KNOWN_BLOCKS.has(keyword) ? keyword : 'unknown');
98
+ blocks.push({
99
+ kind,
100
+ keyword,
101
+ labels,
102
+ body: body.trim(),
103
+ raw,
104
+ source
105
+ });
106
+ index = endIndex + 1;
107
+ }
108
+ return blocks;
109
+ }
110
+ }
111
+ exports.BlockScanner = BlockScanner;
112
+ /**
113
+ * Normalizes raw block content for consistent formatting.
114
+ * - Removes common leading indentation
115
+ * - Normalizes whitespace around '=' operators
116
+ * - Trims trailing whitespace from lines
117
+ *
118
+ * @param raw - The raw block content
119
+ * @returns Normalized block content
120
+ */
121
+ function normalizeRaw(raw) {
122
+ const trimmed = raw.trim();
123
+ const lines = trimmed.split(/\r?\n/);
124
+ if (lines.length === 1) {
125
+ return lines[0];
126
+ }
127
+ // Calculate minimum indentation (excluding first line and empty lines)
128
+ const indents = lines
129
+ .slice(1)
130
+ .filter((line) => line.trim().length > 0)
131
+ .map((line) => (line.match(/^(\s*)/)?.[1].length ?? 0));
132
+ const minIndent = indents.length ? Math.min(...indents) : 0;
133
+ // Normalize alignment around '=' operators
134
+ const normalizeAlignment = (line) => line
135
+ .replace(/\s{2,}=\s*/g, ' = ')
136
+ .replace(/\s*=\s{2,}/g, ' = ')
137
+ .trimEnd();
138
+ const normalized = lines.map((line, index) => {
139
+ const withoutIndent = index === 0 ? line.trimStart() : line.slice(Math.min(minIndent, line.length));
140
+ return normalizeAlignment(withoutIndent);
141
+ });
142
+ return normalized.join('\n');
143
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Shared lexer utilities for HCL parsing.
3
+ * Provides common functions for tokenization and string handling.
4
+ */
5
+ /**
6
+ * Result of reading a quoted string from source.
7
+ */
8
+ export interface QuotedStringResult {
9
+ /** The unquoted string content */
10
+ text: string;
11
+ /** The index after the closing quote */
12
+ end: number;
13
+ }
14
+ /**
15
+ * Result of reading a raw value from source.
16
+ */
17
+ export interface ValueReadResult {
18
+ /** The raw value text (trimmed) */
19
+ raw: string;
20
+ /** The index after the value */
21
+ end: number;
22
+ }
23
+ /**
24
+ * Checks if a character is a quote character (single or double).
25
+ * @param char - The character to check
26
+ * @returns True if the character is a quote
27
+ */
28
+ export declare function isQuote(char: string | undefined): boolean;
29
+ /**
30
+ * Checks if a character at a given position is escaped by counting preceding backslashes.
31
+ * Handles consecutive backslashes correctly (e.g., \\\\ is two escaped backslashes).
32
+ * @param text - The source text
33
+ * @param index - The index of the character to check
34
+ * @returns True if the character is escaped
35
+ */
36
+ export declare function isEscaped(text: string, index: number): boolean;
37
+ /**
38
+ * Skips whitespace and comments (line and block comments).
39
+ * Handles "//", block comments, and "#" style comments.
40
+ * @param text - The source text
41
+ * @param start - The starting index
42
+ * @returns The index of the next non-whitespace, non-comment character
43
+ */
44
+ export declare function skipWhitespaceAndComments(text: string, start: number): number;
45
+ /**
46
+ * Skips a quoted string, handling escape sequences correctly.
47
+ * @param text - The source text
48
+ * @param start - The index of the opening quote
49
+ * @returns The index after the closing quote
50
+ */
51
+ export declare function skipString(text: string, start: number): number;
52
+ /**
53
+ * Skips a heredoc string (<<EOF or <<-EOF style).
54
+ * @param text - The source text
55
+ * @param start - The index of the first '<'
56
+ * @returns The index after the heredoc terminator
57
+ */
58
+ export declare function skipHeredoc(text: string, start: number): number;
59
+ /**
60
+ * Finds the matching closing brace for an opening brace.
61
+ * Handles nested braces, strings, comments, and heredocs.
62
+ * @param content - The source text
63
+ * @param startIndex - The index of the opening brace
64
+ * @returns The index of the matching closing brace, or -1 if not found
65
+ */
66
+ export declare function findMatchingBrace(content: string, startIndex: number): number;
67
+ /**
68
+ * Finds the matching closing bracket for an opening bracket.
69
+ * Handles nested brackets of all types [], {}, ().
70
+ * @param content - The source text
71
+ * @param startIndex - The index of the opening bracket
72
+ * @param openChar - The opening bracket character
73
+ * @param closeChar - The closing bracket character
74
+ * @returns The index of the matching closing bracket, or -1 if not found
75
+ */
76
+ export declare function findMatchingBracket(content: string, startIndex: number, openChar: string, closeChar: string): number;
77
+ /**
78
+ * Reads an identifier from the source text.
79
+ * Identifiers start with a letter or underscore, followed by letters, digits, underscores, or hyphens.
80
+ * @param text - The source text
81
+ * @param start - The starting index
82
+ * @returns The identifier string, or empty string if no identifier found
83
+ */
84
+ export declare function readIdentifier(text: string, start: number): string;
85
+ /**
86
+ * Reads an identifier that may contain dots (for attribute access).
87
+ * @param text - The source text
88
+ * @param start - The starting index
89
+ * @returns The identifier string, or empty string if no identifier found
90
+ */
91
+ export declare function readDottedIdentifier(text: string, start: number): string;
92
+ /**
93
+ * Reads a quoted string and returns its unescaped content.
94
+ * @param text - The source text
95
+ * @param start - The index of the opening quote
96
+ * @returns The unquoted string and the index after the closing quote
97
+ */
98
+ export declare function readQuotedString(text: string, start: number): QuotedStringResult;
99
+ /**
100
+ * Reads a value from HCL source (handles multiline values in brackets).
101
+ * @param text - The source text
102
+ * @param start - The starting index (after the '=' sign)
103
+ * @returns The raw value text and the index after the value
104
+ */
105
+ export declare function readValue(text: string, start: number): ValueReadResult;
106
+ /**
107
+ * Splits an array literal into its elements.
108
+ * Handles nested arrays, objects, and strings correctly.
109
+ * @param raw - The raw array string including brackets
110
+ * @returns Array of raw element strings
111
+ */
112
+ export declare function splitArrayElements(raw: string): string[];
113
+ /**
114
+ * Splits an object literal into key-value pairs.
115
+ * Handles nested objects, arrays, and strings correctly.
116
+ * @param raw - The raw object string including braces
117
+ * @returns Array of [key, value] tuples
118
+ */
119
+ export declare function splitObjectEntries(raw: string): Array<[string, string]>;