@effected/yaml 0.7.0 → 0.9.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 +80 -7
- package/Yaml.js +80 -32
- package/YamlDocument.js +23 -2
- package/YamlFormat.js +162 -21
- package/YamlLint.js +239 -0
- package/YamlLintRule.js +36 -0
- package/YamlNode.js +43 -4
- package/YamlToken.js +139 -0
- package/YamlVisitor.js +52 -16
- package/index.d.ts +446 -12
- package/index.js +4 -1
- package/internal/composer/anchors.js +2 -27
- package/internal/composer/block.js +291 -67
- package/internal/composer/comments.js +217 -0
- package/internal/composer/document.js +141 -14
- package/internal/composer/flow.js +103 -10
- package/internal/composer/scalars.js +48 -11
- package/internal/composer/state.js +16 -2
- package/internal/cst-parser.js +2 -1
- package/internal/diagnostics.js +12 -3
- package/internal/fold.js +22 -10
- package/internal/rules/catalog.js +39 -0
- package/internal/rules/colon-spacing.js +77 -0
- package/internal/rules/comments-spacing.js +68 -0
- package/internal/rules/document-end.js +65 -0
- package/internal/rules/document-start.js +64 -0
- package/internal/rules/empty-lines.js +70 -0
- package/internal/rules/eof-newline.js +33 -0
- package/internal/rules/hyphen-spacing.js +52 -0
- package/internal/rules/indentation.js +103 -0
- package/internal/rules/key-duplicates.js +49 -0
- package/internal/rules/line-length.js +36 -0
- package/internal/rules/parse-validity.js +33 -0
- package/internal/rules/quoted-strings.js +81 -0
- package/internal/rules/trailing-spaces.js +39 -0
- package/internal/rules/truthy.js +84 -0
- package/internal/rules/util.js +92 -0
- package/internal/stringifier.js +342 -54
- package/package.json +3 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { YamlScalar } from "../../YamlNode.js";
|
|
2
2
|
import { registerAnchor } from "./anchors.js";
|
|
3
|
+
import { rawCommentText } from "./comments.js";
|
|
3
4
|
import { lineCol } from "./state.js";
|
|
4
5
|
import { resolveTagHandle } from "./tags.js";
|
|
5
6
|
|
|
@@ -98,13 +99,42 @@ function getScalarStyle(node) {
|
|
|
98
99
|
*/
|
|
99
100
|
function getBlockChomp(node) {
|
|
100
101
|
if (node.type !== "block-scalar") return void 0;
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (header.includes("+")) return "keep";
|
|
105
|
-
if (header.includes("-")) return "strip";
|
|
102
|
+
const indicators = node.source.trimStart().match(/^[|>]([0-9+-]*)/)?.[1] ?? "";
|
|
103
|
+
if (indicators.includes("+")) return "keep";
|
|
104
|
+
if (indicators.includes("-")) return "strip";
|
|
106
105
|
return "clip";
|
|
107
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Extracts the EXPLICIT indentation-indicator digit from a block scalar's
|
|
109
|
+
* header (`|2`, `>1+`), or undefined when the header carries none (the
|
|
110
|
+
* reader auto-detects) or the node is not a block scalar. Inspects only the
|
|
111
|
+
* indicator run — see {@link getBlockChomp} for why the rest of the header
|
|
112
|
+
* line (a comment can contain digits) must not be read.
|
|
113
|
+
*/
|
|
114
|
+
function getBlockIndent(node) {
|
|
115
|
+
if (node.type !== "block-scalar") return void 0;
|
|
116
|
+
const digit = (node.source.trimStart().match(/^[|>]([0-9+-]*)/)?.[1] ?? "").match(/[1-9]/)?.[0];
|
|
117
|
+
return digit === void 0 ? void 0 : Number(digit);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The trailing comment on a block scalar's HEADER line (`key: | # c`,
|
|
121
|
+
* `- >-2 # c`) as stored raw-slice text, or `undefined` when the header
|
|
122
|
+
* carries none. The header is the only line of a block scalar that can carry
|
|
123
|
+
* a `#` comment — body lines absorb `#` as content — and the lexer packs the
|
|
124
|
+
* whole scalar (header comment included) into one CST token, so the comment
|
|
125
|
+
* never surfaces as a sibling comment token for the ordinary attribution
|
|
126
|
+
* pass (#341).
|
|
127
|
+
*/
|
|
128
|
+
function blockScalarHeaderComment(cst) {
|
|
129
|
+
const src = cst.source.trimStart();
|
|
130
|
+
const nl = src.search(/[\r\n]/);
|
|
131
|
+
const header = nl < 0 ? src : src.slice(0, nl);
|
|
132
|
+
const hash = header.indexOf("#");
|
|
133
|
+
if (hash < 1) return void 0;
|
|
134
|
+
const before = header[hash - 1];
|
|
135
|
+
if (before !== " " && before !== " ") return void 0;
|
|
136
|
+
return rawCommentText(header.slice(hash));
|
|
137
|
+
}
|
|
108
138
|
function getScalarValue(node, fullText) {
|
|
109
139
|
if (node.type === "block-scalar") return decodeBlockScalar(node.source, fullText, node.offset);
|
|
110
140
|
const style = getScalarStyle(node);
|
|
@@ -790,18 +820,22 @@ function decodeBlockScalar(raw, fullText, nodeOffset) {
|
|
|
790
820
|
}
|
|
791
821
|
if (hadContent || trailingNewlines.length > 0) {
|
|
792
822
|
if (chomp === "keep") {
|
|
793
|
-
result += "\n";
|
|
823
|
+
if (hadContent) result += "\n";
|
|
794
824
|
for (const _nl of trailingNewlines) result += "\n";
|
|
795
|
-
} else if (chomp !== "strip")
|
|
825
|
+
} else if (chomp !== "strip") {
|
|
826
|
+
if (hadContent) result += "\n";
|
|
827
|
+
}
|
|
796
828
|
}
|
|
797
829
|
value = result;
|
|
798
830
|
} else {
|
|
799
831
|
value = lines.join("\n");
|
|
800
832
|
if (lines.length > 0 || trailingNewlines.length > 0) {
|
|
801
833
|
if (chomp === "keep") {
|
|
802
|
-
value += "\n";
|
|
834
|
+
if (lines.length > 0) value += "\n";
|
|
803
835
|
for (const _nl of trailingNewlines) value += "\n";
|
|
804
|
-
} else if (chomp !== "strip")
|
|
836
|
+
} else if (chomp !== "strip") {
|
|
837
|
+
if (lines.length > 0) value += "\n";
|
|
838
|
+
}
|
|
805
839
|
}
|
|
806
840
|
}
|
|
807
841
|
return value;
|
|
@@ -860,6 +894,8 @@ function makeScalar(cst, state, meta) {
|
|
|
860
894
|
const rawValue = getScalarValue(cst, state.text);
|
|
861
895
|
const value = resolveScalar(rawValue, style, meta?.tag, state);
|
|
862
896
|
const chomp = getBlockChomp(cst);
|
|
897
|
+
const blockIndent = getBlockIndent(cst);
|
|
898
|
+
const comment = (style === "block-literal" || style === "block-folded" ? blockScalarHeaderComment(cst) : void 0) ?? meta?.comment;
|
|
863
899
|
const needsRaw = style === "plain" && typeof value !== "string" && value !== void 0 && shouldPreserveRaw(rawValue, value);
|
|
864
900
|
const scalar = new YamlScalar({
|
|
865
901
|
value,
|
|
@@ -868,8 +904,9 @@ function makeScalar(cst, state, meta) {
|
|
|
868
904
|
length: cst.length,
|
|
869
905
|
...meta?.tag !== void 0 ? { tag: meta.tag } : {},
|
|
870
906
|
...meta?.anchor !== void 0 ? { anchor: meta.anchor } : {},
|
|
871
|
-
...
|
|
907
|
+
...comment !== void 0 ? { comment } : {},
|
|
872
908
|
...chomp !== void 0 ? { chomp } : {},
|
|
909
|
+
...blockIndent !== void 0 ? { blockIndent } : {},
|
|
873
910
|
...needsRaw ? { raw: rawValue } : {}
|
|
874
911
|
});
|
|
875
912
|
if (meta?.anchor) registerAnchor(scalar, meta.anchor, state, cst.offset);
|
|
@@ -894,4 +931,4 @@ function shouldPreserveRaw(rawValue, value) {
|
|
|
894
931
|
}
|
|
895
932
|
|
|
896
933
|
//#endregion
|
|
897
|
-
export { blockMapStartsWithValueSep, classifyPlainNumeric, collectMultilineKey, collectMultilinePlainScalar, findFirstContent, findLastContent, findNextContentChild, findNextContentInList, findNextSignificantChild, findValueSepOffset, foldFlowLines, getBlockChomp, getScalarStyle, getScalarValue, hasBlockMapAfterInList, hasValueSepAfter, hasValueSepAfterInList, hasValueSepBetween, hasValueSepThroughPlainScalars, indexOfChild, makeScalar, resolveScalar, shouldPreserveRaw };
|
|
934
|
+
export { blockMapStartsWithValueSep, classifyPlainNumeric, collectMultilineKey, collectMultilinePlainScalar, findFirstContent, findLastContent, findNextContentChild, findNextContentInList, findNextSignificantChild, findValueSepOffset, foldFlowLines, getBlockChomp, getBlockIndent, getScalarStyle, getScalarValue, hasBlockMapAfterInList, hasValueSepAfter, hasValueSepAfterInList, hasValueSepBetween, hasValueSepThroughPlainScalars, indexOfChild, makeScalar, resolveScalar, shouldPreserveRaw };
|
|
@@ -63,6 +63,19 @@ function clearMeta(m) {
|
|
|
63
63
|
delete m.tag;
|
|
64
64
|
delete m.comment;
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* The comment-fidelity field triple carried by the four public node classes
|
|
68
|
+
* (and `YamlPair`). Conditional-spread helper so AST rebuild sites copy all
|
|
69
|
+
* three without hand-maintaining the list — and never emit an explicit
|
|
70
|
+
* `undefined` into a v4 `optionalKey` field.
|
|
71
|
+
*/
|
|
72
|
+
function commentProps(n) {
|
|
73
|
+
return {
|
|
74
|
+
...n.commentBefore !== void 0 ? { commentBefore: n.commentBefore } : {},
|
|
75
|
+
...n.comment !== void 0 ? { comment: n.comment } : {},
|
|
76
|
+
...n.spaceBefore !== void 0 ? { spaceBefore: n.spaceBefore } : {}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
66
79
|
function createState(text, flow, options) {
|
|
67
80
|
return {
|
|
68
81
|
text,
|
|
@@ -77,7 +90,8 @@ function createState(text, flow, options) {
|
|
|
77
90
|
},
|
|
78
91
|
tagMap: /* @__PURE__ */ new Map(),
|
|
79
92
|
flow,
|
|
80
|
-
depth: 0
|
|
93
|
+
depth: 0,
|
|
94
|
+
escapedComments: []
|
|
81
95
|
};
|
|
82
96
|
}
|
|
83
97
|
/**
|
|
@@ -114,4 +128,4 @@ function exitNesting(state) {
|
|
|
114
128
|
}
|
|
115
129
|
|
|
116
130
|
//#endregion
|
|
117
|
-
export { clearMeta, createState, enterNesting, exitNesting, getLineStarts, hasMeta, hasNonWhitespaceBeforeOnLine, lineCol, lineIndentColumn, sameLine };
|
|
131
|
+
export { clearMeta, commentProps, createState, enterNesting, exitNesting, getLineStarts, hasMeta, hasNonWhitespaceBeforeOnLine, lineCol, lineIndentColumn, sameLine };
|
package/internal/cst-parser.js
CHANGED
|
@@ -365,7 +365,8 @@ function parseBlockValue(state, parentIndent, explicitKey = false) {
|
|
|
365
365
|
const token = peek(state);
|
|
366
366
|
if (!token) break;
|
|
367
367
|
if (token.kind === "whitespace") {
|
|
368
|
-
|
|
368
|
+
advance(state);
|
|
369
|
+
nodes.push(makeLeafNode("whitespace", token, state.text));
|
|
369
370
|
continue;
|
|
370
371
|
}
|
|
371
372
|
if (token.kind === "newline") break;
|
package/internal/diagnostics.js
CHANGED
|
@@ -43,14 +43,23 @@ const YAML_COMPOSE_ERROR_CODES = [
|
|
|
43
43
|
*/
|
|
44
44
|
const YAML_STRINGIFY_ERROR_CODES = ["CircularReference"];
|
|
45
45
|
/**
|
|
46
|
-
* Error codes for the modify stage (`YamlFormat.modify`'s path
|
|
47
|
-
* against an already-composed AST). Not raised by the
|
|
46
|
+
* Error codes for the modify stage (`YamlFormat.modify`'s guards and path
|
|
47
|
+
* navigation against an already-composed AST). Not raised by the
|
|
48
|
+
* parser/composer. `MultiDocumentStream` is the single-document-contract
|
|
49
|
+
* refusal: `modify` re-emits exactly one document, so a multi-document
|
|
50
|
+
* stream fails typed rather than silently truncating documents 2..n.
|
|
51
|
+
* `DirectiveCarryingDocument` is the directive refusal: the stringifier does
|
|
52
|
+
* not re-emit `%YAML`/`%TAG` directive lines, and re-emitting a document
|
|
53
|
+
* without its `%TAG` orphans every shorthand tag that depends on it — the
|
|
54
|
+
* output would be unparseable — so modify fails typed rather than corrupting.
|
|
48
55
|
*/
|
|
49
56
|
const YAML_MODIFY_ERROR_CODES = [
|
|
50
57
|
"EmptyDocument",
|
|
51
58
|
"PathNotFound",
|
|
52
59
|
"InvalidIndex",
|
|
53
|
-
"NotNavigable"
|
|
60
|
+
"NotNavigable",
|
|
61
|
+
"MultiDocumentStream",
|
|
62
|
+
"DirectiveCarryingDocument"
|
|
54
63
|
];
|
|
55
64
|
/**
|
|
56
65
|
* The single source of truth for which diagnostic codes are fatal to a
|
package/internal/fold.js
CHANGED
|
@@ -174,12 +174,18 @@ function renderSingleQuotedMultiline(s, indent) {
|
|
|
174
174
|
* @param explicitChomp - Original chomp indicator from the AST, when known.
|
|
175
175
|
* `keep` (`+`) and `strip` (`-`) preserve trailing-newline semantics that
|
|
176
176
|
* cannot be inferred from the resolved value alone.
|
|
177
|
+
* @param preserveKeep - Re-emit a REDUNDANT explicit `+` (a keep header whose
|
|
178
|
+
* value ends in exactly one newline, where clip would read identically) —
|
|
179
|
+
* fidelity-path header preservation; canonical mode passes false.
|
|
180
|
+
* @param explicitIndent - Explicit indentation-indicator digit from the AST,
|
|
181
|
+
* re-emitted only when it matches the rendered indent (fidelity path).
|
|
177
182
|
*/
|
|
178
|
-
function renderBlockLiteral(s, indent, explicitChomp, parentPosition) {
|
|
183
|
+
function renderBlockLiteral(s, indent, explicitChomp, parentPosition, preserveKeep = false, explicitIndent) {
|
|
179
184
|
let chomp = "";
|
|
180
185
|
const onlyNewlines = s.length > 0 && /^\n+$/.test(s);
|
|
181
186
|
if (s.endsWith("\n\n") || onlyNewlines && explicitChomp === "keep") chomp = "+";
|
|
182
187
|
else if (!s.endsWith("\n")) chomp = "-";
|
|
188
|
+
else if (preserveKeep && explicitChomp === "keep") chomp = "+";
|
|
183
189
|
const lines = s.split("\n");
|
|
184
190
|
if (s.endsWith("\n")) lines.pop();
|
|
185
191
|
let indentIndicator = "";
|
|
@@ -187,6 +193,7 @@ function renderBlockLiteral(s, indent, explicitChomp, parentPosition) {
|
|
|
187
193
|
const hasContent = firstContent !== void 0;
|
|
188
194
|
if (firstContent?.startsWith(" ") || lines.length > 0 && lines[0] === "" && hasContent) indentIndicator = String(indent.length);
|
|
189
195
|
else if (!hasContent && chomp === "+" && parentPosition === "block-map-value" && indent.length > 0) indentIndicator = String(indent.length);
|
|
196
|
+
else if (explicitIndent !== void 0 && explicitIndent === indent.length) indentIndicator = String(explicitIndent);
|
|
190
197
|
return `|${indentIndicator}${chomp}\n${lines.map((l) => l === "" ? "" : `${indent}${l}`).join("\n")}`;
|
|
191
198
|
}
|
|
192
199
|
/**
|
|
@@ -197,34 +204,39 @@ function renderBlockLiteral(s, indent, explicitChomp, parentPosition) {
|
|
|
197
204
|
* the output must contain an empty line (double newline). Each empty line
|
|
198
205
|
* in the value already produces the correct number of blank lines.
|
|
199
206
|
*/
|
|
200
|
-
function renderBlockFolded(s, indent) {
|
|
207
|
+
function renderBlockFolded(s, indent, explicitChomp, explicitIndent) {
|
|
201
208
|
let chomp = "";
|
|
202
209
|
if (s.endsWith("\n\n")) chomp = "+";
|
|
203
210
|
else if (!s.endsWith("\n")) chomp = "-";
|
|
211
|
+
else if (explicitChomp === "keep") chomp = "+";
|
|
204
212
|
const valueLines = s.split("\n");
|
|
205
213
|
if (s.endsWith("\n")) valueLines.pop();
|
|
206
214
|
let indentIndicator = "";
|
|
207
215
|
const firstContent = valueLines.find((l) => l !== "");
|
|
208
216
|
if (firstContent?.startsWith(" ") || valueLines.length >= 2 && valueLines[0] === "" && valueLines[1] === "" && firstContent !== void 0) indentIndicator = String(indent.length);
|
|
217
|
+
else if (explicitIndent !== void 0 && explicitIndent === indent.length) indentIndicator = String(explicitIndent);
|
|
209
218
|
const outputLines = [];
|
|
219
|
+
let lastContentIdx = -1;
|
|
220
|
+
for (let i = valueLines.length - 1; i >= 0; i--) if (valueLines[i] !== "") {
|
|
221
|
+
lastContentIdx = i;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
210
224
|
let prevNonEmpty = false;
|
|
211
225
|
let prevMoreIndented = false;
|
|
226
|
+
let pendingCompensation = false;
|
|
212
227
|
for (let i = 0; i < valueLines.length; i++) {
|
|
213
228
|
const line = valueLines[i];
|
|
214
229
|
if (line === "") {
|
|
215
|
-
if (prevNonEmpty && !prevMoreIndented)
|
|
216
|
-
let nextContentMoreIndented = false;
|
|
217
|
-
for (let j = i + 1; j < valueLines.length; j++) if (valueLines[j] !== "") {
|
|
218
|
-
nextContentMoreIndented = valueLines[j].startsWith(" ") || valueLines[j].startsWith(" ");
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
if (!nextContentMoreIndented) outputLines.push("");
|
|
222
|
-
}
|
|
230
|
+
if (prevNonEmpty && !prevMoreIndented && i < lastContentIdx) pendingCompensation = true;
|
|
223
231
|
outputLines.push("");
|
|
224
232
|
prevNonEmpty = false;
|
|
225
233
|
prevMoreIndented = false;
|
|
226
234
|
} else {
|
|
227
235
|
const isMoreIndented = line.startsWith(" ") || line.startsWith(" ");
|
|
236
|
+
if (pendingCompensation) {
|
|
237
|
+
if (!isMoreIndented) outputLines.push("");
|
|
238
|
+
pendingCompensation = false;
|
|
239
|
+
}
|
|
228
240
|
if (prevNonEmpty && !isMoreIndented) outputLines.push("");
|
|
229
241
|
outputLines.push(`${indent}${line}`);
|
|
230
242
|
prevNonEmpty = true;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { colonSpacing, colonSpacingOptions } from "./colon-spacing.js";
|
|
2
|
+
import { commentsSpacing, commentsSpacingOptions } from "./comments-spacing.js";
|
|
3
|
+
import { documentEnd, documentEndOptions } from "./document-end.js";
|
|
4
|
+
import { documentStart, documentStartOptions } from "./document-start.js";
|
|
5
|
+
import { emptyLines, emptyLinesOptions } from "./empty-lines.js";
|
|
6
|
+
import { eofNewline, eofNewlineOptions } from "./eof-newline.js";
|
|
7
|
+
import { hyphenSpacing, hyphenSpacingOptions } from "./hyphen-spacing.js";
|
|
8
|
+
import { indentation, indentationOptions } from "./indentation.js";
|
|
9
|
+
import { keyDuplicates, keyDuplicatesOptions } from "./key-duplicates.js";
|
|
10
|
+
import { lineLength, lineLengthOptions } from "./line-length.js";
|
|
11
|
+
import { parseValidity, parseValidityOptions } from "./parse-validity.js";
|
|
12
|
+
import { quotedStrings, quotedStringsOptions } from "./quoted-strings.js";
|
|
13
|
+
import { trailingSpaces, trailingSpacesOptions } from "./trailing-spaces.js";
|
|
14
|
+
import { truthy, truthyOptions } from "./truthy.js";
|
|
15
|
+
|
|
16
|
+
//#region src/internal/rules/catalog.ts
|
|
17
|
+
const catalog = [
|
|
18
|
+
[parseValidity, parseValidityOptions],
|
|
19
|
+
[lineLength, lineLengthOptions],
|
|
20
|
+
[trailingSpaces, trailingSpacesOptions],
|
|
21
|
+
[emptyLines, emptyLinesOptions],
|
|
22
|
+
[eofNewline, eofNewlineOptions],
|
|
23
|
+
[documentStart, documentStartOptions],
|
|
24
|
+
[documentEnd, documentEndOptions],
|
|
25
|
+
[keyDuplicates, keyDuplicatesOptions],
|
|
26
|
+
[quotedStrings, quotedStringsOptions],
|
|
27
|
+
[truthy, truthyOptions],
|
|
28
|
+
[commentsSpacing, commentsSpacingOptions],
|
|
29
|
+
[colonSpacing, colonSpacingOptions],
|
|
30
|
+
[hyphenSpacing, hyphenSpacingOptions],
|
|
31
|
+
[indentation, indentationOptions]
|
|
32
|
+
];
|
|
33
|
+
/** The built-in rules, in catalog order (parse-validity is rule `#1`). */
|
|
34
|
+
const builtinRules = catalog.map(([rule]) => rule);
|
|
35
|
+
/** Per-rule options schemas — the rule-aware half of config validation. */
|
|
36
|
+
const builtinOptionsSchemas = new Map(catalog.map(([rule, options]) => [rule.id, options]));
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { builtinOptionsSchemas, builtinRules };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
|
+
import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
+
import { nonNegativeIntegerOption, positiveIntegerOption } from "./util.js";
|
|
4
|
+
import { Schema } from "effect";
|
|
5
|
+
|
|
6
|
+
//#region src/internal/rules/colon-spacing.ts
|
|
7
|
+
/**
|
|
8
|
+
* Options for `colon-spacing`: `maxSpacesBefore` (default 0) and
|
|
9
|
+
* `maxSpacesAfter` (default 1) around the `:` indicator. `maxSpacesBefore: 0`
|
|
10
|
+
* is legal (`key:` needs no space before the colon), but at least one
|
|
11
|
+
* separation space must FOLLOW it — `0` would make the fix emit `a:val`, a
|
|
12
|
+
* plain scalar, not a mapping entry.
|
|
13
|
+
*/
|
|
14
|
+
const colonSpacingOptions = Schema.Struct({
|
|
15
|
+
severity: Schema.optionalKey(YamlLintSeverity),
|
|
16
|
+
maxSpacesBefore: Schema.optionalKey(nonNegativeIntegerOption),
|
|
17
|
+
maxSpacesAfter: Schema.optionalKey(positiveIntegerOption)
|
|
18
|
+
});
|
|
19
|
+
/** Spacing around the block-mapping `:` indicator. */
|
|
20
|
+
const colonSpacing = {
|
|
21
|
+
id: "colon-spacing",
|
|
22
|
+
check: (ctx, options) => {
|
|
23
|
+
const opts = options ?? {};
|
|
24
|
+
const maxBefore = opts.maxSpacesBefore ?? 0;
|
|
25
|
+
const maxAfter = Math.max(1, opts.maxSpacesAfter ?? 1);
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const token of ctx.tokens) {
|
|
28
|
+
if (token.kind !== "block-map-value") continue;
|
|
29
|
+
let i = token.offset - 1;
|
|
30
|
+
let before = 0;
|
|
31
|
+
while (i >= 0 && ctx.text[i] === " ") {
|
|
32
|
+
before++;
|
|
33
|
+
i--;
|
|
34
|
+
}
|
|
35
|
+
if (i >= 0 && ctx.text[i] !== "\n" && ctx.text[i] !== "\r" && ctx.text[i] !== " " && before > maxBefore) out.push(new YamlLintDiagnostic({
|
|
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
|
+
}));
|
|
49
|
+
let j = token.offset + token.length;
|
|
50
|
+
let after = 0;
|
|
51
|
+
while (j < ctx.text.length && ctx.text[j] === " ") {
|
|
52
|
+
after++;
|
|
53
|
+
j++;
|
|
54
|
+
}
|
|
55
|
+
const next = ctx.text[j];
|
|
56
|
+
if (next === void 0 || next === "\n" || next === "\r" || next === "#") continue;
|
|
57
|
+
if (after > maxAfter) out.push(new YamlLintDiagnostic({
|
|
58
|
+
rule: "colon-spacing",
|
|
59
|
+
severity: "error",
|
|
60
|
+
message: `Too many spaces after ":" (${after} > ${maxAfter})`,
|
|
61
|
+
offset: token.offset + token.length,
|
|
62
|
+
length: after,
|
|
63
|
+
line: token.line,
|
|
64
|
+
character: token.character + token.length,
|
|
65
|
+
fix: YamlEdit.make({
|
|
66
|
+
offset: token.offset + token.length,
|
|
67
|
+
length: after - maxAfter,
|
|
68
|
+
content: ""
|
|
69
|
+
})
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { colonSpacing, colonSpacingOptions };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
|
+
import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
+
import { nonNegativeIntegerOption } from "./util.js";
|
|
4
|
+
import { Schema } from "effect";
|
|
5
|
+
|
|
6
|
+
//#region src/internal/rules/comments-spacing.ts
|
|
7
|
+
/**
|
|
8
|
+
* Options for `comments-spacing`: `minSpacesBefore` between content and a
|
|
9
|
+
* trailing `#` (default 1 — the kit's own emission spelling) and
|
|
10
|
+
* `requireSpaceAfter` the `#` (default `true`).
|
|
11
|
+
*/
|
|
12
|
+
const commentsSpacingOptions = Schema.Struct({
|
|
13
|
+
severity: Schema.optionalKey(YamlLintSeverity),
|
|
14
|
+
minSpacesBefore: Schema.optionalKey(nonNegativeIntegerOption),
|
|
15
|
+
requireSpaceAfter: Schema.optionalKey(Schema.Boolean)
|
|
16
|
+
});
|
|
17
|
+
/** Comment spacing: after the `#`, and before a trailing comment's `#`. */
|
|
18
|
+
const commentsSpacing = {
|
|
19
|
+
id: "comments-spacing",
|
|
20
|
+
check: (ctx, options) => {
|
|
21
|
+
const opts = options ?? {};
|
|
22
|
+
const minBefore = opts.minSpacesBefore ?? 1;
|
|
23
|
+
const requireAfter = opts.requireSpaceAfter ?? true;
|
|
24
|
+
const out = [];
|
|
25
|
+
for (const token of ctx.tokens) {
|
|
26
|
+
if (token.kind !== "comment") continue;
|
|
27
|
+
if (token.offset === 0 && token.text.startsWith("#!")) continue;
|
|
28
|
+
if (requireAfter && token.text.length > 1 && !/[ \t]/.test(token.text[1])) out.push(new YamlLintDiagnostic({
|
|
29
|
+
rule: "comments-spacing",
|
|
30
|
+
severity: "error",
|
|
31
|
+
message: "Missing space after \"#\"",
|
|
32
|
+
offset: token.offset,
|
|
33
|
+
length: 1,
|
|
34
|
+
line: token.line,
|
|
35
|
+
character: token.character,
|
|
36
|
+
fix: YamlEdit.make({
|
|
37
|
+
offset: token.offset + 1,
|
|
38
|
+
length: 0,
|
|
39
|
+
content: " "
|
|
40
|
+
})
|
|
41
|
+
}));
|
|
42
|
+
let i = token.offset - 1;
|
|
43
|
+
let spaces = 0;
|
|
44
|
+
while (i >= 0 && (ctx.text[i] === " " || ctx.text[i] === " ")) {
|
|
45
|
+
spaces++;
|
|
46
|
+
i--;
|
|
47
|
+
}
|
|
48
|
+
if (i >= 0 && ctx.text[i] !== "\n" && ctx.text[i] !== "\r" && spaces < minBefore) out.push(new YamlLintDiagnostic({
|
|
49
|
+
rule: "comments-spacing",
|
|
50
|
+
severity: "error",
|
|
51
|
+
message: `Too few spaces before comment (${spaces} < ${minBefore})`,
|
|
52
|
+
offset: token.offset - spaces,
|
|
53
|
+
length: spaces,
|
|
54
|
+
line: token.line,
|
|
55
|
+
character: token.character - spaces,
|
|
56
|
+
fix: YamlEdit.make({
|
|
57
|
+
offset: token.offset,
|
|
58
|
+
length: 0,
|
|
59
|
+
content: " ".repeat(minBefore - spaces)
|
|
60
|
+
})
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
export { commentsSpacing, commentsSpacingOptions };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
|
+
import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
+
import { Schema } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/internal/rules/document-end.ts
|
|
6
|
+
/** Options for `document-end`: require (`true`, default) or forbid the marker. */
|
|
7
|
+
const documentEndOptions = Schema.Struct({
|
|
8
|
+
severity: Schema.optionalKey(YamlLintSeverity),
|
|
9
|
+
present: Schema.optionalKey(Schema.Boolean)
|
|
10
|
+
});
|
|
11
|
+
const TRIVIA = /* @__PURE__ */ new Set([
|
|
12
|
+
"newline",
|
|
13
|
+
"whitespace",
|
|
14
|
+
"comment",
|
|
15
|
+
"byte-order-mark"
|
|
16
|
+
]);
|
|
17
|
+
/** The `...` marker at the tail of the stream. */
|
|
18
|
+
const documentEnd = {
|
|
19
|
+
id: "document-end",
|
|
20
|
+
check: (ctx, options) => {
|
|
21
|
+
const present = options?.present ?? true;
|
|
22
|
+
const tail = [...ctx.tokens].reverse().find((t) => !TRIVIA.has(t.kind));
|
|
23
|
+
const ended = tail?.kind === "document-end";
|
|
24
|
+
if (present && !ended && ctx.text.trim() !== "") {
|
|
25
|
+
const lastLine = ctx.lines[ctx.lines.length - 1];
|
|
26
|
+
const trailingNewline = ctx.text.endsWith("\n");
|
|
27
|
+
return [new YamlLintDiagnostic({
|
|
28
|
+
rule: "document-end",
|
|
29
|
+
severity: "error",
|
|
30
|
+
message: "Missing \"...\" document end marker",
|
|
31
|
+
offset: ctx.text.length,
|
|
32
|
+
length: 0,
|
|
33
|
+
line: (lastLine?.number ?? 0) + (trailingNewline ? 1 : 0),
|
|
34
|
+
character: trailingNewline ? 0 : lastLine?.text.length ?? 0,
|
|
35
|
+
fix: YamlEdit.make({
|
|
36
|
+
offset: ctx.text.length,
|
|
37
|
+
length: 0,
|
|
38
|
+
content: ctx.text.endsWith("\n") ? "...\n" : "\n...\n"
|
|
39
|
+
})
|
|
40
|
+
})];
|
|
41
|
+
}
|
|
42
|
+
if (!present && tail !== void 0 && tail.kind === "document-end") {
|
|
43
|
+
const alone = (ctx.lines[tail.line]?.text ?? "").trim() === "...";
|
|
44
|
+
const terminator = ctx.text.startsWith("\r\n", tail.offset + tail.length) ? 2 : 1;
|
|
45
|
+
return [new YamlLintDiagnostic({
|
|
46
|
+
rule: "document-end",
|
|
47
|
+
severity: "error",
|
|
48
|
+
message: "Forbidden \"...\" document end marker",
|
|
49
|
+
offset: tail.offset,
|
|
50
|
+
length: tail.length,
|
|
51
|
+
line: tail.line,
|
|
52
|
+
character: tail.character,
|
|
53
|
+
...alone ? { fix: YamlEdit.make({
|
|
54
|
+
offset: tail.offset,
|
|
55
|
+
length: tail.length + terminator,
|
|
56
|
+
content: ""
|
|
57
|
+
}) } : {}
|
|
58
|
+
})];
|
|
59
|
+
}
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { documentEnd, documentEndOptions };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
|
+
import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
+
import { Schema } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/internal/rules/document-start.ts
|
|
6
|
+
/** Options for `document-start`: require (`true`, default) or forbid the marker. */
|
|
7
|
+
const documentStartOptions = Schema.Struct({
|
|
8
|
+
severity: Schema.optionalKey(YamlLintSeverity),
|
|
9
|
+
present: Schema.optionalKey(Schema.Boolean)
|
|
10
|
+
});
|
|
11
|
+
const TRIVIA = /* @__PURE__ */ new Set([
|
|
12
|
+
"newline",
|
|
13
|
+
"whitespace",
|
|
14
|
+
"comment",
|
|
15
|
+
"byte-order-mark",
|
|
16
|
+
"directive"
|
|
17
|
+
]);
|
|
18
|
+
/** The `---` marker at the head of the stream. */
|
|
19
|
+
const documentStart = {
|
|
20
|
+
id: "document-start",
|
|
21
|
+
check: (ctx, options) => {
|
|
22
|
+
const present = options?.present ?? true;
|
|
23
|
+
const hasDirectives = ctx.tokens.some((t) => t.kind === "directive");
|
|
24
|
+
const first = ctx.tokens.find((t) => !TRIVIA.has(t.kind));
|
|
25
|
+
const headed = first?.kind === "document-start";
|
|
26
|
+
if (present && !headed && ctx.text.trim() !== "") return [new YamlLintDiagnostic({
|
|
27
|
+
rule: "document-start",
|
|
28
|
+
severity: "error",
|
|
29
|
+
message: "Missing \"---\" document start marker",
|
|
30
|
+
offset: 0,
|
|
31
|
+
length: 0,
|
|
32
|
+
line: 0,
|
|
33
|
+
character: 0,
|
|
34
|
+
...hasDirectives ? {} : { fix: YamlEdit.make({
|
|
35
|
+
offset: 0,
|
|
36
|
+
length: 0,
|
|
37
|
+
content: "---\n"
|
|
38
|
+
}) }
|
|
39
|
+
})];
|
|
40
|
+
if (!present && headed && first !== void 0) {
|
|
41
|
+
const lineText = ctx.lines[first.line]?.text ?? "";
|
|
42
|
+
const alone = !hasDirectives && lineText.trim() === "---";
|
|
43
|
+
const terminator = ctx.text.startsWith("\r\n", first.offset + first.length) ? 2 : 1;
|
|
44
|
+
return [new YamlLintDiagnostic({
|
|
45
|
+
rule: "document-start",
|
|
46
|
+
severity: "error",
|
|
47
|
+
message: "Forbidden \"---\" document start marker",
|
|
48
|
+
offset: first.offset,
|
|
49
|
+
length: first.length,
|
|
50
|
+
line: first.line,
|
|
51
|
+
character: first.character,
|
|
52
|
+
...alone ? { fix: YamlEdit.make({
|
|
53
|
+
offset: first.offset,
|
|
54
|
+
length: first.length + terminator,
|
|
55
|
+
content: ""
|
|
56
|
+
}) } : {}
|
|
57
|
+
})];
|
|
58
|
+
}
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
export { documentStart, documentStartOptions };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { YamlEdit } from "../../YamlEdit.js";
|
|
2
|
+
import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
|
|
3
|
+
import { insideScalarSpan, nonNegativeIntegerOption } from "./util.js";
|
|
4
|
+
import { Schema } from "effect";
|
|
5
|
+
|
|
6
|
+
//#region src/internal/rules/empty-lines.ts
|
|
7
|
+
/**
|
|
8
|
+
* Options for `empty-lines`: `max` consecutive blank lines in the body
|
|
9
|
+
* (default 2), `maxStart` at the document start and `maxEnd` at the end
|
|
10
|
+
* (both default 0).
|
|
11
|
+
*/
|
|
12
|
+
const emptyLinesOptions = Schema.Struct({
|
|
13
|
+
severity: Schema.optionalKey(YamlLintSeverity),
|
|
14
|
+
max: Schema.optionalKey(nonNegativeIntegerOption),
|
|
15
|
+
maxStart: Schema.optionalKey(nonNegativeIntegerOption),
|
|
16
|
+
maxEnd: Schema.optionalKey(nonNegativeIntegerOption)
|
|
17
|
+
});
|
|
18
|
+
/** Runs of blank lines beyond the configured caps, with a deleting fix. */
|
|
19
|
+
const emptyLines = {
|
|
20
|
+
id: "empty-lines",
|
|
21
|
+
check: (ctx, options) => {
|
|
22
|
+
const opts = options ?? {};
|
|
23
|
+
const max = opts.max ?? 2;
|
|
24
|
+
const maxStart = opts.maxStart ?? 0;
|
|
25
|
+
const maxEnd = opts.maxEnd ?? 0;
|
|
26
|
+
const out = [];
|
|
27
|
+
const lines = ctx.lines;
|
|
28
|
+
let i = 0;
|
|
29
|
+
while (i < lines.length) {
|
|
30
|
+
const line = lines[i];
|
|
31
|
+
if (line === void 0 || line.text !== "" || insideScalarSpan(ctx.tokens, line.offset)) {
|
|
32
|
+
i++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
let end = i;
|
|
36
|
+
while (end < lines.length && (lines[end]?.text ?? "x") === "") end++;
|
|
37
|
+
const runLength = end - i;
|
|
38
|
+
const atStart = i === 0;
|
|
39
|
+
const atEnd = end === lines.length;
|
|
40
|
+
const allowed = atStart ? maxStart : atEnd ? maxEnd : max;
|
|
41
|
+
if (runLength > allowed) {
|
|
42
|
+
const firstExcess = lines[i + allowed];
|
|
43
|
+
const lastBlank = lines[end - 1];
|
|
44
|
+
if (firstExcess !== void 0 && lastBlank !== void 0) {
|
|
45
|
+
const nextLine = lines[end];
|
|
46
|
+
const deleteEnd = nextLine !== void 0 ? nextLine.offset : ctx.text.length;
|
|
47
|
+
out.push(new YamlLintDiagnostic({
|
|
48
|
+
rule: "empty-lines",
|
|
49
|
+
severity: "error",
|
|
50
|
+
message: `Too many consecutive blank lines (${runLength} > ${allowed})`,
|
|
51
|
+
offset: firstExcess.offset,
|
|
52
|
+
length: deleteEnd - firstExcess.offset,
|
|
53
|
+
line: firstExcess.number,
|
|
54
|
+
character: 0,
|
|
55
|
+
fix: YamlEdit.make({
|
|
56
|
+
offset: firstExcess.offset,
|
|
57
|
+
length: deleteEnd - firstExcess.offset,
|
|
58
|
+
content: ""
|
|
59
|
+
})
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
i = end;
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { emptyLines, emptyLinesOptions };
|