diff --git a/.github/codecov.yml b/.github/codecov.yml index d18bf9bb4..55bcb2dca 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -39,18 +39,21 @@ flags: paths: - src/ - index.js + - stateless.js - testing.js integration-Ubuntu: carryforward: true paths: - src/ - index.js + - stateless.js - testing.js integration-Windows: carryforward: true paths: - src/ - index.js + - stateless.js - testing.js unit: carryforward: true diff --git a/.gitignore b/.gitignore index 267021fd3..d012441ae 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ checksums.txt crash-* index.cjs index.d.cts +stateless.cjs +stateless.d.cts testing.cjs testing.d.cts diff --git a/.npmignore b/.npmignore index 93251b098..4cc8b9fb6 100644 --- a/.npmignore +++ b/.npmignore @@ -13,6 +13,10 @@ !LICENSE !README.md !SECURITY.md +!stateless.cjs +!stateless.d.cts +!stateless.d.ts +!stateless.js !testing.cjs !testing.d.cts !testing.d.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index dda26eaf2..4efffc944 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Versioning]. ## [Unreleased] +- Add `shescape/stateless` module with v1-like API. ([#1130]) - Re-export `Shescape` as `Stubscape` from `shescape/testing`. ([#1308]) ## [2.0.2] - 2023-11-19 @@ -321,6 +322,7 @@ Versioning]. [#1082]: https://github.com/ericcornelissen/shescape/pull/1082 [#1083]: https://github.com/ericcornelissen/shescape/pull/1083 [#1094]: https://github.com/ericcornelissen/shescape/pull/1094 +[#1130]: https://github.com/ericcornelissen/shescape/pull/1130 [#1137]: https://github.com/ericcornelissen/shescape/pull/1137 [#1142]: https://github.com/ericcornelissen/shescape/pull/1142 [#1149]: https://github.com/ericcornelissen/shescape/pull/1149 diff --git a/config/c8/breakage.json b/config/c8/breakage.json index 80bfe7237..dc93482e3 100644 --- a/config/c8/breakage.json +++ b/config/c8/breakage.json @@ -1,6 +1,6 @@ { "all": true, - "include": ["src/", "index.js", "testing.js"], + "include": ["src/", "index.js", "stateless.js", "testing.js"], "exclude": [], "check-coverage": false, "reports-dir": "_reports/coverage/breakage", diff --git a/config/c8/compat.json b/config/c8/compat.json index b74fd5dd1..f17e1d742 100644 --- a/config/c8/compat.json +++ b/config/c8/compat.json @@ -1,6 +1,6 @@ { "all": true, - "include": ["src/", "index.js", "testing.js"], + "include": ["src/", "index.js", "stateless.js", "testing.js"], "exclude": [], "check-coverage": false, "reports-dir": "_reports/coverage/compat", diff --git a/config/c8/integration-unix.json b/config/c8/integration-unix.json index 2a7c2d4e5..29d565a40 100644 --- a/config/c8/integration-unix.json +++ b/config/c8/integration-unix.json @@ -1,6 +1,6 @@ { "all": true, - "include": ["src/", "index.js", "testing.js"], + "include": ["src/", "index.js", "stateless.js", "testing.js"], "exclude": ["src/win/", "src/win.js"], "check-coverage": false, "reports-dir": "_reports/coverage/integration", diff --git a/config/c8/integration-win.json b/config/c8/integration-win.json index 004c2dd6d..ddf6d7d0e 100644 --- a/config/c8/integration-win.json +++ b/config/c8/integration-win.json @@ -1,6 +1,6 @@ { "all": true, - "include": ["src/", "index.js", "testing.js"], + "include": ["src/", "index.js", "stateless.js", "testing.js"], "exclude": ["src/unix/", "src/unix.js"], "check-coverage": false, "reports-dir": "_reports/coverage/integration", diff --git a/config/eslint.yml b/config/eslint.yml index 8b00a3076..4e7ee19be 100644 --- a/config/eslint.yml +++ b/config/eslint.yml @@ -755,5 +755,7 @@ ignorePatterns: - script/maybe-run.js - index.cjs - index.d.cts + - stateless.cjs + - stateless.d.cts - testing.cjs - testing.d.cts diff --git a/config/rollup.js b/config/rollup.js index 02604709d..6ae71ff90 100644 --- a/config/rollup.js +++ b/config/rollup.js @@ -18,6 +18,14 @@ export default [ }, external, }, + { + input: "stateless.js", + output: { + file: "stateless.cjs", + format: "cjs", + }, + external, + }, { input: "testing.js", output: { diff --git a/config/stryker/integration.js b/config/stryker/integration.js index 8e4714b47..192d48de8 100644 --- a/config/stryker/integration.js +++ b/config/stryker/integration.js @@ -3,7 +3,7 @@ export default { coverageAnalysis: "perTest", inPlace: false, - mutate: ["index.js", "testing.js"], + mutate: ["index.js", "stateless.js", "testing.js"], testRunner: "tap", tap: { testFiles: ["test/integration/**/*.test.js"], diff --git a/index.d.ts b/index.d.ts index 6d552ab72..7da86cf97 100644 --- a/index.d.ts +++ b/index.d.ts @@ -8,7 +8,7 @@ * * @since 2.0.0 */ -interface ShescapeOptions { +export interface ShescapeOptions { /** * Whether or not to protect against flag and option (such as `--verbose`) * injection diff --git a/package.json b/package.json index 69014bc0e..f9a8c1d93 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,16 @@ "default": "./index.cjs" } }, + "./stateless": { + "import": { + "types": "./stateless.d.ts", + "default": "./stateless.js" + }, + "require": { + "types": "./stateless.d.cts", + "default": "./stateless.cjs" + } + }, "./testing": { "import": { "types": "./testing.d.ts", diff --git a/script/clean.js b/script/clean.js index 7c705b398..78f63ac6d 100644 --- a/script/clean.js +++ b/script/clean.js @@ -9,7 +9,14 @@ import path from "node:path"; import { common } from "./_.js"; -const files = ["index.cjs", "index.d.cts", "testing.cjs", "testing.d.cts"]; +const files = [ + "index.cjs", + "index.d.cts", + "stateless.cjs", + "stateless.d.cts", + "testing.cjs", + "testing.d.cts", +]; const folders = [".corpus/", ".nyc_output/", ".temp/", "_reports/"]; for (const file of files) { diff --git a/script/create-d-cts.js b/script/create-d-cts.js index 98917e07d..42f5450a7 100644 --- a/script/create-d-cts.js +++ b/script/create-d-cts.js @@ -8,7 +8,7 @@ import path from "node:path"; import { common } from "./_.js"; -const files = ["index.d.ts", "testing.d.ts"]; +const files = ["index.d.ts", "stateless.d.ts", "testing.d.ts"]; for (const file of files) { const copy = file.replace(".d.ts", ".d.cts"); diff --git a/stateless.d.ts b/stateless.d.ts new file mode 100644 index 000000000..2410f5033 --- /dev/null +++ b/stateless.d.ts @@ -0,0 +1,108 @@ +/** + * @overview Contains TypeScript type definitions for Shescape's stateless + * alternative. + * @license MPL-2.0 + */ + +import type { ShescapeOptions } from "shescape"; + +/** + * Take a single value, the argument, and escape any dangerous characters. + * + * Non-string inputs will be converted to strings using a `toString()` method. + * + * @example + * import { spawn } from "node:child_process"; + * spawn( + * "echo", + * ["Hello", shescape.escape(userInput, { shell: false })], + * null // `options.shell` MUST be falsy + * ); + * @param {string} arg The argument to escape. + * @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. + * @returns {string} The escaped argument. + * @throws {TypeError} The argument is not stringable. + * @throws {Error} The shell is not supported or could not be found. + * @since 2.1.0 + */ +export function escape(arg: string, options?: ShescapeOptions): string; + +/** + * Take an 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"; + * spawn( + * "echo", + * shescape.escapeAll(["Hello", userInput], { shell: false }), + * null // `options.shell` MUST be falsy + * ); + * @param {string[]} args The arguments to escape. + * @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. + * @returns {string[]} The escaped arguments. + * @throws {TypeError} One of the arguments is not stringable. + * @throws {Error} The shell is not supported or could not be found. + * @since 2.1.0 + */ +export function escapeAll(args: string[], options?: ShescapeOptions): string[]; + +/** + * 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. + * + * @example + * import { spawn } from "node:child_process"; + * const spawnOptions = { shell: true }; // `options.shell` SHOULD be truthy + * spawn( + * "echo", + * ["Hello", shescape.quote(userInput, { shell: spawnOptions.shell })], + * spawnOptions + * ); + * @param {string} arg The argument to quote and escape. + * @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. + * @returns {string} The quoted and escaped argument. + * @throws {TypeError} The argument is not stringable. + * @throws {Error} The shell is not supported or could not be found. + * @throws {Error} Quoting is not supported with `shell: false`. + * @since 2.1.0 + */ +export function quote(arg: string, options?: ShescapeOptions): string; + +/** + * Take an array of values, the arguments, put shell-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 + * spawn( + * "echo", + * shescape.quoteAll(["Hello", userInput], { shell: spawnOptions.shell }), + * spawnOptions + * ); + * @param {string[]} args The arguments to quote and escape. + * @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. + * @returns {string[]} The quoted and escaped arguments. + * @throws {TypeError} One of the arguments is not stringable. + * @throws {Error} The shell is not supported or could not be found. + * @throws {Error} Quoting is not supported with `shell: false`. + * @since 2.1.0 + */ +export function quoteAll(args: string[], options?: ShescapeOptions): string[]; diff --git a/stateless.js b/stateless.js new file mode 100644 index 000000000..db024fa9e --- /dev/null +++ b/stateless.js @@ -0,0 +1,120 @@ +/** + * @overview Alternative entrypoint for the library that provides a stateless + * API. + * @license MPL-2.0 + */ + +import { Shescape } from "./index.js"; + +/** + * Take a single value, the argument, and escape any dangerous characters. + * + * Non-string inputs will be converted to strings using a `toString()` method. + * + * @example + * import { spawn } from "node:child_process"; + * spawn( + * "echo", + * ["Hello", shescape.escape(userInput, { shell: false })], + * null // `options.shell` MUST be falsy + * ); + * @param {string} arg The argument to escape. + * @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. + * @returns {string} The escaped argument. + * @throws {TypeError} The argument is not stringable. + * @throws {Error} The shell is not supported or could not be found. + * @since 2.1.0 + */ +export function escape(arg, options) { + const shescape = new Shescape(options); + return shescape.escape(arg); +} + +/** + * Take an 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"; + * spawn( + * "echo", + * shescape.escapeAll(["Hello", userInput], { shell: false }), + * null // `options.shell` MUST be falsy + * ); + * @param {string[]} args The arguments to escape. + * @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. + * @returns {string[]} The escaped arguments. + * @throws {TypeError} One of the arguments is not stringable. + * @throws {Error} The shell is not supported or could not be found. + * @since 2.1.0 + */ +export function escapeAll(args, options) { + const shescape = new Shescape(options); + return shescape.escapeAll(args); +} + +/** + * 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. + * + * @example + * import { spawn } from "node:child_process"; + * const spawnOptions = { shell: true }; // `options.shell` SHOULD be truthy + * spawn( + * "echo", + * ["Hello", shescape.quote(userInput, { shell: spawnOptions.shell })], + * spawnOptions + * ); + * @param {string} arg The argument to quote and escape. + * @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. + * @returns {string} The quoted and escaped argument. + * @throws {TypeError} The argument is not stringable. + * @throws {Error} The shell is not supported or could not be found. + * @throws {Error} Quoting is not supported with `shell: false`. + * @since 2.1.0 + */ +export function quote(arg, options) { + const shescape = new Shescape(options); + return shescape.quote(arg); +} + +/** + * Take an array of values, the arguments, put shell-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 + * spawn( + * "echo", + * shescape.quoteAll(["Hello", userInput], { shell: spawnOptions.shell }), + * spawnOptions + * ); + * @param {string[]} args The arguments to quote and escape. + * @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. + * @returns {string[]} The quoted and escaped arguments. + * @throws {TypeError} One of the arguments is not stringable. + * @throws {Error} The shell is not supported or could not be found. + * @throws {Error} Quoting is not supported with `shell: false`. + * @since 2.1.0 + */ +export function quoteAll(args, options) { + const shescape = new Shescape(options); + return shescape.quoteAll(args); +} diff --git a/test/breakage/stateless.test.js b/test/breakage/stateless.test.js new file mode 100644 index 000000000..9a5d88adb --- /dev/null +++ b/test/breakage/stateless.test.js @@ -0,0 +1,125 @@ +/** + * @overview Contains breakage tests for the stateless shescape variant. + * @license MIT + */ + +import { testProp } from "@fast-check/ava"; +import * as fc from "fast-check"; + +import { arbitrary } from "./_.js"; + +import * as shescape from "shescape/stateless"; + +// TODO: import after release 2.1.0. It's currently commented because the API +// has not yet been released. +//import * as previouscape from "shescape-previous/stateless"; + +// TODO: unskip after release 2.1.0. It's currently skipped because the API has +// not yet been released. +testProp.skip( + "shescape.escape", + [fc.anything(), arbitrary.shescapeOptions()], + (t, arg, options) => { + let result, errored; + let previousResult, previousErrored; + + try { + result = shescape.escape(arg, options); + } catch (_) { + errored = true; + } + + try { + previousResult = previouscape.escape(arg, options); + } catch (_) { + previousErrored = true; + } + + t.is(errored, previousErrored); + t.is(typeof result, typeof previousResult); + }, +); + +// TODO: unskip after release 2.1.0. It's currently skipped because the API has +// not yet been released. +testProp.skip( + "shescape.escapeAll", + [ + fc.oneof(fc.anything(), fc.array(fc.anything())), + arbitrary.shescapeOptions(), + ], + (t, args, options) => { + let result, errored; + let previousResult, previousErrored; + + try { + result = shescape.escapeAll(args, options); + } catch (_) { + errored = true; + } + + try { + previousResult = previouscape.escapeAll(args, options); + } catch (_) { + previousErrored = true; + } + + t.is(errored, previousErrored); + t.is(typeof result, typeof previousResult); + }, +); + +// TODO: unskip after release 2.1.0. It's currently skipped because the API has +// not yet been released. +testProp.skip( + "shescape.quote", + [fc.anything(), arbitrary.shescapeOptions()], + (t, arg, options) => { + let result, errored; + let previousResult, previousErrored; + + try { + result = shescape.quote(arg, options); + } catch (_) { + errored = true; + } + + try { + previousResult = previouscape.quote(arg, options); + } catch (_) { + previousErrored = true; + } + + t.is(errored, previousErrored); + t.is(typeof result, typeof previousResult); + }, +); + +// TODO: unskip after release 2.1.0. It's currently skipped because the API has +// not yet been released. +testProp.skip( + "shescape.quoteAll", + [ + fc.oneof(fc.anything(), fc.array(fc.anything())), + arbitrary.shescapeOptions(), + ], + (t, args, options) => { + let result, errored; + let previousResult, previousErrored; + + try { + result = shescape.quoteAll(args, options); + } catch (_) { + errored = true; + } + + try { + previousResult = previouscape.quoteAll(args, options); + } catch (_) { + previousErrored = true; + } + + t.is(errored, previousErrored); + t.is(typeof result, typeof previousResult); + }, +); diff --git a/test/compat/runner.js b/test/compat/runner.js index ceb161827..0e22fee00 100644 --- a/test/compat/runner.js +++ b/test/compat/runner.js @@ -15,6 +15,7 @@ */ import * as index from "./index.test.js"; +import * as stateless from "./stateless.test.js"; import * as testing from "./testing.test.js"; index.testShescapeEscape(); @@ -22,6 +23,11 @@ index.testShescapeEscapeAll(); index.testShescapeQuote(); index.testShescapeQuoteAll(); +stateless.testShescapeEscape(); +stateless.testShescapeEscapeAll(); +stateless.testShescapeQuote(); +stateless.testShescapeQuoteAll(); + testing.testStubscapeEscape(); testing.testStubscapeEscapeAll(); testing.testStubscapeQuote(); diff --git a/test/compat/stateless.test.js b/test/compat/stateless.test.js new file mode 100644 index 000000000..38456a4ff --- /dev/null +++ b/test/compat/stateless.test.js @@ -0,0 +1,109 @@ +/** + * @overview Contains smoke tests for the stateless shescape module to verify + * compatibility with Node.js versions. + * @license MIT + */ + +import * as fc from "fast-check"; + +import { arbitrary } from "./_.js"; + +import * as shescape from "../../stateless.js"; + +export function testShescapeEscape() { + fc.assert( + fc.property( + arbitrary.shescapeArg(), + arbitrary.shescapeOptions(), + (arg, options) => { + try { + shescape.escape(arg, options); + } catch (error) { + const known = [ + "No executable could be found for ", + "Cannot read property 'escape' of undefined", + "Cannot read properties of undefined (reading 'escape')", + ]; + + if (!known.some((knownError) => error.message.includes(knownError))) { + throw new Error(`Unexpected error:\n${error}`); + } + } + }, + ), + ); +} + +export function testShescapeEscapeAll() { + fc.assert( + fc.property( + fc.array(arbitrary.shescapeArg()), + arbitrary.shescapeOptions(), + (args, options) => { + try { + shescape.escapeAll(args, options); + } catch (error) { + const known = [ + "No executable could be found for ", + "Cannot read property 'escapeAll' of undefined", + "Cannot read properties of undefined (reading 'escapeAll')", + ]; + + if (!known.some((knownError) => error.message.includes(knownError))) { + throw new Error(`Unexpected error:\n${error}`); + } + } + }, + ), + ); +} + +export function testShescapeQuote() { + fc.assert( + fc.property( + arbitrary.shescapeArg(), + arbitrary.shescapeOptions(), + (arg, options) => { + try { + shescape.quote(arg, options); + } catch (error) { + const known = [ + "No executable could be found for ", + "Cannot read property 'quote' of undefined", + "Cannot read properties of undefined (reading 'quote')", + "Quoting is not supported when no shell is used", + ]; + + if (!known.some((knownError) => error.message.includes(knownError))) { + throw new Error(`Unexpected error:\n${error}`); + } + } + }, + ), + ); +} + +export function testShescapeQuoteAll() { + fc.assert( + fc.property( + fc.array(arbitrary.shescapeArg()), + arbitrary.shescapeOptions(), + (args, options) => { + try { + shescape.quoteAll(args, options); + } catch (error) { + const known = [ + "No executable could be found for ", + "Cannot read property 'quoteAll' of undefined", + "Cannot read properties of undefined (reading 'quoteAll')", + "Quoting is not supported when no shell is used", + ]; + + if (!known.some((knownError) => error.message.includes(knownError))) { + throw new Error(`Unexpected error:\n${error}`); + } + } + }, + ), + ); +} diff --git a/test/integration/stateless/commonjs.test.js b/test/integration/stateless/commonjs.test.js new file mode 100644 index 000000000..66d57896a --- /dev/null +++ b/test/integration/stateless/commonjs.test.js @@ -0,0 +1,109 @@ +/** + * @overview Contains integration tests for the CommonJS version of the + * stateless version of Shescape. + * @license MIT + */ + +import { testProp } from "@fast-check/ava"; +import * as fc from "fast-check"; + +import { arbitrary } from "../_.js"; + +import * as shescape from "shescape/stateless"; +import * as shescapeCjs from "../../../stateless.cjs"; + +testProp( + "shescape.escape (esm === cjs)", + [arbitrary.shescapeArg(), arbitrary.shescapeOptions()], + (t, arg, options) => { + let resultEsm, errorEsm; + let resultCjs, errorCjs; + + try { + resultEsm = shescape.escape(arg, options); + } catch (error) { + errorEsm = error; + } + + try { + resultCjs = shescapeCjs.escape(arg, options); + } catch (error) { + errorCjs = error; + } + + t.is(resultEsm, resultCjs); + t.deepEqual(errorEsm, errorCjs); + }, +); + +testProp( + "shescape.escapeAll (esm === cjs)", + [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()], + (t, args, options) => { + let resultEsm, errorEsm; + let resultCjs, errorCjs; + + try { + resultEsm = shescape.escapeAll(args, options); + } catch (error) { + errorEsm = error; + } + + try { + resultCjs = shescapeCjs.escapeAll(args, options); + } catch (error) { + errorCjs = error; + } + + t.deepEqual(resultEsm, resultCjs); + t.deepEqual(errorEsm, errorCjs); + }, +); + +testProp( + "shescape.quote (esm === cjs)", + [arbitrary.shescapeArg(), arbitrary.shescapeOptions()], + (t, arg, options) => { + let resultEsm, errorEsm; + let resultCjs, errorCjs; + + try { + resultEsm = shescape.quote(arg, options); + } catch (error) { + errorEsm = error; + } + + try { + resultCjs = shescapeCjs.quote(arg, options); + } catch (error) { + errorCjs = error; + } + + t.is(resultEsm, resultCjs); + t.deepEqual(errorEsm, errorCjs); + }, +); + +testProp( + "shescape.quoteAll (esm === cjs)", + [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()], + (t, args, options) => { + let resultEsm, errorEsm; + let resultCjs, errorCjs; + + try { + resultEsm = shescape.quoteAll(args, options); + } catch (error) { + errorEsm = error; + } + + try { + resultCjs = shescapeCjs.quoteAll(args, options); + } catch (error) { + errorCjs = error; + } + + t.deepEqual(resultEsm, resultCjs); + t.deepEqual(errorEsm, errorCjs); + }, +); diff --git a/test/integration/stateless/functional.test.js b/test/integration/stateless/functional.test.js new file mode 100644 index 000000000..833794736 --- /dev/null +++ b/test/integration/stateless/functional.test.js @@ -0,0 +1,112 @@ +/** + * @overview Contains integration tests for the stateless version of Shescape. + * @license MIT + */ + +import { testProp } from "@fast-check/ava"; +import * as fc from "fast-check"; + +import { arbitrary } from "../_.js"; + +import { Shescape } from "shescape"; +import * as shescape from "shescape/stateless"; + +testProp( + "shescape.escape", + [arbitrary.shescapeArg(), arbitrary.shescapeOptions()], + (t, arg, options) => { + let actual, actualError; + let expected, expectedError; + + try { + actual = shescape.escape(arg, options); + } catch (error) { + actualError = error; + } + + try { + const shescape = new Shescape(options); + expected = shescape.escape(arg); + } catch (error) { + expectedError = error; + } + + t.is(actual, expected); + t.deepEqual(actualError, expectedError); + }, +); + +testProp( + "shescape.escapeAll", + [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()], + (t, arg, options) => { + let actual, actualError; + let expected, expectedError; + + try { + actual = shescape.escapeAll(arg, options); + } catch (error) { + actualError = error; + } + + try { + const shescape = new Shescape(options); + expected = shescape.escapeAll(arg); + } catch (error) { + expectedError = error; + } + + t.deepEqual(actual, expected); + t.deepEqual(actualError, expectedError); + }, +); + +testProp( + "shescape.quote", + [arbitrary.shescapeArg(), arbitrary.shescapeOptions()], + (t, arg, options) => { + let actual, actualError; + let expected, expectedError; + + try { + actual = shescape.quote(arg, options); + } catch (error) { + actualError = error; + } + + try { + const shescape = new Shescape(options); + expected = shescape.quote(arg); + } catch (error) { + expectedError = error; + } + + t.is(actual, expected); + t.deepEqual(actualError, expectedError); + }, +); + +testProp( + "shescape.quoteAll", + [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()], + (t, arg, options) => { + let actual, actualError; + let expected, expectedError; + + try { + actual = shescape.quoteAll(arg, options); + } catch (error) { + actualError = error; + } + + try { + const shescape = new Shescape(options); + expected = shescape.quoteAll(arg); + } catch (error) { + expectedError = error; + } + + t.deepEqual(actual, expected); + t.deepEqual(actualError, expectedError); + }, +);