atmn 2.0.21 → 2.0.23
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/dist/bin.js +174 -55
- package/dist/index.js +12 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/actions/pull/applyPreview.ts +21 -2
- package/src/actions/pull/elementStatesPlanId.ts +70 -0
- package/src/actions/pull/resolveCollectionTarget.ts +2 -2
- package/src/generated/apiRoutes.ts +1 -1
- package/src/generated/emit.ts +0 -2
- package/src/generated/features.ts +0 -1
- package/src/generated/lintRules.ts +7 -4
- package/src/generated/lintRuntime.ts +11 -0
- package/src/generated/skills.ts +10 -10
- package/src/surgery/appendToArray.ts +114 -26
- package/src/surgery/appendToBinding.ts +4 -1
- package/src/surgery/appendToCollection.ts +3 -1
|
@@ -5,51 +5,76 @@ import { leadingIndentOfLine, lineStartOf } from "./fixtureEdit";
|
|
|
5
5
|
export type AppendText = string | ((elementIndent: string) => string);
|
|
6
6
|
|
|
7
7
|
/** Append one element to an array literal, matching its siblings' indent and
|
|
8
|
-
* comma style; an empty array is reflowed onto its own lines.
|
|
8
|
+
* comma style; an empty array is reflowed onto its own lines. With `after`,
|
|
9
|
+
* the element lands right behind the last sibling the predicate accepts. */
|
|
9
10
|
export const appendElementToArray = ({
|
|
10
11
|
source,
|
|
11
12
|
root,
|
|
12
13
|
array,
|
|
13
14
|
text,
|
|
15
|
+
after,
|
|
14
16
|
}: {
|
|
15
17
|
source: string;
|
|
16
18
|
root: SgNode;
|
|
17
19
|
array: SgNode;
|
|
18
20
|
text: AppendText;
|
|
21
|
+
after?: (element: SgNode) => boolean;
|
|
19
22
|
}): string => {
|
|
20
|
-
|
|
23
|
+
// A comment is a named child too, but never an element.
|
|
24
|
+
const elements = array
|
|
25
|
+
.namedChildren()
|
|
26
|
+
.filter((child) => child.kind() !== "comment");
|
|
21
27
|
if (elements.length === 0) {
|
|
22
28
|
const indent = leadingIndentOfLine(source, array.range().start.index);
|
|
23
29
|
// The seeded element's first line lands one tab deeper than the array.
|
|
24
30
|
const resolved = resolveText({ text, elementIndent: `${indent}\t` });
|
|
25
31
|
return root.commitEdits([
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
},
|
|
32
|
+
rebuildArrayEdit({
|
|
33
|
+
array,
|
|
34
|
+
lines: [...commentLines({ array }), `${resolved},`],
|
|
35
|
+
indent,
|
|
36
|
+
}),
|
|
31
37
|
]);
|
|
32
38
|
}
|
|
33
39
|
const last = elements[elements.length - 1];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
let anchorIndex = -1;
|
|
41
|
+
if (after !== undefined) {
|
|
42
|
+
for (let index = elements.length - 1; index >= 0; index -= 1) {
|
|
43
|
+
if (after(elements[index])) {
|
|
44
|
+
anchorIndex = index;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const anchor = anchorIndex === -1 ? last : elements[anchorIndex];
|
|
50
|
+
const { insertAt, hasTrailingComma } = insertionAfter({
|
|
51
|
+
source,
|
|
52
|
+
array,
|
|
53
|
+
anchor,
|
|
54
|
+
});
|
|
55
|
+
// A missing comma goes straight after the anchor, ahead of any comment.
|
|
56
|
+
const commaEdits = hasTrailingComma
|
|
57
|
+
? []
|
|
58
|
+
: [
|
|
59
|
+
{
|
|
60
|
+
startPos: anchor.range().end.index,
|
|
61
|
+
endPos: anchor.range().end.index,
|
|
62
|
+
insertedText: ",",
|
|
63
|
+
},
|
|
64
|
+
];
|
|
41
65
|
const spansLines = source
|
|
42
66
|
.slice(array.range().start.index, last.range().start.index)
|
|
43
67
|
.includes("\n");
|
|
44
68
|
if (spansLines) {
|
|
45
|
-
// Sibling indent: the whitespace opening the
|
|
46
|
-
const indent = leadingIndentOfLine(source,
|
|
69
|
+
// Sibling indent: the whitespace opening the anchor element's line.
|
|
70
|
+
const indent = leadingIndentOfLine(source, anchor.range().start.index);
|
|
47
71
|
const resolved = resolveText({ text, elementIndent: indent });
|
|
48
72
|
return root.commitEdits([
|
|
73
|
+
...commaEdits,
|
|
49
74
|
{
|
|
50
75
|
startPos: insertAt,
|
|
51
76
|
endPos: insertAt,
|
|
52
|
-
insertedText:
|
|
77
|
+
insertedText: `\n${indent}${resolved},`,
|
|
53
78
|
},
|
|
54
79
|
]);
|
|
55
80
|
}
|
|
@@ -58,26 +83,89 @@ export const appendElementToArray = ({
|
|
|
58
83
|
const resolved = resolveText({ text, elementIndent });
|
|
59
84
|
if (!resolved.includes("\n")) {
|
|
60
85
|
return root.commitEdits([
|
|
86
|
+
...commaEdits,
|
|
61
87
|
{
|
|
62
88
|
startPos: insertAt,
|
|
63
89
|
endPos: insertAt,
|
|
64
|
-
insertedText:
|
|
90
|
+
insertedText: ` ${resolved},`,
|
|
65
91
|
},
|
|
66
92
|
]);
|
|
67
93
|
}
|
|
68
|
-
// A one-line array cannot hold a multi-line element inline: reflow it
|
|
69
|
-
|
|
70
|
-
|
|
94
|
+
// A one-line array cannot hold a multi-line element inline: reflow it,
|
|
95
|
+
// one line per element, with comments kept in place on their own lines.
|
|
96
|
+
const children = array.namedChildren();
|
|
97
|
+
const lines = children.map((child) =>
|
|
98
|
+
child.kind() === "comment" ? child.text() : `${child.text()},`,
|
|
71
99
|
);
|
|
100
|
+
const anchorStart = anchor.range().start.index;
|
|
101
|
+
const position =
|
|
102
|
+
anchorIndex === -1
|
|
103
|
+
? lines.length
|
|
104
|
+
: children.findIndex(
|
|
105
|
+
(child) => child.range().start.index === anchorStart,
|
|
106
|
+
) + 1;
|
|
107
|
+
lines.splice(position, 0, `${resolved},`);
|
|
72
108
|
return root.commitEdits([
|
|
73
|
-
{
|
|
74
|
-
startPos: array.range().start.index,
|
|
75
|
-
endPos: array.range().end.index,
|
|
76
|
-
insertedText: `[\n${lines.join("\n")}\n${lineIndent}]`,
|
|
77
|
-
},
|
|
109
|
+
rebuildArrayEdit({ array, lines, indent: lineIndent }),
|
|
78
110
|
]);
|
|
79
111
|
};
|
|
80
112
|
|
|
113
|
+
const commentLines = ({ array }: { array: SgNode }): string[] =>
|
|
114
|
+
array
|
|
115
|
+
.namedChildren()
|
|
116
|
+
.filter((child) => child.kind() === "comment")
|
|
117
|
+
.map((child) => child.text());
|
|
118
|
+
|
|
119
|
+
/** The whole literal on its own lines, each entry one tab deeper than the array. */
|
|
120
|
+
const rebuildArrayEdit = ({
|
|
121
|
+
array,
|
|
122
|
+
lines,
|
|
123
|
+
indent,
|
|
124
|
+
}: {
|
|
125
|
+
array: SgNode;
|
|
126
|
+
lines: string[];
|
|
127
|
+
indent: string;
|
|
128
|
+
}) => ({
|
|
129
|
+
startPos: array.range().start.index,
|
|
130
|
+
endPos: array.range().end.index,
|
|
131
|
+
insertedText: `[\n${lines.map((line) => `${indent}\t${line}`).join("\n")}\n${indent}]`,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
/** Where the next element goes: past the anchor's comma and any comment that
|
|
135
|
+
* shares its line, so `plan() /* note *\/,` and `plan(), // legacy` keep their trivia. */
|
|
136
|
+
const insertionAfter = ({
|
|
137
|
+
source,
|
|
138
|
+
array,
|
|
139
|
+
anchor,
|
|
140
|
+
}: {
|
|
141
|
+
source: string;
|
|
142
|
+
array: SgNode;
|
|
143
|
+
anchor: SgNode;
|
|
144
|
+
}): { insertAt: number; hasTrailingComma: boolean } => {
|
|
145
|
+
const children = array.children();
|
|
146
|
+
const start = children.findIndex(
|
|
147
|
+
(child) => child.range().start.index === anchor.range().start.index,
|
|
148
|
+
);
|
|
149
|
+
let insertAt = anchor.range().end.index;
|
|
150
|
+
let hasTrailingComma = false;
|
|
151
|
+
for (const child of children.slice(start + 1)) {
|
|
152
|
+
const kind = child.kind();
|
|
153
|
+
if (kind === ",") {
|
|
154
|
+
if (hasTrailingComma) break;
|
|
155
|
+
hasTrailingComma = true;
|
|
156
|
+
insertAt = child.range().end.index;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (kind !== "comment") break;
|
|
160
|
+
const sameLine = !source
|
|
161
|
+
.slice(insertAt, child.range().start.index)
|
|
162
|
+
.includes("\n");
|
|
163
|
+
if (!sameLine) break;
|
|
164
|
+
insertAt = child.range().end.index;
|
|
165
|
+
}
|
|
166
|
+
return { insertAt, hasTrailingComma };
|
|
167
|
+
};
|
|
168
|
+
|
|
81
169
|
const resolveText = ({
|
|
82
170
|
text,
|
|
83
171
|
elementIndent,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SgNode } from "@ast-grep/napi";
|
|
1
2
|
import { Lang, parse } from "@ast-grep/napi";
|
|
2
3
|
import { type AppendText, appendElementToArray } from "./appendToArray";
|
|
3
4
|
import { findArrayBinding } from "./arrayBinding";
|
|
@@ -7,13 +8,15 @@ export const appendToBinding = ({
|
|
|
7
8
|
source,
|
|
8
9
|
name,
|
|
9
10
|
text,
|
|
11
|
+
after,
|
|
10
12
|
}: {
|
|
11
13
|
source: string;
|
|
12
14
|
name: string;
|
|
13
15
|
text: AppendText;
|
|
16
|
+
after?: (element: SgNode) => boolean;
|
|
14
17
|
}): string | null => {
|
|
15
18
|
const root = parse(Lang.TypeScript, source).root();
|
|
16
19
|
const array = findArrayBinding({ root, name });
|
|
17
20
|
if (array === null) return null;
|
|
18
|
-
return appendElementToArray({ source, root, array, text });
|
|
21
|
+
return appendElementToArray({ source, root, array, text, after });
|
|
19
22
|
};
|
|
@@ -6,10 +6,12 @@ export const appendToCollection = ({
|
|
|
6
6
|
source,
|
|
7
7
|
collection,
|
|
8
8
|
text,
|
|
9
|
+
after,
|
|
9
10
|
}: {
|
|
10
11
|
source: string;
|
|
11
12
|
collection: string;
|
|
12
13
|
text: AppendText;
|
|
14
|
+
after?: (element: SgNode) => boolean;
|
|
13
15
|
}): string | null => {
|
|
14
16
|
const root = parse(Lang.TypeScript, source).root();
|
|
15
17
|
// A bare `collection: [$$$]` parses as a type annotation, so the anchor must
|
|
@@ -20,7 +22,7 @@ export const appendToCollection = ({
|
|
|
20
22
|
if (anchor === null) return null;
|
|
21
23
|
const array = collectionArray({ anchor, collection });
|
|
22
24
|
if (array === null) return null;
|
|
23
|
-
return appendElementToArray({ source, root, array, text });
|
|
25
|
+
return appendElementToArray({ source, root, array, text, after });
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
const collectionArray = ({
|