Skip to content

Commit 8f027d7

Browse files
committed
feat(eslint): add no-pointless-reassignments rule
- Create custom ESLint rule to detect pointless variable reassignments - A pointless reassignment is when a const/let is assigned to a single identifier with no transformation (e.g., `const x = y;` where y is already the target) - Exempt underscore-prefixed variables (exhaustiveness check patterns) - Fix all 16 violations throughout the codebase by: - Removing unnecessary schema aliases in CLI commands - Refactoring json-to-md parentDir initialization to use ternary - Converting remove-node's let-based accumulator to immutable updates object - Removing unused type alias export (TraceNode const) All 458 tests pass. Linting now 100% clean with new rule enabled.
1 parent 03db894 commit 8f027d7

13 files changed

Lines changed: 97 additions & 92 deletions

File tree

eslint.config.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,42 @@ const indexReExportsOnly: Rule.RuleModule = {
7575
},
7676
};
7777

78+
const noPointlessReassignments: Rule.RuleModule = {
79+
meta: {
80+
type: "problem",
81+
messages: {
82+
pointlessReassignment:
83+
"Pointless reassignment. {{ name }} is just an alias for {{ value }}. Use the original directly instead.",
84+
},
85+
},
86+
create(context) {
87+
return {
88+
VariableDeclarator(node) {
89+
if (node.id.type !== "Identifier" || node.init?.type !== "Identifier") {
90+
return;
91+
}
92+
// Skip intentional patterns (underscore prefix = exhaustiveness checks, etc.)
93+
if (node.id.name.startsWith("_")) {
94+
return;
95+
}
96+
context.report({
97+
node,
98+
messageId: "pointlessReassignment",
99+
data: {
100+
name: node.id.name,
101+
value: node.init.name,
102+
},
103+
});
104+
},
105+
};
106+
},
107+
};
108+
78109
const barrelPlugin = {
79110
rules: {
80111
"no-re-exports": noReExports,
81112
"index-re-exports-only": indexReExportsOnly,
113+
"no-pointless-reassignments": noPointlessReassignments,
82114
},
83115
};
84116

@@ -135,6 +167,7 @@ export default defineConfig(
135167
],
136168
"barrel/no-re-exports": "error",
137169
"barrel/index-re-exports-only": "error",
170+
"barrel/no-pointless-reassignments": "error",
138171
"eslint-comments/no-use": "error",
139172
},
140173
},

src/cli/commands/check.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ import type { CommandDef } from "../define-command.js";
22
import { checkOp } from "../../operations/index.js";
33
import { noArgs, readOpts, loadDoc } from "../shared.js";
44

