@effected/yaml 0.7.0 → 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 (39) hide show
  1. package/README.md +78 -7
  2. package/Yaml.js +80 -32
  3. package/YamlDocument.js +22 -3
  4. package/YamlFormat.js +170 -19
  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 +48 -11
  18. package/internal/composer/state.js +16 -2
  19. package/internal/cst-parser.js +2 -1
  20. package/internal/diagnostics.js +12 -3
  21. package/internal/fold.js +22 -10
  22. package/internal/rules/catalog.js +39 -0
  23. package/internal/rules/colon-spacing.js +77 -0
  24. package/internal/rules/comments-spacing.js +68 -0
  25. package/internal/rules/document-end.js +65 -0
  26. package/internal/rules/document-start.js +64 -0
  27. package/internal/rules/empty-lines.js +70 -0
  28. package/internal/rules/eof-newline.js +33 -0
  29. package/internal/rules/hyphen-spacing.js +52 -0
  30. package/internal/rules/indentation.js +103 -0
  31. package/internal/rules/key-duplicates.js +49 -0
  32. package/internal/rules/line-length.js +36 -0
  33. package/internal/rules/parse-validity.js +33 -0
  34. package/internal/rules/quoted-strings.js +81 -0
  35. package/internal/rules/trailing-spaces.js +39 -0
  36. package/internal/rules/truthy.js +84 -0
  37. package/internal/rules/util.js +92 -0
  38. package/internal/stringifier.js +266 -54
  39. package/package.json +3 -2
