diff --git a/src/github.js b/src/github.js index 28d642f7..af6d453f 100644 --- a/src/github.js +++ b/src/github.js @@ -3,8 +3,6 @@ const { name: actionName } = require("../package"); const { getEnv, getInput, log } = require("./utils/action"); const request = require("./utils/request"); -const ANNOTATION_LEVELS = ["notice", "warning", "failure"]; - /** * Returns information about the GitHub repository and action trigger event * @@ -51,25 +49,24 @@ function getContext() { /** * Creates a new check on GitHub which annotates the relevant commit with linting errors - * * @param checkName {string}: Name which will be displayed in the check list * @param sha {string}: SHA of the commit which should be annotated * @param context {{actor: string, branch: string, event: object, eventName: string, repository: * string, token: string, username: string, workspace: string}}: Object information about the GitHub * repository and action trigger event - * @param results {object[]}: Results from the linter execution + * @param lintResult {{isSuccess: boolean, warning: [], error: []}}: Parsed lint result * @param summary {string}: Summary for the GitHub check */ -async function createCheck(checkName, sha, context, results, summary) { +async function createCheck(checkName, sha, context, lintResult, summary) { let annotations = []; - for (let level = 0; level < 3; level += 1) { + for (const level of ["warning", "error"]) { annotations = [ ...annotations, - ...results[level].map(result => ({ + ...lintResult[level].map(result => ({ path: result.path, start_line: result.firstLine, end_line: result.lastLine, - annotation_level: ANNOTATION_LEVELS[level], + annotation_level: level === "warning" ? "warning" : "failure", message: result.message, })), ]; @@ -78,7 +75,7 @@ async function createCheck(checkName, sha, context, results, summary) { // Only use the first 50 annotations (limit for a single API request) if (annotations.length > 50) { log( - `There are more than 50 errors/warnings from ${checkName}. Annotations are created for the first 50 results only.`, + `There are more than 50 errors/warnings from ${checkName}. Annotations are created for the first 50 violations only.`, ); annotations = annotations.slice(0, 50); } @@ -86,7 +83,7 @@ async function createCheck(checkName, sha, context, results, summary) { const body = { name: checkName, head_sha: sha, - conclusion: results[2].length === 0 ? "success" : "failure", + conclusion: lintResult.isSuccess ? "success" : "failure", output: { title: checkName, summary, diff --git a/src/index.js b/src/index.js index 59d76e8f..2f6ef918 100644 --- a/src/index.js +++ b/src/index.js @@ -57,17 +57,24 @@ async function runAction() { log( `Linting ${autoFix ? "and auto-fixing " : ""}files in ${lintDirAbs} with ${linter.name}…`, ); - const results = linter.lint(lintDirAbs, fileExtList, autoFix); - const resultsParsed = linter.parseResults(context.workspace, results); + const lintOutput = linter.lint(lintDirAbs, fileExtList, autoFix); + + // Parse output of linting command + const lintResult = linter.parseOutput(context.workspace, lintOutput); + log( + `Linting result of ${linter.name} is considered a ${ + lintResult.isSuccess ? "success" : "failure" + }`, + ); // Build and log a summary of linting errors/warnings let summary; - if (resultsParsed[1].length > 0 && resultsParsed[2].length > 0) { - summary = `Found ${resultsParsed[2].length} errors and ${resultsParsed[1].length} warnings with ${linter.name}`; - } else if (resultsParsed[2].length > 0) { - summary = `Found ${resultsParsed[2].length} errors with ${linter.name}`; - } else if (resultsParsed[1].length > 0) { - summary = `Found ${resultsParsed[1].length} warnings with ${linter.name}`; + if (lintResult.warning.length > 0 && lintResult.error.length > 0) { + summary = `Found ${lintResult.error.length} errors and ${lintResult.warning.length} warnings with ${linter.name}`; + } else if (lintResult.error.length > 0) { + summary = `Found ${lintResult.error.length} errors with ${linter.name}`; + } else if (lintResult.warning.length > 0) { + summary = `Found ${lintResult.warning.length} warnings with ${linter.name}`; } else { summary = `No code style issues found with ${linter.name}`; } @@ -79,7 +86,7 @@ async function runAction() { git.pushChanges(context); } - checks.push({ checkName: linter.name, resultsParsed, summary }); + checks.push({ checkName: linter.name, lintResult, summary }); } } @@ -89,8 +96,8 @@ async function runAction() { log(""); // Create empty line in logs const headSha = git.getHeadSha(); await Promise.all( - checks.map(({ checkName, resultsParsed, summary }) => - github.createCheck(checkName, headSha, context, resultsParsed, summary), + checks.map(({ checkName, lintResult, summary }) => + github.createCheck(checkName, headSha, context, lintResult, summary), ), ); } diff --git a/src/linters/black.js b/src/linters/black.js index 126c79e8..055cf9db 100644 --- a/src/linters/black.js +++ b/src/linters/black.js @@ -1,6 +1,7 @@ const commandExists = require("../../vendor/command-exists"); const { run } = require("../utils/action"); -const { diffToParsedResults } = require("../utils/diff"); +const { parseErrorsFromDiff } = require("../utils/diff"); +const { initLintResult } = require("../utils/lint-result"); /** * https://black.readthedocs.io @@ -11,9 +12,7 @@ class Black { } /** - * Verifies that all required programs are installed. Exits the GitHub action if one of the - * programs is missing - * + * Verifies that all required programs are installed. Throws an error if programs are missing * @param {string} dir: Directory to run the linting program in */ static async verifySetup(dir) { @@ -30,28 +29,33 @@ class Black { /** * Runs the linting program and returns the command output - * - * @param {string} dir: Directory to run the linting program in - * @param {string[]} extensions: Array of file extensions which should be linted + * @param {string} dir: Directory to run the linter in + * @param {string[]} extensions: File extensions which should be linted * @param {boolean} fix: Whether the linter should attempt to fix code style issues automatically - * @returns {string}: Results of the linting process + * @returns {{status: number, stdout: string, stderr: string}}: Output of the lint command */ static lint(dir, extensions, fix = false) { - return run(`black ${fix ? "" : "--diff"} --include "^.*\\.(${extensions.join("|")})$" "."`, { - dir, - ignoreErrors: true, - }).stdout; + return run( + `black ${fix ? "" : "--check --diff"} --include "^.*\\.(${extensions.join("|")})$" "."`, + { + dir, + ignoreErrors: true, + }, + ); } /** - * Parses the results of the linting process and returns it as a processable array - * - * @param {string} dir: Directory in which the linting program has been run - * @param {string} results: Results of the linting process - * @returns {object[]}: Parsed results + * Parses the output of the lint command. Determines the success of the lint process and the + * severity of the identified code style violations + * @param {string} dir: Directory in which the linter has been run + * @param {{status: number, stdout: string, stderr: string}} output: Output of the lint command + * @returns {{isSuccess: boolean, warning: [], error: []}}: Parsed lint result */ - static parseResults(dir, results) { - return diffToParsedResults(results); + static parseOutput(dir, output) { + const lintResult = initLintResult(); + lintResult.error = parseErrorsFromDiff(output.stdout); + lintResult.isSuccess = output.status === 0; + return lintResult; } } diff --git a/src/linters/eslint.js b/src/linters/eslint.js index 109d15a8..da6ad728 100644 --- a/src/linters/eslint.js +++ b/src/linters/eslint.js @@ -1,5 +1,6 @@ const commandExists = require("../../vendor/command-exists"); const { run } = require("../utils/action"); +const { initLintResult } = require("../utils/lint-result"); const { removeTrailingPeriod } = require("../utils/string"); /** @@ -11,9 +12,7 @@ class ESLint { } /** - * Verifies that all required programs are installed. Exits the GitHub action if one of the - * programs is missing - * + * Verifies that all required programs are installed. Throws an error if programs are missing * @param {string} dir: Directory to run the linting program in */ static async verifySetup(dir) { @@ -32,11 +31,10 @@ class ESLint { /** * Runs the linting program and returns the command output - * - * @param {string} dir: Directory to run the linting program in - * @param {string[]} extensions: Array of file extensions which should be linted + * @param {string} dir: Directory to run the linter in + * @param {string[]} extensions: File extensions which should be linted * @param {boolean} fix: Whether the linter should attempt to fix code style issues automatically - * @returns {string}: Results of the linting process + * @returns {{status: number, stdout: string, stderr: string}}: Output of the lint command */ static lint(dir, extensions, fix = false) { return run( @@ -47,37 +45,42 @@ class ESLint { dir, ignoreErrors: true, }, - ).stdout; + ); } /** - * Parses the results of the linting process and returns it as a processable array - * - * @param {string} dir: Directory in which the linting program has been run - * @param {string} results: Results of the linting process - * @returns {object[]}: Parsed results + * Parses the output of the lint command. Determines the success of the lint process and the + * severity of the identified code style violations + * @param {string} dir: Directory in which the linter has been run + * @param {{status: number, stdout: string, stderr: string}} output: Output of the lint command + * @returns {{isSuccess: boolean, warning: [], error: []}}: Parsed lint result */ - static parseResults(dir, results) { - const resultsJson = JSON.parse(results); - - // Parsed results: [notices, warnings, failures] - const resultsParsed = [[], [], []]; + static parseOutput(dir, output) { + const lintResult = initLintResult(); + lintResult.isSuccess = output.status === 0; - for (const result of resultsJson) { - const { filePath, messages } = result; + const outputJson = JSON.parse(output.stdout); + for (const violation of outputJson) { + const { filePath, messages } = violation; const path = filePath.substring(dir.length + 1); for (const msg of messages) { const { line, message, ruleId, severity } = msg; - resultsParsed[severity].push({ + const entry = { path, firstLine: line, lastLine: line, message: `${removeTrailingPeriod(message)} (${ruleId})`, - }); + }; + if (severity === 1) { + lintResult.warning.push(entry); + } + if (severity === 2) { + lintResult.error.push(entry); + } } } - return resultsParsed; + return lintResult; } } diff --git a/src/linters/flake8.js b/src/linters/flake8.js index 04a26204..4ff3e8de 100644 --- a/src/linters/flake8.js +++ b/src/linters/flake8.js @@ -1,6 +1,7 @@ const commandExists = require("../../vendor/command-exists"); const { sep } = require("path"); const { log, run } = require("../utils/action"); +const { initLintResult } = require("../utils/lint-result"); const { capitalizeFirstLetter } = require("../utils/string"); const PARSE_REGEX = /^(.*):([0-9]+):[0-9]+: (\w*) (.*)$/gm; @@ -14,9 +15,7 @@ class Flake8 { } /** - * Verifies that all required programs are installed. Exits the GitHub action if one of the - * programs is missing - * + * Verifies that all required programs are installed. Throws an error if programs are missing * @param {string} dir: Directory to run the linting program in */ static async verifySetup(dir) { @@ -33,11 +32,10 @@ class Flake8 { /** * Runs the linting program and returns the command output - * - * @param {string} dir: Directory to run the linting program in - * @param {string[]} extensions: Array of file extensions which should be linted + * @param {string} dir: Directory to run the linter in + * @param {string[]} extensions: File extensions which should be linted * @param {boolean} fix: Whether the linter should attempt to fix code style issues automatically - * @returns {string}: Results of the linting process + * @returns {{status: number, stdout: string, stderr: string}}: Output of the lint command */ static lint(dir, extensions, fix = false) { if (fix) { @@ -46,27 +44,26 @@ class Flake8 { return run(`flake8 --filename ${extensions.map(ext => `"**${sep}*.${ext}"`).join(",")}`, { dir, ignoreErrors: true, - }).stdout; + }); } /** - * Parses the results of the linting process and returns it as a processable array - * - * @param {string} dir: Directory in which the linting program has been run - * @param {string} results: Results of the linting process - * @returns {object[]}: Parsed results + * Parses the output of the lint command. Determines the success of the lint process and the + * severity of the identified code style violations + * @param {string} dir: Directory in which the linter has been run + * @param {{status: number, stdout: string, stderr: string}} output: Output of the lint command + * @returns {{isSuccess: boolean, warning: [], error: []}}: Parsed lint result */ - static parseResults(dir, results) { - const matches = results.matchAll(PARSE_REGEX); - - // Parsed results: [notices, warnings, failures] - const resultsParsed = [[], [], []]; + static parseOutput(dir, output) { + const lintResult = initLintResult(); + lintResult.isSuccess = output.status === 0; + const matches = output.stdout.matchAll(PARSE_REGEX); for (const match of matches) { const [_, pathFull, line, rule, text] = match; const path = pathFull.substring(2); // Remove "./" or ".\" from start of path const lineNr = parseInt(line, 10); - resultsParsed[2].push({ + lintResult.error.push({ path, firstLine: lineNr, lastLine: lineNr, @@ -74,7 +71,7 @@ class Flake8 { }); } - return resultsParsed; + return lintResult; } } diff --git a/src/linters/gofmt.js b/src/linters/gofmt.js index cd0fbedd..8fa43a93 100644 --- a/src/linters/gofmt.js +++ b/src/linters/gofmt.js @@ -1,6 +1,7 @@ const commandExists = require("../../vendor/command-exists"); const { run } = require("../utils/action"); -const { diffToParsedResults } = require("../utils/diff"); +const { parseErrorsFromDiff } = require("../utils/diff"); +const { initLintResult } = require("../utils/lint-result"); /** * https://golang.org/cmd/gofmt @@ -11,9 +12,7 @@ class Gofmt { } /** - * Verifies that all required programs are installed. Exits the GitHub action if one of the - * programs is missing - * + * Verifies that all required programs are installed. Throws an error if programs are missing * @param {string} dir: Directory to run the linting program in */ static async verifySetup(dir) { @@ -25,11 +24,10 @@ class Gofmt { /** * Runs the linting program and returns the command output - * - * @param {string} dir: Directory to run the linting program in - * @param {string[]} extensions: Array of file extensions which should be linted + * @param {string} dir: Directory to run the linter in + * @param {string[]} extensions: File extensions which should be linted * @param {boolean} fix: Whether the linter should attempt to fix code style issues automatically - * @returns {string}: Results of the linting process + * @returns {{status: number, stdout: string, stderr: string}}: Output of the lint command */ static lint(dir, extensions, fix = false) { if (extensions.length !== 1 || extensions[0] !== "go") { @@ -43,17 +41,19 @@ class Gofmt { return run(`gofmt -s ${fix ? "-w" : "-d -e"} "."`, { dir, ignoreErrors: true, - }).stdout; + }); } /** - * Parses the results of the linting process and returns it as a processable array - * - * @param {string} dir: Directory in which the linting program has been run - * @param {string} results: Results of the linting process - * @returns {object[]}: Parsed results + * Parses the output of the lint command. Determines the success of the lint process and the + * severity of the identified code style violations + * @param {string} dir: Directory in which the linter has been run + * @param {{status: number, stdout: string, stderr: string}} output: Output of the lint command + * @returns {{isSuccess: boolean, warning: [], error: []}}: Parsed lint result */ - static parseResults(dir, results) { + static parseOutput(dir, output) { + const lintResult = initLintResult(); + // The gofmt output lines starting with "diff" differ from the ones of tools like Git: // // - gofmt: "diff -u file-old.txt file-new.txt" @@ -63,11 +63,17 @@ class Gofmt { // start. Without these strings, this would not be possible, because file names may include // spaces, which are not escaped in unified diffs. As a workaround, these lines are filtered out // from the gofmt diff so the diff parser can read the diff without errors - const resultsFiltered = results + const filteredOutput = output.stderr .split(/\r?\n/) .filter(line => !line.startsWith("diff -u")) .join("\n"); - return diffToParsedResults(resultsFiltered); + lintResult.error = parseErrorsFromDiff(filteredOutput); + + // gofmt exits with 0 even if there are formatting issues. Therefore, this function determines + // the success of the linting process based on the number of parsed errors + lintResult.isSuccess = lintResult.error.length === 0; + + return lintResult; } } diff --git a/src/linters/golint.js b/src/linters/golint.js index 48e96648..9d55ef45 100644 --- a/src/linters/golint.js +++ b/src/linters/golint.js @@ -1,5 +1,6 @@ const commandExists = require("../../vendor/command-exists"); const { log, run } = require("../utils/action"); +const { initLintResult } = require("../utils/lint-result"); const { capitalizeFirstLetter } = require("../utils/string"); const PARSE_REGEX = /^(.+):([0-9]+):[0-9]+: (.+)$/gm; @@ -13,9 +14,7 @@ class Golint { } /** - * Verifies that all required programs are installed. Exits the GitHub action if one of the - * programs is missing - * + * Verifies that all required programs are installed. Throws an error if programs are missing * @param {string} dir: Directory to run the linting program in */ static async verifySetup(dir) { @@ -27,43 +26,41 @@ class Golint { /** * Runs the linting program and returns the command output - * - * @param {string} dir: Directory to run the linting program in - * @param {string[]} extensions: Array of file extensions which should be linted + * @param {string} dir: Directory to run the linter in + * @param {string[]} extensions: File extensions which should be linted * @param {boolean} fix: Whether the linter should attempt to fix code style issues automatically - * @returns {string}: Results of the linting process + * @returns {{status: number, stdout: string, stderr: string}}: Output of the lint command */ static lint(dir, extensions, fix = false) { if (extensions.length !== 1 || extensions[0] !== "go") { throw new Error(`${this.name} error: File extensions are not configurable`); } - if (fix) { log(`${this.name} does not support auto-fixing`, "warning"); } - return run(`golint "."`, { + + return run(`golint -set_exit_status "."`, { dir, ignoreErrors: true, - }).stdout; + }); } /** - * Parses the results of the linting process and returns it as a processable array - * - * @param {string} dir: Directory in which the linting program has been run - * @param {string} results: Results of the linting process - * @returns {object[]}: Parsed results + * Parses the output of the lint command. Determines the success of the lint process and the + * severity of the identified code style violations + * @param {string} dir: Directory in which the linter has been run + * @param {{status: number, stdout: string, stderr: string}} output: Output of the lint command + * @returns {{isSuccess: boolean, warning: [], error: []}}: Parsed lint result */ - static parseResults(dir, results) { - const matches = results.matchAll(PARSE_REGEX); - - // Parsed results: [notices, warnings, failures] - const resultsParsed = [[], [], []]; + static parseOutput(dir, output) { + const lintResult = initLintResult(); + lintResult.isSuccess = output.status === 0; + const matches = output.stdout.matchAll(PARSE_REGEX); for (const match of matches) { const [_, path, line, text] = match; const lineNr = parseInt(line, 10); - resultsParsed[2].push({ + lintResult.error.push({ path, firstLine: lineNr, lastLine: lineNr, @@ -71,7 +68,7 @@ class Golint { }); } - return resultsParsed; + return lintResult; } } diff --git a/src/linters/prettier.js b/src/linters/prettier.js index 6eb6cc4e..1a1d2b69 100644 --- a/src/linters/prettier.js +++ b/src/linters/prettier.js @@ -1,5 +1,6 @@ const commandExists = require("../../vendor/command-exists"); const { run } = require("../utils/action"); +const { initLintResult } = require("../utils/lint-result"); /** * https://prettier.io @@ -10,9 +11,7 @@ class Prettier { } /** - * Verifies that all required programs are installed. Exits the GitHub action if one of the - * programs is missing - * + * Verifies that all required programs are installed. Throws an error if programs are missing * @param {string} dir: Directory to run the linting program in */ static async verifySetup(dir) { @@ -31,42 +30,39 @@ class Prettier { /** * Runs the linting program and returns the command output - * - * @param {string} dir: Directory to run the linting program in - * @param {string[]} extensions: Array of file extensions which should be linted + * @param {string} dir: Directory to run the linter in + * @param {string[]} extensions: File extensions which should be linted * @param {boolean} fix: Whether the linter should attempt to fix code style issues automatically - * @returns {string}: Results of the linting process + * @returns {{status: number, stdout: string, stderr: string}}: Output of the lint command */ static lint(dir, extensions, fix = false) { const files = extensions.length === 1 ? `**/*.${extensions[0]}` : `**/*.{${extensions.join(",")}}`; - const results = run( + return run( `npx --no-install prettier ${fix ? "--write" : "--list-different"} --no-color "${files}"`, { dir, ignoreErrors: true, }, - ).stdout; - return fix ? "" : results; + ); } /** - * Parses the results of the linting process and returns it as a processable array - * - * @param {string} dir: Directory in which the linting program has been run - * @param {string} results: Results of the linting process - * @returns {object[]}: Parsed results + * Parses the output of the lint command. Determines the success of the lint process and the + * severity of the identified code style violations + * @param {string} dir: Directory in which the linter has been run + * @param {{status: number, stdout: string, stderr: string}} output: Output of the lint command + * @returns {{isSuccess: boolean, warning: [], error: []}}: Parsed lint result */ - static parseResults(dir, results) { - // Parsed results: [notices, warnings, failures] - const resultsParsed = [[], [], []]; - - if (!results) { - return resultsParsed; + static parseOutput(dir, output) { + const lintResult = initLintResult(); + lintResult.isSuccess = output.status === 0; + if (lintResult.isSuccess || !output) { + return lintResult; } - const paths = results.split(/\r?\n/); - resultsParsed[2] = paths.map(path => ({ + const paths = output.stdout.split(/\r?\n/); + lintResult.error = paths.map(path => ({ path, firstLine: 1, lastLine: 1, @@ -74,7 +70,7 @@ class Prettier { "There are issues with this file's formatting, please run Prettier to fix the errors", })); - return resultsParsed; + return lintResult; } } diff --git a/src/linters/rubocop.js b/src/linters/rubocop.js index 5f62871a..56ab0d3d 100644 --- a/src/linters/rubocop.js +++ b/src/linters/rubocop.js @@ -1,13 +1,15 @@ const commandExists = require("../../vendor/command-exists"); const { run } = require("../utils/action"); +const { initLintResult } = require("../utils/lint-result"); const { removeTrailingPeriod } = require("../utils/string"); -const severityIndices = { - convention: 1, - refactor: 1, - warning: 1, - error: 2, - fatal: 2, +// Mapping of RuboCop severities to severities used for GitHub commit annotations +const severityMap = { + convention: "warning", + refactor: "warning", + warning: "warning", + error: "error", + fatal: "error", }; /** @@ -19,9 +21,7 @@ class RuboCop { } /** - * Verifies that all required programs are installed. Exits the GitHub action if one of the - * programs is missing - * + * Verifies that all required programs are installed. Throws an error if programs are missing * @param {string} dir: Directory to run the linting program in */ static async verifySetup(dir) { @@ -38,11 +38,10 @@ class RuboCop { /** * Runs the linting program and returns the command output - * - * @param {string} dir: Directory to run the linting program in - * @param {string[]} extensions: Array of file extensions which should be linted + * @param {string} dir: Directory to run the linter in + * @param {string[]} extensions: File extensions which should be linted * @param {boolean} fix: Whether the linter should attempt to fix code style issues automatically - * @returns {string}: Results of the linting process + * @returns {{status: number, stdout: string, stderr: string}}: Output of the lint command */ static lint(dir, extensions, fix = false) { if (extensions.length !== 1 || extensions[0] !== "rb") { @@ -52,29 +51,28 @@ class RuboCop { return run(`rubocop --format json ${fix ? "--auto-correct" : ""} ${dir}`, { dir, ignoreErrors: true, - }).stdout; + }); } /** - * Parses the results of the linting process and returns it as a processable array - * - * @param {string} dir: Directory in which the linting program has been run - * @param {string} results: Results of the linting process - * @returns {object[]}: Parsed results + * Parses the output of the lint command. Determines the success of the lint process and the + * severity of the identified code style violations + * @param {string} dir: Directory in which the linter has been run + * @param {{status: number, stdout: string, stderr: string}} output: Output of the lint command + * @returns {{isSuccess: boolean, warning: [], error: []}}: Parsed lint result */ - static parseResults(dir, results) { - const resultsJson = JSON.parse(results); - - // Parsed results: [notices, warnings, failures] - const resultsParsed = [[], [], []]; + static parseOutput(dir, output) { + const lintResult = initLintResult(); + lintResult.isSuccess = output.status === 0; - for (const file of resultsJson.files) { + const outputJson = JSON.parse(output.stdout); + for (const file of outputJson.files) { const { path, offenses } = file; for (const offense of offenses) { const { severity, message, cop_name: rule, corrected, location } = offense; if (!corrected) { - const severityIdx = severityIndices[severity] || 2; - resultsParsed[severityIdx].push({ + const mappedSeverity = severityMap[severity] || "error"; + lintResult[mappedSeverity].push({ path, firstLine: location.start_line, lastLine: location.last_line, @@ -84,7 +82,7 @@ class RuboCop { } } - return resultsParsed; + return lintResult; } } diff --git a/src/linters/stylelint.js b/src/linters/stylelint.js index f3b5b22f..9fa1be5b 100644 --- a/src/linters/stylelint.js +++ b/src/linters/stylelint.js @@ -1,7 +1,6 @@ const commandExists = require("../../vendor/command-exists"); const { run } = require("../utils/action"); - -const SEVERITY_LEVELS = ["", "warning", "error"]; +const { initLintResult } = require("../utils/lint-result"); /** * https://stylelint.io @@ -12,9 +11,7 @@ class Stylelint { } /** - * Verifies that all required programs are installed. Exits the GitHub action if one of the - * programs is missing - * + * Verifies that all required programs are installed. Throws an error if programs are missing * @param {string} dir: Directory to run the linting program in */ static async verifySetup(dir) { @@ -33,11 +30,10 @@ class Stylelint { /** * Runs the linting program and returns the command output - * - * @param {string} dir: Directory to run the linting program in - * @param {string[]} extensions: Array of file extensions which should be linted + * @param {string} dir: Directory to run the linter in + * @param {string[]} extensions: File extensions which should be linted * @param {boolean} fix: Whether the linter should attempt to fix code style issues automatically - * @returns {string}: Results of the linting process + * @returns {{status: number, stdout: string, stderr: string}}: Output of the lint command */ static lint(dir, extensions, fix = false) { const files = @@ -48,38 +44,38 @@ class Stylelint { dir, ignoreErrors: true, }, - ).stdout; + ); } /** - * Parses the results of the linting process and returns it as a processable array - * - * @param {string} dir: Directory in which the linting program has been run - * @param {string} results: Results of the linting process - * @returns {object[]}: Parsed results + * Parses the output of the lint command. Determines the success of the lint process and the + * severity of the identified code style violations + * @param {string} dir: Directory in which the linter has been run + * @param {{status: number, stdout: string, stderr: string}} output: Output of the lint command + * @returns {{isSuccess: boolean, warning: [], error: []}}: Parsed lint result */ - static parseResults(dir, results) { - const resultsJson = JSON.parse(results); - - // Parsed results: [notices, warnings, failures] - const resultsParsed = [[], [], []]; + static parseOutput(dir, output) { + const lintResult = initLintResult(); + lintResult.isSuccess = output.status === 0; - for (const result of resultsJson) { - const { source, warnings } = result; + const outputJson = JSON.parse(output.stdout); + for (const violation of outputJson) { + const { source, warnings } = violation; const path = source.substring(dir.length + 1); for (const warning of warnings) { const { line, severity, text } = warning; - const severityIdx = SEVERITY_LEVELS.indexOf(severity); - resultsParsed[severityIdx].push({ - path, - firstLine: line, - lastLine: line, - message: text, - }); + if (severity in lintResult) { + lintResult[severity].push({ + path, + firstLine: line, + lastLine: line, + message: text, + }); + } } } - return resultsParsed; + return lintResult; } } diff --git a/src/linters/swiftformat.js b/src/linters/swiftformat.js index 011261f1..0854294e 100644 --- a/src/linters/swiftformat.js +++ b/src/linters/swiftformat.js @@ -1,5 +1,6 @@ const commandExists = require("../../vendor/command-exists"); const { run } = require("../utils/action"); +const { initLintResult } = require("../utils/lint-result"); const PARSE_REGEX = /^(.*):([0-9]+):[0-9]+: \w+: \((\w+)\) (.*)\.$/gm; @@ -12,9 +13,7 @@ class SwiftFormat { } /** - * Verifies that all required programs are installed. Exits the GitHub action if one of the - * programs is missing - * + * Verifies that all required programs are installed. Throws an error if programs are missing * @param {string} dir: Directory to run the linting program in */ static async verifySetup(dir) { @@ -26,11 +25,10 @@ class SwiftFormat { /** * Runs the linting program and returns the command output - * - * @param {string} dir: Directory to run the linting program in - * @param {string[]} extensions: Array of file extensions which should be linted + * @param {string} dir: Directory to run the linter in + * @param {string[]} extensions: File extensions which should be linted * @param {boolean} fix: Whether the linter should attempt to fix code style issues automatically - * @returns {string}: Results of the linting process + * @returns {{status: number, stdout: string, stderr: string}}: Output of the lint command */ static lint(dir, extensions, fix = false) { if (extensions.length !== 1 || extensions[0] !== "swift") { @@ -40,29 +38,28 @@ class SwiftFormat { return run(`swiftformat ${fix ? "" : "--lint"} "."`, { dir, ignoreErrors: true, - }).stderr; + }); } /** - * Parses the results of the linting process and returns it as a processable array - * - * @param {string} dir: Directory in which the linting program has been run - * @param {string} results: Results of the linting process - * @returns {object[]}: Parsed results + * Parses the output of the lint command. Determines the success of the lint process and the + * severity of the identified code style violations + * @param {string} dir: Directory in which the linter has been run + * @param {{status: number, stdout: string, stderr: string}} output: Output of the lint command + * @returns {{isSuccess: boolean, warning: [], error: []}}: Parsed lint result */ - static parseResults(dir, results) { - const matches = results.matchAll(PARSE_REGEX); - - // Parsed results: [notices, warnings, failures] - const resultsParsed = [[], [], []]; + static parseOutput(dir, output) { + const lintResult = initLintResult(); + lintResult.isSuccess = output.status === 0; + const matches = output.stderr.matchAll(PARSE_REGEX); for (const match of matches) { const [_, pathFull, line, rule, message] = match; const path = pathFull.substring(dir.length + 1); const lineNr = parseInt(line, 10); // SwiftFormat only seems to use the "warning" level, which this action will therefore // categorize as errors - resultsParsed[2].push({ + lintResult.error.push({ path, firstLine: lineNr, lastLine: lineNr, @@ -70,7 +67,7 @@ class SwiftFormat { }); } - return resultsParsed; + return lintResult; } } diff --git a/src/linters/swiftlint.js b/src/linters/swiftlint.js index 0e7e567a..aa16a205 100644 --- a/src/linters/swiftlint.js +++ b/src/linters/swiftlint.js @@ -1,8 +1,8 @@ const commandExists = require("../../vendor/command-exists"); const { run } = require("../utils/action"); +const { initLintResult } = require("../utils/lint-result"); const PARSE_REGEX = /^(.*):([0-9]+):[0-9]+: (warning|error): (.*)$/gm; -const LEVELS = ["", "warning", "error"]; /** * https://github.com/realm/SwiftLint @@ -13,9 +13,7 @@ class SwiftLint { } /** - * Verifies that all required programs are installed. Exits the GitHub action if one of the - * programs is missing - * + * Verifies that all required programs are installed. Throws an error if programs are missing * @param {string} dir: Directory to run the linting program in */ static async verifySetup(dir) { @@ -27,11 +25,10 @@ class SwiftLint { /** * Runs the linting program and returns the command output - * - * @param {string} dir: Directory to run the linting program in - * @param {string[]} extensions: Array of file extensions which should be linted + * @param {string} dir: Directory to run the linter in + * @param {string[]} extensions: File extensions which should be linted * @param {boolean} fix: Whether the linter should attempt to fix code style issues automatically - * @returns {string}: Results of the linting process + * @returns {{status: number, stdout: string, stderr: string}}: Output of the lint command */ static lint(dir, extensions, fix = false) { if (extensions.length !== 1 || extensions[0] !== "swift") { @@ -41,28 +38,26 @@ class SwiftLint { return run(`swiftlint ${fix ? "autocorrect" : ""}`, { dir, ignoreErrors: true, - }).stdout; + }); } /** - * Parses the results of the linting process and returns it as a processable array - * - * @param {string} dir: Directory in which the linting program has been run - * @param {string} results: Results of the linting process - * @returns {object[]}: Parsed results + * Parses the output of the lint command. Determines the success of the lint process and the + * severity of the identified code style violations + * @param {string} dir: Directory in which the linter has been run + * @param {{status: number, stdout: string, stderr: string}} output: Output of the lint command + * @returns {{isSuccess: boolean, warning: [], error: []}}: Parsed lint result */ - static parseResults(dir, results) { - const matches = results.matchAll(PARSE_REGEX); - - // Parsed results: [notices, warnings, failures] - const resultsParsed = [[], [], []]; + static parseOutput(dir, output) { + const lintResult = initLintResult(); + lintResult.isSuccess = output.status === 0; + const matches = output.stdout.matchAll(PARSE_REGEX); for (const match of matches) { const [_, pathFull, line, level, message] = match; const path = pathFull.substring(dir.length + 1); const lineNr = parseInt(line, 10); - const levelIdx = LEVELS.indexOf(level); - resultsParsed[levelIdx].push({ + lintResult[level].push({ path, firstLine: lineNr, lastLine: lineNr, @@ -70,7 +65,7 @@ class SwiftLint { }); } - return resultsParsed; + return lintResult; } } diff --git a/src/utils/action.js b/src/utils/action.js index ca46b1e1..deb24556 100644 --- a/src/utils/action.js +++ b/src/utils/action.js @@ -58,10 +58,10 @@ function getInput(name, required = false) { * Executes the provided shell command * * @param cmd {string}: Shell command to execute - * @param options {object}: {@see RUN_OPTIONS_DEFAULTS} - * @returns {object}: Output of the shell command + * @param [options] {{dir: string, ignoreErrors: boolean}}: {@see RUN_OPTIONS_DEFAULTS} + * @returns {{status: number, stdout: string, stderr: string}}: Output of the shell command */ -function run(cmd, options = null) { +function run(cmd, options) { const optionsWithDefaults = { ...RUN_OPTIONS_DEFAULTS, ...options, diff --git a/src/utils/diff.js b/src/utils/diff.js index 1f59ee79..4a41cf20 100644 --- a/src/utils/diff.js +++ b/src/utils/diff.js @@ -1,22 +1,20 @@ const parseDiff = require("../../vendor/parse-diff"); /** - * Parses a unified diff and converts it to a results array - * - * @param diff {string}: Unified diff (output of a linting/formatting command) - * @returns {[][]}: Array of parsed results ([notices, warnings, failures]) + * Parses linting errors from a unified diff + * @param {string} diff: Unified diff + * @returns {{path: string, firstLine: number, lastLine: number, message: string}[]}: Array of + * parsed errors */ -function diffToParsedResults(diff) { - // Parsed results: [notices, warnings, failures] - const resultsParsed = [[], [], []]; - +function parseErrorsFromDiff(diff) { + const errors = []; const files = parseDiff(diff); for (const file of files) { const { chunks, to: path } = file; for (const chunk of chunks) { const { oldStart, oldLines, changes } = chunk; const chunkDiff = changes.map(change => change.content).join("\n"); - resultsParsed[2].push({ + errors.push({ path, firstLine: oldStart, lastLine: oldStart + oldLines, @@ -24,10 +22,9 @@ function diffToParsedResults(diff) { }); } } - - return resultsParsed; + return errors; } module.exports = { - diffToParsedResults, + parseErrorsFromDiff, }; diff --git a/src/utils/lint-result.js b/src/utils/lint-result.js new file mode 100644 index 00000000..b5c9be26 --- /dev/null +++ b/src/utils/lint-result.js @@ -0,0 +1,15 @@ +/** + * Returns an object for storing linting results + * @returns {{isSuccess: boolean, warning: [], error: []}}: Default object + */ +function initLintResult() { + return { + isSuccess: true, // Usually determined by the exit code of the linting command + warning: [], + error: [], + }; +} + +module.exports = { + initLintResult, +}; diff --git a/test/github/github.test.js b/test/github/github.test.js index 9f1b56d4..f75aed1b 100644 --- a/test/github/github.test.js +++ b/test/github/github.test.js @@ -67,7 +67,11 @@ describe("getContext()", () => { }); describe("createCheck()", () => { - const RESULTS = [[], [], []]; + const LINT_RESULT = { + isSuccess: true, + warning: [], + error: [], + }; const context = { ...expectedContext, event: {}, @@ -75,12 +79,12 @@ describe("createCheck()", () => { }; test("mocked request should be successful", async () => { - await expect(createCheck("check-name", "sha", context, RESULTS, "summary")).resolves.toEqual( - undefined, - ); + await expect( + createCheck("check-name", "sha", context, LINT_RESULT, "summary"), + ).resolves.toEqual(undefined); }); - test("mocked request should fail without results", async () => { + test("mocked request should fail when no lint results are provided", async () => { await expect(createCheck("check-name", "sha", context, null, "summary")).rejects.toEqual( expect.any(Error), ); diff --git a/test/linters/linters.test.js b/test/linters/linters.test.js index d0efbbda..56247df1 100644 --- a/test/linters/linters.test.js +++ b/test/linters/linters.test.js @@ -30,61 +30,63 @@ if (process.platform === "darwin") { linterParams.push(swiftformatParams, swiftlintParams); } -describe.each(linterParams)( - "%s", - (projectName, linter, extensions, getLintParams, getFixParams) => { - const projectDir = join(__dirname, "projects", projectName); - const tmpDir = join(__dirname, "..", "tmp", projectName); - const lintParams = getLintParams(tmpDir); - const fixParams = getFixParams(tmpDir); +// Test lint and auto-fix modes +describe.each([ + ["lint", false], + ["auto-fix", true], +])("%s", (lintMode, autoFix) => { + // Test all linters + describe.each(linterParams)( + "%s", + (projectName, linter, extensions, getLintParams, getFixParams) => { + const projectDir = join(__dirname, "projects", projectName); + const tmpDir = join(__dirname, "..", "tmp", projectName); + const expected = autoFix ? getFixParams(tmpDir) : getLintParams(tmpDir); - beforeAll(async () => { - // Move test project into temporary directory (where files can be modified by the linters) - copySync(projectDir, tmpDir); - await linter.verifySetup(tmpDir); - }); + beforeAll(async () => { + // Move test project into temporary directory (where files can be modified by the linters) + copySync(projectDir, tmpDir); + await linter.verifySetup(tmpDir); + }); - afterAll(() => { - // Remove temporary directory after test completion - removeSync(tmpDir); - }); + afterAll(() => { + // Remove temporary directory after test completion + removeSync(tmpDir); + }); - test(`${linter.name} returns correct lint results`, () => { - let actualStdout = linter.lint(tmpDir, extensions); - actualStdout = normalizeDates(actualStdout); - if ("stdout" in lintParams) { - expect(actualStdout).toEqual(lintParams.stdout); - } else if ("stdoutParts" in lintParams) { - lintParams.stdoutParts.forEach(stdoutPart => - expect(actualStdout).toEqual(expect.stringContaining(stdoutPart)), - ); - } else { - throw Error("`lintParams` must contain either `stdout` or `stdoutParts` key"); - } - }); + // Test `lint` function + test(`${linter.name} returns expected ${lintMode} output`, () => { + const cmdOutput = linter.lint(tmpDir, extensions, autoFix); - test(`${linter.name} parses lint results correctly`, () => { - const actualParsed = linter.parseResults(tmpDir, lintParams.parseInput); - expect(actualParsed).toEqual(lintParams.parseResult); - }); + // Exit code + expect(cmdOutput.status).toEqual(expected.cmdOutput.status); - test(`${linter.name} returns correct auto-fix results`, () => { - let actualStdout = linter.lint(tmpDir, extensions, true); - actualStdout = normalizeDates(actualStdout); - if ("stdout" in fixParams) { - expect(actualStdout).toEqual(fixParams.stdout); - } else if ("stdoutParts" in fixParams) { - fixParams.stdoutParts.forEach(stdoutPart => - expect(actualStdout).toEqual(expect.stringContaining(stdoutPart)), - ); - } else { - throw Error("`fixParams` must contain either `stdout` or `stdoutParts` key"); - } - }); + // stdout + const stdout = normalizeDates(cmdOutput.stdout); + if ("stdoutParts" in expected.lintResult) { + expected.lintResult.stdoutParts.forEach(stdoutPart => + expect(stdout).toEqual(expect.stringContaining(stdoutPart)), + ); + } else if ("stdout" in expected.lintResult) { + expect(stdout).toEqual(expected.stdout); + } - test(`${linter.name} parses auto-fix results correctly`, () => { - const actualParsed = linter.parseResults(tmpDir, fixParams.parseInput); - expect(actualParsed).toEqual(fixParams.parseResult); - }); - }, -); + // stderr + const stderr = normalizeDates(cmdOutput.stderr); + if ("stderrParts" in expected.lintResult) { + expected.lintResult.stderrParts.forEach(stderrParts => + expect(stderr).toEqual(expect.stringContaining(stderrParts)), + ); + } else if ("stderr" in expected.lintResult) { + expect(stderr).toEqual(expected.stderr); + } + }); + + // Test `parseOutput` function + test(`${linter.name} parses ${lintMode} output correctly`, () => { + const lintResult = linter.parseOutput(tmpDir, expected.cmdOutput); + expect(lintResult).toEqual(expected.lintResult); + }); + }, + ); +}); diff --git a/test/linters/params/black.js b/test/linters/params/black.js index 752f7b36..4c5a8041 100644 --- a/test/linters/params/black.js +++ b/test/linters/params/black.js @@ -5,20 +5,22 @@ const testName = "black"; const linter = Black; const extensions = ["py"]; -// Testing input/output for the Linter.lint function, with auto-fixing disabled +// Linting without auto-fixing function getLintParams(dir) { - const resultsFile1 = `--- file1.py ${TEST_DATE}\n+++ file1.py ${TEST_DATE}\n@@ -1,10 +1,10 @@\n var_1 = "hello"\n var_2 = "world"\n \n \n-def main (): # Whitespace error\n+def main(): # Whitespace error\n print("hello " + var_2)\n \n \n def add(num_1, num_2):\n return num_1 + num_2\n@@ -19,9 +19,10 @@\n \n \n def divide(num_1, num_2):\n return num_1 / num_2\n \n+\n # Blank lines error\n \n main()`; - const resultsFile2 = `--- file2.py ${TEST_DATE}\n+++ file2.py ${TEST_DATE}\n@@ -1,3 +1,3 @@\n def add(num_1, num_2):\n- return num_1 + num_2 # Indentation error\n+ return num_1 + num_2 # Indentation error`; + const stdoutFile1 = `--- file1.py ${TEST_DATE}\n+++ file1.py ${TEST_DATE}\n@@ -1,10 +1,10 @@\n var_1 = "hello"\n var_2 = "world"\n \n \n-def main (): # Whitespace error\n+def main(): # Whitespace error\n print("hello " + var_2)\n \n \n def add(num_1, num_2):\n return num_1 + num_2\n@@ -19,9 +19,10 @@\n \n \n def divide(num_1, num_2):\n return num_1 / num_2\n \n+\n # Blank lines error\n \n main()`; + const stdoutFile2 = `--- file2.py ${TEST_DATE}\n+++ file2.py ${TEST_DATE}\n@@ -1,3 +1,3 @@\n def add(num_1, num_2):\n- return num_1 + num_2 # Indentation error\n+ return num_1 + num_2 # Indentation error`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `${resultsFile1}\n \n${resultsFile2}`, + // Expected output of the linting function + cmdOutput: { + status: 1, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `${stdoutFile1}\n \n${stdoutFile2}`, + }, // Expected output of the parsing function - parseResult: [ - [], - [], - [ + lintResult: { + isSuccess: false, + warning: [], + error: [ { path: "file1.py", firstLine: 1, @@ -38,19 +40,24 @@ function getLintParams(dir) { message: ` def add(num_1, num_2):\n- return num_1 + num_2 # Indentation error\n+ return num_1 + num_2 # Indentation error`, }, ], - ], + }, }; } -// Testing input/output for the Linter.lint function, with auto-fixing enabled +// Linting with auto-fixing function getFixParams(dir) { return { - // stdout of the lint command - stdout: "", - // Example output of the lint command, used to test the parsing function - parseInput: "", + // Expected output of the linting function + cmdOutput: { + status: 0, + stdout: "", + }, // Expected output of the parsing function - parseResult: [[], [], []], + lintResult: { + isSuccess: true, + warning: [], + error: [], + }, }; } diff --git a/test/linters/params/eslint-typescript.js b/test/linters/params/eslint-typescript.js index 1b49c64e..c5b14511 100644 --- a/test/linters/params/eslint-typescript.js +++ b/test/linters/params/eslint-typescript.js @@ -5,25 +5,27 @@ const testName = "eslint-typescript"; const linter = ESLint; const extensions = ["js", "ts"]; -// Testing input/output for the Linter.lint function, with auto-fixing disabled +// Linting without auto-fixing function getLintParams(dir) { - const resultsFile1 = `{"filePath":"${joinDoubleBackslash( + const stdoutFile1 = `{"filePath":"${joinDoubleBackslash( dir, "file1.ts", )}","messages":[{"ruleId":"prefer-const","severity":1,"message":"'str' is never reassigned. Use 'const' instead.","line":1,"column":5,"nodeType":"Identifier","messageId":"useConst","endLine":1,"endColumn":8,"fix":{"range":[0,3],"text":"const"}},{"ruleId":"no-console","severity":2,"message":"Unexpected console statement.","line":4,"column":2,"nodeType":"MemberExpression","messageId":"unexpected","endLine":4,"endColumn":13}],"errorCount":1,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":1,"source":"let str = \\"world\\"; // \\"prefer-const\\" warning\\n\\nfunction main(): void {\\n\\tconsole.log(\\"hello \\" + str); // \\"no-console\\" error\\n}\\n\\nmain();\\n"}`; - const resultsFile2 = `{"filePath":"${joinDoubleBackslash( + const stdoutFile2 = `{"filePath":"${joinDoubleBackslash( dir, "file2.js", )}","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'str' is assigned a value but never used.","line":1,"column":7,"nodeType":"Identifier","endLine":1,"endColumn":10}],"errorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"const str = \\"Hello world\\"; // \\"no-unused-vars\\" error\\n"}`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `[${resultsFile1},${resultsFile2}]`, + // Expected output of the linting function + cmdOutput: { + status: 1, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `[${stdoutFile1},${stdoutFile2}]`, + }, // Expected output of the parsing function - parseResult: [ - [], - [ + lintResult: { + isSuccess: false, + warning: [ { path: "file1.ts", firstLine: 1, @@ -31,7 +33,7 @@ function getLintParams(dir) { message: "'str' is never reassigned. Use 'const' instead (prefer-const)", }, ], - [ + error: [ { path: "file1.ts", firstLine: 4, @@ -45,30 +47,32 @@ function getLintParams(dir) { message: "'str' is assigned a value but never used (no-unused-vars)", }, ], - ], + }, }; } -// Testing input/output for the Linter.lint function, with auto-fixing enabled +// Linting with auto-fixing function getFixParams(dir) { - const resultsFile1 = `{"filePath":"${joinDoubleBackslash( + const stdoutFile1 = `{"filePath":"${joinDoubleBackslash( dir, "file1.ts", )}","messages":[{"ruleId":"no-console","severity":2,"message":"Unexpected console statement.","line":4,"column":2,"nodeType":"MemberExpression","messageId":"unexpected","endLine":4,"endColumn":13}],"errorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"const str = \\"world\\"; // \\"prefer-const\\" warning\\n\\nfunction main(): void {\\n\\tconsole.log(\\"hello \\" + str); // \\"no-console\\" error\\n}\\n\\nmain();\\n"}`; - const resultsFile2 = `{"filePath":"${joinDoubleBackslash( + const stdoutFile2 = `{"filePath":"${joinDoubleBackslash( dir, "file2.js", )}","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'str' is assigned a value but never used.","line":1,"column":7,"nodeType":"Identifier","endLine":1,"endColumn":10}],"errorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"const str = \\"Hello world\\"; // \\"no-unused-vars\\" error\\n"}`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `[${resultsFile1},${resultsFile2}]`, + // Expected output of the linting function + cmdOutput: { + status: 1, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `[${stdoutFile1},${stdoutFile2}]`, + }, // Expected output of the parsing function - parseResult: [ - [], - [], - [ + lintResult: { + isSuccess: false, + warning: [], + error: [ { path: "file1.ts", firstLine: 4, @@ -82,7 +86,7 @@ function getFixParams(dir) { message: "'str' is assigned a value but never used (no-unused-vars)", }, ], - ], + }, }; } diff --git a/test/linters/params/eslint.js b/test/linters/params/eslint.js index 694cb93a..a4c0fe7a 100644 --- a/test/linters/params/eslint.js +++ b/test/linters/params/eslint.js @@ -5,25 +5,27 @@ const testName = "eslint"; const linter = ESLint; const extensions = ["js"]; -// Testing input/output for the Linter.lint function, with auto-fixing disabled +// Linting without auto-fixing function getLintParams(dir) { - const resultsFile1 = `{"filePath":"${joinDoubleBackslash( + const stdoutFile1 = `{"filePath":"${joinDoubleBackslash( dir, "file1.js", )}","messages":[{"ruleId":"prefer-const","severity":1,"message":"'str' is never reassigned. Use 'const' instead.","line":1,"column":5,"nodeType":"Identifier","messageId":"useConst","endLine":1,"endColumn":8,"fix":{"range":[0,3],"text":"const"}},{"ruleId":"no-console","severity":2,"message":"Unexpected console statement.","line":4,"column":2,"nodeType":"MemberExpression","messageId":"unexpected","endLine":4,"endColumn":13}],"errorCount":1,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":1,"source":"let str = \\"world\\"; // \\"prefer-const\\" warning\\n\\nfunction main() {\\n\\tconsole.log(\\"hello \\" + str); // \\"no-console\\" error\\n}\\n\\nmain();\\n"}`; - const resultsFile2 = `{"filePath":"${joinDoubleBackslash( + const stdoutFile2 = `{"filePath":"${joinDoubleBackslash( dir, "file2.js", )}","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'str' is assigned a value but never used.","line":1,"column":7,"nodeType":"Identifier","endLine":1,"endColumn":10}],"errorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"const str = \\"Hello world\\"; // \\"no-unused-vars\\" error\\n"}`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `[${resultsFile1},${resultsFile2}]`, + // Expected output of the linting function + cmdOutput: { + status: 1, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `[${stdoutFile1},${stdoutFile2}]`, + }, // Expected output of the parsing function - parseResult: [ - [], - [ + lintResult: { + isSuccess: false, + warning: [ { path: "file1.js", firstLine: 1, @@ -31,7 +33,7 @@ function getLintParams(dir) { message: "'str' is never reassigned. Use 'const' instead (prefer-const)", }, ], - [ + error: [ { path: "file1.js", firstLine: 4, @@ -45,30 +47,32 @@ function getLintParams(dir) { message: "'str' is assigned a value but never used (no-unused-vars)", }, ], - ], + }, }; } -// Testing input/output for the Linter.lint function, with auto-fixing enabled +// Linting with auto-fixing function getFixParams(dir) { - const resultsFile1 = `{"filePath":"${joinDoubleBackslash( + const stdoutFile1 = `{"filePath":"${joinDoubleBackslash( dir, "file1.js", )}","messages":[{"ruleId":"no-console","severity":2,"message":"Unexpected console statement.","line":4,"column":2,"nodeType":"MemberExpression","messageId":"unexpected","endLine":4,"endColumn":13}],"errorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"const str = \\"world\\"; // \\"prefer-const\\" warning\\n\\nfunction main() {\\n\\tconsole.log(\\"hello \\" + str); // \\"no-console\\" error\\n}\\n\\nmain();\\n"}`; - const resultsFile2 = `{"filePath":"${joinDoubleBackslash( + const stdoutFile2 = `{"filePath":"${joinDoubleBackslash( dir, "file2.js", )}","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'str' is assigned a value but never used.","line":1,"column":7,"nodeType":"Identifier","endLine":1,"endColumn":10}],"errorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"const str = \\"Hello world\\"; // \\"no-unused-vars\\" error\\n"}`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `[${resultsFile1},${resultsFile2}]`, + // Expected output of the linting function + cmdOutput: { + status: 1, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `[${stdoutFile1},${stdoutFile2}]`, + }, // Expected output of the parsing function - parseResult: [ - [], - [], - [ + lintResult: { + isSuccess: false, + warning: [], + error: [ { path: "file1.js", firstLine: 4, @@ -82,7 +86,7 @@ function getFixParams(dir) { message: "'str' is assigned a value but never used (no-unused-vars)", }, ], - ], + }, }; } diff --git a/test/linters/params/flake8.js b/test/linters/params/flake8.js index 2a080411..483502d1 100644 --- a/test/linters/params/flake8.js +++ b/test/linters/params/flake8.js @@ -6,20 +6,22 @@ const testName = "flake8"; const linter = Flake8; const extensions = ["py"]; -// Testing input/output for the Linter.lint function, with auto-fixing disabled +// Linting without auto-fixing function getLintParams(dir) { - const resultsFile1 = `.${sep}file1.py:5:9: E211 whitespace before '('${EOL}.${sep}file1.py:26:1: E305 expected 2 blank lines after class or function definition, found 1`; - const resultsFile2 = `.${sep}file2.py:2:3: E111 indentation is not a multiple of four`; + const stdoutFile1 = `.${sep}file1.py:5:9: E211 whitespace before '('${EOL}.${sep}file1.py:26:1: E305 expected 2 blank lines after class or function definition, found 1`; + const stdoutFile2 = `.${sep}file2.py:2:3: E111 indentation is not a multiple of four`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `${resultsFile1}${EOL}${resultsFile2}`, + // Expected output of the linting function + cmdOutput: { + status: 1, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `${stdoutFile1}${EOL}${stdoutFile2}`, + }, // Expected output of the parsing function - parseResult: [ - [], - [], - [ + lintResult: { + isSuccess: false, + warning: [], + error: [ { path: "file1.py", firstLine: 5, @@ -39,11 +41,11 @@ function getLintParams(dir) { message: "Indentation is not a multiple of four (E111)", }, ], - ], + }, }; } -// Testing input/output for the Linter.lint function, with auto-fixing enabled +// Linting with auto-fixing const getFixParams = getLintParams; // Does not support auto-fixing -> option has no effect module.exports = [testName, linter, extensions, getLintParams, getFixParams]; diff --git a/test/linters/params/gofmt.js b/test/linters/params/gofmt.js index e7b25b1a..137f298b 100644 --- a/test/linters/params/gofmt.js +++ b/test/linters/params/gofmt.js @@ -5,20 +5,22 @@ const testName = "gofmt"; const linter = Gofmt; const extensions = ["go"]; -// Testing input/output for the Linter.lint function, with auto-fixing disabled +// Linting without auto-fixing function getLintParams(dir) { - const resultsFile1 = `diff -u file1.go.orig file1.go\n--- file1.go.orig ${TEST_DATE}\n+++ file1.go ${TEST_DATE}\n@@ -4,7 +4,7 @@\n \n var str = "world"\n \n-func main () { // Whitespace error\n+func main() { // Whitespace error\n fmt.Println("hello " + str)\n }\n \n@@ -17,5 +17,5 @@\n }\n \n func multiply(num1 int, num2 int) int {\n- return num1 * num2 // Indentation error\n+ return num1 * num2 // Indentation error\n }`; - const resultsFile2 = `diff -u file2.go.orig file2.go\n--- file2.go.orig ${TEST_DATE}\n+++ file2.go ${TEST_DATE}\n@@ -1,5 +1,5 @@\n package main\n \n func divide(num1 int, num2 int) int {\n- return num1 / num2 // Whitespace error\n+ return num1 / num2 // Whitespace error\n }`; + const stderrFile1 = `diff -u file1.go.orig file1.go\n--- file1.go.orig ${TEST_DATE}\n+++ file1.go ${TEST_DATE}\n@@ -4,7 +4,7 @@\n \n var str = "world"\n \n-func main () { // Whitespace error\n+func main() { // Whitespace error\n fmt.Println("hello " + str)\n }\n \n@@ -17,5 +17,5 @@\n }\n \n func multiply(num1 int, num2 int) int {\n- return num1 * num2 // Indentation error\n+ return num1 * num2 // Indentation error\n }`; + const stderrFile2 = `diff -u file2.go.orig file2.go\n--- file2.go.orig ${TEST_DATE}\n+++ file2.go ${TEST_DATE}\n@@ -1,5 +1,5 @@\n package main\n \n func divide(num1 int, num2 int) int {\n- return num1 / num2 // Whitespace error\n+ return num1 / num2 // Whitespace error\n }`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `${resultsFile1}\n${resultsFile2}`, + // Expected output of the linting function + cmdOutput: { + status: 0, // gofmt always uses exit code 0 + stderrParts: [stderrFile1, stderrFile2], + stderr: `${stderrFile1}\n${stderrFile2}`, + }, // Expected output of the parsing function - parseResult: [ - [], - [], - [ + lintResult: { + isSuccess: false, + warning: [], + error: [ { path: "file1.go", firstLine: 4, @@ -38,19 +40,24 @@ function getLintParams(dir) { message: ` package main\n \n func divide(num1 int, num2 int) int {\n-\treturn num1 / num2 // Whitespace error\n+\treturn num1 / num2 // Whitespace error\n }`, }, ], - ], + }, }; } -// Testing input/output for the Linter.lint function, with auto-fixing enabled +// Linting with auto-fixing function getFixParams(dir) { return { - // stdout of the lint command - stdout: "", - // Example output of the lint command, used to test the parsing function - parseInput: "", + // Expected output of the linting function + cmdOutput: { + status: 0, // gofmt always uses exit code 0 + stderr: "", + }, // Expected output of the parsing function - parseResult: [[], [], []], + lintResult: { + isSuccess: true, + warning: [], + error: [], + }, }; } diff --git a/test/linters/params/golint.js b/test/linters/params/golint.js index bb64322c..1e35aaf1 100644 --- a/test/linters/params/golint.js +++ b/test/linters/params/golint.js @@ -4,22 +4,24 @@ const testName = "golint"; const linter = Golint; const extensions = ["go"]; -// Testing input/output for the Linter.lint function, with auto-fixing disabled +// Linting without auto-fixing function getLintParams(dir) { - const resultsFile1 = + const stdoutFile1 = "file1.go:14:9: if block ends with a return statement, so drop this else and outdent its block"; - const resultsFile2 = + const stdoutFile2 = 'file2.go:3:1: comment on exported function Divide should be of the form "Divide ..."'; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `${resultsFile1}\n${resultsFile2}`, + // Expected output of the linting function + cmdOutput: { + status: 1, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `${stdoutFile1}\n${stdoutFile2}`, + }, // Expected output of the parsing function - parseResult: [ - [], - [], - [ + lintResult: { + isSuccess: false, + warning: [], + error: [ { path: "file1.go", firstLine: 14, @@ -33,11 +35,11 @@ function getLintParams(dir) { message: `Comment on exported function Divide should be of the form "Divide ..."`, }, ], - ], + }, }; } -// Testing input/output for the Linter.lint function, with auto-fixing enabled +// Linting with auto-fixing const getFixParams = getLintParams; // Does not support auto-fixing -> option has no effect module.exports = [testName, linter, extensions, getLintParams, getFixParams]; diff --git a/test/linters/params/prettier.js b/test/linters/params/prettier.js index 3e8a05f5..865fcf81 100644 --- a/test/linters/params/prettier.js +++ b/test/linters/params/prettier.js @@ -18,20 +18,22 @@ const extensions = [ "yml", ]; -// Testing input/output for the Linter.lint function, with auto-fixing disabled +// Linting without auto-fixing function getLintParams(dir) { - const resultsFile1 = `file1.js`; - const resultsFile2 = `file2.css`; + const stdoutFile1 = `file1.js`; + const stdoutFile2 = `file2.css`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `${resultsFile1}\n${resultsFile2}`, + // Expected output of the linting function + cmdOutput: { + status: 1, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `${stdoutFile1}\n${stdoutFile2}`, + }, // Expected output of the parsing function - parseResult: [ - [], - [], - [ + lintResult: { + isSuccess: false, + warning: [], + error: [ { path: "file1.js", firstLine: 1, @@ -47,19 +49,24 @@ function getLintParams(dir) { "There are issues with this file's formatting, please run Prettier to fix the errors", }, ], - ], + }, }; } -// Testing input/output for the Linter.lint function, with auto-fixing enabled +// Linting with auto-fixing function getFixParams(dir) { return { - // stdout of the lint command - stdout: "", - // Example output of the lint command, used to test the parsing function - parseInput: "", + // Expected output of the linting function + cmdOutput: { + status: 0, + stdout: "", + }, // Expected output of the parsing function - parseResult: [[], [], []], + lintResult: { + isSuccess: true, + warning: [], + error: [], + }, }; } diff --git a/test/linters/params/rubocop.js b/test/linters/params/rubocop.js index 91fbf2f8..11ffa9b7 100644 --- a/test/linters/params/rubocop.js +++ b/test/linters/params/rubocop.js @@ -4,19 +4,21 @@ const testName = "rubocop"; const linter = RuboCop; const extensions = ["rb"]; -// Testing input/output for the Linter.lint function, with auto-fixing disabled +// Linting without auto-fixing function getLintParams(dir) { - const resultsFile1 = `{"path":"file1.rb","offenses":[{"severity":"convention","message":"Redundant \`return\` detected.","cop_name":"Style/RedundantReturn","corrected":false,"location":{"start_line":5,"start_column":3,"last_line":5,"last_column":8,"length":6,"line":5,"column":3}}]}`; - const resultsFile2 = `{"path":"file2.rb","offenses":[{"severity":"warning","message":"Useless assignment to variable - \`x\`.","cop_name":"Lint/UselessAssignment","corrected":false,"location":{"start_line":4,"start_column":1,"last_line":4,"last_column":1,"length":1,"line":4,"column":1}}]}`; + const stdoutFile1 = `{"path":"file1.rb","offenses":[{"severity":"convention","message":"Redundant \`return\` detected.","cop_name":"Style/RedundantReturn","corrected":false,"location":{"start_line":5,"start_column":3,"last_line":5,"last_column":8,"length":6,"line":5,"column":3}}]}`; + const stdoutFile2 = `{"path":"file2.rb","offenses":[{"severity":"warning","message":"Useless assignment to variable - \`x\`.","cop_name":"Lint/UselessAssignment","corrected":false,"location":{"start_line":4,"start_column":1,"last_line":4,"last_column":1,"length":1,"line":4,"column":1}}]}`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `{"metadata":{"rubocop_version":"0.71.0","ruby_engine":"ruby","ruby_version":"2.5.3","ruby_patchlevel":"105","ruby_platform":"x86_64-darwin18"},"files":[${resultsFile1},${resultsFile2}],"summary":{"offense_count":2,"target_file_count":2,"inspected_file_count":2}}`, + // Expected output of the linting function + cmdOutput: { + status: 1, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `{"metadata":{"rubocop_version":"0.71.0","ruby_engine":"ruby","ruby_version":"2.5.3","ruby_patchlevel":"105","ruby_platform":"x86_64-darwin18"},"files":[${stdoutFile1},${stdoutFile2}],"summary":{"offense_count":2,"target_file_count":2,"inspected_file_count":2}}`, + }, // Expected output of the parsing function - parseResult: [ - [], - [ + lintResult: { + isSuccess: false, + warning: [ { path: "file1.rb", firstLine: 5, @@ -30,24 +32,26 @@ function getLintParams(dir) { message: "Useless assignment to variable - `x` (Lint/UselessAssignment)", }, ], - [], - ], + error: [], + }, }; } -// Testing input/output for the Linter.lint function, with auto-fixing enabled +// Linting with auto-fixing function getFixParams(dir) { - const resultsFile1 = `{"path":"file1.rb","offenses":[{"severity":"convention","message":"Redundant \`return\` detected.","cop_name":"Style/RedundantReturn","corrected":true,"location":{"start_line":5,"start_column":3,"last_line":5,"last_column":8,"length":6,"line":5,"column":3}}]}`; - const resultsFile2 = `{"path":"file2.rb","offenses":[{"severity":"warning","message":"Useless assignment to variable - \`x\`.","cop_name":"Lint/UselessAssignment","corrected":false,"location":{"start_line":4,"start_column":1,"last_line":4,"last_column":1,"length":1,"line":4,"column":1}}]}`; + const stdoutFile1 = `{"path":"file1.rb","offenses":[{"severity":"convention","message":"Redundant \`return\` detected.","cop_name":"Style/RedundantReturn","corrected":true,"location":{"start_line":5,"start_column":3,"last_line":5,"last_column":8,"length":6,"line":5,"column":3}}]}`; + const stdoutFile2 = `{"path":"file2.rb","offenses":[{"severity":"warning","message":"Useless assignment to variable - \`x\`.","cop_name":"Lint/UselessAssignment","corrected":false,"location":{"start_line":4,"start_column":1,"last_line":4,"last_column":1,"length":1,"line":4,"column":1}}]}`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `{"metadata":{"rubocop_version":"0.71.0","ruby_engine":"ruby","ruby_version":"2.5.3","ruby_patchlevel":"105","ruby_platform":"x86_64-darwin18"},"files":[${resultsFile1},${resultsFile2}],"summary":{"offense_count":2,"target_file_count":2,"inspected_file_count":2}}`, + // Expected output of the linting function + cmdOutput: { + status: 1, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `{"metadata":{"rubocop_version":"0.71.0","ruby_engine":"ruby","ruby_version":"2.5.3","ruby_patchlevel":"105","ruby_platform":"x86_64-darwin18"},"files":[${stdoutFile1},${stdoutFile2}],"summary":{"offense_count":2,"target_file_count":2,"inspected_file_count":2}}`, + }, // Expected output of the parsing function - parseResult: [ - [], - [ + lintResult: { + isSuccess: false, + warning: [ { path: "file2.rb", firstLine: 4, @@ -55,8 +59,8 @@ function getFixParams(dir) { message: "Useless assignment to variable - `x` (Lint/UselessAssignment)", }, ], - [], - ], + error: [], + }, }; } diff --git a/test/linters/params/stylelint.js b/test/linters/params/stylelint.js index e81db0fe..763cdf90 100644 --- a/test/linters/params/stylelint.js +++ b/test/linters/params/stylelint.js @@ -5,25 +5,27 @@ const testName = "stylelint"; const linter = Stylelint; const extensions = ["css", "sass", "scss"]; -// Testing input/output for the Linter.lint function, with auto-fixing disabled +// Linting without auto-fixing function getLintParams(dir) { - const resultsFile1 = `{"source":"${joinDoubleBackslash( + const stdoutFile1 = `{"source":"${joinDoubleBackslash( dir, "file1.css", )}","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[{"line":2,"column":13,"rule":"no-extra-semicolons","severity":"warning","text":"Unexpected extra semicolon (no-extra-semicolons)"}]}`; - const resultsFile2 = `{"source":"${joinDoubleBackslash( + const stdoutFile2 = `{"source":"${joinDoubleBackslash( dir, "file2.scss", )}","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":true,"warnings":[{"line":1,"column":6,"rule":"block-no-empty","severity":"error","text":"Unexpected empty block (block-no-empty)"}]}`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `[${resultsFile1},${resultsFile2}]`, + // Expected output of the linting function + cmdOutput: { + status: 2, // stylelint exits with the highest severity index found (warning = 1, error = 2) + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `[${stdoutFile1},${stdoutFile2}]`, + }, // Expected output of the parsing function - parseResult: [ - [], - [ + lintResult: { + isSuccess: false, + warning: [ { path: "file1.css", firstLine: 2, @@ -31,7 +33,7 @@ function getLintParams(dir) { message: "Unexpected extra semicolon (no-extra-semicolons)", }, ], - [ + error: [ { path: "file2.scss", firstLine: 1, @@ -39,30 +41,32 @@ function getLintParams(dir) { message: "Unexpected empty block (block-no-empty)", }, ], - ], + }, }; } -// Testing input/output for the Linter.lint function, with auto-fixing enabled +// Linting with auto-fixing function getFixParams(dir) { - const resultsFile1 = `{"source":"${joinDoubleBackslash( + const stdoutFile1 = `{"source":"${joinDoubleBackslash( dir, "file1.css", )}","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":false,"warnings":[]}`; - const resultsFile2 = `{"source":"${joinDoubleBackslash( + const stdoutFile2 = `{"source":"${joinDoubleBackslash( dir, "file2.scss", )}","deprecations":[],"invalidOptionWarnings":[],"parseErrors":[],"errored":true,"warnings":[{"line":1,"column":6,"rule":"block-no-empty","severity":"error","text":"Unexpected empty block (block-no-empty)"}]}`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `[${resultsFile1},${resultsFile2}]`, + // Expected output of the linting function + cmdOutput: { + status: 2, // stylelint exits with the highest severity index found (warning = 1, error = 2) + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `[${stdoutFile1},${stdoutFile2}]`, + }, // Expected output of the parsing function - parseResult: [ - [], - [], - [ + lintResult: { + isSuccess: false, + warning: [], + error: [ { path: "file2.scss", firstLine: 1, @@ -70,7 +74,7 @@ function getFixParams(dir) { message: "Unexpected empty block (block-no-empty)", }, ], - ], + }, }; } diff --git a/test/linters/params/swiftformat.js b/test/linters/params/swiftformat.js index 9ade0575..1985cf19 100644 --- a/test/linters/params/swiftformat.js +++ b/test/linters/params/swiftformat.js @@ -5,7 +5,7 @@ const testName = "swiftformat"; const linter = SwiftFormat; const extensions = ["swift"]; -// Testing input/output for the Linter.lint function, with auto-fixing disabled +// Linting without auto-fixing function getLintParams(dir) { const warning1 = `${join( dir, @@ -17,15 +17,17 @@ function getLintParams(dir) { )}:7:1: warning: (indent) Indent code in accordance with the scope level.`; const warning3 = `${join(dir, "file2.swift")}:2:1: warning: (semicolons) Remove semicolons.`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [warning1, warning2, warning3], - // Example output of the lint command, used to test the parsing function - parseInput: `Running SwiftFormat...\n(lint mode - no files will be changed.)\n${warning1}\n${warning2}\n${warning3}\nwarning: No swift version was specified, so some formatting features were disabled. Specify the version of swift you are using with the --swiftversion command line option, or by adding a .swift-version file to your project.\nSwiftFormat completed in 0.01s.\nSource input did not pass lint check.\n2/2 files require formatting.`, + // Expected output of the linting function + cmdOutput: { + status: 1, + stderrParts: [warning1, warning2, warning3], + stderr: `Running SwiftFormat...\n(lint mode - no files will be changed.)\n${warning1}\n${warning2}\n${warning3}\nwarning: No swift version was specified, so some formatting features were disabled. Specify the version of swift you are using with the --swiftversion command line option, or by adding a .swift-version file to your project.\nSwiftFormat completed in 0.01s.\nSource input did not pass lint check.\n2/2 files require formatting.`, + }, // Expected output of the parsing function - parseResult: [ - [], - [], - [ + lintResult: { + isSuccess: false, + warning: [], + error: [ { path: "file1.swift", firstLine: 3, @@ -46,19 +48,24 @@ function getLintParams(dir) { message: "Remove semicolons (semicolons)", }, ], - ], + }, }; } -// Testing input/output for the Linter.lint function, with auto-fixing enabled +// Linting with auto-fixing function getFixParams(dir) { return { - // stdout of the lint command - stdout: "", - // Example output of the lint command, used to test the parsing function - parseInput: "", + // Expected output of the linting function + cmdOutput: { + status: 0, + stderr: "", + }, // Expected output of the parsing function - parseResult: [[], [], []], + lintResult: { + isSuccess: true, + warning: [], + error: [], + }, }; } diff --git a/test/linters/params/swiftlint.js b/test/linters/params/swiftlint.js index 823ce8ed..75ebfa9d 100644 --- a/test/linters/params/swiftlint.js +++ b/test/linters/params/swiftlint.js @@ -5,25 +5,32 @@ const testName = "swiftlint"; const linter = Swiftlint; const extensions = ["swift"]; -// Testing input/output for the Linter.lint function, with auto-fixing disabled +// Linting without auto-fixing function getLintParams(dir) { - const resultsFile1 = `${join( + const stdoutFile1 = `${join( dir, "file1.swift", )}:5:1: warning: Vertical Whitespace Violation: Limit vertical whitespace to a single empty line. Currently 2. (vertical_whitespace)`; - const resultsFile2 = `${join( + const stdoutFile2 = `${join( dir, "file2.swift", )}:2:22: error: Trailing Semicolon Violation: Lines should not have trailing semicolons. (trailing_semicolon)`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `${resultsFile1}\n${resultsFile2}`, + // Expected output of the linting function + cmdOutput: { + // SwiftLint exit codes: + // - 0: No errors + // - 1: Usage or system error + // - 2: Style violations of severity "Error" + // - 3: No style violations of severity "Error", but severity "Warning" with --strict + status: 2, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `${stdoutFile1}\n${stdoutFile2}`, + }, // Expected output of the parsing function - parseResult: [ - [], - [ + lintResult: { + isSuccess: false, + warning: [ { path: "file1.swift", firstLine: 5, @@ -32,7 +39,7 @@ function getLintParams(dir) { "Vertical Whitespace Violation: Limit vertical whitespace to a single empty line. Currently 2. (vertical_whitespace)", }, ], - [ + error: [ { path: "file2.swift", firstLine: 2, @@ -41,21 +48,32 @@ function getLintParams(dir) { "Trailing Semicolon Violation: Lines should not have trailing semicolons. (trailing_semicolon)", }, ], - ], + }, }; } -// Testing input/output for the Linter.lint function, with auto-fixing enabled +// Linting with auto-fixing function getFixParams(dir) { - const resultsFile1 = `${join(dir, "file1.swift")}:4:1 Corrected Vertical Whitespace`; - const resultsFile2 = `${join(dir, "file2.swift")}:2:22 Corrected Trailing Semicolon`; + const stdoutFile1 = `${join(dir, "file1.swift")}:4:1 Corrected Vertical Whitespace`; + const stdoutFile2 = `${join(dir, "file2.swift")}:2:22 Corrected Trailing Semicolon`; return { - // Strings that must be contained in the stdout of the lint command - stdoutParts: [resultsFile1, resultsFile2], - // Example output of the lint command, used to test the parsing function - parseInput: `${resultsFile1}\n${resultsFile2}`, + // Expected output of the linting function + cmdOutput: { + // SwiftLint exit codes: + // - 0: No errors + // - 1: Usage or system error + // - 2: Style violations of severity "Error" + // - 3: No style violations of severity "Error", but severity "Warning" with --strict + status: 0, + stdoutParts: [stdoutFile1, stdoutFile2], + stdout: `${stdoutFile1}\n${stdoutFile2}`, + }, // Expected output of the parsing function - parseResult: [[], [], []], + lintResult: { + isSuccess: true, + warning: [], + error: [], + }, }; }