@zgeoff/format-codemod 0.0.2 → 0.0.4
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/README.md +13 -4
- package/dist/cli.js +144 -109
- package/dist/index.js +122 -92
- package/dist/transform/needs-blank-line.d.ts +11 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,11 +19,18 @@ no "never" rules.
|
|
|
19
19
|
- after a `function` or `class` declaration
|
|
20
20
|
- on both sides of a control-flow block: `if`, `for`, `for…in`, `for…of`, `while`, `switch`, `try`
|
|
21
21
|
- on both sides of any statement that spans multiple lines
|
|
22
|
-
- at the boundary between statement kinds — a `const`/`let`/`var` declaration, a
|
|
23
|
-
assignment/increment statement — in any
|
|
22
|
+
- at the boundary between statement kinds — a `const`/`let`/`var` declaration, a
|
|
23
|
+
`using`/`await using` declaration, a call statement, an assignment/increment statement — in any
|
|
24
|
+
order; runs of one kind stay tight
|
|
25
|
+
- after a `using`/`await using` block, before any statement of another kind
|
|
24
26
|
- at the boundary between an instantiation-headed statement (`new` at the head of its expression
|
|
25
27
|
chain, as in `new X(…)` or `new X(…).method()`) and any other statement kind — `new` in argument
|
|
26
28
|
position doesn't count, and runs of instantiations stay glued
|
|
29
|
+
- at the boundary between an awaited and a non-awaited statement — awaited means an `await using`
|
|
30
|
+
declaration, or an `await` in the head of the statement's value chain (`await f()`,
|
|
31
|
+
`const x = await f()`, `x = (await f()).prop`); an `await` in argument position (`use(await f())`)
|
|
32
|
+
or under a non-chain head (`flag ? await f() : g()`) doesn't count, and runs of awaited statements
|
|
33
|
+
stay glued
|
|
27
34
|
|
|
28
35
|
**`@stylistic/lines-between-class-members`** — one blank line between class members.
|
|
29
36
|
|
|
@@ -32,8 +39,10 @@ no "never" rules.
|
|
|
32
39
|
- **Consecutive single-line `const`/`let`/`var` declarations stay glued** (no blank between them); a
|
|
33
40
|
multiline declaration is separated like any other multiline statement.
|
|
34
41
|
- **Bare declarations only.** `export const`, `export function`, and `export class` are not matched
|
|
35
|
-
— ESLint's selectors do not look through `export`, and neither does this. `await using`
|
|
36
|
-
|
|
42
|
+
— ESLint's selectors do not look through `export`, and neither does this. `using`/`await using`
|
|
43
|
+
declarations are not var declarations; they form their own statement kind.
|
|
44
|
+
- **The `using` and awaited-boundary rules go beyond ESLint** — `padding-line-between-statements`
|
|
45
|
+
cannot express either, so on these the codemod is the spec.
|
|
37
46
|
- **`.d.ts` files are skipped.**
|
|
38
47
|
|
|
39
48
|
## CLI
|
package/dist/cli.js
CHANGED
|
@@ -53,7 +53,7 @@ function isIgnored(file, patterns) {
|
|
|
53
53
|
import { parseArgs } from "node:util";
|
|
54
54
|
function parseCLIArgs(argv) {
|
|
55
55
|
try {
|
|
56
|
-
const
|
|
56
|
+
const parsed = parseArgs({
|
|
57
57
|
args: [...argv],
|
|
58
58
|
options: {
|
|
59
59
|
check: { type: "boolean", default: false },
|
|
@@ -68,13 +68,13 @@ function parseCLIArgs(argv) {
|
|
|
68
68
|
strict: true
|
|
69
69
|
});
|
|
70
70
|
return {
|
|
71
|
-
mode: pickMode(values.check, values.dry),
|
|
72
|
-
quiet: values.quiet,
|
|
73
|
-
bench: values.bench,
|
|
74
|
-
help: values.help,
|
|
75
|
-
version: values.version,
|
|
76
|
-
ignore: values.ignore,
|
|
77
|
-
inputs: positionals
|
|
71
|
+
mode: pickMode(parsed.values.check, parsed.values.dry),
|
|
72
|
+
quiet: parsed.values.quiet,
|
|
73
|
+
bench: parsed.values.bench,
|
|
74
|
+
help: parsed.values.help,
|
|
75
|
+
version: parsed.values.version,
|
|
76
|
+
ignore: parsed.values.ignore,
|
|
77
|
+
inputs: parsed.positionals
|
|
78
78
|
};
|
|
79
79
|
} catch (error) {
|
|
80
80
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -190,6 +190,128 @@ function collectChildNodes(node) {
|
|
|
190
190
|
return children;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
// src/transform/needs-blank-line.ts
|
|
194
|
+
var CONTROL_FLOW_TYPES = new Set([
|
|
195
|
+
"IfStatement",
|
|
196
|
+
"ForStatement",
|
|
197
|
+
"ForInStatement",
|
|
198
|
+
"ForOfStatement",
|
|
199
|
+
"WhileStatement",
|
|
200
|
+
"SwitchStatement",
|
|
201
|
+
"TryStatement"
|
|
202
|
+
]);
|
|
203
|
+
function needsBlankLine(container, prev, next) {
|
|
204
|
+
if (container.type === "ClassBody") {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (isVarDecl(prev) && !isVarDecl(next) || isUsingDecl(prev) && !isUsingDecl(next)) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
if (next.type === "ReturnStatement") {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
if (isFnOrClassDecl(prev)) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
return isNewHeaded(prev) !== isNewHeaded(next) || isAwaitHeaded(prev) !== isAwaitHeaded(next) || isKindBoundary(prev, next) || CONTROL_FLOW_TYPES.has(next.type) || CONTROL_FLOW_TYPES.has(prev.type);
|
|
217
|
+
}
|
|
218
|
+
function isNewHeaded(node) {
|
|
219
|
+
return getStatementValues(node).some((value) => getExpressionHead(value).type === "NewExpression");
|
|
220
|
+
}
|
|
221
|
+
function getStatementValues(node) {
|
|
222
|
+
if (node.type === "VariableDeclaration") {
|
|
223
|
+
return collectASTNodes(node["declarations"]).map((declarator) => declarator["init"]).filter((init) => isASTNode(init));
|
|
224
|
+
}
|
|
225
|
+
const expression = node["expression"];
|
|
226
|
+
if (node.type !== "ExpressionStatement" || !isASTNode(expression)) {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
const right = expression["right"];
|
|
230
|
+
if (expression.type === "AssignmentExpression" && isASTNode(right)) {
|
|
231
|
+
return [right];
|
|
232
|
+
}
|
|
233
|
+
return [expression];
|
|
234
|
+
}
|
|
235
|
+
var HEAD_PROPERTY = {
|
|
236
|
+
CallExpression: "callee",
|
|
237
|
+
MemberExpression: "object",
|
|
238
|
+
ChainExpression: "expression",
|
|
239
|
+
AwaitExpression: "argument",
|
|
240
|
+
TSNonNullExpression: "expression",
|
|
241
|
+
ParenthesizedExpression: "expression"
|
|
242
|
+
};
|
|
243
|
+
function getExpressionHead(node) {
|
|
244
|
+
let current = node;
|
|
245
|
+
let property = HEAD_PROPERTY[current.type];
|
|
246
|
+
while (property !== undefined) {
|
|
247
|
+
const inner = current[property];
|
|
248
|
+
if (!isASTNode(inner)) {
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
current = inner;
|
|
252
|
+
property = HEAD_PROPERTY[current.type];
|
|
253
|
+
}
|
|
254
|
+
return current;
|
|
255
|
+
}
|
|
256
|
+
function isAwaitHeaded(node) {
|
|
257
|
+
if (node.type === "VariableDeclaration" && node.kind === "await using") {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
return getStatementValues(node).some((value) => hasAwaitInHeadChain(value));
|
|
261
|
+
}
|
|
262
|
+
function hasAwaitInHeadChain(node) {
|
|
263
|
+
let current = node;
|
|
264
|
+
while (current !== undefined) {
|
|
265
|
+
if (current.type === "AwaitExpression") {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
const property = HEAD_PROPERTY[current.type];
|
|
269
|
+
const inner = property === undefined ? undefined : current[property];
|
|
270
|
+
current = isASTNode(inner) ? inner : undefined;
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
var CALL_TYPES = ["CallExpression", "AwaitExpression"];
|
|
275
|
+
var MUTATION_TYPES = ["AssignmentExpression", "UpdateExpression"];
|
|
276
|
+
function isKindBoundary(prev, next) {
|
|
277
|
+
const prevKind = getStatementKind(prev);
|
|
278
|
+
const nextKind = getStatementKind(next);
|
|
279
|
+
return prevKind !== null && nextKind !== null && prevKind !== nextKind;
|
|
280
|
+
}
|
|
281
|
+
function getStatementKind(node) {
|
|
282
|
+
if (isUsingDecl(node)) {
|
|
283
|
+
return "using";
|
|
284
|
+
}
|
|
285
|
+
if (isVarDecl(node)) {
|
|
286
|
+
return "declaration";
|
|
287
|
+
}
|
|
288
|
+
if (isExpressionStatementOf(node, CALL_TYPES)) {
|
|
289
|
+
return "call";
|
|
290
|
+
}
|
|
291
|
+
return isExpressionStatementOf(node, MUTATION_TYPES) ? "mutation" : null;
|
|
292
|
+
}
|
|
293
|
+
function isExpressionStatementOf(node, types) {
|
|
294
|
+
const expression = node["expression"];
|
|
295
|
+
return node.type === "ExpressionStatement" && isASTNode(expression) && types.includes(expression.type);
|
|
296
|
+
}
|
|
297
|
+
var VAR_DECL_KINDS = new Set(["const", "let", "var"]);
|
|
298
|
+
function isVarDecl(node) {
|
|
299
|
+
if (node.type === "VariableDeclaration") {
|
|
300
|
+
return node.kind !== undefined && VAR_DECL_KINDS.has(node.kind);
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
var USING_DECL_KINDS = new Set(["using", "await using"]);
|
|
305
|
+
function isUsingDecl(node) {
|
|
306
|
+
if (node.type === "VariableDeclaration") {
|
|
307
|
+
return node.kind !== undefined && USING_DECL_KINDS.has(node.kind);
|
|
308
|
+
}
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
function isFnOrClassDecl(node) {
|
|
312
|
+
return node.type === "FunctionDeclaration" || node.type === "ClassDeclaration";
|
|
313
|
+
}
|
|
314
|
+
|
|
193
315
|
// src/transform/plan-gap-edit.ts
|
|
194
316
|
function planGapEdit(file, prev, next) {
|
|
195
317
|
const gap = buildGap(file, prev, next);
|
|
@@ -326,98 +448,6 @@ function isMultiline(src, node) {
|
|
|
326
448
|
return typeof node.start === "number" && typeof node.end === "number" && src.slice(node.start, node.end).includes(`
|
|
327
449
|
`);
|
|
328
450
|
}
|
|
329
|
-
var CONTROL_FLOW_TYPES = new Set([
|
|
330
|
-
"IfStatement",
|
|
331
|
-
"ForStatement",
|
|
332
|
-
"ForInStatement",
|
|
333
|
-
"ForOfStatement",
|
|
334
|
-
"WhileStatement",
|
|
335
|
-
"SwitchStatement",
|
|
336
|
-
"TryStatement"
|
|
337
|
-
]);
|
|
338
|
-
function needsBlankLine(container, prev, next) {
|
|
339
|
-
if (container.type === "ClassBody") {
|
|
340
|
-
return true;
|
|
341
|
-
}
|
|
342
|
-
if (isVarDecl(prev) && !isVarDecl(next)) {
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
if (next.type === "ReturnStatement") {
|
|
346
|
-
return true;
|
|
347
|
-
}
|
|
348
|
-
if (isFnOrClassDecl(prev)) {
|
|
349
|
-
return true;
|
|
350
|
-
}
|
|
351
|
-
return isNewHeaded(prev) !== isNewHeaded(next) || isKindBoundary(prev, next) || CONTROL_FLOW_TYPES.has(next.type) || CONTROL_FLOW_TYPES.has(prev.type);
|
|
352
|
-
}
|
|
353
|
-
function isNewHeaded(node) {
|
|
354
|
-
return getStatementValues(node).some((value) => getExpressionHead(value).type === "NewExpression");
|
|
355
|
-
}
|
|
356
|
-
function getStatementValues(node) {
|
|
357
|
-
if (node.type === "VariableDeclaration") {
|
|
358
|
-
return collectASTNodes(node["declarations"]).map((declarator) => declarator["init"]).filter((init) => isASTNode(init));
|
|
359
|
-
}
|
|
360
|
-
const { expression } = node;
|
|
361
|
-
if (node.type !== "ExpressionStatement" || !isASTNode(expression)) {
|
|
362
|
-
return [];
|
|
363
|
-
}
|
|
364
|
-
const { right } = expression;
|
|
365
|
-
if (expression.type === "AssignmentExpression" && isASTNode(right)) {
|
|
366
|
-
return [right];
|
|
367
|
-
}
|
|
368
|
-
return [expression];
|
|
369
|
-
}
|
|
370
|
-
var HEAD_PROPERTY = {
|
|
371
|
-
CallExpression: "callee",
|
|
372
|
-
MemberExpression: "object",
|
|
373
|
-
ChainExpression: "expression",
|
|
374
|
-
AwaitExpression: "argument",
|
|
375
|
-
TSNonNullExpression: "expression",
|
|
376
|
-
ParenthesizedExpression: "expression"
|
|
377
|
-
};
|
|
378
|
-
function getExpressionHead(node) {
|
|
379
|
-
let current = node;
|
|
380
|
-
let property = HEAD_PROPERTY[current.type];
|
|
381
|
-
while (property !== undefined) {
|
|
382
|
-
const inner = current[property];
|
|
383
|
-
if (!isASTNode(inner)) {
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
current = inner;
|
|
387
|
-
property = HEAD_PROPERTY[current.type];
|
|
388
|
-
}
|
|
389
|
-
return current;
|
|
390
|
-
}
|
|
391
|
-
var CALL_TYPES = ["CallExpression", "AwaitExpression"];
|
|
392
|
-
var MUTATION_TYPES = ["AssignmentExpression", "UpdateExpression"];
|
|
393
|
-
function isKindBoundary(prev, next) {
|
|
394
|
-
const prevKind = getStatementKind(prev);
|
|
395
|
-
const nextKind = getStatementKind(next);
|
|
396
|
-
return prevKind !== null && nextKind !== null && prevKind !== nextKind;
|
|
397
|
-
}
|
|
398
|
-
function getStatementKind(node) {
|
|
399
|
-
if (isVarDecl(node)) {
|
|
400
|
-
return "declaration";
|
|
401
|
-
}
|
|
402
|
-
if (isExpressionStatementOf(node, CALL_TYPES)) {
|
|
403
|
-
return "call";
|
|
404
|
-
}
|
|
405
|
-
return isExpressionStatementOf(node, MUTATION_TYPES) ? "mutation" : null;
|
|
406
|
-
}
|
|
407
|
-
function isExpressionStatementOf(node, types) {
|
|
408
|
-
const { expression } = node;
|
|
409
|
-
return node.type === "ExpressionStatement" && isASTNode(expression) && types.includes(expression.type);
|
|
410
|
-
}
|
|
411
|
-
var VAR_DECL_KINDS = new Set(["const", "let", "var"]);
|
|
412
|
-
function isVarDecl(node) {
|
|
413
|
-
if (node.type === "VariableDeclaration") {
|
|
414
|
-
return node.kind !== undefined && VAR_DECL_KINDS.has(node.kind);
|
|
415
|
-
}
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
function isFnOrClassDecl(node) {
|
|
419
|
-
return node.type === "FunctionDeclaration" || node.type === "ClassDeclaration";
|
|
420
|
-
}
|
|
421
451
|
|
|
422
452
|
// src/transform/parse-source.ts
|
|
423
453
|
import { parseSync } from "oxc-parser";
|
|
@@ -523,10 +553,11 @@ class MyersDiff {
|
|
|
523
553
|
return ops.toReversed();
|
|
524
554
|
}
|
|
525
555
|
unwindRound(pos, d) {
|
|
526
|
-
const
|
|
556
|
+
const previous = this.findPrevious(pos, d);
|
|
557
|
+
const prev = previous.prev;
|
|
527
558
|
const ops = this.buildEqualOps(pos, prev);
|
|
528
559
|
if (d > 0) {
|
|
529
|
-
const changeOp = moveDown ? { kind: "+", text: this.b[prev.y] ?? "" } : { kind: "-", text: this.a[prev.x] ?? "" };
|
|
560
|
+
const changeOp = previous.moveDown ? { kind: "+", text: this.b[prev.y] ?? "" } : { kind: "-", text: this.a[prev.x] ?? "" };
|
|
530
561
|
ops.push(changeOp);
|
|
531
562
|
}
|
|
532
563
|
return { ops, pos: prev };
|
|
@@ -541,7 +572,8 @@ class MyersDiff {
|
|
|
541
572
|
}
|
|
542
573
|
buildEqualOps(pos, prev) {
|
|
543
574
|
const ops = [];
|
|
544
|
-
let
|
|
575
|
+
let x = pos.x;
|
|
576
|
+
let y = pos.y;
|
|
545
577
|
while (x > prev.x && y > prev.y) {
|
|
546
578
|
ops.push({ kind: " ", text: this.a[x - 1] ?? "" });
|
|
547
579
|
x--;
|
|
@@ -571,12 +603,12 @@ function buildWindows(ops) {
|
|
|
571
603
|
return windows;
|
|
572
604
|
}
|
|
573
605
|
function buildHunk(ops, w) {
|
|
574
|
-
const
|
|
606
|
+
const preceding = countPrecedingLines(ops, w.from);
|
|
575
607
|
const slice = ops.slice(w.from, w.to);
|
|
576
608
|
const aCount = slice.filter((o) => o.kind !== "+").length;
|
|
577
609
|
const bCount = slice.filter((o) => o.kind !== "-").length;
|
|
578
610
|
return {
|
|
579
|
-
header: `@@ -${formatRange(aLine, aCount)} +${formatRange(bLine, bCount)} @@
|
|
611
|
+
header: `@@ -${formatRange(preceding.aLine, aCount)} +${formatRange(preceding.bLine, bCount)} @@
|
|
580
612
|
`,
|
|
581
613
|
lines: slice.map((o) => `${o.kind}${o.text}
|
|
582
614
|
`)
|
|
@@ -653,20 +685,23 @@ if (typeof parsedArgs === "string") {
|
|
|
653
685
|
console.error(parsedArgs);
|
|
654
686
|
process.exit(2);
|
|
655
687
|
}
|
|
656
|
-
var
|
|
657
|
-
|
|
688
|
+
var mode = parsedArgs.mode;
|
|
689
|
+
var quiet = parsedArgs.quiet;
|
|
690
|
+
var bench = parsedArgs.bench;
|
|
691
|
+
var inputs = parsedArgs.inputs;
|
|
692
|
+
if (parsedArgs.help || inputs.length === 0 && !parsedArgs.version) {
|
|
658
693
|
const exitCode = inputs.length === 0 ? 2 : 0;
|
|
659
694
|
printHelp();
|
|
660
695
|
process.exit(exitCode);
|
|
661
696
|
}
|
|
662
|
-
if (version) {
|
|
697
|
+
if (parsedArgs.version) {
|
|
663
698
|
const pkgPath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
664
699
|
process.stdout.write(`${readPackageVersion(pkgPath)}
|
|
665
700
|
`);
|
|
666
701
|
process.exit(0);
|
|
667
702
|
}
|
|
668
703
|
var t0 = Date.now();
|
|
669
|
-
var files = await expandInputs(inputs, ignore);
|
|
704
|
+
var files = await expandInputs(inputs, parsedArgs.ignore);
|
|
670
705
|
if (files.length === 0) {
|
|
671
706
|
console.error("no files matched");
|
|
672
707
|
process.exit(2);
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,128 @@ function collectChildNodes(node) {
|
|
|
40
40
|
return children;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// src/transform/needs-blank-line.ts
|
|
44
|
+
var CONTROL_FLOW_TYPES = new Set([
|
|
45
|
+
"IfStatement",
|
|
46
|
+
"ForStatement",
|
|
47
|
+
"ForInStatement",
|
|
48
|
+
"ForOfStatement",
|
|
49
|
+
"WhileStatement",
|
|
50
|
+
"SwitchStatement",
|
|
51
|
+
"TryStatement"
|
|
52
|
+
]);
|
|
53
|
+
function needsBlankLine(container, prev, next) {
|
|
54
|
+
if (container.type === "ClassBody") {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (isVarDecl(prev) && !isVarDecl(next) || isUsingDecl(prev) && !isUsingDecl(next)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (next.type === "ReturnStatement") {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (isFnOrClassDecl(prev)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return isNewHeaded(prev) !== isNewHeaded(next) || isAwaitHeaded(prev) !== isAwaitHeaded(next) || isKindBoundary(prev, next) || CONTROL_FLOW_TYPES.has(next.type) || CONTROL_FLOW_TYPES.has(prev.type);
|
|
67
|
+
}
|
|
68
|
+
function isNewHeaded(node) {
|
|
69
|
+
return getStatementValues(node).some((value) => getExpressionHead(value).type === "NewExpression");
|
|
70
|
+
}
|
|
71
|
+
function getStatementValues(node) {
|
|
72
|
+
if (node.type === "VariableDeclaration") {
|
|
73
|
+
return collectASTNodes(node["declarations"]).map((declarator) => declarator["init"]).filter((init) => isASTNode(init));
|
|
74
|
+
}
|
|
75
|
+
const expression = node["expression"];
|
|
76
|
+
if (node.type !== "ExpressionStatement" || !isASTNode(expression)) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
const right = expression["right"];
|
|
80
|
+
if (expression.type === "AssignmentExpression" && isASTNode(right)) {
|
|
81
|
+
return [right];
|
|
82
|
+
}
|
|
83
|
+
return [expression];
|
|
84
|
+
}
|
|
85
|
+
var HEAD_PROPERTY = {
|
|
86
|
+
CallExpression: "callee",
|
|
87
|
+
MemberExpression: "object",
|
|
88
|
+
ChainExpression: "expression",
|
|
89
|
+
AwaitExpression: "argument",
|
|
90
|
+
TSNonNullExpression: "expression",
|
|
91
|
+
ParenthesizedExpression: "expression"
|
|
92
|
+
};
|
|
93
|
+
function getExpressionHead(node) {
|
|
94
|
+
let current = node;
|
|
95
|
+
let property = HEAD_PROPERTY[current.type];
|
|
96
|
+
while (property !== undefined) {
|
|
97
|
+
const inner = current[property];
|
|
98
|
+
if (!isASTNode(inner)) {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
current = inner;
|
|
102
|
+
property = HEAD_PROPERTY[current.type];
|
|
103
|
+
}
|
|
104
|
+
return current;
|
|
105
|
+
}
|
|
106
|
+
function isAwaitHeaded(node) {
|
|
107
|
+
if (node.type === "VariableDeclaration" && node.kind === "await using") {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return getStatementValues(node).some((value) => hasAwaitInHeadChain(value));
|
|
111
|
+
}
|
|
112
|
+
function hasAwaitInHeadChain(node) {
|
|
113
|
+
let current = node;
|
|
114
|
+
while (current !== undefined) {
|
|
115
|
+
if (current.type === "AwaitExpression") {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
const property = HEAD_PROPERTY[current.type];
|
|
119
|
+
const inner = property === undefined ? undefined : current[property];
|
|
120
|
+
current = isASTNode(inner) ? inner : undefined;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
var CALL_TYPES = ["CallExpression", "AwaitExpression"];
|
|
125
|
+
var MUTATION_TYPES = ["AssignmentExpression", "UpdateExpression"];
|
|
126
|
+
function isKindBoundary(prev, next) {
|
|
127
|
+
const prevKind = getStatementKind(prev);
|
|
128
|
+
const nextKind = getStatementKind(next);
|
|
129
|
+
return prevKind !== null && nextKind !== null && prevKind !== nextKind;
|
|
130
|
+
}
|
|
131
|
+
function getStatementKind(node) {
|
|
132
|
+
if (isUsingDecl(node)) {
|
|
133
|
+
return "using";
|
|
134
|
+
}
|
|
135
|
+
if (isVarDecl(node)) {
|
|
136
|
+
return "declaration";
|
|
137
|
+
}
|
|
138
|
+
if (isExpressionStatementOf(node, CALL_TYPES)) {
|
|
139
|
+
return "call";
|
|
140
|
+
}
|
|
141
|
+
return isExpressionStatementOf(node, MUTATION_TYPES) ? "mutation" : null;
|
|
142
|
+
}
|
|
143
|
+
function isExpressionStatementOf(node, types) {
|
|
144
|
+
const expression = node["expression"];
|
|
145
|
+
return node.type === "ExpressionStatement" && isASTNode(expression) && types.includes(expression.type);
|
|
146
|
+
}
|
|
147
|
+
var VAR_DECL_KINDS = new Set(["const", "let", "var"]);
|
|
148
|
+
function isVarDecl(node) {
|
|
149
|
+
if (node.type === "VariableDeclaration") {
|
|
150
|
+
return node.kind !== undefined && VAR_DECL_KINDS.has(node.kind);
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
var USING_DECL_KINDS = new Set(["using", "await using"]);
|
|
155
|
+
function isUsingDecl(node) {
|
|
156
|
+
if (node.type === "VariableDeclaration") {
|
|
157
|
+
return node.kind !== undefined && USING_DECL_KINDS.has(node.kind);
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
function isFnOrClassDecl(node) {
|
|
162
|
+
return node.type === "FunctionDeclaration" || node.type === "ClassDeclaration";
|
|
163
|
+
}
|
|
164
|
+
|
|
43
165
|
// src/transform/plan-gap-edit.ts
|
|
44
166
|
function planGapEdit(file, prev, next) {
|
|
45
167
|
const gap = buildGap(file, prev, next);
|
|
@@ -176,98 +298,6 @@ function isMultiline(src, node) {
|
|
|
176
298
|
return typeof node.start === "number" && typeof node.end === "number" && src.slice(node.start, node.end).includes(`
|
|
177
299
|
`);
|
|
178
300
|
}
|
|
179
|
-
var CONTROL_FLOW_TYPES = new Set([
|
|
180
|
-
"IfStatement",
|
|
181
|
-
"ForStatement",
|
|
182
|
-
"ForInStatement",
|
|
183
|
-
"ForOfStatement",
|
|
184
|
-
"WhileStatement",
|
|
185
|
-
"SwitchStatement",
|
|
186
|
-
"TryStatement"
|
|
187
|
-
]);
|
|
188
|
-
function needsBlankLine(container, prev, next) {
|
|
189
|
-
if (container.type === "ClassBody") {
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
if (isVarDecl(prev) && !isVarDecl(next)) {
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
if (next.type === "ReturnStatement") {
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
if (isFnOrClassDecl(prev)) {
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
201
|
-
return isNewHeaded(prev) !== isNewHeaded(next) || isKindBoundary(prev, next) || CONTROL_FLOW_TYPES.has(next.type) || CONTROL_FLOW_TYPES.has(prev.type);
|
|
202
|
-
}
|
|
203
|
-
function isNewHeaded(node) {
|
|
204
|
-
return getStatementValues(node).some((value) => getExpressionHead(value).type === "NewExpression");
|
|
205
|
-
}
|
|
206
|
-
function getStatementValues(node) {
|
|
207
|
-
if (node.type === "VariableDeclaration") {
|
|
208
|
-
return collectASTNodes(node["declarations"]).map((declarator) => declarator["init"]).filter((init) => isASTNode(init));
|
|
209
|
-
}
|
|
210
|
-
const { expression } = node;
|
|
211
|
-
if (node.type !== "ExpressionStatement" || !isASTNode(expression)) {
|
|
212
|
-
return [];
|
|
213
|
-
}
|
|
214
|
-
const { right } = expression;
|
|
215
|
-
if (expression.type === "AssignmentExpression" && isASTNode(right)) {
|
|
216
|
-
return [right];
|
|
217
|
-
}
|
|
218
|
-
return [expression];
|
|
219
|
-
}
|
|
220
|
-
var HEAD_PROPERTY = {
|
|
221
|
-
CallExpression: "callee",
|
|
222
|
-
MemberExpression: "object",
|
|
223
|
-
ChainExpression: "expression",
|
|
224
|
-
AwaitExpression: "argument",
|
|
225
|
-
TSNonNullExpression: "expression",
|
|
226
|
-
ParenthesizedExpression: "expression"
|
|
227
|
-
};
|
|
228
|
-
function getExpressionHead(node) {
|
|
229
|
-
let current = node;
|
|
230
|
-
let property = HEAD_PROPERTY[current.type];
|
|
231
|
-
while (property !== undefined) {
|
|
232
|
-
const inner = current[property];
|
|
233
|
-
if (!isASTNode(inner)) {
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
current = inner;
|
|
237
|
-
property = HEAD_PROPERTY[current.type];
|
|
238
|
-
}
|
|
239
|
-
return current;
|
|
240
|
-
}
|
|
241
|
-
var CALL_TYPES = ["CallExpression", "AwaitExpression"];
|
|
242
|
-
var MUTATION_TYPES = ["AssignmentExpression", "UpdateExpression"];
|
|
243
|
-
function isKindBoundary(prev, next) {
|
|
244
|
-
const prevKind = getStatementKind(prev);
|
|
245
|
-
const nextKind = getStatementKind(next);
|
|
246
|
-
return prevKind !== null && nextKind !== null && prevKind !== nextKind;
|
|
247
|
-
}
|
|
248
|
-
function getStatementKind(node) {
|
|
249
|
-
if (isVarDecl(node)) {
|
|
250
|
-
return "declaration";
|
|
251
|
-
}
|
|
252
|
-
if (isExpressionStatementOf(node, CALL_TYPES)) {
|
|
253
|
-
return "call";
|
|
254
|
-
}
|
|
255
|
-
return isExpressionStatementOf(node, MUTATION_TYPES) ? "mutation" : null;
|
|
256
|
-
}
|
|
257
|
-
function isExpressionStatementOf(node, types) {
|
|
258
|
-
const { expression } = node;
|
|
259
|
-
return node.type === "ExpressionStatement" && isASTNode(expression) && types.includes(expression.type);
|
|
260
|
-
}
|
|
261
|
-
var VAR_DECL_KINDS = new Set(["const", "let", "var"]);
|
|
262
|
-
function isVarDecl(node) {
|
|
263
|
-
if (node.type === "VariableDeclaration") {
|
|
264
|
-
return node.kind !== undefined && VAR_DECL_KINDS.has(node.kind);
|
|
265
|
-
}
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
function isFnOrClassDecl(node) {
|
|
269
|
-
return node.type === "FunctionDeclaration" || node.type === "ClassDeclaration";
|
|
270
|
-
}
|
|
271
301
|
|
|
272
302
|
// src/transform/parse-source.ts
|
|
273
303
|
import { parseSync } from "oxc-parser";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ASTNode } from '../types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The "always" rules: a blank line between class members, after a var or
|
|
4
|
+
* using block before a statement of any other kind, before a return, after a
|
|
5
|
+
* function/class declaration, on both sides of a control-flow block — its
|
|
6
|
+
* closing brace ends a visual unit just like its opening keyword starts one —
|
|
7
|
+
* and at the boundary between statement kinds: call vs mutation,
|
|
8
|
+
* instantiation vs anything else, and awaited vs non-awaited. There are no
|
|
9
|
+
* "never" rules, so any match means one blank.
|
|
10
|
+
*/
|
|
11
|
+
export declare function needsBlankLine(container: ASTNode, prev: ASTNode, next: ASTNode): boolean;
|