forked from actions-rs/clippy-check
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
45310b9
commit a992ca0
Showing
5 changed files
with
288 additions
and
118 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import * as core from "@actions/core"; | ||
import * as exec from "@actions/exec"; | ||
import { Cargo, Cross } from "@actions-rs-plus/core"; | ||
|
||
import type * as input from "./input"; | ||
import { OutputParser } from "./outputParser"; | ||
import { Reporter } from "./reporter"; | ||
import type { AnnotationWithMessageAndLevel, Context, Stats } from "./schema"; | ||
|
||
type Program = Cargo | Cross; | ||
|
||
interface ClippyResult { | ||
stats: Stats; | ||
annotations: AnnotationWithMessageAndLevel[]; | ||
exitCode: number; | ||
} | ||
|
||
async function buildContext(program: Program): Promise<Context> { | ||
const context: Context = { | ||
cargo: "", | ||
clippy: "", | ||
rustc: "", | ||
}; | ||
|
||
await Promise.all([ | ||
await exec.exec("rustc", ["-V"], { | ||
silent: true, | ||
listeners: { | ||
stdout: (buffer: Buffer) => { | ||
return (context.rustc = buffer.toString().trim()); | ||
}, | ||
}, | ||
}), | ||
await program.call(["-V"], { | ||
silent: true, | ||
listeners: { | ||
stdout: (buffer: Buffer) => { | ||
return (context.cargo = buffer.toString().trim()); | ||
}, | ||
}, | ||
}), | ||
await program.call(["clippy", "-V"], { | ||
silent: true, | ||
listeners: { | ||
stdout: (buffer: Buffer) => { | ||
return (context.clippy = buffer.toString().trim()); | ||
}, | ||
}, | ||
}), | ||
]); | ||
|
||
return context; | ||
} | ||
|
||
async function runClippy(actionInput: input.ParsedInput, program: Program): Promise<ClippyResult> { | ||
const args = buildArgs(actionInput); | ||
const outputParser = new OutputParser(); | ||
|
||
let exitCode = 0; | ||
|
||
try { | ||
core.startGroup("Executing cargo clippy (JSON output)"); | ||
exitCode = await program.call(args, { | ||
ignoreReturnCode: true, | ||
failOnStdErr: false, | ||
listeners: { | ||
stdline: (line: string) => { | ||
outputParser.tryParseClippyLine(line); | ||
}, | ||
}, | ||
}); | ||
} finally { | ||
core.endGroup(); | ||
} | ||
|
||
return { | ||
stats: outputParser.stats, | ||
annotations: outputParser.annotations, | ||
exitCode, | ||
}; | ||
} | ||
|
||
function getProgram(useCross: boolean): Promise<Program> { | ||
if (useCross) { | ||
return Cross.getOrInstall(); | ||
} else { | ||
return Cargo.get(); | ||
} | ||
} | ||
|
||
export async function run(actionInput: input.ParsedInput): Promise<void> { | ||
const program: Program = await getProgram(actionInput.useCross); | ||
|
||
const context = await buildContext(program); | ||
|
||
const { stats, annotations, exitCode } = await runClippy(actionInput, program); | ||
|
||
await new Reporter().report(stats, annotations, context); | ||
|
||
if (exitCode !== 0) { | ||
throw new Error(`Clippy had exited with the ${exitCode} exit code`); | ||
} | ||
} | ||
|
||
function buildArgs(actionInput: input.ParsedInput): string[] { | ||
const args: string[] = []; | ||
|
||
// Toolchain selection MUST go first in any condition | ||
if (actionInput.toolchain) { | ||
args.push(`+${actionInput.toolchain}`); | ||
} | ||
|
||
args.push("clippy"); | ||
|
||
// `--message-format=json` should just right after the `cargo clippy` | ||
// because usually people are adding the `-- -D warnings` at the end | ||
// of arguments and it will mess up the output. | ||
args.push("--message-format=json"); | ||
|
||
return args.concat(actionInput.args); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import * as exec from "@actions/exec"; | ||
|
||
import { run } from "clippy"; | ||
import { type ParsedInput } from "input"; | ||
import { Reporter } from "reporter"; | ||
import { type CargoMessage } from "schema"; | ||
|
||
jest.mock("@actions/core"); | ||
jest.mock("@actions/exec"); | ||
jest.mock("reporter"); | ||
|
||
describe("clippy", () => { | ||
it("runs with cargo", async () => { | ||
jest.spyOn(exec, "exec").mockResolvedValue(0); | ||
|
||
const actionInput: ParsedInput = { | ||
toolchain: "stable", | ||
args: [], | ||
useCross: false, | ||
}; | ||
|
||
await expect(run(actionInput)).resolves.toBeUndefined(); | ||
}); | ||
|
||
it("runs with cross", async () => { | ||
jest.spyOn(exec, "exec").mockResolvedValue(0); | ||
|
||
const actionInput: ParsedInput = { | ||
toolchain: "stable", | ||
args: [], | ||
useCross: true, | ||
}; | ||
|
||
await expect(run(actionInput)).resolves.toBeUndefined(); | ||
}); | ||
|
||
it("reports when clippy fails", async () => { | ||
jest.spyOn(exec, "exec").mockImplementation((_commandline: string, args?: string[] | undefined) => { | ||
const expected = ["clippy", "--message-format=json"]; | ||
|
||
if ( | ||
(args ?? []).length > 0 && | ||
expected.every((c) => { | ||
return args?.includes(c); | ||
}) | ||
) { | ||
return Promise.resolve(101); | ||
} else { | ||
return Promise.resolve(0); | ||
} | ||
}); | ||
|
||
const actionInput: ParsedInput = { | ||
toolchain: "stable", | ||
args: [], | ||
useCross: false, | ||
}; | ||
|
||
await expect(run(actionInput)).rejects.toThrow(/Clippy had exited with the (\d)+ exit code/); | ||
}); | ||
|
||
it("records versions", async () => { | ||
const reportSpy = jest.spyOn(Reporter.prototype, "report"); | ||
jest.spyOn(exec, "exec").mockImplementation((commandline: string, args?: string[], options?: exec.ExecOptions) => { | ||
if (commandline.endsWith("cargo")) { | ||
if (args?.[0] === "-V") { | ||
options?.listeners?.stdout?.(Buffer.from("cargo version")); | ||
} else if (args?.[0] === "clippy" && args?.[1] === "-V") { | ||
options?.listeners?.stdout?.(Buffer.from("clippy version")); | ||
} | ||
} else if (commandline === "rustc" && args?.[0] === "-V") { | ||
options?.listeners?.stdout?.(Buffer.from("rustc version")); | ||
} | ||
return Promise.resolve(0); | ||
}); | ||
|
||
const actionInput: ParsedInput = { | ||
toolchain: "stable", | ||
args: [], | ||
useCross: false, | ||
}; | ||
|
||
await expect(run(actionInput)).resolves.toBeUndefined(); | ||
|
||
expect(reportSpy).toBeCalledWith({ error: 0, help: 0, ice: 0, note: 0, warning: 0 }, [], { cargo: "cargo version", clippy: "clippy version", rustc: "rustc version" }); | ||
}); | ||
|
||
it("clippy captures stdout", async () => { | ||
jest.spyOn(exec, "exec").mockImplementation((_commandline: string, args?: string[] | undefined, options?: exec.ExecOptions) => { | ||
const expected = ["clippy", "--message-format=json"]; | ||
|
||
if ( | ||
(args ?? []).length > 0 && | ||
expected.every((c) => { | ||
return args?.includes(c); | ||
}) | ||
) { | ||
const data: CargoMessage = { | ||
reason: "compiler-message", | ||
message: { | ||
code: "500", | ||
level: "warning", | ||
message: "message", | ||
rendered: "rendered", | ||
spans: [{ is_primary: true, file_name: "main.rs", line_start: 12, line_end: 12, column_start: 30, column_end: 45 }], | ||
}, | ||
}; | ||
options?.listeners?.stdline?.(JSON.stringify(data)); | ||
} | ||
|
||
return Promise.resolve(0); | ||
}); | ||
|
||
const actionInput: ParsedInput = { | ||
toolchain: "stable", | ||
args: [], | ||
useCross: false, | ||
}; | ||
|
||
await expect(run(actionInput)).resolves.toBeUndefined(); | ||
}); | ||
}); |
Oops, something went wrong.