diff --git a/dist/index.js b/dist/index.js index cf6190a9..7a550c3c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -8196,13 +8196,18 @@ module.exports = PHPCodeSniffer; /***/ 3460: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +const { sep } = __nccwpck_require__(1017); + const { run } = __nccwpck_require__(9575); const commandExists = __nccwpck_require__(5265); const { initLintResult } = __nccwpck_require__(9149); const { getNpmBinCommand } = __nccwpck_require__(1838); +const { removeANSIColorCodes } = __nccwpck_require__(9321); /** @typedef {import('../utils/lint-result').LintResult} LintResult */ +const PARSE_REGEX = /^\[(warning|error)] ([^:]*): (.*) \(([0-9]+):([0-9]+)\)$/gm; + /** * https://prettier.io */ @@ -8266,13 +8271,41 @@ class Prettier { } const paths = output.stdout.split(/\r?\n/); - lintResult.error = paths.map((path) => ({ - path, - firstLine: 1, - lastLine: 1, - message: - "There are issues with this file's formatting, please run Prettier to fix the errors", - })); + lintResult.error = paths + .filter((path) => !!path) + .map((path) => ({ + path, + firstLine: 1, + lastLine: 1, + message: + "There are issues with this file's formatting, please run Prettier to fix the errors", + })); + + // Fall back to stderr if stdout is empty + if (output.stderr) { + // -no-color not fully respected + const matches = removeANSIColorCodes(output.stderr).matchAll(PARSE_REGEX); + for (const match of matches) { + const [_, level, pathFull, text, line] = match; + const leadingSep = `.${sep}`; + let path = pathFull; + if (path.startsWith(leadingSep)) { + path = path.substring(2); // Remove "./" or ".\" from start of path + } + const lineNr = parseInt(line, 10); + const result = { + path, + firstLine: lineNr, + lastLine: lineNr, + message: text, + }; + if (level === "error") { + lintResult.error.push(result); + } else if (level === "warning") { + lintResult.warning.push(result); + } + } + } return lintResult; } @@ -9437,9 +9470,23 @@ function removeTrailingPeriod(str) { return str[str.length - 1] === "." ? str.substring(0, str.length - 1) : str; } +/** + * Strips ansi escape codes + * @param {string} str - Console output to escape + * @returns {string} - Pure ansi output + */ +function removeANSIColorCodes(str) { + return str.replace( + // eslint-disable-next-line no-control-regex + /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, + "", + ); +} + module.exports = { capitalizeFirstLetter, removeTrailingPeriod, + removeANSIColorCodes, }; diff --git a/src/linters/prettier.js b/src/linters/prettier.js index 8ff1dcae..8ebac49e 100644 --- a/src/linters/prettier.js +++ b/src/linters/prettier.js @@ -1,10 +1,15 @@ +const { sep } = require("path"); + const { run } = require("../utils/action"); const commandExists = require("../utils/command-exists"); const { initLintResult } = require("../utils/lint-result"); const { getNpmBinCommand } = require("../utils/npm/get-npm-bin-command"); +const { removeANSIColorCodes } = require("../utils/string"); /** @typedef {import('../utils/lint-result').LintResult} LintResult */ +const PARSE_REGEX = /^\[(warning|error)] ([^:]*): (.*) \(([0-9]+):([0-9]+)\)$/gm; + /** * https://prettier.io */ @@ -68,13 +73,41 @@ class Prettier { } const paths = output.stdout.split(/\r?\n/); - lintResult.error = paths.map((path) => ({ - path, - firstLine: 1, - lastLine: 1, - message: - "There are issues with this file's formatting, please run Prettier to fix the errors", - })); + lintResult.error = paths + .filter((path) => !!path) + .map((path) => ({ + path, + firstLine: 1, + lastLine: 1, + message: + "There are issues with this file's formatting, please run Prettier to fix the errors", + })); + + // Fall back to stderr if stdout is empty + if (output.stderr) { + // -no-color not fully respected + const matches = removeANSIColorCodes(output.stderr).matchAll(PARSE_REGEX); + for (const match of matches) { + const [_, level, pathFull, text, line] = match; + const leadingSep = `.${sep}`; + let path = pathFull; + if (path.startsWith(leadingSep)) { + path = path.substring(2); // Remove "./" or ".\" from start of path + } + const lineNr = parseInt(line, 10); + const result = { + path, + firstLine: lineNr, + lastLine: lineNr, + message: text, + }; + if (level === "error") { + lintResult.error.push(result); + } else if (level === "warning") { + lintResult.warning.push(result); + } + } + } return lintResult; } diff --git a/src/utils/string.js b/src/utils/string.js index aa88b7c7..18b2096c 100644 --- a/src/utils/string.js +++ b/src/utils/string.js @@ -16,7 +16,21 @@ function removeTrailingPeriod(str) { return str[str.length - 1] === "." ? str.substring(0, str.length - 1) : str; } +/** + * Strips ansi escape codes + * @param {string} str - Console output to escape + * @returns {string} - Pure ansi output + */ +function removeANSIColorCodes(str) { + return str.replace( + // eslint-disable-next-line no-control-regex + /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, + "", + ); +} + module.exports = { capitalizeFirstLetter, removeTrailingPeriod, + removeANSIColorCodes, }; diff --git a/test/linters/linters.test.js b/test/linters/linters.test.js index 6c7366b9..1b60dbf4 100644 --- a/test/linters/linters.test.js +++ b/test/linters/linters.test.js @@ -2,6 +2,7 @@ const { join } = require("path"); const { copy, remove } = require("fs-extra"); +const { removeANSIColorCodes } = require("../../src/utils/string"); const { normalizeDates, normalizePaths, createTmpDir } = require("../test-utils"); const autopep8Params = require("./params/autopep8"); const blackParams = require("./params/black"); @@ -17,6 +18,7 @@ const golintParams = require("./params/golint"); const mypyParams = require("./params/mypy"); const phpCodeSnifferParams = require("./params/php-codesniffer"); const prettierParams = require("./params/prettier"); +const prettierInvalidParams = require("./params/prettier-invalid"); const pylintParams = require("./params/pylint"); const ruboCopParams = require("./params/rubocop"); const rustfmtParams = require("./params/rustfmt"); @@ -42,6 +44,7 @@ const linterParams = [ mypyParams, phpCodeSnifferParams, prettierParams, + prettierInvalidParams, pylintParams, ruboCopParams, rustfmtParams, @@ -109,10 +112,10 @@ describe.each(linterParams)( stderr = normalizePaths(stderr, tmpDir); if ("stderrParts" in expected.cmdOutput) { expected.cmdOutput.stderrParts.forEach((stderrParts) => - expect(stderr).toEqual(expect.stringContaining(stderrParts)), + expect(removeANSIColorCodes(stderr)).toEqual(expect.stringContaining(stderrParts)), ); } else if ("stderr" in expected.cmdOutput) { - expect(stderr).toEqual(expected.cmdOutput.stderr); + expect(removeANSIColorCodes(stderr)).toEqual(expected.cmdOutput.stderr); } }); diff --git a/test/linters/params/prettier-invalid.js b/test/linters/params/prettier-invalid.js new file mode 100644 index 00000000..a83ce3a7 --- /dev/null +++ b/test/linters/params/prettier-invalid.js @@ -0,0 +1,63 @@ +const Prettier = require("../../../src/linters/prettier"); + +const testName = "prettier-invalid"; +const linter = Prettier; +const args = ""; +const commandPrefix = ""; +const extensions = [ + "css", + "html", + "js", + "json", + "jsx", + "md", + "sass", + "scss", + "ts", + "tsx", + "vue", + "yaml", + "yml", +]; + +// Linting without auto-fixing +function getLintParams(dir) { + const stdoutFile2 = `file2.css`; + return { + // Expected output of the linting function + cmdOutput: { + status: 2, + stdoutParts: [stdoutFile2], + stdout: `${stdoutFile2}`, + stderr: + "[error] file1.ts: SyntaxError: ')' expected. (8:38)\n[error] 6 |\n[error] 7 | let userName: string = 'John';\n[error] > 8 | let greeting: string = greet(userName; // Syntax error\n[error] | ^\n[error] 9 |\n[error] 10 | console.log(greeting);\n[error] 11 |", + }, + // Expected output of the parsing function + lintResult: { + isSuccess: false, + warning: [], + error: [ + { + path: "file2.css", + firstLine: 1, + lastLine: 1, + message: + "There are issues with this file's formatting, please run Prettier to fix the errors", + }, + { + path: "file1.ts", + firstLine: 8, + lastLine: 8, + message: "SyntaxError: ')' expected.", + }, + ], + }, + }; +} + +// Linting with auto-fixing +function getFixParams(dir) { + return getLintParams(dir); +} + +module.exports = [testName, linter, commandPrefix, extensions, args, getLintParams, getFixParams]; diff --git a/test/linters/projects/prettier-invalid/.gitignore b/test/linters/projects/prettier-invalid/.gitignore new file mode 100644 index 00000000..c2658d7d --- /dev/null +++ b/test/linters/projects/prettier-invalid/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/test/linters/projects/prettier-invalid/.prettierrc.json b/test/linters/projects/prettier-invalid/.prettierrc.json new file mode 100644 index 00000000..b8a6a2e9 --- /dev/null +++ b/test/linters/projects/prettier-invalid/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "semi": true, + "singleQuote": true, + "useTabs": true +} diff --git a/test/linters/projects/prettier-invalid/file1.ts b/test/linters/projects/prettier-invalid/file1.ts new file mode 100644 index 00000000..9218d294 --- /dev/null +++ b/test/linters/projects/prettier-invalid/file1.ts @@ -0,0 +1,10 @@ +// example.ts + +function greet(name: string): string { + return `Hello, ${name}!` +} + +let userName: string = 'John'; +let greeting: string = greet(userName; // Syntax error + +console.log(greeting); diff --git a/test/linters/projects/prettier-invalid/file2.css b/test/linters/projects/prettier-invalid/file2.css new file mode 100644 index 00000000..0d5d61b9 --- /dev/null +++ b/test/linters/projects/prettier-invalid/file2.css @@ -0,0 +1 @@ +body {color: red;} /* Line break errors */ diff --git a/test/linters/projects/prettier-invalid/package.json b/test/linters/projects/prettier-invalid/package.json new file mode 100644 index 00000000..19b3db4d --- /dev/null +++ b/test/linters/projects/prettier-invalid/package.json @@ -0,0 +1,8 @@ +{ + "name": "test-prettier-invalid", + "main": "index.js", + "private": true, + "devDependencies": { + "prettier": "^2.8.1" + } +} diff --git a/test/linters/projects/prettier-invalid/yarn.lock b/test/linters/projects/prettier-invalid/yarn.lock new file mode 100644 index 00000000..e07cd701 --- /dev/null +++ b/test/linters/projects/prettier-invalid/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +prettier@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.1.tgz#4e1fd11c34e2421bc1da9aea9bd8127cd0a35efc" + integrity sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==