Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ Options:
of union) (default: "union")
--tsc-only use the tsc resolver only — opt out of Jelly
edges (overrides --call-graph-provider)
-f, --format <fmt> output format: json (default) | msgpack (not
yet implemented) (default: "json")
-c, --cache-dir <dir> cache/intermediate directory
-v, --verbose increase verbosity (repeatable)
-h, --help display help for command
Expand Down
12 changes: 12 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export function buildProgram(): Command {
"union",
)
.option("--tsc-only", "use the tsc resolver only — opt out of Jelly edges (overrides --call-graph-provider)")
.option("-f, --format <fmt>", "output format: json (default) | msgpack (not yet implemented)", "json")
.option("-c, --cache-dir <dir>", "cache/intermediate directory")
.option("-v, --verbose", "increase verbosity (repeatable)", (_v: string, prev: number) => prev + 1, 0)
.allowExcessArguments(true);
Expand Down Expand Up @@ -98,6 +99,16 @@ export function parseArgs(argv: string[]): AnalysisOptions {
level = 4; // force max implemented depth so the projected graph is the full CPG
}

// --format: strict validation — an unrecognized value must error, never silently fall back to
// json; msgpack is a recognized-but-unimplemented value and must error too, not silently degrade.
const format = String(o.format ?? "json");
if (!["json", "msgpack"].includes(format)) {
program.error(`error: invalid --format '${format}' (expected: json, msgpack)`);
}
if (format === "msgpack") {
program.error("error: msgpack output is not yet implemented; use --format json");
}

// --graphs: strict validation (never a silent fallback). Default = all rungs at or below the level;
// cfg/dfg/pdg require -a 3, sdg requires -a 4.
let graphs: GraphSelector[] = level >= 4 ? [...ALL_GRAPHS] : level === 3 ? ["cfg", "dfg", "pdg"] : [];
Expand Down Expand Up @@ -158,6 +169,7 @@ export function parseArgs(argv: string[]): AnalysisOptions {
input: o.input ? path.resolve(String(o.input)) : "",
output: o.output ? path.resolve(String(o.output)) : null,
emit,
format: format as "json",
appName: o.appName ? String(o.appName) : null,
neo4jUri: o.neo4jUri ? String(o.neo4jUri) : null,
neo4jUser: String(o.neo4jUser),
Expand Down
5 changes: 5 additions & 0 deletions src/options/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export interface AnalysisOptions {
output: string | null;
/** Output target: json (analysis.json, default) or neo4j (graph.cypher / live Bolt push). */
emit: EmitTarget;
/**
* Output serialization format (`-f/--format`). Only "json" is implemented; msgpack is rejected
* at the CLI layer (`parseArgs`), so it never reaches here — the type reflects that invariant.
*/
format: "json";
/** Logical application name for the graph's :Application anchor; null ⇒ derived from input. */
appName: string | null;
/** Bolt URI for a live Neo4j push (incremental). null ⇒ write a graph.cypher snapshot to -o. */
Expand Down
30 changes: 30 additions & 0 deletions test/dataflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function options(level: 1 | 2 | 3, cacheDir: string, jobs: number): AnalysisOpti
input: FIXTURE,
output: null,
emit: "json",
format: "json",
appName: null,
neo4jUri: null,
neo4jUser: "neo4j",
Expand Down Expand Up @@ -423,3 +424,32 @@ describe("--graphs flag validation", () => {
expect(r.stderr.toString()).toContain("invalid --jobs");
});
});

describe("--format flag validation", () => {
const cli = (...args: string[]) =>
Bun.spawnSync(["bun", "run", path.resolve(import.meta.dir, "../src/index.ts"), ...args], {
cwd: path.resolve(import.meta.dir, ".."),
});

test("--format msgpack is rejected — not yet implemented, never a silent fallback to json", () => {
const r = cli("-i", FIXTURE, "--format", "msgpack", "--no-build");
expect(r.exitCode).not.toBe(0);
expect(r.stderr.toString()).toContain("msgpack output is not yet implemented");
});

test("unknown --format value fails with a clear error", () => {
const r = cli("-i", FIXTURE, "--format", "bogus", "--no-build");
expect(r.exitCode).not.toBe(0);
expect(r.stderr.toString()).toContain("invalid --format 'bogus'");
});

test("--format json succeeds (exit 0)", () => {
const r = cli("-i", FIXTURE, "--format", "json", "--no-build");
expect(r.exitCode).toBe(0);
});

test("omitting --format succeeds (defaults to json)", () => {
const r = cli("-i", FIXTURE, "--no-build");
expect(r.exitCode).toBe(0);
});
});
1 change: 1 addition & 0 deletions test/neo4j-bolt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function optsFor(overrides: Partial<AnalysisOptions> = {}): AnalysisOptions {
input: FIXTURE,
output: null,
emit: "json",
format: "json",
appName: null,
neo4jUri: null,
neo4jUser: "neo4j",
Expand Down
2 changes: 1 addition & 1 deletion test/neo4j-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async function fixtureRows() {
const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), "cants-schema-test-"));
// --emit neo4j is always full-depth, so exercise every node/edge kind at L4.
const opts: AnalysisOptions = {
input: FIXTURE, output: null, emit: "neo4j", appName: "dataflow-app",
input: FIXTURE, output: null, emit: "neo4j", format: "json", appName: "dataflow-app",
neo4jUri: null, neo4jUser: "neo4j", neo4jPassword: "", neo4jDatabase: null,
analysisLevel: 4, graphs: ["cfg", "dfg", "pdg", "sdg"], graphFieldDepth: 3, jobs: 1,
targetFiles: null, skipTests: true, eager: true,
Expand Down
1 change: 1 addition & 0 deletions test/schema-v2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function options(): AnalysisOptions {
input: FIXTURE,
output: null,
emit: "json",
format: "json",
appName: null,
neo4jUri: null,
neo4jUser: "neo4j",
Expand Down