@@ -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 };
@@ -0,0 +1,84 @@
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/truthy.ts
7
+ /**
8
+ * Options for `truthy`: the `allowed` boolean spellings (default
9
+ * `["true", "false"]`) and whether mapping keys are checked (`checkKeys`,
10
+ * default `true` — the workflow `on:` key is the point).
11
+ */
12
+ const truthyOptions = Schema.Struct({
13
+ severity: Schema.optionalKey(YamlLintSeverity),
14
+ allowed: Schema.optionalKey(Schema.Array(Schema.String)),
15
+ checkKeys: Schema.optionalKey(Schema.Boolean)
16
+ });
17
+ /** The YAML 1.1 boolean family, per spelling case the 1.1 grammar admits. */
18
+ const TRUTHY = /* @__PURE__ */ new Set([
19
+ "yes",
20
+ "Yes",
21
+ "YES",
22
+ "no",
23
+ "No",
24
+ "NO",
25
+ "on",
26
+ "On",
27
+ "ON",
28
+ "off",
29
+ "Off",
30
+ "OFF",
31
+ "true",
32
+ "True",
33
+ "TRUE",
34
+ "false",
35
+ "False",
36
+ "FALSE"
37
+ ]);
38
+ const TRUE_SET = /* @__PURE__ */ new Set([
39
+ "yes",
40
+ "on",
41
+ "true"
42
+ ]);
43
+ /** YAML 1.1 truthy spellings outside the allowed list. */
44
+ const truthy = {
45
+ id: "truthy",
46
+ check: (ctx, options) => {
47
+ const opts = options ?? {};
48
+ const allowed = new Set(opts.allowed ?? ["true", "false"]);
49
+ const checkKeys = opts.checkKeys ?? true;
50
+ const out = [];
51
+ walkScalars(ctx.document.contents, "root", (scalar, role) => {
52
+ if (role === "key" && !checkKeys) return;
53
+ if (scalar.style !== "plain" || scalar.tag !== void 0) return;
54
+ const raw = ctx.text.slice(scalar.offset, scalar.offset + scalar.length);
55
+ if (!TRUTHY.has(raw) || allowed.has(raw)) return;
56
+ const pos = positionAt(ctx.lines, scalar.offset);
57
+ const isBool = typeof scalar.value === "boolean";
58
+ const respell = TRUE_SET.has(raw.toLowerCase()) ? "true" : "false";
59
+ const fix = isBool ? allowed.has(respell) ? YamlEdit.make({
60
+ offset: scalar.offset,
61
+ length: scalar.length,
62
+ content: respell
63
+ }) : void 0 : YamlEdit.make({
64
+ offset: scalar.offset,
65
+ length: scalar.length,
66
+ content: `"${raw}"`
67
+ });
68
+ out.push(new YamlLintDiagnostic({
69
+ rule: "truthy",
70
+ severity: "error",
71
+ message: `Truthy value "${raw}" is not in the allowed spellings`,
72
+ offset: scalar.offset,
73
+ length: scalar.length,
74
+ line: pos.line,
75
+ character: pos.character,
76
+ ...fix !== void 0 ? { fix } : {}
77
+ }));
78
+ });
79
+ return out;
80
+ }
81
+ };
82
+
83
+ //#endregion
84
+ export { truthy, truthyOptions };
@@ -0,0 +1,92 @@
1
+ import { YamlMap, YamlScalar, YamlSeq } from "../../YamlNode.js";
2
+ import { Schema } from "effect";
3
+
4
+ //#region src/internal/rules/util.ts
5
+ /**
6
+ * A non-negative integer — the shape every numeric rule option takes.
7
+ * Rejects NaN, negatives and fractions with a message naming the constraint;
8
+ * the config layer's wrapper names the rule and the field.
9
+ */
10
+ const nonNegativeIntegerOption = Schema.Number.check(Schema.makeFilter((n) => Number.isInteger(n) && n >= 0 ? void 0 : "Expected a non-negative integer"));
11
+ /**
12
+ * A positive integer — for the `maxSpacesAfter` options, where `0` would make
13
+ * the fix delete the separation space after an indicator and fuse it with its
14
+ * content (`- item` → `-item`, `a: val` → `a:val` — different tokens, not a
15
+ * spacing change).
16
+ */
17
+ const positiveIntegerOption = Schema.Number.check(Schema.makeFilter((n) => Number.isInteger(n) && n >= 1 ? void 0 : "Expected an integer greater than or equal to 1"));
18
+ /** Depth-first walk over every scalar node with its structural role. */
19
+ function walkScalars(node, role, visit) {
20
+ if (node === null) return;
21
+ if (node instanceof YamlScalar) {
22
+ visit(node, role);
23
+ return;
24
+ }
25
+ if (node instanceof YamlMap) {
26
+ for (const pair of node.items) {
27
+ walkScalars(pair.key, "key", visit);
28
+ walkScalars(pair.value, "value", visit);
29
+ }
30
+ return;
31
+ }
32
+ if (node instanceof YamlSeq) for (const item of node.items) walkScalars(item, "item", visit);
33
+ }
34
+ /**
35
+ * True when the first content of a line is the CONTINUATION of a scalar
36
+ * token that began on an earlier line — block-scalar bodies and multi-line
37
+ * plain/quoted scalars. The indentation rule skips such lines: their layout
38
+ * is the value's, not block structure's.
39
+ */
40
+ function isScalarContinuationLine(tokens, lineOffset, probeOffset) {
41
+ const token = coveringToken(tokens, probeOffset);
42
+ return token !== void 0 && token.kind === "scalar" && token.offset < lineOffset;
43
+ }
44
+ /** The token whose span covers `offset`, when one does. */
45
+ function coveringToken(tokens, offset) {
46
+ let lo = 0;
47
+ let hi = tokens.length - 1;
48
+ while (lo <= hi) {
49
+ const mid = lo + hi >> 1;
50
+ const token = tokens[mid];
51
+ if (offset < token.offset) hi = mid - 1;
52
+ else if (offset >= token.offset + token.length) lo = mid + 1;
53
+ else return token;
54
+ }
55
+ }
56
+ /**
57
+ * True when `offset` falls inside a scalar token's span. Layout rules use
58
+ * this to stay off scalar CONTENT — trailing whitespace or blank lines
59
+ * inside a block scalar are part of the parsed value, and a lint layer that
60
+ * edits content under the banner of layout is corrupting, not fixing.
61
+ */
62
+ function insideScalarSpan(tokens, offset) {
63
+ return coveringToken(tokens, offset)?.kind === "scalar";
64
+ }
65
+ /**
66
+ * The line containing `offset` and the character index within it — a binary
67
+ * search over the ordered `LintLine` array (lines are ordered by `offset`,
68
+ * the same invariant {@link coveringToken} rests on for tokens).
69
+ */
70
+ function positionAt(lines, offset) {
71
+ let lo = 0;
72
+ let hi = lines.length - 1;
73
+ let found;
74
+ while (lo <= hi) {
75
+ const mid = lo + hi >> 1;
76
+ const line = lines[mid];
77
+ if (line.offset <= offset) {
78
+ found = line;
79
+ lo = mid + 1;
80
+ } else hi = mid - 1;
81
+ }
82
+ return found === void 0 ? {
83
+ line: 0,
84
+ character: 0
85
+ } : {
86
+ line: found.number,
87
+ character: offset - found.offset
88
+ };
89
+ }
90
+
91
+ //#endregion
92
+ export { coveringToken, insideScalarSpan, isScalarContinuationLine, nonNegativeIntegerOption, positionAt, positiveIntegerOption, walkScalars };