@effected/yaml 0.25.0 → 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.
@@ -169,44 +169,22 @@ function columnAt(text, offset) {
169
169
  * included.
170
170
  */
171
171
  function withCommentFields(node, fields) {
172
+ const merged = mergedCommentFields(node, fields);
172
173
  if (node instanceof YamlScalar) return new YamlScalar({
173
- value: node.value,
174
- style: node.style,
175
- ...node.tag !== void 0 ? { tag: node.tag } : {},
176
- ...node.anchor !== void 0 ? { anchor: node.anchor } : {},
177
- ...mergedCommentFields(node, fields),
178
- ...node.chomp !== void 0 ? { chomp: node.chomp } : {},
179
- ...node.blockIndent !== void 0 ? { blockIndent: node.blockIndent } : {},
180
- ...node.raw !== void 0 ? { raw: node.raw } : {},
181
- ...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
182
- offset: node.offset,
183
- length: node.length
174
+ ...node,
175
+ ...merged
184
176
  });
185
177
  if (node instanceof YamlMap) return new YamlMap({
186
- items: node.items,
187
- style: node.style,
188
- ...node.tag !== void 0 ? { tag: node.tag } : {},
189
- ...node.anchor !== void 0 ? { anchor: node.anchor } : {},
190
- ...mergedCommentFields(node, fields),
191
- ...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
192
- offset: node.offset,
193
- length: node.length
178
+ ...node,
179
+ ...merged
194
180
  });
195
181
  if (node instanceof YamlSeq) return new YamlSeq({
196
- items: node.items,
197
- style: node.style,
198
- ...node.tag !== void 0 ? { tag: node.tag } : {},
199
- ...node.anchor !== void 0 ? { anchor: node.anchor } : {},
200
- ...mergedCommentFields(node, fields),
201
- ...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
202
- offset: node.offset,
203
- length: node.length
182
+ ...node,
183
+ ...merged
204
184
  });
205
185
  return new YamlAlias({
206
- name: node.name,
207
- ...mergedCommentFields(node, fields),
208
- offset: node.offset,
209
- length: node.length
186
+ ...node,
187
+ ...merged
210
188
  });
211
189
  }
212
190
  function mergedCommentFields(existing, incoming) {
@@ -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("\n");
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: [],
@@ -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(text) {
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(text) {
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
- 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
- }));
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
- if (next === void 0 || next === "\n" || next === "\r" || next === "#") continue;
57
- if (after > maxAfter) out.push(new YamlLintDiagnostic({
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: after - maxAfter,
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.endsWith("\n");
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.endsWith("\n") ? "...\n" : "\n...\n"
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 !== "" || insideScalarSpan(ctx.tokens, line.offset)) {
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 !== "" || insideScalarSpan(ctx.tokens, line.offset)) {
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.endsWith("\n")) return [];
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) 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({
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 - maxAfter,
43
- content: ""
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",
@@ -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.25.0",
3
+ "version": "0.26.0",
4
4
  "private": false,
5
- "description": "Zero-dependency YAML parsing, editing, formatting and linting as Effect schemas.",
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
  },
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.59.1"
8
+ "packageVersion": "7.59.4"
9
9
  }
10
10
  ]
11
11
  }