@zgeoff/format-codemod 0.0.3 → 0.0.5
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 +23 -4
- package/dist/cli/load-format-ignore.d.ts +8 -0
- package/dist/cli.js +48 -25
- package/dist/index.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,10 +50,11 @@ no "never" rules.
|
|
|
50
50
|
```bash
|
|
51
51
|
format-codemod [options] <file|dir|glob> ...
|
|
52
52
|
|
|
53
|
-
--check
|
|
54
|
-
--dry
|
|
55
|
-
--bench
|
|
56
|
-
--quiet
|
|
53
|
+
--check exit 1 if any file would change; do not write
|
|
54
|
+
--dry print unified diff to stdout; do not write
|
|
55
|
+
--bench print parse stats as JSON to stderr
|
|
56
|
+
--quiet only print files that would change
|
|
57
|
+
--ignore <glob> skip files matching the glob (repeatable)
|
|
57
58
|
--version
|
|
58
59
|
--help
|
|
59
60
|
```
|
|
@@ -79,6 +80,24 @@ file is processed once.
|
|
|
79
80
|
`--dry` prints a real unified diff — `@@` hunk headers with three lines of context — that applies
|
|
80
81
|
cleanly with `patch(1)`.
|
|
81
82
|
|
|
83
|
+
### Ignoring files
|
|
84
|
+
|
|
85
|
+
A `.formatignore` file in the working directory supplies ignore globs without bloating the command
|
|
86
|
+
line — one per line, with blank lines and `#`-prefixed comment lines skipped:
|
|
87
|
+
|
|
88
|
+
```gitignore
|
|
89
|
+
# committed codegen
|
|
90
|
+
src/generated/**
|
|
91
|
+
|
|
92
|
+
# build output
|
|
93
|
+
dist/**
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Each line uses the same glob semantics as `--ignore` (Node's `path.matchesGlob`, tried against both
|
|
97
|
+
the expanded path and the path relative to the working directory). It is not a gitignore dialect:
|
|
98
|
+
`!` negation and directory-anchoring rules are not supported. `--ignore` flags remain available and
|
|
99
|
+
merge additively with the file's globs.
|
|
100
|
+
|
|
82
101
|
## Dialect handling
|
|
83
102
|
|
|
84
103
|
The filename picks the parse dialect: `.tsx` enables JSX, everything else parses as plain
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads `.formatignore` from the given directory: one glob per line, blank
|
|
3
|
+
* lines and `#`-prefixed comment lines skipped. Lines share --ignore's glob
|
|
4
|
+
* semantics — this is not a gitignore dialect, so `!` negation and directory
|
|
5
|
+
* anchoring don't apply. A missing file yields an empty list, so callers
|
|
6
|
+
* merge unconditionally.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadFormatIgnore(dir: string): readonly string[];
|
package/dist/cli.js
CHANGED
|
@@ -49,11 +49,23 @@ function isIgnored(file, patterns) {
|
|
|
49
49
|
return patterns.some((pattern) => path.matchesGlob(file, pattern) || path.matchesGlob(relative, pattern));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// src/cli/load-format-ignore.ts
|
|
53
|
+
import fs2 from "node:fs";
|
|
54
|
+
import path2 from "node:path";
|
|
55
|
+
function loadFormatIgnore(dir) {
|
|
56
|
+
const file = path2.join(dir, ".formatignore");
|
|
57
|
+
if (!fs2.existsSync(file)) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
return fs2.readFileSync(file, "utf8").split(`
|
|
61
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
62
|
+
}
|
|
63
|
+
|
|
52
64
|
// src/cli/parse-cli-args.ts
|
|
53
65
|
import { parseArgs } from "node:util";
|
|
54
66
|
function parseCLIArgs(argv) {
|
|
55
67
|
try {
|
|
56
|
-
const
|
|
68
|
+
const parsed = parseArgs({
|
|
57
69
|
args: [...argv],
|
|
58
70
|
options: {
|
|
59
71
|
check: { type: "boolean", default: false },
|
|
@@ -68,13 +80,13 @@ function parseCLIArgs(argv) {
|
|
|
68
80
|
strict: true
|
|
69
81
|
});
|
|
70
82
|
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
|
|
83
|
+
mode: pickMode(parsed.values.check, parsed.values.dry),
|
|
84
|
+
quiet: parsed.values.quiet,
|
|
85
|
+
bench: parsed.values.bench,
|
|
86
|
+
help: parsed.values.help,
|
|
87
|
+
version: parsed.values.version,
|
|
88
|
+
ignore: parsed.values.ignore,
|
|
89
|
+
inputs: parsed.positionals
|
|
78
90
|
};
|
|
79
91
|
} catch (error) {
|
|
80
92
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -103,6 +115,11 @@ options:
|
|
|
103
115
|
--version
|
|
104
116
|
--help
|
|
105
117
|
|
|
118
|
+
files:
|
|
119
|
+
.formatignore read from the working directory when present: one glob per
|
|
120
|
+
line, blank lines and # comments skipped, merged with
|
|
121
|
+
--ignore
|
|
122
|
+
|
|
106
123
|
exit codes:
|
|
107
124
|
0 clean
|
|
108
125
|
1 files would change (--check)
|
|
@@ -133,9 +150,9 @@ function printReport(file, report, quiet) {
|
|
|
133
150
|
}
|
|
134
151
|
|
|
135
152
|
// src/cli/read-package-version.ts
|
|
136
|
-
import
|
|
153
|
+
import fs3 from "node:fs";
|
|
137
154
|
function readPackageVersion(pkgPath) {
|
|
138
|
-
const parsed = JSON.parse(
|
|
155
|
+
const parsed = JSON.parse(fs3.readFileSync(pkgPath, "utf8"));
|
|
139
156
|
if (!isPackageJSON(parsed)) {
|
|
140
157
|
throw new TypeError("Invalid package.json");
|
|
141
158
|
}
|
|
@@ -146,7 +163,7 @@ function isPackageJSON(value) {
|
|
|
146
163
|
}
|
|
147
164
|
|
|
148
165
|
// src/cli/check-file.ts
|
|
149
|
-
import
|
|
166
|
+
import fs5 from "node:fs";
|
|
150
167
|
|
|
151
168
|
// src/transform/apply-edits.ts
|
|
152
169
|
function applyEdits(src, edits) {
|
|
@@ -222,11 +239,11 @@ function getStatementValues(node) {
|
|
|
222
239
|
if (node.type === "VariableDeclaration") {
|
|
223
240
|
return collectASTNodes(node["declarations"]).map((declarator) => declarator["init"]).filter((init) => isASTNode(init));
|
|
224
241
|
}
|
|
225
|
-
const
|
|
242
|
+
const expression = node["expression"];
|
|
226
243
|
if (node.type !== "ExpressionStatement" || !isASTNode(expression)) {
|
|
227
244
|
return [];
|
|
228
245
|
}
|
|
229
|
-
const
|
|
246
|
+
const right = expression["right"];
|
|
230
247
|
if (expression.type === "AssignmentExpression" && isASTNode(right)) {
|
|
231
248
|
return [right];
|
|
232
249
|
}
|
|
@@ -291,7 +308,7 @@ function getStatementKind(node) {
|
|
|
291
308
|
return isExpressionStatementOf(node, MUTATION_TYPES) ? "mutation" : null;
|
|
292
309
|
}
|
|
293
310
|
function isExpressionStatementOf(node, types) {
|
|
294
|
-
const
|
|
311
|
+
const expression = node["expression"];
|
|
295
312
|
return node.type === "ExpressionStatement" && isASTNode(expression) && types.includes(expression.type);
|
|
296
313
|
}
|
|
297
314
|
var VAR_DECL_KINDS = new Set(["const", "let", "var"]);
|
|
@@ -478,7 +495,7 @@ function transform(src, options) {
|
|
|
478
495
|
}
|
|
479
496
|
|
|
480
497
|
// src/cli/apply-diff-mode.ts
|
|
481
|
-
import
|
|
498
|
+
import fs4 from "node:fs";
|
|
482
499
|
|
|
483
500
|
// src/cli/build-unified-diff.ts
|
|
484
501
|
function buildUnifiedDiff(before, after, label) {
|
|
@@ -553,10 +570,11 @@ class MyersDiff {
|
|
|
553
570
|
return ops.toReversed();
|
|
554
571
|
}
|
|
555
572
|
unwindRound(pos, d) {
|
|
556
|
-
const
|
|
573
|
+
const previous = this.findPrevious(pos, d);
|
|
574
|
+
const prev = previous.prev;
|
|
557
575
|
const ops = this.buildEqualOps(pos, prev);
|
|
558
576
|
if (d > 0) {
|
|
559
|
-
const changeOp = moveDown ? { kind: "+", text: this.b[prev.y] ?? "" } : { kind: "-", text: this.a[prev.x] ?? "" };
|
|
577
|
+
const changeOp = previous.moveDown ? { kind: "+", text: this.b[prev.y] ?? "" } : { kind: "-", text: this.a[prev.x] ?? "" };
|
|
560
578
|
ops.push(changeOp);
|
|
561
579
|
}
|
|
562
580
|
return { ops, pos: prev };
|
|
@@ -571,7 +589,8 @@ class MyersDiff {
|
|
|
571
589
|
}
|
|
572
590
|
buildEqualOps(pos, prev) {
|
|
573
591
|
const ops = [];
|
|
574
|
-
let
|
|
592
|
+
let x = pos.x;
|
|
593
|
+
let y = pos.y;
|
|
575
594
|
while (x > prev.x && y > prev.y) {
|
|
576
595
|
ops.push({ kind: " ", text: this.a[x - 1] ?? "" });
|
|
577
596
|
x--;
|
|
@@ -601,12 +620,12 @@ function buildWindows(ops) {
|
|
|
601
620
|
return windows;
|
|
602
621
|
}
|
|
603
622
|
function buildHunk(ops, w) {
|
|
604
|
-
const
|
|
623
|
+
const preceding = countPrecedingLines(ops, w.from);
|
|
605
624
|
const slice = ops.slice(w.from, w.to);
|
|
606
625
|
const aCount = slice.filter((o) => o.kind !== "+").length;
|
|
607
626
|
const bCount = slice.filter((o) => o.kind !== "-").length;
|
|
608
627
|
return {
|
|
609
|
-
header: `@@ -${formatRange(aLine, aCount)} +${formatRange(bLine, bCount)} @@
|
|
628
|
+
header: `@@ -${formatRange(preceding.aLine, aCount)} +${formatRange(preceding.bLine, bCount)} @@
|
|
610
629
|
`,
|
|
611
630
|
lines: slice.map((o) => `${o.kind}${o.text}
|
|
612
631
|
`)
|
|
@@ -640,13 +659,13 @@ function applyDiffMode(edit, mode) {
|
|
|
640
659
|
if (mode === "dry") {
|
|
641
660
|
return { message: null, stdout: buildUnifiedDiff(edit.src, edit.result.output, edit.file) };
|
|
642
661
|
}
|
|
643
|
-
|
|
662
|
+
fs4.writeFileSync(edit.file, edit.result.output);
|
|
644
663
|
return { message: `WROTE ${edit.file} ${edit.result.edits} edit(s)`, stdout: null };
|
|
645
664
|
}
|
|
646
665
|
|
|
647
666
|
// src/cli/check-file.ts
|
|
648
667
|
function checkFile(file, mode) {
|
|
649
|
-
const src =
|
|
668
|
+
const src = fs5.readFileSync(file, "utf8");
|
|
650
669
|
const bytes = Buffer.byteLength(src);
|
|
651
670
|
const result = transform(src, { filename: file });
|
|
652
671
|
if (result.parseError !== null) {
|
|
@@ -683,19 +702,23 @@ if (typeof parsedArgs === "string") {
|
|
|
683
702
|
console.error(parsedArgs);
|
|
684
703
|
process.exit(2);
|
|
685
704
|
}
|
|
686
|
-
var
|
|
687
|
-
|
|
705
|
+
var mode = parsedArgs.mode;
|
|
706
|
+
var quiet = parsedArgs.quiet;
|
|
707
|
+
var bench = parsedArgs.bench;
|
|
708
|
+
var inputs = parsedArgs.inputs;
|
|
709
|
+
if (parsedArgs.help || inputs.length === 0 && !parsedArgs.version) {
|
|
688
710
|
const exitCode = inputs.length === 0 ? 2 : 0;
|
|
689
711
|
printHelp();
|
|
690
712
|
process.exit(exitCode);
|
|
691
713
|
}
|
|
692
|
-
if (version) {
|
|
714
|
+
if (parsedArgs.version) {
|
|
693
715
|
const pkgPath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
694
716
|
process.stdout.write(`${readPackageVersion(pkgPath)}
|
|
695
717
|
`);
|
|
696
718
|
process.exit(0);
|
|
697
719
|
}
|
|
698
720
|
var t0 = Date.now();
|
|
721
|
+
var ignore = [...loadFormatIgnore(process.cwd()), ...parsedArgs.ignore];
|
|
699
722
|
var files = await expandInputs(inputs, ignore);
|
|
700
723
|
if (files.length === 0) {
|
|
701
724
|
console.error("no files matched");
|
package/dist/index.js
CHANGED
|
@@ -72,11 +72,11 @@ function getStatementValues(node) {
|
|
|
72
72
|
if (node.type === "VariableDeclaration") {
|
|
73
73
|
return collectASTNodes(node["declarations"]).map((declarator) => declarator["init"]).filter((init) => isASTNode(init));
|
|
74
74
|
}
|
|
75
|
-
const
|
|
75
|
+
const expression = node["expression"];
|
|
76
76
|
if (node.type !== "ExpressionStatement" || !isASTNode(expression)) {
|
|
77
77
|
return [];
|
|
78
78
|
}
|
|
79
|
-
const
|
|
79
|
+
const right = expression["right"];
|
|
80
80
|
if (expression.type === "AssignmentExpression" && isASTNode(right)) {
|
|
81
81
|
return [right];
|
|
82
82
|
}
|
|
@@ -141,7 +141,7 @@ function getStatementKind(node) {
|
|
|
141
141
|
return isExpressionStatementOf(node, MUTATION_TYPES) ? "mutation" : null;
|
|
142
142
|
}
|
|
143
143
|
function isExpressionStatementOf(node, types) {
|
|
144
|
-
const
|
|
144
|
+
const expression = node["expression"];
|
|
145
145
|
return node.type === "ExpressionStatement" && isASTNode(expression) && types.includes(expression.type);
|
|
146
146
|
}
|
|
147
147
|
var VAR_DECL_KINDS = new Set(["const", "let", "var"]);
|