diff --git a/README.md b/README.md index 2215bb32..8ab76171 100644 --- a/README.md +++ b/README.md @@ -19,19 +19,19 @@ to install the most recent `nightly` clippy version. on: push name: Clippy check jobs: - clippy_check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - components: clippy - override: true - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + components: clippy + override: true + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features ``` ### With stable clippy @@ -40,23 +40,24 @@ jobs: on: push name: Clippy check jobs: - clippy_check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - run: rustup component add clippy - - uses: actions-rs/clippy-check@v1 - with: - args: --all-features + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - run: rustup component add clippy + - uses: actions-rs/clippy-check@v1 + with: + args: --all-features ``` ## Inputs -| Name | Required | Description | Type | Default | -| ------------| :------: | ---------------------------------------------------------------------------------------------------------------------------------------| ------ | --------| -| `toolchain` | | Rust toolchain to use; override or system default toolchain will be used if omitted | string | | -| `args` | | Arguments for the `cargo clippy` command | string | | -| `use-cross` | | Use [`cross`](https://github.com/rust-embedded/cross) instead of `cargo` | bool | false | +| Name | Required | Description | Type | Default | +| ------------------- | :------: | ----------------------------------------------------------------------------------- | ------ | ------- | +| `toolchain` | | Rust toolchain to use; override or system default toolchain will be used if omitted | string | | +| `args` | | Arguments for the `cargo clippy` command | string | | +| `use-cross` | | Use [`cross`](https://github.com/rust-embedded/cross) instead of `cargo` | bool | false | +| `working-directory` | | Specify where rust directory is | string | . | For extra details about the `toolchain`, `args` and `use-cross` inputs, see [`cargo` Action](https://github.com/actions-rs/cargo#inputs) documentation. diff --git a/action.yml b/action.yml index 3b1ee9fa..04eb189a 100644 --- a/action.yml +++ b/action.yml @@ -15,6 +15,9 @@ inputs: use-cross: description: Use cross instead of cargo required: false + working-directory: + description: Specify where rust directory is. By default runs in root directory + required: false runs: using: "node20" diff --git a/src/clippy.ts b/src/clippy.ts index 7c7b4963..4aae3e7a 100644 --- a/src/clippy.ts +++ b/src/clippy.ts @@ -1,3 +1,5 @@ +import { join } from "path"; + import * as core from "@actions/core"; import * as exec from "@actions/exec"; import { Cargo, Cross } from "@actions-rs-plus/core"; @@ -56,19 +58,25 @@ async function runClippy(actionInput: input.ParsedInput, program: Program): Prom const args = buildArgs(actionInput); const outputParser = new OutputParser(); + const options: exec.ExecOptions = { + ignoreReturnCode: true, + failOnStdErr: false, + listeners: { + stdline: (line: string) => { + outputParser.tryParseClippyLine(line); + }, + }, + }; + + if (actionInput.workingDirectory) { + options.cwd = join(process.cwd(), actionInput.workingDirectory); + } + 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); - }, - }, - }); + exitCode = await program.call(args, options); } finally { core.endGroup(); } diff --git a/src/input.ts b/src/input.ts index 049f1302..b28f8db1 100644 --- a/src/input.ts +++ b/src/input.ts @@ -6,6 +6,7 @@ export interface ParsedInput { toolchain: string | undefined; args: string[]; useCross: boolean; + workingDirectory: string | undefined; } export function get(): ParsedInput { @@ -18,6 +19,7 @@ export function get(): ParsedInput { return { args: stringArgv(input.getInput("args")), useCross: input.getInputBool("use-cross"), - toolchain: toolchain !== "" ? toolchain : undefined, + workingDirectory: input.getInput("working-directory") || undefined, + toolchain: toolchain || undefined, }; } diff --git a/src/outputParser.ts b/src/outputParser.ts index 87954c56..c07495c4 100644 --- a/src/outputParser.ts +++ b/src/outputParser.ts @@ -1,13 +1,17 @@ +import { join } from "path"; + import * as core from "@actions/core"; import type { AnnotationWithMessageAndLevel, CargoMessage, MaybeCargoMessage, Stats } from "./schema"; import { AnnotationLevel } from "./schema"; export class OutputParser { + private readonly _workingDirectory: string | null; private readonly _uniqueAnnotations: Map; private readonly _stats: Stats; - public constructor() { + public constructor(workingDirectory?: string) { + this._workingDirectory = workingDirectory ?? null; this._uniqueAnnotations = new Map(); this._stats = { ice: 0, @@ -47,7 +51,7 @@ export class OutputParser { const cargoMessage = contents as CargoMessage; - const parsedAnnotation = OutputParser.makeAnnotation(cargoMessage); + const parsedAnnotation = this.makeAnnotation(cargoMessage); const key = JSON.stringify(parsedAnnotation); @@ -93,7 +97,7 @@ export class OutputParser { /// Convert parsed JSON line into the GH annotation object /// /// https://developer.github.com/v3/checks/runs/#annotations-object - private static makeAnnotation(contents: CargoMessage): AnnotationWithMessageAndLevel { + private makeAnnotation(contents: CargoMessage): AnnotationWithMessageAndLevel { const primarySpan = contents.message.spans.find((span) => { return span.is_primary; }); @@ -103,11 +107,17 @@ export class OutputParser { throw new Error("Unable to find primary span for message"); } + let path = primarySpan.file_name; + + if (this._workingDirectory) { + path = join(this._workingDirectory, path); + } + const annotation: AnnotationWithMessageAndLevel = { level: OutputParser.parseLevel(contents.message.level), message: contents.message.rendered, properties: { - file: primarySpan.file_name, + file: path, startLine: primarySpan.line_start, endLine: primarySpan.line_end, title: contents.message.message, diff --git a/src/tests/clippy.test.ts b/src/tests/clippy.test.ts index fa9c6776..4a72850b 100644 --- a/src/tests/clippy.test.ts +++ b/src/tests/clippy.test.ts @@ -17,6 +17,7 @@ describe("clippy", () => { toolchain: "stable", args: [], useCross: false, + workingDirectory: undefined, }; await expect(run(actionInput)).resolves.toBeUndefined(); @@ -29,6 +30,7 @@ describe("clippy", () => { toolchain: "stable", args: [], useCross: true, + workingDirectory: undefined, }; await expect(run(actionInput)).resolves.toBeUndefined(); @@ -54,6 +56,7 @@ describe("clippy", () => { toolchain: "stable", args: [], useCross: false, + workingDirectory: undefined, }; await expect(run(actionInput)).rejects.toThrow(/Clippy had exited with the (\d)+ exit code/); @@ -78,6 +81,7 @@ describe("clippy", () => { toolchain: "stable", args: [], useCross: false, + workingDirectory: undefined, }; await expect(run(actionInput)).resolves.toBeUndefined(); @@ -115,6 +119,7 @@ describe("clippy", () => { toolchain: "stable", args: [], useCross: false, + workingDirectory: "./my/sources/are/here", }; await expect(run(actionInput)).resolves.toBeUndefined(); diff --git a/src/tests/input.test.ts b/src/tests/input.test.ts index 7fc27195..3eb20892 100644 --- a/src/tests/input.test.ts +++ b/src/tests/input.test.ts @@ -12,26 +12,26 @@ describe("input", () => { }); test("get 1, parses defaults", () => { - expect(get()).toStrictEqual({ args: [], toolchain: undefined, useCross: false }); + expect(get()).toStrictEqual({ args: [], toolchain: undefined, useCross: false, workingDirectory: undefined }); }); test("get 2, can use cross", () => { process.env["INPUT_USE-CROSS"] = "true"; - expect(get()).toStrictEqual({ args: [], toolchain: undefined, useCross: true }); + expect(get()).toStrictEqual({ args: [], toolchain: undefined, useCross: true, workingDirectory: undefined }); }); test("get 3, parses toolchain", () => { process.env["INPUT_TOOLCHAIN"] = "nightly"; - expect(get()).toStrictEqual({ args: [], toolchain: "nightly", useCross: false }); + expect(get()).toStrictEqual({ args: [], toolchain: "nightly", useCross: false, workingDirectory: undefined }); }); test("get 4, parses +toolchain to toolchain", () => { process.env["INPUT_TOOLCHAIN"] = "+nightly"; - expect(get()).toStrictEqual({ args: [], toolchain: "nightly", useCross: false }); + expect(get()).toStrictEqual({ args: [], toolchain: "nightly", useCross: false, workingDirectory: undefined }); }); test("get 5, parses arguments", () => { process.env["INPUT_ARGS"] = "--all-features --all-targets"; - expect(get()).toStrictEqual({ args: ["--all-features", "--all-targets"], toolchain: undefined, useCross: false }); + expect(get()).toStrictEqual({ args: ["--all-features", "--all-targets"], toolchain: undefined, useCross: false, workingDirectory: undefined }); }); }); diff --git a/src/tests/outputParser.test.ts b/src/tests/outputParser.test.ts index 171df07b..d9722e1a 100644 --- a/src/tests/outputParser.test.ts +++ b/src/tests/outputParser.test.ts @@ -134,6 +134,35 @@ describe("outputParser", () => { }).toThrow(/Unable to find primary span for message/); }); + it("parses annotations into AnnotationWithMessageAndLevel", () => { + const outputParser = new OutputParser("./my/sources/are/here"); + + outputParser.tryParseClippyLine( + JSON.stringify({ + reason: defaultMessage.reason, + message: { + ...defaultMessage.message, + level: "error", + }, + }), + ); + + expect(outputParser.annotations).toEqual([ + { + level: 0, + message: "rendered", + properties: { + endColumn: 15, + endLine: 30, + file: "my/sources/are/here/main.rs", + startColumn: 10, + startLine: 30, + title: "message", + }, + }, + ]); + }); + it("parses annotations into AnnotationWithMessageAndLevel", () => { const outputParser = new OutputParser(); @@ -146,6 +175,7 @@ describe("outputParser", () => { }, }), ); + outputParser.tryParseClippyLine( JSON.stringify({ reason: defaultMessage.reason,