@limaformat/lima 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/dist/block-scalar.d.ts +33 -0
- package/dist/block-scalar.js +109 -0
- package/dist/block.js +167 -80
- package/dist/builder.d.ts +8 -9
- package/dist/core.js +43 -89
- package/dist/flow.d.ts +4 -3
- package/dist/flow.js +97 -16
- package/dist/normalize.d.ts +8 -9
- package/dist/normalize.js +2 -11
- package/dist/{references.d.ts → positioned-tree.d.ts} +9 -37
- package/dist/positioned-tree.js +123 -0
- package/dist/reference-tokens2.d.ts +34 -3
- package/dist/reference-tokens2.js +56 -23
- package/dist/references2.d.ts +2 -0
- package/dist/references2.js +47 -16
- package/dist/scalars.d.ts +84 -14
- package/dist/scalars.js +158 -29
- package/dist/scanner.d.ts +26 -13
- package/dist/scanner.js +33 -15
- package/package.json +24 -24
- package/dist/references.js +0 -524
|
@@ -1,12 +1,43 @@
|
|
|
1
1
|
/** Reference tokens recorded while the annotated Core tree is being built. */
|
|
2
|
-
import type { StringSourceSpan } from './builder.js';
|
|
3
2
|
export type ReferenceToken2 = {
|
|
4
3
|
token: string;
|
|
4
|
+
/**
|
|
5
|
+
* Index of the token within the decoded scalar value — the splice point
|
|
6
|
+
* for interpolation. A UTF-16 offset (JS `String.slice` units); stays
|
|
7
|
+
* implementation-local, unlike `line`/`offset`.
|
|
8
|
+
*/
|
|
5
9
|
index: number;
|
|
6
|
-
|
|
10
|
+
/** Physical 1-based source line of the token (References 2.0 §2.4). */
|
|
7
11
|
line: number;
|
|
12
|
+
/**
|
|
13
|
+
* 0-based **codepoint** offset of the token within its source line — the
|
|
14
|
+
* `column - 1` a diagnostic reports, and the tie-break key for §5 error
|
|
15
|
+
* ordering. Identical across implementations.
|
|
16
|
+
*/
|
|
17
|
+
offset: number;
|
|
8
18
|
documentPath?: string;
|
|
9
19
|
partialPath?: string;
|
|
10
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* Where a scalar begins in the source: physical 1-based `line` and 0-based
|
|
23
|
+
* codepoint column `col` of its first character. A token's position is this
|
|
24
|
+
* anchor plus its codepoint offset within the value.
|
|
25
|
+
*
|
|
26
|
+
* `raw` is the value text a token's position is *read from*, before comment
|
|
27
|
+
* stripping and `\#` collapse (so `x: a\#b ${t}` reports `${t}` at its real
|
|
28
|
+
* column) — for a block scalar it is the original body lines joined with
|
|
29
|
+
* `\n` (§2.4 forbids reconstructing a token's line from the `^^`-merged
|
|
30
|
+
* string). Omitted only when it equals the decoded value.
|
|
31
|
+
*
|
|
32
|
+
* `tabAdjust[i]` is the column count Core §3 added to physical line
|
|
33
|
+
* `line + i` by leading-tab expansion; it is subtracted so the reported
|
|
34
|
+
* column is the *original*-source column (§2.4).
|
|
35
|
+
*/
|
|
36
|
+
export type ReferenceSource = {
|
|
37
|
+
raw?: string;
|
|
38
|
+
line: number;
|
|
39
|
+
col: number;
|
|
40
|
+
tabAdjust?: number[];
|
|
41
|
+
};
|
|
11
42
|
export declare const PURE_REFERENCE_2: RegExp;
|
|
12
|
-
export declare const scanReferenceTokens2: (value: string,
|
|
43
|
+
export declare const scanReferenceTokens2: (value: string, source: ReferenceSource | undefined) => ReferenceToken2[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Reference tokens recorded while the annotated Core tree is being built. */
|
|
1
2
|
const SEGMENT = '[a-zA-Z0-9_][a-zA-Z0-9_:-]*';
|
|
2
3
|
const DOC_PATH = `${SEGMENT}(?:\\.${SEGMENT})*`;
|
|
3
4
|
const PARTIAL_NAME = '[a-zA-Z0-9_][a-zA-Z0-9_:/-]*';
|
|
@@ -5,33 +6,65 @@ const PARTIAL_PATH = `${PARTIAL_NAME}(?:\\.${SEGMENT})*`;
|
|
|
5
6
|
export const PURE_REFERENCE_2 = new RegExp(`^(?:\\$\\{(${DOC_PATH})\\}|\\$\\((${PARTIAL_PATH})\\))$`);
|
|
6
7
|
const REFERENCE_2 = new RegExp(`\\$\\{(${DOC_PATH})\\}|\\$\\((${PARTIAL_PATH})\\)`, 'g');
|
|
7
8
|
const NO_TOKENS = [];
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/** Codepoint length of the UTF-16 slice `[a, b)` of `s`. */
|
|
10
|
+
const codepointLen = (s, a, b) => {
|
|
11
|
+
let n = 0;
|
|
12
|
+
for (let i = a; i < b;) {
|
|
13
|
+
i += s.codePointAt(i) > 0xffff ? 2 : 1;
|
|
14
|
+
n++;
|
|
15
|
+
}
|
|
16
|
+
return n;
|
|
17
|
+
};
|
|
18
|
+
/** Splice points (decoded value order): token text, UTF-16 index, path. */
|
|
19
|
+
const scanDecoded = (value) => {
|
|
11
20
|
REFERENCE_2.lastIndex = 0;
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
const out = [];
|
|
22
|
+
for (const m of value.matchAll(REFERENCE_2)) {
|
|
23
|
+
out.push({
|
|
24
|
+
token: m[0], index: m.index ?? 0,
|
|
25
|
+
...(m[1] !== undefined ? { documentPath: m[1] } : { partialPath: m[2] }),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
};
|
|
30
|
+
/** Physical `(line, offset)` of each token, in source order, from the raw text. */
|
|
31
|
+
const scanPhysical = (raw, firstLine, firstCol, tabAdjust) => {
|
|
32
|
+
REFERENCE_2.lastIndex = 0;
|
|
33
|
+
const out = [];
|
|
15
34
|
let scanned = 0;
|
|
16
|
-
let
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
let line = firstLine;
|
|
36
|
+
let col = firstCol;
|
|
37
|
+
let lineIndex = 0;
|
|
38
|
+
for (const m of raw.matchAll(REFERENCE_2)) {
|
|
39
|
+
const idx = m.index ?? 0;
|
|
40
|
+
while (scanned < idx) {
|
|
41
|
+
const cp = raw.codePointAt(scanned);
|
|
42
|
+
if (cp === 10) {
|
|
21
43
|
line++;
|
|
22
|
-
|
|
44
|
+
col = 0;
|
|
45
|
+
lineIndex++;
|
|
23
46
|
}
|
|
24
|
-
|
|
47
|
+
else
|
|
48
|
+
col++;
|
|
49
|
+
scanned += cp > 0xffff ? 2 : 1;
|
|
25
50
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
out.push({ line, offset: col - (tabAdjust?.[lineIndex] ?? 0) });
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
};
|
|
55
|
+
export const scanReferenceTokens2 = (value, source) => {
|
|
56
|
+
if (!value.includes('${') && !value.includes('$('))
|
|
57
|
+
return NO_TOKENS;
|
|
58
|
+
const decoded = scanDecoded(value);
|
|
59
|
+
if (decoded.length === 0)
|
|
60
|
+
return NO_TOKENS;
|
|
61
|
+
// The raw and decoded scans see the same tokens in the same order —
|
|
62
|
+
// `\#` collapse and `^^` merge never add or remove a `${…}` / `$(…)`.
|
|
63
|
+
const physical = source === undefined
|
|
64
|
+
? scanPhysical(value, 1, 0)
|
|
65
|
+
: scanPhysical(source.raw ?? value, source.line, source.col, source.tabAdjust);
|
|
66
|
+
if (physical.length !== decoded.length) {
|
|
67
|
+
throw new Error(`Lima internal: reference token count mismatch (raw ${physical.length}, decoded ${decoded.length})`);
|
|
35
68
|
}
|
|
36
|
-
return
|
|
69
|
+
return decoded.map((d, i) => ({ ...d, line: physical[i].line, offset: physical[i].offset }));
|
|
37
70
|
};
|
package/dist/references2.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export type ParseOptions = {
|
|
|
8
8
|
onWarning?: (diagnostic: Diagnostic) => void;
|
|
9
9
|
};
|
|
10
10
|
export declare const parse: <T extends Record<string, unknown> = Meta>(frontMatter: string, options?: ParseOptions) => T;
|
|
11
|
+
/** Test oracle: exercises the resolver without its dependency-result cache. */
|
|
12
|
+
export declare const __parseWithoutResolveCacheForTest: <T extends Record<string, unknown> = Meta>(frontMatter: string, options?: ParseOptions) => T;
|
|
11
13
|
/** @deprecated Use parse(). */
|
|
12
14
|
export declare const parseReferences: typeof parse;
|
|
13
15
|
export {};
|
package/dist/references2.js
CHANGED
|
@@ -3,11 +3,19 @@ import { canonicalString, codepointLength, countNodes, ingestPartialValue, PARTI
|
|
|
3
3
|
import { NESTING_DEPTH_LIMIT, parseCore, parseCoreWithPositions, toPlainValue, } from './core.js';
|
|
4
4
|
import { hasActiveReferences2 } from './scalars.js';
|
|
5
5
|
import { LimaError } from './errors.js';
|
|
6
|
-
import { collectAllParticipants, deepCopyPositioned, earliestParticipant, finalizePositioned, partialToPositioned, } from './
|
|
7
|
-
const emptyMapping = () => Object.create(null);
|
|
6
|
+
import { collectAllParticipants, deepCopyPositioned, earliestParticipant, emptyMapping, finalizePositioned, partialToPositioned, } from './positioned-tree.js';
|
|
8
7
|
const PARTIAL_NAME = '[a-zA-Z0-9_][a-zA-Z0-9_:/-]*';
|
|
9
8
|
const PARTIAL_NAME_RE = new RegExp(`^${PARTIAL_NAME}$`);
|
|
10
9
|
const MAX_EDGES = 3;
|
|
10
|
+
/** Copies a pure-reference target and stamps the new insertion without
|
|
11
|
+
* discarding provenance already attached to the copied root (R-112). */
|
|
12
|
+
const copyWithInsertion = (target, insertedAt) => {
|
|
13
|
+
const copy = deepCopyPositioned(target);
|
|
14
|
+
const priorInsertions = copy.insertedAt === undefined
|
|
15
|
+
? copy.priorInsertions
|
|
16
|
+
: [...(copy.priorInsertions ?? []), copy.insertedAt];
|
|
17
|
+
return { ...copy, insertedAt, priorInsertions };
|
|
18
|
+
};
|
|
11
19
|
const addDiagnostic = (ctx, diagnostic, offset = 0) => {
|
|
12
20
|
ctx.diagnostics.push({ ...diagnostic, offset });
|
|
13
21
|
};
|
|
@@ -75,9 +83,9 @@ const resolveNodeUncached = (node, document, partials, ctx, remainingEdges, stac
|
|
|
75
83
|
const match = tokenMatches(item)[0];
|
|
76
84
|
const token = match?.token ?? item.value;
|
|
77
85
|
addDiagnostic(ctx, {
|
|
78
|
-
code: 'INVALID_REFERENCE_SHAPE', line: item.line, token,
|
|
79
|
-
message: `Lima: reference "${token}" resolves to an array, which cannot be inserted as a sequence item at line ${item.line}`,
|
|
80
|
-
});
|
|
86
|
+
code: 'INVALID_REFERENCE_SHAPE', line: match?.line ?? item.line, token,
|
|
87
|
+
message: `Lima: reference "${token}" resolves to an array, which cannot be inserted as a sequence item at line ${match?.line ?? item.line}`,
|
|
88
|
+
}, match?.offset ?? 0);
|
|
81
89
|
return item;
|
|
82
90
|
}
|
|
83
91
|
return result.value;
|
|
@@ -120,8 +128,10 @@ const resolveNodeUncached = (node, document, partials, ctx, remainingEdges, stac
|
|
|
120
128
|
if (target === undefined || stack.has(target))
|
|
121
129
|
return { value: node, complete: false };
|
|
122
130
|
if (pure.partialPath !== undefined) {
|
|
131
|
+
// `partialToPositioned` never annotates partial targets: partials are
|
|
132
|
+
// inert and have no source token before this document insertion.
|
|
123
133
|
return {
|
|
124
|
-
value:
|
|
134
|
+
value: copyWithInsertion(target, { line: pure.line, offset: pure.offset, token: pure.token }),
|
|
125
135
|
complete: true,
|
|
126
136
|
};
|
|
127
137
|
}
|
|
@@ -134,7 +144,9 @@ const resolveNodeUncached = (node, document, partials, ctx, remainingEdges, stac
|
|
|
134
144
|
if (selected === undefined)
|
|
135
145
|
return { value: node, complete: false };
|
|
136
146
|
return {
|
|
137
|
-
|
|
147
|
+
// `selected` may itself be the root inserted by an earlier reference;
|
|
148
|
+
// both it and this outer copy participate in final-structure errors.
|
|
149
|
+
value: copyWithInsertion(selected, { line: pure.line, offset: pure.offset, token: pure.token }),
|
|
138
150
|
complete: true,
|
|
139
151
|
};
|
|
140
152
|
}
|
|
@@ -182,13 +194,24 @@ const resolveNodeUncached = (node, document, partials, ctx, remainingEdges, stac
|
|
|
182
194
|
}
|
|
183
195
|
return { value: { ...node, value: output, references2: unresolvedTokens }, complete };
|
|
184
196
|
};
|
|
185
|
-
/**
|
|
197
|
+
/**
|
|
198
|
+
* The cache key is complete without `stack`: for a fixed node and edge
|
|
199
|
+
* budget, its outgoing targets and traversal order are fixed by the parsed
|
|
200
|
+
* document. If one of those targets is already on the current stack, that
|
|
201
|
+
* target is on a dependency cycle reachable from this node; entering from a
|
|
202
|
+
* different ancestor cannot make the cycle complete, and the monotonically
|
|
203
|
+
* decreasing edge budget fixes where an overlong path becomes unresolved.
|
|
204
|
+
* Thus `stack` only detects an already-inevitable incomplete dependency; it
|
|
205
|
+
* does not select a different successful value. Top-level calls are excluded
|
|
206
|
+
* (`stack.size > 1`) and cached Resolution trees are immutable, so sharing a
|
|
207
|
+
* successful or incomplete result at the same remaining budget is safe.
|
|
208
|
+
*/
|
|
186
209
|
const resolveNode = (node, document, partials, ctx, remainingEdges, stack) => {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
210
|
+
const cache = ctx.cache;
|
|
211
|
+
if (cache === undefined || stack.size <= 1 || !(node.kind === 'array' || node.kind === 'mapping' ||
|
|
212
|
+
(isActiveString(node) && tokenMatches(node).length > 0)))
|
|
190
213
|
return resolveNodeUncached(node, document, partials, ctx, remainingEdges, stack);
|
|
191
|
-
const byBudget =
|
|
214
|
+
const byBudget = cache.get(node);
|
|
192
215
|
const cached = byBudget?.get(remainingEdges);
|
|
193
216
|
if (cached !== undefined)
|
|
194
217
|
return cached;
|
|
@@ -196,7 +219,7 @@ const resolveNode = (node, document, partials, ctx, remainingEdges, stack) => {
|
|
|
196
219
|
if (byBudget !== undefined)
|
|
197
220
|
byBudget.set(remainingEdges, result);
|
|
198
221
|
else
|
|
199
|
-
|
|
222
|
+
cache.set(node, new Map([[remainingEdges, result]]));
|
|
200
223
|
return result;
|
|
201
224
|
};
|
|
202
225
|
const validatePartials = (raw) => {
|
|
@@ -243,7 +266,7 @@ const scanUnresolved = (node, ctx) => {
|
|
|
243
266
|
for (const child of node.entries.values())
|
|
244
267
|
scanUnresolved(child, ctx);
|
|
245
268
|
};
|
|
246
|
-
|
|
269
|
+
const parseInternal = (frontMatter, options, useResolveCache) => {
|
|
247
270
|
if (options?.mode !== undefined && options.mode !== 'core' && options.mode !== 'references') {
|
|
248
271
|
throw new TypeError(`Lima: invalid parse mode "${String(options.mode)}"`);
|
|
249
272
|
}
|
|
@@ -260,7 +283,7 @@ export const parse = (frontMatter, options) => {
|
|
|
260
283
|
return parseCore(frontMatter, options);
|
|
261
284
|
}
|
|
262
285
|
const document = parseCoreWithPositions(frontMatter, { strict: options?.strict ?? false, onWarning: options?.onWarning });
|
|
263
|
-
const ctx = { diagnostics: [], cache: new WeakMap() };
|
|
286
|
+
const ctx = { diagnostics: [], ...(useResolveCache ? { cache: new WeakMap() } : {}) };
|
|
264
287
|
const resolved = new Map();
|
|
265
288
|
for (const [key, value] of document) {
|
|
266
289
|
const stack = new Set([value]);
|
|
@@ -271,7 +294,10 @@ export const parse = (frontMatter, options) => {
|
|
|
271
294
|
scanUnresolved(value, ctx);
|
|
272
295
|
if (ctx.diagnostics.length > 0) {
|
|
273
296
|
ctx.diagnostics.sort((a, b) => a.line - b.line || a.offset - b.offset);
|
|
274
|
-
|
|
297
|
+
const winner = ctx.diagnostics[0];
|
|
298
|
+
// §2.4: expose the token's physical position (1-based) — Rust and Go
|
|
299
|
+
// already do; `offset` was internal-only here.
|
|
300
|
+
throw new LimaError({ ...winner, column: winner.offset + 1 });
|
|
275
301
|
}
|
|
276
302
|
const finalized = [...resolved].map(([key, value]) => [key, finalizePositioned(value)]);
|
|
277
303
|
const depth = finalized.length === 0 ? 0 : Math.max(...finalized.map(([, value]) => value.depth));
|
|
@@ -280,6 +306,7 @@ export const parse = (frontMatter, options) => {
|
|
|
280
306
|
const winner = earliestParticipant(participants);
|
|
281
307
|
throw new LimaError({
|
|
282
308
|
code: 'RESOURCE_LIMIT', line: winner?.line ?? 1, token: winner?.token,
|
|
309
|
+
...(winner?.offset !== undefined ? { column: winner.offset + 1 } : {}),
|
|
283
310
|
message: `Lima: nesting depth exceeds maximum of ${NESTING_DEPTH_LIMIT} at line ${winner?.line ?? 1}`,
|
|
284
311
|
});
|
|
285
312
|
}
|
|
@@ -293,6 +320,7 @@ export const parse = (frontMatter, options) => {
|
|
|
293
320
|
const winner = earliestParticipant(participants);
|
|
294
321
|
throw new LimaError({
|
|
295
322
|
code: 'RESOURCE_LIMIT', line: winner?.line ?? 1, token: winner?.token,
|
|
323
|
+
...(winner?.offset !== undefined ? { column: winner.offset + 1 } : {}),
|
|
296
324
|
message: `Lima: result exceeds maximum size of ${RESULT_NODE_LIMIT} total nodes at line ${winner?.line ?? 1}`,
|
|
297
325
|
});
|
|
298
326
|
}
|
|
@@ -301,5 +329,8 @@ export const parse = (frontMatter, options) => {
|
|
|
301
329
|
out[key] = value.native;
|
|
302
330
|
return out;
|
|
303
331
|
};
|
|
332
|
+
export const parse = (frontMatter, options) => parseInternal(frontMatter, options, true);
|
|
333
|
+
/** Test oracle: exercises the resolver without its dependency-result cache. */
|
|
334
|
+
export const __parseWithoutResolveCacheForTest = (frontMatter, options) => parseInternal(frontMatter, options, false);
|
|
304
335
|
/** @deprecated Use parse(). */
|
|
305
336
|
export const parseReferences = parse;
|
package/dist/scalars.d.ts
CHANGED
|
@@ -7,40 +7,47 @@
|
|
|
7
7
|
import { type LimaValue } from './value.js';
|
|
8
8
|
import { type ParseContext } from './normalize.js';
|
|
9
9
|
import type { ValueBuilder } from './builder.js';
|
|
10
|
-
import { type ReferenceToken2 } from './reference-tokens2.js';
|
|
10
|
+
import { type ReferenceSource, type ReferenceToken2 } from './reference-tokens2.js';
|
|
11
11
|
/**
|
|
12
12
|
* `insertedAt` is never set by Core — it's a References-only annotation
|
|
13
13
|
* (see references.ts's `resolveTree`), stamped on the root of a value
|
|
14
14
|
* copied in by a successful pure-reference resolution, with the source
|
|
15
|
-
* token and
|
|
16
|
-
* global-error attribution (R-112): when a final-result
|
|
17
|
-
* depth, total node count) is violated, the lowest
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* token, line, and character offset that caused the insertion. It powers
|
|
16
|
+
* References §5's global-error attribution (R-112): when a final-result
|
|
17
|
+
* limit (nesting depth, total node count) is violated, the lowest source
|
|
18
|
+
* position (`line`, then `offset`) among the participating nodes identifies
|
|
19
|
+
* which reference token to blame — the spec requires the error message to
|
|
20
|
+
* include both the token and the line. `priorInsertions` retains older
|
|
21
|
+
* root provenance when a resolved pure-reference result is copied again.
|
|
20
22
|
*/
|
|
21
23
|
export type InsertedAt = {
|
|
22
24
|
line: number;
|
|
23
25
|
token: string;
|
|
26
|
+
offset?: number;
|
|
24
27
|
};
|
|
25
28
|
export type PositionedValue = {
|
|
26
29
|
kind: 'null';
|
|
27
30
|
line: number;
|
|
28
31
|
insertedAt?: InsertedAt;
|
|
32
|
+
priorInsertions?: InsertedAt[];
|
|
29
33
|
} | {
|
|
30
34
|
kind: 'bool';
|
|
31
35
|
value: boolean;
|
|
32
36
|
line: number;
|
|
33
37
|
insertedAt?: InsertedAt;
|
|
38
|
+
priorInsertions?: InsertedAt[];
|
|
34
39
|
} | {
|
|
35
40
|
kind: 'int';
|
|
36
41
|
value: number;
|
|
37
42
|
line: number;
|
|
38
43
|
insertedAt?: InsertedAt;
|
|
44
|
+
priorInsertions?: InsertedAt[];
|
|
39
45
|
} | {
|
|
40
46
|
kind: 'float';
|
|
41
47
|
value: number;
|
|
42
48
|
line: number;
|
|
43
49
|
insertedAt?: InsertedAt;
|
|
50
|
+
priorInsertions?: InsertedAt[];
|
|
44
51
|
} | {
|
|
45
52
|
kind: 'string';
|
|
46
53
|
value: string;
|
|
@@ -48,23 +55,27 @@ export type PositionedValue = {
|
|
|
48
55
|
quoted: boolean;
|
|
49
56
|
references2?: ReferenceToken2[];
|
|
50
57
|
insertedAt?: InsertedAt;
|
|
58
|
+
priorInsertions?: InsertedAt[];
|
|
51
59
|
} | {
|
|
52
60
|
kind: 'instant';
|
|
53
61
|
value: Date;
|
|
54
62
|
line: number;
|
|
55
63
|
insertedAt?: InsertedAt;
|
|
64
|
+
priorInsertions?: InsertedAt[];
|
|
56
65
|
} | {
|
|
57
66
|
kind: 'array';
|
|
58
67
|
items: PositionedValue[];
|
|
59
68
|
line: number;
|
|
60
69
|
references2Active?: boolean;
|
|
61
70
|
insertedAt?: InsertedAt;
|
|
71
|
+
priorInsertions?: InsertedAt[];
|
|
62
72
|
} | {
|
|
63
73
|
kind: 'mapping';
|
|
64
74
|
entries: Map<string, PositionedValue>;
|
|
65
75
|
line: number;
|
|
66
76
|
references2Active?: boolean;
|
|
67
77
|
insertedAt?: InsertedAt;
|
|
78
|
+
priorInsertions?: InsertedAt[];
|
|
68
79
|
};
|
|
69
80
|
export declare const hasActiveReferences2: (value: PositionedValue) => boolean;
|
|
70
81
|
/** The `ValueBuilder<PositionedValue>` — reconstructs today's annotated tree exactly, for References. */
|
|
@@ -78,17 +89,76 @@ export declare const NO_SPAN_VALUE: unique symbol;
|
|
|
78
89
|
* handled by the full string-based scalar/flow grammar.
|
|
79
90
|
*/
|
|
80
91
|
export declare const parseSimpleScalarSpan: <V, M>(source: string, start: number, end: number, line: number, strict: boolean, builder: ValueBuilder<V, M>) => V | typeof NO_SPAN_VALUE;
|
|
92
|
+
/**
|
|
93
|
+
* Index of the quote that closes a quoted scalar opening at `s[0]`, or -1
|
|
94
|
+
* if it is never closed. Escape-aware, so `"a\""` closes at its last
|
|
95
|
+
* character rather than the escaped inner `"`:
|
|
96
|
+
* - double quotes: a backslash escapes the next character (§6.1.2);
|
|
97
|
+
* - single-quoted *values* (§6.1.3 "Backslash pairing"): scanning left
|
|
98
|
+
* to right, `\\` (two literal backslashes) and `\'` (an escaped quote)
|
|
99
|
+
* are each consumed as a two-character unit, so a lone `\` is literal
|
|
100
|
+
* and a closing `'` after an even backslash run closes the string.
|
|
101
|
+
* Pass `singleQuoteEscape = false` for single-quoted *keys*, which are
|
|
102
|
+
* fully literal (§5.2), where the first `'` closes.
|
|
103
|
+
* `s[0]` is assumed to be `'` or `"`.
|
|
104
|
+
*/
|
|
105
|
+
export declare const closingQuoteIndex: (s: string, singleQuoteEscape?: boolean) => number;
|
|
106
|
+
/**
|
|
107
|
+
* Whether `raw` (a key candidate as written, before quote stripping) is a
|
|
108
|
+
* usable Lima key:
|
|
109
|
+
* - a quoted string that is properly closed at its final character, or
|
|
110
|
+
* - an unquoted token matching the Core §5.1 grammar.
|
|
111
|
+
*
|
|
112
|
+
* §5.1 is a closed pattern: a key with a space, a dot, a slash, a paren, or
|
|
113
|
+
* any other character outside `[a-zA-Z0-9_:\-]` (a surviving NBSP included)
|
|
114
|
+
* is not an unquoted key. Per §4, such a line/item is unrecognised and
|
|
115
|
+
* skipped in *both* modes — §10's strict error list stays closed. The three
|
|
116
|
+
* mapping contexts (top level, block-nested, flow) now share this one
|
|
117
|
+
* check; `docs/decisions/structural-indentation-unicode-whitespace.md`
|
|
118
|
+
* governs only whether Unicode whitespace counts as *indentation*, not
|
|
119
|
+
* whether it may appear in a key.
|
|
120
|
+
*/
|
|
121
|
+
export declare const isValidKey: (raw: string) => boolean;
|
|
81
122
|
export declare const unescapeDQ: (s: string, strict?: boolean, line?: number) => string;
|
|
123
|
+
/**
|
|
124
|
+
* The value text with a trailing `#` comment removed, but with `\#`
|
|
125
|
+
* escapes left as written. This is the *physical* form a References 2.0
|
|
126
|
+
* token's column is measured against (§2.4): the comment is not part of
|
|
127
|
+
* the value (so its `${…}`-shaped text must not be scanned as a token),
|
|
128
|
+
* but a `\#` before a token still occupies its two source columns.
|
|
129
|
+
*
|
|
130
|
+
* `trimEnd()` is exactly the project whitespace set (ECMAScript WhiteSpace
|
|
131
|
+
* + LineTerminator = `isTrimWhitespace`) — a U+00A0 / FEFF between the
|
|
132
|
+
* value and the `#` is not part of the value. The Rust and Go ports must
|
|
133
|
+
* use `is_trim_whitespace` here, not a host `trim_end` or `" \t"`.
|
|
134
|
+
*/
|
|
135
|
+
export declare const stripCommentKeepEscapes: (val: string) => string;
|
|
136
|
+
/**
|
|
137
|
+
* The value text with a trailing `#` comment removed and each `\#` *outside*
|
|
138
|
+
* a quoted string collapsed to `#` (Core §6.1.4 — the escaped-hash rule is
|
|
139
|
+
* defined for unquoted values only). Inside `"..."` / `'...'` the backslash
|
|
140
|
+
* is left intact: `\#` there is an unknown double-quoted escape (§6.1.2:
|
|
141
|
+
* strict throws, non-strict keeps it) or a literal backslash in a
|
|
142
|
+
* single-quoted string (§6.1.3). Finds the same comment boundary as
|
|
143
|
+
* `stripCommentKeepEscapes` — the two must agree, they annotate the same
|
|
144
|
+
* value.
|
|
145
|
+
*/
|
|
82
146
|
export declare const stripComment: (val: string) => string;
|
|
83
|
-
/**
|
|
84
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Strips a key's surrounding quotes — unescaping a double-quoted key
|
|
149
|
+
* (§6.1.2 escapes, strict-checked), taking a single-quoted key literally
|
|
150
|
+
* (§5.2). Returns `s` unchanged when it is not a properly-closed quoted
|
|
151
|
+
* string. Callers gate on `isValidKey` first, so an unterminated or
|
|
152
|
+
* trailing-content key never reaches here as a real key.
|
|
153
|
+
*/
|
|
154
|
+
export declare const stripKeyQuotes: (s: string, strict?: boolean, line?: number) => string;
|
|
85
155
|
/**
|
|
86
156
|
* Quoted-or-typed scalar, shared by every value position (top-level inline
|
|
87
157
|
* values, flow-sequence/flow-mapping items, block-array scalar items).
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
158
|
+
* §10.1's two quoted-string strict checks — "unterminated quoted string"
|
|
159
|
+
* and "non-whitespace content after closing quote in an inline value" —
|
|
160
|
+
* apply in every one of those positions, so they are enforced here rather
|
|
161
|
+
* than gated to the top level.
|
|
92
162
|
*/
|
|
93
|
-
export declare const parseQuotedOrTyped: <V, M>(raw: string, ctx: ParseContext, line: number,
|
|
94
|
-
export declare const parseScalarValue: <V, M>(raw: string, ctx: ParseContext, line: number, builder: ValueBuilder<V, M
|
|
163
|
+
export declare const parseQuotedOrTyped: <V, M>(raw: string, ctx: ParseContext, line: number, builder: ValueBuilder<V, M>, source?: ReferenceSource) => V;
|
|
164
|
+
export declare const parseScalarValue: <V, M>(raw: string, ctx: ParseContext, line: number, builder: ValueBuilder<V, M>, source?: ReferenceSource) => V;
|