@amritk/lint 0.3.2 → 0.4.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/AI.md +55 -0
- package/dist/core/document.js +12 -10
- package/dist/core/formats.js +10 -13
- package/dist/core/glob.js +96 -118
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +30 -11
- package/dist/core/jsonpath.js +487 -561
- package/dist/core/lint.js +78 -85
- package/dist/core/plugin.js +21 -29
- package/dist/core/pointers.js +107 -151
- package/dist/core/ruleset.js +0 -0
- package/dist/core/runner.js +214 -272
- package/dist/core/types.js +4 -1
- package/dist/core/validate-ruleset.js +98 -112
- package/dist/fix/apply.d.ts +1 -1
- package/dist/fix/apply.js +58 -96
- package/dist/fix/index.js +7 -2
- package/dist/fix/plugin.js +15 -20
- package/dist/fix/types.d.ts +1 -1
- package/dist/functions/alphabetical.d.ts +1 -1
- package/dist/functions/alphabetical.js +39 -50
- package/dist/functions/casing.d.ts +1 -1
- package/dist/functions/casing.js +39 -45
- package/dist/functions/defined.d.ts +1 -1
- package/dist/functions/defined.js +7 -5
- package/dist/functions/enumeration.d.ts +1 -1
- package/dist/functions/enumeration.js +15 -25
- package/dist/functions/falsy.d.ts +1 -1
- package/dist/functions/falsy.js +7 -5
- package/dist/functions/index.d.ts +1 -1
- package/dist/functions/index.js +60 -44
- package/dist/functions/length.d.ts +1 -1
- package/dist/functions/length.js +22 -32
- package/dist/functions/or.d.ts +1 -1
- package/dist/functions/or.js +18 -24
- package/dist/functions/pattern.d.ts +1 -1
- package/dist/functions/pattern.js +39 -49
- package/dist/functions/schema.d.ts +1 -1
- package/dist/functions/schema.js +93 -119
- package/dist/functions/truthy.d.ts +1 -1
- package/dist/functions/truthy.js +7 -5
- package/dist/functions/typed-enum.d.ts +1 -1
- package/dist/functions/typed-enum.js +33 -36
- package/dist/functions/undefined.d.ts +1 -1
- package/dist/functions/undefined.js +7 -8
- package/dist/functions/unreferenced-reusable-object.d.ts +1 -1
- package/dist/functions/unreferenced-reusable-object.js +35 -47
- package/dist/functions/xor.d.ts +1 -1
- package/dist/functions/xor.js +13 -16
- package/dist/index.d.ts +17 -4
- package/dist/index.js +149 -164
- package/dist/parsers/edit-model.js +316 -444
- package/dist/parsers/index.d.ts +1 -1
- package/dist/parsers/index.js +23 -19
- package/dist/parsers/json.js +39 -37
- package/dist/parsers/lines.js +23 -26
- package/dist/parsers/types.js +9 -7
- package/dist/parsers/yaml.js +137 -204
- package/dist/rules/openapi/fixers.js +166 -221
- package/dist/rules/openapi/formats.js +26 -27
- package/dist/rules/openapi/functions/example-validation.d.ts +1 -1
- package/dist/rules/openapi/functions/example-validation.js +98 -138
- package/dist/rules/openapi/functions/helpers.js +8 -10
- package/dist/rules/openapi/functions/index.d.ts +1 -1
- package/dist/rules/openapi/functions/index.js +98 -72
- package/dist/rules/openapi/functions/oas-additional-operations.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-additional-operations.js +16 -22
- package/dist/rules/openapi/functions/oas-discriminator.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-discriminator.js +24 -22
- package/dist/rules/openapi/functions/oas-example-external-value.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-example-external-value.js +13 -21
- package/dist/rules/openapi/functions/oas-example-value.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-example-value.js +24 -27
- package/dist/rules/openapi/functions/oas-mutually-exclusive.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-mutually-exclusive.js +15 -19
- package/dist/rules/openapi/functions/oas-no-nullable.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-no-nullable.js +13 -21
- package/dist/rules/openapi/functions/oas-op-form-data-consume-check.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-op-form-data-consume-check.js +21 -19
- package/dist/rules/openapi/functions/oas-op-id-unique.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-op-id-unique.js +27 -27
- package/dist/rules/openapi/functions/oas-op-params.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-op-params.js +36 -42
- package/dist/rules/openapi/functions/oas-op-security-defined.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-op-security-defined.js +40 -39
- package/dist/rules/openapi/functions/oas-op-success-response.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-op-success-response.js +11 -13
- package/dist/rules/openapi/functions/oas-path-param.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-path-param.js +75 -96
- package/dist/rules/openapi/functions/oas-schema-example-deprecated.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-schema-example-deprecated.js +33 -40
- package/dist/rules/openapi/functions/oas-schema.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-schema.js +9 -14
- package/dist/rules/openapi/functions/oas-server-name-unique.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-server-name-unique.js +21 -19
- package/dist/rules/openapi/functions/oas-server-variables.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-server-variables.js +45 -49
- package/dist/rules/openapi/functions/oas-tag-defined.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-tag-defined.js +20 -20
- package/dist/rules/openapi/functions/oas-tag-kind.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-tag-kind.js +16 -16
- package/dist/rules/openapi/functions/oas-tag-parent-defined.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-tag-parent-defined.js +40 -43
- package/dist/rules/openapi/functions/oas-tags-unique.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-tags-unique.js +18 -16
- package/dist/rules/openapi/functions/oas-unused-component.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-unused-component.js +48 -58
- package/dist/rules/openapi/functions/ref-siblings.d.ts +1 -1
- package/dist/rules/openapi/functions/ref-siblings.js +13 -11
- package/dist/rules/openapi/index.d.ts +2 -1
- package/dist/rules/openapi/index.js +99 -117
- package/dist/rules/openapi/oas.d.ts +1 -1
- package/dist/rules/openapi/oas.js +524 -536
- package/dist/rules/openapi/schemas/index.js +17 -33
- package/dist/rules/openapi/schemas/oas20.json +1 -1592
- package/dist/rules/openapi/schemas/oas30.json +1 -1651
- package/dist/rules/openapi/schemas/oas31.json +1 -1412
- package/dist/rules/openapi/schemas/oas32.json +1 -1684
- package/package.json +11 -6
|
@@ -1,495 +1,367 @@
|
|
|
1
|
-
import { isAlias, isMap, isScalar, isSeq, parseDocument } from
|
|
2
|
-
import { applyEdits, findNodeAtLocation, modify, parseTree } from
|
|
1
|
+
import { isAlias, isMap, isScalar, isSeq, parseDocument } from "@amritk/yaml";
|
|
2
|
+
import { applyEdits, findNodeAtLocation, modify, parseTree } from "jsonc-parser";
|
|
3
3
|
const splice = (text, start, end, replacement) => text.slice(0, start) + replacement + text.slice(end);
|
|
4
|
-
/** The offset of the start of the line containing `offset`. */
|
|
5
4
|
const lineStart = (text, offset) => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
let s = offset;
|
|
6
|
+
while (s > 0 && text.charCodeAt(s - 1) !== 10)
|
|
7
|
+
s--;
|
|
8
|
+
return s;
|
|
10
9
|
};
|
|
11
|
-
/** Expands `[start, end)` to cover whole lines: back to the line start, forward past the trailing newline. */
|
|
12
10
|
const expandLine = (text, start, end) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
const s = lineStart(text, start);
|
|
12
|
+
let e = end;
|
|
13
|
+
while (e < text.length && text.charCodeAt(e) !== 10)
|
|
14
|
+
e++;
|
|
15
|
+
if (e < text.length)
|
|
16
|
+
e++;
|
|
17
|
+
return [s, e];
|
|
20
18
|
};
|
|
21
|
-
/**
|
|
22
|
-
* The document's dominant line ending. We look at the first break so inserted
|
|
23
|
-
* lines match the file rather than always emitting a bare `\n` (which would leave
|
|
24
|
-
* a CRLF file with mixed endings).
|
|
25
|
-
*/
|
|
26
19
|
const detectEol = (text) => {
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
const i = text.indexOf("\n");
|
|
21
|
+
return i > 0 && text.charCodeAt(i - 1) === 13 ? "\r\n" : "\n";
|
|
29
22
|
};
|
|
30
|
-
// --- YAML ------------------------------------------------------------------
|
|
31
|
-
/**
|
|
32
|
-
* Stringifies a key the way `toJS` does, so the paths we address by match the
|
|
33
|
-
* keys the projected data exposes: a null key is `null`, a bool/number key is its
|
|
34
|
-
* `String()` form, an alias key is `*name`, and a complex (map/seq) key is empty.
|
|
35
|
-
*/
|
|
36
23
|
const keyName = (key) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
24
|
+
if (isScalar(key)) {
|
|
25
|
+
const v = key.value;
|
|
26
|
+
return typeof v === "string" ? v : v === null ? "null" : String(v);
|
|
27
|
+
}
|
|
28
|
+
if (isAlias(key))
|
|
29
|
+
return `*${key.source}`;
|
|
30
|
+
if (isMap(key) || isSeq(key))
|
|
31
|
+
return "";
|
|
32
|
+
return String(key);
|
|
46
33
|
};
|
|
47
|
-
/** Navigates the YAML CST to the node at `path`, or `undefined` if absent. */
|
|
48
34
|
const yamlNodeAt = (root, path) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
else if (isSeq(current)) {
|
|
62
|
-
current = current.items[Number(segment)];
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
return undefined;
|
|
66
|
-
}
|
|
35
|
+
let current = root ?? void 0;
|
|
36
|
+
for (const segment of path) {
|
|
37
|
+
if (current === void 0)
|
|
38
|
+
return void 0;
|
|
39
|
+
if (isMap(current)) {
|
|
40
|
+
const target = String(segment);
|
|
41
|
+
const pair = current.items.findLast((item) => keyName(item.key) === target);
|
|
42
|
+
current = pair?.value ?? void 0;
|
|
43
|
+
} else if (isSeq(current)) {
|
|
44
|
+
current = current.items[Number(segment)];
|
|
45
|
+
} else {
|
|
46
|
+
return void 0;
|
|
67
47
|
}
|
|
68
|
-
|
|
48
|
+
}
|
|
49
|
+
return current;
|
|
69
50
|
};
|
|
70
|
-
/** Resolves the key/value CST nodes of the property at `path` (last segment = key). */
|
|
71
51
|
const yamlPairAt = (root, path) => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
52
|
+
const parent = yamlNodeAt(root, path.slice(0, -1));
|
|
53
|
+
if (!parent || !isMap(parent))
|
|
54
|
+
return {};
|
|
55
|
+
const last = String(path[path.length - 1]);
|
|
56
|
+
const pair = parent.items.findLast((item) => keyName(item.key) === last);
|
|
57
|
+
if (!pair)
|
|
58
|
+
return {};
|
|
59
|
+
return { key: pair.key, ...pair.value ? { value: pair.value } : {} };
|
|
80
60
|
};
|
|
81
|
-
/**
|
|
82
|
-
* Checks that writing `plain` bare into YAML round-trips back to the string
|
|
83
|
-
* `value`. A bare scalar can silently change meaning — `true` becomes a boolean,
|
|
84
|
-
* `1.0` a number, an empty string a null — and text carrying `: ` or ` #` or a
|
|
85
|
-
* newline reshapes the line, so we parse the candidate on its own and require it
|
|
86
|
-
* to come back as exactly the same string before trusting it unquoted.
|
|
87
|
-
*/
|
|
88
61
|
const plainStringRoundTrips = (plain, value) => {
|
|
89
|
-
|
|
90
|
-
|
|
62
|
+
const doc = parseDocument(plain);
|
|
63
|
+
return doc.errors.length === 0 && isScalar(doc.contents) && doc.contents.value === value;
|
|
91
64
|
};
|
|
92
|
-
// Keys made only of these characters are safe to write bare in YAML; anything
|
|
93
|
-
// else (spaces, colons, flow indicators) gets double-quoted to stay valid.
|
|
94
65
|
const SAFE_YAML_KEY = /^[\w./-]+$/;
|
|
95
|
-
/** Serializes a scalar, preserving the quoting style of the value it replaces. */
|
|
96
66
|
const yamlScalar = (value, original) => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// this exact string; otherwise fall back to a double-quoted literal so we never
|
|
106
|
-
// turn a string into a bool/number/null or corrupt the line.
|
|
107
|
-
return plainStringRoundTrips(value, value) ? value : JSON.stringify(value);
|
|
67
|
+
if (typeof value !== "string")
|
|
68
|
+
return value === null ? "null" : String(value);
|
|
69
|
+
const quote = original.charCodeAt(0);
|
|
70
|
+
if (quote === 34)
|
|
71
|
+
return JSON.stringify(value);
|
|
72
|
+
if (quote === 39)
|
|
73
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
74
|
+
return plainStringRoundTrips(value, value) ? value : JSON.stringify(value);
|
|
108
75
|
};
|
|
109
76
|
const yamlKey = (key, original) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// otherwise safe bare gets quoted, matching the value-side treatment.
|
|
117
|
-
return SAFE_YAML_KEY.test(key) && plainStringRoundTrips(key, key) ? key : JSON.stringify(key);
|
|
77
|
+
const quote = original.charCodeAt(0);
|
|
78
|
+
if (quote === 34)
|
|
79
|
+
return JSON.stringify(key);
|
|
80
|
+
if (quote === 39)
|
|
81
|
+
return `'${key.replace(/'/g, "''")}'`;
|
|
82
|
+
return SAFE_YAML_KEY.test(key) && plainStringRoundTrips(key, key) ? key : JSON.stringify(key);
|
|
118
83
|
};
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
/**
|
|
122
|
-
* Serializes a value for insertion. YAML is a JSON superset, so JSON output is
|
|
123
|
-
* always valid YAML: scalars become bare literals (`42`, `true`, `"text"`) and
|
|
124
|
-
* objects/arrays become inline flow (`{"a":1}`). That keeps the inserted node on
|
|
125
|
-
* one line without us having to re-implement a block serializer.
|
|
126
|
-
*/
|
|
127
|
-
const yamlInsertValue = (value) => JSON.stringify(value) ?? 'null';
|
|
128
|
-
/** Serializes a fresh scalar (no original to mirror), quoting strings only when bare would change meaning. */
|
|
84
|
+
const yamlInsertKey = (key) => SAFE_YAML_KEY.test(key) ? key : JSON.stringify(key);
|
|
85
|
+
const yamlInsertValue = (value) => JSON.stringify(value) ?? "null";
|
|
129
86
|
const yamlFreshScalar = (value) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
87
|
+
if (value !== null && typeof value === "object")
|
|
88
|
+
return yamlInsertValue(value);
|
|
89
|
+
if (typeof value === "string")
|
|
90
|
+
return plainStringRoundTrips(value, value) ? value : JSON.stringify(value);
|
|
91
|
+
return value === null ? "null" : String(value);
|
|
135
92
|
};
|
|
136
|
-
/** Returns the leading whitespace of the line containing `offset` (its indentation). */
|
|
137
93
|
const lineIndent = (text, offset) => {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
94
|
+
const start = lineStart(text, offset);
|
|
95
|
+
let end = start;
|
|
96
|
+
while (end < text.length && (text.charCodeAt(end) === 32 || text.charCodeAt(end) === 9))
|
|
97
|
+
end++;
|
|
98
|
+
return text.slice(start, end);
|
|
143
99
|
};
|
|
144
|
-
/**
|
|
145
|
-
* The indentation a new block-sequence item should carry, measured from the
|
|
146
|
-
* column of the first item's own `- ` dash. Using the dash column (not the line's
|
|
147
|
-
* leading whitespace) keeps a nested sequence correct: for `- - 1` the inner
|
|
148
|
-
* item's dash sits at column 2, so a new inner item indents to match it rather
|
|
149
|
-
* than appending to the outer sequence.
|
|
150
|
-
*/
|
|
151
100
|
const seqItemIndent = (text, firstItem) => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
101
|
+
const start = lineStart(text, firstItem.start);
|
|
102
|
+
let dash = firstItem.start - 1;
|
|
103
|
+
while (dash > start && text.charCodeAt(dash) !== 45)
|
|
104
|
+
dash--;
|
|
105
|
+
return text.charCodeAt(dash) === 45 ? " ".repeat(dash - start) : lineIndent(text, firstItem.start);
|
|
157
106
|
};
|
|
158
107
|
const applyYamlOp = (text, op) => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const colon = text.indexOf(':', key.end);
|
|
171
|
-
if (colon !== -1)
|
|
172
|
-
return splice(text, colon + 1, colon + 1, ` ${yamlFreshScalar(op.value)}`);
|
|
173
|
-
}
|
|
174
|
-
return text;
|
|
175
|
-
}
|
|
176
|
-
// Scalars keep the node's original quoting; objects/arrays are written as
|
|
177
|
-
// inline flow JSON (valid YAML) so a scalar can be widened to a collection.
|
|
178
|
-
const replacement = op.value !== null && typeof op.value === 'object'
|
|
179
|
-
? yamlInsertValue(op.value)
|
|
180
|
-
: yamlScalar(op.value, text.slice(node.start, node.end));
|
|
181
|
-
return splice(text, node.start, node.end, replacement);
|
|
182
|
-
}
|
|
183
|
-
case 'renameProperty': {
|
|
184
|
-
const { key } = yamlPairAt(root, op.path);
|
|
185
|
-
if (!key)
|
|
186
|
-
return text;
|
|
187
|
-
return splice(text, key.start, key.end, yamlKey(op.newKey, text.slice(key.start, key.end)));
|
|
108
|
+
const root = parseDocument(text).contents;
|
|
109
|
+
const eol = detectEol(text);
|
|
110
|
+
switch (op.op) {
|
|
111
|
+
case "setValue": {
|
|
112
|
+
const node = yamlNodeAt(root, op.path);
|
|
113
|
+
if (!node) {
|
|
114
|
+
const { key, value } = yamlPairAt(root, op.path);
|
|
115
|
+
if (key && value === void 0) {
|
|
116
|
+
const colon = text.indexOf(":", key.end);
|
|
117
|
+
if (colon !== -1)
|
|
118
|
+
return splice(text, colon + 1, colon + 1, ` ${yamlFreshScalar(op.value)}`);
|
|
188
119
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// the dash and merge this item into its sibling, so splice only the pair's own
|
|
218
|
-
// span. A lone key falls through to whole-line removal (the item disappears).
|
|
219
|
-
const dashPrefix = text.slice(lineStart(text, key.start), key.start);
|
|
220
|
-
if (/^\s*-\s+$/.test(dashPrefix) && parent.items.length > 1) {
|
|
221
|
-
const next = parent.items[index + 1]?.key;
|
|
222
|
-
if (next)
|
|
223
|
-
return splice(text, key.start, next.start, '');
|
|
224
|
-
return splice(text, key.start, (value ?? key).end, '');
|
|
225
|
-
}
|
|
226
|
-
// Block map: drop the whole line(s) the property occupies.
|
|
227
|
-
const [start, end] = expandLine(text, key.start, value ? value.end : key.end);
|
|
228
|
-
return splice(text, start, end, '');
|
|
229
|
-
}
|
|
230
|
-
case 'removeItems': {
|
|
231
|
-
const seq = yamlNodeAt(root, op.path);
|
|
232
|
-
if (!seq || !isSeq(seq))
|
|
233
|
-
return text;
|
|
234
|
-
const removed = new Set(op.indices);
|
|
235
|
-
return rewriteYamlSeq(text, seq, seq.items.filter((_, index) => !removed.has(index)));
|
|
236
|
-
}
|
|
237
|
-
case 'reorderArray': {
|
|
238
|
-
const seq = yamlNodeAt(root, op.path);
|
|
239
|
-
if (!seq || !isSeq(seq) || seq.items.length === 0)
|
|
240
|
-
return text;
|
|
241
|
-
const reordered = op.order.map((index) => seq.items[index]).filter((item) => item != null);
|
|
242
|
-
return rewriteYamlSeq(text, seq, reordered);
|
|
120
|
+
return text;
|
|
121
|
+
}
|
|
122
|
+
const replacement = op.value !== null && typeof op.value === "object" ? yamlInsertValue(op.value) : yamlScalar(op.value, text.slice(node.start, node.end));
|
|
123
|
+
return splice(text, node.start, node.end, replacement);
|
|
124
|
+
}
|
|
125
|
+
case "renameProperty": {
|
|
126
|
+
const { key } = yamlPairAt(root, op.path);
|
|
127
|
+
if (!key)
|
|
128
|
+
return text;
|
|
129
|
+
return splice(text, key.start, key.end, yamlKey(op.newKey, text.slice(key.start, key.end)));
|
|
130
|
+
}
|
|
131
|
+
case "removeProperty": {
|
|
132
|
+
const parent = yamlNodeAt(root, op.path.slice(0, -1));
|
|
133
|
+
if (!parent || !isMap(parent))
|
|
134
|
+
return text;
|
|
135
|
+
const last = String(op.path[op.path.length - 1]);
|
|
136
|
+
const index = parent.items.findLastIndex((item) => keyName(item.key) === last);
|
|
137
|
+
if (index === -1)
|
|
138
|
+
return text;
|
|
139
|
+
const pair = parent.items[index];
|
|
140
|
+
const key = pair?.key;
|
|
141
|
+
const value = pair?.value;
|
|
142
|
+
if (text.charCodeAt(parent.start) === 123) {
|
|
143
|
+
if (parent.items.length === 1)
|
|
144
|
+
return splice(text, parent.start + 1, parent.end - 1, "");
|
|
145
|
+
if (index < parent.items.length - 1) {
|
|
146
|
+
const next = parent.items[index + 1]?.key;
|
|
147
|
+
return splice(text, key.start, next.start, "");
|
|
243
148
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
// indent (2) would place the new key outside the item — use the column (4).
|
|
284
|
-
const keyLineStart = lineStart(text, lastKey.start);
|
|
285
|
-
const prefix = text.slice(keyLineStart, lastKey.start);
|
|
286
|
-
const indent = /^\s*$/.test(prefix) ? prefix : ' '.repeat(lastKey.start - keyLineStart);
|
|
287
|
-
const [, end] = expandLine(text, lastKey.start, (last?.value ?? lastKey).end);
|
|
288
|
-
const lead = end > 0 && text.charCodeAt(end - 1) !== 10 ? eol : '';
|
|
289
|
-
return splice(text, end, end, `${lead}${indent}${pair}${eol}`);
|
|
149
|
+
const prev = parent.items[index - 1];
|
|
150
|
+
const prevEnd = (prev?.value ?? prev?.key).end;
|
|
151
|
+
return splice(text, prevEnd, (value ?? key).end, "");
|
|
152
|
+
}
|
|
153
|
+
const dashPrefix = text.slice(lineStart(text, key.start), key.start);
|
|
154
|
+
if (/^\s*-\s+$/.test(dashPrefix) && parent.items.length > 1) {
|
|
155
|
+
const next = parent.items[index + 1]?.key;
|
|
156
|
+
if (next)
|
|
157
|
+
return splice(text, key.start, next.start, "");
|
|
158
|
+
return splice(text, key.start, (value ?? key).end, "");
|
|
159
|
+
}
|
|
160
|
+
const [start, end] = expandLine(text, key.start, value ? value.end : key.end);
|
|
161
|
+
return splice(text, start, end, "");
|
|
162
|
+
}
|
|
163
|
+
case "removeItems": {
|
|
164
|
+
const seq = yamlNodeAt(root, op.path);
|
|
165
|
+
if (!seq || !isSeq(seq))
|
|
166
|
+
return text;
|
|
167
|
+
const removed = new Set(op.indices);
|
|
168
|
+
return rewriteYamlSeq(text, seq, seq.items.filter((_, index) => !removed.has(index)));
|
|
169
|
+
}
|
|
170
|
+
case "reorderArray": {
|
|
171
|
+
const seq = yamlNodeAt(root, op.path);
|
|
172
|
+
if (!seq || !isSeq(seq) || seq.items.length === 0)
|
|
173
|
+
return text;
|
|
174
|
+
const reordered = op.order.map((index) => seq.items[index]).filter((item) => item != null);
|
|
175
|
+
return rewriteYamlSeq(text, seq, reordered);
|
|
176
|
+
}
|
|
177
|
+
case "insertProperty": {
|
|
178
|
+
const parent = yamlNodeAt(root, op.path);
|
|
179
|
+
if (!parent) {
|
|
180
|
+
const { key, value } = yamlPairAt(root, op.path);
|
|
181
|
+
if (key && value === void 0) {
|
|
182
|
+
const colon = text.indexOf(":", key.end);
|
|
183
|
+
if (colon !== -1) {
|
|
184
|
+
const indent2 = " ".repeat(key.start - lineStart(text, key.start) + 2);
|
|
185
|
+
const pair2 = `${yamlInsertKey(op.key)}: ${yamlInsertValue(op.value)}`;
|
|
186
|
+
return splice(text, colon + 1, colon + 1, `${eol}${indent2}${pair2}`);
|
|
187
|
+
}
|
|
290
188
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return text;
|
|
304
|
-
const blocks = seqBlocks(text, seq.items);
|
|
305
|
-
const regionStart = blocks[0][0];
|
|
306
|
-
const regionEnd = blocks[blocks.length - 1][1];
|
|
307
|
-
const lines = seq.items.map((_, index) => text.slice(...blocks[index]));
|
|
308
|
-
const newLine = `${seqItemIndent(text, seq.items[0])}- ${value}${eol}`;
|
|
309
|
-
lines.splice(clampIndex(op.index, lines.length), 0, newLine);
|
|
310
|
-
return splice(text, regionStart, regionEnd, lines.join(''));
|
|
189
|
+
return text;
|
|
190
|
+
}
|
|
191
|
+
if (!isMap(parent))
|
|
192
|
+
return text;
|
|
193
|
+
if (parent.items.some((item) => keyName(item.key) === op.key))
|
|
194
|
+
return text;
|
|
195
|
+
const pair = `${yamlInsertKey(op.key)}: ${yamlInsertValue(op.value)}`;
|
|
196
|
+
if (text.charCodeAt(parent.start) === 123) {
|
|
197
|
+
const last2 = parent.items[parent.items.length - 1];
|
|
198
|
+
if (last2) {
|
|
199
|
+
const lastEnd = (last2.value ?? last2.key).end;
|
|
200
|
+
return splice(text, lastEnd, lastEnd, `, ${pair}`);
|
|
311
201
|
}
|
|
202
|
+
return splice(text, parent.start + 1, parent.start + 1, pair);
|
|
203
|
+
}
|
|
204
|
+
const last = parent.items[parent.items.length - 1];
|
|
205
|
+
const lastKey = last?.key;
|
|
206
|
+
if (!lastKey)
|
|
207
|
+
return text;
|
|
208
|
+
const keyLineStart = lineStart(text, lastKey.start);
|
|
209
|
+
const prefix = text.slice(keyLineStart, lastKey.start);
|
|
210
|
+
const indent = /^\s*$/.test(prefix) ? prefix : " ".repeat(lastKey.start - keyLineStart);
|
|
211
|
+
const [, end] = expandLine(text, lastKey.start, (last?.value ?? lastKey).end);
|
|
212
|
+
const lead = end > 0 && text.charCodeAt(end - 1) !== 10 ? eol : "";
|
|
213
|
+
return splice(text, end, end, `${lead}${indent}${pair}${eol}`);
|
|
312
214
|
}
|
|
215
|
+
case "insertItem": {
|
|
216
|
+
const seq = yamlNodeAt(root, op.path);
|
|
217
|
+
if (!seq || !isSeq(seq))
|
|
218
|
+
return text;
|
|
219
|
+
const value = yamlInsertValue(op.value);
|
|
220
|
+
if (text.charCodeAt(seq.start) === 91) {
|
|
221
|
+
const items = seq.items.map((item) => text.slice(item.start, item.end));
|
|
222
|
+
items.splice(clampIndex(op.index, items.length), 0, value);
|
|
223
|
+
return splice(text, seq.start + 1, seq.end - 1, items.join(", "));
|
|
224
|
+
}
|
|
225
|
+
if (seq.items.length === 0)
|
|
226
|
+
return text;
|
|
227
|
+
const blocks = seqBlocks(text, seq.items);
|
|
228
|
+
const regionStart = blocks[0][0];
|
|
229
|
+
const regionEnd = blocks[blocks.length - 1][1];
|
|
230
|
+
const lines = seq.items.map((_, index) => text.slice(...blocks[index]));
|
|
231
|
+
const newLine = `${seqItemIndent(text, seq.items[0])}- ${value}${eol}`;
|
|
232
|
+
lines.splice(clampIndex(op.index, lines.length), 0, newLine);
|
|
233
|
+
return splice(text, regionStart, regionEnd, lines.join(""));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
313
236
|
};
|
|
314
|
-
/** Clamps an optional insertion index into `[0, length]`, defaulting to an append. */
|
|
315
237
|
const clampIndex = (index, length) => Math.max(0, Math.min(index ?? length, length));
|
|
316
|
-
/**
|
|
317
|
-
* Whole-line spans for each block-sequence item, with any comment-only or blank
|
|
318
|
-
* lines that precede an item folded into that item's block. Attaching leading
|
|
319
|
-
* comments to the following item means a reorder carries them along and a removal
|
|
320
|
-
* takes only the comments that belong to the dropped item — honoring the module's
|
|
321
|
-
* promise to preserve comments rather than dropping every line between items.
|
|
322
|
-
*/
|
|
323
238
|
const seqBlocks = (text, items) => {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
});
|
|
331
|
-
return blocks;
|
|
239
|
+
const blocks = [];
|
|
240
|
+
items.forEach((item, i) => {
|
|
241
|
+
const [start, end] = expandLine(text, item.start, item.end);
|
|
242
|
+
blocks.push([i === 0 ? start : blocks[i - 1][1], end]);
|
|
243
|
+
});
|
|
244
|
+
return blocks;
|
|
332
245
|
};
|
|
333
|
-
/**
|
|
334
|
-
* Rewrites a YAML sequence to contain exactly `newItems` (a subset and/or
|
|
335
|
-
* reordering of the original nodes), preserving each kept item's own text. Flow
|
|
336
|
-
* sequences (`[a, b]`) are rebuilt inside their brackets; block sequences (one
|
|
337
|
-
* `- item` per line) are rebuilt from their whole-line spans, carrying each item's
|
|
338
|
-
* preceding comments with it.
|
|
339
|
-
*/
|
|
340
246
|
const rewriteYamlSeq = (text, seq, newItems) => {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
247
|
+
const isFlow = text.charCodeAt(seq.start) === 91;
|
|
248
|
+
if (isFlow) {
|
|
249
|
+
const inner = newItems.map((item) => text.slice(item.start, item.end)).join(", ");
|
|
250
|
+
return splice(text, seq.start + 1, seq.end - 1, inner);
|
|
251
|
+
}
|
|
252
|
+
if (seq.items.length === 0)
|
|
253
|
+
return text;
|
|
254
|
+
const blocks = seqBlocks(text, seq.items);
|
|
255
|
+
const regionStart = blocks[0][0];
|
|
256
|
+
const regionEnd = blocks[blocks.length - 1][1];
|
|
257
|
+
const blockText = new Map(seq.items.map((item, index) => [item, text.slice(...blocks[index])]));
|
|
258
|
+
const rebuilt = newItems.map((item) => blockText.get(item) ?? "").join("");
|
|
259
|
+
return splice(text, regionStart, regionEnd, rebuilt);
|
|
354
260
|
};
|
|
355
|
-
// --- JSON ------------------------------------------------------------------
|
|
356
|
-
/** Formatting options for jsonc-parser edits, carrying the file's own line ending. */
|
|
357
261
|
const jsonFormat = (eol) => ({
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
262
|
+
insertSpaces: true,
|
|
263
|
+
tabSize: 2,
|
|
264
|
+
eol
|
|
361
265
|
});
|
|
362
|
-
/**
|
|
363
|
-
* jsonc-parser keys path segments by JS type — strings index objects, numbers
|
|
364
|
-
* index arrays — but a finding's path carries numeric-like object keys (e.g. a
|
|
365
|
-
* `"200"` response) as plain numbers. Walk the tree and coerce each segment to
|
|
366
|
-
* the type its parent expects, so both keys and indices resolve (and `modify`
|
|
367
|
-
* doesn't mistake a `"200"` key for an array index and throw).
|
|
368
|
-
*/
|
|
369
266
|
const normalizeJsonPath = (root, path) => {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
267
|
+
const result = [];
|
|
268
|
+
let node = root;
|
|
269
|
+
for (const segment of path) {
|
|
270
|
+
const normalized = node?.type === "array" ? Number(segment) : String(segment);
|
|
271
|
+
result.push(normalized);
|
|
272
|
+
node = node ? findNodeAtLocation(node, [normalized]) : void 0;
|
|
273
|
+
}
|
|
274
|
+
return result;
|
|
378
275
|
};
|
|
379
|
-
/**
|
|
380
|
-
* Rewrites a JSON array to contain exactly `keptChildren`, slicing each element's
|
|
381
|
-
* original source so numeric literals (`1.50`), escapes, and Unicode survive byte
|
|
382
|
-
* for byte. Separators, leading, and trailing whitespace are taken from the
|
|
383
|
-
* original array, so a one-line array stays on one line and a multi-line array
|
|
384
|
-
* keeps its indentation instead of being re-serialized with a hardcoded style.
|
|
385
|
-
*/
|
|
386
276
|
const rewriteJsonArray = (text, node, keptChildren) => {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const inner = keptChildren.map((child) => text.slice(child.offset, child.offset + child.length)).join(separator);
|
|
402
|
-
return splice(text, open, close, leading + inner + trailing);
|
|
277
|
+
const children = node.children ?? [];
|
|
278
|
+
const open = node.offset + 1;
|
|
279
|
+
const close = node.offset + node.length - 1;
|
|
280
|
+
if (children.length === 0)
|
|
281
|
+
return text;
|
|
282
|
+
const first = children[0];
|
|
283
|
+
const last = children[children.length - 1];
|
|
284
|
+
if (keptChildren.length === 0)
|
|
285
|
+
return splice(text, open, close, "");
|
|
286
|
+
const leading = text.slice(open, first.offset);
|
|
287
|
+
const trailing = text.slice(last.offset + last.length, close);
|
|
288
|
+
const separator = children.length > 1 ? text.slice(first.offset + first.length, children[1].offset) : ", ";
|
|
289
|
+
const inner = keptChildren.map((child) => text.slice(child.offset, child.offset + child.length)).join(separator);
|
|
290
|
+
return splice(text, open, close, leading + inner + trailing);
|
|
403
291
|
};
|
|
404
292
|
const applyJsonOp = (text, op) => {
|
|
405
|
-
|
|
406
|
-
|
|
293
|
+
const root = parseTree(text);
|
|
294
|
+
if (!root)
|
|
295
|
+
return text;
|
|
296
|
+
const path = normalizeJsonPath(root, op.path);
|
|
297
|
+
const format = jsonFormat(detectEol(text));
|
|
298
|
+
switch (op.op) {
|
|
299
|
+
case "setValue":
|
|
300
|
+
if (!findNodeAtLocation(root, path))
|
|
407
301
|
return text;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
return applyEdits(text, modify(text, path, undefined, { formattingOptions: format }));
|
|
422
|
-
case 'renameProperty': {
|
|
423
|
-
const valueNode = findNodeAtLocation(root, path);
|
|
424
|
-
// Only a real object member can be renamed. An array-index path has an
|
|
425
|
-
// `array` parent, whose first child is element 0 — renaming there would
|
|
426
|
-
// overwrite that element, so bail instead.
|
|
427
|
-
if (valueNode?.parent?.type !== 'property')
|
|
428
|
-
return text;
|
|
429
|
-
const keyNode = valueNode.parent.children?.[0];
|
|
430
|
-
if (!keyNode)
|
|
431
|
-
return text;
|
|
432
|
-
return splice(text, keyNode.offset, keyNode.offset + keyNode.length, JSON.stringify(op.newKey));
|
|
433
|
-
}
|
|
434
|
-
case 'removeItems': {
|
|
435
|
-
const node = findNodeAtLocation(root, path);
|
|
436
|
-
if (!node || node.type !== 'array')
|
|
437
|
-
return text;
|
|
438
|
-
const removed = new Set(op.indices);
|
|
439
|
-
const kept = (node.children ?? []).filter((_, index) => !removed.has(index));
|
|
440
|
-
return rewriteJsonArray(text, node, kept);
|
|
441
|
-
}
|
|
442
|
-
case 'reorderArray': {
|
|
443
|
-
const node = findNodeAtLocation(root, path);
|
|
444
|
-
if (!node || node.type !== 'array')
|
|
445
|
-
return text;
|
|
446
|
-
const children = node.children ?? [];
|
|
447
|
-
const reordered = op.order.map((index) => children[index]).filter((child) => child != null);
|
|
448
|
-
return rewriteJsonArray(text, node, reordered);
|
|
449
|
-
}
|
|
450
|
-
case 'insertProperty': {
|
|
451
|
-
const node = findNodeAtLocation(root, path);
|
|
452
|
-
if (!node || node.type !== 'object')
|
|
453
|
-
return text;
|
|
454
|
-
// Additive only: writing to an existing key would replace its value, so bail.
|
|
455
|
-
if (node.children?.some((property) => property.children?.[0]?.value === op.key))
|
|
456
|
-
return text;
|
|
457
|
-
return applyEdits(text, modify(text, [...path, op.key], op.value, { formattingOptions: format }));
|
|
458
|
-
}
|
|
459
|
-
case 'insertItem': {
|
|
460
|
-
const node = findNodeAtLocation(root, path);
|
|
461
|
-
if (!node || node.type !== 'array')
|
|
462
|
-
return text;
|
|
463
|
-
const index = clampIndex(op.index, node.children?.length ?? 0);
|
|
464
|
-
return applyEdits(text, modify(text, [...path, index], op.value, { formattingOptions: format, isArrayInsertion: true }));
|
|
465
|
-
}
|
|
302
|
+
return applyEdits(text, modify(text, path, op.value, { formattingOptions: format }));
|
|
303
|
+
case "removeProperty":
|
|
304
|
+
if (!findNodeAtLocation(root, path))
|
|
305
|
+
return text;
|
|
306
|
+
return applyEdits(text, modify(text, path, void 0, { formattingOptions: format }));
|
|
307
|
+
case "renameProperty": {
|
|
308
|
+
const valueNode = findNodeAtLocation(root, path);
|
|
309
|
+
if (valueNode?.parent?.type !== "property")
|
|
310
|
+
return text;
|
|
311
|
+
const keyNode = valueNode.parent.children?.[0];
|
|
312
|
+
if (!keyNode)
|
|
313
|
+
return text;
|
|
314
|
+
return splice(text, keyNode.offset, keyNode.offset + keyNode.length, JSON.stringify(op.newKey));
|
|
466
315
|
}
|
|
316
|
+
case "removeItems": {
|
|
317
|
+
const node = findNodeAtLocation(root, path);
|
|
318
|
+
if (!node || node.type !== "array")
|
|
319
|
+
return text;
|
|
320
|
+
const removed = new Set(op.indices);
|
|
321
|
+
const kept = (node.children ?? []).filter((_, index) => !removed.has(index));
|
|
322
|
+
return rewriteJsonArray(text, node, kept);
|
|
323
|
+
}
|
|
324
|
+
case "reorderArray": {
|
|
325
|
+
const node = findNodeAtLocation(root, path);
|
|
326
|
+
if (!node || node.type !== "array")
|
|
327
|
+
return text;
|
|
328
|
+
const children = node.children ?? [];
|
|
329
|
+
const reordered = op.order.map((index) => children[index]).filter((child) => child != null);
|
|
330
|
+
return rewriteJsonArray(text, node, reordered);
|
|
331
|
+
}
|
|
332
|
+
case "insertProperty": {
|
|
333
|
+
const node = findNodeAtLocation(root, path);
|
|
334
|
+
if (!node || node.type !== "object")
|
|
335
|
+
return text;
|
|
336
|
+
if (node.children?.some((property) => property.children?.[0]?.value === op.key))
|
|
337
|
+
return text;
|
|
338
|
+
return applyEdits(text, modify(text, [...path, op.key], op.value, { formattingOptions: format }));
|
|
339
|
+
}
|
|
340
|
+
case "insertItem": {
|
|
341
|
+
const node = findNodeAtLocation(root, path);
|
|
342
|
+
if (!node || node.type !== "array")
|
|
343
|
+
return text;
|
|
344
|
+
const index = clampIndex(op.index, node.children?.length ?? 0);
|
|
345
|
+
return applyEdits(text, modify(text, [...path, index], op.value, { formattingOptions: format, isArrayInsertion: true }));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
467
348
|
};
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
let text = source;
|
|
478
|
-
const changed = [];
|
|
479
|
-
for (const op of ops) {
|
|
480
|
-
const before = text;
|
|
481
|
-
// An op that can't be lowered (an unresolved path, a key the underlying
|
|
482
|
-
// editor rejects) must be a no-op, never a thrown error that aborts the
|
|
483
|
-
// whole batch — one bad edit should not drop every other fix.
|
|
484
|
-
try {
|
|
485
|
-
text = format === 'json' ? applyJsonOp(text, op) : applyYamlOp(text, op);
|
|
486
|
-
}
|
|
487
|
-
catch {
|
|
488
|
-
text = before;
|
|
489
|
-
}
|
|
490
|
-
changed.push(text !== before);
|
|
349
|
+
const applyEditOpsWithChanges = (source, format, ops) => {
|
|
350
|
+
let text = source;
|
|
351
|
+
const changed = [];
|
|
352
|
+
for (const op of ops) {
|
|
353
|
+
const before = text;
|
|
354
|
+
try {
|
|
355
|
+
text = format === "json" ? applyJsonOp(text, op) : applyYamlOp(text, op);
|
|
356
|
+
} catch {
|
|
357
|
+
text = before;
|
|
491
358
|
}
|
|
492
|
-
|
|
359
|
+
changed.push(text !== before);
|
|
360
|
+
}
|
|
361
|
+
return { output: text, changed };
|
|
362
|
+
};
|
|
363
|
+
const applyEditOps = (source, format, ops) => applyEditOpsWithChanges(source, format, ops).output;
|
|
364
|
+
export {
|
|
365
|
+
applyEditOps,
|
|
366
|
+
applyEditOpsWithChanges
|
|
493
367
|
};
|
|
494
|
-
/** Applies `ops` to `source` and returns just the edited text. See {@link applyEditOpsWithChanges}. */
|
|
495
|
-
export const applyEditOps = (source, format, ops) => applyEditOpsWithChanges(source, format, ops).output;
|