From df31ae8635c46366688e0bea44d7aba2e1468813 Mon Sep 17 00:00:00 2001 From: Hugo van Rijswijk Date: Tue, 30 Jul 2024 13:08:13 +0200 Subject: [PATCH] Update action and dependencies to node 20 (#768) --- .github/workflows/build.yml | 4 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/lint.yml | 4 +- .github/workflows/lock.yml | 2 +- .github/workflows/test-action.yml | 6 +- .github/workflows/test.yml | 18 +- .nvmrc | 2 +- README.md | 16 +- action.yml | 2 +- dist/index.js | 1809 ++++++++++++++++--------- package.json | 11 +- src/linters/clang-format.js | 4 +- test/linters/params/clippy.js | 4 +- test/linters/params/swiftlint.js | 2 +- test/test-utils.js | 2 +- yarn.lock | 118 +- 16 files changed, 1291 insertions(+), 715 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2862ca33..3c5d8f14 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,13 +19,13 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Custom token to allow commits trigger other workflows. token: ${{ secrets.BUILD_ACTION_GITHUB_TOKEN }} - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: ".nvmrc" cache: "yarn" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 573a3037..a4fd5abd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c0236808..7d9f3d7f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,10 +22,10 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: ".nvmrc" cache: "yarn" diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 56134032..8bd415d8 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v4 + - uses: dessant/lock-threads@v5 with: issue-inactive-days: 10 pr-inactive-days: 10 diff --git a/.github/workflows/test-action.yml b/.github/workflows/test-action.yml index 6ce4937c..88a1a619 100644 --- a/.github/workflows/test-action.yml +++ b/.github/workflows/test-action.yml @@ -28,16 +28,16 @@ jobs: steps: - name: Check out repository (push) if: ${{ github.event_name == 'push' }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check out repository (pull_request_target) if: ${{ github.event_name == 'pull_request_target' }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: ".nvmrc" cache: "yarn" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0849f0b9..ceaf0351 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # ClangFormat @@ -44,7 +44,7 @@ jobs: # Go - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: 1.19.4 @@ -56,7 +56,7 @@ jobs: # Node.js - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: ".nvmrc" cache: "yarn" @@ -92,7 +92,7 @@ jobs: # Python - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 cache: "pip" @@ -114,7 +114,7 @@ jobs: bundler: 2 - name: Set up bundle cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./test/linters/projects/**/vendor/bundle key: ${{ runner.os }}-gems-${{ hashFiles('./test/linters/projects/**/Gemfile.lock', '.github/workflows/test.yml') }} @@ -139,15 +139,15 @@ jobs: - name: Set up Swift cache (Linux) id: cache-swift if: startsWith(matrix.os, 'ubuntu') - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./swift-format/.build - key: ${{ runner.os }}-swift-0.50500.0 + key: ${{ runner.os }}-swift-509.0.0 - name: Install Swift dependencies (Linux, uncached) if: steps.cache-swift.outputs.cache-hit != 'true' && startsWith(matrix.os, 'ubuntu') run: | - git clone --branch 0.50500.0 --depth 1 https://github.com/apple/swift-format + git clone --branch 509.0.0 --depth 1 https://github.com/apple/swift-format cd swift-format swift build -c release echo "${PWD}/.build/release" >> $GITHUB_PATH @@ -161,7 +161,7 @@ jobs: - name: Set up Mint cache (macOS) if: startsWith(matrix.os, 'macos') - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.mint key: ${{ runner.os }}-mint-${{ hashFiles('./test/linters/projects/**/Mintfile', '.github/workflows/test.yml') }} diff --git a/.nvmrc b/.nvmrc index b6a7d89c..209e3ef4 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16 +20 diff --git a/README.md b/README.md index e4a08da4..5332f3ef 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Install your linters here @@ -130,7 +130,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v1 @@ -172,7 +172,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 @@ -211,7 +211,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 @@ -253,7 +253,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v1 @@ -292,7 +292,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install ClangFormat run: sudo apt-get install -y clang-format @@ -325,7 +325,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up .NET uses: actions/setup-dotnet@v1 @@ -360,7 +360,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v1 diff --git a/action.yml b/action.yml index 0bc82a37..a828f3f5 100644 --- a/action.yml +++ b/action.yml @@ -637,7 +637,7 @@ inputs: default: "true" runs: - using: node16 + using: node20 main: ./dist/index.js branding: diff --git a/dist/index.js b/dist/index.js index cf6190a9..545a4267 100644 --- a/dist/index.js +++ b/dist/index.js @@ -6320,128 +6320,118 @@ exports["default"] = _default; /***/ }), -/***/ 4207: +/***/ 6143: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { -const isWindows = process.platform === 'win32' || - process.env.OSTYPE === 'cygwin' || - process.env.OSTYPE === 'msys' - -const path = __nccwpck_require__(1017) -const COLON = isWindows ? ';' : ':' const isexe = __nccwpck_require__(7126) +const { join, delimiter, sep, posix } = __nccwpck_require__(1017) + +const isWindows = process.platform === 'win32' + +// used to check for slashed in commands passed in. always checks for the posix +// seperator on all platforms, and checks for the current separator when not on +// a posix platform. don't use the isWindows check for this since that is mocked +// in tests but we still need the code to actually work when called. that is also +// why it is ignored from coverage. +/* istanbul ignore next */ +const rSlash = new RegExp(`[${posix.sep}${sep === posix.sep ? '' : sep}]`.replace(/(\\)/g, '\\$1')) +const rRel = new RegExp(`^\\.${rSlash.source}`) const getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: 'ENOENT' }) -const getPathInfo = (cmd, opt) => { - const colon = opt.colon || COLON - +const getPathInfo = (cmd, { + path: optPath = process.env.PATH, + pathExt: optPathExt = process.env.PATHEXT, + delimiter: optDelimiter = delimiter, +}) => { // If it has a slash, then we don't bother searching the pathenv. // just check the file itself, and that's it. - const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [''] - : ( - [ - // windows always checks the cwd first - ...(isWindows ? [process.cwd()] : []), - ...(opt.path || process.env.PATH || - /* istanbul ignore next: very unusual */ '').split(colon), - ] - ) - const pathExtExe = isWindows - ? opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM' - : '' - const pathExt = isWindows ? pathExtExe.split(colon) : [''] + const pathEnv = cmd.match(rSlash) ? [''] : [ + // windows always checks the cwd first + ...(isWindows ? [process.cwd()] : []), + ...(optPath || /* istanbul ignore next: very unusual */ '').split(optDelimiter), + ] if (isWindows) { - if (cmd.indexOf('.') !== -1 && pathExt[0] !== '') + const pathExtExe = optPathExt || + ['.EXE', '.CMD', '.BAT', '.COM'].join(optDelimiter) + const pathExt = pathExtExe.split(optDelimiter).reduce((acc, item) => { + acc.push(item) + acc.push(item.toLowerCase()) + return acc + }, []) + if (cmd.includes('.') && pathExt[0] !== '') { pathExt.unshift('') + } + return { pathEnv, pathExt, pathExtExe } } - return { - pathEnv, - pathExt, - pathExtExe, - } + return { pathEnv, pathExt: [''] } } -const which = (cmd, opt, cb) => { - if (typeof opt === 'function') { - cb = opt - opt = {} - } - if (!opt) - opt = {} +const getPathPart = (raw, cmd) => { + const pathPart = /^".*"$/.test(raw) ? raw.slice(1, -1) : raw + const prefix = !pathPart && rRel.test(cmd) ? cmd.slice(0, 2) : '' + return prefix + join(pathPart, cmd) +} +const which = async (cmd, opt = {}) => { const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) const found = [] - const step = i => new Promise((resolve, reject) => { - if (i === pathEnv.length) - return opt.all && found.length ? resolve(found) - : reject(getNotFoundError(cmd)) + for (const envPart of pathEnv) { + const p = getPathPart(envPart, cmd) - const ppRaw = pathEnv[i] - const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw - - const pCmd = path.join(pathPart, cmd) - const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd - : pCmd + for (const ext of pathExt) { + const withExt = p + ext + const is = await isexe(withExt, { pathExt: pathExtExe, ignoreErrors: true }) + if (is) { + if (!opt.all) { + return withExt + } + found.push(withExt) + } + } + } - resolve(subStep(p, i, 0)) - }) + if (opt.all && found.length) { + return found + } - const subStep = (p, i, ii) => new Promise((resolve, reject) => { - if (ii === pathExt.length) - return resolve(step(i + 1)) - const ext = pathExt[ii] - isexe(p + ext, { pathExt: pathExtExe }, (er, is) => { - if (!er && is) { - if (opt.all) - found.push(p + ext) - else - return resolve(p + ext) - } - return resolve(subStep(p, i, ii + 1)) - }) - }) + if (opt.nothrow) { + return null + } - return cb ? step(0).then(res => cb(null, res), cb) : step(0) + throw getNotFoundError(cmd) } -const whichSync = (cmd, opt) => { - opt = opt || {} - +const whichSync = (cmd, opt = {}) => { const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) const found = [] - for (let i = 0; i < pathEnv.length; i ++) { - const ppRaw = pathEnv[i] - const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw + for (const pathEnvPart of pathEnv) { + const p = getPathPart(pathEnvPart, cmd) - const pCmd = path.join(pathPart, cmd) - const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd - : pCmd - - for (let j = 0; j < pathExt.length; j ++) { - const cur = p + pathExt[j] - try { - const is = isexe.sync(cur, { pathExt: pathExtExe }) - if (is) { - if (opt.all) - found.push(cur) - else - return cur + for (const ext of pathExt) { + const withExt = p + ext + const is = isexe.sync(withExt, { pathExt: pathExtExe, ignoreErrors: true }) + if (is) { + if (!opt.all) { + return withExt } - } catch (ex) {} + found.push(withExt) + } } } - if (opt.all && found.length) + if (opt.all && found.length) { return found + } - if (opt.nothrow) + if (opt.nothrow) { return null + } throw getNotFoundError(cmd) } @@ -7013,12 +7003,14 @@ module.exports = Black; /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { const glob = __nccwpck_require__(1957); -const { quoteAll } = __nccwpck_require__(4411); +const { Shescape } = __nccwpck_require__(4411); const { run } = __nccwpck_require__(9575); const commandExists = __nccwpck_require__(5265); const { initLintResult } = __nccwpck_require__(9149); +const { quoteAll } = new Shescape({ shell: false }); + /** @typedef {import('../utils/lint-result').LintResult} LintResult */ /** @@ -9509,27 +9501,59 @@ module.exports = require("net"); /***/ }), -/***/ 2037: +/***/ 7561: /***/ ((module) => { "use strict"; -module.exports = require("os"); +module.exports = require("node:fs"); /***/ }), -/***/ 1017: +/***/ 612: /***/ ((module) => { "use strict"; -module.exports = require("path"); +module.exports = require("node:os"); + +/***/ }), + +/***/ 9411: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:path"); + +/***/ }), + +/***/ 7742: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:process"); + +/***/ }), + +/***/ 7261: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:util"); + +/***/ }), + +/***/ 2037: +/***/ ((module) => { + +"use strict"; +module.exports = require("os"); /***/ }), -/***/ 7282: +/***/ 1017: /***/ ((module) => { "use strict"; -module.exports = require("process"); +module.exports = require("path"); /***/ }), @@ -9555,12 +9579,12 @@ module.exports = require("util"); "use strict"; -var os = __nccwpck_require__(2037); -var process = __nccwpck_require__(7282); -var fs = __nccwpck_require__(7147); -var path = __nccwpck_require__(1017); -var util = __nccwpck_require__(3837); -var which = __nccwpck_require__(4207); +var os = __nccwpck_require__(612); +var process = __nccwpck_require__(7742); +var fs = __nccwpck_require__(7561); +var path = __nccwpck_require__(9411); +var which = __nccwpck_require__(6143); +var node_util = __nccwpck_require__(7261); function _interopNamespaceDefault(e) { var n = Object.create(null); @@ -9583,59 +9607,7 @@ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs); var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path); /** - * @overview Provides functionality related to working with executables. - * @license MPL-2.0 - */ - -/** - * Resolves the location of an executable given an arbitrary valid string - * representation of that executable. - * - * To obtain the location of the executable this function (if necessary): - * - Expands the provided string to a absolute path. - * - Follows symbolic links. - * - * @param {object} args The arguments for this function. - * @param {string} args.executable A string representation of the executable. - * @param {object} deps The dependencies for this function. - * @param {Function} deps.exists A function to check if a file exists. - * @param {Function} deps.readlink A function to resolve (sym)links. - * @param {Function} deps.which A function to perform a `which(1)`-like lookup. - * @returns {string} The full path to the binary of the executable. - * @throws {Error} If the `deps` aren't provided. - */ -function resolveExecutable({ executable }, { exists, readlink, which }) { - if (readlink === undefined || which === undefined) { - throw new Error(); - } - - try { - executable = which(executable); - } catch (_) { - // For backwards compatibility return the executable even if its location - // cannot be obtained - return executable; - } - - if (!exists(executable)) { - // For backwards compatibility return the executable even if there exists no - // file at the specified path - return executable; - } - - try { - executable = readlink(executable); - } catch (_) { - // An error will be thrown if the executable is not a (sym)link, this is not - // a problem so the error is ignored - } - - return executable; -} - -/** - * @overview Provides an API to consistently escape or quote shell arguments - * across platforms. + * @overview Provides reflection functionality. * @license MPL-2.0 */ @@ -9665,408 +9637,728 @@ const typeofFunction = "function"; const typeofString = "string"; /** - * Checks if a value is a string. + * Check if the given object has the given property as an own property. * - * @param {any} value The value of interest. - * @returns {boolean} `true` if `value` is a string, `false` otherwise. + * This custom function is used over `Object.hasOwn` because that isn't + * available in all supported Node.js versions. + * + * @param {object} object The object of interest. + * @param {string} property The property of interest. + * @returns {boolean} `true` if property is an own-property, `false` otherwise. */ -function isString(value) { - return typeof value === typeofString; +function hasOwn(object, property) { + return Object.prototype.hasOwnProperty.call(object, property); } /** - * Checks if a value can be converted into a string. + * Checks if a value can be converted into a string and converts it if possible. * * @param {any} value The value of interest. - * @returns {boolean} `true` if `value` is stringable, `false` otherwise. + * @returns {string | null} If possible the string of `value`, otherwise `null`. */ -function isStringable(value) { +function maybeToString(value) { if (value === undefined || value === null) { - return false; + return null; } if (typeof value.toString !== typeofFunction) { - return false; + return null; } - const str = value.toString(); - return isString(str); + const maybeStr = value.toString(); + if (isString(maybeStr)) { + return maybeStr; + } else { + return null; + } } /** - * Parses options provided to {@link escapeShellArg} or {@link quoteShellArg}. + * Convert a value into a string if that is possible. * - * @param {object} args The arguments for this function. - * @param {object} args.options The options for escaping. - * @param {string} [args.options.shell] The shell to escape for. - * @param {boolean} [args.options.interpolation] Is interpolation enabled. - * @param {object} args.process The `process` values. - * @param {object} args.process.env The environment variables. - * @param {object} deps The dependencies for this function. - * @param {Function} deps.getDefaultShell Get the default shell for the system. - * @param {Function} deps.getShellName Get the name of a shell. - * @returns {object} The parsed arguments. + * @param {any} value The value to convert into a string. + * @returns {string} The `value` as a string. + * @throws {TypeError} The `value` is not stringable. */ -function parseOptions( - { options: { interpolation, shell }, process: { env } }, - { getDefaultShell, getShellName } -) { - interpolation = interpolation ? true : false; - shell = isString(shell) ? shell : getDefaultShell({ env }); - - const shellName = getShellName({ shell }, { resolveExecutable }); - return { interpolation, shellName }; -} +function checkedToString(value) { + if (isString(value)) { + return value; + } -/** - * Escapes an argument for the given shell. - * - * @param {object} args The arguments for this function. - * @param {string} args.arg The argument to escape. - * @param {boolean} args.interpolation Is interpolation enabled. - * @param {boolean} args.quoted Is `arg` being quoted. - * @param {string} args.shellName The name of the shell to escape `arg` for. - * @param {object} deps The dependencies for this function. - * @param {Function} deps.getEscapeFunction Get the escape function for a shell. - * @returns {string} The escaped argument. - * @throws {TypeError} The argument to escape is not stringable. - */ -function escape$1( - { arg, interpolation, quoted, shellName }, - { getEscapeFunction } -) { - if (!isStringable(arg)) { + const maybeStr = maybeToString(value); + if (maybeStr === null) { throw new TypeError(typeError); } - const argAsString = arg.toString(); - const escape = getEscapeFunction(shellName); - const escapedArg = escape(argAsString, { interpolation, quoted }); - return escapedArg; + return maybeStr; } /** - * Quotes and escape an argument for the given shell. + * Checks if a value is a string. * - * @param {object} args The arguments for this function. - * @param {string} args.arg The argument to escape. - * @param {string} args.shellName The name of the shell to escape `arg` for. - * @param {object} deps The dependencies for this function. - * @param {Function} deps.getEscapeFunction Get the escape function for a shell. - * @param {Function} deps.getQuoteFunction Get the quote function for a shell. - * @returns {string} The quoted and escaped argument. - * @throws {TypeError} The argument to escape is not stringable. + * @param {any} value The value of interest. + * @returns {boolean} `true` if `value` is a string, `false` otherwise. */ -function quote$1({ arg, shellName }, { getEscapeFunction, getQuoteFunction }) { - const escapedArg = escape$1( - { arg, interpolation: false, quoted: true, shellName }, - { getEscapeFunction } - ); - const quote = getQuoteFunction(shellName); - const escapedAndQuotedArg = quote(escapedArg); - return escapedAndQuotedArg; +function isString(value) { + return typeof value === typeofString; } /** - * Escapes an argument for the given shell. + * @overview Provides functionality related to working with executables. + * @license MPL-2.0 + */ + + +/** + * Build error messages for when executables cannot be found. * - * @param {object} args The arguments for this function. - * @param {string} args.arg The argument to escape. - * @param {object} args.options The options for escaping `arg`. - * @param {boolean} [args.options.interpolation] Is interpolation enabled. - * @param {string} [args.options.shell] The shell to escape `arg` for. - * @param {object} args.process The `process` values. - * @param {object} args.process.env The environment variables. - * @param {object} deps The dependencies for this function. - * @param {Function} deps.getDefaultShell Get the default shell for the system. - * @param {Function} deps.getEscapeFunction Get an escape function for a shell. - * @param {Function} deps.getShellName Get the name of a shell. - * @returns {string} The escaped argument. + * @param {string} executable The executable being looked up. + * @returns {string} The executable not found error message. */ -function escapeShellArg( - { arg, options: { interpolation, shell }, process: { env } }, - { getDefaultShell, getEscapeFunction, getShellName } -) { - const options = parseOptions( - { options: { interpolation, shell }, process: { env } }, - { getDefaultShell, getShellName } - ); - return escape$1( - { - arg, - interpolation: options.interpolation, - quoted: false, - shellName: options.shellName, - }, - { getEscapeFunction } - ); +function notFoundError(executable) { + return `No executable could be found for ${executable}`; } /** - * Quotes and escape an argument for the given shell. + * Resolves the location of an executable given an arbitrary valid string + * representation of that executable. + * + * To obtain the location of the executable this function (if necessary): + * - Expands the provided string to an absolute path. + * - Follows symbolic links. * * @param {object} args The arguments for this function. - * @param {string} args.arg The argument to escape. - * @param {object} args.options The options for escaping `arg`. - * @param {string} [args.options.shell] The shell to escape `arg` for. - * @param {object} args.process The `process` values. - * @param {object} args.process.env The environment variables. + * @param {Object} args.env The environment variables. + * @param {string} args.executable A string representation of the executable. * @param {object} deps The dependencies for this function. - * @param {Function} deps.getDefaultShell Get the default shell for the system. - * @param {Function} deps.getEscapeFunction Get an escape function for a shell. - * @param {Function} deps.getQuoteFunction Get a quote function for a shell. - * @param {Function} deps.getShellName Get the name of a shell. - * @returns {string} The quoted and escaped argument. + * @param {Function} deps.exists A function to check if a file exists. + * @param {Function} deps.readlink A function to resolve (sym)links. + * @param {Function} deps.which A function to perform a `which(1)`-like lookup. + * @returns {string} The full path to the binary of the executable. + * @throws {Error} If the executable could not be found. */ -function quoteShellArg( - { arg, options: { shell }, process: { env } }, - { getDefaultShell, getEscapeFunction, getQuoteFunction, getShellName } +function resolveExecutable( + { env, executable }, + { exists, readlink, which }, ) { - const options = parseOptions( - { options: { shell }, process: { env } }, - { getDefaultShell, getShellName } - ); - return quote$1( - { arg, shellName: options.shellName }, - { getEscapeFunction, getQuoteFunction } - ); + let resolved = executable; + try { + const path = hasOwn(env, "PATH") + ? env.PATH + : hasOwn(env, "Path") + ? env.Path + : undefined; + resolved = which(resolved, { path }); + } catch (_) { + throw new Error(notFoundError(executable)); + } + + if (!exists(resolved)) { + throw new Error(notFoundError(executable)); + } + + try { + resolved = readlink(resolved); + } catch (_) { + // An error will be thrown if the executable is not a (sym)link, this is not + // a problem so the error is ignored + } + + return resolved; } /** - * @overview Provides functionality specifically for Unix systems. + * @overview Provides functionality for parsing shescape options. * @license MPL-2.0 */ -/** - * The name of the Bourne-again shell (Bash) binary. - * - * @constant - * @type {string} - */ -const binBash = "bash"; /** - * The name of the C shell (csh) binary. + * The identifier for 'no shell' or the absence of a shell. * * @constant - * @type {string} + * @type {symbol} */ -const binCsh = "csh"; +const noShell = Symbol(); /** - * The name of the Debian Almquist shell (Dash) binary. + * Build error messages for unsupported shells. * - * @constant - * @type {string} + * @param {string} shellName The full name of a shell. + * @returns {string} The unsupported shell error message. */ -const binDash = "dash"; +function unsupportedError$2(shellName) { + return `Shescape does not support the shell ${shellName}`; +} /** - * The name of the Z shell (Zsh) binary. + * Parses options provided to shescape. * - * @constant - * @type {string} + * @param {object} args The arguments for this function. + * @param {Object} args.env The environment variables. + * @param {object} args.options The options for escaping. + * @param {object} deps The dependencies for this function. + * @param {Function} deps.getDefaultShell Function to get the default shell. + * @param {Function} deps.getShellName Function to get the name of a shell. + * @param {Function} deps.isShellSupported Function to see if a shell is usable. + * @returns {object} The parsed arguments. + * @throws {Error} The shell is not supported or could not be found. */ -const binZsh = "zsh"; +function parseOptions( + { env, options }, + { getDefaultShell, getShellName, isShellSupported }, +) { + let flagProtection = hasOwn(options, "flagProtection") + ? options.flagProtection + : undefined; + let shell = hasOwn(options, "shell") ? options.shell : undefined; -/** - * Escapes a shell argument for use in Bash(-like shells). - * - * @param {string} arg The argument to escape. - * @param {object} options The escape options. - * @param {boolean} options.interpolation Is interpolation enabled. - * @param {boolean} options.quoted Is `arg` being quoted. - * @returns {string} The escaped argument. - */ -function escapeArgBash(arg, { interpolation, quoted }) { - let result = arg - .replace(/[\0\u0008\u001B\u009B]/gu, "") - .replace(/\r(?!\n)/gu, ""); + flagProtection = + flagProtection === undefined ? true : flagProtection ? true : false; + + let shellName = noShell; + if (shell !== false) { + if (!isString(shell)) { + shell = getDefaultShell({ env }); + } - if (interpolation) { - result = result - .replace(/\\/gu, "\\\\") - .replace(/\r?\n/gu, " ") - .replace(/(^|\s)([#~])/gu, "$1\\$2") - .replace(/(["$&'()*;<>?`{|])/gu, "\\$1") - .replace(/(?<=[:=])(~)(?=[\s+\-/0:=]|$)/gu, "\\$1") - .replace(/([\t ])/gu, "\\$1"); - } else if (quoted) { - result = result.replace(/'/gu, `'\\''`); + shellName = getShellName({ env, shell }, { resolveExecutable }); } - return result; + if (!isShellSupported(shellName)) { + throw new Error(unsupportedError$2(shellName)); + } + + return { flagProtection, shellName }; } /** - * Escapes a shell argument for use in csh. + * @overview Provides functionality for the Bourne-again shell (Bash). + * @license MPL-2.0 + */ + +/** + * Escape an argument for use in Bash. * * @param {string} arg The argument to escape. - * @param {object} options The escape options. - * @param {boolean} options.interpolation Is interpolation enabled. - * @param {boolean} options.quoted Is `arg` being quoted. * @returns {string} The escaped argument. */ -function escapeArgCsh(arg, { interpolation, quoted }) { - let result = arg - .replace(/[\0\u0008\u001B\u009B]/gu, "") - .replace(/\r?\n|\r/gu, " "); - - if (interpolation) { - result = result - .replace(/\\/gu, "\\\\") - .replace(/(^|\s)(~)/gu, "$1\\$2") - .replace(/(["#$&'()*;<>?[`{|])/gu, "\\$1") - .replace(/([\t ])/gu, "\\$1"); - - const textEncoder = new util.TextEncoder(); - result = result - .split("") - .map( - // Due to a bug in C shell version 20110502-7, when a character whose - // utf-8 encoding includes the bytes 0xA0 (160 in decimal) appears in - // an argument after an escaped character, it will hang and endlessly - // consume memory unless the character is escaped with quotes. - // ref: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=995013 - (char) => (textEncoder.encode(char).includes(160) ? `'${char}'` : char) - ) - .join(""); - } else { - result = result.replace(/\\!$/gu, "\\\\!"); - if (quoted) { - result = result.replace(/'/gu, `'\\''`); - } - } - - result = result.replace(/!(?!$)/gu, "\\!"); - - return result; +function escapeArg$7(arg) { + return arg + .replace(/[\0\u0008\r\u001B\u009B]/gu, "") + .replace(/\n/gu, " ") + .replace(/\\/gu, "\\\\") + .replace(/(?<=^|\s)([#~])/gu, "\\$1") + .replace(/(["$&'()*;<>?`{|])/gu, "\\$1") + .replace(/(?<=[:=])(~)(?=[\s+\-/0:=]|$)/gu, "\\$1") + .replace(/([\t ])/gu, "\\$1"); } /** - * Escapes a shell argument for use in Dash. + * Returns a function to escape arguments for use in Bash for the given use + * case. * - * @param {string} arg The argument to escape. - * @param {object} options The escape options. - * @param {boolean} options.interpolation Is interpolation enabled. - * @param {boolean} options.quoted Is `arg` being quoted. - * @returns {string} The escaped argument. + * @returns {Function} A function to escape arguments. */ -function escapeArgDash(arg, { interpolation, quoted }) { - let result = arg - .replace(/[\0\u0008\u001B\u009B]/gu, "") - .replace(/\r(?!\n)/gu, ""); - - if (interpolation) { - result = result - .replace(/\\/gu, "\\\\") - .replace(/\r?\n/gu, " ") - .replace(/(^|\s)([#~])/gu, "$1\\$2") - .replace(/(["$&'()*;<>?`|])/gu, "\\$1") - .replace(/([\t\n ])/gu, "\\$1"); - } else if (quoted) { - result = result.replace(/'/gu, `'\\''`); - } - - return result; +function getEscapeFunction$9() { + return escapeArg$7; } /** - * Escapes a shell argument for use in Zsh. + * Escape an argument for use in Bash when the argument is being quoted. * * @param {string} arg The argument to escape. - * @param {object} options The escape options. - * @param {boolean} options.interpolation Is interpolation enabled. - * @param {boolean} options.quoted Is `arg` being quoted. * @returns {string} The escaped argument. */ -function escapeArgZsh(arg, { interpolation, quoted }) { - let result = arg +function escapeArgForQuoted$5(arg) { + return arg .replace(/[\0\u0008\u001B\u009B]/gu, "") - .replace(/\r(?!\n)/gu, ""); - - if (interpolation) { - result = result - .replace(/\\/gu, "\\\\") - .replace(/\r?\n/gu, " ") - .replace(/(^|\s)([#=~])/gu, "$1\\$2") - .replace(/(["$&'()*;<>?[\]`{|}])/gu, "\\$1") - .replace(/([\t ])/gu, "\\$1"); - } else if (quoted) { - result = result.replace(/'/gu, `'\\''`); - } - - return result; + .replace(/\r(?!\n)/gu, "") + .replace(/'/gu, "'\\''"); } /** - * Quotes an argument for use in a Unix shell. + * Quotes an argument for use in Bash. * * @param {string} arg The argument to quote. * @returns {string} The quoted argument. */ -function quoteArg$1(arg) { +function quoteArg$5(arg) { return `'${arg}'`; } /** - * Returns the basename of a directory or file path on a Unix system. + * Returns a pair of functions to escape and quote arguments for use in Bash. * - * @param {string} fullPath A Unix-style directory or file path. - * @returns {string} The basename of `fullPath`. + * @returns {Function[]} A function pair to escape & quote arguments. */ -function getBasename$1(fullPath) { - return path__namespace.basename(fullPath); +function getQuoteFunction$9() { + return [escapeArgForQuoted$5, quoteArg$5]; } /** - * Returns the default shell for Unix systems. + * Remove any prefix from the provided argument that might be interpreted as a + * flag on Unix systems for Bash. * - * For more information, see `options.shell` in: - * https://nodejs.org/api/child_process.html#child_processexeccommand-options-callback. - * - * @returns {string} The default shell. + * @param {string} arg The argument to update. + * @returns {string} The updated argument. */ -function getDefaultShell$1() { - return "/bin/sh"; +function stripFlagPrefix$7(arg) { + return arg.replace(/^-+/gu, ""); } /** - * Returns a function to escape arguments for use in a particular shell. + * Returns a function to protect against flag injection for Bash. * - * @param {string} shellName The name of a Unix shell. - * @returns {Function?} A function to escape arguments for use in the shell. + * @returns {Function} A function to protect against flag injection. */ -function getEscapeFunction$1(shellName) { - switch (shellName) { - case binBash: - return escapeArgBash; - case binCsh: - return escapeArgCsh; - case binDash: - return escapeArgDash; - case binZsh: - return escapeArgZsh; - default: - return null; - } +function getFlagProtectionFunction$9() { + return stripFlagPrefix$7; } /** - * Returns a function to quote arguments for use in a particular shell. - * - * @param {string} shellName The name of a Unix shell. - * @returns {Function?} A function to quote arguments for use in the shell. + * @overview Provides functionality for the C shell (csh). + * @license MPL-2.0 + */ + + +/** + * Escape an argument for use in csh. + * + * @param {string} arg The argument to escape. + * @returns {string} The escaped argument. + */ +function escapeArg$6(arg) { + const textEncoder = new node_util.TextEncoder(); + return arg + .replace(/[\0\u0008\r\u001B\u009B]/gu, "") + .replace(/\n/gu, " ") + .replace(/\\/gu, "\\\\") + .replace(/(?<=^|\s)(~)/gu, "\\$1") + .replace(/!(?!$)/gu, "\\!") + .replace(/(["#$&'()*;<>?[`{|])/gu, "\\$1") + .replace(/([\t ])/gu, "\\$1") + .split("") + .map( + // Due to a bug in C shell version 20110502-7, when a character whose + // utf-8 encoding includes the bytes 0xA0 (160 in decimal) appears in + // an argument after an escaped character, it will hang and endlessly + // consume memory unless the character is escaped with quotes. + // ref: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=995013 + (char) => (textEncoder.encode(char).includes(160) ? `'${char}'` : char), + ) + .join(""); +} + +/** + * Returns a function to escape arguments for use in csh for the given use case. + * + * @returns {Function} A function to escape arguments. + */ +function getEscapeFunction$8() { + return escapeArg$6; +} + +/** + * Escape an argument for use in csh when the argument is being quoted. + * + * @param {string} arg The argument to escape. + * @returns {string} The escaped argument. + */ +function escapeArgForQuoted$4(arg) { + return arg + .replace(/[\0\u0008\r\u001B\u009B]/gu, "") + .replace(/\n/gu, " ") + .replace(/'/gu, "'\\''") + .replace(/\\!$/gu, "\\\\!") + .replace(/!(?!$)/gu, "\\!"); +} + +/** + * Quotes an argument for use in csh. + * + * @param {string} arg The argument to quote. + * @returns {string} The quoted argument. + */ +function quoteArg$4(arg) { + return `'${arg}'`; +} + +/** + * Returns a pair of functions to escape and quote arguments for use in csh. + * + * @returns {Function[]} A function pair to escape & quote arguments. + */ +function getQuoteFunction$8() { + return [escapeArgForQuoted$4, quoteArg$4]; +} + +/** + * Remove any prefix from the provided argument that might be interpreted as a + * flag on Unix systems for csh. + * + * @param {string} arg The argument to update. + * @returns {string} The updated argument. + */ +function stripFlagPrefix$6(arg) { + return arg.replace(/^-+/gu, ""); +} + +/** + * Returns a function to protect against flag injection for csh. + * + * @returns {Function} A function to protect against flag injection. + */ +function getFlagProtectionFunction$8() { + return stripFlagPrefix$6; +} + +/** + * @overview Provides functionality for the Debian Almquist shell (Dash). + * @license MPL-2.0 + */ + +/** + * Escape an argument for use in Dash. + * + * @param {string} arg The argument to escape. + * @returns {string} The escaped argument. + */ +function escapeArg$5(arg) { + return arg + .replace(/[\0\u0008\r\u001B\u009B]/gu, "") + .replace(/\n/gu, " ") + .replace(/\\/gu, "\\\\") + .replace(/(?<=^|\s)([#~])/gu, "\\$1") + .replace(/(["$&'()*;<>?`|])/gu, "\\$1") + .replace(/([\t ])/gu, "\\$1"); +} + +/** + * Returns a function to escape arguments for use in Dash for the given use + * case. + * + * @returns {Function} A function to escape arguments. */ -function getQuoteFunction$1(shellName) { +function getEscapeFunction$7() { + return escapeArg$5; +} + +/** + * Escape an argument for use in Dash when the argument is being quoted. + * + * @param {string} arg The argument to escape. + * @returns {string} The escaped argument. + */ +function escapeArgForQuoted$3(arg) { + return arg + .replace(/[\0\u0008\u001B\u009B]/gu, "") + .replace(/\r(?!\n)/gu, "") + .replace(/'/gu, "'\\''"); +} + +/** + * Quotes an argument for use in Dash. + * + * @param {string} arg The argument to quote. + * @returns {string} The quoted argument. + */ +function quoteArg$3(arg) { + return `'${arg}'`; +} + +/** + * Returns a pair of functions to escape and quote arguments for use in Dash. + * + * @returns {Function[]} A function pair to escape & quote arguments. + */ +function getQuoteFunction$7() { + return [escapeArgForQuoted$3, quoteArg$3]; +} + +/** + * Remove any prefix from the provided argument that might be interpreted as a + * flag on Unix systems for Dash. + * + * @param {string} arg The argument to update. + * @returns {string} The updated argument. + */ +function stripFlagPrefix$5(arg) { + return arg.replace(/^-+/gu, ""); +} + +/** + * Returns a function to protect against flag injection for Dash. + * + * @returns {Function} A function to protect against flag injection. + */ +function getFlagProtectionFunction$7() { + return stripFlagPrefix$5; +} + +/** + * @overview Provides functionality for shell-less escaping on Unix systems. + * @license MPL-2.0 + */ + +/** + * The error message for use of quoting functionality. + * + * @constant + * @type {string} + */ +const unsupportedError$1 = "Quoting is not supported when no shell is used"; + +/** + * Escape an argument for shell-less use. + * + * @param {string} arg The argument to escape. + * @returns {string} The escaped argument. + */ +function escapeArg$4(arg) { + return arg.replace(/[\0\u0008\u001B\u009B]/gu, "").replace(/\r(?!\n)/gu, ""); +} + +/** + * Returns a function to escape arguments for shell-less use. + * + * @returns {Function} A function to escape arguments. + */ +function getEscapeFunction$6() { + return escapeArg$4; +} + +/** + * Returns the provided value. + * + * @throws {Error} Always. + */ +function unsupported$1() { + throw new Error(unsupportedError$1); +} + +/** + * Returns a pair of functions that will indicate this operation is unsupported. + * + * @returns {Function[]} A pair of functions. + */ +function getQuoteFunction$6() { + return [unsupported$1, unsupported$1]; +} + +/** + * Remove any prefix from the provided argument that might be interpreted as a + * flag on Unix systems. + * + * @param {string} arg The argument to update. + * @returns {string} The updated argument. + */ +function stripFlagPrefix$4(arg) { + return arg.replace(/^-+/gu, ""); +} + +/** + * Returns a function to protect against flag injection for Unix systems. + * + * @returns {Function} A function to protect against flag injection. + */ +function getFlagProtectionFunction$6() { + return stripFlagPrefix$4; +} + +/** + * @overview Provides functionality for the Z shell (Zsh). + * @license MPL-2.0 + */ + +/** + * Escape an argument for use in Zsh. + * + * @param {string} arg The argument to escape. + * @returns {string} The escaped argument. + */ +function escapeArg$3(arg) { + return arg + .replace(/[\0\u0008\r\u001B\u009B]/gu, "") + .replace(/\n/gu, " ") + .replace(/\\/gu, "\\\\") + .replace(/(?<=^|\s)([#=~])/gu, "\\$1") + .replace(/(["$&'()*;<>?[\]`{|}])/gu, "\\$1") + .replace(/([\t ])/gu, "\\$1"); +} + +/** + * Returns a function to escape arguments for use in Zsh for the given use case. + * + * @returns {Function} A function to escape arguments. + */ +function getEscapeFunction$5() { + return escapeArg$3; +} + +/** + * Escape an argument for use in Zsh when the argument is being quoted. + * + * @param {string} arg The argument to escape. + * @returns {string} The escaped argument. + */ +function escapeArgForQuoted$2(arg) { + return arg + .replace(/[\0\u0008\u001B\u009B]/gu, "") + .replace(/\r(?!\n)/gu, "") + .replace(/'/gu, "'\\''"); +} + +/** + * Quotes an argument for use in Zsh. + * + * @param {string} arg The argument to quote. + * @returns {string} The quoted argument. + */ +function quoteArg$2(arg) { + return `'${arg}'`; +} + +/** + * Returns a pair of functions to escape and quote arguments for use in Zsh. + * + * @returns {Function[]} A function pair to escape & quote arguments. + */ +function getQuoteFunction$5() { + return [escapeArgForQuoted$2, quoteArg$2]; +} + +/** + * Remove any prefix from the provided argument that might be interpreted as a + * flag on Unix systems for Zsh. + * + * @param {string} arg The argument to update. + * @returns {string} The updated argument. + */ +function stripFlagPrefix$3(arg) { + return arg.replace(/^-+/gu, ""); +} + +/** + * Returns a function to protect against flag injection for Zsh. + * + * @returns {Function} A function to protect against flag injection. + */ +function getFlagProtectionFunction$5() { + return stripFlagPrefix$3; +} + +/** + * @overview Provides functionality for Unix systems. + * @license MPL-2.0 + */ + + +/** + * The name of the Bourne-again shell (Bash) binary. + * + * @constant + * @type {string} + */ +const binBash = "bash"; + +/** + * The name of the C shell (csh) binary. + * + * @constant + * @type {string} + */ +const binCsh = "csh"; + +/** + * The name of the Debian Almquist shell (Dash) binary. + * + * @constant + * @type {string} + */ +const binDash = "dash"; + +/** + * The name of the Z shell (Zsh) binary. + * + * @constant + * @type {string} + */ +const binZsh = "zsh"; + +/** + * Returns the default shell for Unix systems. + * + * For more information, see `options.shell` in: + * https://nodejs.org/api/child_process.html#child_processexeccommand-options-callback. + * + * @returns {string} The default shell. + */ +function getDefaultShell$1() { + return "/bin/sh"; +} + +/** + * Returns a function to escape arguments for use in a particular shell. + * + * @param {string | symbol} shellName The name of a Unix shell. + * @returns {Function | undefined} A function to escape arguments. + */ +function getEscapeFunction$4(shellName) { switch (shellName) { + case noShell: + return getEscapeFunction$6(); case binBash: + return getEscapeFunction$9(); case binCsh: + return getEscapeFunction$8(); case binDash: + return getEscapeFunction$7(); case binZsh: - return quoteArg$1; - default: - return null; + return getEscapeFunction$5(); + } +} + +/** + * Returns a pair of functions to escape and quote arguments for use in a + * particular shell. + * + * @param {string | symbol} shellName The name of a Unix shell. + * @returns {Function[] | undefined} A function pair to escape & quote arguments. + */ +function getQuoteFunction$4(shellName) { + switch (shellName) { + case noShell: + return getQuoteFunction$6(); + case binBash: + return getQuoteFunction$9(); + case binCsh: + return getQuoteFunction$8(); + case binDash: + return getQuoteFunction$7(); + case binZsh: + return getQuoteFunction$5(); + } +} + +/** + * Returns a function to protect against flag injection. + * + * @param {string | symbol} shellName The name of a Unix shell. + * @returns {Function | undefined} A function to protect against flag injection. + */ +function getFlagProtectionFunction$4(shellName) { + switch (shellName) { + case noShell: + return getFlagProtectionFunction$6(); + case binBash: + return getFlagProtectionFunction$9(); + case binCsh: + return getFlagProtectionFunction$8(); + case binDash: + return getFlagProtectionFunction$7(); + case binZsh: + return getFlagProtectionFunction$5(); } } @@ -10074,127 +10366,335 @@ function getQuoteFunction$1(shellName) { * Determines the name of the shell identified by a file path or file name. * * @param {object} args The arguments for this function. + * @param {Object} args.env The environment variables. * @param {string} args.shell The name or path of the shell. * @param {object} deps The dependencies for this function. * @param {Function} deps.resolveExecutable Resolve the path to an executable. * @returns {string} The shell name. */ -function getShellName$1({ shell }, { resolveExecutable }) { +function getShellName$1({ env, shell }, { resolveExecutable }) { shell = resolveExecutable( - { executable: shell }, - { exists: fs__namespace.existsSync, readlink: fs__namespace.readlinkSync, which: which.sync } + { env, executable: shell }, + { exists: fs__namespace.existsSync, readlink: fs__namespace.readlinkSync, which: which.sync }, ); - const shellName = getBasename$1(shell); - if (getEscapeFunction$1(shellName) === null) { - return binBash; - } - + const shellName = path__namespace.basename(shell); return shellName; } +/** + * Checks if the given shell is supported on Unix or not. + * + * @param {string} shellName The name of a Unix shell. + * @returns {boolean} `true` if the shell is supported, `false` otherwise. + */ +function isShellSupported$1(shellName) { + return getEscapeFunction$4(shellName) !== undefined; +} + var unix = /*#__PURE__*/Object.freeze({ __proto__: null, getDefaultShell: getDefaultShell$1, - getEscapeFunction: getEscapeFunction$1, - getQuoteFunction: getQuoteFunction$1, - getShellName: getShellName$1 + getEscapeFunction: getEscapeFunction$4, + getFlagProtectionFunction: getFlagProtectionFunction$4, + getQuoteFunction: getQuoteFunction$4, + getShellName: getShellName$1, + isShellSupported: isShellSupported$1 }); /** - * @overview Provides functionality specifically for Windows systems. + * @overview Provides functionality for the Windows Command Prompt. * @license MPL-2.0 */ /** - * The name of the Windows Command Prompt binary. + * Escape an argument for use in CMD. * - * @constant - * @type {string} + * @param {string} arg The argument to escape. + * @returns {string} The escaped argument. */ -const binCmd = "cmd.exe"; +function escapeArg$2(arg) { + let shouldEscapeSpecialChar = true; + return arg + .replace(/[\0\u0008\r\u001B\u009B]/gu, "") + .replace(/\n/gu, " ") + .replace(/(? { + if (char === '"') { + shouldEscapeSpecialChar = !shouldEscapeSpecialChar; + } else if (shouldEscapeSpecialChar && /[%&<>^|]/u.test(char)) { + return `^${char}`; + } + + return char; + }, + ) + .join(""); +} /** - * The name of the Windows PowerShell binary. + * Returns a function to escape arguments for use in CMD for the given use case. + * + * @returns {Function} A function to escape arguments. + */ +function getEscapeFunction$3() { + return escapeArg$2; +} + +/** + * Escape an argument for use in CMD when the argument is being quoted. + * + * @param {string} arg The argument to escape. + * @returns {string} The escaped argument. + */ +function escapeArgForQuoted$1(arg) { + return escapeArg$2(arg).replace(/(?|])/gu, "^$1"); - } else if (quoted) { - result = result.replace(/"/gu, `""`); +/** + * Returns a pair of functions that will indicate this operation is unsupported. + * + * @returns {Function[]} A pair of functions. + */ +function getQuoteFunction$2() { + return [unsupported, unsupported]; +} + +/** + * Remove any prefix from the provided argument that might be interpreted as a + * flag on Windows systems. + * + * @param {string} arg The argument to update. + * @returns {string} The updated argument. + */ +function stripFlagPrefix$1(arg) { + return arg.replace(/^(?:-+|\/+)/gu, ""); +} + +/** + * Returns a function to protect against flag injection for Windows systems. + * + * @returns {Function} A function to protect against flag injection. + */ +function getFlagProtectionFunction$2() { + return stripFlagPrefix$1; +} + +/** + * @overview Provides functionality for Windows PowerShell. + * @license MPL-2.0 + */ + +/** + * Escape an argument for use in PowerShell. + * + * @param {string} arg The argument to escape. + * @returns {string} The escaped argument. + */ +function escapeArg(arg) { + arg = arg + .replace(/[\0\u0008\r\u001B\u009B]/gu, "") + .replace(/\n/gu, " ") + .replace(/`/gu, "``") + .replace(/(?<=^|[\s\u0085])([*1-6]?)(>)/gu, "$1`$2") + .replace(/(?<=^|[\s\u0085])([#\-:<@\]])/gu, "`$1") + .replace(/([$&'(),;{|}‘’‚‛“”„])/gu, "`$1"); + + if (/[\s\u0085]/u.test(arg.replace(/^[\s\u0085]+/gu, ""))) { + arg = arg + .replace(/(?)/gu, "$1$2`$3") - .replace(/(^|[\s\u0085])([#\-:<@\]])/gu, "$1`$2") - .replace(/(["&'(),;{|}‘’‚‛“”„])/gu, "`$1") - .replace(/([\s\u0085])/gu, "`$1"); - } else if (quoted) { - result = result.replace(/(["“”„])/gu, "$1$1"); + if (/[\s\u0085]/u.test(arg)) { + arg = arg + .replace(/(?} args.env The environment variables. * @returns {string} The default shell. */ -function getDefaultShell({ env: { ComSpec } }) { +function getDefaultShell({ env }) { + const ComSpec = hasOwn(env, "ComSpec") ? env.ComSpec : undefined; if (ComSpec !== undefined) { return ComSpec; } @@ -10217,33 +10717,58 @@ function getDefaultShell({ env: { ComSpec } }) { /** * Returns a function to escape arguments for use in a particular shell. * - * @param {string} shellName The name of a Windows shell. - * @returns {Function?} A function to escape arguments for use in the shell. + * @param {string | symbol} shellName The name of a Windows shell. + * @returns {Function | undefined} A function to escape arguments. */ function getEscapeFunction(shellName) { - switch (shellName) { + if (shellName === noShell) { + return getEscapeFunction$2(); + } + + switch (shellName.toLowerCase()) { case binCmd: - return escapeArgCmd; + return getEscapeFunction$3(); case binPowerShell: - return escapeArgPowerShell; - default: - return null; + return getEscapeFunction$1(); } } /** - * Returns a function to quote arguments for use in a particular shell. + * Returns a pair of functions to escape and quote arguments for use in a + * particular shell. * - * @param {string} shellName The name of a Windows shell. - * @returns {Function?} A function to quote arguments for use in the shell. + * @param {string | symbol} shellName The name of a Windows shell. + * @returns {Function[] | undefined} A function pair to escape & quote arguments. */ function getQuoteFunction(shellName) { - switch (shellName) { + if (shellName === noShell) { + return getQuoteFunction$2(); + } + + switch (shellName.toLowerCase()) { case binCmd: + return getQuoteFunction$3(); case binPowerShell: - return quoteArg; - default: - return null; + return getQuoteFunction$1(); + } +} + +/** + * Returns a function to protect against flag injection. + * + * @param {string | symbol} shellName The name of a Windows shell. + * @returns {Function | undefined} A function to protect against flag injection. + */ +function getFlagProtectionFunction(shellName) { + if (shellName === noShell) { + return getFlagProtectionFunction$2(); + } + + switch (shellName.toLowerCase()) { + case binCmd: + return getFlagProtectionFunction$3(); + case binPowerShell: + return getFlagProtectionFunction$1(); } } @@ -10251,31 +10776,40 @@ function getQuoteFunction(shellName) { * Determines the name of the shell identified by a file path or file name. * * @param {object} args The arguments for this function. + * @param {Object} args.env The environment variables. * @param {string} args.shell The name or path of the shell. * @param {object} deps The dependencies for this function. * @param {Function} deps.resolveExecutable Resolve the path to an executable. * @returns {string} The shell name. */ -function getShellName({ shell }, { resolveExecutable }) { +function getShellName({ env, shell }, { resolveExecutable }) { shell = resolveExecutable( - { executable: shell }, - { exists: fs__namespace.existsSync, readlink: fs__namespace.readlinkSync, which: which.sync } + { env, executable: shell }, + { exists: fs__namespace.existsSync, readlink: fs__namespace.readlinkSync, which: which.sync }, ); - const shellName = getBasename(shell); - if (getEscapeFunction(shellName) === null) { - return binCmd; - } - + const shellName = path__namespace.win32.basename(shell); return shellName; } +/** + * Checks if the given shell is supported on Windows or not. + * + * @param {string} shellName The name of a Windows shell. + * @returns {boolean} `true` if the shell is supported, `false` otherwise. + */ +function isShellSupported(shellName) { + return getEscapeFunction(shellName) !== undefined; +} + var win = /*#__PURE__*/Object.freeze({ __proto__: null, getDefaultShell: getDefaultShell, getEscapeFunction: getEscapeFunction, + getFlagProtectionFunction: getFlagProtectionFunction, getQuoteFunction: getQuoteFunction, - getShellName: getShellName + getShellName: getShellName, + isShellSupported: isShellSupported }); /** @@ -10284,6 +10818,7 @@ var win = /*#__PURE__*/Object.freeze({ * @license MPL-2.0 */ + /** * The string identifying the OS type Cygwin. * @@ -10317,7 +10852,8 @@ const win32 = "win32"; * @returns {boolean} `true` if the system is Windows, `false` otherwise. */ function isWindow({ env, platform }) { - return env.OSTYPE === cygwin || env.OSTYPE === msys || platform === win32; + const osType = hasOwn(env, "OSTYPE") ? env.OSTYPE : undefined; + return osType === cygwin || osType === msys || platform === win32; } /** @@ -10342,154 +10878,155 @@ function getHelpersByPlatform({ env, platform }) { * * @overview Entrypoint for the library. * @module shescape - * @version 1.6.4 + * @version 2.1.0 * @license MPL-2.0 */ -/** - * Get the helper functions for the current platform. - * - * @returns {object} The helper functions for the current platform. - */ -function getPlatformHelpers() { - const platform = os.platform(); - const helpers = getHelpersByPlatform({ env: process.env, platform }); - return helpers; -} - -/** - * Converts the provided value into an array if it is not already an array and - * returns the array. - * - * @param {Array | any} x The value to convert to an array if necessary. - * @returns {Array} An array containing `x` or `x` itself. - */ -function toArrayIfNecessary(x) { - return Array.isArray(x) ? x : [x]; -} /** - * Take a single value, the argument, and escape any dangerous characters. - * - * Non-string inputs will be converted to strings using a `toString()` method. - * - * NOTE: when the `interpolation` option is set to `true`, whitespace is escaped - * to prevent argument splitting except for cmd.exe (which does not support it). + * A class to escape user-controlled inputs to shell commands to prevent shell + * injection. * * @example * import { spawn } from "node:child_process"; + * const shescape = new Shescape({ shell: false }); * spawn( * "echo", * ["Hello", shescape.escape(userInput)], * null // `options.shell` MUST be falsy * ); - * @param {string} arg The argument to escape. - * @param {object} [options] The escape options. - * @param {boolean} [options.interpolation=false] Is interpolation enabled. - * @param {boolean | string} [options.shell] The shell to escape for. - * @returns {string} The escaped argument. - * @throws {TypeError} The argument is not stringable. - * @since 0.1.0 - */ -function escape(arg, options = {}) { - const helpers = getPlatformHelpers(); - return escapeShellArg({ arg, options, process }, helpers); -} - -/** - * Take a array of values, the arguments, and escape any dangerous characters in - * every argument. - * - * Non-array inputs will be converted to one-value arrays and non-string values - * will be converted to strings using a `toString()` method. - * * @example * import { spawn } from "node:child_process"; + * const shescape = new Shescape({ shell: false }); * spawn( * "echo", * shescape.escapeAll(["Hello", userInput]), * null // `options.shell` MUST be falsy * ); - * @param {string[]} args The arguments to escape. - * @param {object} [options] The escape options. - * @param {boolean} [options.interpolation=false] Is interpolation enabled. - * @param {boolean | string} [options.shell] The shell to escape for. - * @returns {string[]} The escaped arguments. - * @throws {TypeError} One of the arguments is not stringable. - * @since 1.1.0 - */ -function escapeAll(args, options = {}) { - args = toArrayIfNecessary(args); - return args.map((arg) => escape(arg, options)); -} - -/** - * Take a single value, the argument, put OS-specific quotes around it and - * escape any dangerous characters. - * - * Non-string inputs will be converted to strings using a `toString()` method. - * * @example * import { spawn } from "node:child_process"; * const spawnOptions = { shell: true }; // `options.shell` SHOULD be truthy - * const shescapeOptions = { ...spawnOptions }; + * const shescape = new Shescape({ shell: spawnOptions.shell }); * spawn( * "echo", - * ["Hello", shescape.quote(userInput, shescapeOptions)], + * ["Hello", shescape.quote(userInput)], * spawnOptions * ); * @example - * import { exec } from "node:child_process"; - * const execOptions = null || { }; - * const shescapeOptions = { ...execOptions }; - * exec( - * `echo Hello ${shescape.quote(userInput, shescapeOptions)}`, - * execOptions - * ); - * @param {string} arg The argument to quote and escape. - * @param {object} [options] The escape and quote options. - * @param {boolean | string} [options.shell] The shell to escape for. - * @returns {string} The quoted and escaped argument. - * @throws {TypeError} The argument is not stringable. - * @since 0.3.0 - */ -function quote(arg, options = {}) { - const helpers = getPlatformHelpers(); - return quoteShellArg({ arg, options, process }, helpers); -} - -/** - * Take an array of values, the arguments, put OS-specific quotes around every - * argument and escape any dangerous characters in every argument. - * - * Non-array inputs will be converted to one-value arrays and non-string values - * will be converted to strings using a `toString()` method. - * - * @example * import { spawn } from "node:child_process"; * const spawnOptions = { shell: true }; // `options.shell` SHOULD be truthy - * const shescapeOptions = { ...spawnOptions }; + * const shescape = new Shescape({ shell: spawnOptions.shell }); * spawn( * "echo", - * shescape.quoteAll(["Hello", userInput], shescapeOptions), + * shescape.quoteAll(["Hello", userInput]), * spawnOptions * ); - * @param {string[]} args The arguments to quote and escape. - * @param {object} [options] The escape and quote options. - * @param {boolean | string} [options.shell] The shell to escape for. - * @returns {string[]} The quoted and escaped arguments. - * @throws {TypeError} One of the arguments is not stringable. - * @since 0.4.0 */ -function quoteAll(args, options = {}) { - args = toArrayIfNecessary(args); - return args.map((arg) => quote(arg, options)); -} +class Shescape { + /** + * Create a new {@link Shescape} instance. + * + * @param {object} [options] The escape options. + * @param {boolean} [options.flagProtection=true] Is flag protection enabled. + * @param {boolean | string} [options.shell=true] The shell to escape for. + * @throws {Error} The shell is not supported or could not be found. + * @since 2.0.0 + */ + constructor(options = {}) { + const platform = os.platform(); + const helpers = getHelpersByPlatform({ env: process.env, platform }); + + options = parseOptions({ env: process.env, options }, helpers); + const { flagProtection, shellName } = options; + + { + const escape = helpers.getEscapeFunction(shellName); + if (flagProtection) { + const flagProtect = helpers.getFlagProtectionFunction(shellName); + this._escape = (arg) => flagProtect(escape(arg)); + } else { + this._escape = escape; + } + } + + { + const [escape, quote] = helpers.getQuoteFunction(shellName); + if (flagProtection) { + const flagProtect = helpers.getFlagProtectionFunction(shellName); + this._quote = (arg) => quote(flagProtect(escape(arg))); + } else { + this._quote = (arg) => quote(escape(arg)); + } + } + } -exports.escape = escape; -exports.escapeAll = escapeAll; -exports.quote = quote; -exports.quoteAll = quoteAll; + /** + * Take a single value, the argument, and escape any dangerous characters. + * + * Non-string inputs will be converted to strings using a `toString()` method. + * + * @param {string} arg The argument to escape. + * @returns {string} The escaped argument. + * @throws {TypeError} The argument is not stringable. + * @since 2.0.0 + */ + escape(arg) { + const argAsString = checkedToString(arg); + return this._escape(argAsString); + } + + /** + * Take an array of values, the arguments, and escape any dangerous characters + * in every argument. + * + * Non-string inputs will be converted to strings using a `toString()` method. + * + * @param {string[]} args The arguments to escape. + * @returns {string[]} The escaped arguments. + * @throws {TypeError} The arguments are not an array. + * @throws {TypeError} One of the arguments is not stringable. + * @since 2.0.0 + */ + escapeAll(args) { + return args.map((arg) => this.escape(arg)); + } + + /** + * Take a single value, the argument, put shell-specific quotes around it and + * escape any dangerous characters. + * + * Non-string inputs will be converted to strings using a `toString()` method. + * + * @param {string} arg The argument to quote and escape. + * @returns {string} The quoted and escaped argument. + * @throws {TypeError} The argument is not stringable. + * @throws {Error} Quoting is not supported with `shell: false`. + * @since 2.0.0 + */ + quote(arg) { + const argAsString = checkedToString(arg); + return this._quote(argAsString); + } + + /** + * Take an array of values, the arguments, put shell-specific quotes around + * every argument and escape any dangerous characters in every argument. + * + * Non-string inputs will be converted to strings using a `toString()` method. + * + * @param {string[]} args The arguments to quote and escape. + * @returns {string[]} The quoted and escaped arguments. + * @throws {TypeError} The arguments are not an array. + * @throws {TypeError} One of the arguments is not stringable. + * @throws {Error} Quoting is not supported with `shell: false`. + * @since 2.0.0 + */ + quoteAll(args) { + return args.map((arg) => this.quote(arg)); + } +} + +exports.Shescape = Shescape; /***/ }), @@ -10498,7 +11035,7 @@ exports.quoteAll = quoteAll; /***/ ((module) => { "use strict"; -module.exports = JSON.parse('{"name":"lint-action","version":"2.3.0","description":"GitHub Action for detecting and fixing linting errors","repository":"github:wearerequired/lint-action","license":"MIT","private":true,"main":"./dist/index.js","scripts":{"test":"jest","lint":"eslint --max-warnings 0 \\"**/*.js\\"","lint:fix":"yarn lint --fix","format":"prettier --list-different \\"**/*.{css,html,js,json,jsx,less,md,scss,ts,tsx,vue,yaml,yml}\\"","format:fix":"yarn format --write","build":"ncc build ./src/index.js"},"dependencies":{"@actions/core":"^1.10.0","command-exists":"^1.2.9","glob":"^8.1.0","parse-diff":"^0.11.0","shescape":"^1.6.4"},"peerDependencies":{},"devDependencies":{"@samuelmeuli/eslint-config":"^6.0.0","@samuelmeuli/prettier-config":"^2.0.1","@vercel/ncc":"^0.36.0","eslint":"8.32.0","eslint-config-airbnb-base":"15.0.0","eslint-config-prettier":"^8.6.0","eslint-plugin-import":"^2.26.0","eslint-plugin-jsdoc":"^39.6.7","fs-extra":"^11.1.0","jest":"^29.3.1","prettier":"^2.8.3"},"eslintConfig":{"root":true,"extends":["@samuelmeuli/eslint-config","plugin:jsdoc/recommended"],"env":{"node":true,"jest":true},"settings":{"jsdoc":{"mode":"typescript"}},"rules":{"no-await-in-loop":"off","no-unused-vars":["error",{"args":"none","varsIgnorePattern":"^_"}],"jsdoc/check-indentation":"error","jsdoc/check-syntax":"error","jsdoc/newline-after-description":["error","never"],"jsdoc/require-description":"error","jsdoc/require-hyphen-before-param-description":"error","jsdoc/require-jsdoc":"off"}},"eslintIgnore":["node_modules/","test/linters/projects/","test/tmp/","dist/"],"jest":{"setupFiles":["./test/mock-actions-core.js"]},"prettier":"@samuelmeuli/prettier-config"}'); +module.exports = JSON.parse('{"name":"lint-action","version":"2.3.0","description":"GitHub Action for detecting and fixing linting errors","repository":"github:wearerequired/lint-action","license":"MIT","private":true,"main":"./dist/index.js","scripts":{"test":"jest","lint":"eslint --max-warnings 0 \\"**/*.js\\"","lint:fix":"yarn lint --fix","format":"prettier --list-different \\"**/*.{css,html,js,json,jsx,less,md,scss,ts,tsx,vue,yaml,yml}\\"","format:fix":"yarn format --write","build":"ncc build ./src/index.js"},"dependencies":{"@actions/core":"^1.10.0","command-exists":"^1.2.9","glob":"^8.1.0","parse-diff":"^0.11.0","shescape":"^2.1.0"},"peerDependencies":{},"devDependencies":{"@samuelmeuli/eslint-config":"^6.0.0","@samuelmeuli/prettier-config":"^2.0.1","@vercel/ncc":"^0.38.1","eslint":"8.32.0","eslint-config-airbnb-base":"15.0.0","eslint-config-prettier":"^8.6.0","eslint-plugin-import":"^2.26.0","eslint-plugin-jsdoc":"^48.0.0","fs-extra":"^11.1.0","jest":"^29.3.1","prettier":"^2.8.3"},"eslintConfig":{"root":true,"extends":["@samuelmeuli/eslint-config","plugin:jsdoc/recommended"],"env":{"node":true,"jest":true},"settings":{"jsdoc":{"mode":"typescript"}},"rules":{"no-await-in-loop":"off","no-unused-vars":["error",{"args":"none","varsIgnorePattern":"^_"}],"jsdoc/check-indentation":"error","jsdoc/check-syntax":"error","jsdoc/tag-lines":"error","jsdoc/require-description":"error","jsdoc/require-hyphen-before-param-description":"error","jsdoc/require-jsdoc":"off"}},"eslintIgnore":["node_modules/","test/linters/projects/","test/tmp/","dist/"],"jest":{"setupFiles":["./test/mock-actions-core.js"]},"prettier":"@samuelmeuli/prettier-config"}'); /***/ }) diff --git a/package.json b/package.json index e595327a..227c742c 100644 --- a/package.json +++ b/package.json @@ -19,18 +19,18 @@ "command-exists": "^1.2.9", "glob": "^8.1.0", "parse-diff": "^0.11.0", - "shescape": "^1.6.4" + "shescape": "^2.1.0" }, "peerDependencies": {}, "devDependencies": { "@samuelmeuli/eslint-config": "^6.0.0", "@samuelmeuli/prettier-config": "^2.0.1", - "@vercel/ncc": "^0.36.0", + "@vercel/ncc": "^0.38.1", "eslint": "8.32.0", "eslint-config-airbnb-base": "15.0.0", "eslint-config-prettier": "^8.6.0", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsdoc": "^39.6.7", + "eslint-plugin-jsdoc": "^48.0.0", "fs-extra": "^11.1.0", "jest": "^29.3.1", "prettier": "^2.8.3" @@ -61,10 +61,7 @@ ], "jsdoc/check-indentation": "error", "jsdoc/check-syntax": "error", - "jsdoc/newline-after-description": [ - "error", - "never" - ], + "jsdoc/tag-lines": "error", "jsdoc/require-description": "error", "jsdoc/require-hyphen-before-param-description": "error", "jsdoc/require-jsdoc": "off" diff --git a/src/linters/clang-format.js b/src/linters/clang-format.js index 38fb50c1..79a3805a 100644 --- a/src/linters/clang-format.js +++ b/src/linters/clang-format.js @@ -1,10 +1,12 @@ const glob = require("glob"); -const { quoteAll } = require("shescape"); +const { Shescape } = require("shescape"); const { run } = require("../utils/action"); const commandExists = require("../utils/command-exists"); const { initLintResult } = require("../utils/lint-result"); +const { quoteAll } = new Shescape({ shell: false }); + /** @typedef {import('../utils/lint-result').LintResult} LintResult */ /** diff --git a/test/linters/params/clippy.js b/test/linters/params/clippy.js index 4d9ab20a..3193cb61 100644 --- a/test/linters/params/clippy.js +++ b/test/linters/params/clippy.js @@ -29,7 +29,7 @@ function getLintParams(dir) { )}","target":{"kind":["bin"],"crate_types":["bin"],"name":"lint","src_path":"${joinDoubleBackslash( dir, srcPath, - )}","edition":"2021","doc":true,"doctest":false,"test":true},"message":{"rendered":"warning: function \`sayHi\` should have a snake case name\\n --> ${file1}:1:8\\n |\\n1 | pub fn sayHi(name: &str) {\\n | ^^^^^ help: convert the identifier to snake case: \`say_hi\`\\n |\\n = note: \`#[warn(non_snake_case)]\` on by default\\n\\n","children":[{"children":[],"code":null,"level":"note","message":"\`#[warn(non_snake_case)]\` on by default","rendered":null,"spans":[]},{"children":[],"code":null,"level":"help","message":"convert the identifier to snake case","rendered":null,"spans":[{"byte_end":12,"byte_start":7,"column_end":13,"column_start":8,"expansion":null,"file_name":"${file1}","is_primary":true,"label":null,"line_end":1,"line_start":1,"suggested_replacement":"say_hi","suggestion_applicability":"MaybeIncorrect","text":[{"highlight_end":13,"highlight_start":8,"text":"pub fn sayHi(name: &str) {"}]}]}],"code":{"code":"non_snake_case","explanation":null},"level":"warning","message":"function \`sayHi\` should have a snake case name","spans":[{"byte_end":12,"byte_start":7,"column_end":13,"column_start":8,"expansion":null,"file_name":"${file1}","is_primary":true,"label":null,"line_end":1,"line_start":1,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":13,"highlight_start":8,"text":"pub fn sayHi(name: &str) {"}]}]}}\n`; + )}","edition":"2021","doc":true,"doctest":false,"test":true},"message":{"rendered":"warning: function \`sayHi\` should have a snake case name\\n --> ${file1}:1:8\\n |\\n1 | pub fn sayHi(name: &str) {\\n | ^^^^^ help: convert the identifier to snake case: \`say_hi\`\\n |\\n = note: \`#[warn(non_snake_case)]\` on by default\\n\\n","$message_type":"diagnostic","children":[{"children":[],"code":null,"level":"note","message":"\`#[warn(non_snake_case)]\` on by default","rendered":null,"spans":[]},{"children":[],"code":null,"level":"help","message":"convert the identifier to snake case","rendered":null,"spans":[{"byte_end":12,"byte_start":7,"column_end":13,"column_start":8,"expansion":null,"file_name":"${file1}","is_primary":true,"label":null,"line_end":1,"line_start":1,"suggested_replacement":"say_hi","suggestion_applicability":"MaybeIncorrect","text":[{"highlight_end":13,"highlight_start":8,"text":"pub fn sayHi(name: &str) {"}]}]}],"code":{"code":"non_snake_case","explanation":null},"level":"warning","message":"function \`sayHi\` should have a snake case name","spans":[{"byte_end":12,"byte_start":7,"column_end":13,"column_start":8,"expansion":null,"file_name":"${file1}","is_primary":true,"label":null,"line_end":1,"line_start":1,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":13,"highlight_start":8,"text":"pub fn sayHi(name: &str) {"}]}]}}\n`; const stderrPart2 = `{"reason":"compiler-message","package_id":"lint 0.0.1 (path+file://${pathFile})","manifest_path":"${joinDoubleBackslash( dir, "Cargo.toml", @@ -91,7 +91,7 @@ function getFixParams(dir) { )}","target":{"kind":["bin"],"crate_types":["bin"],"name":"lint","src_path":"${joinDoubleBackslash( dir, srcPath, - )}","edition":"2021","doc":true,"doctest":false,"test":true},"message":{"rendered":"warning: function \`sayHi\` should have a snake case name\\n --> ${file1}:1:8\\n |\\n1 | pub fn sayHi(name: &str) {\\n | ^^^^^ help: convert the identifier to snake case: \`say_hi\`\\n |\\n = note: \`#[warn(non_snake_case)]\` on by default\\n\\n","children":[{"children":[],"code":null,"level":"note","message":"\`#[warn(non_snake_case)]\` on by default","rendered":null,"spans":[]},{"children":[],"code":null,"level":"help","message":"convert the identifier to snake case","rendered":null,"spans":[{"byte_end":12,"byte_start":7,"column_end":13,"column_start":8,"expansion":null,"file_name":"${file1}","is_primary":true,"label":null,"line_end":1,"line_start":1,"suggested_replacement":"say_hi","suggestion_applicability":"MaybeIncorrect","text":[{"highlight_end":13,"highlight_start":8,"text":"pub fn sayHi(name: &str) {"}]}]}],"code":{"code":"non_snake_case","explanation":null},"level":"warning","message":"function \`sayHi\` should have a snake case name","spans":[{"byte_end":12,"byte_start":7,"column_end":13,"column_start":8,"expansion":null,"file_name":"${file1}","is_primary":true,"label":null,"line_end":1,"line_start":1,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":13,"highlight_start":8,"text":"pub fn sayHi(name: &str) {"}]}]}}\n`; + )}","edition":"2021","doc":true,"doctest":false,"test":true},"message":{"rendered":"warning: function \`sayHi\` should have a snake case name\\n --> ${file1}:1:8\\n |\\n1 | pub fn sayHi(name: &str) {\\n | ^^^^^ help: convert the identifier to snake case: \`say_hi\`\\n |\\n = note: \`#[warn(non_snake_case)]\` on by default\\n\\n","$message_type":"diagnostic","children":[{"children":[],"code":null,"level":"note","message":"\`#[warn(non_snake_case)]\` on by default","rendered":null,"spans":[]},{"children":[],"code":null,"level":"help","message":"convert the identifier to snake case","rendered":null,"spans":[{"byte_end":12,"byte_start":7,"column_end":13,"column_start":8,"expansion":null,"file_name":"${file1}","is_primary":true,"label":null,"line_end":1,"line_start":1,"suggested_replacement":"say_hi","suggestion_applicability":"MaybeIncorrect","text":[{"highlight_end":13,"highlight_start":8,"text":"pub fn sayHi(name: &str) {"}]}]}],"code":{"code":"non_snake_case","explanation":null},"level":"warning","message":"function \`sayHi\` should have a snake case name","spans":[{"byte_end":12,"byte_start":7,"column_end":13,"column_start":8,"expansion":null,"file_name":"${file1}","is_primary":true,"label":null,"line_end":1,"line_start":1,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":13,"highlight_start":8,"text":"pub fn sayHi(name: &str) {"}]}]}}\n`; const stderrPart2 = `{"reason":"compiler-message","package_id":"lint 0.0.1 (path+file://${pathFile})","manifest_path":"${joinDoubleBackslash( dir, "Cargo.toml", diff --git a/test/linters/params/swiftlint.js b/test/linters/params/swiftlint.js index 8f6be3c3..fa6c13f7 100644 --- a/test/linters/params/swiftlint.js +++ b/test/linters/params/swiftlint.js @@ -13,7 +13,7 @@ function getLintParams(dir) { const stdoutFile1 = `${join( dir, "file1.swift", - )}:5:1: warning: Vertical Whitespace Violation: Limit vertical whitespace to a single empty line. Currently 2. (vertical_whitespace)`; + )}:5:1: warning: Vertical Whitespace Violation: Limit vertical whitespace to a single empty line; currently 2 (vertical_whitespace)`; const stdoutFile2 = `${join( dir, "file2.swift", diff --git a/test/test-utils.js b/test/test-utils.js index 530d3b57..646450ce 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -1,7 +1,7 @@ const { mkdtempSync, realpathSync } = require("fs"); const { join } = require("path"); -const DATE_REGEX = /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d+ [+-]\d{4}/g; +const DATE_REGEX = /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d+ ?[+-]\d{2}:?\d{2}/g; const TEST_DATE = "2019-01-01 00:00:00.000000 +0000"; /** diff --git a/yarn.lock b/yarn.lock index 504fed50..63d31be2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -313,14 +313,14 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@es-joy/jsdoccomment@~0.36.1": - version "0.36.1" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz#c37db40da36e4b848da5fd427a74bae3b004a30f" - integrity sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg== +"@es-joy/jsdoccomment@~0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz#4a2f7db42209c0425c71a1476ef1bdb6dcd836f6" + integrity sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw== dependencies: - comment-parser "1.3.1" - esquery "^1.4.0" - jsdoc-type-pratt-parser "~3.1.0" + comment-parser "1.4.1" + esquery "^1.5.0" + jsdoc-type-pratt-parser "~4.0.0" "@eslint/eslintrc@^1.4.1": version "1.4.1" @@ -745,10 +745,10 @@ dependencies: "@types/yargs-parser" "*" -"@vercel/ncc@^0.36.0": - version "0.36.0" - resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.36.0.tgz#1f262b86fc4f0770bbc0fc1d331d5aaa1bd47334" - integrity sha512-/ZTUJ/ZkRt694k7KJNimgmHjtQcRuVwsST2Z6XfYveQIuBbHR+EqkTc1jfgPkQmMyk/vtpxo3nVxe8CNuau86A== +"@vercel/ncc@^0.38.1": + version "0.38.1" + resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.38.1.tgz#13f08738111e1d9e8a22fd6141f3590e54d9a60e" + integrity sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw== acorn-jsx@^5.3.2: version "5.3.2" @@ -809,6 +809,11 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" +are-docs-informative@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963" + integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -951,6 +956,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -1059,10 +1069,10 @@ command-exists@^1.2.9: resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== -comment-parser@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.1.tgz#3d7ea3adaf9345594aedee6563f422348f165c1b" - integrity sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA== +comment-parser@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.1.tgz#bdafead37961ac079be11eb7ec65c4d021eaf9cc" + integrity sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg== concat-map@0.0.1: version "0.0.1" @@ -1299,18 +1309,20 @@ eslint-plugin-import@^2.26.0: resolve "^1.22.0" tsconfig-paths "^3.14.1" -eslint-plugin-jsdoc@^39.6.7: - version "39.6.7" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.6.7.tgz#f7f9abec75993b9cb3ede814c1d9d5afc3d7bf22" - integrity sha512-0mrzXrHvL2ZLe3QK9X0OEDy7Fs2cFQ/f1d1G5KHEGD+13D1qg56Iovq0uOkYf5bJlHiKPytWVgOOO9y7kLW3VA== +eslint-plugin-jsdoc@^48.0.0: + version "48.0.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.0.4.tgz#ca54da8937b2f6fd242a16dbb1b9ff5050baf223" + integrity sha512-A0cH+5svWPXzGZszBjXA1t0aAqVGS+/x3i02KFmb73rU0iMLnadEcVWcD/dGBZHIfAMKr3YpWh58f6wn4N909w== dependencies: - "@es-joy/jsdoccomment" "~0.36.1" - comment-parser "1.3.1" + "@es-joy/jsdoccomment" "~0.41.0" + are-docs-informative "^0.0.2" + comment-parser "1.4.1" debug "^4.3.4" escape-string-regexp "^4.0.0" - esquery "^1.4.0" - semver "^7.3.8" - spdx-expression-parse "^3.0.1" + esquery "^1.5.0" + is-builtin-module "^3.2.1" + semver "^7.5.4" + spdx-expression-parse "^4.0.0" eslint-scope@^7.1.1: version "7.1.1" @@ -1403,6 +1415,13 @@ esquery@^1.4.0: dependencies: estraverse "^5.1.0" +esquery@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" @@ -1777,6 +1796,13 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -2314,10 +2340,10 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsdoc-type-pratt-parser@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz#a4a56bdc6e82e5865ffd9febc5b1a227ff28e67e" - integrity sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw== +jsdoc-type-pratt-parser@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz#136f0571a99c184d84ec84662c45c29ceff71114" + integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ== jsesc@^2.5.1: version "2.5.2" @@ -2795,13 +2821,20 @@ semver@^6.0.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.5, semver@^7.3.8: +semver@^7.3.5: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== dependencies: lru-cache "^6.0.0" +semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -2814,12 +2847,12 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shescape@^1.6.4: - version "1.6.4" - resolved "https://registry.yarnpkg.com/shescape/-/shescape-1.6.4.tgz#b29a1cc33657ee48d624d89a8f7c8b9da780fe1d" - integrity sha512-3nJDj7UyM2V6UTjVH2PR/FVVKytxlT1sN8FdGHsF0KlyAHxWatdwZAc7D6UMhYw8BRmAeow/t7X62ABAineUjg== +shescape@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/shescape/-/shescape-2.1.0.tgz#1b5c2f612418ed6184a720e27f06f0b11fb8300c" + integrity sha512-VFjtoo9Y25/fprhMo+bIb+OAM+mFUP0Veg57Bg973j2RSWEAoNPia5hBY+lYVIra5Nl/6CtlQYmDGWSvA3IWWw== dependencies: - which "^2.0.0" + which "^3.0.0" side-channel@^1.0.4: version "1.0.4" @@ -2863,10 +2896,10 @@ spdx-exceptions@^2.1.0: resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== -spdx-expression-parse@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== +spdx-expression-parse@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz#a23af9f3132115465dac215c099303e4ceac5794" + integrity sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" @@ -3106,13 +3139,20 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which@^2.0.0, which@^2.0.1: +which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" +which@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/which/-/which-3.0.1.tgz#89f1cd0c23f629a8105ffe69b8172791c87b4be1" + integrity sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg== + dependencies: + isexe "^2.0.0" + word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"