@effected/yaml 0.6.1 → 0.8.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.
Files changed (40) hide show
  1. package/README.md +78 -7
  2. package/Yaml.js +87 -39
  3. package/YamlDocument.js +25 -6
  4. package/YamlFormat.js +171 -20
  5. package/YamlLint.js +239 -0
  6. package/YamlLintRule.js +36 -0
  7. package/YamlNode.js +36 -2
  8. package/YamlToken.js +139 -0
  9. package/YamlVisitor.js +40 -5
  10. package/index.d.ts +439 -12
  11. package/index.js +4 -1
  12. package/internal/composer/anchors.js +2 -27
  13. package/internal/composer/block.js +246 -58
  14. package/internal/composer/comments.js +187 -0
  15. package/internal/composer/document.js +49 -12
  16. package/internal/composer/flow.js +82 -9
  17. package/internal/composer/scalars.js +77 -28
  18. package/internal/composer/state.js +16 -2
  19. package/internal/cst-parser.js +11 -2
  20. package/internal/diagnostics.js +12 -3
  21. package/internal/fold.js +22 -10
  22. package/internal/lexer.js +23 -17
  23. package/internal/rules/catalog.js +39 -0
  24. package/internal/rules/colon-spacing.js +77 -0
  25. package/internal/rules/comments-spacing.js +68 -0
  26. package/internal/rules/document-end.js +65 -0
  27. package/internal/rules/document-start.js +64 -0
  28. package/internal/rules/empty-lines.js +70 -0
  29. package/internal/rules/eof-newline.js +33 -0
  30. package/internal/rules/hyphen-spacing.js +52 -0
  31. package/internal/rules/indentation.js +103 -0
  32. package/internal/rules/key-duplicates.js +49 -0
  33. package/internal/rules/line-length.js +36 -0
  34. package/internal/rules/parse-validity.js +33 -0
  35. package/internal/rules/quoted-strings.js +81 -0
  36. package/internal/rules/trailing-spaces.js +39 -0
  37. package/internal/rules/truthy.js +84 -0
  38. package/internal/rules/util.js +92 -0
  39. package/internal/stringifier.js +285 -69
  40. package/package.json +4 -3
