@telorun/ide-support 0.11.3 → 0.12.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.
@@ -0,0 +1,40 @@
1
+ /** Rendering a diagnostic's repair back into YAML source.
2
+ *
3
+ * A `fix.replacement` is a bare VALUE — a CEL expression, a kind name — while
4
+ * the span it replaces is the value node as written, which includes the
5
+ * scalar's quotes. (The YAML tag sits outside that span, so `!cel` survives a
6
+ * replacement without anything doing it on purpose.) Writing the bare value
7
+ * into a quoted span therefore strips the quotes, and a CEL expression is
8
+ * exactly the kind of text that stops being one scalar once unquoted: a
9
+ * `: ` inside it starts a mapping, a trailing `#` starts a comment, a leading
10
+ * `%` or `&` is an indicator.
11
+ *
12
+ * So the replacement is re-quoted in the style the author used, and promoted
13
+ * to double quotes when a plain scalar could not survive the round-trip. This
14
+ * lives here rather than in one editor because both surfaces — the VS Code
15
+ * extension's quick fix and the Tauri editor's — apply the same repair to the
16
+ * same source, and the two must not disagree about how a value is written. */
17
+ /** Whether `value` can be written as a plain scalar without changing meaning. */
18
+ export declare function isPlainSafe(value: string): boolean;
19
+ /** Quote style of the source text a fix is replacing. */
20
+ export type QuoteStyle = "double" | "single" | "plain";
21
+ export declare function quoteStyleOf(source: string): QuoteStyle;
22
+ /** Render `replacement` so it occupies `originalSource`'s span as the same
23
+ * scalar the author would have written by hand, or `undefined` when the span
24
+ * cannot be rewritten safely.
25
+ *
26
+ * A plain original is kept plain when it can be — rewriting `Run.Sequenc` to
27
+ * `"Run.Sequence"` would be a correct but noisy diff on a kind name — and
28
+ * promoted to double quotes when the new value would not survive unquoted.
29
+ *
30
+ * **A multi-line span is refused.** A block scalar's span covers its `|`/`>-`
31
+ * indicator AND its trailing newline, so writing a single-line scalar over it
32
+ * deletes the line break that ended the mapping entry and glues the next key
33
+ * onto the value — the document stops parsing. Re-emitting a block scalar
34
+ * correctly needs the node's indentation, which no consumer of this function
35
+ * has. A multi-line REPLACEMENT is refused for the mirror reason: its
36
+ * continuation lines would land at column 0, which is not a legal mapping
37
+ * value. `DiagnosticFix` promises a repair that can be applied without review,
38
+ * so the only honest answer for these is no repair. */
39
+ export declare function renderFixReplacement(originalSource: string, replacement: string): string | undefined;
40
+ //# sourceMappingURL=fix-edit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix-edit.d.ts","sourceRoot":"","sources":["../../src/diagnostics/fix-edit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;+EAe+E;AAS/E,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAUlD;AAED,yDAAyD;AACzD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEvD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAIvD;AAED;;;;;;;;;;;;;;;;wDAgBwD;AACxD,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAcpB"}
@@ -0,0 +1,78 @@
1
+ /** Rendering a diagnostic's repair back into YAML source.
2
+ *
3
+ * A `fix.replacement` is a bare VALUE — a CEL expression, a kind name — while
4
+ * the span it replaces is the value node as written, which includes the
5
+ * scalar's quotes. (The YAML tag sits outside that span, so `!cel` survives a
6
+ * replacement without anything doing it on purpose.) Writing the bare value
7
+ * into a quoted span therefore strips the quotes, and a CEL expression is
8
+ * exactly the kind of text that stops being one scalar once unquoted: a
9
+ * `: ` inside it starts a mapping, a trailing `#` starts a comment, a leading
10
+ * `%` or `&` is an indicator.
11
+ *
12
+ * So the replacement is re-quoted in the style the author used, and promoted
13
+ * to double quotes when a plain scalar could not survive the round-trip. This
14
+ * lives here rather than in one editor because both surfaces — the VS Code
15
+ * extension's quick fix and the Tauri editor's — apply the same repair to the
16
+ * same source, and the two must not disagree about how a value is written. */
17
+ /** Characters that make a plain (unquoted) YAML scalar reparse as something
18
+ * else. `-` and `?` are indicators only when followed by a space, so they are
19
+ * handled by the leading-token check rather than listed here. */
20
+ const PLAIN_UNSAFE_LEAD = new Set([
21
+ "&", "*", "!", "|", ">", "%", "@", "`", "#", "'", '"', "{", "[", "}", "]", ",",
22
+ ]);
23
+ /** Whether `value` can be written as a plain scalar without changing meaning. */
24
+ export function isPlainSafe(value) {
25
+ if (value === "" || value.trim() !== value)
26
+ return false;
27
+ if (PLAIN_UNSAFE_LEAD.has(value[0]))
28
+ return false;
29
+ // `-`/`?`/`:` lead only matter when a space follows — `-x` is a scalar,
30
+ // `- x` is a sequence entry.
31
+ if (/^[-?:]\s/.test(value))
32
+ return false;
33
+ // A colon-space anywhere opens a mapping; a space-hash opens a comment.
34
+ if (value.includes(": ") || value.includes(" #"))
35
+ return false;
36
+ if (value.endsWith(":"))
37
+ return false;
38
+ return !/[\n\r]/.test(value);
39
+ }
40
+ export function quoteStyleOf(source) {
41
+ if (source.length >= 2 && source.startsWith('"') && source.endsWith('"'))
42
+ return "double";
43
+ if (source.length >= 2 && source.startsWith("'") && source.endsWith("'"))
44
+ return "single";
45
+ return "plain";
46
+ }
47
+ /** Render `replacement` so it occupies `originalSource`'s span as the same
48
+ * scalar the author would have written by hand, or `undefined` when the span
49
+ * cannot be rewritten safely.
50
+ *
51
+ * A plain original is kept plain when it can be — rewriting `Run.Sequenc` to
52
+ * `"Run.Sequence"` would be a correct but noisy diff on a kind name — and
53
+ * promoted to double quotes when the new value would not survive unquoted.
54
+ *
55
+ * **A multi-line span is refused.** A block scalar's span covers its `|`/`>-`
56
+ * indicator AND its trailing newline, so writing a single-line scalar over it
57
+ * deletes the line break that ended the mapping entry and glues the next key
58
+ * onto the value — the document stops parsing. Re-emitting a block scalar
59
+ * correctly needs the node's indentation, which no consumer of this function
60
+ * has. A multi-line REPLACEMENT is refused for the mirror reason: its
61
+ * continuation lines would land at column 0, which is not a legal mapping
62
+ * value. `DiagnosticFix` promises a repair that can be applied without review,
63
+ * so the only honest answer for these is no repair. */
64
+ export function renderFixReplacement(originalSource, replacement) {
65
+ if (/[\n\r]/.test(originalSource) || /[\n\r]/.test(replacement))
66
+ return undefined;
67
+ const style = quoteStyleOf(originalSource);
68
+ if (style === "single") {
69
+ // A single-quoted YAML scalar escapes only the quote, by doubling it. CEL
70
+ // string literals use single quotes constantly, so this is the common case
71
+ // for an expression written in a single-quoted scalar.
72
+ return `'${replacement.replaceAll("'", "''")}'`;
73
+ }
74
+ if (style === "double" || !isPlainSafe(replacement)) {
75
+ return `"${replacement.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
76
+ }
77
+ return replacement;
78
+ }
@@ -1,4 +1,5 @@
1
1
  export { findPositions } from "./find-positions.js";
2
+ export { isPlainSafe, quoteStyleOf, renderFixReplacement, type QuoteStyle, } from "./fix-edit.js";
2
3
  export { assembleGraphDiagnostics, compromisedFiles } from "./graph-diagnostics.js";
3
4
  export { normalizeDiagnostic } from "./normalize.js";
4
5
  export { resolveRange } from "./range-resolver.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diagnostics/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diagnostics/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACL,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,KAAK,UAAU,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export { findPositions } from "./find-positions.js";
2
+ export { isPlainSafe, quoteStyleOf, renderFixReplacement, } from "./fix-edit.js";
2
3
  export { assembleGraphDiagnostics, compromisedFiles } from "./graph-diagnostics.js";
3
4
  export { normalizeDiagnostic } from "./normalize.js";
4
5
  export { resolveRange } from "./range-resolver.js";
@@ -1,11 +1,15 @@
1
- import type { AnalysisDiagnostic } from "@telorun/analyzer";
1
+ import { type AnalysisDiagnostic } from "@telorun/analyzer";
2
2
  import type { DiagnosticContext, NormalizedDiagnostic } from "../types.js";
3
3
  /** Converts a raw analyzer diagnostic into a host-ready shape:
4
4
  * - Guarantees `range` and `severity`.
5
- * - Surfaces `data.suggestedKind` (stamped by the analyzer for UNDEFINED_KIND)
6
- * as a structured `{ kind: "replace-kind", replacement }` entry in
7
- * `suggestions`, which editor hosts can wire into CodeActions.
8
- * Does not rewrite the message the analyzer already formatted the human-readable
9
- * "Did you mean '…'?" hint, keeping CLI and IDE output in sync. */
5
+ * - Surfaces the analyzer's `fix` stamp as a structured `suggestions` entry
6
+ * hosts wire into a CodeAction.
7
+ * Does not rewrite the message the analyzer already formatted the
8
+ * human-readable hint, keeping CLI and IDE output in sync.
9
+ *
10
+ * One suggestion kind, not one per producer: an unknown kind name and a
11
+ * mis-called CEL function are the same gesture at the host (replace the value
12
+ * at this range), and a second kind would mean a second action path in every
13
+ * editor for no difference the user can see. */
10
14
  export declare function normalizeDiagnostic(d: AnalysisDiagnostic, ctx: DiagnosticContext): NormalizedDiagnostic;
11
15
  //# sourceMappingURL=normalize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/diagnostics/normalize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAI3E;;;;;;oEAMoE;AACpE,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,kBAAkB,EACrB,GAAG,EAAE,iBAAiB,GACrB,oBAAoB,CAetB"}
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/diagnostics/normalize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAI3E;;;;;;;;;;iDAUiD;AACjD,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,kBAAkB,EACrB,GAAG,EAAE,iBAAiB,GACrB,oBAAoB,CAatB"}
@@ -1,17 +1,20 @@
1
+ import { diagnosticFix } from "@telorun/analyzer";
1
2
  import { resolveRange } from "./range-resolver.js";
2
3
  import { resolveSeverity } from "./severity.js";
3
4
  /** Converts a raw analyzer diagnostic into a host-ready shape:
4
5
  * - Guarantees `range` and `severity`.
5
- * - Surfaces `data.suggestedKind` (stamped by the analyzer for UNDEFINED_KIND)
6
- * as a structured `{ kind: "replace-kind", replacement }` entry in
7
- * `suggestions`, which editor hosts can wire into CodeActions.
8
- * Does not rewrite the message the analyzer already formatted the human-readable
9
- * "Did you mean '…'?" hint, keeping CLI and IDE output in sync. */
6
+ * - Surfaces the analyzer's `fix` stamp as a structured `suggestions` entry
7
+ * hosts wire into a CodeAction.
8
+ * Does not rewrite the message the analyzer already formatted the
9
+ * human-readable hint, keeping CLI and IDE output in sync.
10
+ *
11
+ * One suggestion kind, not one per producer: an unknown kind name and a
12
+ * mis-called CEL function are the same gesture at the host (replace the value
13
+ * at this range), and a second kind would mean a second action path in every
14
+ * editor for no difference the user can see. */
10
15
  export function normalizeDiagnostic(d, ctx) {
11
- const suggestedKind = d.data?.suggestedKind;
12
- const suggestions = suggestedKind
13
- ? [{ kind: "replace-kind", replacement: suggestedKind }]
14
- : undefined;
16
+ const fix = diagnosticFix(d);
17
+ const suggestions = fix ? [{ kind: "replace", replacement: fix.replacement }] : undefined;
15
18
  return {
16
19
  range: resolveRange(d, ctx),
17
20
  severity: resolveSeverity(d),
package/dist/types.d.ts CHANGED
@@ -97,8 +97,10 @@ export interface NormalizedDiagnostic {
97
97
  code: string;
98
98
  source: string;
99
99
  message: string;
100
+ /** Mechanically applicable repairs. `replacement` is the whole corrected
101
+ * value at the diagnostic's range — apply it by replacing that range. */
100
102
  suggestions?: Array<{
101
- kind: "replace-kind";
103
+ kind: "replace";
102
104
  replacement: string;
103
105
  }>;
104
106
  /** Preserved verbatim from the source `AnalysisDiagnostic`. Carries
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/ide-support",
3
- "version": "0.11.3",
3
+ "version": "0.12.0",
4
4
  "description": "Editor-host-agnostic IDE support (completions, diagnostic normalization) for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -36,7 +36,7 @@
36
36
  "src/**"
37
37
  ],
38
38
  "dependencies": {
39
- "@telorun/analyzer": "0.56.1"
39
+ "@telorun/analyzer": "0.57.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^20.0.0",
@@ -0,0 +1,81 @@
1
+ /** Rendering a diagnostic's repair back into YAML source.
2
+ *
3
+ * A `fix.replacement` is a bare VALUE — a CEL expression, a kind name — while
4
+ * the span it replaces is the value node as written, which includes the
5
+ * scalar's quotes. (The YAML tag sits outside that span, so `!cel` survives a
6
+ * replacement without anything doing it on purpose.) Writing the bare value
7
+ * into a quoted span therefore strips the quotes, and a CEL expression is
8
+ * exactly the kind of text that stops being one scalar once unquoted: a
9
+ * `: ` inside it starts a mapping, a trailing `#` starts a comment, a leading
10
+ * `%` or `&` is an indicator.
11
+ *
12
+ * So the replacement is re-quoted in the style the author used, and promoted
13
+ * to double quotes when a plain scalar could not survive the round-trip. This
14
+ * lives here rather than in one editor because both surfaces — the VS Code
15
+ * extension's quick fix and the Tauri editor's — apply the same repair to the
16
+ * same source, and the two must not disagree about how a value is written. */
17
+
18
+ /** Characters that make a plain (unquoted) YAML scalar reparse as something
19
+ * else. `-` and `?` are indicators only when followed by a space, so they are
20
+ * handled by the leading-token check rather than listed here. */
21
+ const PLAIN_UNSAFE_LEAD = new Set([
22
+ "&", "*", "!", "|", ">", "%", "@", "`", "#", "'", '"', "{", "[", "}", "]", ",",
23
+ ]);
24
+
25
+ /** Whether `value` can be written as a plain scalar without changing meaning. */
26
+ export function isPlainSafe(value: string): boolean {
27
+ if (value === "" || value.trim() !== value) return false;
28
+ if (PLAIN_UNSAFE_LEAD.has(value[0]!)) return false;
29
+ // `-`/`?`/`:` lead only matter when a space follows — `-x` is a scalar,
30
+ // `- x` is a sequence entry.
31
+ if (/^[-?:]\s/.test(value)) return false;
32
+ // A colon-space anywhere opens a mapping; a space-hash opens a comment.
33
+ if (value.includes(": ") || value.includes(" #")) return false;
34
+ if (value.endsWith(":")) return false;
35
+ return !/[\n\r]/.test(value);
36
+ }
37
+
38
+ /** Quote style of the source text a fix is replacing. */
39
+ export type QuoteStyle = "double" | "single" | "plain";
40
+
41
+ export function quoteStyleOf(source: string): QuoteStyle {
42
+ if (source.length >= 2 && source.startsWith('"') && source.endsWith('"')) return "double";
43
+ if (source.length >= 2 && source.startsWith("'") && source.endsWith("'")) return "single";
44
+ return "plain";
45
+ }
46
+
47
+ /** Render `replacement` so it occupies `originalSource`'s span as the same
48
+ * scalar the author would have written by hand, or `undefined` when the span
49
+ * cannot be rewritten safely.
50
+ *
51
+ * A plain original is kept plain when it can be — rewriting `Run.Sequenc` to
52
+ * `"Run.Sequence"` would be a correct but noisy diff on a kind name — and
53
+ * promoted to double quotes when the new value would not survive unquoted.
54
+ *
55
+ * **A multi-line span is refused.** A block scalar's span covers its `|`/`>-`
56
+ * indicator AND its trailing newline, so writing a single-line scalar over it
57
+ * deletes the line break that ended the mapping entry and glues the next key
58
+ * onto the value — the document stops parsing. Re-emitting a block scalar
59
+ * correctly needs the node's indentation, which no consumer of this function
60
+ * has. A multi-line REPLACEMENT is refused for the mirror reason: its
61
+ * continuation lines would land at column 0, which is not a legal mapping
62
+ * value. `DiagnosticFix` promises a repair that can be applied without review,
63
+ * so the only honest answer for these is no repair. */
64
+ export function renderFixReplacement(
65
+ originalSource: string,
66
+ replacement: string,
67
+ ): string | undefined {
68
+ if (/[\n\r]/.test(originalSource) || /[\n\r]/.test(replacement)) return undefined;
69
+ const style = quoteStyleOf(originalSource);
70
+
71
+ if (style === "single") {
72
+ // A single-quoted YAML scalar escapes only the quote, by doubling it. CEL
73
+ // string literals use single quotes constantly, so this is the common case
74
+ // for an expression written in a single-quoted scalar.
75
+ return `'${replacement.replaceAll("'", "''")}'`;
76
+ }
77
+ if (style === "double" || !isPlainSafe(replacement)) {
78
+ return `"${replacement.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
79
+ }
80
+ return replacement;
81
+ }
@@ -1,4 +1,10 @@
1
1
  export { findPositions } from "./find-positions.js";
2
+ export {
3
+ isPlainSafe,
4
+ quoteStyleOf,
5
+ renderFixReplacement,
6
+ type QuoteStyle,
7
+ } from "./fix-edit.js";
2
8
  export { assembleGraphDiagnostics, compromisedFiles } from "./graph-diagnostics.js";
3
9
  export { normalizeDiagnostic } from "./normalize.js";
4
10
  export { resolveRange } from "./range-resolver.js";
@@ -1,23 +1,25 @@
1
- import type { AnalysisDiagnostic } from "@telorun/analyzer";
1
+ import { diagnosticFix, type AnalysisDiagnostic } from "@telorun/analyzer";
2
2
  import type { DiagnosticContext, NormalizedDiagnostic } from "../types.js";
3
3
  import { resolveRange } from "./range-resolver.js";
4
4
  import { resolveSeverity } from "./severity.js";
5
5
 
6
6
  /** Converts a raw analyzer diagnostic into a host-ready shape:
7
7
  * - Guarantees `range` and `severity`.
8
- * - Surfaces `data.suggestedKind` (stamped by the analyzer for UNDEFINED_KIND)
9
- * as a structured `{ kind: "replace-kind", replacement }` entry in
10
- * `suggestions`, which editor hosts can wire into CodeActions.
11
- * Does not rewrite the message the analyzer already formatted the human-readable
12
- * "Did you mean '…'?" hint, keeping CLI and IDE output in sync. */
8
+ * - Surfaces the analyzer's `fix` stamp as a structured `suggestions` entry
9
+ * hosts wire into a CodeAction.
10
+ * Does not rewrite the message the analyzer already formatted the
11
+ * human-readable hint, keeping CLI and IDE output in sync.
12
+ *
13
+ * One suggestion kind, not one per producer: an unknown kind name and a
14
+ * mis-called CEL function are the same gesture at the host (replace the value
15
+ * at this range), and a second kind would mean a second action path in every
16
+ * editor for no difference the user can see. */
13
17
  export function normalizeDiagnostic(
14
18
  d: AnalysisDiagnostic,
15
19
  ctx: DiagnosticContext,
16
20
  ): NormalizedDiagnostic {
17
- const suggestedKind = (d.data as { suggestedKind?: string } | undefined)?.suggestedKind;
18
- const suggestions = suggestedKind
19
- ? [{ kind: "replace-kind" as const, replacement: suggestedKind }]
20
- : undefined;
21
+ const fix = diagnosticFix(d);
22
+ const suggestions = fix ? [{ kind: "replace" as const, replacement: fix.replacement }] : undefined;
21
23
 
22
24
  return {
23
25
  range: resolveRange(d, ctx),
package/src/types.ts CHANGED
@@ -124,7 +124,9 @@ export interface NormalizedDiagnostic {
124
124
  code: string;
125
125
  source: string;
126
126
  message: string;
127
- suggestions?: Array<{ kind: "replace-kind"; replacement: string }>;
127
+ /** Mechanically applicable repairs. `replacement` is the whole corrected
128
+ * value at the diagnostic's range — apply it by replacing that range. */
129
+ suggestions?: Array<{ kind: "replace"; replacement: string }>;
128
130
  /** Preserved verbatim from the source `AnalysisDiagnostic`. Carries
129
131
  * resource/path stamps that downstream UIs (popovers, "at <path>" hints,
130
132
  * CodeAction wiring) read after normalization. Opaque on purpose so this