@limaformat/lima 0.5.1 → 0.7.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 +99 -4
- package/README.md +6 -6
- package/dist/block-cursor.d.ts +4 -3
- package/dist/block-cursor.js +7 -3
- package/dist/block.js +123 -48
- package/dist/core.d.ts +4 -3
- package/dist/core.js +49 -30
- package/dist/document.d.ts +2 -0
- package/dist/document.js +29 -0
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/key-grammar.d.ts +3 -0
- package/dist/key-grammar.js +58 -0
- package/dist/key-hints.d.ts +2 -0
- package/dist/key-hints.js +17 -0
- package/dist/reference-tokens2.d.ts +0 -5
- package/dist/reference-tokens2.js +65 -15
- package/dist/references2.js +8 -7
- package/dist/scalars.js +12 -7
- package/dist/scanner.js +28 -4
- package/dist/structure.d.ts +13 -0
- package/dist/structure.js +118 -0
- package/dist/unicode-key-table.d.ts +1 -0
- package/dist/unicode-key-table.js +245 -0
- package/package.json +2 -2
package/dist/core.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lima Core 1.
|
|
2
|
+
* Lima Core 1.1 parser — reference-unaware by construction (Appendix B:
|
|
3
3
|
* reference resolution is exclusively the concern of the optional
|
|
4
4
|
* References extension). `($key)`/`(%key)`-shaped text is never
|
|
5
5
|
* recognised or coerced here; it simply falls through as an ordinary
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* and mappings) — this file is the orchestrator that ties them together.
|
|
20
20
|
*/
|
|
21
21
|
import { checkKeyLength, byteLength, DOCUMENT_SIZE_LIMIT, TOP_LEVEL_KEY_LIMIT, NESTING_DEPTH_LIMIT, } from './normalize.js';
|
|
22
|
+
import { validateDocument } from './document.js';
|
|
22
23
|
import { unescapeDQ, stripComment, stripCommentKeepEscapes, positionedBuilder, NO_SPAN_VALUE, parseSimpleScalarSpan, } from './scalars.js';
|
|
23
24
|
import { isTrimWhitespace } from './chars.js';
|
|
24
25
|
import { parseFlowOrScalarValue } from './flow.js';
|
|
@@ -26,6 +27,7 @@ import { parseBlockRange } from './block.js';
|
|
|
26
27
|
import { buildBlockScalar } from './block-scalar.js';
|
|
27
28
|
import { LimaError } from './errors.js';
|
|
28
29
|
import { KeyCursor, scanKeys } from './scanner.js';
|
|
30
|
+
import { checkRangeLines, isBlockScalarMarker } from './structure.js';
|
|
29
31
|
export { NESTING_DEPTH_LIMIT, SCALAR_LENGTH_LIMIT } from './normalize.js';
|
|
30
32
|
export { toPlainValue } from './scalars.js';
|
|
31
33
|
/** Every Lima mapping result must be a prototype-free object (Core §11.1). */
|
|
@@ -77,7 +79,7 @@ const lineAt = (s, pos) => {
|
|
|
77
79
|
return n;
|
|
78
80
|
};
|
|
79
81
|
/**
|
|
80
|
-
* Parses Lima Core 1.
|
|
82
|
+
* Parses Lima Core 1.1 syntax, generic over the output representation
|
|
81
83
|
* (`builder` — see `builder.ts`): References needs the annotated
|
|
82
84
|
* `PositionedValue` tree (`parseCoreWithPositions` below fixes `V` to
|
|
83
85
|
* that), while `parseCore` fixes `V` to the public native shape directly,
|
|
@@ -186,6 +188,18 @@ const parseCoreGeneric = (frontMatter, ctx, builder, computeDepth) => {
|
|
|
186
188
|
}
|
|
187
189
|
const firstNewline = inlineEnd ?? frontMatter.indexOf('\n', rawStart);
|
|
188
190
|
const newlineInRange = firstNewline < nextStart;
|
|
191
|
+
// §6.1.5: `|` alone is a literal block scalar marker, with a body or without one (then its value is empty).
|
|
192
|
+
// The introducing key is at column 0; `buildBlockScalar` finds the scalar's own extent within the range (which
|
|
193
|
+
// runs to the next top-level key), so a dedented comment or dedented freetext before that key ends the scalar
|
|
194
|
+
// rather than being absorbed into it.
|
|
195
|
+
if (!isBlock && frontMatter.charCodeAt(rawStart) === 124) {
|
|
196
|
+
const markerEnd = newlineInRange && firstNewline >= 0 ? firstNewline : nextStart;
|
|
197
|
+
if (frontMatter.slice(rawStart, markerEnd).trim() === '|') {
|
|
198
|
+
const bodyLines = markerEnd < nextStart ? frontMatter.slice(markerEnd + 1, nextStart).split('\n') : [];
|
|
199
|
+
builder.setMapping(root, key, buildBlockScalar(bodyLines, 0, line, ctx, builder).value);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
189
203
|
// Physical codepoint column of the value's first character in its
|
|
190
204
|
// source line — the anchor for References 2.0 token positions (§2.4).
|
|
191
205
|
// References 2.0 token positions (§2.4): only build the source anchor
|
|
@@ -232,31 +246,18 @@ const parseCoreGeneric = (frontMatter, ctx, builder, computeDepth) => {
|
|
|
232
246
|
lineStart = j + 1;
|
|
233
247
|
}
|
|
234
248
|
}
|
|
235
|
-
{
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
const line0Trimmed = lines[0].trim();
|
|
241
|
-
if (lines.length === 1 || line0Trimmed !== '|') {
|
|
242
|
-
const line0 = lines[0];
|
|
243
|
-
const hasHash = line0.includes('#');
|
|
244
|
-
const val = hasHash ? stripComment(line0) : line0;
|
|
245
|
-
const source = wantSource ? {
|
|
246
|
-
raw: hasHash ? stripCommentKeepEscapes(line0) : line0,
|
|
247
|
-
line, col: valueCol, tabAdjust: lineTabAdjust,
|
|
248
|
-
} : undefined;
|
|
249
|
-
builder.setMapping(root, key, parseFlowOrScalarValue(val, ctx, line, builder, source));
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
// Multi-line string (`|` literal block scalar). The introducing key
|
|
253
|
-
// is at column 0 here; `buildBlockScalar` finds the scalar's own
|
|
254
|
-
// extent within the range (which runs to the next top-level key),
|
|
255
|
-
// so a dedented comment or dedented freetext before that key ends
|
|
256
|
-
// the scalar rather than being absorbed into it.
|
|
257
|
-
const bodyLines = raw.slice(raw.indexOf('\n') + 1).split('\n');
|
|
258
|
-
builder.setMapping(root, key, buildBlockScalar(bodyLines, 0, line, ctx, builder).value);
|
|
249
|
+
if (lines.length === 0) {
|
|
250
|
+
builder.setMapping(root, key, builder.null(line));
|
|
251
|
+
return;
|
|
259
252
|
}
|
|
253
|
+
const line0 = lines[0];
|
|
254
|
+
const hasHash = line0.includes('#');
|
|
255
|
+
const val = hasHash ? stripComment(line0) : line0;
|
|
256
|
+
const source = wantSource ? {
|
|
257
|
+
raw: hasHash ? stripCommentKeepEscapes(line0) : line0,
|
|
258
|
+
line, col: valueCol, tabAdjust: lineTabAdjust,
|
|
259
|
+
} : undefined;
|
|
260
|
+
builder.setMapping(root, key, parseFlowOrScalarValue(val, ctx, line, builder, source));
|
|
260
261
|
};
|
|
261
262
|
// A recognised top-level entry needs at least one UTF-16 unit of key text,
|
|
262
263
|
// `:`, and its terminating newline: three units. Consequently 129 entries
|
|
@@ -293,6 +294,9 @@ const parseCoreGeneric = (frontMatter, ctx, builder, computeDepth) => {
|
|
|
293
294
|
else {
|
|
294
295
|
const matches = scanKeys(frontMatter);
|
|
295
296
|
const keyCount = matches.length;
|
|
297
|
+
// Core 1.1: before the first key no line belongs to any structure (structure.ts)
|
|
298
|
+
if (strict)
|
|
299
|
+
checkRangeLines(frontMatter, 0, keyCount === 0 ? frontMatter.length : matches[0].matchStart, 1, 'none');
|
|
296
300
|
if (keyCount === 0)
|
|
297
301
|
return root;
|
|
298
302
|
if (keyCount > TOP_LEVEL_KEY_LIMIT) {
|
|
@@ -305,8 +309,19 @@ const parseCoreGeneric = (frontMatter, ctx, builder, computeDepth) => {
|
|
|
305
309
|
const m = matches[i];
|
|
306
310
|
const rawDQ = m.doubleQuotedRaw;
|
|
307
311
|
const key = m.unquoted ?? m.singleQuoted ?? (rawDQ !== undefined ? unescapeDQ(rawDQ, strict, m.line) : undefined);
|
|
312
|
+
const nextStart = i + 1 < keyCount ? matches[i + 1].matchStart : frontMatter.length;
|
|
308
313
|
if (key !== undefined)
|
|
309
|
-
parseEntry(key, m.line, m.rawStart, m.isBlock, m.inlineEnd,
|
|
314
|
+
parseEntry(key, m.line, m.rawStart, m.isBlock, m.inlineEnd, nextStart);
|
|
315
|
+
// Core 1.1: the lines after the key's own line, up to the next key, belong to its block or to nothing
|
|
316
|
+
if (strict) {
|
|
317
|
+
const lineEnd = m.isBlock ? m.rawStart - 1 : (m.inlineEnd ?? frontMatter.indexOf('\n', m.rawStart));
|
|
318
|
+
// no line after the key's own one: nothing to check, the usual case
|
|
319
|
+
if (lineEnd !== -1 && lineEnd + 1 < nextStart) {
|
|
320
|
+
const owner = m.isBlock ? 'mapping'
|
|
321
|
+
: isBlockScalarMarker(frontMatter, m.rawStart, lineEnd) ? 'scalar' : 'inline';
|
|
322
|
+
checkRangeLines(frontMatter, lineEnd + 1, nextStart, m.line + 1, owner, m.rawStart, lineEnd);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
310
325
|
}
|
|
311
326
|
}
|
|
312
327
|
// Core §9 nesting depth, over Core's own reference-inert tree. When
|
|
@@ -322,7 +337,7 @@ const parseCoreGeneric = (frontMatter, ctx, builder, computeDepth) => {
|
|
|
322
337
|
return root;
|
|
323
338
|
};
|
|
324
339
|
/**
|
|
325
|
-
* Parses Lima Core 1.
|
|
340
|
+
* Parses Lima Core 1.1 syntax into the internal annotated value tree —
|
|
326
341
|
* every node carrying its source line, string leaves additionally carrying
|
|
327
342
|
* whether they came from quoted syntax. `($key)`/`(%key)` text is left
|
|
328
343
|
* exactly as written; nothing here ever inspects or resolves it. The
|
|
@@ -438,13 +453,17 @@ export const toNativeFromPositioned = (v) => {
|
|
|
438
453
|
}
|
|
439
454
|
};
|
|
440
455
|
/**
|
|
441
|
-
* Public Core 1.
|
|
456
|
+
* Public Core 1.1 entry point. Never resolves `($key)`/`(%key)` text — see
|
|
442
457
|
* the module doc comment. Equivalent in observable behavior to calling
|
|
443
458
|
* `parseReferences()` with no partials on a document that happens to
|
|
444
459
|
* contain no references, except that here reference-shaped text is
|
|
445
460
|
* guaranteed to always pass through unresolved, even in strict mode.
|
|
446
461
|
*/
|
|
447
|
-
export const
|
|
462
|
+
export const parseCoreValidated = (frontMatter, options) => {
|
|
448
463
|
const ctx = { strict: options?.strict ?? false, onWarning: options?.onWarning };
|
|
449
464
|
return parseCoreGeneric(frontMatter, ctx, nativeBuilder, depthOfNative);
|
|
450
465
|
};
|
|
466
|
+
export const parseCore = (frontMatter, options) => {
|
|
467
|
+
validateDocument(frontMatter);
|
|
468
|
+
return parseCoreValidated(frontMatter, options);
|
|
469
|
+
};
|
package/dist/document.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { LimaError } from './errors.js';
|
|
2
|
+
/** Core §1: a document is a sequence of Unicode scalar values. */
|
|
3
|
+
export const validateDocument = (document) => {
|
|
4
|
+
// Node 22's native scalar-value check keeps the common valid path out of
|
|
5
|
+
// JavaScript. Only malformed input pays for the line-attribution walk.
|
|
6
|
+
if (document.isWellFormed())
|
|
7
|
+
return;
|
|
8
|
+
let line = 1;
|
|
9
|
+
for (let i = 0; i < document.length; i++) {
|
|
10
|
+
const c = document.charCodeAt(i);
|
|
11
|
+
if (c === 10) {
|
|
12
|
+
line++;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (c >= 0xd800 && c <= 0xdbff && i + 1 < document.length) {
|
|
16
|
+
const low = document.charCodeAt(i + 1);
|
|
17
|
+
if (low >= 0xdc00 && low <= 0xdfff) {
|
|
18
|
+
i++;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (c >= 0xd800 && c <= 0xdfff) {
|
|
23
|
+
throw new LimaError({
|
|
24
|
+
code: 'INVALID_DOCUMENT', line,
|
|
25
|
+
message: `Lima: document contains an invalid Unicode scalar value at line ${line}`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
package/dist/errors.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* module directly rather than duplicating it) inspect *why* a parse failed
|
|
9
9
|
* without re-parsing the message string.
|
|
10
10
|
*/
|
|
11
|
-
export type LimaDiagnosticCode = 'INVALID_ESCAPE' | 'INVALID_QUOTE' | 'INVALID_DATE' | 'INVALID_NUMBER' | 'INVALID_REFERENCE_SHAPE' | 'INVALID_INDENTATION' | 'INVALID_FLOW_SYNTAX' | 'DUPLICATE_KEY' | 'RESOURCE_LIMIT' | 'UNRESOLVED_REFERENCE' | 'INVALID_INTERPOLATION' | 'INVALID_PARTIAL';
|
|
11
|
+
export type LimaDiagnosticCode = 'INVALID_DOCUMENT' | 'INVALID_ESCAPE' | 'INVALID_QUOTE' | 'INVALID_DATE' | 'INVALID_NUMBER' | 'INVALID_REFERENCE_SHAPE' | 'INVALID_INDENTATION' | 'UNRECOGNIZED_LINE' | 'INVALID_FLOW_SYNTAX' | 'DUPLICATE_KEY' | 'RESOURCE_LIMIT' | 'UNRESOLVED_REFERENCE' | 'INVALID_INTERPOLATION' | 'INVALID_PARTIAL';
|
|
12
12
|
export interface LimaDiagnostic {
|
|
13
13
|
code: LimaDiagnosticCode;
|
|
14
14
|
message: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Public entry point. The implementation is split per Appendix B's own
|
|
5
5
|
* layering:
|
|
6
|
-
* - `core.ts` — Lima Core 1.
|
|
7
|
-
* - `references2.ts` — the References 2.
|
|
6
|
+
* - `core.ts` — Lima Core 1.2, reference-unaware by construction.
|
|
7
|
+
* - `references2.ts` — the References 2.1 extension layered on Core.
|
|
8
8
|
* - `references.ts` — the frozen internal References 1.0 implementation.
|
|
9
9
|
* - `value.ts` — the shared Lima Value Model both build on.
|
|
10
10
|
*
|
|
11
|
-
* `parse` is the primary References 2.
|
|
11
|
+
* `parse` is the primary References 2.1 entry point. `parseReferences` is
|
|
12
12
|
* retained as its deprecated compatibility alias.
|
|
13
13
|
*/
|
|
14
14
|
export { parseCore, type CoreOptions, type Diagnostic } from './core.js';
|
package/dist/index.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Public entry point. The implementation is split per Appendix B's own
|
|
5
5
|
* layering:
|
|
6
|
-
* - `core.ts` — Lima Core 1.
|
|
7
|
-
* - `references2.ts` — the References 2.
|
|
6
|
+
* - `core.ts` — Lima Core 1.2, reference-unaware by construction.
|
|
7
|
+
* - `references2.ts` — the References 2.1 extension layered on Core.
|
|
8
8
|
* - `references.ts` — the frozen internal References 1.0 implementation.
|
|
9
9
|
* - `value.ts` — the shared Lima Value Model both build on.
|
|
10
10
|
*
|
|
11
|
-
* `parse` is the primary References 2.
|
|
11
|
+
* `parse` is the primary References 2.1 entry point. `parseReferences` is
|
|
12
12
|
* retained as its deprecated compatibility alias.
|
|
13
13
|
*/
|
|
14
14
|
export { parseCore } from './core.js';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { UNICODE_KEY_RANGES } from './unicode-key-table.js';
|
|
2
|
+
const unicodeKeyCategory = (codePoint) => {
|
|
3
|
+
let low = 0;
|
|
4
|
+
let high = UNICODE_KEY_RANGES.length / 3;
|
|
5
|
+
while (low < high) {
|
|
6
|
+
const mid = (low + high) >>> 1;
|
|
7
|
+
const offset = mid * 3;
|
|
8
|
+
if (codePoint < UNICODE_KEY_RANGES[offset])
|
|
9
|
+
high = mid;
|
|
10
|
+
else if (codePoint > UNICODE_KEY_RANGES[offset + 1])
|
|
11
|
+
low = mid + 1;
|
|
12
|
+
else
|
|
13
|
+
return UNICODE_KEY_RANGES[offset + 2];
|
|
14
|
+
}
|
|
15
|
+
return 0;
|
|
16
|
+
};
|
|
17
|
+
export const isKeyInitialCodePoint = (c) => {
|
|
18
|
+
if (c <= 0x7f)
|
|
19
|
+
return c === 95 || c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122;
|
|
20
|
+
return (unicodeKeyCategory(c) & 5) !== 0; // L or N
|
|
21
|
+
};
|
|
22
|
+
export const isKeyCharacterCodePoint = (c) => {
|
|
23
|
+
if (c <= 0x7f)
|
|
24
|
+
return isKeyInitialCodePoint(c) || c === 58 || c === 45 || c === 42;
|
|
25
|
+
return c === 0xb7 || unicodeKeyCategory(c) !== 0; // L, M or N
|
|
26
|
+
};
|
|
27
|
+
export const isValidUnquotedKey = (raw, allowSlash = false) => {
|
|
28
|
+
if (raw.length === 0)
|
|
29
|
+
return false;
|
|
30
|
+
let i = 0;
|
|
31
|
+
let cp = raw.charCodeAt(0);
|
|
32
|
+
if (cp <= 0x7f) {
|
|
33
|
+
if (!(cp === 95 || cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122))
|
|
34
|
+
return false;
|
|
35
|
+
i = 1;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
cp = raw.codePointAt(0);
|
|
39
|
+
if (!isKeyInitialCodePoint(cp))
|
|
40
|
+
return false;
|
|
41
|
+
i = cp > 0xffff ? 2 : 1;
|
|
42
|
+
}
|
|
43
|
+
while (i < raw.length) {
|
|
44
|
+
cp = raw.charCodeAt(i);
|
|
45
|
+
if (cp <= 0x7f) {
|
|
46
|
+
if (!(cp === 95 || cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 ||
|
|
47
|
+
cp === 58 || cp === 45 || cp === 42 || allowSlash && cp === 47))
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
cp = raw.codePointAt(i);
|
|
52
|
+
if (!isKeyCharacterCodePoint(cp))
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
i += cp > 0xffff ? 2 : 1;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const KEY_GRAMMAR_HINT = " (an unquoted key starts with a Unicode letter or number or \"_\", then has letters, marks, numbers, \"_\", \":\", \"-\", \"*\" and \"\u00B7\"; quote any other)";
|
|
2
|
+
export declare const specificKeyHint: (text: string) => string | undefined;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { isTrimWhitespace } from './chars.js';
|
|
2
|
+
import { isValidUnquotedKey } from './key-grammar.js';
|
|
3
|
+
export const KEY_GRAMMAR_HINT = ' (an unquoted key starts with a Unicode letter or number or "_", then has letters, marks, numbers, "_", ":", "-", "*" and "·"; quote any other)';
|
|
4
|
+
export const specificKeyHint = (text) => {
|
|
5
|
+
let i = 0;
|
|
6
|
+
while (text.charCodeAt(i) === 32)
|
|
7
|
+
i++;
|
|
8
|
+
const cp = text.codePointAt(i);
|
|
9
|
+
if (cp !== undefined && cp !== 32 && isTrimWhitespace(cp)) {
|
|
10
|
+
return ` (U+${cp.toString(16).toUpperCase().padStart(4, '0')} before the key; Lima indents by spaces only)`;
|
|
11
|
+
}
|
|
12
|
+
const colon = text.indexOf('\uFF1A');
|
|
13
|
+
if (colon > 0 && isValidUnquotedKey(text.slice(0, colon))) {
|
|
14
|
+
return ' (the key-value separator is ": " (U+003A and a space), not U+FF1A FULLWIDTH COLON)';
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
};
|
|
@@ -39,11 +39,6 @@ export type ReferenceSource = {
|
|
|
39
39
|
col: number;
|
|
40
40
|
tabAdjust?: number[];
|
|
41
41
|
};
|
|
42
|
-
export declare const SEGMENT = "[a-zA-Z0-9_][a-zA-Z0-9_:-]*";
|
|
43
|
-
export declare const DOC_PATH = "[a-zA-Z0-9_][a-zA-Z0-9_:-]*(?:\\.[a-zA-Z0-9_][a-zA-Z0-9_:-]*)*";
|
|
44
|
-
export declare const PARTIAL_NAME = "[a-zA-Z0-9_][a-zA-Z0-9_:/-]*";
|
|
45
|
-
export declare const PARTIAL_PATH = "[a-zA-Z0-9_][a-zA-Z0-9_:/-]*(?:\\.[a-zA-Z0-9_][a-zA-Z0-9_:-]*)*";
|
|
46
|
-
export declare const PURE_REFERENCE_2: RegExp;
|
|
47
42
|
/** Converts a 0-based codepoint offset to JavaScript/LSP UTF-16 units. */
|
|
48
43
|
export declare const codepointOffsetToUtf16: (text: string, codepointOffset: number) => number;
|
|
49
44
|
/** Converts a 0-based JavaScript/LSP UTF-16 offset to a codepoint offset. */
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/** Reference tokens recorded while the annotated Core tree is being built. */
|
|
2
|
-
|
|
3
|
-
export const DOC_PATH = `${SEGMENT}(?:\\.${SEGMENT})*`;
|
|
4
|
-
export const PARTIAL_NAME = '[a-zA-Z0-9_][a-zA-Z0-9_:/-]*';
|
|
5
|
-
export const PARTIAL_PATH = `${PARTIAL_NAME}(?:\\.${SEGMENT})*`;
|
|
6
|
-
export const PURE_REFERENCE_2 = new RegExp(`^(?:\\$\\{(${DOC_PATH})\\}|\\$\\((${PARTIAL_PATH})\\))$`);
|
|
7
|
-
const REFERENCE_2 = new RegExp(`\\$\\{(${DOC_PATH})\\}|\\$\\((${PARTIAL_PATH})\\)`, 'g');
|
|
2
|
+
import { isKeyCharacterCodePoint, isKeyInitialCodePoint } from './key-grammar.js';
|
|
8
3
|
const NO_TOKENS = [];
|
|
4
|
+
const isAsciiReferenceInitial = (c) => (c >= 97 && c <= 122) || (c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 95;
|
|
5
|
+
const isAsciiReferenceCharacter = (c, allowSlash) => isAsciiReferenceInitial(c) || c === 58 || c === 45 || c === 42 || allowSlash && c === 47;
|
|
9
6
|
const utf16WidthAt = (text, offset) => text.codePointAt(offset) > 0xffff ? 2 : 1;
|
|
10
7
|
/** Codepoint length of the UTF-16 slice `[a, b)` of `s`. */
|
|
11
8
|
const codepointLen = (s, a, b) => {
|
|
@@ -38,27 +35,80 @@ export const utf16OffsetToCodepoint = (text, utf16Offset) => {
|
|
|
38
35
|
return codepoints;
|
|
39
36
|
};
|
|
40
37
|
/** Splice points (decoded value order): token text, UTF-16 index, path. */
|
|
38
|
+
const scanComponent = (value, start, allowSlash) => {
|
|
39
|
+
let i = start;
|
|
40
|
+
if (i >= value.length)
|
|
41
|
+
return -1;
|
|
42
|
+
let cp = value.charCodeAt(i);
|
|
43
|
+
if (cp <= 0x7f) {
|
|
44
|
+
if (!isAsciiReferenceInitial(cp))
|
|
45
|
+
return -1;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
cp = value.codePointAt(i);
|
|
49
|
+
if (!isKeyInitialCodePoint(cp))
|
|
50
|
+
return -1;
|
|
51
|
+
}
|
|
52
|
+
i += cp > 0xffff ? 2 : 1;
|
|
53
|
+
while (i < value.length && isAsciiReferenceCharacter(value.charCodeAt(i), allowSlash))
|
|
54
|
+
i++;
|
|
55
|
+
while (i < value.length) {
|
|
56
|
+
cp = value.charCodeAt(i);
|
|
57
|
+
if (cp <= 0x7f)
|
|
58
|
+
break;
|
|
59
|
+
cp = value.codePointAt(i);
|
|
60
|
+
if (!isKeyCharacterCodePoint(cp))
|
|
61
|
+
break;
|
|
62
|
+
i += cp > 0xffff ? 2 : 1;
|
|
63
|
+
while (i < value.length && isAsciiReferenceCharacter(value.charCodeAt(i), allowSlash))
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
return i;
|
|
67
|
+
};
|
|
68
|
+
const scanPath = (value, start, partial) => {
|
|
69
|
+
let i = scanComponent(value, start, partial);
|
|
70
|
+
if (i < 0)
|
|
71
|
+
return -1;
|
|
72
|
+
while (value.charCodeAt(i) === 46) {
|
|
73
|
+
const next = scanComponent(value, i + 1, false);
|
|
74
|
+
if (next < 0)
|
|
75
|
+
return -1;
|
|
76
|
+
i = next;
|
|
77
|
+
}
|
|
78
|
+
return i;
|
|
79
|
+
};
|
|
41
80
|
const scanDecoded = (value) => {
|
|
42
|
-
REFERENCE_2.lastIndex = 0;
|
|
43
81
|
const out = [];
|
|
44
|
-
for (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
82
|
+
for (let i = 0; i + 2 < value.length;) {
|
|
83
|
+
const partial = value.startsWith('$(', i);
|
|
84
|
+
const document = !partial && value.startsWith('${', i);
|
|
85
|
+
if (!partial && !document) {
|
|
86
|
+
const cp = value.codePointAt(i);
|
|
87
|
+
i += cp > 0xffff ? 2 : 1;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const end = scanPath(value, i + 2, partial);
|
|
91
|
+
const close = partial ? 41 : 125;
|
|
92
|
+
if (end >= 0 && value.charCodeAt(end) === close) {
|
|
93
|
+
const token = value.slice(i, end + 1);
|
|
94
|
+
const path = value.slice(i + 2, end);
|
|
95
|
+
out.push({ token, index: i, ...(partial ? { partialPath: path } : { documentPath: path }) });
|
|
96
|
+
i = end + 1;
|
|
97
|
+
}
|
|
98
|
+
else
|
|
99
|
+
i++;
|
|
49
100
|
}
|
|
50
101
|
return out;
|
|
51
102
|
};
|
|
52
103
|
/** Physical `(line, offset)` of each token, in source order, from the raw text. */
|
|
53
104
|
const scanPhysical = (raw, firstLine, firstCol, tabAdjust) => {
|
|
54
|
-
REFERENCE_2.lastIndex = 0;
|
|
55
105
|
const out = [];
|
|
56
106
|
let scanned = 0;
|
|
57
107
|
let line = firstLine;
|
|
58
108
|
let col = firstCol;
|
|
59
109
|
let lineIndex = 0;
|
|
60
|
-
for (const m of raw
|
|
61
|
-
const idx = m.index
|
|
110
|
+
for (const m of scanDecoded(raw)) {
|
|
111
|
+
const idx = m.index;
|
|
62
112
|
while (scanned < idx) {
|
|
63
113
|
const cp = raw.codePointAt(scanned);
|
|
64
114
|
if (cp === 10) {
|
package/dist/references2.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/** Lima References 2.
|
|
1
|
+
/** Lima References 2.1 resolver. References 1.0 remains isolated in references.ts. */
|
|
2
2
|
import { canonicalString, codepointLength, countNodes, ingestPartialValue, PARTIAL_COUNT_LIMIT, PARTIAL_NAME_LENGTH_LIMIT, PARTIAL_NODE_LIMIT, RESULT_NODE_LIMIT, SCALAR_LENGTH_LIMIT, } from './value.js';
|
|
3
|
-
import {
|
|
3
|
+
import { isValidUnquotedKey } from './key-grammar.js';
|
|
4
|
+
import { validateDocument } from './document.js';
|
|
5
|
+
import { NESTING_DEPTH_LIMIT, parseCoreValidated, parseCoreWithPositions, toPlainValue, } from './core.js';
|
|
4
6
|
import { hasActiveReferences2 } from './scalars.js';
|
|
5
7
|
import { LimaError } from './errors.js';
|
|
6
8
|
import { collectAllParticipants, deepCopyPositioned, earliestParticipant, emptyMapping, finalizePositioned, partialToPositioned, } from './positioned-tree.js';
|
|
7
|
-
const PARTIAL_NAME = '[a-zA-Z0-9_][a-zA-Z0-9_:/-]*';
|
|
8
|
-
const PARTIAL_NAME_RE = new RegExp(`^${PARTIAL_NAME}$`);
|
|
9
9
|
const MAX_EDGES = 3;
|
|
10
10
|
// Looking up the root path is itself the first reference edge; recursion into
|
|
11
11
|
// that root therefore gets the public resolver's remaining two-edge budget.
|
|
@@ -317,7 +317,7 @@ const validatePartials = (raw) => {
|
|
|
317
317
|
throw new LimaError({ code: 'INVALID_PARTIAL', message: `Lima: too many partials (max ${PARTIAL_COUNT_LIMIT})` });
|
|
318
318
|
}
|
|
319
319
|
for (const name of names) {
|
|
320
|
-
if (!
|
|
320
|
+
if (!isValidUnquotedKey(name, true)) {
|
|
321
321
|
throw new LimaError({ code: 'INVALID_PARTIAL', partial: name, message: `Lima: invalid partial name "${name}"` });
|
|
322
322
|
}
|
|
323
323
|
if (codepointLength(name) > PARTIAL_NAME_LENGTH_LIMIT) {
|
|
@@ -356,6 +356,7 @@ const scanUnresolved = (node, ctx) => {
|
|
|
356
356
|
scanUnresolved(child, ctx);
|
|
357
357
|
};
|
|
358
358
|
const parseInternal = (frontMatter, options, useResolveCache) => {
|
|
359
|
+
validateDocument(frontMatter);
|
|
359
360
|
if (options?.mode !== undefined && options.mode !== 'core' && options.mode !== 'references') {
|
|
360
361
|
throw new TypeError(`Lima: invalid parse mode "${String(options.mode)}"`);
|
|
361
362
|
}
|
|
@@ -365,11 +366,11 @@ const parseInternal = (frontMatter, options, useResolveCache) => {
|
|
|
365
366
|
}
|
|
366
367
|
// Core ignores the additional `mode` property. Passing the same object
|
|
367
368
|
// avoids allocating a projected options object on this hot dispatch path.
|
|
368
|
-
return
|
|
369
|
+
return parseCoreValidated(frontMatter, options);
|
|
369
370
|
}
|
|
370
371
|
const partials = validatePartials(options?.partials ?? {});
|
|
371
372
|
if (!frontMatter.includes('${') && !frontMatter.includes('$(')) {
|
|
372
|
-
return
|
|
373
|
+
return parseCoreValidated(frontMatter, options);
|
|
373
374
|
}
|
|
374
375
|
const document = parseCoreWithPositions(frontMatter, { strict: options?.strict ?? false, onWarning: options?.onWarning });
|
|
375
376
|
const ctx = { diagnostics: [], ...(useResolveCache ? { cache: new WeakMap() } : {}) };
|
package/dist/scalars.js
CHANGED
|
@@ -354,8 +354,9 @@ export const closingQuoteIndex = (s, singleQuoteEscape = true) => {
|
|
|
354
354
|
* Kept byte-for-byte identical to `scanner.ts`'s `isKeyStartChar` /
|
|
355
355
|
* `isKeyContinueChar`, which enforce the same grammar at the top level.
|
|
356
356
|
*/
|
|
357
|
-
|
|
358
|
-
const
|
|
357
|
+
import { isValidUnquotedKey } from './key-grammar.js';
|
|
358
|
+
const isAsciiUnquotedKeyStart = (c) => (c >= 97 && c <= 122) || (c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 95;
|
|
359
|
+
const isAsciiUnquotedKeyContinue = (c) => isAsciiUnquotedKeyStart(c) || c === 58 || c === 45 || c === 42;
|
|
359
360
|
/**
|
|
360
361
|
* Whether `raw` (a key candidate as written, before quote stripping) is a
|
|
361
362
|
* usable Lima key:
|
|
@@ -375,13 +376,17 @@ export const isValidKey = (raw) => {
|
|
|
375
376
|
const f = raw.charCodeAt(0);
|
|
376
377
|
if (f === 34 || f === 39)
|
|
377
378
|
return closingQuoteIndex(raw, false) === raw.length - 1;
|
|
378
|
-
if (
|
|
379
|
-
|
|
380
|
-
for (let i = 1; i < raw.length; i++) {
|
|
381
|
-
if (!isUnquotedKeyContinue(raw.charCodeAt(i)))
|
|
379
|
+
if (f <= 0x7f) {
|
|
380
|
+
if (!isAsciiUnquotedKeyStart(f))
|
|
382
381
|
return false;
|
|
382
|
+
for (let i = 1; i < raw.length; i++) {
|
|
383
|
+
const c = raw.charCodeAt(i);
|
|
384
|
+
if (!isAsciiUnquotedKeyContinue(c))
|
|
385
|
+
return c > 0x7f && isValidUnquotedKey(raw);
|
|
386
|
+
}
|
|
387
|
+
return true;
|
|
383
388
|
}
|
|
384
|
-
return
|
|
389
|
+
return isValidUnquotedKey(raw);
|
|
385
390
|
};
|
|
386
391
|
const ANY_ESCAPE_RE = /\\(u[0-9a-fA-F]{0,4}|U[0-9a-fA-F]{0,8}|x[0-9a-fA-F]{0,2}|.)/gs;
|
|
387
392
|
const SINGLE_CHAR_ESCAPES = '"\\/bfnrt'; // deliberately excludes '0' — Core Appendix A: \0 is unknown, not a null shorthand.
|
package/dist/scanner.js
CHANGED
|
@@ -50,8 +50,9 @@
|
|
|
50
50
|
* U+2028 / U+2029 included, is consumed as an escape pair here and
|
|
51
51
|
* resolved at decode time.
|
|
52
52
|
*/
|
|
53
|
-
|
|
54
|
-
const
|
|
53
|
+
import { isKeyCharacterCodePoint, isKeyInitialCodePoint } from './key-grammar.js';
|
|
54
|
+
const isAsciiKeyStart = (c) => (c >= 97 && c <= 122) || (c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 95;
|
|
55
|
+
const isAsciiKeyContinue = (c) => isAsciiKeyStart(c) || c === 58 || c === 45 || c === 42;
|
|
55
56
|
/** Positive = block end, negative = inline end, zero = no separator. */
|
|
56
57
|
const matchSeparator = (s, pos) => {
|
|
57
58
|
let i = pos;
|
|
@@ -59,6 +60,10 @@ const matchSeparator = (s, pos) => {
|
|
|
59
60
|
i++; // consume spaces
|
|
60
61
|
if (i < s.length && s.charCodeAt(i) === 10)
|
|
61
62
|
return i + 1;
|
|
63
|
+
// Core 1.1: the end of the input ends the key's line as a newline does, so a last `key:` is `key: null`, as it
|
|
64
|
+
// is at any nested level (it was no key at all at the top level before)
|
|
65
|
+
if (i === s.length)
|
|
66
|
+
return i;
|
|
62
67
|
// Backtracking a pure-space run can never reveal a `\n` the maximal
|
|
63
68
|
// scan didn't already see (every intermediate position is followed by
|
|
64
69
|
// either another space or the same non-`\n` stop character) — so
|
|
@@ -121,11 +126,30 @@ const matchAt = (s, pos, line, out) => {
|
|
|
121
126
|
keyKind = 2;
|
|
122
127
|
}
|
|
123
128
|
else {
|
|
124
|
-
if (!
|
|
129
|
+
if (c <= 0x7f && !isAsciiKeyStart(c))
|
|
125
130
|
return false;
|
|
126
131
|
let runEnd = pos + 1;
|
|
127
|
-
|
|
132
|
+
if (c > 0x7f) {
|
|
133
|
+
const first = s.codePointAt(pos);
|
|
134
|
+
if (!isKeyInitialCodePoint(first))
|
|
135
|
+
return false;
|
|
136
|
+
runEnd = pos + (first > 0xffff ? 2 : 1);
|
|
137
|
+
}
|
|
138
|
+
// Preserve the old tiny ASCII loop. Unicode decoding is entered only
|
|
139
|
+
// when this key actually reaches a non-ASCII code unit.
|
|
140
|
+
while (runEnd < s.length && isAsciiKeyContinue(s.charCodeAt(runEnd)))
|
|
128
141
|
runEnd++;
|
|
142
|
+
while (runEnd < s.length) {
|
|
143
|
+
let cp = s.charCodeAt(runEnd);
|
|
144
|
+
if (cp <= 0x7f)
|
|
145
|
+
break;
|
|
146
|
+
cp = s.codePointAt(runEnd);
|
|
147
|
+
if (!isKeyCharacterCodePoint(cp))
|
|
148
|
+
break;
|
|
149
|
+
runEnd += cp > 0xffff ? 2 : 1;
|
|
150
|
+
while (runEnd < s.length && isAsciiKeyContinue(s.charCodeAt(runEnd)))
|
|
151
|
+
runEnd++;
|
|
152
|
+
}
|
|
129
153
|
for (let k = runEnd - 1; k >= pos + 1; k--) {
|
|
130
154
|
if (s.charCodeAt(k) !== 58)
|
|
131
155
|
continue;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the lines after a key belong to: nothing (`none`, before the first key; `inline`, after an inline value), a
|
|
3
|
+
* block mapping or sequence (`mapping`, after a bare key) or a `|` scalar (`scalar`).
|
|
4
|
+
*/
|
|
5
|
+
export type RangeOwner = 'none' | 'inline' | 'mapping' | 'scalar';
|
|
6
|
+
/**
|
|
7
|
+
* Checks the lines of `frontMatter` from `start` (a line start) to `end` that follow a key owned as `owner`; `line`
|
|
8
|
+
* is the 1-based line at `start`; `valueStart` to `valueEnd` is the inline value of that key, read only for the hint a
|
|
9
|
+
* YAML marker earns in the error. Throws for the first line that belongs to no structure.
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkRangeLines(frontMatter: string, start: number, end: number, line: number, owner: RangeOwner, valueStart?: number, valueEnd?: number): void;
|
|
12
|
+
/** Whether the inline value that starts at `rawStart` and ends its line at `lineEnd` is a `|` block scalar marker. */
|
|
13
|
+
export declare const isBlockScalarMarker: (frontMatter: string, rawStart: number, lineEnd: number) => boolean;
|