@safeaccess/inline 0.1.1 → 0.1.2
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.
- package/.gitattributes +1 -1
- package/CHANGELOG.md +10 -5
- package/LICENSE +1 -1
- package/README.md +56 -14
- package/dist/accessors/abstract-accessor.d.ts +22 -10
- package/dist/accessors/abstract-accessor.js +21 -8
- package/dist/accessors/abstract-integration-accessor.d.ts +22 -0
- package/dist/accessors/abstract-integration-accessor.js +23 -0
- package/dist/accessors/formats/any-accessor.d.ts +10 -8
- package/dist/accessors/formats/any-accessor.js +9 -8
- package/dist/accessors/formats/array-accessor.d.ts +2 -0
- package/dist/accessors/formats/array-accessor.js +2 -0
- package/dist/accessors/formats/env-accessor.d.ts +2 -0
- package/dist/accessors/formats/env-accessor.js +2 -0
- package/dist/accessors/formats/ini-accessor.d.ts +2 -0
- package/dist/accessors/formats/ini-accessor.js +2 -0
- package/dist/accessors/formats/json-accessor.d.ts +2 -0
- package/dist/accessors/formats/json-accessor.js +2 -0
- package/dist/accessors/formats/ndjson-accessor.d.ts +2 -0
- package/dist/accessors/formats/ndjson-accessor.js +2 -0
- package/dist/accessors/formats/object-accessor.d.ts +2 -0
- package/dist/accessors/formats/object-accessor.js +2 -0
- package/dist/accessors/formats/xml-accessor.d.ts +2 -0
- package/dist/accessors/formats/xml-accessor.js +2 -0
- package/dist/accessors/formats/yaml-accessor.d.ts +3 -1
- package/dist/accessors/formats/yaml-accessor.js +4 -2
- package/dist/cache/simple-path-cache.d.ts +51 -0
- package/dist/cache/simple-path-cache.js +72 -0
- package/dist/contracts/accessors-interface.d.ts +2 -0
- package/dist/contracts/factory-accessors-interface.d.ts +2 -0
- package/dist/contracts/filter-evaluator-interface.d.ts +28 -0
- package/dist/contracts/filter-evaluator-interface.js +1 -0
- package/dist/contracts/parse-integration-interface.d.ts +2 -0
- package/dist/contracts/parser-interface.d.ts +92 -0
- package/dist/contracts/parser-interface.js +1 -0
- package/dist/contracts/path-cache-interface.d.ts +7 -6
- package/dist/contracts/readable-accessors-interface.d.ts +11 -6
- package/dist/contracts/security-guard-interface.d.ts +2 -0
- package/dist/contracts/security-parser-interface.d.ts +2 -0
- package/dist/contracts/validatable-parser-interface.d.ts +59 -0
- package/dist/contracts/validatable-parser-interface.js +1 -0
- package/dist/contracts/writable-accessors-interface.d.ts +5 -0
- package/dist/core/accessor-factory.d.ts +124 -0
- package/dist/core/accessor-factory.js +157 -0
- package/dist/core/dot-notation-parser.d.ts +34 -5
- package/dist/core/dot-notation-parser.js +51 -10
- package/dist/core/inline-builder-accessor.d.ts +82 -0
- package/dist/core/inline-builder-accessor.js +107 -0
- package/dist/exceptions/accessor-exception.d.ts +9 -0
- package/dist/exceptions/accessor-exception.js +9 -0
- package/dist/exceptions/invalid-format-exception.d.ts +5 -0
- package/dist/exceptions/invalid-format-exception.js +5 -0
- package/dist/exceptions/parser-exception.d.ts +4 -0
- package/dist/exceptions/parser-exception.js +4 -0
- package/dist/exceptions/path-not-found-exception.d.ts +4 -0
- package/dist/exceptions/path-not-found-exception.js +4 -0
- package/dist/exceptions/readonly-violation-exception.d.ts +4 -0
- package/dist/exceptions/readonly-violation-exception.js +4 -0
- package/dist/exceptions/security-exception.d.ts +6 -0
- package/dist/exceptions/security-exception.js +6 -0
- package/dist/exceptions/unsupported-type-exception.d.ts +4 -0
- package/dist/exceptions/unsupported-type-exception.js +4 -0
- package/dist/exceptions/yaml-parse-exception.d.ts +4 -0
- package/dist/exceptions/yaml-parse-exception.js +4 -0
- package/dist/index.js +2 -1
- package/dist/inline.d.ts +22 -56
- package/dist/inline.js +39 -111
- package/dist/parser/xml-parser.js +23 -10
- package/dist/parser/yaml-parser.d.ts +54 -7
- package/dist/parser/yaml-parser.js +268 -51
- package/dist/path-query/segment-filter-parser.d.ts +142 -0
- package/dist/path-query/segment-filter-parser.js +384 -0
- package/dist/path-query/segment-parser.d.ts +98 -0
- package/dist/path-query/segment-parser.js +283 -0
- package/dist/path-query/segment-path-resolver.d.ts +149 -0
- package/dist/path-query/segment-path-resolver.js +351 -0
- package/dist/path-query/segment-type.d.ts +85 -0
- package/dist/path-query/segment-type.js +35 -0
- package/dist/security/forbidden-keys.d.ts +2 -2
- package/dist/security/forbidden-keys.js +5 -5
- package/dist/security/security-guard.d.ts +3 -1
- package/dist/security/security-guard.js +5 -2
- package/dist/security/security-parser.d.ts +10 -1
- package/dist/security/security-parser.js +10 -1
- package/dist/type-format.d.ts +2 -0
- package/dist/type-format.js +2 -0
- package/package.json +11 -3
- package/src/accessors/abstract-accessor.ts +23 -19
- package/src/accessors/abstract-integration-accessor.ts +27 -0
- package/src/accessors/formats/any-accessor.ts +11 -11
- package/src/accessors/formats/array-accessor.ts +2 -0
- package/src/accessors/formats/env-accessor.ts +2 -0
- package/src/accessors/formats/ini-accessor.ts +2 -0
- package/src/accessors/formats/json-accessor.ts +2 -0
- package/src/accessors/formats/ndjson-accessor.ts +2 -0
- package/src/accessors/formats/object-accessor.ts +2 -0
- package/src/accessors/formats/xml-accessor.ts +2 -0
- package/src/accessors/formats/yaml-accessor.ts +4 -2
- package/src/cache/simple-path-cache.ts +77 -0
- package/src/contracts/accessors-interface.ts +2 -0
- package/src/contracts/factory-accessors-interface.ts +2 -0
- package/src/contracts/filter-evaluator-interface.ts +30 -0
- package/src/contracts/parse-integration-interface.ts +2 -0
- package/src/contracts/parser-interface.ts +114 -0
- package/src/contracts/path-cache-interface.ts +8 -6
- package/src/contracts/readable-accessors-interface.ts +11 -6
- package/src/contracts/security-guard-interface.ts +2 -0
- package/src/contracts/security-parser-interface.ts +2 -0
- package/src/contracts/validatable-parser-interface.ts +64 -0
- package/src/contracts/writable-accessors-interface.ts +5 -0
- package/src/core/accessor-factory.ts +173 -0
- package/src/core/dot-notation-parser.ts +74 -11
- package/src/core/inline-builder-accessor.ts +163 -0
- package/src/exceptions/accessor-exception.ts +9 -0
- package/src/exceptions/invalid-format-exception.ts +5 -0
- package/src/exceptions/parser-exception.ts +4 -0
- package/src/exceptions/path-not-found-exception.ts +4 -0
- package/src/exceptions/readonly-violation-exception.ts +4 -0
- package/src/exceptions/security-exception.ts +6 -0
- package/src/exceptions/unsupported-type-exception.ts +4 -0
- package/src/exceptions/yaml-parse-exception.ts +4 -0
- package/src/index.ts +3 -1
- package/src/inline.ts +42 -120
- package/src/parser/xml-parser.ts +31 -10
- package/src/parser/yaml-parser.ts +310 -45
- package/src/path-query/segment-filter-parser.ts +444 -0
- package/src/path-query/segment-parser.ts +321 -0
- package/src/path-query/segment-path-resolver.ts +521 -0
- package/src/path-query/segment-type.ts +82 -0
- package/src/security/forbidden-keys.ts +5 -5
- package/src/security/security-guard.ts +7 -2
- package/src/security/security-parser.ts +18 -3
- package/src/type-format.ts +2 -0
- package/stryker.config.json +8 -10
- package/tests/accessors/abstract-accessor.test.ts +217 -0
- package/tests/accessors/abstract-integration-accessor.test.ts +37 -0
- package/tests/accessors/formats/any-accessor.test.ts +57 -0
- package/tests/accessors/formats/array-accessor.test.ts +42 -0
- package/tests/accessors/formats/env-accessor.test.ts +103 -0
- package/tests/accessors/formats/ini-accessor.test.ts +186 -0
- package/tests/accessors/{json-accessor.test.ts → formats/json-accessor.test.ts} +6 -6
- package/tests/accessors/formats/ndjson-accessor.test.ts +49 -0
- package/tests/accessors/formats/object-accessor.test.ts +172 -0
- package/tests/accessors/formats/xml-accessor.test.ts +162 -0
- package/tests/accessors/formats/yaml-accessor.test.ts +36 -0
- package/tests/cache/simple-path-cache.test.ts +168 -0
- package/tests/core/accessor-factory.test.ts +157 -0
- package/tests/core/dot-notation-parser-edge-cases.test.ts +415 -0
- package/tests/core/dot-notation-parser.test.ts +0 -288
- package/tests/core/inline-builder-accessor.test.ts +114 -0
- package/tests/exceptions/accessor-exception.test.ts +28 -0
- package/tests/exceptions/invalid-format-exception.test.ts +31 -0
- package/tests/exceptions/path-not-found-exception.test.ts +33 -0
- package/tests/exceptions/readonly-violation-exception.test.ts +35 -0
- package/tests/exceptions/security-exception.test.ts +33 -0
- package/tests/exceptions/unsupported-type-exception.test.ts +33 -0
- package/tests/exceptions/yaml-parse-exception.test.ts +38 -0
- package/tests/mocks/fake-path-cache.ts +4 -3
- package/tests/parity-from.test.ts +118 -0
- package/tests/parity.test.ts +227 -10
- package/tests/parser/xml-parser-mutations.test.ts +579 -0
- package/tests/parser/xml-parser-scanner.test.ts +332 -0
- package/tests/parser/xml-parser.test.ts +10 -334
- package/tests/parser/yaml-parser-mutations.test.ts +750 -0
- package/tests/parser/yaml-parser.test.ts +844 -18
- package/tests/path-query/segment-filter-parser-mutations.test.ts +735 -0
- package/tests/path-query/segment-filter-parser.test.ts +1091 -0
- package/tests/path-query/segment-parser-mutations.test.ts +539 -0
- package/tests/path-query/segment-parser.test.ts +606 -0
- package/tests/path-query/segment-path-resolver-mutations.test.ts +626 -0
- package/tests/path-query/segment-path-resolver.test.ts +1009 -0
- package/tests/security/security-guard-advanced.test.ts +413 -0
- package/tests/security/security-guard-forbidden-keys.test.ts +87 -0
- package/tests/security/security-guard.test.ts +3 -484
- package/tests/security/security-parser.test.ts +18 -14
- package/vitest.config.ts +3 -3
- package/benchmarks/get.bench.ts +0 -26
- package/benchmarks/parse.bench.ts +0 -41
- package/tests/accessors/accessors.test.ts +0 -1017
|
@@ -7,14 +7,26 @@ import { YamlParseException } from '../exceptions/yaml-parse-exception.js';
|
|
|
7
7
|
* tags (!! and !), anchors (&), aliases (*), and merge keys (<<).
|
|
8
8
|
*
|
|
9
9
|
* Does not depend on external YAML libraries, making the package portable.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
10
12
|
*/
|
|
11
13
|
export class YamlParser {
|
|
14
|
+
private readonly maxDepth: number;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param maxDepth - Maximum allowed nesting depth during parsing.
|
|
18
|
+
* Defaults to 512 to match SecurityParser.maxDepth.
|
|
19
|
+
*/
|
|
20
|
+
constructor(maxDepth: number = 512) {
|
|
21
|
+
this.maxDepth = maxDepth;
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
/**
|
|
13
25
|
* Parse a YAML string into a plain object.
|
|
14
26
|
*
|
|
15
27
|
* @param yaml - Raw YAML content.
|
|
16
28
|
* @returns Parsed data structure.
|
|
17
|
-
* @throws {YamlParseException} When unsafe constructs
|
|
29
|
+
* @throws {YamlParseException} When unsafe constructs, syntax errors, or nesting depth exceeded.
|
|
18
30
|
*
|
|
19
31
|
* @example
|
|
20
32
|
* new YamlParser().parse('key: value'); // { key: 'value' }
|
|
@@ -22,7 +34,7 @@ export class YamlParser {
|
|
|
22
34
|
parse(yaml: string): Record<string, unknown> {
|
|
23
35
|
const lines = yaml.replace(/\r\n/g, '\n').split('\n');
|
|
24
36
|
this.assertNoUnsafeConstructs(lines);
|
|
25
|
-
const result = this.parseLines(lines, 0, 0, lines.length);
|
|
37
|
+
const result = this.parseLines(lines, 0, 0, lines.length, 0);
|
|
26
38
|
if (Array.isArray(result)) {
|
|
27
39
|
return {};
|
|
28
40
|
}
|
|
@@ -73,9 +85,23 @@ export class YamlParser {
|
|
|
73
85
|
* @param baseIndent - Indentation level for this block.
|
|
74
86
|
* @param start - First line index (inclusive).
|
|
75
87
|
* @param end - Last line index (exclusive).
|
|
88
|
+
* @param depth - Current nesting depth.
|
|
76
89
|
* @returns Parsed value.
|
|
90
|
+
*
|
|
91
|
+
* @throws {YamlParseException} When nesting depth exceeds the configured maximum.
|
|
77
92
|
*/
|
|
78
|
-
private parseLines(
|
|
93
|
+
private parseLines(
|
|
94
|
+
lines: string[],
|
|
95
|
+
baseIndent: number,
|
|
96
|
+
start: number,
|
|
97
|
+
end: number,
|
|
98
|
+
depth: number = 0,
|
|
99
|
+
): unknown {
|
|
100
|
+
if (depth > this.maxDepth) {
|
|
101
|
+
throw new YamlParseException(
|
|
102
|
+
`YAML nesting depth ${depth} exceeds maximum of ${this.maxDepth}.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
79
105
|
const mapResult: Record<string, unknown> = {};
|
|
80
106
|
const arrResult: unknown[] = [];
|
|
81
107
|
let isSequence = false;
|
|
@@ -120,15 +146,18 @@ export class YamlParser {
|
|
|
120
146
|
i,
|
|
121
147
|
childIndent,
|
|
122
148
|
childEnd,
|
|
149
|
+
depth,
|
|
123
150
|
);
|
|
124
|
-
this.mergeChildLines(lines, i + 1, childEnd, childIndent, subMap);
|
|
151
|
+
this.mergeChildLines(lines, i + 1, childEnd, childIndent, subMap, depth);
|
|
125
152
|
arrResult.push(subMap);
|
|
126
153
|
i = childEnd;
|
|
127
154
|
} else if (itemContent === '') {
|
|
128
155
|
const childIndent = currentIndent + 2;
|
|
129
156
|
const childEnd = this.findBlockEnd(lines, childIndent, i + 1, end);
|
|
130
157
|
if (childEnd > i + 1) {
|
|
131
|
-
arrResult.push(
|
|
158
|
+
arrResult.push(
|
|
159
|
+
this.parseLines(lines, childIndent, i + 1, childEnd, depth + 1),
|
|
160
|
+
);
|
|
132
161
|
i = childEnd;
|
|
133
162
|
} else {
|
|
134
163
|
arrResult.push(null);
|
|
@@ -148,7 +177,14 @@ export class YamlParser {
|
|
|
148
177
|
const rawValue = mapMatch[2] as string;
|
|
149
178
|
const childIndent = currentIndent + 2;
|
|
150
179
|
const childEnd = this.findBlockEnd(lines, childIndent, i + 1, end);
|
|
151
|
-
mapResult[key] = this.resolveValue(
|
|
180
|
+
mapResult[key] = this.resolveValue(
|
|
181
|
+
rawValue,
|
|
182
|
+
lines,
|
|
183
|
+
i,
|
|
184
|
+
childIndent,
|
|
185
|
+
childEnd,
|
|
186
|
+
depth,
|
|
187
|
+
);
|
|
152
188
|
i = childEnd;
|
|
153
189
|
continue;
|
|
154
190
|
}
|
|
@@ -171,6 +207,8 @@ export class YamlParser {
|
|
|
171
207
|
* @param end - Last child line index (exclusive).
|
|
172
208
|
* @param childIndent - Expected indentation for child lines.
|
|
173
209
|
* @param map - Map to merge values into.
|
|
210
|
+
* @param depth - Current nesting depth.
|
|
211
|
+
* @throws {YamlParseException} When resolved values exceed the configured nesting depth.
|
|
174
212
|
*/
|
|
175
213
|
private mergeChildLines(
|
|
176
214
|
lines: string[],
|
|
@@ -178,6 +216,7 @@ export class YamlParser {
|
|
|
178
216
|
end: number,
|
|
179
217
|
childIndent: number,
|
|
180
218
|
map: Record<string, unknown>,
|
|
219
|
+
depth: number,
|
|
181
220
|
): void {
|
|
182
221
|
let ci = start;
|
|
183
222
|
while (ci < end) {
|
|
@@ -206,6 +245,7 @@ export class YamlParser {
|
|
|
206
245
|
ci,
|
|
207
246
|
childCurrentIndent + 2,
|
|
208
247
|
nextChildEnd,
|
|
248
|
+
depth,
|
|
209
249
|
);
|
|
210
250
|
ci = nextChildEnd;
|
|
211
251
|
continue;
|
|
@@ -224,7 +264,9 @@ export class YamlParser {
|
|
|
224
264
|
* @param lineIndex - Line where the value was found.
|
|
225
265
|
* @param childIndent - Expected indentation for child block.
|
|
226
266
|
* @param childEnd - End index of the child block.
|
|
267
|
+
* @param depth - Current nesting depth.
|
|
227
268
|
* @returns Resolved typed value.
|
|
269
|
+
* @throws {YamlParseException} When child block nesting exceeds the configured maximum.
|
|
228
270
|
*/
|
|
229
271
|
private resolveValue(
|
|
230
272
|
rawValue: string,
|
|
@@ -232,41 +274,49 @@ export class YamlParser {
|
|
|
232
274
|
lineIndex: number,
|
|
233
275
|
childIndent: number,
|
|
234
276
|
childEnd: number,
|
|
277
|
+
depth: number = 0,
|
|
235
278
|
): unknown {
|
|
236
|
-
const trimmed = rawValue.trim();
|
|
279
|
+
const trimmed = this.stripInlineComment(rawValue.trim());
|
|
237
280
|
|
|
238
|
-
// Block scalar literal (
|
|
239
|
-
if (trimmed
|
|
281
|
+
// Block scalar literal (|, |-, |+) or folded (>, >-, >+)
|
|
282
|
+
if (/^\|[+-]?$/.test(trimmed) || /^>[+-]?$/.test(trimmed)) {
|
|
240
283
|
return this.parseBlockScalar(
|
|
241
284
|
lines,
|
|
242
285
|
lineIndex + 1,
|
|
243
286
|
childEnd,
|
|
244
287
|
childIndent,
|
|
245
|
-
trimmed
|
|
288
|
+
trimmed.startsWith('>'),
|
|
289
|
+
trimmed,
|
|
246
290
|
);
|
|
247
291
|
}
|
|
248
292
|
|
|
249
|
-
// Inline flow
|
|
250
|
-
if (trimmed.startsWith('[')
|
|
251
|
-
return this.
|
|
293
|
+
// Inline flow sequence [a, b, c]
|
|
294
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
295
|
+
return this.parseFlowSequence(trimmed);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Inline flow map {a: b, c: d}
|
|
299
|
+
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
300
|
+
return this.parseFlowMap(trimmed);
|
|
252
301
|
}
|
|
253
302
|
|
|
254
303
|
// Nested block
|
|
255
304
|
if (trimmed === '' && childEnd > lineIndex + 1) {
|
|
256
|
-
return this.parseLines(lines, childIndent, lineIndex + 1, childEnd);
|
|
305
|
+
return this.parseLines(lines, childIndent, lineIndex + 1, childEnd, depth + 1);
|
|
257
306
|
}
|
|
258
307
|
|
|
259
308
|
return this.castScalar(trimmed);
|
|
260
309
|
}
|
|
261
310
|
|
|
262
311
|
/**
|
|
263
|
-
* Parse a YAML block scalar (literal | or folded >).
|
|
312
|
+
* Parse a YAML block scalar (literal | or folded >) with chomping modifiers.
|
|
264
313
|
*
|
|
265
314
|
* @param lines - All lines of the YAML document.
|
|
266
315
|
* @param start - First line of the scalar block (inclusive).
|
|
267
316
|
* @param end - Last line of the scalar block (exclusive).
|
|
268
|
-
* @param indent -
|
|
317
|
+
* @param indent - Expected minimum indentation for scalar lines.
|
|
269
318
|
* @param folded - Whether to fold lines (> style) or keep literal (| style).
|
|
319
|
+
* @param chomping - Chomping indicator (|, |-, |+, >, >-, >+).
|
|
270
320
|
* @returns The assembled string.
|
|
271
321
|
*/
|
|
272
322
|
private parseBlockScalar(
|
|
@@ -275,65 +325,280 @@ export class YamlParser {
|
|
|
275
325
|
end: number,
|
|
276
326
|
indent: number,
|
|
277
327
|
folded: boolean,
|
|
328
|
+
chomping: string = '|',
|
|
278
329
|
): string {
|
|
279
|
-
const
|
|
330
|
+
const blockLines: string[] = [];
|
|
331
|
+
let actualIndent: number | null = null;
|
|
332
|
+
|
|
280
333
|
for (let i = start; i < end; i++) {
|
|
281
334
|
const line = lines[i] as string;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
335
|
+
|
|
336
|
+
if (line.trim() === '') {
|
|
337
|
+
blockLines.push('');
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const lineIndent = line.length - line.trimStart().length;
|
|
342
|
+
if (actualIndent === null) {
|
|
343
|
+
actualIndent = lineIndent;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (lineIndent < actualIndent) {
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
blockLines.push(line.substring(actualIndent));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Remove trailing empty lines for clip (default) and strip (-) modes.
|
|
354
|
+
// Keep (+) mode preserves all trailing blank lines.
|
|
355
|
+
if (!chomping.endsWith('+')) {
|
|
356
|
+
while (blockLines.length > 0 && blockLines[blockLines.length - 1] === '') {
|
|
357
|
+
blockLines.pop();
|
|
286
358
|
}
|
|
287
359
|
}
|
|
288
360
|
|
|
361
|
+
let result: string;
|
|
289
362
|
if (folded) {
|
|
290
|
-
|
|
363
|
+
result = '';
|
|
364
|
+
let prevEmpty = false;
|
|
365
|
+
for (const bl of blockLines) {
|
|
366
|
+
if (bl === '') {
|
|
367
|
+
result += '\n';
|
|
368
|
+
prevEmpty = true;
|
|
369
|
+
} else {
|
|
370
|
+
if (result !== '' && !prevEmpty && !result.endsWith('\n')) {
|
|
371
|
+
result += ' ';
|
|
372
|
+
}
|
|
373
|
+
result += bl;
|
|
374
|
+
prevEmpty = false;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
result = blockLines.join('\n');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Default YAML chomping: add trailing newline unless strip (-)
|
|
382
|
+
if (!chomping.endsWith('-') && !result.endsWith('\n')) {
|
|
383
|
+
result += '\n';
|
|
291
384
|
}
|
|
292
385
|
|
|
293
|
-
return
|
|
386
|
+
return result;
|
|
294
387
|
}
|
|
295
388
|
|
|
296
389
|
/**
|
|
297
|
-
* Parse
|
|
390
|
+
* Parse a YAML flow sequence ([a, b, c]) into an array.
|
|
298
391
|
*
|
|
299
|
-
* @param
|
|
300
|
-
* @returns Parsed
|
|
392
|
+
* @param value - Raw flow sequence string including brackets.
|
|
393
|
+
* @returns Parsed sequence values.
|
|
301
394
|
*/
|
|
302
|
-
private
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return JSON.parse(jsonLike);
|
|
307
|
-
} catch {
|
|
308
|
-
// Fallback: return raw string
|
|
309
|
-
return raw;
|
|
395
|
+
private parseFlowSequence(value: string): unknown[] {
|
|
396
|
+
const inner = value.slice(1, -1).trim();
|
|
397
|
+
if (inner === '') {
|
|
398
|
+
return [];
|
|
310
399
|
}
|
|
400
|
+
|
|
401
|
+
const items = this.splitFlowItems(inner);
|
|
402
|
+
return items.map((item) => this.castScalar(item.trim()));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Parse a YAML flow map ({a: b, c: d}) into a record.
|
|
407
|
+
*
|
|
408
|
+
* @param value - Raw flow map string including braces.
|
|
409
|
+
* @returns Parsed key-value pairs.
|
|
410
|
+
*/
|
|
411
|
+
private parseFlowMap(value: string): Record<string, unknown> {
|
|
412
|
+
const inner = value.slice(1, -1).trim();
|
|
413
|
+
if (inner === '') {
|
|
414
|
+
return {};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const result: Record<string, unknown> = {};
|
|
418
|
+
const items = this.splitFlowItems(inner);
|
|
419
|
+
for (const item of items) {
|
|
420
|
+
const trimmedItem = item.trim();
|
|
421
|
+
const colonPos = trimmedItem.indexOf(':');
|
|
422
|
+
if (colonPos === -1) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
const key = trimmedItem.substring(0, colonPos).trim();
|
|
426
|
+
const val = trimmedItem.substring(colonPos + 1).trim();
|
|
427
|
+
result[key] = this.castScalar(val);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Split flow-syntax items by comma, respecting nested brackets and quotes.
|
|
435
|
+
*
|
|
436
|
+
* @param inner - Content between outer brackets/braces.
|
|
437
|
+
* @returns Individual item strings.
|
|
438
|
+
*/
|
|
439
|
+
private splitFlowItems(inner: string): string[] {
|
|
440
|
+
const items: string[] = [];
|
|
441
|
+
let depth = 0;
|
|
442
|
+
let current = '';
|
|
443
|
+
let inQuote = false;
|
|
444
|
+
let quoteChar = '';
|
|
445
|
+
|
|
446
|
+
for (let i = 0; i < inner.length; i++) {
|
|
447
|
+
const ch = inner[i] as string;
|
|
448
|
+
|
|
449
|
+
if (inQuote) {
|
|
450
|
+
current += ch;
|
|
451
|
+
if (ch === quoteChar) {
|
|
452
|
+
inQuote = false;
|
|
453
|
+
}
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (ch === '"' || ch === "'") {
|
|
458
|
+
inQuote = true;
|
|
459
|
+
quoteChar = ch;
|
|
460
|
+
current += ch;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (ch === '[' || ch === '{') {
|
|
465
|
+
depth++;
|
|
466
|
+
current += ch;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (ch === ']' || ch === '}') {
|
|
471
|
+
depth--;
|
|
472
|
+
current += ch;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (ch === ',' && depth === 0) {
|
|
477
|
+
items.push(current);
|
|
478
|
+
current = '';
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
current += ch;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (current.trim() !== '') {
|
|
486
|
+
items.push(current);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return items;
|
|
311
490
|
}
|
|
312
491
|
|
|
313
492
|
/**
|
|
314
493
|
* Cast a scalar string to its native type.
|
|
315
494
|
*
|
|
495
|
+
* Handles quoted strings, null, boolean, integer (decimal/octal/hex),
|
|
496
|
+
* float, infinity, and NaN values.
|
|
497
|
+
*
|
|
316
498
|
* @param value - Raw scalar string.
|
|
317
499
|
* @returns Typed value (null, boolean, number, or string).
|
|
318
500
|
*/
|
|
319
501
|
private castScalar(value: string): unknown {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
502
|
+
value = value.trim();
|
|
503
|
+
|
|
504
|
+
// Quoted strings
|
|
505
|
+
if (value.length >= 2) {
|
|
506
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
507
|
+
return this.unescapeDoubleQuoted(value.slice(1, -1));
|
|
508
|
+
}
|
|
509
|
+
if (value.startsWith("'") && value.endsWith("'")) {
|
|
510
|
+
return value.slice(1, -1).replace(/''/g, "'");
|
|
511
|
+
}
|
|
512
|
+
}
|
|
323
513
|
|
|
324
|
-
//
|
|
514
|
+
// Null
|
|
325
515
|
if (
|
|
326
|
-
|
|
327
|
-
|
|
516
|
+
value === '' ||
|
|
517
|
+
value === '~' ||
|
|
518
|
+
value === 'null' ||
|
|
519
|
+
value === 'Null' ||
|
|
520
|
+
value === 'NULL'
|
|
328
521
|
) {
|
|
329
|
-
return
|
|
522
|
+
return null;
|
|
330
523
|
}
|
|
331
524
|
|
|
332
|
-
//
|
|
333
|
-
|
|
525
|
+
// Boolean
|
|
526
|
+
const lower = value.toLowerCase();
|
|
527
|
+
if (lower === 'true' || lower === 'yes' || lower === 'on') return true;
|
|
528
|
+
if (lower === 'false' || lower === 'no' || lower === 'off') return false;
|
|
529
|
+
|
|
530
|
+
// Integer patterns
|
|
531
|
+
if (/^-?(?:0|[1-9]\d*)$/.test(value)) return parseInt(value, 10);
|
|
532
|
+
if (/^0o[0-7]+$/i.test(value)) return parseInt(value.slice(2), 8);
|
|
533
|
+
if (/^0x[0-9a-fA-F]+$/.test(value)) return parseInt(value, 16);
|
|
334
534
|
|
|
335
|
-
// Float
|
|
336
|
-
if (
|
|
535
|
+
// Float patterns
|
|
536
|
+
if (/^-?(?:0|[1-9]\d*)?(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(value) && value.includes('.')) {
|
|
537
|
+
return parseFloat(value);
|
|
538
|
+
}
|
|
539
|
+
if (lower === '.inf' || lower === '+.inf') return Infinity;
|
|
540
|
+
if (lower === '-.inf') return -Infinity;
|
|
541
|
+
if (lower === '.nan') return NaN;
|
|
542
|
+
|
|
543
|
+
return value;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Unescape YAML double-quoted string escape sequences.
|
|
548
|
+
*
|
|
549
|
+
* @param value - String content between double quotes.
|
|
550
|
+
* @returns Unescaped string.
|
|
551
|
+
*/
|
|
552
|
+
private unescapeDoubleQuoted(value: string): string {
|
|
553
|
+
return value
|
|
554
|
+
.replace(/\\n/g, '\n')
|
|
555
|
+
.replace(/\\t/g, '\t')
|
|
556
|
+
.replace(/\\r/g, '\r')
|
|
557
|
+
.replace(/\\\\/g, '\\')
|
|
558
|
+
.replace(/\\"/g, '"')
|
|
559
|
+
.replace(/\\0/g, '\0')
|
|
560
|
+
.replace(/\\a/g, '\x07')
|
|
561
|
+
.replace(/\\b/g, '\x08')
|
|
562
|
+
.replace(/\\f/g, '\x0C')
|
|
563
|
+
.replace(/\\v/g, '\x0B');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Strip inline comments from a value string, respecting quoted regions.
|
|
568
|
+
*
|
|
569
|
+
* @param value - Raw value potentially containing inline comments.
|
|
570
|
+
* @returns Value with inline comments removed.
|
|
571
|
+
*/
|
|
572
|
+
private stripInlineComment(value: string): string {
|
|
573
|
+
value = value.trim();
|
|
574
|
+
if (value === '') {
|
|
575
|
+
return '';
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Don't strip from quoted strings
|
|
579
|
+
if (value[0] === '"' || value[0] === "'") {
|
|
580
|
+
const closePos = value.indexOf(value[0], 1);
|
|
581
|
+
if (closePos !== -1) {
|
|
582
|
+
const afterQuote = value.substring(closePos + 1).trim();
|
|
583
|
+
if (afterQuote === '' || afterQuote[0] === '#') {
|
|
584
|
+
return value.substring(0, closePos + 1);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Strip # comments (but not inside strings)
|
|
590
|
+
let inSingle = false;
|
|
591
|
+
let inDouble = false;
|
|
592
|
+
for (let i = 0; i < value.length; i++) {
|
|
593
|
+
const ch = value[i];
|
|
594
|
+
if (ch === "'" && !inDouble) {
|
|
595
|
+
inSingle = !inSingle;
|
|
596
|
+
} else if (ch === '"' && !inSingle) {
|
|
597
|
+
inDouble = !inDouble;
|
|
598
|
+
} else if (ch === '#' && !inSingle && !inDouble && i > 0 && value[i - 1] === ' ') {
|
|
599
|
+
return value.substring(0, i).trim();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
337
602
|
|
|
338
603
|
return value;
|
|
339
604
|
}
|