@safeaccess/inline 0.1.1 → 0.1.3

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 (179) hide show
  1. package/.gitattributes +1 -1
  2. package/CHANGELOG.md +23 -5
  3. package/LICENSE +1 -1
  4. package/README.md +79 -21
  5. package/dist/accessors/abstract-accessor.d.ts +24 -10
  6. package/dist/accessors/abstract-accessor.js +21 -8
  7. package/dist/accessors/abstract-integration-accessor.d.ts +22 -0
  8. package/dist/accessors/abstract-integration-accessor.js +23 -0
  9. package/dist/accessors/formats/any-accessor.d.ts +10 -8
  10. package/dist/accessors/formats/any-accessor.js +9 -8
  11. package/dist/accessors/formats/array-accessor.d.ts +2 -0
  12. package/dist/accessors/formats/array-accessor.js +2 -0
  13. package/dist/accessors/formats/env-accessor.d.ts +2 -0
  14. package/dist/accessors/formats/env-accessor.js +2 -0
  15. package/dist/accessors/formats/ini-accessor.d.ts +2 -0
  16. package/dist/accessors/formats/ini-accessor.js +2 -0
  17. package/dist/accessors/formats/json-accessor.d.ts +2 -0
  18. package/dist/accessors/formats/json-accessor.js +2 -0
  19. package/dist/accessors/formats/ndjson-accessor.d.ts +2 -0
  20. package/dist/accessors/formats/ndjson-accessor.js +2 -0
  21. package/dist/accessors/formats/object-accessor.d.ts +2 -0
  22. package/dist/accessors/formats/object-accessor.js +2 -0
  23. package/dist/accessors/formats/xml-accessor.d.ts +2 -0
  24. package/dist/accessors/formats/xml-accessor.js +2 -0
  25. package/dist/accessors/formats/yaml-accessor.d.ts +3 -1
  26. package/dist/accessors/formats/yaml-accessor.js +4 -2
  27. package/dist/cache/simple-path-cache.d.ts +51 -0
  28. package/dist/cache/simple-path-cache.js +72 -0
  29. package/dist/contracts/accessors-interface.d.ts +2 -0
  30. package/dist/contracts/factory-accessors-interface.d.ts +2 -0
  31. package/dist/contracts/filter-evaluator-interface.d.ts +28 -0
  32. package/dist/contracts/filter-evaluator-interface.js +1 -0
  33. package/dist/contracts/parse-integration-interface.d.ts +2 -0
  34. package/dist/contracts/parser-interface.d.ts +92 -0
  35. package/dist/contracts/parser-interface.js +1 -0
  36. package/dist/contracts/path-cache-interface.d.ts +7 -6
  37. package/dist/contracts/readable-accessors-interface.d.ts +11 -6
  38. package/dist/contracts/security-guard-interface.d.ts +2 -0
  39. package/dist/contracts/security-parser-interface.d.ts +2 -0
  40. package/dist/contracts/validatable-parser-interface.d.ts +59 -0
  41. package/dist/contracts/validatable-parser-interface.js +1 -0
  42. package/dist/contracts/writable-accessors-interface.d.ts +5 -0
  43. package/dist/core/accessor-factory.d.ts +124 -0
  44. package/dist/core/accessor-factory.js +157 -0
  45. package/dist/core/dot-notation-parser.d.ts +34 -5
  46. package/dist/core/dot-notation-parser.js +51 -10
  47. package/dist/core/inline-builder-accessor.d.ts +82 -0
  48. package/dist/core/inline-builder-accessor.js +107 -0
  49. package/dist/exceptions/accessor-exception.d.ts +9 -0
  50. package/dist/exceptions/accessor-exception.js +9 -0
  51. package/dist/exceptions/invalid-format-exception.d.ts +5 -0
  52. package/dist/exceptions/invalid-format-exception.js +5 -0
  53. package/dist/exceptions/parser-exception.d.ts +4 -0
  54. package/dist/exceptions/parser-exception.js +4 -0
  55. package/dist/exceptions/path-not-found-exception.d.ts +4 -0
  56. package/dist/exceptions/path-not-found-exception.js +4 -0
  57. package/dist/exceptions/readonly-violation-exception.d.ts +4 -0
  58. package/dist/exceptions/readonly-violation-exception.js +4 -0
  59. package/dist/exceptions/security-exception.d.ts +6 -0
  60. package/dist/exceptions/security-exception.js +6 -0
  61. package/dist/exceptions/unsupported-type-exception.d.ts +4 -0
  62. package/dist/exceptions/unsupported-type-exception.js +4 -0
  63. package/dist/exceptions/yaml-parse-exception.d.ts +4 -0
  64. package/dist/exceptions/yaml-parse-exception.js +4 -0
  65. package/dist/index.js +2 -1
  66. package/dist/inline.d.ts +26 -56
  67. package/dist/inline.js +43 -111
  68. package/dist/parser/xml-parser.js +23 -10
  69. package/dist/parser/yaml-parser.d.ts +54 -7
  70. package/dist/parser/yaml-parser.js +268 -51
  71. package/dist/path-query/segment-filter-parser.d.ts +142 -0
  72. package/dist/path-query/segment-filter-parser.js +384 -0
  73. package/dist/path-query/segment-parser.d.ts +98 -0
  74. package/dist/path-query/segment-parser.js +283 -0
  75. package/dist/path-query/segment-path-resolver.d.ts +149 -0
  76. package/dist/path-query/segment-path-resolver.js +351 -0
  77. package/dist/path-query/segment-type.d.ts +85 -0
  78. package/dist/path-query/segment-type.js +35 -0
  79. package/dist/security/forbidden-keys.d.ts +2 -2
  80. package/dist/security/forbidden-keys.js +5 -5
  81. package/dist/security/security-guard.d.ts +4 -1
  82. package/dist/security/security-guard.js +7 -2
  83. package/dist/security/security-parser.d.ts +10 -1
  84. package/dist/security/security-parser.js +10 -1
  85. package/dist/type-format.d.ts +2 -0
  86. package/dist/type-format.js +2 -0
  87. package/package.json +11 -3
  88. package/src/accessors/abstract-accessor.ts +25 -19
  89. package/src/accessors/abstract-integration-accessor.ts +27 -0
  90. package/src/accessors/formats/any-accessor.ts +11 -11
  91. package/src/accessors/formats/array-accessor.ts +2 -0
  92. package/src/accessors/formats/env-accessor.ts +2 -0
  93. package/src/accessors/formats/ini-accessor.ts +2 -0
  94. package/src/accessors/formats/json-accessor.ts +2 -0
  95. package/src/accessors/formats/ndjson-accessor.ts +2 -0
  96. package/src/accessors/formats/object-accessor.ts +2 -0
  97. package/src/accessors/formats/xml-accessor.ts +2 -0
  98. package/src/accessors/formats/yaml-accessor.ts +4 -2
  99. package/src/cache/simple-path-cache.ts +77 -0
  100. package/src/contracts/accessors-interface.ts +2 -0
  101. package/src/contracts/factory-accessors-interface.ts +2 -0
  102. package/src/contracts/filter-evaluator-interface.ts +30 -0
  103. package/src/contracts/parse-integration-interface.ts +2 -0
  104. package/src/contracts/parser-interface.ts +114 -0
  105. package/src/contracts/path-cache-interface.ts +8 -6
  106. package/src/contracts/readable-accessors-interface.ts +11 -6
  107. package/src/contracts/security-guard-interface.ts +2 -0
  108. package/src/contracts/security-parser-interface.ts +2 -0
  109. package/src/contracts/validatable-parser-interface.ts +64 -0
  110. package/src/contracts/writable-accessors-interface.ts +5 -0
  111. package/src/core/accessor-factory.ts +173 -0
  112. package/src/core/dot-notation-parser.ts +74 -11
  113. package/src/core/inline-builder-accessor.ts +163 -0
  114. package/src/exceptions/accessor-exception.ts +9 -0
  115. package/src/exceptions/invalid-format-exception.ts +5 -0
  116. package/src/exceptions/parser-exception.ts +4 -0
  117. package/src/exceptions/path-not-found-exception.ts +4 -0
  118. package/src/exceptions/readonly-violation-exception.ts +4 -0
  119. package/src/exceptions/security-exception.ts +6 -0
  120. package/src/exceptions/unsupported-type-exception.ts +4 -0
  121. package/src/exceptions/yaml-parse-exception.ts +4 -0
  122. package/src/index.ts +3 -1
  123. package/src/inline.ts +46 -120
  124. package/src/parser/xml-parser.ts +31 -10
  125. package/src/parser/yaml-parser.ts +310 -45
  126. package/src/path-query/segment-filter-parser.ts +444 -0
  127. package/src/path-query/segment-parser.ts +321 -0
  128. package/src/path-query/segment-path-resolver.ts +521 -0
  129. package/src/path-query/segment-type.ts +82 -0
  130. package/src/security/forbidden-keys.ts +5 -5
  131. package/src/security/security-guard.ts +10 -2
  132. package/src/security/security-parser.ts +18 -3
  133. package/src/type-format.ts +2 -0
  134. package/stryker.config.json +8 -10
  135. package/tests/accessors/abstract-accessor.test.ts +217 -0
  136. package/tests/accessors/abstract-integration-accessor.test.ts +37 -0
  137. package/tests/accessors/formats/any-accessor.test.ts +57 -0
  138. package/tests/accessors/formats/array-accessor.test.ts +42 -0
  139. package/tests/accessors/formats/env-accessor.test.ts +103 -0
  140. package/tests/accessors/formats/ini-accessor.test.ts +186 -0
  141. package/tests/accessors/{json-accessor.test.ts → formats/json-accessor.test.ts} +6 -6
  142. package/tests/accessors/formats/ndjson-accessor.test.ts +49 -0
  143. package/tests/accessors/formats/object-accessor.test.ts +172 -0
  144. package/tests/accessors/formats/xml-accessor.test.ts +162 -0
  145. package/tests/accessors/formats/yaml-accessor.test.ts +36 -0
  146. package/tests/cache/simple-path-cache.test.ts +168 -0
  147. package/tests/core/accessor-factory.test.ts +157 -0
  148. package/tests/core/dot-notation-parser-edge-cases.test.ts +415 -0
  149. package/tests/core/dot-notation-parser.test.ts +0 -288
  150. package/tests/core/inline-builder-accessor.test.ts +114 -0
  151. package/tests/exceptions/accessor-exception.test.ts +28 -0
  152. package/tests/exceptions/invalid-format-exception.test.ts +31 -0
  153. package/tests/exceptions/path-not-found-exception.test.ts +33 -0
  154. package/tests/exceptions/readonly-violation-exception.test.ts +35 -0
  155. package/tests/exceptions/security-exception.test.ts +33 -0
  156. package/tests/exceptions/unsupported-type-exception.test.ts +33 -0
  157. package/tests/exceptions/yaml-parse-exception.test.ts +38 -0
  158. package/tests/mocks/fake-path-cache.ts +4 -3
  159. package/tests/parity-from.test.ts +118 -0
  160. package/tests/parity.test.ts +227 -10
  161. package/tests/parser/xml-parser-mutations.test.ts +579 -0
  162. package/tests/parser/xml-parser-scanner.test.ts +379 -0
  163. package/tests/parser/xml-parser.test.ts +17 -330
  164. package/tests/parser/yaml-parser-mutations.test.ts +750 -0
  165. package/tests/parser/yaml-parser.test.ts +844 -18
  166. package/tests/path-query/segment-filter-parser-mutations.test.ts +735 -0
  167. package/tests/path-query/segment-filter-parser.test.ts +1091 -0
  168. package/tests/path-query/segment-parser-mutations.test.ts +539 -0
  169. package/tests/path-query/segment-parser.test.ts +606 -0
  170. package/tests/path-query/segment-path-resolver-mutations.test.ts +626 -0
  171. package/tests/path-query/segment-path-resolver.test.ts +1009 -0
  172. package/tests/security/security-guard-advanced.test.ts +413 -0
  173. package/tests/security/security-guard-forbidden-keys.test.ts +87 -0
  174. package/tests/security/security-guard.test.ts +8 -479
  175. package/tests/security/security-parser.test.ts +18 -14
  176. package/vitest.config.ts +3 -3
  177. package/benchmarks/get.bench.ts +0 -26
  178. package/benchmarks/parse.bench.ts +0 -41
  179. 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 or syntax errors are found.
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 - Minimum indentation for scalar lines.
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 an inline flow collection (JSON-like array or object).
87
+ * Parse a YAML flow sequence ([a, b, c]) into an array.
72
88
  *