5-
const optsSchema = readOpts;
6-
7-
export const checkCommand: CommandDef<typeof noArgs, typeof optsSchema> = {
5+
export const checkCommand: CommandDef<typeof noArgs, typeof readOpts> = {
86
name: "check",
97
description: checkOp.def.description,
108
apiLink: checkOp.def.name,
11-
opts: optsSchema,
9+
opts: readOpts,
1210
action(_args, opts) {
1311
const { doc } = loadDoc(opts.path);
1412
const result = checkOp({ doc });

src/cli/commands/json2md.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,8 @@ export const json2mdCommand: CommandDef = {
4343
action(args: unknown, opts: unknown) {
4444
if (!isArgs(args)) throw new Error("Invalid args");
4545
if (!isOpts(opts)) throw new Error("Invalid opts");
46-
const typedArgs = args;
47-
const typedOpts = opts;
48-
const inputPath = resolve(typedArgs.input);
49-
const outputPath = resolve(typedArgs.output);
46+
const inputPath = resolve(args.input);
47+
const outputPath = resolve(args.output);
5048

5149
const raw: unknown = JSON.parse(readFileSync(inputPath, "utf8"));
5250

@@ -62,7 +60,7 @@ export const json2mdCommand: CommandDef = {
6260
}
6361

6462
const form =
65-
typedOpts.singleFile || outputPath.endsWith(".md")
63+
opts.singleFile || outputPath.endsWith(".md")
6664
? "single-file"
6765
: "multi-doc";
6866

src/cli/commands/md2json.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@ export const md2jsonCommand: CommandDef = {
2828
opts: z.object({}).strict(),
2929
action(args: unknown) {
3030
if (!isArgs(args)) throw new Error("Invalid args");
31-
const typedArgs = args;
32-
const inputPath = resolve(typedArgs.input);
33-
const outputPath = resolve(typedArgs.output);
31+
const inputPath = resolve(args.input);
32+
const outputPath = resolve(args.output);
3433

3534
const doc = markdownToJson(inputPath);
3635
writeFileSync(outputPath, canonicalise(doc, { indent: "\t" }) + "\n");

src/cli/commands/query.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ const nodesOpts = readOpts.extend({
9696
const nodeArgs = z.object({
9797
id: z.string().describe("node ID to retrieve"),
9898
});
99-
const nodeOpts = readOpts;
10099

101100
const relsOpts = readOpts.extend({
102101
from: z.string().optional().describe("filter relationships by source node"),
@@ -107,7 +106,6 @@ const relsOpts = readOpts.extend({
107106
const traceArgs = z.object({
108107
id: z.string().describe("node ID to start trace from"),
109108
});
110-
const traceOpts = readOpts;
111109

112110
const timelineOpts = readOpts.extend({
113111
node: z.string().optional().describe("filter events to a specific node"),
@@ -116,7 +114,6 @@ const timelineOpts = readOpts.extend({
116114
const stateAtArgs = z.object({
117115
time: z.string().describe("ISO timestamp to query"),
118116
});
119-
const stateAtOpts = readOpts;
120117

121118
// ---------------------------------------------------------------------------
122119
// Subcommands
@@ -145,10 +142,10 @@ const nodeSubcommand: CommandDef = {
145142
description: queryNodeOp.def.description,
146143
apiLink: queryNodeOp.def.name,
147144
args: nodeArgs,
148-
opts: nodeOpts,
145+
opts: readOpts,
149146
action(rawArgs: unknown, rawOpts: unknown) {
150147
const args = nodeArgs.parse(rawArgs);
151-
const opts = nodeOpts.parse(rawOpts);
148+
const opts = readOpts.parse(rawOpts);
152149
const { doc } = loadDoc(opts.path);
153150
const result = queryNodeOp({ doc, id: args.id });
154151
if (!result) {
@@ -205,10 +202,10 @@ const traceSubcommand: CommandDef = {
205202
description: traceFromNodeOp.def.description,
206203
apiLink: traceFromNodeOp.def.name,
207204
args: traceArgs,
208-
opts: traceOpts,
205+
opts: readOpts,
209206
action(rawArgs: unknown, rawOpts: unknown) {
210207
const args = traceArgs.parse(rawArgs);
211-
const opts = traceOpts.parse(rawOpts);
208+
const opts = readOpts.parse(rawOpts);
212209
const { doc } = loadDoc(opts.path);
213210
const trace = traceFromNodeOp({ doc, startId: args.id });
214211
if (opts.json) {
@@ -251,10 +248,10 @@ const stateAtSubcommand: CommandDef = {
251248
description: stateAtOp.def.description,
252249
apiLink: stateAtOp.def.name,
253250
args: stateAtArgs,
254-
opts: stateAtOpts,
251+
opts: readOpts,
255252
action(rawArgs: unknown, rawOpts: unknown) {
256253
const args = stateAtArgs.parse(rawArgs);
257-
const opts = stateAtOpts.parse(rawOpts);
254+
const opts = readOpts.parse(rawOpts);
258255
const { doc } = loadDoc(opts.path);
259256
const result = stateAtOp({ doc, timestamp: args.time });
260257
if (opts.json) {

src/cli/commands/rename.ts

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,32 @@ const argsSchema = z.object({
88
newId: z.string().describe("New node ID"),
99
});
1010

11-
const optsSchema = mutationOpts;
11+
export const renameCommand: CommandDef<typeof argsSchema, typeof mutationOpts> =
12+
{
13+
name: "rename",
14+
description: renameOp.def.description,
15+
apiLink: renameOp.def.name,
16+
args: argsSchema,
17+
opts: mutationOpts,
18+
action(args, opts) {
19+
try {
20+
const loaded = loadDoc(opts.path);
21+
const { doc } = loaded;
22+
const updated = renameOp({ doc, oldId: args.oldId, newId: args.newId });
23+
persistDoc(updated, loaded, opts);
1224

13-
export const renameCommand: CommandDef<typeof argsSchema, typeof optsSchema> = {
14-
name: "rename",
15-
description: renameOp.def.description,
16-
apiLink: renameOp.def.name,
17-
args: argsSchema,
18-
opts: optsSchema,
19-
action(args, opts) {
20-
try {
21-
const loaded = loadDoc(opts.path);
22-
const { doc } = loaded;
23-
const updated = renameOp({ doc, oldId: args.oldId, newId: args.newId });
24-
persistDoc(updated, loaded, opts);
25-
26-
if (opts.json) {
27-
console.log(
28-
JSON.stringify({ oldId: args.oldId, newId: args.newId }, null, 2),
29-
);
30-
} else {
31-
console.log(
32-
`${opts.dryRun ? "[dry-run] Would rename" : "Renamed"} ${args.oldId}${args.newId}`,
33-
);
25+
if (opts.json) {
26+
console.log(
27+
JSON.stringify({ oldId: args.oldId, newId: args.newId }, null, 2),
28+
);
29+
} else {
30+
console.log(
31+
`${opts.dryRun ? "[dry-run] Would rename" : "Renamed"} ${args.oldId}${args.newId}`,
32+
);
33+
}
34+
} catch (err: unknown) {
35+
console.error(err instanceof Error ? err.message : String(err));
36+
process.exit(1);
3437
}
35-
} catch (err: unknown) {
36-
console.error(err instanceof Error ? err.message : String(err));
37-
process.exit(1);
38-
}
39-
},
40-
};
38+
},
39+
};

src/cli/commands/search.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@ const argsSchema = z.object({
88
term: z.string().describe("Search term"),
99
});
1010

11-
const optsSchema = readOpts;
12-
13-
export const searchCommand: CommandDef<typeof argsSchema, typeof optsSchema> = {
11+
export const searchCommand: CommandDef<typeof argsSchema, typeof readOpts> = {
1412
name: "search",
1513
description: searchOp.def.description,
1614
apiLink: searchOp.def.name,
1715
args: argsSchema,
18-
opts: optsSchema,
16+
opts: readOpts,
1917
action(args, opts) {
2018
const { doc } = loadDoc(opts.path);
2119
const matches = searchOp({ doc, term: args.term });

src/cli/commands/stats.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ import type { CommandDef } from "../define-command.js";
33
import { statsOp } from "../../operations/index.js";
44
import { noArgs, readOpts, loadDoc } from "../shared.js";
55

6-
const optsSchema = readOpts;
7-
8-
export const statsCommand: CommandDef<typeof noArgs, typeof optsSchema> = {
6+
export const statsCommand: CommandDef<typeof noArgs, typeof readOpts> = {
97
name: "stats",
108
description: statsOp.def.description,
119
apiLink: statsOp.def.name,
12-
opts: optsSchema,
10+
opts: readOpts,
1311
action(_args, opts) {
1412
const { doc } = loadDoc(opts.path);
1513
const s = statsOp({ doc });

src/cli/commands/update.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,12 @@ const addRelArgs = z.object({
5454
type: RelationshipType.describe("relationship type"),
5555
to: z.string().describe("target node ID"),
5656
});
57-
const addRelOpts = mutationOpts;
5857

5958
const removeRelArgs = z.object({
6059
from: z.string().describe("source node ID"),
6160
type: RelationshipType.describe("relationship type"),
6261
to: z.string().describe("target node ID"),
6362
});
64-
const removeRelOpts = mutationOpts;
6563

6664
const metaOpts = mutationOpts.extend({
6765
fields: z
@@ -141,10 +139,10 @@ const addRelSubcommand: CommandDef = {
141139
description: addRelationshipOp.def.description,
142140
apiLink: addRelationshipOp.def.name,
143141
args: addRelArgs,
144-
opts: addRelOpts,
142+
opts: mutationOpts,
145143
action(rawArgs: unknown, rawOpts: unknown) {
146144
const args = addRelArgs.parse(rawArgs);
147-
const opts = addRelOpts.parse(rawOpts);
145+
const opts = mutationOpts.parse(rawOpts);
148146
const loaded = loadDoc(opts.path);
149147
const { doc } = loaded;
150148

@@ -173,10 +171,10 @@ const removeRelSubcommand: CommandDef = {
173171
description: removeRelationshipOp.def.description,
174172
apiLink: removeRelationshipOp.def.name,
175173
args: removeRelArgs,
176-
opts: removeRelOpts,
174+
opts: mutationOpts,
177175
action(rawArgs: unknown, rawOpts: unknown) {
178176
const args = removeRelArgs.parse(rawArgs);
179-
const opts = removeRelOpts.parse(rawOpts);
177+
const opts = mutationOpts.parse(rawOpts);
180178
const loaded = loadDoc(opts.path);
181179
const { doc } = loaded;
182180

src/cli/commands/validate.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ import type { CommandDef } from "../define-command.js";
33
import { validateOp } from "../../operations/index.js";
44
import { noArgs, readOpts, loadDoc } from "../shared.js";
55

6-
const optsSchema = readOpts;
7-
8-
export const validateCommand: CommandDef<typeof noArgs, typeof optsSchema> = {
6+
export const validateCommand: CommandDef<typeof noArgs, typeof readOpts> = {
97
name: "validate",
108
description: validateOp.def.description,
119
apiLink: validateOp.def.name,
12-
opts: optsSchema,
10+
opts: readOpts,
1311
action(_args, opts) {
1412
const { doc } = loadDoc(opts.path);
1513
const result = validateOp({ doc });

0 commit comments

Comments
 (0)