@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
|
@@ -5,14 +5,22 @@
|
|
|
5
5
|
* tags (!! and !), anchors (&), aliases (*), and merge keys (<<).
|
|
6
6
|
*
|
|
7
7
|
* Does not depend on external YAML libraries, making the package portable.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
8
10
|
*/
|
|
9
11
|
export declare class YamlParser {
|
|
12
|
+
private readonly maxDepth;
|
|
13
|
+
/**
|
|
14
|
+
* @param maxDepth - Maximum allowed nesting depth during parsing.
|
|
15
|
+
* Defaults to 512 to match SecurityParser.maxDepth.
|
|
16
|
+
*/
|
|
17
|
+
constructor(maxDepth?: number);
|
|
10
18
|
/**
|
|
11
19
|
* Parse a YAML string into a plain object.
|
|
12
20
|
*
|
|
13
21
|
* @param yaml - Raw YAML content.
|
|
14
22
|
* @returns Parsed data structure.
|
|
15
|
-
* @throws {YamlParseException} When unsafe constructs
|
|
23
|
+
* @throws {YamlParseException} When unsafe constructs, syntax errors, or nesting depth exceeded.
|
|
16
24
|
*
|
|
17
25
|
* @example
|
|
18
26
|
* new YamlParser().parse('key: value'); // { key: 'value' }
|
|
@@ -32,7 +40,10 @@ export declare class YamlParser {
|
|
|
32
40
|
* @param baseIndent - Indentation level for this block.
|
|
33
41
|
* @param start - First line index (inclusive).
|
|
34
42
|
* @param end - Last line index (exclusive).
|
|
43
|
+
* @param depth - Current nesting depth.
|
|
35
44
|
* @returns Parsed value.
|
|
45
|
+
*
|
|
46
|
+
* @throws {YamlParseException} When nesting depth exceeds the configured maximum.
|
|
36
47
|
*/
|
|
37
48
|
private parseLines;
|
|
38
49
|
/**
|
|
@@ -43,6 +54,8 @@ export declare class YamlParser {
|
|
|
43
54
|
* @param end - Last child line index (exclusive).
|
|
44
55
|
* @param childIndent - Expected indentation for child lines.
|
|
45
56
|
* @param map - Map to merge values into.
|
|
57
|
+
* @param depth - Current nesting depth.
|
|
58
|
+
* @throws {YamlParseException} When resolved values exceed the configured nesting depth.
|
|
46
59
|
*/
|
|
47
60
|
private mergeChildLines;
|
|
48
61
|
/**
|
|
@@ -53,34 +66,68 @@ export declare class YamlParser {
|
|
|
53
66
|
* @param lineIndex - Line where the value was found.
|
|
54
67
|
* @param childIndent - Expected indentation for child block.
|
|
55
68
|
* @param childEnd - End index of the child block.
|
|
69
|
+
* @param depth - Current nesting depth.
|
|
56
70
|
* @returns Resolved typed value.
|
|
71
|
+
* @throws {YamlParseException} When child block nesting exceeds the configured maximum.
|
|
57
72
|
*/
|
|
58
73
|
private resolveValue;
|
|
59
74
|
/**
|
|
60
|
-
* Parse a YAML block scalar (literal | or folded >).
|
|
75
|
+
* Parse a YAML block scalar (literal | or folded >) with chomping modifiers.
|
|
61
76
|
*
|
|
62
77
|
* @param lines - All lines of the YAML document.
|
|
63
78
|
* @param start - First line of the scalar block (inclusive).
|
|
64
79
|
* @param end - Last line of the scalar block (exclusive).
|
|
65
|
-
* @param indent -
|
|
80
|
+
* @param indent - Expected minimum indentation for scalar lines.
|
|
66
81
|
* @param folded - Whether to fold lines (> style) or keep literal (| style).
|
|
82
|
+
* @param chomping - Chomping indicator (|, |-, |+, >, >-, >+).
|
|
67
83
|
* @returns The assembled string.
|
|
68
84
|
*/
|
|
69
85
|
private parseBlockScalar;
|
|
70
86
|
/**
|
|
71
|
-
* Parse
|
|
87
|
+
* Parse a YAML flow sequence ([a, b, c]) into an array.
|
|
72
88
|
*
|
|
73
|
-
* @param
|
|
74
|
-
* @returns Parsed
|
|
89
|
+
* @param value - Raw flow sequence string including brackets.
|
|
90
|
+
* @returns Parsed sequence values.
|
|
75
91
|
*/
|
|
76
|
-
private
|
|
92
|
+
private parseFlowSequence;
|
|
93
|
+
/**
|
|
94
|
+
* Parse a YAML flow map ({a: b, c: d}) into a record.
|
|
95
|
+
*
|
|
96
|
+
* @param value - Raw flow map string including braces.
|
|
97
|
+
* @returns Parsed key-value pairs.
|
|
98
|
+
*/
|
|
99
|
+
private parseFlowMap;
|
|
100
|
+
/**
|
|
101
|
+
* Split flow-syntax items by comma, respecting nested brackets and quotes.
|
|
102
|
+
*
|
|
103
|
+
* @param inner - Content between outer brackets/braces.
|
|
104
|
+
* @returns Individual item strings.
|
|
105
|
+
*/
|
|
106
|
+
private splitFlowItems;
|
|
77
107
|
/**
|
|
78
108
|
* Cast a scalar string to its native type.
|
|
79
109
|
*
|
|
110
|
+
* Handles quoted strings, null, boolean, integer (decimal/octal/hex),
|
|
111
|
+
* float, infinity, and NaN values.
|
|
112
|
+
*
|
|
80
113
|
* @param value - Raw scalar string.
|
|
81
114
|
* @returns Typed value (null, boolean, number, or string).
|
|
82
115
|
*/
|
|
83
116
|
private castScalar;
|
|
117
|
+
/**
|
|
118
|
+
* Unescape YAML double-quoted string escape sequences.
|
|
119
|
+
*
|
|
120
|
+
* @param value - String content between double quotes.
|
|
121
|
+
* @returns Unescaped string.
|
|
122
|
+
*/
|
|
123
|
+
private unescapeDoubleQuoted;
|
|
124
|
+
/**
|
|
125
|
+
* Strip inline comments from a value string, respecting quoted regions.
|
|
126
|
+
*
|
|
127
|
+
* @param value - Raw value potentially containing inline comments.
|
|
128
|
+
* @returns Value with inline comments removed.
|
|
129
|
+
*/
|
|
130
|
+
private stripInlineComment;
|
|
84
131
|
/**
|
|
85
132
|
* Find where a child block ends based on indentation.
|
|
86
133
|
*
|
|
@@ -6,14 +6,24 @@ import { YamlParseException } from '../exceptions/yaml-parse-exception.js';
|
|
|
6
6
|
* tags (!! and !), anchors (&), aliases (*), and merge keys (<<).
|
|
7
7
|
*
|
|
8
8
|
* Does not depend on external YAML libraries, making the package portable.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
9
11
|
*/
|
|
10
12
|
export class YamlParser {
|
|
13
|
+
maxDepth;
|
|
14
|
+
/**
|
|
15
|
+
* @param maxDepth - Maximum allowed nesting depth during parsing.
|
|
16
|
+
* Defaults to 512 to match SecurityParser.maxDepth.
|
|
17
|
+
*/
|
|
18
|
+
constructor(maxDepth = 512) {
|
|
19
|
+
this.maxDepth = maxDepth;
|
|
20
|
+
}
|
|
11
21
|
/**
|
|
12
22
|
* Parse a YAML string into a plain object.
|
|
13
23
|
*
|
|
14
24
|
* @param yaml - Raw YAML content.
|
|
15
25
|
* @returns Parsed data structure.
|
|
16
|
-
* @throws {YamlParseException} When unsafe constructs
|
|
26
|
+
* @throws {YamlParseException} When unsafe constructs, syntax errors, or nesting depth exceeded.
|
|
17
27
|
*
|
|
18
28
|
* @example
|
|
19
29
|
* new YamlParser().parse('key: value'); // { key: 'value' }
|
|
@@ -21,7 +31,7 @@ export class YamlParser {
|
|
|
21
31
|
parse(yaml) {
|
|
22
32
|
const lines = yaml.replace(/\r\n/g, '\n').split('\n');
|
|
23
33
|
this.assertNoUnsafeConstructs(lines);
|
|
24
|
-
const result = this.parseLines(lines, 0, 0, lines.length);
|
|
34
|
+
const result = this.parseLines(lines, 0, 0, lines.length, 0);
|
|
25
35
|
if (Array.isArray(result)) {
|
|
26
36
|
return {};
|
|
27
37
|
}
|
|
@@ -61,9 +71,15 @@ export class YamlParser {
|
|
|
61
71
|
* @param baseIndent - Indentation level for this block.
|
|
62
72
|
* @param start - First line index (inclusive).
|
|
63
73
|
* @param end - Last line index (exclusive).
|
|
74
|
+
* @param depth - Current nesting depth.
|
|
64
75
|
* @returns Parsed value.
|
|
76
|
+
*
|
|
77
|
+
* @throws {YamlParseException} When nesting depth exceeds the configured maximum.
|
|
65
78
|
*/
|
|
66
|
-
parseLines(lines, baseIndent, start, end) {
|
|
79
|
+
parseLines(lines, baseIndent, start, end, depth = 0) {
|
|
80
|
+
if (depth > this.maxDepth) {
|
|
81
|
+
throw new YamlParseException(`YAML nesting depth ${depth} exceeds maximum of ${this.maxDepth}.`);
|
|
82
|
+
}
|
|
67
83
|
const mapResult = {};
|
|
68
84
|
const arrResult = [];
|
|
69
85
|
let isSequence = false;
|
|
@@ -94,8 +110,8 @@ export class YamlParser {
|
|
|
94
110
|
const childIndent = currentIndent + 2;
|
|
95
111
|
const childEnd = this.findBlockEnd(lines, childIndent, i + 1, end);
|
|
96
112
|
const subMap = {};
|
|
97
|
-
subMap[match[1]] = this.resolveValue(match[2], lines, i, childIndent, childEnd);
|
|
98
|
-
this.mergeChildLines(lines, i + 1, childEnd, childIndent, subMap);
|
|
113
|
+
subMap[match[1]] = this.resolveValue(match[2], lines, i, childIndent, childEnd, depth);
|
|
114
|
+
this.mergeChildLines(lines, i + 1, childEnd, childIndent, subMap, depth);
|
|
99
115
|
arrResult.push(subMap);
|
|
100
116
|
i = childEnd;
|
|
101
117
|
}
|
|
@@ -103,7 +119,7 @@ export class YamlParser {
|
|
|
103
119
|
const childIndent = currentIndent + 2;
|
|
104
120
|
const childEnd = this.findBlockEnd(lines, childIndent, i + 1, end);
|
|
105
121
|
if (childEnd > i + 1) {
|
|
106
|
-
arrResult.push(this.parseLines(lines, childIndent, i + 1, childEnd));
|
|
122
|
+
arrResult.push(this.parseLines(lines, childIndent, i + 1, childEnd, depth + 1));
|
|
107
123
|
i = childEnd;
|
|
108
124
|
}
|
|
109
125
|
else {
|
|
@@ -124,7 +140,7 @@ export class YamlParser {
|
|
|
124
140
|
const rawValue = mapMatch[2];
|
|
125
141
|
const childIndent = currentIndent + 2;
|
|
126
142
|
const childEnd = this.findBlockEnd(lines, childIndent, i + 1, end);
|
|
127
|
-
mapResult[key] = this.resolveValue(rawValue, lines, i, childIndent, childEnd);
|
|
143
|
+
mapResult[key] = this.resolveValue(rawValue, lines, i, childIndent, childEnd, depth);
|
|
128
144
|
i = childEnd;
|
|
129
145
|
continue;
|
|
130
146
|
}
|
|
@@ -143,8 +159,10 @@ export class YamlParser {
|
|
|
143
159
|
* @param end - Last child line index (exclusive).
|
|
144
160
|
* @param childIndent - Expected indentation for child lines.
|
|
145
161
|
* @param map - Map to merge values into.
|
|
162
|
+
* @param depth - Current nesting depth.
|
|
163
|
+
* @throws {YamlParseException} When resolved values exceed the configured nesting depth.
|
|
146
164
|
*/
|
|
147
|
-
mergeChildLines(lines, start, end, childIndent, map) {
|
|
165
|
+
mergeChildLines(lines, start, end, childIndent, map, depth) {
|
|
148
166
|
let ci = start;
|
|
149
167
|
while (ci < end) {
|
|
150
168
|
const childLine = lines[ci];
|
|
@@ -158,7 +176,7 @@ export class YamlParser {
|
|
|
158
176
|
const cm = /^([^\s:][^:]*?)\s*:\s*(.*)$/.exec(childTrimmed);
|
|
159
177
|
if (cm !== null) {
|
|
160
178
|
const nextChildEnd = this.findBlockEnd(lines, childCurrentIndent + 2, ci + 1, end);
|
|
161
|
-
map[cm[1]] = this.resolveValue(cm[2], lines, ci, childCurrentIndent + 2, nextChildEnd);
|
|
179
|
+
map[cm[1]] = this.resolveValue(cm[2], lines, ci, childCurrentIndent + 2, nextChildEnd, depth);
|
|
162
180
|
ci = nextChildEnd;
|
|
163
181
|
continue;
|
|
164
182
|
}
|
|
@@ -174,90 +192,289 @@ export class YamlParser {
|
|
|
174
192
|
* @param lineIndex - Line where the value was found.
|
|
175
193
|
* @param childIndent - Expected indentation for child block.
|
|
176
194
|
* @param childEnd - End index of the child block.
|
|
195
|
+
* @param depth - Current nesting depth.
|
|
177
196
|
* @returns Resolved typed value.
|
|
197
|
+
* @throws {YamlParseException} When child block nesting exceeds the configured maximum.
|
|
178
198
|
*/
|
|
179
|
-
resolveValue(rawValue, lines, lineIndex, childIndent, childEnd) {
|
|
180
|
-
const trimmed = rawValue.trim();
|
|
181
|
-
// Block scalar literal (
|
|
182
|
-
if (trimmed
|
|
183
|
-
return this.parseBlockScalar(lines, lineIndex + 1, childEnd, childIndent, trimmed
|
|
199
|
+
resolveValue(rawValue, lines, lineIndex, childIndent, childEnd, depth = 0) {
|
|
200
|
+
const trimmed = this.stripInlineComment(rawValue.trim());
|
|
201
|
+
// Block scalar literal (|, |-, |+) or folded (>, >-, >+)
|
|
202
|
+
if (/^\|[+-]?$/.test(trimmed) || /^>[+-]?$/.test(trimmed)) {
|
|
203
|
+
return this.parseBlockScalar(lines, lineIndex + 1, childEnd, childIndent, trimmed.startsWith('>'), trimmed);
|
|
204
|
+
}
|
|
205
|
+
// Inline flow sequence [a, b, c]
|
|
206
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
207
|
+
return this.parseFlowSequence(trimmed);
|
|
184
208
|
}
|
|
185
|
-
// Inline flow
|
|
186
|
-
if (trimmed.startsWith('
|
|
187
|
-
return this.
|
|
209
|
+
// Inline flow map {a: b, c: d}
|
|
210
|
+
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
211
|
+
return this.parseFlowMap(trimmed);
|
|
188
212
|
}
|
|
189
213
|
// Nested block
|
|
190
214
|
if (trimmed === '' && childEnd > lineIndex + 1) {
|
|
191
|
-
return this.parseLines(lines, childIndent, lineIndex + 1, childEnd);
|
|
215
|
+
return this.parseLines(lines, childIndent, lineIndex + 1, childEnd, depth + 1);
|
|
192
216
|
}
|
|
193
217
|
return this.castScalar(trimmed);
|
|
194
218
|
}
|
|
195
219
|
/**
|
|
196
|
-
* Parse a YAML block scalar (literal | or folded >).
|
|
220
|
+
* Parse a YAML block scalar (literal | or folded >) with chomping modifiers.
|
|
197
221
|
*
|
|
198
222
|
* @param lines - All lines of the YAML document.
|
|
199
223
|
* @param start - First line of the scalar block (inclusive).
|
|
200
224
|
* @param end - Last line of the scalar block (exclusive).
|
|
201
|
-
* @param indent -
|
|
225
|
+
* @param indent - Expected minimum indentation for scalar lines.
|
|
202
226
|
* @param folded - Whether to fold lines (> style) or keep literal (| style).
|
|
227
|
+
* @param chomping - Chomping indicator (|, |-, |+, >, >-, >+).
|
|
203
228
|
* @returns The assembled string.
|
|
204
229
|
*/
|
|
205
|
-
parseBlockScalar(lines, start, end, indent, folded) {
|
|
206
|
-
const
|
|
230
|
+
parseBlockScalar(lines, start, end, indent, folded, chomping = '|') {
|
|
231
|
+
const blockLines = [];
|
|
232
|
+
let actualIndent = null;
|
|
207
233
|
for (let i = start; i < end; i++) {
|
|
208
234
|
const line = lines[i];
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
235
|
+
if (line.trim() === '') {
|
|
236
|
+
blockLines.push('');
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const lineIndent = line.length - line.trimStart().length;
|
|
240
|
+
if (actualIndent === null) {
|
|
241
|
+
actualIndent = lineIndent;
|
|
242
|
+
}
|
|
243
|
+
if (lineIndent < actualIndent) {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
blockLines.push(line.substring(actualIndent));
|
|
247
|
+
}
|
|
248
|
+
// Remove trailing empty lines for clip (default) and strip (-) modes.
|
|
249
|
+
// Keep (+) mode preserves all trailing blank lines.
|
|
250
|
+
if (!chomping.endsWith('+')) {
|
|
251
|
+
while (blockLines.length > 0 && blockLines[blockLines.length - 1] === '') {
|
|
252
|
+
blockLines.pop();
|
|
213
253
|
}
|
|
214
254
|
}
|
|
255
|
+
let result;
|
|
215
256
|
if (folded) {
|
|
216
|
-
|
|
257
|
+
result = '';
|
|
258
|
+
let prevEmpty = false;
|
|
259
|
+
for (const bl of blockLines) {
|
|
260
|
+
if (bl === '') {
|
|
261
|
+
result += '\n';
|
|
262
|
+
prevEmpty = true;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
if (result !== '' && !prevEmpty && !result.endsWith('\n')) {
|
|
266
|
+
result += ' ';
|
|
267
|
+
}
|
|
268
|
+
result += bl;
|
|
269
|
+
prevEmpty = false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
result = blockLines.join('\n');
|
|
275
|
+
}
|
|
276
|
+
// Default YAML chomping: add trailing newline unless strip (-)
|
|
277
|
+
if (!chomping.endsWith('-') && !result.endsWith('\n')) {
|
|
278
|
+
result += '\n';
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Parse a YAML flow sequence ([a, b, c]) into an array.
|
|
284
|
+
*
|
|
285
|
+
* @param value - Raw flow sequence string including brackets.
|
|
286
|
+
* @returns Parsed sequence values.
|
|
287
|
+
*/
|
|
288
|
+
parseFlowSequence(value) {
|
|
289
|
+
const inner = value.slice(1, -1).trim();
|
|
290
|
+
if (inner === '') {
|
|
291
|
+
return [];
|
|
217
292
|
}
|
|
218
|
-
|
|
293
|
+
const items = this.splitFlowItems(inner);
|
|
294
|
+
return items.map((item) => this.castScalar(item.trim()));
|
|
219
295
|
}
|
|
220
296
|
/**
|
|
221
|
-
* Parse
|
|
297
|
+
* Parse a YAML flow map ({a: b, c: d}) into a record.
|
|
222
298
|
*
|
|
223
|
-
* @param
|
|
224
|
-
* @returns Parsed value
|
|
299
|
+
* @param value - Raw flow map string including braces.
|
|
300
|
+
* @returns Parsed key-value pairs.
|
|
225
301
|
*/
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return JSON.parse(jsonLike);
|
|
302
|
+
parseFlowMap(value) {
|
|
303
|
+
const inner = value.slice(1, -1).trim();
|
|
304
|
+
if (inner === '') {
|
|
305
|
+
return {};
|
|
231
306
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
307
|
+
const result = {};
|
|
308
|
+
const items = this.splitFlowItems(inner);
|
|
309
|
+
for (const item of items) {
|
|
310
|
+
const trimmedItem = item.trim();
|
|
311
|
+
const colonPos = trimmedItem.indexOf(':');
|
|
312
|
+
if (colonPos === -1) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const key = trimmedItem.substring(0, colonPos).trim();
|
|
316
|
+
const val = trimmedItem.substring(colonPos + 1).trim();
|
|
317
|
+
result[key] = this.castScalar(val);
|
|
235
318
|
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Split flow-syntax items by comma, respecting nested brackets and quotes.
|
|
323
|
+
*
|
|
324
|
+
* @param inner - Content between outer brackets/braces.
|
|
325
|
+
* @returns Individual item strings.
|
|
326
|
+
*/
|
|
327
|
+
splitFlowItems(inner) {
|
|
328
|
+
const items = [];
|
|
329
|
+
let depth = 0;
|
|
330
|
+
let current = '';
|
|
331
|
+
let inQuote = false;
|
|
332
|
+
let quoteChar = '';
|
|
333
|
+
for (let i = 0; i < inner.length; i++) {
|
|
334
|
+
const ch = inner[i];
|
|
335
|
+
if (inQuote) {
|
|
336
|
+
current += ch;
|
|
337
|
+
if (ch === quoteChar) {
|
|
338
|
+
inQuote = false;
|
|
339
|
+
}
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (ch === '"' || ch === "'") {
|
|
343
|
+
inQuote = true;
|
|
344
|
+
quoteChar = ch;
|
|
345
|
+
current += ch;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (ch === '[' || ch === '{') {
|
|
349
|
+
depth++;
|
|
350
|
+
current += ch;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (ch === ']' || ch === '}') {
|
|
354
|
+
depth--;
|
|
355
|
+
current += ch;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (ch === ',' && depth === 0) {
|
|
359
|
+
items.push(current);
|
|
360
|
+
current = '';
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
current += ch;
|
|
364
|
+
}
|
|
365
|
+
if (current.trim() !== '') {
|
|
366
|
+
items.push(current);
|
|
367
|
+
}
|
|
368
|
+
return items;
|
|
236
369
|
}
|
|
237
370
|
/**
|
|
238
371
|
* Cast a scalar string to its native type.
|
|
239
372
|
*
|
|
373
|
+
* Handles quoted strings, null, boolean, integer (decimal/octal/hex),
|
|
374
|
+
* float, infinity, and NaN values.
|
|
375
|
+
*
|
|
240
376
|
* @param value - Raw scalar string.
|
|
241
377
|
* @returns Typed value (null, boolean, number, or string).
|
|
242
378
|
*/
|
|
243
379
|
castScalar(value) {
|
|
244
|
-
|
|
380
|
+
value = value.trim();
|
|
381
|
+
// Quoted strings
|
|
382
|
+
if (value.length >= 2) {
|
|
383
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
384
|
+
return this.unescapeDoubleQuoted(value.slice(1, -1));
|
|
385
|
+
}
|
|
386
|
+
if (value.startsWith("'") && value.endsWith("'")) {
|
|
387
|
+
return value.slice(1, -1).replace(/''/g, "'");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Null
|
|
391
|
+
if (value === '' ||
|
|
392
|
+
value === '~' ||
|
|
393
|
+
value === 'null' ||
|
|
394
|
+
value === 'Null' ||
|
|
395
|
+
value === 'NULL') {
|
|
245
396
|
return null;
|
|
246
|
-
|
|
397
|
+
}
|
|
398
|
+
// Boolean
|
|
399
|
+
const lower = value.toLowerCase();
|
|
400
|
+
if (lower === 'true' || lower === 'yes' || lower === 'on')
|
|
247
401
|
return true;
|
|
248
|
-
if (
|
|
402
|
+
if (lower === 'false' || lower === 'no' || lower === 'off')
|
|
249
403
|
return false;
|
|
250
|
-
//
|
|
251
|
-
if ((
|
|
252
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
253
|
-
return value.slice(1, -1);
|
|
254
|
-
}
|
|
255
|
-
// Integer
|
|
256
|
-
if (/^-?\d+$/.test(value))
|
|
404
|
+
// Integer patterns
|
|
405
|
+
if (/^-?(?:0|[1-9]\d*)$/.test(value))
|
|
257
406
|
return parseInt(value, 10);
|
|
258
|
-
|
|
259
|
-
|
|
407
|
+
if (/^0o[0-7]+$/i.test(value))
|
|
408
|
+
return parseInt(value.slice(2), 8);
|
|
409
|
+
if (/^0x[0-9a-fA-F]+$/.test(value))
|
|
410
|
+
return parseInt(value, 16);
|
|
411
|
+
// Float patterns
|
|
412
|
+
if (/^-?(?:0|[1-9]\d*)?(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(value) && value.includes('.')) {
|
|
260
413
|
return parseFloat(value);
|
|
414
|
+
}
|
|
415
|
+
if (lower === '.inf' || lower === '+.inf')
|
|
416
|
+
return Infinity;
|
|
417
|
+
if (lower === '-.inf')
|
|
418
|
+
return -Infinity;
|
|
419
|
+
if (lower === '.nan')
|
|
420
|
+
return NaN;
|
|
421
|
+
return value;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Unescape YAML double-quoted string escape sequences.
|
|
425
|
+
*
|
|
426
|
+
* @param value - String content between double quotes.
|
|
427
|
+
* @returns Unescaped string.
|
|
428
|
+
*/
|
|
429
|
+
unescapeDoubleQuoted(value) {
|
|
430
|
+
return value
|
|
431
|
+
.replace(/\\n/g, '\n')
|
|
432
|
+
.replace(/\\t/g, '\t')
|
|
433
|
+
.replace(/\\r/g, '\r')
|
|
434
|
+
.replace(/\\\\/g, '\\')
|
|
435
|
+
.replace(/\\"/g, '"')
|
|
436
|
+
.replace(/\\0/g, '\0')
|
|
437
|
+
.replace(/\\a/g, '\x07')
|
|
438
|
+
.replace(/\\b/g, '\x08')
|
|
439
|
+
.replace(/\\f/g, '\x0C')
|
|
440
|
+
.replace(/\\v/g, '\x0B');
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Strip inline comments from a value string, respecting quoted regions.
|
|
444
|
+
*
|
|
445
|
+
* @param value - Raw value potentially containing inline comments.
|
|
446
|
+
* @returns Value with inline comments removed.
|
|
447
|
+
*/
|
|
448
|
+
stripInlineComment(value) {
|
|
449
|
+
value = value.trim();
|
|
450
|
+
if (value === '') {
|
|
451
|
+
return '';
|
|
452
|
+
}
|
|
453
|
+
// Don't strip from quoted strings
|
|
454
|
+
if (value[0] === '"' || value[0] === "'") {
|
|
455
|
+
const closePos = value.indexOf(value[0], 1);
|
|
456
|
+
if (closePos !== -1) {
|
|
457
|
+
const afterQuote = value.substring(closePos + 1).trim();
|
|
458
|
+
if (afterQuote === '' || afterQuote[0] === '#') {
|
|
459
|
+
return value.substring(0, closePos + 1);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// Strip # comments (but not inside strings)
|
|
464
|
+
let inSingle = false;
|
|
465
|
+
let inDouble = false;
|
|
466
|
+
for (let i = 0; i < value.length; i++) {
|
|
467
|
+
const ch = value[i];
|
|
468
|
+
if (ch === "'" && !inDouble) {
|
|
469
|
+
inSingle = !inSingle;
|
|
470
|
+
}
|
|
471
|
+
else if (ch === '"' && !inSingle) {
|
|
472
|
+
inDouble = !inDouble;
|
|
473
|
+
}
|
|
474
|
+
else if (ch === '#' && !inSingle && !inDouble && i > 0 && value[i - 1] === ' ') {
|
|
475
|
+
return value.substring(0, i).trim();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
261
478
|
return value;
|
|
262
479
|
}
|
|
263
480
|
/**
|