73
- * @param raw - Raw inline flow string.
74
- * @returns Parsed value or the raw string if parsing fails.
89
+ * @param value - Raw flow sequence string including brackets.
90
+ * @returns Parsed sequence values.
75
91
  */
76
- private parseInlineFlow;
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 or syntax errors are found.
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 (|) or folded (>)
182
- if (trimmed === '|' || 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 (array or map) starting with [ or {
186
- if (trimmed.startsWith('[') || trimmed.startsWith('{')) {
187
- return this.parseInlineFlow(trimmed);
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 - Minimum indentation for scalar lines.
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 parts = [];
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
- const trimmed = line.trimStart();
210
- const lineIndent = line.length - trimmed.length;
211
- if (lineIndent >= indent || trimmed === '') {
212
- parts.push(trimmed);
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
- return parts.join(' ').trim();
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
- return parts.join('\n').trimEnd();
293
+ const items = this.splitFlowItems(inner);
294
+ return items.map((item) => this.castScalar(item.trim()));
219
295
  }
220
296
  /**
221
- * Parse an inline flow collection (JSON-like array or object).
297
+ * Parse a YAML flow map ({a: b, c: d}) into a record.
222
298
  *
223
- * @param raw - Raw inline flow string.
224
- * @returns Parsed value or the raw string if parsing fails.
299
+ * @param value - Raw flow map string including braces.
300
+ * @returns Parsed key-value pairs.
225
301
  */
226
- parseInlineFlow(raw) {
227
- try {
228
- // Convert YAML flow to JSON by single-quoting to double-quoting
229
- const jsonLike = raw.replace(/'/g, '"').replace(/(\w+)\s*:/g, '"$1":');
230
- return JSON.parse(jsonLike);
302
+ parseFlowMap(value) {
303
+ const inner = value.slice(1, -1).trim();
304
+ if (inner === '') {
305
+ return {};
231
306
  }
232
- catch {
233
- // Fallback: return raw string
234
- return raw;
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
- if (value === '' || value === 'null' || value === '~')
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
- if (value === 'true')
397
+ }
398
+ // Boolean
399
+ const lower = value.toLowerCase();
400
+ if (lower === 'true' || lower === 'yes' || lower === 'on')
247
401
  return true;
248
- if (value === 'false')
402
+ if (lower === 'false' || lower === 'no' || lower === 'off')
249
403
  return false;
250
- // Quoted string
251
- if ((value.startsWith('"') && value.endsWith('"')) ||
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
- // Float
259
- if (/^-?\d+\.\d+$/.test(value))
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
  /**