@effected/yaml 0.25.1 → 0.26.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/README.md +2 -2
- package/Yaml.js +5 -2
- package/YamlDiagnostic.js +33 -31
- package/YamlDocument.js +17 -11
- package/YamlLint.js +26 -35
- package/YamlToken.js +25 -15
- package/YamlVisitor.js +7 -6
- package/index.d.ts +98 -493
- package/index.js +2 -1
- package/internal/composer/scalars.js +18 -3
- package/internal/composer/state.js +3 -1
- package/internal/cst-parser.js +3 -1
- package/internal/lexer.js +4 -1
- package/internal/line-breaks.js +12 -0
- package/internal/rules/colon-spacing.js +30 -20
- package/internal/rules/comments-spacing.js +10 -3
- package/internal/rules/document-end.js +3 -2
- package/internal/rules/empty-lines.js +12 -3
- package/internal/rules/eof-newline.js +2 -1
- package/internal/rules/hyphen-spacing.js +20 -14
- package/internal/rules/trailing-spaces.js +2 -2
- package/internal/rules/util.js +92 -1
- package/package.json +5 -2
- package/tsdoc-metadata.json +1 -1
|
@@ -285,7 +285,7 @@ function decodeDoubleQuoted(raw) {
|
|
|
285
285
|
* - Leading whitespace (indentation) on continuation lines trimmed
|
|
286
286
|
*/
|
|
287
287
|
function foldFlowLines(text) {
|
|
288
|
-
const lines = text.split(
|
|
288
|
+
const lines = text.split(/\r?\n/);
|
|
289
289
|
let result = "";
|
|
290
290
|
for (let i = 0; i < lines.length; i++) {
|
|
291
291
|
const line = lines[i] ?? "";
|
|
@@ -660,18 +660,33 @@ function findParentIndent(fullText, indicatorOffset) {
|
|
|
660
660
|
let scanBack = indicatorOffset - 1;
|
|
661
661
|
while (scanBack >= 0 && (fullText[scanBack] === " " || fullText[scanBack] === " ")) scanBack--;
|
|
662
662
|
if (scanBack >= 0 && fullText[scanBack] === ":") return findKeyIndent(fullText, scanBack);
|
|
663
|
-
if (scanBack >= 0 && fullText[scanBack] === "-") return findColOnLine(fullText, scanBack);
|
|
663
|
+
if (scanBack >= 0 && fullText[scanBack] === "-") return isDocumentStartDash(fullText, scanBack) ? 0 : findColOnLine(fullText, scanBack);
|
|
664
664
|
while (scanBack >= 0) {
|
|
665
665
|
const ch = fullText[scanBack];
|
|
666
666
|
if (ch === ":") return findKeyIndent(fullText, scanBack);
|
|
667
667
|
if (ch === "-") {
|
|
668
668
|
const afterDash = scanBack + 1;
|
|
669
|
-
if (afterDash >= fullText.length || fullText[afterDash] === " " || fullText[afterDash] === " " || fullText[afterDash] === "\n" || fullText[afterDash] === "\r") return findColOnLine(fullText, scanBack);
|
|
669
|
+
if (afterDash >= fullText.length || fullText[afterDash] === " " || fullText[afterDash] === " " || fullText[afterDash] === "\n" || fullText[afterDash] === "\r") return isDocumentStartDash(fullText, scanBack) ? 0 : findColOnLine(fullText, scanBack);
|
|
670
670
|
}
|
|
671
671
|
scanBack--;
|
|
672
672
|
}
|
|
673
673
|
return 0;
|
|
674
674
|
}
|
|
675
|
+
/**
|
|
676
|
+
* True when the `-` at `pos` belongs to a `---` document-start marker. The
|
|
677
|
+
* marker opens a document at the top level, so a block scalar after it has
|
|
678
|
+
* the root's parent indent, 0. Taking its last `-` for a sequence entry put
|
|
679
|
+
* the parent at column 2: `---\n>1\n #:a` decoded to `""` where `>1\n #:a`
|
|
680
|
+
* decodes to `"#:a\n"`, and inserting the marker (`document-start`'s fix)
|
|
681
|
+
* changed the value.
|
|
682
|
+
*/
|
|
683
|
+
function isDocumentStartDash(text, pos) {
|
|
684
|
+
let lineStart = pos;
|
|
685
|
+
while (lineStart > 0 && text[lineStart - 1] !== "\n" && text[lineStart - 1] !== "\r") lineStart--;
|
|
686
|
+
if (pos - lineStart > 2 || !text.startsWith("---", lineStart)) return false;
|
|
687
|
+
const after = text[lineStart + 3];
|
|
688
|
+
return after === void 0 || after === " " || after === " " || after === "\n" || after === "\r";
|
|
689
|
+
}
|
|
675
690
|
/** Find the column of a character on its line. */
|
|
676
691
|
function findColOnLine(text, pos) {
|
|
677
692
|
let lineStart = pos;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeLineBreaks } from "../line-breaks.js";
|
|
2
|
+
|
|
1
3
|
// src/internal/composer/state.ts
|
|
2
4
|
let lineStartsText;
|
|
3
5
|
let lineStartsCache = [];
|
|
@@ -85,7 +87,7 @@ function commentProps(n) {
|
|
|
85
87
|
}
|
|
86
88
|
function createState(text, flow, options) {
|
|
87
89
|
return {
|
|
88
|
-
text,
|
|
90
|
+
text: normalizeLineBreaks(text),
|
|
89
91
|
anchors: /* @__PURE__ */ new Map(),
|
|
90
92
|
aliasCount: 0,
|
|
91
93
|
errors: [],
|
package/internal/cst-parser.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeLineBreaks } from "./line-breaks.js";
|
|
1
2
|
import { lexAll } from "./lexer.js";
|
|
2
3
|
|
|
3
4
|
// src/internal/cst-parser.ts
|
|
@@ -717,7 +718,8 @@ function parseDocuments(tokens, text) {
|
|
|
717
718
|
* whitespace, comments, and structural indicators. No value interpretation
|
|
718
719
|
* occurs at this stage — `true` is still the string `"true"`.
|
|
719
720
|
*/
|
|
720
|
-
function parseCSTAll(
|
|
721
|
+
function parseCSTAll(source) {
|
|
722
|
+
const text = normalizeLineBreaks(source);
|
|
721
723
|
return parseDocuments(lexAll(text), text);
|
|
722
724
|
}
|
|
723
725
|
|
package/internal/lexer.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeLineBreaks } from "./line-breaks.js";
|
|
2
|
+
|
|
1
3
|
// src/internal/lexer.ts
|
|
2
4
|
/**
|
|
3
5
|
* Create a new YAML scanner for the given source text.
|
|
@@ -5,7 +7,8 @@
|
|
|
5
7
|
* Returns a stateful, pull-based scanner. Call {@link YamlScanner.scan} to
|
|
6
8
|
* advance to the next token, then use `getToken*` methods to inspect it.
|
|
7
9
|
*/
|
|
8
|
-
function createScanner(
|
|
10
|
+
function createScanner(source) {
|
|
11
|
+
const text = normalizeLineBreaks(source);
|
|
9
12
|
let pos = 0;
|
|
10
13
|
let line = 0;
|
|
11
14
|
let col = 0;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/internal/line-breaks.ts
|
|
2
|
+
const BARE_CR = /\r(?!\n)/g;
|
|
3
|
+
/**
|
|
4
|
+
* `text` with every bare `\r` (one not followed by `\n`) replaced by `\n`.
|
|
5
|
+
* Returns `text` itself when it holds no `\r`, so the common case allocates
|
|
6
|
+
* nothing and reference-keyed caches downstream keep hitting.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
const normalizeLineBreaks = (text) => text.indexOf("\r") === -1 ? text : text.replace(BARE_CR, "\n");
|
|
11
|
+
|
|
12
|
+
export { normalizeLineBreaks };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
2
|
import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
-
import { nonNegativeIntegerOption, positiveIntegerOption } from "./util.js";
|
|
3
|
+
import { composedScalars, insideComposedScalar, laterLineInWindow, nonNegativeIntegerOption, positiveIntegerOption } from "./util.js";
|
|
4
4
|
import { Schema } from "effect";
|
|
5
5
|
|
|
6
6
|
// src/internal/rules/colon-spacing.ts
|
|
@@ -26,26 +26,14 @@ const colonSpacing = {
|
|
|
26
26
|
const out = [];
|
|
27
27
|
for (const token of ctx.tokens) {
|
|
28
28
|
if (token.kind !== "block-map-value") continue;
|
|
29
|
+
if (insideComposedScalar(ctx, token.offset)) continue;
|
|
29
30
|
let i = token.offset - 1;
|
|
30
31
|
let before = 0;
|
|
31
32
|
while (i >= 0 && ctx.text[i] === " ") {
|
|
32
33
|
before++;
|
|
33
34
|
i--;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
rule: "colon-spacing",
|
|
37
|
-
severity: "error",
|
|
38
|
-
message: `Too many spaces before ":" (${before} > ${maxBefore})`,
|
|
39
|
-
offset: token.offset - before,
|
|
40
|
-
length: before,
|
|
41
|
-
line: token.line,
|
|
42
|
-
character: token.character - before,
|
|
43
|
-
fix: YamlEdit.make({
|
|
44
|
-
offset: token.offset - before,
|
|
45
|
-
length: before - maxBefore,
|
|
46
|
-
content: ""
|
|
47
|
-
})
|
|
48
|
-
}));
|
|
36
|
+
const hasContentBefore = i >= 0 && ctx.text[i] !== "\n" && ctx.text[i] !== "\r" && ctx.text[i] !== " ";
|
|
49
37
|
let j = token.offset + token.length;
|
|
50
38
|
let after = 0;
|
|
51
39
|
while (j < ctx.text.length && ctx.text[j] === " ") {
|
|
@@ -53,8 +41,30 @@ const colonSpacing = {
|
|
|
53
41
|
j++;
|
|
54
42
|
}
|
|
55
43
|
const next = ctx.text[j];
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
const inlineValue = next !== void 0 && next !== "\n" && next !== "\r" && next !== "#";
|
|
45
|
+
const shiftBefore = hasContentBefore && before > maxBefore ? before - maxBefore : 0;
|
|
46
|
+
const shiftAfter = inlineValue && after > maxAfter ? after - maxAfter : 0;
|
|
47
|
+
const valueColumn = token.character + token.length + after;
|
|
48
|
+
const moveSafe = !inlineValue || !laterLineInWindow(ctx, token.line, token.character - shiftBefore, valueColumn - shiftBefore - shiftAfter, valueColumn);
|
|
49
|
+
if (shiftBefore > 0) {
|
|
50
|
+
const keyEnd = i + 1;
|
|
51
|
+
const safe = moveSafe && (maxBefore === 0 ? composedScalars(ctx).some((scalar) => scalar.end === keyEnd && scalar.role === "key") : true);
|
|
52
|
+
out.push(new YamlLintDiagnostic({
|
|
53
|
+
rule: "colon-spacing",
|
|
54
|
+
severity: "error",
|
|
55
|
+
message: `Too many spaces before ":" (${before} > ${maxBefore})`,
|
|
56
|
+
offset: token.offset - before,
|
|
57
|
+
length: before,
|
|
58
|
+
line: token.line,
|
|
59
|
+
character: token.character - before,
|
|
60
|
+
...safe ? { fix: YamlEdit.make({
|
|
61
|
+
offset: token.offset - before,
|
|
62
|
+
length: before - maxBefore,
|
|
63
|
+
content: ""
|
|
64
|
+
}) } : {}
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
if (shiftAfter > 0) out.push(new YamlLintDiagnostic({
|
|
58
68
|
rule: "colon-spacing",
|
|
59
69
|
severity: "error",
|
|
60
70
|
message: `Too many spaces after ":" (${after} > ${maxAfter})`,
|
|
@@ -62,11 +72,11 @@ const colonSpacing = {
|
|
|
62
72
|
length: after,
|
|
63
73
|
line: token.line,
|
|
64
74
|
character: token.character + token.length,
|
|
65
|
-
fix: YamlEdit.make({
|
|
75
|
+
...moveSafe ? { fix: YamlEdit.make({
|
|
66
76
|
offset: token.offset + token.length,
|
|
67
|
-
length:
|
|
77
|
+
length: shiftAfter,
|
|
68
78
|
content: ""
|
|
69
|
-
})
|
|
79
|
+
}) } : {}
|
|
70
80
|
}));
|
|
71
81
|
}
|
|
72
82
|
return out;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
2
|
import { StyleVote, YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
-
import { nonNegativeIntegerOption } from "./util.js";
|
|
3
|
+
import { insideComposedScalar, nonNegativeIntegerOption } from "./util.js";
|
|
4
4
|
import { Schema } from "effect";
|
|
5
5
|
|
|
6
6
|
// src/internal/rules/comments-spacing.ts
|
|
@@ -31,6 +31,13 @@ const spacingBefore = (ctx, token) => {
|
|
|
31
31
|
hasContentBefore: i >= 0 && ctx.text[i] !== "\n" && ctx.text[i] !== "\r"
|
|
32
32
|
};
|
|
33
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* A `comment` token the composer reads as scalar CONTENT is no comment: on
|
|
36
|
+
* `1-\n - ##` the composer folds the second line into the plain scalar
|
|
37
|
+
* `"1- - ##"`, so inserting a space after the `#` edited the value. Such a
|
|
38
|
+
* token neither reports nor votes.
|
|
39
|
+
*/
|
|
40
|
+
const isContent = (ctx, token) => insideComposedScalar(ctx, token.offset);
|
|
34
41
|
/** Shebang exemption: `#!` at the very start of the stream. */
|
|
35
42
|
const isShebang = (token) => token.offset === 0 && token.text.startsWith("#!");
|
|
36
43
|
/** Comment spacing: after the `#`, and before a trailing comment's `#`. */
|
|
@@ -43,7 +50,7 @@ const commentsSpacing = {
|
|
|
43
50
|
const out = [];
|
|
44
51
|
for (const token of ctx.tokens) {
|
|
45
52
|
if (token.kind !== "comment") continue;
|
|
46
|
-
if (isShebang(token)) continue;
|
|
53
|
+
if (isShebang(token) || isContent(ctx, token)) continue;
|
|
47
54
|
if (requireAfter && token.text.length > 1 && !/[ \t]/.test(token.text[1])) out.push(new YamlLintDiagnostic({
|
|
48
55
|
rule: "comments-spacing",
|
|
49
56
|
severity: "error",
|
|
@@ -80,7 +87,7 @@ const commentsSpacing = {
|
|
|
80
87
|
const out = [];
|
|
81
88
|
for (const token of ctx.tokens) {
|
|
82
89
|
if (token.kind !== "comment") continue;
|
|
83
|
-
if (isShebang(token)) continue;
|
|
90
|
+
if (isShebang(token) || isContent(ctx, token)) continue;
|
|
84
91
|
if (token.text.length > 1) out.push(StyleVote.make({
|
|
85
92
|
dimension: "requireSpaceAfter",
|
|
86
93
|
value: /[ \t]/.test(token.text[1]),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
2
|
import { StyleVote, YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
+
import { endsWithLineBreak } from "./util.js";
|
|
3
4
|
import { Schema } from "effect";
|
|
4
5
|
|
|
5
6
|
// src/internal/rules/document-end.ts
|
|
@@ -25,7 +26,7 @@ const tailToken = (ctx) => [...ctx.tokens].reverse().find((t) => !TRIVIA.has(t.k
|
|
|
25
26
|
*/
|
|
26
27
|
const endOfStreamPosition = (ctx) => {
|
|
27
28
|
const lastLine = ctx.lines[ctx.lines.length - 1];
|
|
28
|
-
const trailingNewline = ctx.text
|
|
29
|
+
const trailingNewline = endsWithLineBreak(ctx.text);
|
|
29
30
|
return {
|
|
30
31
|
offset: ctx.text.length,
|
|
31
32
|
length: 0,
|
|
@@ -48,7 +49,7 @@ const documentEnd = {
|
|
|
48
49
|
fix: YamlEdit.make({
|
|
49
50
|
offset: ctx.text.length,
|
|
50
51
|
length: 0,
|
|
51
|
-
content: ctx.text
|
|
52
|
+
content: endsWithLineBreak(ctx.text) ? "...\n" : "\n...\n"
|
|
52
53
|
})
|
|
53
54
|
})];
|
|
54
55
|
if (!present && tail !== void 0 && tail.kind === "document-end") {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
2
|
import { StyleFloor, YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
-
import { insideScalarSpan, nonNegativeIntegerOption } from "./util.js";
|
|
3
|
+
import { insideComposedScalar, insideScalarSpan, nonNegativeIntegerOption } from "./util.js";
|
|
4
4
|
import { Schema } from "effect";
|
|
5
5
|
|
|
6
6
|
// src/internal/rules/empty-lines.ts
|
|
@@ -15,6 +15,15 @@ const emptyLinesOptions = Schema.Struct({
|
|
|
15
15
|
maxStart: Schema.optionalKey(nonNegativeIntegerOption),
|
|
16
16
|
maxEnd: Schema.optionalKey(nonNegativeIntegerOption)
|
|
17
17
|
});
|
|
18
|
+
/**
|
|
19
|
+
* Whether a blank line at `offset` is scalar CONTENT: inside a block scalar
|
|
20
|
+
* token, or inside any scalar the composer reads. The second half matters
|
|
21
|
+
* for a multi-line plain or quoted scalar, which the lexer splits into one
|
|
22
|
+
* token per line: its blank lines sit between tokens, yet each one folds
|
|
23
|
+
* into the value as a line break (`a\n\n\n\nb` is `"a\n\n\nb"`), so deleting
|
|
24
|
+
* one changes the value.
|
|
25
|
+
*/
|
|
26
|
+
const insideScalarContent = (ctx, offset) => insideScalarSpan(ctx.tokens, offset) || insideComposedScalar(ctx, offset);
|
|
18
27
|
/** Runs of blank lines beyond the configured caps, with a deleting fix. */
|
|
19
28
|
const emptyLines = {
|
|
20
29
|
id: "empty-lines",
|
|
@@ -28,7 +37,7 @@ const emptyLines = {
|
|
|
28
37
|
let i = 0;
|
|
29
38
|
while (i < lines.length) {
|
|
30
39
|
const line = lines[i];
|
|
31
|
-
if (line === void 0 || line.text !== "" ||
|
|
40
|
+
if (line === void 0 || line.text !== "" || insideScalarContent(ctx, line.offset)) {
|
|
32
41
|
i++;
|
|
33
42
|
continue;
|
|
34
43
|
}
|
|
@@ -70,7 +79,7 @@ const emptyLines = {
|
|
|
70
79
|
let i = 0;
|
|
71
80
|
while (i < lines.length) {
|
|
72
81
|
const line = lines[i];
|
|
73
|
-
if (line === void 0 || line.text !== "" ||
|
|
82
|
+
if (line === void 0 || line.text !== "" || insideScalarContent(ctx, line.offset)) {
|
|
74
83
|
i++;
|
|
75
84
|
continue;
|
|
76
85
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
2
|
import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
+
import { endsWithLineBreak } from "./util.js";
|
|
3
4
|
import { Schema } from "effect";
|
|
4
5
|
|
|
5
6
|
// src/internal/rules/eof-newline.ts
|
|
@@ -9,7 +10,7 @@ const eofNewlineOptions = Schema.Struct({ severity: Schema.optionalKey(YamlLintS
|
|
|
9
10
|
const eofNewline = {
|
|
10
11
|
id: "eof-newline",
|
|
11
12
|
check: (ctx) => {
|
|
12
|
-
if (ctx.text.length === 0 || ctx.text
|
|
13
|
+
if (ctx.text.length === 0 || endsWithLineBreak(ctx.text)) return [];
|
|
13
14
|
const lastLine = ctx.lines[ctx.lines.length - 1];
|
|
14
15
|
if (lastLine === void 0) return [];
|
|
15
16
|
return [new YamlLintDiagnostic({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
2
|
import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
-
import { positiveIntegerOption } from "./util.js";
|
|
3
|
+
import { insideComposedScalar, laterLineInWindow, positiveIntegerOption } from "./util.js";
|
|
4
4
|
import { Schema } from "effect";
|
|
5
5
|
|
|
6
6
|
// src/internal/rules/hyphen-spacing.ts
|
|
@@ -21,6 +21,7 @@ const hyphenSpacing = {
|
|
|
21
21
|
const out = [];
|
|
22
22
|
for (const token of ctx.tokens) {
|
|
23
23
|
if (token.kind !== "block-seq-entry") continue;
|
|
24
|
+
if (insideComposedScalar(ctx, token.offset)) continue;
|
|
24
25
|
let j = token.offset + token.length;
|
|
25
26
|
let after = 0;
|
|
26
27
|
while (j < ctx.text.length && ctx.text[j] === " ") {
|
|
@@ -29,20 +30,25 @@ const hyphenSpacing = {
|
|
|
29
30
|
}
|
|
30
31
|
const next = ctx.text[j];
|
|
31
32
|
if (next === void 0 || next === "\n" || next === "\r" || next === "#") continue;
|
|
32
|
-
if (after > maxAfter)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
fix: YamlEdit.make({
|
|
33
|
+
if (after > maxAfter) {
|
|
34
|
+
const oldColumn = token.character + token.length + after;
|
|
35
|
+
const newColumn = token.character + token.length + maxAfter;
|
|
36
|
+
const safe = !laterLineInWindow(ctx, token.line, token.character, newColumn, oldColumn);
|
|
37
|
+
out.push(new YamlLintDiagnostic({
|
|
38
|
+
rule: "hyphen-spacing",
|
|
39
|
+
severity: "error",
|
|
40
|
+
message: `Too many spaces after "-" (${after} > ${maxAfter})`,
|
|
41
41
|
offset: token.offset + token.length,
|
|
42
|
-
length: after
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
length: after,
|
|
43
|
+
line: token.line,
|
|
44
|
+
character: token.character + token.length,
|
|
45
|
+
...safe ? { fix: YamlEdit.make({
|
|
46
|
+
offset: token.offset + token.length,
|
|
47
|
+
length: after - maxAfter,
|
|
48
|
+
content: ""
|
|
49
|
+
}) } : {}
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
46
52
|
}
|
|
47
53
|
return out;
|
|
48
54
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
2
|
import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
-
import { insideScalarSpan } from "./util.js";
|
|
3
|
+
import { insideComposedScalar, insideScalarSpan } from "./util.js";
|
|
4
4
|
import { Schema } from "effect";
|
|
5
5
|
|
|
6
6
|
// src/internal/rules/trailing-spaces.ts
|
|
@@ -15,7 +15,7 @@ const trailingSpaces = {
|
|
|
15
15
|
const match = /[ \t]+$/.exec(line.text);
|
|
16
16
|
if (match === null) continue;
|
|
17
17
|
const runOffset = line.offset + match.index;
|
|
18
|
-
if (insideScalarSpan(ctx.tokens, runOffset)) continue;
|
|
18
|
+
if (insideScalarSpan(ctx.tokens, runOffset) || insideComposedScalar(ctx, runOffset)) continue;
|
|
19
19
|
out.push(new YamlLintDiagnostic({
|
|
20
20
|
rule: "trailing-spaces",
|
|
21
21
|
severity: "error",
|
package/internal/rules/util.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { YamlMap, YamlScalar, YamlSeq } from "../../YamlNode.js";
|
|
2
|
+
import { composeAllDocuments } from "../composer/document.js";
|
|
2
3
|
import { Schema } from "effect";
|
|
3
4
|
|
|
4
5
|
// src/internal/rules/util.ts
|
|
5
6
|
/**
|
|
7
|
+
* Whether `text` ends in a line break. A bare `\r` counts, as it does
|
|
8
|
+
* everywhere positions are computed (YAML 1.2 `b-break`, the lexer's
|
|
9
|
+
* `newline` token, `LintLine` splitting): a bare-CR document lints the way
|
|
10
|
+
* its LF twin does.
|
|
11
|
+
*/
|
|
12
|
+
const endsWithLineBreak = (text) => text.endsWith("\n") || text.endsWith("\r");
|
|
13
|
+
/**
|
|
6
14
|
* A non-negative integer — the shape every numeric rule option takes.
|
|
7
15
|
* Rejects NaN, negatives and fractions with a message naming the constraint;
|
|
8
16
|
* the config layer's wrapper names the rule and the field.
|
|
@@ -87,5 +95,88 @@ function positionAt(lines, offset) {
|
|
|
87
95
|
character: offset - found.offset
|
|
88
96
|
};
|
|
89
97
|
}
|
|
98
|
+
const composedScalarsCache = /* @__PURE__ */ new WeakMap();
|
|
99
|
+
const composedContentsCache = /* @__PURE__ */ new WeakMap();
|
|
100
|
+
/**
|
|
101
|
+
* The composed contents of every document in the context's text. The lint
|
|
102
|
+
* context carries only the FIRST document, and a fix in a later one must be
|
|
103
|
+
* judged against what the composer reads there too.
|
|
104
|
+
*/
|
|
105
|
+
function composedContents(ctx) {
|
|
106
|
+
const cached = composedContentsCache.get(ctx);
|
|
107
|
+
if (cached !== void 0) return cached;
|
|
108
|
+
const contents = composeAllDocuments(ctx.text, { uniqueKeys: false }).documents.map((d) => d.contents);
|
|
109
|
+
composedContentsCache.set(ctx, contents);
|
|
110
|
+
return contents;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Every scalar the COMPOSER reads in the context's text, across all of its
|
|
114
|
+
* documents, ordered by offset. Built once per lint context, on first use.
|
|
115
|
+
*
|
|
116
|
+
* Tokens are the lexer's view and can disagree with the composer: on
|
|
117
|
+
* `"b\n - a"` the lexer emits a `block-seq-entry` for the `-` while the
|
|
118
|
+
* composer folds the second line into the plain scalar `"b - a"`. A fix
|
|
119
|
+
* keyed on that token edits scalar content and changes the parsed value, so
|
|
120
|
+
* a fix that must stay off content asks the composer instead.
|
|
121
|
+
*/
|
|
122
|
+
function composedScalars(ctx) {
|
|
123
|
+
const cached = composedScalarsCache.get(ctx);
|
|
124
|
+
if (cached !== void 0) return cached;
|
|
125
|
+
const out = [];
|
|
126
|
+
for (const contents of composedContents(ctx)) walkScalars(contents, "root", (scalar, role) => {
|
|
127
|
+
out.push({
|
|
128
|
+
offset: scalar.offset,
|
|
129
|
+
end: scalar.offset + scalar.length,
|
|
130
|
+
role,
|
|
131
|
+
style: scalar.style
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
out.sort((a, b) => a.offset - b.offset);
|
|
135
|
+
composedScalarsCache.set(ctx, out);
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* True when `offset` is CONTENT of a scalar the composer reads: inside its
|
|
140
|
+
* span `[offset, end)` and past its first character. Scalars never nest, so
|
|
141
|
+
* the spans are disjoint and a binary search finds the only candidate.
|
|
142
|
+
*/
|
|
143
|
+
function insideComposedScalar(ctx, offset) {
|
|
144
|
+
const scalars = composedScalars(ctx);
|
|
145
|
+
let lo = 0;
|
|
146
|
+
let hi = scalars.length - 1;
|
|
147
|
+
let found;
|
|
148
|
+
while (lo <= hi) {
|
|
149
|
+
const mid = lo + hi >> 1;
|
|
150
|
+
const scalar = scalars[mid];
|
|
151
|
+
if (scalar.offset <= offset) {
|
|
152
|
+
found = scalar;
|
|
153
|
+
lo = mid + 1;
|
|
154
|
+
} else hi = mid - 1;
|
|
155
|
+
}
|
|
156
|
+
return found !== void 0 && offset > found.offset && offset < found.end;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* True when a later line of the same construct starts in the column window
|
|
160
|
+
* `[lo, hi]` — the columns a spacing fix moves content across when it
|
|
161
|
+
* shifts that content from column `hi` to column `lo` on line `line`.
|
|
162
|
+
*
|
|
163
|
+
* Block structure is read off columns, so a line indented into that window
|
|
164
|
+
* can change parents when the content moves: it sat left of the content
|
|
165
|
+
* (a sibling or a parent's entry) and ends up at or right of it (a child),
|
|
166
|
+
* or the reverse. Lines are scanned until the first one indented at or left
|
|
167
|
+
* of `stop`, the column of the indicator that owns the construct, which
|
|
168
|
+
* ends it. Blank and comment-only lines carry no structure and are skipped.
|
|
169
|
+
*/
|
|
170
|
+
function laterLineInWindow(ctx, line, stop, lo, hi) {
|
|
171
|
+
for (let n = line + 1; n < ctx.lines.length; n++) {
|
|
172
|
+
const text = ctx.lines[n].text;
|
|
173
|
+
let indent = 0;
|
|
174
|
+
while (indent < text.length && text[indent] === " ") indent++;
|
|
175
|
+
if (indent === text.length || text[indent] === "#") continue;
|
|
176
|
+
if (indent <= stop) return false;
|
|
177
|
+
if (indent >= lo && indent <= hi) return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
90
181
|
|
|
91
|
-
export { coveringToken, insideScalarSpan, isScalarContinuationLine, nonNegativeIntegerOption, positionAt, positiveIntegerOption, walkScalars };
|
|
182
|
+
export { composedScalars, coveringToken, endsWithLineBreak, insideComposedScalar, insideScalarSpan, isScalarContinuationLine, laterLineInWindow, nonNegativeIntegerOption, positionAt, positiveIntegerOption, walkScalars };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/yaml",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "YAML parsing, editing, formatting and linting as Effect schemas, with no IO and no dependency beyond effect and the kit's own position helpers.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"yaml",
|
|
8
8
|
"parser",
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
},
|
|
41
41
|
"./package.json": "./package.json"
|
|
42
42
|
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@effected/source": "^0.1.0"
|
|
45
|
+
},
|
|
43
46
|
"peerDependencies": {
|
|
44
47
|
"effect": "^4.0.0"
|
|
45
48
|
},
|