@@ -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 };
@@ -0,0 +1,33 @@
1
+ import { YamlEdit } from "../../YamlEdit.js";
2
+ import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
3
+ import { Schema } from "effect";
4
+
5
+ //#region src/internal/rules/eof-newline.ts
6
+ /** Options for `eof-newline` (severity only — nothing to tune). */
7
+ const eofNewlineOptions = Schema.Struct({ severity: Schema.optionalKey(YamlLintSeverity) });
8
+ /** A missing final newline, with an inserting fix. */
9
+ const eofNewline = {
10
+ id: "eof-newline",
11
+ check: (ctx) => {
12
+ if (ctx.text.length === 0 || ctx.text.endsWith("\n")) return [];
13
+ const lastLine = ctx.lines[ctx.lines.length - 1];
14
+ if (lastLine === void 0) return [];
15
+ return [new YamlLintDiagnostic({
16
+ rule: "eof-newline",
17
+ severity: "error",
18
+ message: "No newline at end of file",
19
+ offset: ctx.text.length,
20
+ length: 0,
21
+ line: lastLine.number,
22
+ character: lastLine.text.length,
23
+ fix: YamlEdit.make({
24
+ offset: ctx.text.length,
25
+ length: 0,
26
+ content: "\n"
27
+ })
28
+ })];
29
+ }
30
+ };
31
+
32
+ //#endregion
33
+ export { eofNewline, eofNewlineOptions };
@@ -0,0 +1,52 @@
1
+ import { YamlEdit } from "../../YamlEdit.js";
2
+ import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
3
+ import { positiveIntegerOption } from "./util.js";
4
+ import { Schema } from "effect";
5
+
6
+ //#region src/internal/rules/hyphen-spacing.ts
7
+ /**
8
+ * Options for `hyphen-spacing`: `maxSpacesAfter` (default 1) after the `-`.
9
+ * At least one separation space must follow the indicator — `0` would make
10
+ * the fix emit `-item`, a plain scalar, not a sequence entry.
11
+ */
12
+ const hyphenSpacingOptions = Schema.Struct({
13
+ severity: Schema.optionalKey(YamlLintSeverity),
14
+ maxSpacesAfter: Schema.optionalKey(positiveIntegerOption)
15
+ });
16
+ /** Spacing after the block-sequence `-` indicator. */
17
+ const hyphenSpacing = {
18
+ id: "hyphen-spacing",
19
+ check: (ctx, options) => {
20
+ const maxAfter = Math.max(1, (options ?? {}).maxSpacesAfter ?? 1);
21
+ const out = [];
22
+ for (const token of ctx.tokens) {
23
+ if (token.kind !== "block-seq-entry") continue;
24
+ let j = token.offset + token.length;
25
+ let after = 0;
26
+ while (j < ctx.text.length && ctx.text[j] === " ") {
27
+ after++;
28
+ j++;
29
+ }
30
+ const next = ctx.text[j];
31
+ if (next === void 0 || next === "\n" || next === "\r" || next === "#") continue;
32
+ if (after > maxAfter) out.push(new YamlLintDiagnostic({
33
+ rule: "hyphen-spacing",
34
+ severity: "error",
35
+ message: `Too many spaces after "-" (${after} > ${maxAfter})`,
36
+ offset: token.offset + token.length,
37
+ length: after,
38
+ line: token.line,
39
+ character: token.character + token.length,
40
+ fix: YamlEdit.make({
41
+ offset: token.offset + token.length,
42
+ length: after - maxAfter,
43
+ content: ""
44
+ })
45
+ }));
46
+ }
47
+ return out;
48
+ }
49
+ };
50
+
51
+ //#endregion
52
+ export { hyphenSpacing, hyphenSpacingOptions };
@@ -0,0 +1,103 @@
1
+ import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
2
+ import { coveringToken, isScalarContinuationLine, nonNegativeIntegerOption } from "./util.js";
3
+ import { Schema } from "effect";
4
+
5
+ //#region src/internal/rules/indentation.ts
6
+ /**
7
+ * Options for `indentation`: `spaces` per level (number or "consistent",
8
+ * default "consistent") and `indentSequences` (boolean or "consistent",
9
+ * default "consistent").
10
+ */
11
+ const indentationOptions = Schema.Struct({
12
+ severity: Schema.optionalKey(YamlLintSeverity),
13
+ spaces: Schema.optionalKey(Schema.Union([nonNegativeIntegerOption, Schema.Literals(["consistent"])])),
14
+ indentSequences: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Literals(["consistent"])]))
15
+ });
16
+ /** Flow-collection depth at each line start, from the token stream. */
17
+ const flowDepthAtLineStarts = (ctx) => {
18
+ const depths = [];
19
+ let depth = 0;
20
+ let tokenIdx = 0;
21
+ for (const line of ctx.lines) {
22
+ while (tokenIdx < ctx.tokens.length) {
23
+ const token = ctx.tokens[tokenIdx];
24
+ if (token === void 0 || token.offset + token.length > line.offset) break;
25
+ if (token.kind === "flow-map-start" || token.kind === "flow-seq-start") depth++;
26
+ if (token.kind === "flow-map-end" || token.kind === "flow-seq-end") depth = Math.max(0, depth - 1);
27
+ tokenIdx++;
28
+ }
29
+ depths.push(depth);
30
+ }
31
+ return depths;
32
+ };
33
+ /** Block-structure indent style. */
34
+ const indentation = {
35
+ id: "indentation",
36
+ check: (ctx, options) => {
37
+ const opts = options ?? {};
38
+ const spacesOpt = opts.spaces ?? "consistent";
39
+ const seqOpt = opts.indentSequences ?? "consistent";
40
+ const out = [];
41
+ const flowDepths = flowDepthAtLineStarts(ctx);
42
+ const content = [];
43
+ for (const line of ctx.lines) {
44
+ const indent = line.text.length - line.text.trimStart().length;
45
+ const firstChar = line.text[indent];
46
+ if (firstChar === void 0) continue;
47
+ if (firstChar === "#") continue;
48
+ if (firstChar === "%") continue;
49
+ if (line.text.slice(0, indent).includes(" ")) continue;
50
+ if ((flowDepths[line.number] ?? 0) > 0) continue;
51
+ if (isScalarContinuationLine(ctx.tokens, line.offset, line.offset + indent)) continue;
52
+ if (line.text.slice(indent).startsWith("---") || line.text.slice(indent).startsWith("...")) continue;
53
+ content.push({
54
+ line,
55
+ indent,
56
+ firstChar
57
+ });
58
+ }
59
+ let unit = typeof spacesOpt === "number" ? spacesOpt : void 0;
60
+ const stack = [0];
61
+ for (const { line, indent } of content) {
62
+ const top = stack[stack.length - 1];
63
+ if (indent > top) {
64
+ const delta = indent - top;
65
+ if (unit === void 0) unit = delta;
66
+ else if (delta !== unit) out.push(new YamlLintDiagnostic({
67
+ rule: "indentation",
68
+ severity: "error",
69
+ message: `Indent of ${delta} spaces, expected ${unit}`,
70
+ offset: line.offset,
71
+ length: indent,
72
+ line: line.number,
73
+ character: 0
74
+ }));
75
+ stack.push(indent);
76
+ } else if (indent < top) while (stack.length > 1 && stack[stack.length - 1] > indent) stack.pop();
77
+ }
78
+ let seqIndented = typeof seqOpt === "boolean" ? seqOpt : void 0;
79
+ for (let i = 1; i < content.length; i++) {
80
+ const prev = content[i - 1];
81
+ const curr = content[i];
82
+ if (curr.firstChar !== "-") continue;
83
+ if (coveringToken(ctx.tokens, curr.line.offset + curr.indent)?.kind !== "block-seq-entry") continue;
84
+ if (!prev.line.text.replace(/ #.*$/, "").trimEnd().endsWith(":")) continue;
85
+ if (curr.indent !== prev.indent && curr.indent !== prev.indent + (unit ?? curr.indent - prev.indent)) continue;
86
+ const indented = curr.indent > prev.indent;
87
+ if (seqIndented === void 0) seqIndented = indented;
88
+ else if (indented !== seqIndented) out.push(new YamlLintDiagnostic({
89
+ rule: "indentation",
90
+ severity: "error",
91
+ message: seqIndented ? "Sequence under a mapping key should be indented" : "Sequence under a mapping key should not be indented",
92
+ offset: curr.line.offset,
93
+ length: curr.indent,
94
+ line: curr.line.number,
95
+ character: 0
96
+ }));
97
+ }
98
+ return out;
99
+ }
100
+ };
101
+
102
+ //#endregion
103
+ export { indentation, indentationOptions };
@@ -0,0 +1,49 @@
1
+ import { YamlMap, YamlScalar, YamlSeq } from "../../YamlNode.js";
2
+ import { keyIdentity } from "../composer/block.js";
3
+ import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
4
+ import { positionAt } from "./util.js";
5
+ import { Schema } from "effect";
6
+
7
+ //#region src/internal/rules/key-duplicates.ts
8
+ /** Options for `key-duplicates` (severity only — duplicates are duplicates). */
9
+ const keyDuplicatesOptions = Schema.Struct({ severity: Schema.optionalKey(YamlLintSeverity) });
10
+ const walk = (node, text, out, ctx) => {
11
+ if (node === null) return;
12
+ if (node instanceof YamlMap) {
13
+ const seen = /* @__PURE__ */ new Set();
14
+ for (const pair of node.items) {
15
+ if (pair.key instanceof YamlScalar) {
16
+ const id = keyIdentity(pair.key, text);
17
+ if (seen.has(id)) {
18
+ const pos = positionAt(ctx.lines, pair.key.offset);
19
+ out.push(new YamlLintDiagnostic({
20
+ rule: "key-duplicates",
21
+ severity: "error",
22
+ message: `Duplicate key: ${String(pair.key.value)}`,
23
+ offset: pair.key.offset,
24
+ length: pair.key.length,
25
+ line: pos.line,
26
+ character: pos.character
27
+ }));
28
+ }
29
+ seen.add(id);
30
+ }
31
+ walk(pair.key, text, out, ctx);
32
+ walk(pair.value, text, out, ctx);
33
+ }
34
+ return;
35
+ }
36
+ if (node instanceof YamlSeq) for (const item of node.items) walk(item, text, out, ctx);
37
+ };
38
+ /** Duplicate mapping keys anywhere in the document. */
39
+ const keyDuplicates = {
40
+ id: "key-duplicates",
41
+ check: (ctx) => {
42
+ const out = [];
43
+ walk(ctx.document.contents, ctx.text, out, ctx);
44
+ return out;
45
+ }
46
+ };
47
+
48
+ //#endregion
49
+ export { keyDuplicates, keyDuplicatesOptions };
@@ -0,0 +1,36 @@
1
+ import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
2
+ import { nonNegativeIntegerOption } from "./util.js";
3
+ import { Schema } from "effect";
4
+
5
+ //#region src/internal/rules/line-length.ts
6
+ /**
7
+ * Options for `line-length`. `max` defaults to 120 — the kit-native line
8
+ * width (the yamllint id is recognizable; the option surface and defaults
9
+ * are ours).
10
+ */
11
+ const lineLengthOptions = Schema.Struct({
12
+ severity: Schema.optionalKey(YamlLintSeverity),
13
+ max: Schema.optionalKey(nonNegativeIntegerOption)
14
+ });
15
+ const DEFAULT_MAX = 120;
16
+ /** Lines longer than the configured maximum. */
17
+ const lineLength = {
18
+ id: "line-length",
19
+ check: (ctx, options) => {
20
+ const max = options?.max ?? DEFAULT_MAX;
21
+ const out = [];
22
+ for (const line of ctx.lines) if (line.text.length > max) out.push(new YamlLintDiagnostic({
23
+ rule: "line-length",
24
+ severity: "error",
25
+ message: `Line is longer than ${max} characters (${line.text.length})`,
26
+ offset: line.offset + max,
27
+ length: line.text.length - max,
28
+ line: line.number,
29
+ character: max
30
+ }));
31
+ return out;
32
+ }
33
+ };
34
+
35
+ //#endregion
36
+ export { lineLength, lineLengthOptions };
@@ -0,0 +1,33 @@
1
+ import { YamlLintDiagnostic } from "../../YamlLintRule.js";
2
+ import { Schema } from "effect";
3
+
4
+ //#region src/internal/rules/parse-validity.ts
5
+ /**
6
+ * parse-validity accepts no options; the config layer additionally rejects
7
+ * any attempt to set a severity or `"off"` on this rule.
8
+ */
9
+ const parseValidityOptions = Schema.Struct({});
10
+ /** The always-on parse-validity rule. */
11
+ const parseValidity = {
12
+ id: "parse-validity",
13
+ check: (ctx) => [...ctx.document.errors.map((d) => new YamlLintDiagnostic({
14
+ rule: "parse-validity",
15
+ severity: "error",
16
+ message: d.message,
17
+ offset: d.offset,
18
+ length: d.length,
19
+ line: d.line,
20
+ character: d.character
21
+ })), ...ctx.document.warnings.map((d) => new YamlLintDiagnostic({
22
+ rule: "parse-validity",
23
+ severity: "warning",
24
+ message: d.message,
25
+ offset: d.offset,
26
+ length: d.length,
27
+ line: d.line,
28
+ character: d.character
29
+ }))]
30
+ };
31
+
32
+ //#endregion
33
+ export { parseValidity, parseValidityOptions };
@@ -0,0 +1,81 @@
1
+ import { YamlEdit } from "../../YamlEdit.js";
2
+ import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
3
+ import { positionAt, walkScalars } from "./util.js";
4
+ import { Schema } from "effect";
5
+
6
+ //#region src/internal/rules/quoted-strings.ts
7
+ /**
8
+ * Options for `quoted-strings`: the preferred `quoteType` (default
9
+ * `"double"`) and whether plain string scalars are `required` to be quoted
10
+ * at all (default `false` — only already-quoted scalars are policed).
11
+ */
12
+ const quotedStringsOptions = Schema.Struct({
13
+ severity: Schema.optionalKey(YamlLintSeverity),
14
+ quoteType: Schema.optionalKey(Schema.Literals(["single", "double"])),
15
+ required: Schema.optionalKey(Schema.Boolean)
16
+ });
17
+ /** A value-preserving requote/wrap edit, or undefined when none is safe. */
18
+ const safeQuoteFix = (ctx, scalar, quote) => {
19
+ if (scalar.tag !== void 0 || scalar.anchor !== void 0) return void 0;
20
+ if (typeof scalar.value !== "string") return void 0;
21
+ const raw = ctx.text.slice(scalar.offset, scalar.offset + scalar.length);
22
+ if (raw.includes("\n")) return void 0;
23
+ const inner = scalar.style === "plain" ? raw : raw.slice(1, -1);
24
+ if (inner !== scalar.value) return void 0;
25
+ if (inner.includes(quote)) return void 0;
26
+ if (quote === "\"" && inner.includes("\\")) return void 0;
27
+ return YamlEdit.make({
28
+ offset: scalar.offset,
29
+ length: scalar.length,
30
+ content: `${quote}${inner}${quote}`
31
+ });
32
+ };
33
+ /** Quote-style policy for string value scalars. */
34
+ const quotedStrings = {
35
+ id: "quoted-strings",
36
+ check: (ctx, options) => {
37
+ const opts = options ?? {};
38
+ const quoteType = opts.quoteType ?? "double";
39
+ const required = opts.required ?? false;
40
+ const quote = quoteType === "double" ? "\"" : "'";
41
+ const wrongStyle = quoteType === "double" ? "single-quoted" : "double-quoted";
42
+ const out = [];
43
+ walkScalars(ctx.document.contents, "root", (scalar, role) => {
44
+ if (role === "key") return;
45
+ if (typeof scalar.value !== "string") return;
46
+ if (scalar.style === wrongStyle) {
47
+ const fix = safeQuoteFix(ctx, scalar, quote);
48
+ const pos = positionAt(ctx.lines, scalar.offset);
49
+ out.push(new YamlLintDiagnostic({
50
+ rule: "quoted-strings",
51
+ severity: "error",
52
+ message: `String should use ${quoteType} quotes`,
53
+ offset: scalar.offset,
54
+ length: scalar.length,
55
+ line: pos.line,
56
+ character: pos.character,
57
+ ...fix !== void 0 ? { fix } : {}
58
+ }));
59
+ return;
60
+ }
61
+ if (required && scalar.style === "plain" && scalar.length > 0) {
62
+ const fix = safeQuoteFix(ctx, scalar, quote);
63
+ const pos = positionAt(ctx.lines, scalar.offset);
64
+ out.push(new YamlLintDiagnostic({
65
+ rule: "quoted-strings",
66
+ severity: "error",
67
+ message: `String should be quoted (${quoteType})`,
68
+ offset: scalar.offset,
69
+ length: scalar.length,
70
+ line: pos.line,
71
+ character: pos.character,
72
+ ...fix !== void 0 ? { fix } : {}
73
+ }));
74
+ }
75
+ });
76
+ return out;
77
+ }
78
+ };
79
+
80
+ //#endregion
81
+ export { quotedStrings, quotedStringsOptions };
@@ -0,0 +1,39 @@
1
+ import { YamlEdit } from "../../YamlEdit.js";
2
+ import { YamlLintDiagnostic, YamlLintSeverity } from "../../YamlLintRule.js";
3
+ import { insideScalarSpan } from "./util.js";
4
+ import { Schema } from "effect";
5
+
6
+ //#region src/internal/rules/trailing-spaces.ts
7
+ /** Options for `trailing-spaces` (severity only — nothing to tune). */
8
+ const trailingSpacesOptions = Schema.Struct({ severity: Schema.optionalKey(YamlLintSeverity) });
9
+ /** Trailing spaces or tabs at the end of a line, with a deleting fix. */
10
+ const trailingSpaces = {
11
+ id: "trailing-spaces",
12
+ check: (ctx) => {
13
+ const out = [];
14
+ for (const line of ctx.lines) {
15
+ const match = /[ \t]+$/.exec(line.text);
16
+ if (match === null) continue;
17
+ const runOffset = line.offset + match.index;
18
+ if (insideScalarSpan(ctx.tokens, runOffset)) continue;
19
+ out.push(new YamlLintDiagnostic({
20
+ rule: "trailing-spaces",
21
+ severity: "error",
22
+ message: "Trailing whitespace",
23
+ offset: runOffset,
24
+ length: match[0].length,
25
+ line: line.number,
26
+ character: match.index,
27
+ fix: YamlEdit.make({
28
+ offset: runOffset,
29
+ length: match[0].length,
30
+ content: ""
31
+ })
32
+ }));
33
+ }
34
+ return out;
35
+ }
36
+ };
37
+
38
+ //#endregion
39
+ export { trailingSpaces, trailingSpacesOptions };