diff --git a/.gitignore b/.gitignore index e23b128..33ae418 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ node_modules/ +.vendor/ coverage/ test/tmp/ +**/test/tmp/ test/*.log +**/test/*.log lib/ *.tsbuildinfo \ No newline at end of file diff --git a/bun.lock b/bun.lock index 24822ee..2b95e3c 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 1, "workspaces": { "": { "name": "proper-lockfile", @@ -12,19 +11,102 @@ "packages/node-utils": { "name": "@alchemy.run/node-utils", "version": "0.0.2", + "dependencies": { + "@effect/platform-bun": "4.0.0-beta.66", + "@effect/platform-node": "4.0.0-beta.66", + "effect": "4.0.0-beta.66", + }, }, }, "packages": { "@alchemy.run/node-utils": ["@alchemy.run/node-utils@workspace:packages/node-utils"], + "@effect/platform-bun": ["@effect/platform-bun@4.0.0-beta.66", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.66" }, "peerDependencies": { "effect": "^4.0.0-beta.66" } }, "sha512-egxbmAEtJZ8FYTaPMrYu3VwjgVuzHj3VdUyPyZ/QhSFP/DW2Yunlk/ouChi1JzX/m+Gj8tdMma7NiqO59J9d5Q=="], + + "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.66", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.66", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.66", "ioredis": "^5.7.0" } }, "sha512-s/0RgaQFuszzdorRnX1PwEQNnSOi+JgMJo3zEe9O2NR3sosMhTr0Uk+1AF6bUOI9uJ2CPT3KpTIIU7q5/TpOkg=="], + + "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.66", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.66" } }, "sha512-+ymrhBnESv/hmn5SKTe2//IY9Ox/hGPeoogEWhW47ZGyhFI5eMYFxdEUBa+3IAV05rrBzrxON9lynu68n0DM7w=="], + + "@ioredis/commands": ["@ioredis/commands@1.5.1", "", {}, "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw=="], + + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], + + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="], + + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="], + + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "effect": ["effect@4.0.0-beta.66", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-4arEr62cziFa8BBVDUwJCJJmaVepXf/kRg7KtC0h8+bufngscrHbwWFhr9c+HonwOF+31U3iD3xUJmw9KzX7Dw=="], + + "fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="], + + "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], + + "ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], + + "ioredis": ["ioredis@5.10.1", "", { "dependencies": { "@ioredis/commands": "1.5.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA=="], + + "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], + + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], + + "lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="], + + "mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "msgpackr": ["msgpackr@1.11.12", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg=="], + + "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], + + "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], + + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + + "pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="], + + "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], + + "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="], + + "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], + + "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="], + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + "undici": ["undici@8.3.0", "", {}, "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q=="], + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "uuid": ["uuid@13.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw=="], + + "ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], } } diff --git a/packages/node-utils/package.json b/packages/node-utils/package.json index 80fe41c..31fa40b 100644 --- a/packages/node-utils/package.json +++ b/packages/node-utils/package.json @@ -1,6 +1,7 @@ { "name": "@alchemy.run/node-utils", "version": "0.0.2", + "type": "module", "author": "Sam Goodwin (sam@alchemy.run)", "repository": { "type": "git", @@ -18,6 +19,11 @@ "default": "./lib/index.js" } }, + "dependencies": { + "@effect/platform-bun": "4.0.0-beta.66", + "@effect/platform-node": "4.0.0-beta.66", + "effect": "4.0.0-beta.66" + }, "scripts": { "test": "bun test" }, diff --git a/packages/node-utils/src/braces/compile.ts b/packages/node-utils/src/braces/compile.ts new file mode 100644 index 0000000..b606298 --- /dev/null +++ b/packages/node-utils/src/braces/compile.ts @@ -0,0 +1,55 @@ +import { fill } from "../fill-range.ts"; +import * as utils from "./utils.ts"; + +export interface CompileOptions { + escapeInvalid?: boolean; + [k: string]: unknown; +} + +export function compile(ast: utils.Node, options: CompileOptions = {}): string { + const walk = (node: utils.Node, parent: utils.Node = {}): string => { + const invalidBlock = utils.isInvalidBrace(parent); + const invalidNode = node.invalid === true && options.escapeInvalid === true; + const invalid = invalidBlock === true || invalidNode === true; + const prefix = options.escapeInvalid === true ? "\\" : ""; + let output = ""; + + if (node.isOpen === true) return prefix + node.value; + if (node.isClose === true) return prefix + node.value; + if (node.type === "open") return invalid ? prefix + node.value : "("; + if (node.type === "close") return invalid ? prefix + node.value : ")"; + if (node.type === "comma") { + return node.prev.type === "comma" ? "" : invalid ? node.value : "|"; + } + + if (node.value) return node.value; + + if (node.nodes && node.ranges > 0) { + const args = utils.reduce(node.nodes); + const range = fill( + args[0] as any, + args[1] as any, + args[2] as any, + { ...options, wrap: false, toRegex: true, strictZeros: true } as any, + ) as string; + + if ((range as unknown as string).length !== 0) { + return args.length > 1 && (range as unknown as string).length > 1 + ? `(${range})` + : (range as string); + } + } + + if (node.nodes) { + for (const child of node.nodes as utils.Node[]) { + output += walk(child, node); + } + } + + return output; + }; + + return walk(ast); +} + +export default compile; diff --git a/packages/node-utils/src/braces/constants.ts b/packages/node-utils/src/braces/constants.ts new file mode 100644 index 0000000..836bc44 --- /dev/null +++ b/packages/node-utils/src/braces/constants.ts @@ -0,0 +1,50 @@ +export const MAX_LENGTH = 10000; + +export const CHAR_0 = "0"; +export const CHAR_9 = "9"; + +export const CHAR_UPPERCASE_A = "A"; +export const CHAR_LOWERCASE_A = "a"; +export const CHAR_UPPERCASE_Z = "Z"; +export const CHAR_LOWERCASE_Z = "z"; + +export const CHAR_LEFT_PARENTHESES = "("; +export const CHAR_RIGHT_PARENTHESES = ")"; + +export const CHAR_ASTERISK = "*"; + +export const CHAR_AMPERSAND = "&"; +export const CHAR_AT = "@"; +export const CHAR_BACKSLASH = "\\"; +export const CHAR_BACKTICK = "`"; +export const CHAR_CARRIAGE_RETURN = "\r"; +export const CHAR_CIRCUMFLEX_ACCENT = "^"; +export const CHAR_COLON = ":"; +export const CHAR_COMMA = ","; +export const CHAR_DOLLAR = "$"; +export const CHAR_DOT = "."; +export const CHAR_DOUBLE_QUOTE = '"'; +export const CHAR_EQUAL = "="; +export const CHAR_EXCLAMATION_MARK = "!"; +export const CHAR_FORM_FEED = "\f"; +export const CHAR_FORWARD_SLASH = "/"; +export const CHAR_HASH = "#"; +export const CHAR_HYPHEN_MINUS = "-"; +export const CHAR_LEFT_ANGLE_BRACKET = "<"; +export const CHAR_LEFT_CURLY_BRACE = "{"; +export const CHAR_LEFT_SQUARE_BRACKET = "["; +export const CHAR_LINE_FEED = "\n"; +export const CHAR_NO_BREAK_SPACE = " "; +export const CHAR_PERCENT = "%"; +export const CHAR_PLUS = "+"; +export const CHAR_QUESTION_MARK = "?"; +export const CHAR_RIGHT_ANGLE_BRACKET = ">"; +export const CHAR_RIGHT_CURLY_BRACE = "}"; +export const CHAR_RIGHT_SQUARE_BRACKET = "]"; +export const CHAR_SEMICOLON = ";"; +export const CHAR_SINGLE_QUOTE = "'"; +export const CHAR_SPACE = " "; +export const CHAR_TAB = "\t"; +export const CHAR_UNDERSCORE = "_"; +export const CHAR_VERTICAL_LINE = "|"; +export const CHAR_ZERO_WIDTH_NOBREAK_SPACE = ""; diff --git a/packages/node-utils/src/braces/expand.ts b/packages/node-utils/src/braces/expand.ts new file mode 100644 index 0000000..28adf7b --- /dev/null +++ b/packages/node-utils/src/braces/expand.ts @@ -0,0 +1,135 @@ +import { fill } from "../fill-range.ts"; +import { stringify } from "./stringify.ts"; +import * as utils from "./utils.ts"; + +export interface ExpandOptions { + rangeLimit?: number | false; + step?: number | string; + [k: string]: unknown; +} + +const append = ( + queue: any = "", + stash: any = "", + enclose = false, +): any[] => { + const result: any[] = []; + queue = ([] as any[]).concat(queue); + stash = ([] as any[]).concat(stash); + + if (!stash.length) return queue; + if (!queue.length) { + return enclose + ? utils.flatten(stash).map((ele) => `{${ele}}`) + : stash; + } + + for (const item of queue) { + if (Array.isArray(item)) { + for (const value of item) { + result.push(append(value, stash, enclose)); + } + } else { + for (let ele of stash) { + if (enclose === true && typeof ele === "string") ele = `{${ele}}`; + result.push(Array.isArray(ele) ? append(item, ele, enclose) : item + ele); + } + } + } + return utils.flatten(result); +}; + +export function expand(ast: utils.Node, options: ExpandOptions = {}): string[] { + const rangeLimit = options.rangeLimit === undefined ? 1000 : options.rangeLimit; + + const walk = (node: utils.Node, parent: utils.Node = {}): any => { + node.queue = []; + + let p = parent; + let q = parent.queue; + + while (p.type !== "brace" && p.type !== "root" && p.parent) { + p = p.parent; + q = p.queue; + } + + if (node.invalid || node.dollar) { + q.push(append(q.pop(), stringify(node, options))); + return; + } + + if ( + node.type === "brace" && + node.invalid !== true && + node.nodes.length === 2 + ) { + q.push(append(q.pop(), ["{}"])); + return; + } + + if (node.nodes && node.ranges > 0) { + const args = utils.reduce(node.nodes); + + if ( + utils.exceedsLimit( + args[0], + args[1], + options.step ?? args[2], + rangeLimit as number | false, + ) + ) { + throw new RangeError( + "expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.", + ); + } + + let range = fill(args[0] as any, args[1] as any, args[2] as any, options as any); + if ((range as any).length === 0) { + range = stringify(node, options) as any; + } + + q.push(append(q.pop(), range)); + node.nodes = []; + return; + } + + const enclose = utils.encloseBrace(node); + let queue = node.queue; + let block = node; + + while (block.type !== "brace" && block.type !== "root" && block.parent) { + block = block.parent; + queue = block.queue; + } + + for (let i = 0; i < node.nodes.length; i++) { + const child = node.nodes[i]; + + if (child.type === "comma" && node.type === "brace") { + if (i === 1) queue.push(""); + queue.push(""); + continue; + } + + if (child.type === "close") { + q.push(append(q.pop(), queue, enclose)); + continue; + } + + if (child.value && child.type !== "open") { + queue.push(append(queue.pop(), child.value)); + continue; + } + + if (child.nodes) { + walk(child, node); + } + } + + return queue; + }; + + return utils.flatten(walk(ast)); +} + +export default expand; diff --git a/packages/node-utils/src/braces/index.ts b/packages/node-utils/src/braces/index.ts new file mode 100644 index 0000000..cfcf400 --- /dev/null +++ b/packages/node-utils/src/braces/index.ts @@ -0,0 +1,90 @@ +import { stringify as _stringify } from "./stringify.ts"; +import { compile as _compile } from "./compile.ts"; +import { expand as _expand } from "./expand.ts"; +import { parse as _parse } from "./parse.ts"; +import type { Node } from "./utils.ts"; + +export interface BracesOptions { + expand?: boolean; + nodupes?: boolean; + noempty?: boolean; + escapeInvalid?: boolean; + keepEscaping?: boolean; + keepQuotes?: boolean; + maxLength?: number; + rangeLimit?: number | false; + step?: number | string; + [k: string]: unknown; +} + +export function braces( + input: string | string[], + options: BracesOptions = {}, +): string[] { + let output: string[] = []; + + if (Array.isArray(input)) { + for (const pattern of input) { + const result = braces.create(pattern, options); + if (Array.isArray(result)) output.push(...result); + else output.push(result); + } + } else { + output = ([] as string[]).concat(braces.create(input, options)); + } + + if (options && options.expand === true && options.nodupes === true) { + output = [...new Set(output)]; + } + return output; +} + +export namespace braces { + export const parse = (input: string, options: BracesOptions = {}): Node => + _parse(input, options); + + export const stringify = ( + input: string | Node, + options: BracesOptions = {}, + ): string => { + if (typeof input === "string") { + return _stringify(parse(input, options), options); + } + return _stringify(input, options); + }; + + export const compile = ( + input: string | Node, + options: BracesOptions = {}, + ): string => { + if (typeof input === "string") { + input = parse(input, options); + } + return _compile(input, options); + }; + + export const expand = ( + input: string | Node, + options: BracesOptions = {}, + ): string[] => { + if (typeof input === "string") { + input = parse(input, options); + } + let result = _expand(input, options); + if (options.noempty === true) result = result.filter(Boolean); + if (options.nodupes === true) result = [...new Set(result)]; + return result; + }; + + export const create = ( + input: string, + options: BracesOptions = {}, + ): string | string[] => { + if (input === "" || input.length < 3) return [input]; + return options.expand !== true + ? compile(input, options) + : expand(input, options); + }; +} + +export default braces; diff --git a/packages/node-utils/src/braces/parse.ts b/packages/node-utils/src/braces/parse.ts new file mode 100644 index 0000000..79558ef --- /dev/null +++ b/packages/node-utils/src/braces/parse.ts @@ -0,0 +1,296 @@ +import { stringify } from "./stringify.ts"; +import { + MAX_LENGTH, + CHAR_BACKSLASH, + CHAR_BACKTICK, + CHAR_COMMA, + CHAR_DOT, + CHAR_LEFT_PARENTHESES, + CHAR_RIGHT_PARENTHESES, + CHAR_LEFT_CURLY_BRACE, + CHAR_RIGHT_CURLY_BRACE, + CHAR_LEFT_SQUARE_BRACKET, + CHAR_RIGHT_SQUARE_BRACKET, + CHAR_DOUBLE_QUOTE, + CHAR_SINGLE_QUOTE, + CHAR_NO_BREAK_SPACE, + CHAR_ZERO_WIDTH_NOBREAK_SPACE, +} from "./constants.ts"; +import type { Node } from "./utils.ts"; + +export interface ParseOptions { + maxLength?: number; + keepEscaping?: boolean; + keepQuotes?: boolean; + [k: string]: unknown; +} + +export function parse(input: string, options: ParseOptions = {}): Node { + if (typeof input !== "string") { + throw new TypeError("Expected a string"); + } + + const opts = options || {}; + const max = + typeof opts.maxLength === "number" + ? Math.min(MAX_LENGTH, opts.maxLength) + : MAX_LENGTH; + if (input.length > max) { + throw new SyntaxError( + `Input length (${input.length}), exceeds max characters (${max})`, + ); + } + + const ast: Node = { type: "root", input, nodes: [] }; + const stack: Node[] = [ast]; + let block: Node = ast; + let prev: Node = ast; + let brackets = 0; + const length = input.length; + let index = 0; + let depth = 0; + let value: string; + + const advance = () => input[index++]; + const push = (node: Node): Node | undefined => { + if (node.type === "text" && prev.type === "dot") { + prev.type = "text"; + } + + if (prev && prev.type === "text" && node.type === "text") { + prev.value += node.value; + return; + } + + block.nodes.push(node); + node.parent = block; + node.prev = prev; + prev = node; + return node; + }; + + push({ type: "bos" }); + + while (index < length) { + block = stack[stack.length - 1]; + value = advance(); + + if ( + value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || + value === CHAR_NO_BREAK_SPACE + ) { + continue; + } + + if (value === CHAR_BACKSLASH) { + push({ + type: "text", + value: (options.keepEscaping ? value : "") + advance(), + }); + continue; + } + + if (value === CHAR_RIGHT_SQUARE_BRACKET) { + push({ type: "text", value: "\\" + value }); + continue; + } + + if (value === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; + let next: string; + + while (index < length && (next = advance())) { + value += next; + + if (next === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; + continue; + } + + if (next === CHAR_BACKSLASH) { + value += advance(); + continue; + } + + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + brackets--; + if (brackets === 0) break; + } + } + + push({ type: "text", value }); + continue; + } + + if (value === CHAR_LEFT_PARENTHESES) { + block = push({ type: "paren", nodes: [] }) as Node; + stack.push(block); + push({ type: "text", value }); + continue; + } + + if (value === CHAR_RIGHT_PARENTHESES) { + if (block.type !== "paren") { + push({ type: "text", value }); + continue; + } + block = stack.pop() as Node; + push({ type: "text", value }); + block = stack[stack.length - 1]; + continue; + } + + if ( + value === CHAR_DOUBLE_QUOTE || + value === CHAR_SINGLE_QUOTE || + value === CHAR_BACKTICK + ) { + const leftQuote = value; + const hasRightQuote = + input + .slice(index) + .search(new RegExp(`[^\\\\]${leftQuote}|^${leftQuote}`)) !== -1; + + let next: string; + + if (hasRightQuote) { + if (options.keepQuotes !== true) { + value = ""; + } + + while (index <= length && (next = advance())) { + if (next === CHAR_BACKSLASH) { + value += next + advance(); + continue; + } + + if (next === leftQuote) { + if (options.keepQuotes === true) value += next; + break; + } + + value += next; + } + } + + push({ type: "text", value }); + continue; + } + + if (value === CHAR_LEFT_CURLY_BRACE) { + depth++; + const dollar = + (prev.value && prev.value.slice(-1) === "$") || block.dollar === true; + const brace: Node = { + type: "brace", + open: true, + close: false, + dollar, + depth, + commas: 0, + ranges: 0, + nodes: [], + }; + + block = push(brace) as Node; + stack.push(block); + push({ type: "open", value }); + continue; + } + + if (value === CHAR_RIGHT_CURLY_BRACE) { + if (block.type !== "brace") { + push({ type: "text", value }); + continue; + } + + const type = "close"; + block = stack.pop() as Node; + block.close = true; + + push({ type, value }); + depth--; + + block = stack[stack.length - 1]; + continue; + } + + if (value === CHAR_COMMA && depth > 0) { + if (block.ranges > 0) { + block.ranges = 0; + const open = block.nodes.shift(); + block.nodes = [open, { type: "text", value: stringify(block) }]; + } + block.invalid = false; + + push({ type: "comma", value }); + block.commas++; + continue; + } + + if (value === CHAR_DOT && depth > 0 && block.commas === 0) { + const siblings = block.nodes; + + if (depth === 0 || siblings.length === 0) { + push({ type: "text", value }); + continue; + } + + if (prev.type === "dot") { + block.range = []; + prev.value += value; + prev.type = "range"; + + if (block.nodes.length !== 3 && block.nodes.length !== 5) { + block.invalid = true; + block.ranges = 0; + prev.type = "text"; + continue; + } + + block.ranges++; + block.args = []; + continue; + } + + if (prev.type === "range") { + siblings.pop(); + + const before = siblings[siblings.length - 1]; + before.value += prev.value + value; + prev = before; + block.ranges--; + continue; + } + + push({ type: "dot", value }); + continue; + } + + push({ type: "text", value }); + } + + do { + block = stack.pop() as Node; + + if (block.type !== "root") { + block.nodes.forEach((node: Node) => { + if (!node.nodes) { + if (node.type === "open") node.isOpen = true; + if (node.type === "close") node.isClose = true; + if (!node.nodes) node.type = "text"; + node.invalid = true; + } + }); + + const parent = stack[stack.length - 1]; + const idx = parent.nodes.indexOf(block); + parent.nodes.splice(idx, 1, ...block.nodes); + } + } while (stack.length > 0); + + push({ type: "eos" }); + return ast; +} + +export default parse; diff --git a/packages/node-utils/src/braces/stringify.ts b/packages/node-utils/src/braces/stringify.ts new file mode 100644 index 0000000..b7af515 --- /dev/null +++ b/packages/node-utils/src/braces/stringify.ts @@ -0,0 +1,32 @@ +import * as utils from "./utils.ts"; + +export interface StringifyOptions { + escapeInvalid?: boolean; + [k: string]: unknown; +} + +export function stringify(ast: utils.Node, options: StringifyOptions = {}): string { + const walk = (node: utils.Node, parent: utils.Node = {}): string => { + const invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent); + const invalidNode = node.invalid === true && options.escapeInvalid === true; + let output = ""; + + if (node.value) { + if ((invalidBlock || invalidNode) && utils.isOpenOrClose(node)) { + return "\\" + node.value; + } + return node.value; + } + + if (node.nodes) { + for (const child of node.nodes as utils.Node[]) { + output += walk(child); + } + } + return output; + }; + + return walk(ast); +} + +export default stringify; diff --git a/packages/node-utils/src/braces/utils.ts b/packages/node-utils/src/braces/utils.ts new file mode 100644 index 0000000..fcfd9f1 --- /dev/null +++ b/packages/node-utils/src/braces/utils.ts @@ -0,0 +1,95 @@ +// AST nodes are highly dynamic — keep loose typing. +export type Node = any; + +export const isInteger = (num: unknown): boolean => { + if (typeof num === "number") return Number.isInteger(num); + if (typeof num === "string" && num.trim() !== "") { + return Number.isInteger(Number(num)); + } + return false; +}; + +export const find = (node: Node, type: string): Node | undefined => + node.nodes.find((n: Node) => n.type === type); + +export const exceedsLimit = ( + min: unknown, + max: unknown, + step: unknown = 1, + limit: number | false, +): boolean => { + if (limit === false) return false; + if (!isInteger(min) || !isInteger(max)) return false; + return (Number(max) - Number(min)) / Number(step) >= limit; +}; + +export const escapeNode = (block: Node, n = 0, type?: string): void => { + const node = block.nodes[n]; + if (!node) return; + + if ( + (type && node.type === type) || + node.type === "open" || + node.type === "close" + ) { + if (node.escaped !== true) { + node.value = "\\" + node.value; + node.escaped = true; + } + } +}; + +export const encloseBrace = (node: Node): boolean => { + if (node.type !== "brace") return false; + // Intentionally matches upstream operator precedence: `a >> 0 + b >> 0` + // groups as `a >> (0 + b) >> 0`. Don't add parens — tests depend on it. + if ((node.commas >> 0 + node.ranges >> 0) === 0) { + node.invalid = true; + return true; + } + return false; +}; + +export const isInvalidBrace = (block: Node): boolean => { + if (block.type !== "brace") return false; + if (block.invalid === true || block.dollar) return true; + // See encloseBrace — same buggy precedence preserved intentionally. + if ((block.commas >> 0 + block.ranges >> 0) === 0) { + block.invalid = true; + return true; + } + if (block.open !== true || block.close !== true) { + block.invalid = true; + return true; + } + return false; +}; + +export const isOpenOrClose = (node: Node): boolean => { + if (node.type === "open" || node.type === "close") return true; + return node.open === true || node.close === true; +}; + +export const reduce = (nodes: Node[]): string[] => + nodes.reduce((acc, node) => { + if (node.type === "text") acc.push(node.value); + if (node.type === "range") node.type = "text"; + return acc; + }, []); + +export const flatten = (...args: any[]): any[] => { + const result: any[] = []; + const flat = (arr: any[]) => { + for (let i = 0; i < arr.length; i++) { + const ele = arr[i]; + if (Array.isArray(ele)) { + flat(ele); + continue; + } + if (ele !== undefined) result.push(ele); + } + return result; + }; + flat(args); + return result; +}; diff --git a/packages/node-utils/src/fast-glob/index.ts b/packages/node-utils/src/fast-glob/index.ts new file mode 100644 index 0000000..bc25210 --- /dev/null +++ b/packages/node-utils/src/fast-glob/index.ts @@ -0,0 +1,96 @@ +/** + * Public fast-glob API, Effect 4 / `FileSystem.FileSystem` / `Path.Path`. + * + * - `glob` returns `Effect.Effect` + * - `globStream` returns `Stream.Stream` + * - `globPromise` is a convenience wrapper that provides `BunServices.layer` + * internally and returns a `Promise` for callers outside Effect. + */ +import { BunServices } from "@effect/platform-bun"; +import { Effect, FileSystem, Path, Stream } from "effect"; +import { runTask } from "./walk.ts"; +import { generate } from "./tasks.ts"; +import { resolveSettings } from "./settings.ts"; +import type { + Entry, + EntryItem, + Options, + Pattern, +} from "./settings.ts"; + +export type { Entry, EntryItem, Options, Pattern }; +export type { Task } from "./tasks.ts"; + +type InputPattern = Pattern | readonly Pattern[]; + +const normalizeInput = (input: InputPattern): Pattern[] => { + const arr = ([] as Pattern[]).concat(input as Pattern[]); + for (const item of arr) { + if (typeof item !== "string" || item === "") { + throw new TypeError("Patterns must be a string (non empty) or an array of strings"); + } + } + return arr; +}; + +export function globStream( + source: InputPattern, + options?: Options, +): Stream.Stream { + const patterns = normalizeInput(source); + const settings = resolveSettings(options); + const tasks = generate(patterns, settings); + + if (tasks.length === 0) return Stream.empty; + + const streams = tasks.map((task) => runTask(task, settings)); + const merged = Stream.flatten(Stream.fromIterable(streams), { concurrency: "unbounded" }); + + if (!settings.unique) return merged; + + const seen = new Set(); + return Stream.filter(merged, (item) => { + const key = typeof item === "string" ? item : item.path; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); +} + +export function glob( + source: InputPattern, + options?: Options, +): Effect.Effect { + return Stream.runCollect(globStream(source, options)).pipe( + Effect.map((chunk) => Array.from(chunk)), + ); +} + +/** + * Convenience: run `glob` with `BunServices.layer` already provided, returning a Promise. + * Use this when you're not already inside an Effect. + */ +export function globPromise( + source: InputPattern, + options?: Options, +): Promise { + return Effect.runPromise( + Effect.provide(glob(source, options), BunServices.layer), + ); +} + +export function generateTasks( + source: InputPattern, + options?: Options, +) { + const patterns = normalizeInput(source); + const settings = resolveSettings(options); + return generate(patterns, settings); +} + +import * as pattern from "./pattern.ts"; + +export function isDynamicPattern(p: Pattern, options?: Options): boolean { + const settings = resolveSettings(options); + return pattern.isDynamicPattern(p, settings); +} diff --git a/packages/node-utils/src/fast-glob/pattern.ts b/packages/node-utils/src/fast-glob/pattern.ts new file mode 100644 index 0000000..e0339b1 --- /dev/null +++ b/packages/node-utils/src/fast-glob/pattern.ts @@ -0,0 +1,150 @@ +/** + * Pattern utilities — glob parsing, classification, brace expansion. + * + * Replaces upstream `utils/pattern.ts` + `glob-parent` dep. We use our vendored + * `micromatch` (which itself uses `picomatch.scan` for splitting base/glob). + */ +import path from "node:path"; +import micromatch from "../micromatch/index.ts"; +import braces from "../braces/index.ts"; +import type { Pattern } from "./settings.ts"; + +const GLOBSTAR = "**"; +const ESCAPE_SYMBOL = "\\"; + +const COMMON_GLOB_SYMBOLS_RE = /[*?]|^!/; +const REGEX_CHARACTER_CLASS_SYMBOLS_RE = /\[[^[]*]/; +const REGEX_GROUP_SYMBOLS_RE = /(?:^|[^!*+?@])\([^(]*\|[^|]*\)/; +const GLOB_EXTENSION_SYMBOLS_RE = /[!*+?@]\([^(]*\)/; +const BRACE_EXPANSION_SEPARATORS_RE = /,|\.\./; + +const DOUBLE_SLASH_RE = /(?!^)\/{2,}/g; + +interface PatternTypeOptions { + braceExpansion?: boolean; + caseSensitiveMatch?: boolean; + extglob?: boolean; +} + +export function isStaticPattern(p: Pattern, options: PatternTypeOptions = {}): boolean { + return !isDynamicPattern(p, options); +} + +export function isDynamicPattern(p: Pattern, options: PatternTypeOptions = {}): boolean { + if (p === "") return false; + if (options.caseSensitiveMatch === false || p.includes(ESCAPE_SYMBOL)) return true; + if ( + COMMON_GLOB_SYMBOLS_RE.test(p) || + REGEX_CHARACTER_CLASS_SYMBOLS_RE.test(p) || + REGEX_GROUP_SYMBOLS_RE.test(p) + ) { + return true; + } + if (options.extglob !== false && GLOB_EXTENSION_SYMBOLS_RE.test(p)) return true; + if (options.braceExpansion !== false && hasBraceExpansion(p)) return true; + return false; +} + +function hasBraceExpansion(p: string): boolean { + const open = p.indexOf("{"); + if (open === -1) return false; + const close = p.indexOf("}", open + 1); + if (close === -1) return false; + return BRACE_EXPANSION_SEPARATORS_RE.test(p.slice(open, close)); +} + +export const convertToPositivePattern = (p: Pattern): Pattern => + isNegativePattern(p) ? p.slice(1) : p; + +export const convertToNegativePattern = (p: Pattern): Pattern => `!${p}`; + +export const isNegativePattern = (p: Pattern): boolean => + p.startsWith("!") && p[1] !== "("; + +export const isPositivePattern = (p: Pattern): boolean => !isNegativePattern(p); + +export const getNegativePatterns = (patterns: Pattern[]): Pattern[] => + patterns.filter(isNegativePattern); + +export const getPositivePatterns = (patterns: Pattern[]): Pattern[] => + patterns.filter(isPositivePattern); + +export const isPatternRelatedToParentDirectory = (p: Pattern): boolean => + p.startsWith("..") || p.startsWith("./.."); + +export const getPatternsInsideCurrentDirectory = (patterns: Pattern[]): Pattern[] => + patterns.filter((p) => !isPatternRelatedToParentDirectory(p)); + +export const getPatternsOutsideCurrentDirectory = (patterns: Pattern[]): Pattern[] => + patterns.filter(isPatternRelatedToParentDirectory); + +/** + * Replaces upstream glob-parent. picomatch.scan returns the static prefix + * directly (handles brackets, parens, etc.). + */ +export function getBaseDirectory(pattern: Pattern): string { + const scan = micromatch.scan(pattern); + if (!scan.base) return "."; + return scan.base; +} + +export const hasGlobStar = (p: Pattern): boolean => p.includes(GLOBSTAR); + +export const endsWithSlashGlobStar = (p: Pattern): boolean => + p.endsWith(`/${GLOBSTAR}`); + +export const isAffectDepthOfReadingPattern = (p: Pattern): boolean => { + const basename = path.basename(p); + return endsWithSlashGlobStar(p) || isStaticPattern(basename); +}; + +export function expandPatternsWithBraceExpansion(patterns: Pattern[]): Pattern[] { + return patterns.flatMap(expandBraceExpansion); +} + +export function expandBraceExpansion(p: Pattern): Pattern[] { + const expanded = braces(p, { expand: true, nodupes: true, keepEscaping: true }); + expanded.sort((a, b) => a.length - b.length); + return expanded.filter((x) => x !== ""); +} + +export function getPatternParts(p: Pattern, options: any = {}): Pattern[] { + const scan = micromatch.scan(p, { ...options, parts: true }); + let parts = scan.parts ?? []; + if (parts.length === 0) parts = [p]; + if (parts[0].startsWith("/")) { + parts[0] = parts[0].slice(1); + parts.unshift(""); + } + return parts; +} + +export const makeRe = (p: Pattern, options: any = {}): RegExp => + micromatch.makeRe(p, options); + +export const convertPatternsToRe = (patterns: Pattern[], options: any = {}): RegExp[] => + patterns.map((p) => makeRe(p, options)); + +export const matchAny = (entry: string, patternsRe: RegExp[]): boolean => + patternsRe.some((re) => re.test(entry)); + +export const removeDuplicateSlashes = (p: string): string => + p.replaceAll(DOUBLE_SLASH_RE, "/"); + +export const isAbsolute = (p: string): boolean => path.isAbsolute(p); + +export function partitionAbsoluteAndRelative( + patterns: Pattern[], +): [Pattern[], Pattern[]] { + const absolute: Pattern[] = []; + const relative: Pattern[] = []; + for (const p of patterns) { + if (isAbsolute(p)) absolute.push(p); + else relative.push(p); + } + return [absolute, relative]; +} + +export function removeBackslashes(p: string): string { + return p.replace(/(?:\[.*?[^\\]\]|\\(?=.))/g, (m) => (m === "\\" ? "" : m)); +} diff --git a/packages/node-utils/src/fast-glob/settings.ts b/packages/node-utils/src/fast-glob/settings.ts new file mode 100644 index 0000000..099e892 --- /dev/null +++ b/packages/node-utils/src/fast-glob/settings.ts @@ -0,0 +1,92 @@ +/** + * Resolved options for a single glob call. Mirrors upstream `fast-glob`'s public + * `Options` surface but drops the `fs` adapter (we use `FileSystem.FileSystem`) + * and node-only bits like `signal` (Effect handles cancellation directly). + */ + +export type Pattern = string; + +export interface Options { + absolute?: boolean; + baseNameMatch?: boolean; + braceExpansion?: boolean; + caseSensitiveMatch?: boolean; + cwd?: string; + deep?: number; + dot?: boolean; + extglob?: boolean; + followSymbolicLinks?: boolean; + globstar?: boolean; + ignore?: readonly Pattern[]; + markDirectories?: boolean; + objectMode?: boolean; + onlyDirectories?: boolean; + onlyFiles?: boolean; + stats?: boolean; + suppressErrors?: boolean; + throwErrorOnBrokenSymbolicLink?: boolean; + unique?: boolean; +} + +export interface Settings { + readonly absolute: boolean; + readonly baseNameMatch: boolean; + readonly braceExpansion: boolean; + readonly caseSensitiveMatch: boolean; + readonly cwd: string; + readonly deep: number; + readonly dot: boolean; + readonly extglob: boolean; + readonly followSymbolicLinks: boolean; + readonly globstar: boolean; + readonly ignore: readonly Pattern[]; + readonly markDirectories: boolean; + readonly objectMode: boolean; + readonly onlyDirectories: boolean; + readonly onlyFiles: boolean; + readonly stats: boolean; + readonly suppressErrors: boolean; + readonly throwErrorOnBrokenSymbolicLink: boolean; + readonly unique: boolean; +} + +export function resolveSettings(options: Options = {}): Settings { + const onlyDirectories = options.onlyDirectories ?? false; + const stats = options.stats ?? false; + return { + absolute: options.absolute ?? false, + baseNameMatch: options.baseNameMatch ?? false, + braceExpansion: options.braceExpansion ?? true, + caseSensitiveMatch: options.caseSensitiveMatch ?? true, + cwd: options.cwd ?? process.cwd(), + deep: options.deep ?? Number.POSITIVE_INFINITY, + dot: options.dot ?? false, + extglob: options.extglob ?? true, + followSymbolicLinks: options.followSymbolicLinks ?? true, + globstar: options.globstar ?? true, + ignore: options.ignore ?? [], + markDirectories: options.markDirectories ?? false, + // stats implies objectMode. + objectMode: stats ? true : (options.objectMode ?? false), + onlyDirectories, + // onlyDirectories disables onlyFiles. + onlyFiles: onlyDirectories ? false : (options.onlyFiles ?? true), + stats, + suppressErrors: options.suppressErrors ?? false, + throwErrorOnBrokenSymbolicLink: options.throwErrorOnBrokenSymbolicLink ?? false, + unique: options.unique ?? true, + }; +} + +export interface Entry { + name: string; + path: string; + dirent: { + isFile: boolean; + isDirectory: boolean; + isSymbolicLink: boolean; + }; + stats?: import("node:fs").Stats; +} + +export type EntryItem = string | Entry; diff --git a/packages/node-utils/src/fast-glob/tasks.ts b/packages/node-utils/src/fast-glob/tasks.ts new file mode 100644 index 0000000..ee3608e --- /dev/null +++ b/packages/node-utils/src/fast-glob/tasks.ts @@ -0,0 +1,115 @@ +/** + * Group input patterns into "tasks" — units of work tied to a base directory. + * Mirrors upstream `managers/tasks.ts` with our flat pattern utils. + */ +import * as pattern from "./pattern.ts"; +import type { Pattern, Settings } from "./settings.ts"; + +export interface Task { + base: string; + dynamic: boolean; + patterns: Pattern[]; + positive: Pattern[]; + negative: Pattern[]; +} + +type PatternsGroup = Record; + +export function generate(input: readonly Pattern[], settings: Settings): Task[] { + const patterns = processPatterns([...input], settings); + const ignore = processPatterns([...settings.ignore], settings); + + const positive = pattern.getPositivePatterns(patterns); + const negative = getNegativePatternsAsPositive(patterns, ignore); + + const staticPatterns = positive.filter((p) => pattern.isStaticPattern(p, settings)); + const dynamicPatterns = positive.filter((p) => pattern.isDynamicPattern(p, settings)); + + const staticTasks = convertPatternsToTasks(staticPatterns, negative, false); + const dynamicTasks = convertPatternsToTasks(dynamicPatterns, negative, true); + + return staticTasks.concat(dynamicTasks); +} + +function processPatterns(input: Pattern[], settings: Settings): Pattern[] { + let patterns: Pattern[] = input; + + if (settings.braceExpansion) { + patterns = pattern.expandPatternsWithBraceExpansion(patterns); + } + if (settings.baseNameMatch) { + patterns = patterns.map((p) => (p.includes("/") ? p : `**/${p}`)); + } + return patterns.map(pattern.removeDuplicateSlashes); +} + +function getNegativePatternsAsPositive( + patterns: Pattern[], + ignore: Pattern[], +): Pattern[] { + const negative = pattern.getNegativePatterns(patterns).concat(ignore); + return negative.map(pattern.convertToPositivePattern); +} + +function convertPatternsToTasks( + positive: Pattern[], + negative: Pattern[], + dynamic: boolean, +): Task[] { + const tasks: Task[] = []; + + const outsidePatterns = pattern.getPatternsOutsideCurrentDirectory(positive); + const insidePatterns = pattern.getPatternsInsideCurrentDirectory(positive); + + const outsideGroup = groupPatternsByBaseDirectory(outsidePatterns); + const insideGroup = groupPatternsByBaseDirectory(insidePatterns); + + tasks.push(...convertPatternGroupsToTasks(outsideGroup, negative, dynamic)); + + if ("." in insideGroup) { + tasks.push(convertPatternGroupToTask(".", insidePatterns, negative, dynamic)); + } else { + tasks.push(...convertPatternGroupsToTasks(insideGroup, negative, dynamic)); + } + + return tasks; +} + +function groupPatternsByBaseDirectory(patterns: Pattern[]): PatternsGroup { + const group: PatternsGroup = {}; + for (const p of patterns) { + let base = pattern.getBaseDirectory(p); + base = pattern.removeBackslashes(base); + if (base in group) group[base].push(p); + else group[base] = [p]; + } + return group; +} + +function convertPatternGroupsToTasks( + positive: PatternsGroup, + negative: Pattern[], + dynamic: boolean, +): Task[] { + return Object.keys(positive).map((base) => + convertPatternGroupToTask(base, positive[base], negative, dynamic), + ); +} + +function convertPatternGroupToTask( + base: string, + positive: Pattern[], + negative: Pattern[], + dynamic: boolean, +): Task { + return { + dynamic, + positive, + negative, + base, + patterns: ([] as Pattern[]).concat( + positive, + negative.map(pattern.convertToNegativePattern), + ), + }; +} diff --git a/packages/node-utils/src/fast-glob/walk.ts b/packages/node-utils/src/fast-glob/walk.ts new file mode 100644 index 0000000..c0d24f0 --- /dev/null +++ b/packages/node-utils/src/fast-glob/walk.ts @@ -0,0 +1,232 @@ +/** + * Directory walker, Effect-based. + * + * Collapses upstream's `readers/` + `providers/` + `filters/` + `transformers/` + * layering into one function. Uses `FileSystem.FileSystem` for IO and `Path.Path` + * for path joining (cross-platform via `BunServices.layer`). + */ +import { Effect, FileSystem, Option, Path, Stream } from "effect"; +import micromatch from "../micromatch/index.ts"; +import * as pattern from "./pattern.ts"; +import type { Entry, Pattern, Settings } from "./settings.ts"; +import type { Task } from "./tasks.ts"; + +const MICROMATCH_DOT_OPTS = { + dot: true, + basename: false, + nobrace: true, + nocase: false, + noext: false, + noglobstar: false, +}; + +interface CompiledTask { + base: string; + positiveRe: RegExp[]; + negativeRe: RegExp[]; + /** Per-segment globs for early pruning of deep traversal. */ + positivePartsRe: RegExp[][]; + hasGlobstar: boolean; + /** Static max depth implied by patterns; Infinity if any contains `**`. */ + maxDepth: number; + dynamic: boolean; +} + +function compileTask(task: Task, settings: Settings): CompiledTask { + const mmOpts = { + dot: settings.dot, + basename: false, + nobrace: !settings.braceExpansion, + nocase: !settings.caseSensitiveMatch, + noext: !settings.extglob, + noglobstar: !settings.globstar, + }; + const positiveRe = pattern.convertPatternsToRe(task.positive, mmOpts); + const negativeRe = pattern.convertPatternsToRe(task.negative, mmOpts); + const positivePartsRe: RegExp[][] = task.positive.map((p) => + pattern.getPatternParts(p, mmOpts).map((part) => + pattern.makeRe(part === "" ? "**" : part, mmOpts), + ), + ); + const hasGlobstar = task.positive.some(pattern.hasGlobStar); + const maxDepth = hasGlobstar + ? Number.POSITIVE_INFINITY + : Math.max( + ...task.positive.map( + (p) => pattern.getPatternParts(p, mmOpts).length, + ), + ); + + return { + base: task.base, + positiveRe, + negativeRe, + positivePartsRe, + hasGlobstar, + maxDepth, + dynamic: task.dynamic, + }; +} + +/** True if any positive pattern could still match something deeper than `depth`. */ +function partialMatch(parts: string[], compiled: CompiledTask): boolean { + if (compiled.hasGlobstar) return true; + for (const patternParts of compiled.positivePartsRe) { + if (patternParts.length <= parts.length) continue; + let ok = true; + for (let i = 0; i < parts.length; i++) { + if (!patternParts[i].test(parts[i])) { + ok = false; + break; + } + } + if (ok) return true; + } + return false; +} + +interface WalkContext { + cwd: string; + base: string; + compiled: CompiledTask; + settings: Settings; +} + +const yieldEntry = (entry: Entry, settings: Settings): string | Entry => { + let path = entry.path; + if (settings.markDirectories && entry.dirent.isDirectory && !path.endsWith("/")) { + path = `${path}/`; + } + if (settings.objectMode) { + return { ...entry, path }; + } + return path; +}; + +const isMatched = ( + relPath: string, + compiled: CompiledTask, + settings: Settings, +): boolean => { + if (settings.dot === false) { + // Reject paths whose any segment starts with `.` unless an explicit positive matched. + for (const seg of relPath.split("/")) { + if (seg.startsWith(".") && seg !== "." && seg !== "..") { + // The pattern itself may include the dot; only filter when no positive Re permits it. + const allowed = compiled.positiveRe.some((re) => re.test(relPath)); + if (!allowed) return false; + break; + } + } + } + if (!pattern.matchAny(relPath, compiled.positiveRe)) return false; + if (pattern.matchAny(relPath, compiled.negativeRe)) return false; + return true; +}; + +/** + * Recursively walk `dir` (relative to `cwd`), emitting matched entries. + */ +function walkDir( + ctx: WalkContext, + relDir: string, + depth: number, +): Stream.Stream { + return Stream.unwrap( + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + + const absDir = relDir === "" ? ctx.base : path.join(ctx.base, relDir); + const fullDir = path.isAbsolute(absDir) + ? absDir + : path.join(ctx.cwd, absDir); + + const namesExit = yield* Effect.exit(fs.readDirectory(fullDir)); + if (namesExit._tag === "Failure") { + if (ctx.settings.suppressErrors) return Stream.empty; + return Stream.failCause(namesExit.cause); + } + + const children: Stream.Stream< + Entry, + unknown, + FileSystem.FileSystem | Path.Path + >[] = []; + + for (const name of namesExit.value) { + if (!ctx.settings.dot && name.startsWith(".")) { + // skip dotfiles up-front unless dot enabled + continue; + } + + const childRel = relDir === "" ? name : `${relDir}/${name}`; + const childFull = path.join(fullDir, name); + + const statExit = yield* Effect.exit(fs.stat(childFull)); + if (statExit._tag === "Failure") { + if (ctx.settings.suppressErrors) continue; + return Stream.failCause(statExit.cause); + } + + const isDir = statExit.value.type === "Directory"; + const isSymlink = statExit.value.type === "SymbolicLink"; + const isFile = statExit.value.type === "File"; + + const entry: Entry = { + name, + path: ctx.settings.absolute ? childFull : childRel, + dirent: { + isFile, + isDirectory: isDir, + isSymbolicLink: isSymlink, + }, + stats: undefined, + }; + + const matchPath = ctx.settings.absolute ? childRel : childRel; + const matched = isMatched(matchPath, ctx.compiled, ctx.settings); + + const include = + matched && + (ctx.settings.onlyFiles ? isFile : true) && + (ctx.settings.onlyDirectories ? isDir : true); + + if (include) children.push(Stream.succeed(entry)); + + // Descend? + if (isDir && depth + 1 < ctx.settings.deep) { + const parts = childRel.split("/"); + if (partialMatch(parts, ctx.compiled)) { + children.push(walkDir(ctx, childRel, depth + 1)); + } + } else if (isSymlink && ctx.settings.followSymbolicLinks) { + // Resolve symlink target + const realExit = yield* Effect.exit(fs.stat(childFull)); + if (realExit._tag === "Success" && realExit.value.type === "Directory") { + const parts = childRel.split("/"); + if (partialMatch(parts, ctx.compiled)) { + children.push(walkDir(ctx, childRel, depth + 1)); + } + } + } + } + + return Stream.flatten(Stream.fromIterable(children), { concurrency: 1 }); + }), + ); +} + +export function runTask( + task: Task, + settings: Settings, +): Stream.Stream { + const compiled = compileTask(task, settings); + const ctx: WalkContext = { + cwd: settings.cwd, + base: task.base, + compiled, + settings, + }; + return Stream.map(walkDir(ctx, "", 0), (e) => yieldEntry(e, settings)); +} diff --git a/packages/node-utils/src/fill-range.ts b/packages/node-utils/src/fill-range.ts new file mode 100644 index 0000000..147f94f --- /dev/null +++ b/packages/node-utils/src/fill-range.ts @@ -0,0 +1,583 @@ +import util from "node:util"; +import { isNumber } from "./is-number.ts"; + +// --------------------------------------------------------------------------- +// to-regex-range (inlined from https://github.com/micromatch/to-regex-range) +// --------------------------------------------------------------------------- + +export interface ToRegexRangeOptions { + relaxZeros?: boolean; + strictZeros?: boolean; + shorthand?: boolean; + capture?: boolean; + wrap?: boolean; +} + +interface ResolvedToRegexRangeOptions { + relaxZeros: boolean; + shorthand?: boolean; + capture?: boolean; + wrap?: boolean; +} + +interface RangeToken { + pattern: string; + count: number[]; + digits: number; + string?: string; +} + +interface RangeState { + min: number; + max: number; + a: number; + b: number; + isPadded?: boolean; + maxLen?: number; + negatives?: RangeToken[]; + positives?: RangeToken[]; + result?: string; +} + +export function toRegexRange( + min: number | string, + max?: number | string, + options?: ToRegexRangeOptions, +): string { + if (isNumber(min) === false) { + throw new TypeError("toRegexRange: expected the first argument to be a number"); + } + + if (max === undefined || min === max) { + return String(min); + } + + if (isNumber(max) === false) { + throw new TypeError("toRegexRange: expected the second argument to be a number."); + } + + const opts: ResolvedToRegexRangeOptions = { relaxZeros: true, ...options }; + if (typeof options?.strictZeros === "boolean") { + opts.relaxZeros = options.strictZeros === false; + } + + const cacheKey = `${min}:${max}=${String(opts.relaxZeros)}${String(opts.shorthand)}${String(opts.capture)}${String(opts.wrap)}`; + if (Object.prototype.hasOwnProperty.call(toRegexRange.cache, cacheKey)) { + return toRegexRange.cache[cacheKey].result!; + } + + const minNum = Number(min); + const maxNum = Number(max); + let a = Math.min(minNum, maxNum); + const b = Math.max(minNum, maxNum); + + if (Math.abs(a - b) === 1) { + const result = `${min}|${max}`; + if (opts.capture) return `(${result})`; + if (opts.wrap === false) return result; + return `(?:${result})`; + } + + const isPadded = hasPadding(String(min)) || hasPadding(String(max)); + const state: RangeState = { min: minNum, max: maxNum, a, b }; + let positives: RangeToken[] = []; + let negatives: RangeToken[] = []; + + if (isPadded) { + state.isPadded = isPadded; + // Preserve original-string padding length when callers passed zero-padded strings. + state.maxLen = String(max).length; + } + + if (a < 0) { + const newMin = b < 0 ? Math.abs(b) : 1; + negatives = splitToPatterns(newMin, Math.abs(a), state, opts); + a = state.a = 0; + } + + if (b >= 0) { + positives = splitToPatterns(a, b, state, opts); + } + + state.negatives = negatives; + state.positives = positives; + state.result = collatePatterns(negatives, positives); + + if (opts.capture === true) { + state.result = `(${state.result})`; + } else if (opts.wrap !== false && positives.length + negatives.length > 1) { + state.result = `(?:${state.result})`; + } + + toRegexRange.cache[cacheKey] = state; + return state.result; +} + +export namespace toRegexRange { + export const cache: Record = {}; + export const clearCache = (): void => { + for (const k of Object.keys(cache)) delete cache[k]; + }; +} + +function collatePatterns(neg: RangeToken[], pos: RangeToken[]): string { + const onlyNegative = filterPatterns(neg, pos, "-", false); + const onlyPositive = filterPatterns(pos, neg, "", false); + const intersected = filterPatterns(neg, pos, "-?", true); + return [...onlyNegative, ...intersected, ...onlyPositive].join("|"); +} + +function splitToRanges(min: number, max: number): number[] { + let nines = 1; + let zeros = 1; + + let stop = countNines(min, nines); + const stops = new Set([max]); + + while (min <= stop && stop <= max) { + stops.add(stop); + nines += 1; + stop = countNines(min, nines); + } + + stop = countZeros(max + 1, zeros) - 1; + + while (min < stop && stop <= max) { + stops.add(stop); + zeros += 1; + stop = countZeros(max + 1, zeros) - 1; + } + + return [...stops].sort((a, b) => (a > b ? 1 : b > a ? -1 : 0)); +} + +function rangeToPattern( + start: string, + stop: string, + options: ResolvedToRegexRangeOptions, +): RangeToken { + if (start === stop) { + return { pattern: start, count: [], digits: 0 }; + } + + const zipped = zip(start, stop); + const digits = zipped.length; + let pattern = ""; + let count = 0; + + for (let i = 0; i < digits; i++) { + const [startDigit, stopDigit] = zipped[i]; + + if (startDigit === stopDigit) { + pattern += startDigit; + } else if (startDigit !== "0" || stopDigit !== "9") { + pattern += toCharacterClass(startDigit, stopDigit); + } else { + count++; + } + } + + if (count) { + pattern += options.shorthand === true ? "\\d" : "[0-9]"; + } + + return { pattern, count: [count], digits }; +} + +function splitToPatterns( + min: number, + max: number, + tok: RangeState, + options: ResolvedToRegexRangeOptions, +): RangeToken[] { + const ranges = splitToRanges(min, max); + const tokens: RangeToken[] = []; + let start = min; + let prev: RangeToken | undefined; + + for (let i = 0; i < ranges.length; i++) { + const maxR = ranges[i]; + const obj = rangeToPattern(String(start), String(maxR), options); + let zerosStr = ""; + + if (!tok.isPadded && prev && prev.pattern === obj.pattern) { + if (prev.count.length > 1) { + prev.count.pop(); + } + prev.count.push(obj.count[0]); + prev.string = prev.pattern + toQuantifier(prev.count); + start = maxR + 1; + continue; + } + + if (tok.isPadded) { + zerosStr = padZeros(maxR, tok, options); + } + + obj.string = zerosStr + obj.pattern + toQuantifier(obj.count); + tokens.push(obj); + start = maxR + 1; + prev = obj; + } + + return tokens; +} + +function filterPatterns( + arr: RangeToken[], + comparison: RangeToken[], + prefix: string, + intersection: boolean, +): string[] { + const result: string[] = []; + for (const ele of arr) { + const { string } = ele; + if (!intersection && !contains(comparison, string!)) result.push(prefix + string); + if (intersection && contains(comparison, string!)) result.push(prefix + string); + } + return result; +} + +function zip(a: string, b: string): [string, string][] { + const arr: [string, string][] = []; + for (let i = 0; i < a.length; i++) arr.push([a[i], b[i]]); + return arr; +} + +function contains(arr: RangeToken[], val: string): boolean { + return arr.some((ele) => ele.string === val); +} + +function countNines(min: number, len: number): number { + return Number(String(min).slice(0, -len) + "9".repeat(len)); +} + +function countZeros(integer: number, zeros: number): number { + return integer - (integer % Math.pow(10, zeros)); +} + +function toQuantifier(digits: number[]): string { + const [start = 0, stop = ""] = digits; + if (stop || start > 1) { + return `{${start}${stop ? `,${stop}` : ""}}`; + } + return ""; +} + +function toCharacterClass(a: string, b: string): string { + return `[${a}${Number(b) - Number(a) === 1 ? "" : "-"}${b}]`; +} + +function hasPadding(str: string): boolean { + return /^-?(0+)\d/.test(str); +} + +function padZeros( + value: number, + tok: RangeState, + options: ResolvedToRegexRangeOptions, +): string { + if (!tok.isPadded) return String(value); + const diff = Math.abs(tok.maxLen! - String(value).length); + const relax = options.relaxZeros !== false; + switch (diff) { + case 0: + return ""; + case 1: + return relax ? "0?" : "0"; + case 2: + return relax ? "0{0,2}" : "00"; + default: + return relax ? `0{0,${diff}}` : `0{${diff}}`; + } +} + +// --------------------------------------------------------------------------- +// fill-range +// --------------------------------------------------------------------------- + +export interface FillRangeOptions { + capture?: boolean; + wrap?: boolean; + toRegex?: boolean; + stringify?: boolean; + strictRanges?: boolean; + strictZeros?: boolean; + shorthand?: boolean; + step?: number | string; + transform?: (value: number | string, index: number) => string | number; +} + +type FillRangeReturn = (string | number)[] | string; + +const isObject = (val: unknown): val is Record => + val !== null && typeof val === "object" && !Array.isArray(val); + +const transform = + (toNumber: boolean) => + (value: number | string): number | string => + toNumber === true ? Number(value) : String(value); + +const isValidValue = (value: unknown): value is number | string => + typeof value === "number" || (typeof value === "string" && value !== ""); + +const isInt = (num: unknown): boolean => Number.isInteger(+(num as number)); + +const hasLeadingZeros = (input: number | string): boolean => { + let value = `${input}`; + let index = -1; + if (value[0] === "-") value = value.slice(1); + if (value === "0") return false; + while (value[++index] === "0"); + return index > 0; +}; + +const shouldStringify = ( + start: number | string, + end: number | string, + options: FillRangeOptions, +): boolean => { + if (typeof start === "string" || typeof end === "string") return true; + return options.stringify === true; +}; + +const pad = ( + input: string | number, + maxLength: number, + toNumber: boolean, +): string | number => { + if (maxLength > 0) { + let str = String(input); + const dash = str[0] === "-" ? "-" : ""; + if (dash) str = str.slice(1); + str = dash + str.padStart(dash ? maxLength - 1 : maxLength, "0"); + if (toNumber === false) return str; + return str; + } + if (toNumber === false) return String(input); + return input; +}; + +const toMaxLen = (input: string, maxLength: number): string => { + let negative = input[0] === "-" ? "-" : ""; + if (negative) { + input = input.slice(1); + maxLength--; + } + while (input.length < maxLength) input = "0" + input; + return negative ? "-" + input : input; +}; + +interface Parts { + negatives: number[]; + positives: number[]; +} + +const toSequence = ( + parts: Parts, + options: FillRangeOptions, + maxLen: number, +): string => { + parts.negatives.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); + parts.positives.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); + + const prefix = options.capture ? "" : "?:"; + let positives = ""; + let negatives = ""; + let result: string; + + if (parts.positives.length) { + positives = parts.positives.map((v) => toMaxLen(String(v), maxLen)).join("|"); + } + if (parts.negatives.length) { + negatives = `-(${prefix}${parts.negatives.map((v) => toMaxLen(String(v), maxLen)).join("|")})`; + } + if (positives && negatives) result = `${positives}|${negatives}`; + else result = positives || negatives; + + if (options.wrap) return `(${prefix}${result})`; + return result; +}; + +const toRange = ( + a: number | string, + b: number | string, + isNumbers: boolean, + options: FillRangeOptions, +): string => { + if (isNumbers) return toRegexRange(a, b, { wrap: false, ...options }); + + const start = String.fromCharCode(Number(a)); + if (a === b) return start; + const stop = String.fromCharCode(Number(b)); + return `[${start}-${stop}]`; +}; + +const toRegex = ( + start: (string | number)[] | number | string, + end: number | string | null, + options: FillRangeOptions, +): string => { + if (Array.isArray(start)) { + const wrap = options.wrap === true; + const prefix = options.capture ? "" : "?:"; + return wrap ? `(${prefix}${start.join("|")})` : start.join("|"); + } + return toRegexRange(start, end ?? undefined, options); +}; + +const rangeError = (args: unknown): RangeError => + new RangeError("Invalid range arguments: " + util.inspect(args)); + +const invalidRange = ( + start: unknown, + end: unknown, + options: FillRangeOptions, +): string[] => { + if (options.strictRanges === true) throw rangeError([start, end]); + return []; +}; + +const invalidStep = (step: unknown, options: FillRangeOptions): string[] => { + if (options.strictRanges === true) { + throw new TypeError(`Expected step "${step}" to be a number`); + } + return []; +}; + +const fillNumbers = ( + start: number | string, + end: number | string, + step: number | string = 1, + options: FillRangeOptions = {}, +): FillRangeReturn => { + let a = Number(start); + let b = Number(end); + + if (!Number.isInteger(a) || !Number.isInteger(b)) { + if (options.strictRanges === true) throw rangeError([start, end]); + return []; + } + + if (a === 0) a = 0; + if (b === 0) b = 0; + + const descending = a > b; + const startString = String(start); + const endString = String(end); + const stepString = String(step); + const stepNum = Math.max(Math.abs(Number(step)), 1); + + const padded = hasLeadingZeros(startString) || hasLeadingZeros(endString) || hasLeadingZeros(stepString); + const maxLen = padded + ? Math.max(startString.length, endString.length, stepString.length) + : 0; + const toNumber = padded === false && shouldStringify(start, end, options) === false; + const format = options.transform || transform(toNumber); + + if (options.toRegex && stepNum === 1) { + return toRange( + toMaxLen(String(start), maxLen), + toMaxLen(String(end), maxLen), + true, + options, + ); + } + + const parts: Parts = { negatives: [], positives: [] }; + const push = (num: number) => + parts[num < 0 ? "negatives" : "positives"].push(Math.abs(num)); + const range: (string | number)[] = []; + let index = 0; + + while (descending ? a >= b : a <= b) { + if (options.toRegex === true && stepNum > 1) { + push(a); + } else { + range.push(pad(format(a, index), maxLen, toNumber)); + } + a = descending ? a - stepNum : a + stepNum; + index++; + } + + if (options.toRegex === true) { + return stepNum > 1 + ? toSequence(parts, options, maxLen) + : toRegex(range, null, { wrap: false, ...options }); + } + + return range; +}; + +const fillLetters = ( + start: number | string, + end: number | string, + step: number = 1, + options: FillRangeOptions = {}, +): FillRangeReturn => { + if ( + (!isInt(start) && String(start).length > 1) || + (!isInt(end) && String(end).length > 1) + ) { + return invalidRange(start, end, options); + } + + const format = + options.transform || + ((val: number | string) => String.fromCharCode(Number(val))); + let a = `${start}`.charCodeAt(0); + const b = `${end}`.charCodeAt(0); + + const descending = a > b; + const min = Math.min(a, b); + const max = Math.max(a, b); + + if (options.toRegex && step === 1) return toRange(min, max, false, options); + + const range: string[] = []; + let index = 0; + + while (descending ? a >= b : a <= b) { + range.push(String(format(a, index))); + a = descending ? a - step : a + step; + index++; + } + + if (options.toRegex === true) return toRegex(range, null, { wrap: false, ...options }); + return range; +}; + +export function fill( + start: number | string, + end?: number | string | null, + step?: + | number + | string + | FillRangeOptions + | ((v: number | string, i: number) => string | number), + options: FillRangeOptions = {}, +): FillRangeReturn { + if (end == null && isValidValue(start)) return [String(start)]; + + if (!isValidValue(start) || !isValidValue(end)) { + return invalidRange(start, end, options); + } + + if (typeof step === "function") return fill(start, end, 1, { transform: step }); + + if (isObject(step)) return fill(start, end, 0, step as FillRangeOptions); + + const opts: FillRangeOptions = { ...options }; + if (opts.capture === true) opts.wrap = true; + const s = (step as number | string) || opts.step || 1; + + if (!isInt(s)) { + if (s != null && !isObject(s)) return invalidStep(s, opts); + return fill(start, end, 1, s as FillRangeOptions); + } + + if (isInt(start) && isInt(end)) return fillNumbers(start, end, s, opts); + return fillLetters(start, end, Math.max(Math.abs(Number(s)), 1), opts); +} + +export default fill; diff --git a/packages/node-utils/src/is-number.ts b/packages/node-utils/src/is-number.ts new file mode 100644 index 0000000..c631259 --- /dev/null +++ b/packages/node-utils/src/is-number.ts @@ -0,0 +1,11 @@ +export function isNumber(num: unknown): boolean { + if (typeof num === "number") { + return num - num === 0; + } + if (typeof num === "string" && num.trim() !== "") { + return Number.isFinite(+num); + } + return false; +} + +export default isNumber; diff --git a/packages/node-utils/src/micromatch/index.ts b/packages/node-utils/src/micromatch/index.ts new file mode 100644 index 0000000..e2ce97f --- /dev/null +++ b/packages/node-utils/src/micromatch/index.ts @@ -0,0 +1,277 @@ +import util from "node:util"; +import { braces as _braces } from "../braces/index.ts"; +import { picomatch } from "../picomatch/index.ts"; +import * as utils from "../picomatch/utils.ts"; + +const isEmptyString = (v: string): boolean => v === "" || v === "./"; +const hasBraces = (v: string): boolean => { + const index = v.indexOf("{"); + return index > -1 && v.indexOf("}", index) > -1; +}; + +export interface MicromatchOptions { + onResult?: (state: any) => void; + failglob?: boolean; + nonull?: boolean; + nullglob?: boolean; + unescape?: boolean; + nobrace?: boolean; + expand?: boolean; + contains?: boolean; + capture?: boolean; + [k: string]: unknown; +} + +export function micromatch( + list: string | string[], + patterns: string | string[], + options?: MicromatchOptions, +): string[] { + patterns = ([] as string[]).concat(patterns); + list = ([] as string[]).concat(list); + + const omit: Set = new Set(); + const keep: Set = new Set(); + const items: Set = new Set(); + let negatives = 0; + + const onResult = (state: any) => { + items.add(state.output); + if (options && options.onResult) options.onResult(state); + }; + + // Match upstream: if running on (or simulating) Windows, default windows mode on. + const resolvedOpts = { + windows: utils.isWindows(), + ...options, + onResult, + }; + + for (let i = 0; i < patterns.length; i++) { + const isMatch = picomatch( + String(patterns[i]), + resolvedOpts as any, + true, + ); + const negated = + (isMatch as any).state.negated || (isMatch as any).state.negatedExtglob; + if (negated) negatives++; + + for (const item of list) { + const matched = (isMatch as any)(item, true); + + const match = negated ? !matched.isMatch : matched.isMatch; + if (!match) continue; + + if (negated) { + omit.add(matched.output); + } else { + omit.delete(matched.output); + keep.add(matched.output); + } + } + } + + const result = negatives === patterns.length ? [...items] : [...keep]; + const matches = result.filter((item) => !omit.has(item)); + + if (options && matches.length === 0) { + if (options.failglob === true) { + throw new Error(`No matches found for "${(patterns as string[]).join(", ")}"`); + } + + if (options.nonull === true || options.nullglob === true) { + return options.unescape + ? (patterns as string[]).map((p) => p.replace(/\\/g, "")) + : (patterns as string[]); + } + } + + return matches; +} + +export namespace micromatch { + export const match = micromatch; + + export const matcher = (pattern: string, options?: MicromatchOptions): any => + picomatch(pattern, options as any); + + export function isMatch( + str: string, + patterns: string | string[], + options?: MicromatchOptions, + ): boolean { + const opts = { windows: utils.isWindows(), ...options }; + return picomatch(patterns, opts as any)(str) as boolean; + } + + export const any = isMatch; + + export function not( + list: string | string[], + patterns: string | string[], + options: MicromatchOptions = {}, + ): string[] { + patterns = ([] as string[]).concat(patterns).map(String); + const result: Set = new Set(); + const items: string[] = []; + + const onResult = (state: any) => { + if (options.onResult) options.onResult(state); + items.push(state.output); + }; + + const matches: Set = new Set( + micromatch(list, patterns, { ...options, onResult }), + ); + + for (const item of items) { + if (!matches.has(item)) result.add(item); + } + return [...result]; + } + + export function contains( + str: string, + pattern: string | string[], + options?: MicromatchOptions, + ): boolean { + if (typeof str !== "string") { + throw new TypeError(`Expected a string: "${util.inspect(str)}"`); + } + + if (Array.isArray(pattern)) { + return pattern.some((p) => contains(str, p, options)); + } + + if (typeof pattern === "string") { + if (isEmptyString(str) || isEmptyString(pattern)) return false; + if ( + str.includes(pattern) || + (str.startsWith("./") && str.slice(2).includes(pattern)) + ) { + return true; + } + } + + return isMatch(str, pattern, { ...options, contains: true }); + } + + export function matchKeys( + obj: Record, + patterns: string | string[], + options?: MicromatchOptions, + ): Record { + if (!utils.isObject(obj)) { + throw new TypeError("Expected the first argument to be an object"); + } + const keys = micromatch(Object.keys(obj), patterns, options); + const res: Record = {}; + for (const key of keys) res[key] = obj[key]; + return res; + } + + export function some( + list: string | string[], + patterns: string | string[], + options?: MicromatchOptions, + ): boolean { + const items = ([] as string[]).concat(list); + for (const pattern of ([] as string[]).concat(patterns)) { + const isMatchFn = picomatch(String(pattern), options as any); + if (items.some((item) => (isMatchFn as any)(item))) return true; + } + return false; + } + + export function every( + list: string | string[], + patterns: string | string[], + options?: MicromatchOptions, + ): boolean { + const items = ([] as string[]).concat(list); + for (const pattern of ([] as string[]).concat(patterns)) { + const isMatchFn = picomatch(String(pattern), options as any); + if (!items.every((item) => (isMatchFn as any)(item))) return false; + } + return true; + } + + export function all( + str: string, + patterns: string | string[], + options?: MicromatchOptions, + ): boolean { + if (typeof str !== "string") { + throw new TypeError(`Expected a string: "${util.inspect(str)}"`); + } + return ([] as string[]) + .concat(patterns) + .every((p) => (picomatch(p, options as any) as any)(str)); + } + + export function capture( + glob: string, + input: string, + options?: MicromatchOptions, + ): string[] | undefined { + const posix = utils.isWindows(); + const regex = picomatch.makeRe(String(glob), { + ...options, + capture: true, + } as any); + const match = regex.exec(posix ? utils.toPosixSlashes(input) : input); + + if (match) { + return match.slice(1).map((v) => (v === undefined ? "" : v)); + } + return undefined; + } + + export const makeRe = ( + pattern: string, + options?: MicromatchOptions, + ): RegExp => picomatch.makeRe(pattern, options as any); + + export const scan = (pattern: string, options?: any) => + picomatch.scan(pattern, options); + + export function parse( + patterns: string | string[], + options?: MicromatchOptions, + ): any[] { + const res: any[] = []; + for (const pattern of ([] as string[]).concat(patterns || [])) { + for (const str of _braces(String(pattern), options as any)) { + res.push(picomatch.parse(str, options as any)); + } + } + return res; + } + + export function braces( + pattern: string, + options?: MicromatchOptions, + ): string[] { + if (typeof pattern !== "string") throw new TypeError("Expected a string"); + if ((options && options.nobrace === true) || !hasBraces(pattern)) { + return [pattern]; + } + return _braces(pattern, options as any); + } + + export function braceExpand( + pattern: string, + options?: MicromatchOptions, + ): string[] { + if (typeof pattern !== "string") throw new TypeError("Expected a string"); + return braces(pattern, { ...options, expand: true }); + } + + export const hasBraces: (v: string) => boolean = (v) => { + const index = v.indexOf("{"); + return index > -1 && v.indexOf("}", index) > -1; + }; +} + +export default micromatch; diff --git a/packages/node-utils/src/picomatch/constants.ts b/packages/node-utils/src/picomatch/constants.ts new file mode 100644 index 0000000..5f0882a --- /dev/null +++ b/packages/node-utils/src/picomatch/constants.ts @@ -0,0 +1,178 @@ +const WIN_SLASH = "\\\\/"; +const WIN_NO_SLASH = `[^${WIN_SLASH}]`; + +export const DEFAULT_MAX_EXTGLOB_RECURSION = 0; + +const DOT_LITERAL = "\\."; +const PLUS_LITERAL = "\\+"; +const QMARK_LITERAL = "\\?"; +const SLASH_LITERAL = "\\/"; +const ONE_CHAR = "(?=.)"; +const QMARK = "[^/]"; +const END_ANCHOR = `(?:${SLASH_LITERAL}|$)`; +const START_ANCHOR = `(?:^|${SLASH_LITERAL})`; +const DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`; +const NO_DOT = `(?!${DOT_LITERAL})`; +const NO_DOTS = `(?!${START_ANCHOR}${DOTS_SLASH})`; +const NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`; +const NO_DOTS_SLASH = `(?!${DOTS_SLASH})`; +const QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`; +const STAR = `${QMARK}*?`; +const SEP = "/"; + +export interface GlobChars { + DOT_LITERAL: string; + PLUS_LITERAL: string; + QMARK_LITERAL: string; + SLASH_LITERAL: string; + ONE_CHAR: string; + QMARK: string; + END_ANCHOR: string; + DOTS_SLASH: string; + NO_DOT: string; + NO_DOTS: string; + NO_DOT_SLASH: string; + NO_DOTS_SLASH: string; + QMARK_NO_DOT: string; + STAR: string; + START_ANCHOR: string; + SEP: string; +} + +const POSIX_CHARS: GlobChars = { + DOT_LITERAL, + PLUS_LITERAL, + QMARK_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + QMARK, + END_ANCHOR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK_NO_DOT, + STAR, + START_ANCHOR, + SEP, +}; + +const WINDOWS_CHARS: GlobChars = { + ...POSIX_CHARS, + + SLASH_LITERAL: `[${WIN_SLASH}]`, + QMARK: WIN_NO_SLASH, + STAR: `${WIN_NO_SLASH}*?`, + DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`, + NO_DOT: `(?!${DOT_LITERAL})`, + NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`, + NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + QMARK_NO_DOT: `[^.${WIN_SLASH}]`, + START_ANCHOR: `(?:^|[${WIN_SLASH}])`, + END_ANCHOR: `(?:[${WIN_SLASH}]|$)`, + SEP: "\\", +}; + +export const POSIX_REGEX_SOURCE: Record = Object.assign( + Object.create(null), + { + alnum: "a-zA-Z0-9", + alpha: "a-zA-Z", + ascii: "\\x00-\\x7F", + blank: " \\t", + cntrl: "\\x00-\\x1F\\x7F", + digit: "0-9", + graph: "\\x21-\\x7E", + lower: "a-z", + print: "\\x20-\\x7E ", + punct: "\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~", + space: " \\t\\r\\n\\v\\f", + upper: "A-Z", + word: "A-Za-z0-9_", + xdigit: "A-Fa-f0-9", + }, +); + +export const MAX_LENGTH = 1024 * 64; + +export const REGEX_BACKSLASH = /\\(?![*+?^${}(|)[\]])/g; +export const REGEX_NON_SPECIAL_CHARS = /^[^@![\].,$*+?^{}()|\\/]+/; +export const REGEX_SPECIAL_CHARS = /[-*+?.^${}(|)[\]]/; +export const REGEX_SPECIAL_CHARS_BACKREF = /(\\?)((\W)(\3*))/g; +export const REGEX_SPECIAL_CHARS_GLOBAL = /([-*+?.^${}(|)[\]])/g; +export const REGEX_REMOVE_BACKSLASH = /(?:\[.*?[^\\]\]|\\(?=.))/g; + +export const REPLACEMENTS: Record = Object.assign( + Object.create(null), + { + "***": "*", + "**/**": "**", + "**/**/**": "**", + }, +); + +export const CHAR_0 = 48; +export const CHAR_9 = 57; +export const CHAR_UPPERCASE_A = 65; +export const CHAR_LOWERCASE_A = 97; +export const CHAR_UPPERCASE_Z = 90; +export const CHAR_LOWERCASE_Z = 122; +export const CHAR_LEFT_PARENTHESES = 40; +export const CHAR_RIGHT_PARENTHESES = 41; +export const CHAR_ASTERISK = 42; +export const CHAR_AMPERSAND = 38; +export const CHAR_AT = 64; +export const CHAR_BACKWARD_SLASH = 92; +export const CHAR_CARRIAGE_RETURN = 13; +export const CHAR_CIRCUMFLEX_ACCENT = 94; +export const CHAR_COLON = 58; +export const CHAR_COMMA = 44; +export const CHAR_DOT = 46; +export const CHAR_DOUBLE_QUOTE = 34; +export const CHAR_EQUAL = 61; +export const CHAR_EXCLAMATION_MARK = 33; +export const CHAR_FORM_FEED = 12; +export const CHAR_FORWARD_SLASH = 47; +export const CHAR_GRAVE_ACCENT = 96; +export const CHAR_HASH = 35; +export const CHAR_HYPHEN_MINUS = 45; +export const CHAR_LEFT_ANGLE_BRACKET = 60; +export const CHAR_LEFT_CURLY_BRACE = 123; +export const CHAR_LEFT_SQUARE_BRACKET = 91; +export const CHAR_LINE_FEED = 10; +export const CHAR_NO_BREAK_SPACE = 160; +export const CHAR_PERCENT = 37; +export const CHAR_PLUS = 43; +export const CHAR_QUESTION_MARK = 63; +export const CHAR_RIGHT_ANGLE_BRACKET = 62; +export const CHAR_RIGHT_CURLY_BRACE = 125; +export const CHAR_RIGHT_SQUARE_BRACKET = 93; +export const CHAR_SEMICOLON = 59; +export const CHAR_SINGLE_QUOTE = 39; +export const CHAR_SPACE = 32; +export const CHAR_TAB = 9; +export const CHAR_UNDERSCORE = 95; +export const CHAR_VERTICAL_LINE = 124; +export const CHAR_ZERO_WIDTH_NOBREAK_SPACE = 65279; + +export interface ExtglobChar { + type: string; + open: string; + close: string; +} + +export function extglobChars(chars: GlobChars): Record { + return { + "!": { type: "negate", open: "(?:(?!(?:", close: `))${chars.STAR})` }, + "?": { type: "qmark", open: "(?:", close: ")?" }, + "+": { type: "plus", open: "(?:", close: ")+" }, + "*": { type: "star", open: "(?:", close: ")*" }, + "@": { type: "at", open: "(?:", close: ")" }, + }; +} + +export function globChars(win32?: boolean): GlobChars { + return win32 === true ? WINDOWS_CHARS : POSIX_CHARS; +} diff --git a/packages/node-utils/src/picomatch/index.ts b/packages/node-utils/src/picomatch/index.ts new file mode 100644 index 0000000..2ce211c --- /dev/null +++ b/packages/node-utils/src/picomatch/index.ts @@ -0,0 +1,247 @@ +import { scan as _scan } from "./scan.ts"; +import { parse as _parse } from "./parse.ts"; +import * as utils from "./utils.ts"; +import * as _constants from "./constants.ts"; +import type { ScanOptions, ScanState } from "./scan.ts"; +import type { ParseOptions } from "./parse.ts"; + +const isObject = (val: unknown): val is Record => + !!val && typeof val === "object" && !Array.isArray(val); + +export interface PicomatchOptions extends ParseOptions { + ignore?: string | string[] | ParsedState; + onMatch?: (result: MatchResult) => void; + onResult?: (result: MatchResult) => void; + onIgnore?: (result: MatchResult) => void; + matchBase?: boolean; + basename?: boolean; + flags?: string; + nocase?: boolean; + debug?: boolean; + format?: (input: string) => string; +} + +export interface MatchResult { + glob: string | string[] | ParsedState; + state: any; + regex: RegExp; + posix?: boolean; + input: string; + output: string; + match: boolean | RegExpExecArray | null; + isMatch: boolean; +} + +type ParsedState = any; + +export type Matcher = (( + input: string, + returnObject?: boolean, +) => boolean | MatchResult) & { state?: any }; + +export function picomatch( + glob: string | string[] | ParsedState, + options?: PicomatchOptions, + returnState = false, +): Matcher { + if (Array.isArray(glob)) { + const fns = glob.map((input) => picomatch(input, options, returnState)); + const arrayMatcher: Matcher = ((str: string) => { + for (const isMatch of fns) { + const state = (isMatch as any)(str); + if (state) return state; + } + return false; + }) as Matcher; + return arrayMatcher; + } + + const isState = isObject(glob) && (glob as any).tokens && (glob as any).input; + + if (glob === "" || (typeof glob !== "string" && !isState)) { + throw new TypeError("Expected pattern to be a non-empty string"); + } + + const opts = options || {}; + const posix = (opts as any).windows; + const regex = isState + ? picomatch.compileRe(glob as ParsedState, options) + : picomatch.makeRe(glob as string, options, false, true); + + const state = (regex as any).state; + delete (regex as any).state; + + let isIgnored: (input: string) => boolean = () => false; + if (opts.ignore) { + const ignoreOpts = { + ...options, + ignore: undefined, + onMatch: undefined, + onResult: undefined, + } as PicomatchOptions; + isIgnored = picomatch(opts.ignore as any, ignoreOpts, returnState) as any; + } + + const matcher: Matcher = ((input: string, returnObject = false) => { + const { isMatch, match, output } = picomatch.test(input, regex, options, { + glob: glob as any, + posix, + }); + const result: MatchResult = { + glob: glob as any, + state, + regex, + posix, + input, + output, + match, + isMatch, + }; + + if (typeof opts.onResult === "function") opts.onResult(result); + + if (isMatch === false) { + result.isMatch = false; + return returnObject ? result : false; + } + + if (isIgnored(input)) { + if (typeof opts.onIgnore === "function") opts.onIgnore(result); + result.isMatch = false; + return returnObject ? result : false; + } + + if (typeof opts.onMatch === "function") opts.onMatch(result); + return returnObject ? result : true; + }) as Matcher; + + if (returnState) matcher.state = state; + + return matcher; +} + +export namespace picomatch { + export function parse( + pattern: string | string[], + options?: PicomatchOptions, + ): any { + if (Array.isArray(pattern)) return pattern.map((p) => parse(p, options)); + return _parse(pattern, { ...options, fastpaths: false }); + } + export const scan = _scan; + export const constants = _constants; + + export interface TestContext { + glob?: string | ParsedState; + posix?: boolean; + } + + export function test( + input: string, + regex: RegExp, + options?: PicomatchOptions, + { glob, posix }: TestContext = {}, + ): { isMatch: boolean; match: boolean | RegExpExecArray | null; output: string } { + if (typeof input !== "string") { + throw new TypeError("Expected input to be a string"); + } + + if (input === "") return { isMatch: false, match: false, output: "" }; + + const opts = options || {}; + const format = opts.format || (posix ? utils.toPosixSlashes : null); + let match: boolean | RegExpExecArray | null = input === glob; + let output = match && format ? format(input) : input; + + if (match === false) { + output = format ? format(input) : input; + match = output === glob; + } + + if (match === false || opts.capture === true) { + if (opts.matchBase === true || opts.basename === true) { + match = matchBase(input, regex, options, posix); + } else { + match = regex.exec(output); + } + } + + return { isMatch: Boolean(match), match, output }; + } + + export function matchBase( + input: string, + glob: RegExp | string, + options?: PicomatchOptions, + _posix?: boolean, + ): boolean { + const regex = glob instanceof RegExp ? glob : makeRe(glob, options); + return regex.test(utils.basename(input)); + } + + export function isMatch( + str: string, + patterns: string | string[], + options?: PicomatchOptions, + ): boolean | MatchResult { + return picomatch(patterns, options)(str); + } + + export function compileRe( + state: ParsedState, + options?: PicomatchOptions, + returnOutput = false, + returnState = false, + ): RegExp { + if (returnOutput === true) return (state as any).output; + + const opts = options || {}; + const prepend = opts.contains ? "" : "^"; + const append = opts.contains ? "" : "$"; + + let source = `${prepend}(?:${(state as any).output})${append}`; + if (state && (state as any).negated === true) { + source = `^(?!${source}).*$`; + } + + const regex = toRegex(source, options); + if (returnState === true) (regex as any).state = state; + + return regex; + } + + export function makeRe( + input: string, + options: PicomatchOptions = {}, + returnOutput = false, + returnState = false, + ): RegExp { + if (!input || typeof input !== "string") { + throw new TypeError("Expected a non-empty string"); + } + + let parsed: ParsedState = { negated: false, fastpaths: true }; + + if (options.fastpaths !== false && (input[0] === "." || input[0] === "*")) { + parsed.output = _parse.fastpaths(input, options); + } + + if (!parsed.output) { + parsed = _parse(input, options); + } + + return compileRe(parsed, options, returnOutput, returnState); + } + + export function toRegex(source: string, options?: PicomatchOptions): RegExp { + try { + const opts = options || {}; + return new RegExp(source, opts.flags || (opts.nocase ? "i" : "")); + } catch (err) { + if (options && options.debug === true) throw err; + return /$^/; + } + } +} + +export default picomatch; diff --git a/packages/node-utils/src/picomatch/parse.ts b/packages/node-utils/src/picomatch/parse.ts new file mode 100644 index 0000000..0f52c45 --- /dev/null +++ b/packages/node-utils/src/picomatch/parse.ts @@ -0,0 +1,1240 @@ +import * as constants from "./constants.ts"; +import * as utils from "./utils.ts"; + +const { + MAX_LENGTH, + POSIX_REGEX_SOURCE, + REGEX_NON_SPECIAL_CHARS, + REGEX_SPECIAL_CHARS_BACKREF, + REPLACEMENTS, +} = constants; + +export interface ParseOptions { + maxLength?: number; + prepend?: string; + capture?: boolean; + windows?: boolean; + dot?: boolean; + bash?: boolean; + noext?: boolean; + noextglob?: boolean; + nonegate?: boolean; + nobracket?: boolean; + nobrace?: boolean; + noglobstar?: boolean; + strictBrackets?: boolean; + strictSlashes?: boolean; + unescape?: boolean; + keepQuotes?: boolean; + literalBrackets?: boolean; + posix?: boolean; + regex?: boolean; + fastpaths?: boolean; + contains?: boolean; + expandRange?: ( + a: string, + b: string, + options: ParseOptions, + ) => string; + maxExtglobRecursion?: number | false; + [k: string]: unknown; +} + +type Token = any; +type State = any; + +const expandRange = (args: string[], options: ParseOptions): string => { + if (typeof options.expandRange === "function") { + return (options.expandRange as any)(...args, options); + } + + args.sort(); + const value = `[${args.join("-")}]`; + + try { + new RegExp(value); + } catch { + return args.map((v) => utils.escapeRegex(v)).join(".."); + } + + return value; +}; + +const syntaxError = (type: string, char: string): string => + `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`; + +const splitTopLevel = (input: string): string[] => { + const parts: string[] = []; + let bracket = 0; + let paren = 0; + let quote = 0; + let value = ""; + let escaped = false; + + for (const ch of input) { + if (escaped === true) { + value += ch; + escaped = false; + continue; + } + if (ch === "\\") { + value += ch; + escaped = true; + continue; + } + if (ch === '"') { + quote = quote === 1 ? 0 : 1; + value += ch; + continue; + } + if (quote === 0) { + if (ch === "[") bracket++; + else if (ch === "]" && bracket > 0) bracket--; + else if (bracket === 0) { + if (ch === "(") paren++; + else if (ch === ")" && paren > 0) paren--; + else if (ch === "|" && paren === 0) { + parts.push(value); + value = ""; + continue; + } + } + } + value += ch; + } + parts.push(value); + return parts; +}; + +const isPlainBranch = (branch: string): boolean => { + let escaped = false; + for (const ch of branch) { + if (escaped === true) { + escaped = false; + continue; + } + if (ch === "\\") { + escaped = true; + continue; + } + if (/[?*+@!()[\]{}]/.test(ch)) return false; + } + return true; +}; + +const normalizeSimpleBranch = (branch: string): string | undefined => { + let value = branch.trim(); + let changed = true; + while (changed === true) { + changed = false; + if (/^@\([^\\()[\]{}|]+\)$/.test(value)) { + value = value.slice(2, -1); + changed = true; + } + } + if (!isPlainBranch(value)) return; + return value.replace(/\\(.)/g, "$1"); +}; + +const hasRepeatedCharPrefixOverlap = (branches: string[]): boolean => { + const values = branches.map(normalizeSimpleBranch).filter(Boolean) as string[]; + + for (let i = 0; i < values.length; i++) { + for (let j = i + 1; j < values.length; j++) { + const a = values[i]; + const b = values[j]; + const char = a[0]; + if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) continue; + if (a === b || a.startsWith(b) || b.startsWith(a)) return true; + } + } + return false; +}; + +interface ExtglobMatch { + type: string; + body: string; + end: number; +} + +const parseRepeatedExtglob = ( + pattern: string, + requireEnd = true, +): ExtglobMatch | undefined => { + if ((pattern[0] !== "+" && pattern[0] !== "*") || pattern[1] !== "(") return; + + let bracket = 0; + let paren = 0; + let quote = 0; + let escaped = false; + + for (let i = 1; i < pattern.length; i++) { + const ch = pattern[i]; + if (escaped === true) { + escaped = false; + continue; + } + if (ch === "\\") { + escaped = true; + continue; + } + if (ch === '"') { + quote = quote === 1 ? 0 : 1; + continue; + } + if (quote === 1) continue; + + if (ch === "[") { + bracket++; + continue; + } + if (ch === "]" && bracket > 0) { + bracket--; + continue; + } + if (bracket > 0) continue; + if (ch === "(") { + paren++; + continue; + } + if (ch === ")") { + paren--; + if (paren === 0) { + if (requireEnd === true && i !== pattern.length - 1) return; + return { type: pattern[0], body: pattern.slice(2, i), end: i }; + } + } + } +}; + +const getStarExtglobSequenceOutput = (pattern: string): string | undefined => { + let index = 0; + const chars: string[] = []; + + while (index < pattern.length) { + const match = parseRepeatedExtglob(pattern.slice(index), false); + if (!match || match.type !== "*") return; + + const branches = splitTopLevel(match.body).map((b) => b.trim()); + if (branches.length !== 1) return; + + const branch = normalizeSimpleBranch(branches[0]); + if (!branch || branch.length !== 1) return; + + chars.push(branch); + index += match.end + 1; + } + + if (chars.length < 1) return; + + const source = + chars.length === 1 + ? utils.escapeRegex(chars[0]) + : `[${chars.map((ch) => utils.escapeRegex(ch)).join("")}]`; + + return `${source}*`; +}; + +const repeatedExtglobRecursion = (pattern: string): number => { + let depth = 0; + let value = pattern.trim(); + let match = parseRepeatedExtglob(value); + while (match) { + depth++; + value = match.body.trim(); + match = parseRepeatedExtglob(value); + } + return depth; +}; + +interface ExtglobAnalysis { + risky: boolean; + safeOutput?: string; +} + +const analyzeRepeatedExtglob = ( + body: string, + options: ParseOptions, +): ExtglobAnalysis => { + if (options.maxExtglobRecursion === false) return { risky: false }; + + const max = + typeof options.maxExtglobRecursion === "number" + ? options.maxExtglobRecursion + : constants.DEFAULT_MAX_EXTGLOB_RECURSION; + + const branches = splitTopLevel(body).map((b) => b.trim()); + + if (branches.length > 1) { + if ( + branches.some((branch) => branch === "") || + branches.some((branch) => /^[*?]+$/.test(branch)) || + hasRepeatedCharPrefixOverlap(branches) + ) { + return { risky: true }; + } + } + + for (const branch of branches) { + const safeOutput = getStarExtglobSequenceOutput(branch); + if (safeOutput) return { risky: true, safeOutput }; + if (repeatedExtglobRecursion(branch) > max) return { risky: true }; + } + + return { risky: false }; +}; + +export function parse(input: string, options?: ParseOptions): State { + if (typeof input !== "string") { + throw new TypeError("Expected a string"); + } + + input = REPLACEMENTS[input] || input; + + const opts: ParseOptions = { ...options }; + const max = + typeof opts.maxLength === "number" + ? Math.min(MAX_LENGTH, opts.maxLength) + : MAX_LENGTH; + + let len = input.length; + if (len > max) { + throw new SyntaxError( + `Input length: ${len}, exceeds maximum allowed length: ${max}`, + ); + } + + const bos: Token = { type: "bos", value: "", output: opts.prepend || "" }; + const tokens: Token[] = [bos]; + + const capture = opts.capture ? "" : "?:"; + + const PLATFORM_CHARS = constants.globChars(opts.windows); + const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS); + + const { + DOT_LITERAL, + PLUS_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK, + QMARK_NO_DOT, + STAR, + START_ANCHOR, + } = PLATFORM_CHARS; + + const globstar = (o: ParseOptions): string => + `(${capture}(?:(?!${START_ANCHOR}${o.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + + const nodot = opts.dot ? "" : NO_DOT; + const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT; + let star = opts.bash === true ? globstar(opts) : STAR; + + if (opts.capture) star = `(${star})`; + + if (typeof opts.noext === "boolean") { + opts.noextglob = opts.noext; + } + + const state: State = { + input, + index: -1, + start: 0, + dot: opts.dot === true, + consumed: "", + output: "", + prefix: "", + backtrack: false, + negated: false, + brackets: 0, + braces: 0, + parens: 0, + quotes: 0, + globstar: false, + tokens, + }; + + input = utils.removePrefix(input, state); + len = input.length; + + const extglobs: Token[] = []; + const braces: Token[] = []; + const stack: string[] = []; + let prev: Token = bos; + let value: string; + + const eos = () => state.index === len - 1; + const peek = (state.peek = (n = 1) => input[state.index + n]); + const advance = (state.advance = () => input[++state.index] || ""); + const remaining = () => input.slice(state.index + 1); + const consume = (v: string = "", num = 0) => { + state.consumed += v; + state.index += num; + }; + + const append = (token: Token) => { + state.output += token.output != null ? token.output : token.value; + consume(token.value); + }; + + const negate = (): boolean => { + let count = 1; + + while (peek() === "!" && (peek(2) !== "(" || peek(3) === "?")) { + advance(); + state.start++; + count++; + } + + if (count % 2 === 0) return false; + + state.negated = true; + state.start++; + return true; + }; + + const increment = (type: string) => { + state[type]++; + stack.push(type); + }; + + const decrement = (type: string) => { + state[type]--; + stack.pop(); + }; + + const push = (tok: Token) => { + if (prev.type === "globstar") { + const isBrace = + state.braces > 0 && (tok.type === "comma" || tok.type === "brace"); + const isExtglob = + tok.extglob === true || + (extglobs.length && (tok.type === "pipe" || tok.type === "paren")); + + if (tok.type !== "slash" && tok.type !== "paren" && !isBrace && !isExtglob) { + state.output = state.output.slice(0, -prev.output.length); + prev.type = "star"; + prev.value = "*"; + prev.output = star; + state.output += prev.output; + } + } + + if (extglobs.length && tok.type !== "paren") { + extglobs[extglobs.length - 1].inner += tok.value; + } + + if (tok.value || tok.output) append(tok); + if (prev && prev.type === "text" && tok.type === "text") { + prev.output = (prev.output || prev.value) + tok.value; + prev.value += tok.value; + return; + } + + tok.prev = prev; + tokens.push(tok); + prev = tok; + }; + + const extglobOpen = (type: string, v: string) => { + const token: Token = { + ...(EXTGLOB_CHARS as any)[v], + conditions: 1, + inner: "", + }; + + token.prev = prev; + token.parens = state.parens; + token.output = state.output; + token.startIndex = state.index; + token.tokensIndex = tokens.length; + const output = (opts.capture ? "(" : "") + token.open; + + increment("parens"); + push({ type, value: v, output: state.output ? "" : ONE_CHAR }); + push({ type: "paren", extglob: true, value: advance(), output }); + extglobs.push(token); + }; + + const extglobClose = (token: Token) => { + const literal = input.slice(token.startIndex, state.index + 1); + const body = input.slice(token.startIndex + 2, state.index); + const analysis = analyzeRepeatedExtglob(body, opts); + + if ((token.type === "plus" || token.type === "star") && analysis.risky) { + const safeOutput = analysis.safeOutput + ? (token.output ? "" : ONE_CHAR) + + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput) + : undefined; + const open = tokens[token.tokensIndex]; + + open.type = "text"; + open.value = literal; + open.output = safeOutput || utils.escapeRegex(literal); + + for (let i = token.tokensIndex + 1; i < tokens.length; i++) { + tokens[i].value = ""; + tokens[i].output = ""; + delete tokens[i].suffix; + } + + state.output = token.output + open.output; + state.backtrack = true; + + push({ type: "paren", extglob: true, value, output: "" }); + decrement("parens"); + return; + } + + let output = token.close + (opts.capture ? ")" : ""); + let rest: string; + + if (token.type === "negate") { + let extglobStar = star; + + if (token.inner && token.inner.length > 1 && token.inner.includes("/")) { + extglobStar = globstar(opts); + } + + if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) { + output = token.close = `)$))${extglobStar}`; + } + + if (token.inner.includes("*") && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) { + const expression = parse(rest, { ...options, fastpaths: false }).output; + output = token.close = `)${expression})${extglobStar})`; + } + + if (token.prev.type === "bos") { + state.negatedExtglob = true; + } + } + + push({ type: "paren", extglob: true, value, output }); + decrement("parens"); + }; + + /** + * Fast paths + */ + if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) { + let backslashes: boolean = false; + + let output = input.replace( + REGEX_SPECIAL_CHARS_BACKREF, + (m, esc, _chars, first, rest, index) => { + if (first === "\\") { + backslashes = true; + return m; + } + + if (first === "?") { + if (esc) return esc + first + (rest ? QMARK.repeat(rest.length) : ""); + if (index === 0) return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : ""); + return QMARK.repeat(_chars.length); + } + + if (first === ".") return DOT_LITERAL.repeat(_chars.length); + + if (first === "*") { + if (esc) return esc + first + (rest ? star : ""); + return star; + } + return esc ? m : `\\${m}`; + }, + ); + + if (backslashes as boolean) { + if (opts.unescape === true) { + output = output.replace(/\\/g, ""); + } else { + output = output.replace(/\\+/g, (m) => { + return m.length % 2 === 0 ? "\\\\" : m ? "\\" : ""; + }); + } + } + + if (output === input && opts.contains === true) { + state.output = input; + return state; + } + + state.output = utils.wrapOutput(output, state, options); + return state; + } + + while (!eos()) { + value = advance(); + + if (value === "\u0000") continue; + + if (value === "\\") { + const next = peek(); + + if (next === "/" && opts.bash !== true) continue; + if (next === "." || next === ";") continue; + if (!next) { + value += "\\"; + push({ type: "text", value }); + continue; + } + + const match = /^\\+/.exec(remaining()); + let slashes = 0; + + if (match && match[0].length > 2) { + slashes = match[0].length; + state.index += slashes; + if (slashes % 2 !== 0) value += "\\"; + } + + if (opts.unescape === true) { + value = advance(); + } else { + value += advance(); + } + + if (state.brackets === 0) { + push({ type: "text", value }); + continue; + } + } + + if ( + state.brackets > 0 && + (value !== "]" || prev.value === "[" || prev.value === "[^") + ) { + if (opts.posix !== false && value === ":") { + const inner = prev.value.slice(1); + if (inner.includes("[")) { + prev.posix = true; + + if (inner.includes(":")) { + const idx = prev.value.lastIndexOf("["); + const pre = prev.value.slice(0, idx); + const rest = prev.value.slice(idx + 2); + const posix = POSIX_REGEX_SOURCE[rest]; + if (posix) { + prev.value = pre + posix; + state.backtrack = true; + advance(); + + if (!bos.output && tokens.indexOf(prev) === 1) { + bos.output = ONE_CHAR; + } + continue; + } + } + } + } + + if ((value === "[" && peek() !== ":") || (value === "-" && peek() === "]")) { + value = `\\${value}`; + } + + if (value === "]" && (prev.value === "[" || prev.value === "[^")) { + value = `\\${value}`; + } + + if (opts.posix === true && value === "!" && prev.value === "[") { + value = "^"; + } + + prev.value += value; + append({ value }); + continue; + } + + if (state.quotes === 1 && value !== '"') { + value = utils.escapeRegex(value); + prev.value += value; + append({ value }); + continue; + } + + if (value === '"') { + state.quotes = state.quotes === 1 ? 0 : 1; + if (opts.keepQuotes === true) { + push({ type: "text", value }); + } + continue; + } + + if (value === "(") { + increment("parens"); + push({ type: "paren", value }); + continue; + } + + if (value === ")") { + if (state.parens === 0 && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError("opening", "(")); + } + + const extglob = extglobs[extglobs.length - 1]; + if (extglob && state.parens === extglob.parens + 1) { + extglobClose(extglobs.pop()!); + continue; + } + + push({ type: "paren", value, output: state.parens ? ")" : "\\)" }); + decrement("parens"); + continue; + } + + if (value === "[") { + if (opts.nobracket === true || !remaining().includes("]")) { + if (opts.nobracket !== true && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError("closing", "]")); + } + + value = `\\${value}`; + } else { + increment("brackets"); + } + + push({ type: "bracket", value }); + continue; + } + + if (value === "]") { + if ( + opts.nobracket === true || + (prev && prev.type === "bracket" && prev.value.length === 1) + ) { + push({ type: "text", value, output: `\\${value}` }); + continue; + } + + if (state.brackets === 0) { + if (opts.strictBrackets === true) { + throw new SyntaxError(syntaxError("opening", "[")); + } + + push({ type: "text", value, output: `\\${value}` }); + continue; + } + + decrement("brackets"); + + const prevValue = prev.value.slice(1); + if (prev.posix !== true && prevValue[0] === "^" && !prevValue.includes("/")) { + value = `/${value}`; + } + + prev.value += value; + append({ value }); + + if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) { + continue; + } + + const escaped = utils.escapeRegex(prev.value); + state.output = state.output.slice(0, -prev.value.length); + + if (opts.literalBrackets === true) { + state.output += escaped; + prev.value = escaped; + continue; + } + + prev.value = `(${capture}${escaped}|${prev.value})`; + state.output += prev.value; + continue; + } + + if (value === "{" && opts.nobrace !== true) { + increment("braces"); + + const open: Token = { + type: "brace", + value, + output: "(", + outputIndex: state.output.length, + tokensIndex: state.tokens.length, + }; + + braces.push(open); + push(open); + continue; + } + + if (value === "}") { + const brace = braces[braces.length - 1]; + + if (opts.nobrace === true || !brace) { + push({ type: "text", value, output: value }); + continue; + } + + let output = ")"; + + if (brace.dots === true) { + const arr = tokens.slice(); + const range: string[] = []; + + for (let i = arr.length - 1; i >= 0; i--) { + tokens.pop(); + if (arr[i].type === "brace") break; + if (arr[i].type !== "dots") range.unshift(arr[i].value); + } + + output = expandRange(range, opts); + state.backtrack = true; + } + + if (brace.comma !== true && brace.dots !== true) { + const out = state.output.slice(0, brace.outputIndex); + const toks = state.tokens.slice(brace.tokensIndex); + brace.value = brace.output = "\\{"; + value = output = "\\}"; + state.output = out; + for (const t of toks) { + state.output += t.output || t.value; + } + } + + push({ type: "brace", value, output }); + decrement("braces"); + braces.pop(); + continue; + } + + if (value === "|") { + if (extglobs.length > 0) { + extglobs[extglobs.length - 1].conditions++; + } + push({ type: "text", value }); + continue; + } + + if (value === ",") { + let output = value; + const brace = braces[braces.length - 1]; + if (brace && stack[stack.length - 1] === "braces") { + brace.comma = true; + output = "|"; + } + + push({ type: "comma", value, output }); + continue; + } + + if (value === "/") { + if (prev.type === "dot" && state.index === state.start + 1) { + state.start = state.index + 1; + state.consumed = ""; + state.output = ""; + tokens.pop(); + prev = bos; + continue; + } + + push({ type: "slash", value, output: SLASH_LITERAL }); + continue; + } + + if (value === ".") { + if (state.braces > 0 && prev.type === "dot") { + if (prev.value === ".") prev.output = DOT_LITERAL; + const brace = braces[braces.length - 1]; + prev.type = "dots"; + prev.output += value; + prev.value += value; + brace.dots = true; + continue; + } + + if ( + state.braces + state.parens === 0 && + prev.type !== "bos" && + prev.type !== "slash" + ) { + push({ type: "text", value, output: DOT_LITERAL }); + continue; + } + + push({ type: "dot", value, output: DOT_LITERAL }); + continue; + } + + if (value === "?") { + const isGroup = prev && prev.value === "("; + if (!isGroup && opts.noextglob !== true && peek() === "(" && peek(2) !== "?") { + extglobOpen("qmark", value); + continue; + } + + if (prev && prev.type === "paren") { + const next = peek(); + let output = value; + + if ( + (prev.value === "(" && !/[!=<:]/.test(next)) || + (next === "<" && !/<([!=]|\w+>)/.test(remaining())) + ) { + output = `\\${value}`; + } + + push({ type: "text", value, output }); + continue; + } + + if (opts.dot !== true && (prev.type === "slash" || prev.type === "bos")) { + push({ type: "qmark", value, output: QMARK_NO_DOT }); + continue; + } + + push({ type: "qmark", value, output: QMARK }); + continue; + } + + if (value === "!") { + if (opts.noextglob !== true && peek() === "(") { + if (peek(2) !== "?" || !/[!=<:]/.test(peek(3))) { + extglobOpen("negate", value); + continue; + } + } + + if (opts.nonegate !== true && state.index === 0) { + negate(); + continue; + } + } + + if (value === "+") { + if (opts.noextglob !== true && peek() === "(" && peek(2) !== "?") { + extglobOpen("plus", value); + continue; + } + + if ((prev && prev.value === "(") || opts.regex === false) { + push({ type: "plus", value, output: PLUS_LITERAL }); + continue; + } + + if ( + (prev && + (prev.type === "bracket" || + prev.type === "paren" || + prev.type === "brace")) || + state.parens > 0 + ) { + push({ type: "plus", value }); + continue; + } + + push({ type: "plus", value: PLUS_LITERAL }); + continue; + } + + if (value === "@") { + if (opts.noextglob !== true && peek() === "(" && peek(2) !== "?") { + push({ type: "at", extglob: true, value, output: "" }); + continue; + } + + push({ type: "text", value }); + continue; + } + + if (value !== "*") { + if (value === "$" || value === "^") { + value = `\\${value}`; + } + + const match = REGEX_NON_SPECIAL_CHARS.exec(remaining()); + if (match) { + value += match[0]; + state.index += match[0].length; + } + + push({ type: "text", value }); + continue; + } + + /** + * Stars + */ + if (prev && (prev.type === "globstar" || prev.star === true)) { + prev.type = "star"; + prev.star = true; + prev.value += value; + prev.output = star; + state.backtrack = true; + state.globstar = true; + consume(value); + continue; + } + + let rest = remaining(); + if (opts.noextglob !== true && /^\([^?]/.test(rest)) { + extglobOpen("star", value); + continue; + } + + if (prev.type === "star") { + if (opts.noglobstar === true) { + consume(value); + continue; + } + + const prior = prev.prev; + const before = prior.prev; + const isStart = prior.type === "slash" || prior.type === "bos"; + const afterStar = + before && (before.type === "star" || before.type === "globstar"); + + if (opts.bash === true && (!isStart || (rest[0] && rest[0] !== "/"))) { + push({ type: "star", value, output: "" }); + continue; + } + + const isBrace = + state.braces > 0 && (prior.type === "comma" || prior.type === "brace"); + const isExtglob = + extglobs.length && (prior.type === "pipe" || prior.type === "paren"); + if (!isStart && prior.type !== "paren" && !isBrace && !isExtglob) { + push({ type: "star", value, output: "" }); + continue; + } + + while (rest.slice(0, 3) === "/**") { + const after = input[state.index + 4]; + if (after && after !== "/") break; + rest = rest.slice(3); + consume("/**", 3); + } + + if (prior.type === "bos" && eos()) { + prev.type = "globstar"; + prev.value += value; + prev.output = globstar(opts); + state.output = prev.output; + state.globstar = true; + consume(value); + continue; + } + + if (prior.type === "slash" && prior.prev.type !== "bos" && !afterStar && eos()) { + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = `(?:${prior.output}`; + + prev.type = "globstar"; + prev.output = globstar(opts) + (opts.strictSlashes ? ")" : "|$)"); + prev.value += value; + state.globstar = true; + state.output += prior.output + prev.output; + consume(value); + continue; + } + + if (prior.type === "slash" && prior.prev.type !== "bos" && rest[0] === "/") { + const end = rest[1] !== undefined ? "|$" : ""; + + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = `(?:${prior.output}`; + + prev.type = "globstar"; + prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`; + prev.value += value; + + state.output += prior.output + prev.output; + state.globstar = true; + + consume(value + advance()); + + push({ type: "slash", value: "/", output: "" }); + continue; + } + + if (prior.type === "bos" && rest[0] === "/") { + prev.type = "globstar"; + prev.value += value; + prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`; + state.output = prev.output; + state.globstar = true; + consume(value + advance()); + push({ type: "slash", value: "/", output: "" }); + continue; + } + + state.output = state.output.slice(0, -prev.output.length); + + prev.type = "globstar"; + prev.output = globstar(opts); + prev.value += value; + + state.output += prev.output; + state.globstar = true; + consume(value); + continue; + } + + const token: Token = { type: "star", value, output: star }; + + if (opts.bash === true) { + token.output = ".*?"; + if (prev.type === "bos" || prev.type === "slash") { + token.output = nodot + token.output; + } + push(token); + continue; + } + + if ( + prev && + (prev.type === "bracket" || prev.type === "paren") && + opts.regex === true + ) { + token.output = value; + push(token); + continue; + } + + if (state.index === state.start || prev.type === "slash" || prev.type === "dot") { + if (prev.type === "dot") { + state.output += NO_DOT_SLASH; + prev.output += NO_DOT_SLASH; + } else if (opts.dot === true) { + state.output += NO_DOTS_SLASH; + prev.output += NO_DOTS_SLASH; + } else { + state.output += nodot; + prev.output += nodot; + } + + if (peek() !== "*") { + state.output += ONE_CHAR; + prev.output += ONE_CHAR; + } + } + + push(token); + } + + while (state.brackets > 0) { + if (opts.strictBrackets === true) + throw new SyntaxError(syntaxError("closing", "]")); + state.output = utils.escapeLast(state.output, "["); + decrement("brackets"); + } + + while (state.parens > 0) { + if (opts.strictBrackets === true) + throw new SyntaxError(syntaxError("closing", ")")); + state.output = utils.escapeLast(state.output, "("); + decrement("parens"); + } + + while (state.braces > 0) { + if (opts.strictBrackets === true) + throw new SyntaxError(syntaxError("closing", "}")); + state.output = utils.escapeLast(state.output, "{"); + decrement("braces"); + } + + if (opts.strictSlashes !== true && (prev.type === "star" || prev.type === "bracket")) { + push({ type: "maybe_slash", value: "", output: `${SLASH_LITERAL}?` }); + } + + if (state.backtrack === true) { + state.output = ""; + + for (const token of state.tokens) { + state.output += token.output != null ? token.output : token.value; + if (token.suffix) state.output += token.suffix; + } + } + + return state; +} + +export namespace parse { + export function fastpaths(input: string, options?: ParseOptions): string | undefined { + const opts: ParseOptions = { ...options }; + const max = + typeof opts.maxLength === "number" + ? Math.min(MAX_LENGTH, opts.maxLength) + : MAX_LENGTH; + const len = input.length; + if (len > max) { + throw new SyntaxError( + `Input length: ${len}, exceeds maximum allowed length: ${max}`, + ); + } + + input = REPLACEMENTS[input] || input; + + const { + DOT_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOTS_SLASH, + STAR, + START_ANCHOR, + } = constants.globChars(opts.windows); + + const nodot = opts.dot ? NO_DOTS : NO_DOT; + const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT; + const capture = opts.capture ? "" : "?:"; + const state: any = { negated: false, prefix: "" }; + let star = opts.bash === true ? ".*?" : STAR; + + if (opts.capture) star = `(${star})`; + + const globstar = (o: ParseOptions): string => { + if (o.noglobstar === true) return star; + return `(${capture}(?:(?!${START_ANCHOR}${o.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + }; + + const create = (str: string): string | undefined => { + switch (str) { + case "*": + return `${nodot}${ONE_CHAR}${star}`; + case ".*": + return `${DOT_LITERAL}${ONE_CHAR}${star}`; + case "*.*": + return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + case "*/*": + return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`; + case "**": + return nodot + globstar(opts); + case "**/*": + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`; + case "**/*.*": + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + case "**/.*": + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`; + default: { + const match = /^(.*?)\.(\w+)$/.exec(str); + if (!match) return; + + const source = create(match[1]); + if (!source) return; + + return source + DOT_LITERAL + match[2]; + } + } + }; + + const output = utils.removePrefix(input, state); + let source = create(output); + + if (source && opts.strictSlashes !== true) { + source += `${SLASH_LITERAL}?`; + } + + return source; + } +} + +export default parse; diff --git a/packages/node-utils/src/picomatch/scan.ts b/packages/node-utils/src/picomatch/scan.ts new file mode 100644 index 0000000..8f71572 --- /dev/null +++ b/packages/node-utils/src/picomatch/scan.ts @@ -0,0 +1,400 @@ +import * as utils from "./utils.ts"; +import { + CHAR_ASTERISK, + CHAR_AT, + CHAR_BACKWARD_SLASH, + CHAR_COMMA, + CHAR_DOT, + CHAR_EXCLAMATION_MARK, + CHAR_FORWARD_SLASH, + CHAR_LEFT_CURLY_BRACE, + CHAR_LEFT_PARENTHESES, + CHAR_LEFT_SQUARE_BRACKET, + CHAR_PLUS, + CHAR_QUESTION_MARK, + CHAR_RIGHT_CURLY_BRACE, + CHAR_RIGHT_PARENTHESES, + CHAR_RIGHT_SQUARE_BRACKET, +} from "./constants.ts"; + +export interface ScanOptions { + parts?: boolean; + scanToEnd?: boolean; + noext?: boolean; + nonegate?: boolean; + noparen?: boolean; + unescape?: boolean; + tokens?: boolean; +} + +export interface ScanToken { + value: string; + depth: number; + isGlob: boolean; + isBrace?: boolean; + isBracket?: boolean; + isExtglob?: boolean; + isGlobstar?: boolean; + negated?: boolean; + backslashes?: boolean; + isPrefix?: boolean; +} + +export interface ScanState { + prefix: string; + input: string; + start: number; + base: string; + glob: string; + isBrace: boolean; + isBracket: boolean; + isGlob: boolean; + isExtglob: boolean; + isGlobstar: boolean; + negated: boolean; + negatedExtglob: boolean; + maxDepth?: number; + tokens?: ScanToken[]; + slashes?: number[]; + parts?: string[]; +} + +const isPathSeparator = (code: number): boolean => + code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; + +const depth = (token: ScanToken): void => { + if (token.isPrefix !== true) { + token.depth = token.isGlobstar ? Infinity : 1; + } +}; + +export function scan(input: string, options?: ScanOptions): ScanState { + const opts = options || {}; + + const length = input.length - 1; + const scanToEnd = opts.parts === true || opts.scanToEnd === true; + const slashes: number[] = []; + const tokens: ScanToken[] = []; + const parts: string[] = []; + + let str = input; + let index = -1; + let start = 0; + let lastIndex = 0; + let isBrace = false; + let isBracket = false; + let isGlob = false; + let isExtglob = false; + let isGlobstar = false; + let braceEscaped = false; + let backslashes = false; + let negated = false; + let negatedExtglob = false; + let finished = false; + let braces = 0; + let prev: number | undefined; + let code = 0; + let token: ScanToken = { value: "", depth: 0, isGlob: false }; + + const eos = () => index >= length; + const peek = () => str.charCodeAt(index + 1); + const advance = () => { + prev = code; + return str.charCodeAt(++index); + }; + + while (index < length) { + code = advance(); + let next: number; + + if (code === CHAR_BACKWARD_SLASH) { + backslashes = token.backslashes = true; + code = advance(); + + if (code === CHAR_LEFT_CURLY_BRACE) { + braceEscaped = true; + } + continue; + } + + if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) { + braces++; + + while (eos() !== true && (code = advance())) { + if (code === CHAR_BACKWARD_SLASH) { + backslashes = token.backslashes = true; + advance(); + continue; + } + + if (code === CHAR_LEFT_CURLY_BRACE) { + braces++; + continue; + } + + if ( + braceEscaped !== true && + code === CHAR_DOT && + (code = advance()) === CHAR_DOT + ) { + isBrace = token.isBrace = true; + isGlob = token.isGlob = true; + finished = true; + + if (scanToEnd === true) continue; + break; + } + + if (braceEscaped !== true && code === CHAR_COMMA) { + isBrace = token.isBrace = true; + isGlob = token.isGlob = true; + finished = true; + + if (scanToEnd === true) continue; + break; + } + + if (code === CHAR_RIGHT_CURLY_BRACE) { + braces--; + if (braces === 0) { + braceEscaped = false; + isBrace = token.isBrace = true; + finished = true; + break; + } + } + } + + if (scanToEnd === true) continue; + break; + } + + if (code === CHAR_FORWARD_SLASH) { + slashes.push(index); + tokens.push(token); + token = { value: "", depth: 0, isGlob: false }; + + if (finished === true) continue; + if (prev === CHAR_DOT && index === start + 1) { + start += 2; + continue; + } + + lastIndex = index + 1; + continue; + } + + if (opts.noext !== true) { + const isExtglobChar = + code === CHAR_PLUS || + code === CHAR_AT || + code === CHAR_ASTERISK || + code === CHAR_QUESTION_MARK || + code === CHAR_EXCLAMATION_MARK; + + if (isExtglobChar === true && peek() === CHAR_LEFT_PARENTHESES) { + isGlob = token.isGlob = true; + isExtglob = token.isExtglob = true; + finished = true; + if (code === CHAR_EXCLAMATION_MARK && index === start) { + negatedExtglob = true; + } + + if (scanToEnd === true) { + while (eos() !== true && (code = advance())) { + if (code === CHAR_BACKWARD_SLASH) { + backslashes = token.backslashes = true; + code = advance(); + continue; + } + + if (code === CHAR_RIGHT_PARENTHESES) { + isGlob = token.isGlob = true; + finished = true; + break; + } + } + continue; + } + break; + } + } + + if (code === CHAR_ASTERISK) { + if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true; + isGlob = token.isGlob = true; + finished = true; + + if (scanToEnd === true) continue; + break; + } + + if (code === CHAR_QUESTION_MARK) { + isGlob = token.isGlob = true; + finished = true; + + if (scanToEnd === true) continue; + break; + } + + if (code === CHAR_LEFT_SQUARE_BRACKET) { + while (eos() !== true && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = token.backslashes = true; + advance(); + continue; + } + + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + isBracket = token.isBracket = true; + isGlob = token.isGlob = true; + finished = true; + break; + } + } + + if (scanToEnd === true) continue; + break; + } + + if ( + opts.nonegate !== true && + code === CHAR_EXCLAMATION_MARK && + index === start + ) { + negated = token.negated = true; + start++; + continue; + } + + if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) { + isGlob = token.isGlob = true; + + if (scanToEnd === true) { + while (eos() !== true && (code = advance())) { + if (code === CHAR_LEFT_PARENTHESES) { + backslashes = token.backslashes = true; + code = advance(); + continue; + } + + if (code === CHAR_RIGHT_PARENTHESES) { + finished = true; + break; + } + } + continue; + } + break; + } + + if (isGlob === true) { + finished = true; + if (scanToEnd === true) continue; + break; + } + } + + if (opts.noext === true) { + isExtglob = false; + isGlob = false; + } + + let base = str; + let prefix = ""; + let glob = ""; + + if (start > 0) { + prefix = str.slice(0, start); + str = str.slice(start); + lastIndex -= start; + } + + if (base && isGlob === true && lastIndex > 0) { + base = str.slice(0, lastIndex); + glob = str.slice(lastIndex); + } else if (isGlob === true) { + base = ""; + glob = str; + } else { + base = str; + } + + if (base && base !== "" && base !== "/" && base !== str) { + if (isPathSeparator(base.charCodeAt(base.length - 1))) { + base = base.slice(0, -1); + } + } + + if (opts.unescape === true) { + if (glob) glob = utils.removeBackslashes(glob); + if (base && backslashes === true) { + base = utils.removeBackslashes(base); + } + } + + const state: ScanState = { + prefix, + input, + start, + base, + glob, + isBrace, + isBracket, + isGlob, + isExtglob, + isGlobstar, + negated, + negatedExtglob, + }; + + if (opts.tokens === true) { + state.maxDepth = 0; + if (!isPathSeparator(code)) { + tokens.push(token); + } + state.tokens = tokens; + } + + if (opts.parts === true || opts.tokens === true) { + let prevIndex: number | undefined; + + for (let idx = 0; idx < slashes.length; idx++) { + const n = prevIndex ? prevIndex + 1 : start; + const i = slashes[idx]; + const value = input.slice(n, i); + if (opts.tokens) { + if (idx === 0 && start !== 0) { + tokens[idx].isPrefix = true; + tokens[idx].value = prefix; + } else { + tokens[idx].value = value; + } + depth(tokens[idx]); + state.maxDepth = (state.maxDepth ?? 0) + tokens[idx].depth; + } + if (idx !== 0 || value !== "") { + parts.push(value); + } + prevIndex = i; + } + + if (prevIndex && prevIndex + 1 < input.length) { + const value = input.slice(prevIndex + 1); + parts.push(value); + + if (opts.tokens) { + tokens[tokens.length - 1].value = value; + depth(tokens[tokens.length - 1]); + state.maxDepth = (state.maxDepth ?? 0) + tokens[tokens.length - 1].depth; + } + } + + state.slashes = slashes; + state.parts = parts; + } + + return state; +} + +export default scan; diff --git a/packages/node-utils/src/picomatch/utils.ts b/packages/node-utils/src/picomatch/utils.ts new file mode 100644 index 0000000..38435bd --- /dev/null +++ b/packages/node-utils/src/picomatch/utils.ts @@ -0,0 +1,94 @@ +import path from "node:path"; +import { + REGEX_BACKSLASH, + REGEX_REMOVE_BACKSLASH, + REGEX_SPECIAL_CHARS, + REGEX_SPECIAL_CHARS_GLOBAL, +} from "./constants.ts"; + +export const isObject = (val: unknown): val is Record => + val !== null && typeof val === "object" && !Array.isArray(val); + +export const hasRegexChars = (str: string): boolean => + REGEX_SPECIAL_CHARS.test(str); + +export const isRegexChar = (str: string): boolean => + str.length === 1 && hasRegexChars(str); + +export const escapeRegex = (str: string): string => + str.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1"); + +export const toPosixSlashes = (str: string): string => + str.replace(REGEX_BACKSLASH, "/"); + +export const isWindows = (): boolean => { + // Tests mutate `path.sep` to simulate Windows. Respect that override so + // mm.capture / utils.toPosixSlashes() paths kick in without a real Windows host. + if (path.sep === "\\") return true; + if (typeof process !== "undefined" && process.platform) { + return process.platform === "win32"; + } + return false; +}; + +export const removeBackslashes = (str: string): string => { + return str.replace(REGEX_REMOVE_BACKSLASH, (match) => + match === "\\" ? "" : match, + ); +}; + +export const escapeLast = ( + input: string, + char: string, + lastIdx?: number, +): string => { + const idx = input.lastIndexOf(char, lastIdx); + if (idx === -1) return input; + if (input[idx - 1] === "\\") return escapeLast(input, char, idx - 1); + return `${input.slice(0, idx)}\\${input.slice(idx)}`; +}; + +export interface PrefixState { + prefix?: string; + negated?: boolean; + [k: string]: unknown; +} + +export const removePrefix = (input: string, state: PrefixState = {}): string => { + let output = input; + if (output.startsWith("./")) { + output = output.slice(2); + state.prefix = "./"; + } + return output; +}; + +export interface WrapOutputOptions { + contains?: boolean; + [k: string]: unknown; +} + +export const wrapOutput = ( + input: string, + state: PrefixState = {}, + options: WrapOutputOptions = {}, +): string => { + const prepend = options.contains ? "" : "^"; + const append = options.contains ? "" : "$"; + + let output = `${prepend}(?:${input})${append}`; + if (state.negated === true) { + output = `(?:^(?!${output}).*$)`; + } + return output; +}; + +export const basename = ( + path: string, + { windows }: { windows?: boolean } = {}, +): string => { + const segs = path.split(windows ? /[\\/]/ : "/"); + const last = segs[segs.length - 1]; + if (last === "") return segs[segs.length - 2]; + return last; +}; diff --git a/packages/node-utils/test/braces/bash-compiled-ranges.test.ts b/packages/node-utils/test/braces/bash-compiled-ranges.test.ts new file mode 100644 index 0000000..f4c2c2e --- /dev/null +++ b/packages/node-utils/test/braces/bash-compiled-ranges.test.ts @@ -0,0 +1,175 @@ +import { describe, expect, test } from 'bun:test'; +import braces from '../../src/braces/index.ts'; + +type Fixture = [string, any, any]; + +const fixtures = [ + ['a{b,c{1..100}/{foo,bar}/,h}x/z', {}, 'a(b|c([1-9]|[1-9][0-9]|100)/(foo|bar)/|h)x/z'], + ['0{1..9} {10..20}', {}, '0([1-9]) (1[0-9]|20)'], + + // should not try to expand ranges with decimals + ['{1.1..2.1}', {}, '{1.1..2.1}'], + ['{1.1..~2.1}', {}, '{1.1..~2.1}'], + + // should escape invalid ranges + ['{1..0f}', {}, '{1..0f}'], + ['{1..10..ff}', {}, '{1..10..ff}'], + ['{1..10.f}', {}, '{1..10.f}'], + ['{1..10f}', {}, '{1..10f}'], + ['{1..20..2f}', {}, '{1..20..2f}'], + ['{1..20..f2}', {}, '{1..20..f2}'], + ['{1..2f..2}', {}, '{1..2f..2}'], + ['{1..ff..2}', {}, '{1..ff..2}'], + ['{1..ff}', {}, '{1..ff}'], + ['{1.20..2}', {}, '{1.20..2}'], + + // should handle weirdly-formed brace expansions (fixed in post-bash-3.1) + ['a-{b{d,e}}-c', {}, 'a-{b(d|e)}-c'], + ['a-{bdef-{g,i}-c', {}, 'a-{bdef-(g|i)-c'], + + // should not expand quoted strings + ['{"klklkl"}{1,2,3}', {}, '{klklkl}(1|2|3)'], + ['{"x,x"}', {}, '{x,x}'], + + // should escaped outer braces in nested non-sets + ['{a-{b,c,d}}', {}, '{a-(b|c|d)}'], + ['{a,{a-{b,c,d}}}', {}, '(a|{a-(b|c|d)})'], + + // should escape imbalanced braces + ['abc{', {}, 'abc{'], + ['{abc{', {}, '{abc{'], + ['{abc', {}, '{abc'], + ['}abc', {}, '}abc'], + ['ab{c', {}, 'ab{c'], + ['{{a,b}', {}, '{(a|b)'], + ['{a,b}}', {}, '(a|b)}'], + ['abcd{efgh', {}, 'abcd{efgh'], + ['a{b{c{d,e}f}gh', {}, 'a{b{c(d|e)f}gh'], + ['a{b{c{d,e}f}g}h', {}, 'a{b{c(d|e)f}g}h'], + ['f{x,y{{g,z}}h}', {}, 'f(x|y{(g|z)}h)'], + ['z{a,b},c}d', {}, 'z(a|b),c}d'], + ['a{b{c{d,e}f{x,y{{g}h', {}, 'a{b{c(d|e)f{x,y{{g}h'], + ['f{x,y{{g}h', {}, 'f{x,y{{g}h'], + ['f{x,y{{g}}h', {}, 'f{x,y{{g}}h'], + ['a{b{c{d,e}f{x,y{}g}h', {}, 'a{b{c(d|e)f(x|y{}g)h'], + ['f{x,y{}g}h', {}, 'f(x|y{}g)h'], + ['z{a,b{,c}d', {}, 'z{a,b(|c)d'], + + // should expand numeric ranges + ['a{0..3}d', {}, 'a([0-3])d'], + ['x{10..1}y', {}, 'x([1-9]|10)y'], + ['x{3..3}y', {}, 'x3y'], + ['{1..10}', {}, '([1-9]|10)'], + ['{1..3}', {}, '([1-3])'], + ['{1..9}', {}, '([1-9])'], + ['{10..1}', {}, '([1-9]|10)'], + ['{10..1}y', {}, '([1-9]|10)y'], + ['{3..3}', {}, '3'], + ['{5..8}', {}, '([5-8])'], + + // should expand ranges with negative numbers + ['{-10..-1}', {}, '(-[1-9]|-10)'], + ['{-20..0}', {}, '(-[1-9]|-1[0-9]|-20|0)'], + ['{0..-5}', {}, '(-[1-5]|0)'], + ['{9..-4}', {}, '(-[1-4]|[0-9])'], + + // should expand alphabetical ranges + ['0{1..9}/{10..20}', {}, '0([1-9])/(1[0-9]|20)'], + ['0{a..d}0', {}, '0([a-d])0'], + ['a/{b..d}/e', {}, 'a/([b-d])/e'], + ['{1..f}', {}, '([1-f])'], + ['{a..A}', {}, '([A-a])'], + ['{A..a}', {}, '([A-a])'], + ['{a..e}', {}, '([a-e])'], + ['{A..E}', {}, '([A-E])'], + ['{a..f}', {}, '([a-f])'], + ['{a..z}', {}, '([a-z])'], + ['{E..A}', {}, '([A-E])'], + ['{f..1}', {}, '([1-f])'], + ['{f..a}', {}, '([a-f])'], + ['{f..f}', {}, 'f'], + + // should expand multiple ranges + ['a/{b..d}/e/{f..h}', {}, 'a/([b-d])/e/([f-h])'], + + // should expand numerical ranges - positive and negative + ['{-10..10}', {}, '(-[1-9]|-?10|[0-9])'], + + // HEADS UP! If you're using the `--mm` flag minimatch freezes on these + // should expand large numbers + ['{2147483645..2147483649}', {}, '(214748364[5-9])'], + ['{214748364..2147483649}', {}, '(21474836[4-9]|2147483[7-9][0-9]|214748[4-9][0-9]{2}|214749[0-9]{3}|2147[5-9][0-9]{4}|214[89][0-9]{5}|21[5-9][0-9]{6}|2[2-9][0-9]{7}|[3-9][0-9]{8}|1[0-9]{9}|20[0-9]{8}|21[0-3][0-9]{7}|214[0-6][0-9]{6}|2147[0-3][0-9]{5}|21474[0-7][0-9]{4}|214748[0-2][0-9]{3}|2147483[0-5][0-9]{2}|21474836[0-4][0-9])'], + + // should expand ranges using steps + ['{1..10..1}', { bash: false }, '([1-9]|10)'], + ['{1..10..2}', { bash: false }, '(1|3|5|7|9)'], + ['{1..20..20}', { bash: false }, '1'], + ['{1..20..2}', { bash: false }, '(1|3|5|7|9|11|13|15|17|19)'], + ['{10..1..2}', { bash: false }, '(2|4|6|8|10)'], + ['{100..0..5}', { bash: false }, '(0|5|10|15|20|25|30|35|40|45|50|55|60|65|70|75|80|85|90|95|100)'], + ['{2..10..1}', { bash: false }, '([2-9]|10)'], + ['{2..10..2}', { bash: false }, '(2|4|6|8|10)'], + ['{2..10..3}', { bash: false }, '(2|5|8)'], + + // should expand negative ranges using steps + ['{-1..-10..-2}', { bash: false }, '(-(?:1|3|5|7|9))'], + ['{-1..-10..2}', { bash: false }, '(-(?:1|3|5|7|9))'], + ['{-10..-2..2}', { bash: false }, '(-(?:2|4|6|8|10))'], + ['{-2..-10..1}', { bash: false }, '(-[2-9]|-10)'], + ['{-2..-10..2}', { bash: false }, '(-(?:2|4|6|8|10))'], + ['{-2..-10..3}', { bash: false }, '(-(?:2|5|8))'], + ['{-9..9..3}', { bash: false }, '(0|3|6|9|-(?:3|6|9))'], + ['{10..1..-2}', { bash: false }, '(2|4|6|8|10)'], + ['{100..0..-5}', { bash: false }, '(0|5|10|15|20|25|30|35|40|45|50|55|60|65|70|75|80|85|90|95|100)'], + + // should expand alpha ranges with steps + ['{a..e..2}', { bash: false }, '(a|c|e)'], + ['{E..A..2}', { bash: false }, '(E|C|A)'], + ['{a..z..2}', { bash: false }, '(a|c|e|g|i|k|m|o|q|s|u|w|y)'], + + // should expand alpha ranges with negative steps + ['{z..a..-2}', { bash: false }, '(z|x|v|t|r|p|n|l|j|h|f|d|b)'], + + // unwanted zero-padding (fixed post-bash-4.0) + ['{10..0..2}', { bash: false }, '(0|2|4|6|8|10)'], + ['{10..0..-2}', { bash: false }, '(0|2|4|6|8|10)'], + ['{-50..-0..5}', { bash: false }, '(0|-(?:5|10|15|20|25|30|35|40|45|50))'], + + // should work with dots in file paths + ['../{1..3}/../foo', {}, '../([1-3])/../foo'], + ['../{2..10..2}/../foo', {}, '../(2|4|6|8|10)/../foo'], + ['../{1..3}/../{a,b,c}/foo', {}, '../([1-3])/../(a|b|c)/foo'], + ['./{a..z..3}/', {}, './(a|d|g|j|m|p|s|v|y)/'], + ['./{"x,y"}/{a..z..3}/', { keepQuotes: true }, './{"x,y"}/(a|d|g|j|m|p|s|v|y)/'], + + // should expand a complex combination of ranges and sets + ['a/{x,y}/{1..5}c{d,e}f.{md,txt}', {}, 'a/(x|y)/([1-5])c(d|e)f.(md|txt)'], + + // should expand complex sets and ranges in `bash` mode + ['a/{x,{1..5},y}/c{d}e', {}, 'a/(x|([1-5])|y)/c{d}e'], + + // should treat glob characters as literal + ['**/{1..5}/a.js', {}, '**/([1-5])/a.js'], + ['x{{0..10},braces}y', {}, 'x(([0-9]|10)|braces)y'], + + // should handle sets with invalid ranges + ['{0..10,braces}', {}, '(0..10|braces)'], + ['{1..10,braces}', {}, '(1..10|braces)'], + + ['./\\{x,y}/{a..z..3}/', {}, './{x,y}/(a|d|g|j|m|p|s|v|y)/'], + ['{braces,{0..10}}', {}, '(braces|([0-9]|10))'], + ['{{0..10},braces}', {}, '(([0-9]|10)|braces)'], + ['{{1..10..2},braces}', { bash: false }, '((1|3|5|7|9)|braces)'], + ['{{1..10..2},braces}', {}, '((1|3|5|7|9)|braces)'] + ]; + +describe('bash ranges - braces.compile()', () => { + for (const arr of fixtures as Fixture[]) { + if (typeof arr === 'string') continue; + const [pattern, options, expected] = arr; + if ((options as any).skip === true) continue; + test('should compile: ' + pattern, () => { + expect(braces.compile(pattern, options)).toEqual(expected); + }); + } +}); diff --git a/packages/node-utils/test/braces/bash-compiled-sets.test.ts b/packages/node-utils/test/braces/bash-compiled-sets.test.ts new file mode 100644 index 0000000..98e8b95 --- /dev/null +++ b/packages/node-utils/test/braces/bash-compiled-sets.test.ts @@ -0,0 +1,156 @@ +import { describe, expect, test } from 'bun:test'; +import braces from '../../src/braces/index.ts'; + +type Fixture = [string, any, any]; + +const fixtures = [ + ['{a,b,c,d,e}', {}, '(a|b|c|d|e)'], + ['a/\\{b,c,d,{x,y}}{e,f\\}/g', {}, 'a/{b,c,d,(x|y)}{e,f}/g'], + ['a/\\{b,c,d\\}\\{e,f\\}/g', {}, 'a/{b,c,d}{e,f}/g'], + ['a/\\{b,c,d\\}\\{e,f}/g', {}, 'a/{b,c,d}{e,f}/g'], + ['a/\\{b,c,d\\}{e,f}/g', {}, 'a/{b,c,d}(e|f)/g'], + ['a/\\{b,c,d{x,y}}{e,f\\}/g', {}, 'a/{b,c,d(x|y)}{e,f}/g'], + ['a/\\{b,c,d}{e,f\\}/g', {}, 'a/{b,c,d}{e,f}/g'], + ['a/\\{b,c,d}{e,f}/g', {}, 'a/{b,c,d}(e|f)/g'], + ['a/\\{{b,c}{e,f}/g', {}, 'a/{(b|c)(e|f)/g'], + ['a/\\{{b,c}{e,f}\\}/g', {}, 'a/{(b|c)(e|f)}/g'], + ['a/\\{{b,c}{e,f}}/g', {}, 'a/{(b|c)(e|f)}/g'], + ['a/b/{b,c,{d,e{f,g},{w,x}/{y,z}}}/h/i', {}, 'a/b/(b|c|(d|e(f|g)|(w|x)/(y|z)))/h/i'], + ['a/{b,c}}{e,f}/g', {}, 'a/(b|c)}(e|f)/g'], + ['a/{b,c\\,d}{e,f}/g', {}, 'a/(b|c,d)(e|f)/g'], + ['a/{b,c\\}}{e,f}/g', {}, 'a/(b|c})(e|f)/g'], + ['a/{b,c}', {}, 'a/(b|c)'], + ['a/{b,c}d{e,f}/g', {}, 'a/(b|c)d(e|f)/g'], + ['a/{b,c}{e,f}/g', {}, 'a/(b|c)(e|f)/g'], + ['a/{b,c}{e,f}{g,h,i}/k', {}, 'a/(b|c)(e|f)(g|h|i)/k'], + ['a/{b,{c,d},e}/f', {}, 'a/(b|(c|d)|e)/f'], + ['a/{b,{c,d}/{e,f},g}/h', {}, 'a/(b|(c|d)/(e|f)|g)/h'], + ['a/{b{c,d},e{f,g}h{i,j}}/k', {}, 'a/(b(c|d)|e(f|g)h(i|j))/k'], + ['a/{b{c,d},e}/f', {}, 'a/(b(c|d)|e)/f'], + ['a/{b{c,d}e{f,g}h{i,j}}/k', {}, 'a/{b(c|d)e(f|g)h(i|j)}/k'], + ['a/{b{c,d}e{f,g},h{i,j}}/k', {}, 'a/(b(c|d)e(f|g)|h(i|j))/k'], + ['a/{x,z}{b,{c,d}/{e,f},g}/h', {}, 'a/(x|z)(b|(c|d)/(e|f)|g)/h'], + ['a/{{a,b}/{c,d}}/z', {}, 'a/{(a|b)/(c|d)}/z'], + ['a/{{b,c}/{d,e}}', {}, 'a/{(b|c)/(d|e)}'], + ['a/{{b,c}/{d,e}}/f', {}, 'a/{(b|c)/(d|e)}/f'], + ['a{b{c{d,e}f{x,y{{g}h', {}, 'a{b{c(d|e)f{x,y{{g}h'], + ['{a,b,{c,d},e}', {}, '(a|b|(c|d)|e)'], + ['{a,b,{c,d}e}', {}, '(a|b|(c|d)e)'], + ['{a,b,{c,d}}', {}, '(a|b|(c|d))'], + ['{a,b{c,d}}', {}, '(a|b(c|d))'], + ['{a,b}/{c,d}', {}, '(a|b)/(c|d)'], + ['{a,b}{c,d}', {}, '(a|b)(c|d)'], + + ['{{a,b},{c,d}}', {}, '((a|b)|(c|d))'], + ['{{a,b}/{c,d}}', {}, '{(a|b)/(c|d)}'], + ['{{a,b}/{c,d}}/z', {}, '{(a|b)/(c|d)}/z'], + + // should not process glob characters + ['{generate,{assemble,update,verb}{file,-generate-*},generator}.js', {}, '(generate|(assemble|update|verb)(file|-generate-*)|generator).js'], + ['**/{foo,bar}.js', {}, '**/(foo|bar).js'], + ['**/{a,b,c}/*.js', {}, '**/(a|b|c)/*.js'], + ['**/{a,b,*}/*.js', {}, '**/(a|b|*)/*.js'], + ['**/{**,b,*}/*.js', {}, '**/(**|b|*)/*.js'], + + // should not expand escaped braces + ['\\{a,b,c,d,e}', {}, '{a,b,c,d,e}'], + ['a/b/c/{x,y\\}', {}, 'a/b/c/{x,y}'], + ['a/\\{x,y}/cde', {}, 'a/{x,y}/cde'], + ['abcd{efgh', {}, 'abcd{efgh'], + ['\\{abc\\}', {}, '{abc}'], + ['{x,y,\\{a,b,c\\}}', {}, '(x|y|{a|b|c})'], + ['{x,y,{a,b,c\\}}', {}, '{x,y,(a|b|c})'], + ['{x,y,{abc},trie}', {}, '(x|y|{abc}|trie)'], + ['x,y,{abc},trie', {}, 'x,y,{abc},trie'], + ['{b{c,d},e}', {}, '(b(c|d)|e)'], + ['{b{c,d},e}/f', {}, '(b(c|d)|e)/f'], + ['{abc}', {}, '{abc}'], + + // should handle empty braces + ['{ }', {}, '{ }'], + ['{', {}, '{'], + ['{}', {}, '{}'], + ['}', {}, '}'], + + // should escape braces when only one value is defined + ['a{b}c', {}, 'a{b}c'], + ['a/b/c{d}e', {}, 'a/b/c{d}e'], + + // should escape closing braces when open is not defined + ['{a,b}c,d}', {}, '(a|b)c,d}'], + ['a,b,c,d}', {}, 'a,b,c,d}'], + + // should not expand braces in sets with es6/bash-like variables + ['abc/${ddd}/xyz', {}, 'abc/${ddd}/xyz'], + ['a${b}c', {}, 'a${b}c'], + ['a${b{a,b}}c', {}, 'a${b{a,b}}c'], + ['a/{${b},c}/d', {}, 'a/(${b}|c)/d'], + ['a${b,d}/{foo,bar}c', {}, 'a${b,d}/(foo|bar)c'], + + // should not expand escaped commas + ['a{b\\,c\\,d}e', {}, 'a{b,c,d}e'], + ['a{b\\,c}d', {}, 'a{b,c}d'], + ['{abc\\,def}', {}, '{abc,def}'], + ['{abc\\,def,ghi}', {}, '(abc,def|ghi)'], + ['a/{b,c}/{x\\,y}/d/e', {}, 'a/(b|c)/{x,y}/d/e'], + + // should not expand escaped braces + ['{a,b\\}c,d}', {}, '(a|b}c|d)'], + ['a/{z,\\{a,b,c,d,e}/d', {}, 'a/(z|{a|b|c|d|e)/d'], + ['a/\\{b,c}/{d,e}/f', {}, 'a/{b,c}/(d|e)/f'], + + // should not expand escaped braces or commas + ['{x\\,y,\\{abc\\},trie}', {}, '(x,y|{abc}|trie)'], + + // should support sequence brace operators + ['ff{c,b,a}', {}, 'ff(c|b|a)'], + ['f{d,e,f}g', {}, 'f(d|e|f)g'], + ['{a,b,c}', {}, '(a|b|c)'], + ['{l,n,m}xyz', {}, '(l|n|m)xyz'], + + // should expand multiple sets + ['a/{a,b}/{c,d}/e', {}, 'a/(a|b)/(c|d)/e'], + ['a{b,c}d{e,f}g', {}, 'a(b|c)d(e|f)g'], + ['a/{x,y}/c{d,e}f.{md,txt}', {}, 'a/(x|y)/c(d|e)f.(md|txt)'], + + // should expand nested sets + ['{a,b}{{a,b},a,b}', {}, '(a|b)((a|b)|a|b)'], + ['/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, '/usr/(ucb/(ex|edit)|lib/(ex|how_ex))'], + ['a{b,c{d,e}f}g', {}, 'a(b|c(d|e)f)g'], + ['a{{x,y},z}b', {}, 'a((x|y)|z)b'], + ['f{x,y{g,z}}h', {}, 'f(x|y(g|z))h'], + ['a{b,c{d,e},h}x/z', {}, 'a(b|c(d|e)|h)x/z'], + ['a{b,c{d,e},h}x{y,z}', {}, 'a(b|c(d|e)|h)x(y|z)'], + ['a{b,c{d,e},{f,g}h}x{y,z}', {}, 'a(b|c(d|e)|(f|g)h)x(y|z)'], + ['a-{b{d,e}}-c', {}, 'a-{b(d|e)}-c'], + + // should expand not modify non-brace characters + ['a/b/{d,e}/*.js', {}, 'a/b/(d|e)/*.js'], + ['a/**/c/{d,e}/f*.js', {}, 'a/**/c/(d|e)/f*.js'], + ['a/**/c/{d,e}/f*.{md,txt}', {}, 'a/**/c/(d|e)/f*.(md|txt)'], + + // should work with leading and trailing commas + ['a{b,}c', {}, 'a(b|)c'], + ['a{,b}c', {}, 'a(|b)c'], + + // should handle spaces + // Bash 4.3 says the this first one should be equivalent to `foo|(1|2)|bar + // That makes sense in Bash, since ' ' is a separator, but not here. + ['foo {1,2} bar', {}, 'foo (1|2) bar'], + ['a{ ,c{d, },h}x', {}, 'a( |c(d| )|h)x'], + ['a{ ,c{d, },h} ', {}, 'a( |c(d| )|h) '], + + // see https://github.com/jonschlinkert/microequal/issues/66 + ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', {}, '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.(html|ejs)'] + ]; + +describe('bash sets - braces.compile()', () => { + for (const arr of fixtures as Fixture[]) { + if (typeof arr === 'string') continue; + const [pattern, options, expected] = arr; + if ((options as any).skip === true) continue; + test('should compile: ' + pattern, () => { + expect(braces.compile(pattern, options)).toEqual(expected); + }); + } +}); diff --git a/packages/node-utils/test/braces/bash-expanded-ranges.test.ts b/packages/node-utils/test/braces/bash-expanded-ranges.test.ts new file mode 100644 index 0000000..611012f --- /dev/null +++ b/packages/node-utils/test/braces/bash-expanded-ranges.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, test } from 'bun:test'; +import braces from '../../src/braces/index.ts'; + +type Fixture = [string, any, any]; + +const fixtures = [ + 'should expand ranges', + + ['a{b,c{1..50}/{d,e,f}/,g}h/i', {}, ['abh/i', 'ac1/d/h/i', 'ac1/e/h/i', 'ac1/f/h/i', 'ac2/d/h/i', 'ac2/e/h/i', 'ac2/f/h/i', 'ac3/d/h/i', 'ac3/e/h/i', 'ac3/f/h/i', 'ac4/d/h/i', 'ac4/e/h/i', 'ac4/f/h/i', 'ac5/d/h/i', 'ac5/e/h/i', 'ac5/f/h/i', 'ac6/d/h/i', 'ac6/e/h/i', 'ac6/f/h/i', 'ac7/d/h/i', 'ac7/e/h/i', 'ac7/f/h/i', 'ac8/d/h/i', 'ac8/e/h/i', 'ac8/f/h/i', 'ac9/d/h/i', 'ac9/e/h/i', 'ac9/f/h/i', 'ac10/d/h/i', 'ac10/e/h/i', 'ac10/f/h/i', 'ac11/d/h/i', 'ac11/e/h/i', 'ac11/f/h/i', 'ac12/d/h/i', 'ac12/e/h/i', 'ac12/f/h/i', 'ac13/d/h/i', 'ac13/e/h/i', 'ac13/f/h/i', 'ac14/d/h/i', 'ac14/e/h/i', 'ac14/f/h/i', 'ac15/d/h/i', 'ac15/e/h/i', 'ac15/f/h/i', 'ac16/d/h/i', 'ac16/e/h/i', 'ac16/f/h/i', 'ac17/d/h/i', 'ac17/e/h/i', 'ac17/f/h/i', 'ac18/d/h/i', 'ac18/e/h/i', 'ac18/f/h/i', 'ac19/d/h/i', 'ac19/e/h/i', 'ac19/f/h/i', 'ac20/d/h/i', 'ac20/e/h/i', 'ac20/f/h/i', 'ac21/d/h/i', 'ac21/e/h/i', 'ac21/f/h/i', 'ac22/d/h/i', 'ac22/e/h/i', 'ac22/f/h/i', 'ac23/d/h/i', 'ac23/e/h/i', 'ac23/f/h/i', 'ac24/d/h/i', 'ac24/e/h/i', 'ac24/f/h/i', 'ac25/d/h/i', 'ac25/e/h/i', 'ac25/f/h/i', 'ac26/d/h/i', 'ac26/e/h/i', 'ac26/f/h/i', 'ac27/d/h/i', 'ac27/e/h/i', 'ac27/f/h/i', 'ac28/d/h/i', 'ac28/e/h/i', 'ac28/f/h/i', 'ac29/d/h/i', 'ac29/e/h/i', 'ac29/f/h/i', 'ac30/d/h/i', 'ac30/e/h/i', 'ac30/f/h/i', 'ac31/d/h/i', 'ac31/e/h/i', 'ac31/f/h/i', 'ac32/d/h/i', 'ac32/e/h/i', 'ac32/f/h/i', 'ac33/d/h/i', 'ac33/e/h/i', 'ac33/f/h/i', 'ac34/d/h/i', 'ac34/e/h/i', 'ac34/f/h/i', 'ac35/d/h/i', 'ac35/e/h/i', 'ac35/f/h/i', 'ac36/d/h/i', 'ac36/e/h/i', 'ac36/f/h/i', 'ac37/d/h/i', 'ac37/e/h/i', 'ac37/f/h/i', 'ac38/d/h/i', 'ac38/e/h/i', 'ac38/f/h/i', 'ac39/d/h/i', 'ac39/e/h/i', 'ac39/f/h/i', 'ac40/d/h/i', 'ac40/e/h/i', 'ac40/f/h/i', 'ac41/d/h/i', 'ac41/e/h/i', 'ac41/f/h/i', 'ac42/d/h/i', 'ac42/e/h/i', 'ac42/f/h/i', 'ac43/d/h/i', 'ac43/e/h/i', 'ac43/f/h/i', 'ac44/d/h/i', 'ac44/e/h/i', 'ac44/f/h/i', 'ac45/d/h/i', 'ac45/e/h/i', 'ac45/f/h/i', 'ac46/d/h/i', 'ac46/e/h/i', 'ac46/f/h/i', 'ac47/d/h/i', 'ac47/e/h/i', 'ac47/f/h/i', 'ac48/d/h/i', 'ac48/e/h/i', 'ac48/f/h/i', 'ac49/d/h/i', 'ac49/e/h/i', 'ac49/f/h/i', 'ac50/d/h/i', 'ac50/e/h/i', 'ac50/f/h/i', 'agh/i']], + ['0{1..9} {10..20}', {}, ['01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20']], + ['a{0..3}d', {}, ['a0d', 'a1d', 'a2d', 'a3d']], + ['x{10..1}y', {}, ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']], + ['x{3..3}y', {}, ['x3y']], + ['{0..10,braces}', {}, ['0..10', 'braces']], + ['{3..3}', {}, ['3']], + ['{5..8}', {}, ['5', '6', '7', '8']], + ['**/{1..5}/a.js', {}, ['**/1/a.js', '**/2/a.js', '**/3/a.js', '**/4/a.js', '**/5/a.js']], + ['{braces,{0..10}}', {}, ['braces', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['./\\{x,y}/{a..z..3}/', {}, ['./{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/']], + ['x{{0..10},braces}y', {}, ['x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y', 'xbracesy']], + ['{braces,{0..10}}', {}, ['braces', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{{0..10},braces}', {}, ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']], + ['{{1..10..2},braces}', {}, ['1', '3', '5', '7', '9', 'braces']], + ['{{1..10},braces}', {}, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']], + ['{1.1..2.1}', {}, ['{1.1..2.1}']], + ['{1.1..~2.1}', {}, ['{1.1..~2.1}']], + ['{1..0f}', {}, ['{1..0f}']], + ['{1..10..ff}', {}, ['{1..10..ff}']], + ['{1..10.f}', {}, ['{1..10.f}']], + ['{1..10f}', {}, ['{1..10f}']], + ['{1..20..2f}', {}, ['{1..20..2f}']], + ['{1..20..f2}', {}, ['{1..20..f2}']], + ['{1..2f..2}', {}, ['{1..2f..2}']], + ['{1..ff..2}', {}, ['{1..ff..2}']], + ['{1..ff}', {}, ['{1..ff}']], + ['{1.20..2}', {}, ['{1.20..2}']], + ['a{0..3}d', {}, ['a0d', 'a1d', 'a2d', 'a3d']], + ['x{10..1}y', {}, ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']], + ['x{3..3}y', {}, ['x3y']], + ['{1..10}', {}, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{1..3}', {}, ['1', '2', '3']], + ['{1..9}', {}, ['1', '2', '3', '4', '5', '6', '7', '8', '9']], + ['{10..1}y', {}, ['10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y']], + ['{3..3}', {}, ['3']], + ['{5..8}', {}, ['5', '6', '7', '8']], + ['{-10..-1}', {}, ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1']], + ['{-20..0}', {}, ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']], + ['{0..-5}', {}, ['0', '-1', '-2', '-3', '-4', '-5']], + ['{9..-4}', {}, ['9', '8', '7', '6', '5', '4', '3', '2', '1', '0', '-1', '-2', '-3', '-4']], + ['0{1..9}/{10..20}', {}, ['01/10', '01/11', '01/12', '01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20', '02/10', '02/11', '02/12', '02/13', '02/14', '02/15', '02/16', '02/17', '02/18', '02/19', '02/20', '03/10', '03/11', '03/12', '03/13', '03/14', '03/15', '03/16', '03/17', '03/18', '03/19', '03/20', '04/10', '04/11', '04/12', '04/13', '04/14', '04/15', '04/16', '04/17', '04/18', '04/19', '04/20', '05/10', '05/11', '05/12', '05/13', '05/14', '05/15', '05/16', '05/17', '05/18', '05/19', '05/20', '06/10', '06/11', '06/12', '06/13', '06/14', '06/15', '06/16', '06/17', '06/18', '06/19', '06/20', '07/10', '07/11', '07/12', '07/13', '07/14', '07/15', '07/16', '07/17', '07/18', '07/19', '07/20', '08/10', '08/11', '08/12', '08/13', '08/14', '08/15', '08/16', '08/17', '08/18', '08/19', '08/20', '09/10', '09/11', '09/12', '09/13', '09/14', '09/15', '09/16', '09/17', '09/18', '09/19', '09/20']], + ['0{a..d}0', {}, ['0a0', '0b0', '0c0', '0d0']], + ['a/{b..d}/e', {}, ['a/b/e', 'a/c/e', 'a/d/e']], + ['{1..f}', { minimatch: false }, ['1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f']], + ['{a..A}', {}, ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']], + ['{A..a}', {}, ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a']], + ['{a..e}', {}, ['a', 'b', 'c', 'd', 'e']], + ['{A..E}', {}, ['A', 'B', 'C', 'D', 'E']], + ['{a..f}', {}, ['a', 'b', 'c', 'd', 'e', 'f']], + ['{a..z}', {}, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']], + ['{E..A}', {}, ['E', 'D', 'C', 'B', 'A']], + ['{f..1}', { minimatch: false }, ['f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '@', '?', '>', '=', '<', ';', ':', '9', '8', '7', '6', '5', '4', '3', '2', '1']], + ['{f..a}', {}, ['f', 'e', 'd', 'c', 'b', 'a']], + ['{f..f}', {}, ['f']], + ['a/{b..d}/e/{f..h}', {}, ['a/b/e/f', 'a/b/e/g', 'a/b/e/h', 'a/c/e/f', 'a/c/e/g', 'a/c/e/h', 'a/d/e/f', 'a/d/e/g', 'a/d/e/h']], + ['{-10..10}', {}, ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{1..10..1}', { optimize: false }, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{1..10..2}', { optimize: false }, ['1', '3', '5', '7', '9']], + ['{1..20..20}', { optimize: false }, ['1']], + ['{1..20..2}', { optimize: false }, ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']], + ['{10..0..2}', { optimize: false }, ['10', '8', '6', '4', '2', '0']], + ['{10..1..2}', { optimize: false }, ['10', '8', '6', '4', '2']], + ['{100..0..5}', { optimize: false }, ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']], + ['{2..10..1}', { optimize: false }, ['2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{2..10..2}', { optimize: false }, ['2', '4', '6', '8', '10']], + ['{2..10..3}', { optimize: false }, ['2', '5', '8']], + ['{a..z..2}', { optimize: false }, ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']], + ['{10..0..-2}', { optimize: false }, ['10', '8', '6', '4', '2', '0']], + ['{-1..-10..-2}', { optimize: false }, ['-1', '-3', '-5', '-7', '-9']], + ['{-1..-10..2}', { optimize: false }, ['-1', '-3', '-5', '-7', '-9']], + ['{-10..-2..2}', { optimize: false }, ['-10', '-8', '-6', '-4', '-2']], + ['{-2..-10..1}', { optimize: false }, ['-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']], + ['{-2..-10..2}', { optimize: false }, ['-2', '-4', '-6', '-8', '-10']], + ['{-2..-10..3}', { optimize: false }, ['-2', '-5', '-8']], + ['{-50..-0..5}', { optimize: false }, ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']], + ['{-9..9..3}', { optimize: false }, ['-9', '-6', '-3', '0', '3', '6', '9']], + ['{10..1..-2}', { optimize: false }, ['10', '8', '6', '4', '2']], + ['{100..0..-5}', { optimize: false }, ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']], + ['{a..e..2}', { optimize: false }, ['a', 'c', 'e']], + ['{E..A..2}', { optimize: false }, ['E', 'C', 'A']], + ['{a..z..2}', { optimize: false }, ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']], + ['{z..a..-2}', { optimize: false }, ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']], + ['{z..a..-2}', { optimize: false }, ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']], + ['{10..0..2}', { optimize: false }, ['10', '8', '6', '4', '2', '0']], + ['{10..0..-2}', { optimize: false }, ['10', '8', '6', '4', '2', '0']], + ['{-50..-0..5}', { optimize: false }, ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']], + ['../{1..3}/../foo', {}, ['../1/../foo', '../2/../foo', '../3/../foo']], + ['../{2..10..2}/../foo', { optimize: false }, ['../2/../foo', '../4/../foo', '../6/../foo', '../8/../foo', '../10/../foo']], + ['../{1..3}/../{a,b,c}/foo', {}, ['../1/../a/foo', '../1/../b/foo', '../1/../c/foo', '../2/../a/foo', '../2/../b/foo', '../2/../c/foo', '../3/../a/foo', '../3/../b/foo', '../3/../c/foo']], + ['./{a..z..3}/', { optimize: false }, ['./a/', './d/', './g/', './j/', './m/', './p/', './s/', './v/', './y/']], + ['./{"x,y"}/{a..z..3}/', { minimatch: false, optimize: false }, ['./{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/']], + ['a/{x,y}/{1..5}c{d,e}f.{md,txt}', {}, ['a/x/1cdf.md', 'a/x/1cdf.txt', 'a/x/1cef.md', 'a/x/1cef.txt', 'a/x/2cdf.md', 'a/x/2cdf.txt', 'a/x/2cef.md', 'a/x/2cef.txt', 'a/x/3cdf.md', 'a/x/3cdf.txt', 'a/x/3cef.md', 'a/x/3cef.txt', 'a/x/4cdf.md', 'a/x/4cdf.txt', 'a/x/4cef.md', 'a/x/4cef.txt', 'a/x/5cdf.md', 'a/x/5cdf.txt', 'a/x/5cef.md', 'a/x/5cef.txt', 'a/y/1cdf.md', 'a/y/1cdf.txt', 'a/y/1cef.md', 'a/y/1cef.txt', 'a/y/2cdf.md', 'a/y/2cdf.txt', 'a/y/2cef.md', 'a/y/2cef.txt', 'a/y/3cdf.md', 'a/y/3cdf.txt', 'a/y/3cef.md', 'a/y/3cef.txt', 'a/y/4cdf.md', 'a/y/4cdf.txt', 'a/y/4cef.md', 'a/y/4cef.txt', 'a/y/5cdf.md', 'a/y/5cdf.txt', 'a/y/5cef.md', 'a/y/5cef.txt']], + ['a/{x,{1..5},y}/c{d}e', {}, ['a/x/c{d}e', 'a/1/c{d}e', 'a/2/c{d}e', 'a/3/c{d}e', 'a/4/c{d}e', 'a/5/c{d}e', 'a/y/c{d}e']] + ]; + +describe('bash ranges - braces.expand()', () => { + for (const arr of fixtures as Fixture[]) { + if (typeof arr === 'string') continue; + const [pattern, options, expected] = arr; + if ((options as any).skip === true) continue; + test('should compile: ' + pattern, () => { + expect(braces.expand(pattern, options)).toEqual(expected); + }); + } +}); diff --git a/packages/node-utils/test/braces/bash-expanded-sets.test.ts b/packages/node-utils/test/braces/bash-expanded-sets.test.ts new file mode 100644 index 0000000..b2ba3bf --- /dev/null +++ b/packages/node-utils/test/braces/bash-expanded-sets.test.ts @@ -0,0 +1,305 @@ +import { describe, expect, test } from 'bun:test'; +import braces from '../../src/braces/index.ts'; + +type Fixture = [string, any, any]; + +const fixtures = [ + ['a/\\{b,c,d,{x,y}}{e,f\\}/g', {}, ['a/{b,c,d,x}{e,f}/g', 'a/{b,c,d,y}{e,f}/g']], + ['a/\\{b,c,d\\}\\{e,f\\}/g', {}, ['a/{b,c,d}{e,f}/g']], + ['a/\\{b,c,d\\}\\{e,f}/g', {}, ['a/{b,c,d}{e,f}/g']], + ['a/\\{b,c,d\\}{e,f}/g', {}, ['a/{b,c,d}e/g', 'a/{b,c,d}f/g']], + ['a/\\{b,c,d{x,y}}{e,f\\}/g', {}, ['a/{b,c,dx}{e,f}/g', 'a/{b,c,dy}{e,f}/g']], + ['a/\\{b,c,d}{e,f\\}/g', {}, ['a/{b,c,d}{e,f}/g']], + ['a/\\{b,c,d}{e,f}/g', {}, ['a/{b,c,d}e/g', 'a/{b,c,d}f/g']], + ['a/\\{x,y}/cde', {}, ['a/{x,y}/cde']], + ['a/\\{{b,c}{e,f}/g', {}, ['a/{be/g', 'a/{bf/g', 'a/{ce/g', 'a/{cf/g']], + ['a/\\{{b,c}{e,f}\\}/g', {}, ['a/{be}/g', 'a/{bf}/g', 'a/{ce}/g', 'a/{cf}/g']], + ['a/\\{{b,c}{e,f}}/g', {}, ['a/{be}/g', 'a/{bf}/g', 'a/{ce}/g', 'a/{cf}/g']], + ['a/b/{b,c,{d,e{f,g},{w,x}/{y,z}}}/h/i', {}, ['a/b/b/h/i', 'a/b/c/h/i', 'a/b/d/h/i', 'a/b/ef/h/i', 'a/b/eg/h/i', 'a/b/w/y/h/i', 'a/b/w/z/h/i', 'a/b/x/y/h/i', 'a/b/x/z/h/i']], + ['a/{b,c,d}{e,f}/g', {}, ['a/be/g', 'a/bf/g', 'a/ce/g', 'a/cf/g', 'a/de/g', 'a/df/g']], + ['a/{b,c\\,d}{e,f}/g', {}, ['a/be/g', 'a/bf/g', 'a/c,de/g', 'a/c,df/g']], + ['a/{b,c\\}}{e,f}/g', {}, ['a/be/g', 'a/bf/g', 'a/c}e/g', 'a/c}f/g']], + ['a/{b,c}', {}, ['a/b', 'a/c']], + ['a/{b,c}d{e,f}/g', {}, ['a/bde/g', 'a/bdf/g', 'a/cde/g', 'a/cdf/g']], + ['a/{b,c}{e,f}/g', {}, ['a/be/g', 'a/bf/g', 'a/ce/g', 'a/cf/g']], + ['a/{b,c}{e,f}{g,h,i}/k', {}, ['a/beg/k', 'a/beh/k', 'a/bei/k', 'a/bfg/k', 'a/bfh/k', 'a/bfi/k', 'a/ceg/k', 'a/ceh/k', 'a/cei/k', 'a/cfg/k', 'a/cfh/k', 'a/cfi/k']], + ['a/{b,{c,d},e}/f', {}, ['a/b/f', 'a/c/f', 'a/d/f', 'a/e/f']], + ['a/{b,{c,d}/{e,f},g}/h', {}, ['a/b/h', 'a/c/e/h', 'a/c/f/h', 'a/d/e/h', 'a/d/f/h', 'a/g/h']], + ['a/{b{c,d},e{f,g}h{i,j}}/k', {}, ['a/bc/k', 'a/bd/k', 'a/efhi/k', 'a/efhj/k', 'a/eghi/k', 'a/eghj/k']], + ['a/{b{c,d},e}/f', {}, ['a/bc/f', 'a/bd/f', 'a/e/f']], + ['a/{b{c,d}e{f,g}h{i,j}}/k', {}, ['a/{bcefhi}/k', 'a/{bcefhj}/k', 'a/{bceghi}/k', 'a/{bceghj}/k', 'a/{bdefhi}/k', 'a/{bdefhj}/k', 'a/{bdeghi}/k', 'a/{bdeghj}/k']], + ['a/{b{c,d}e{f,g},h{i,j}}/k', {}, ['a/bcef/k', 'a/bceg/k', 'a/bdef/k', 'a/bdeg/k', 'a/hi/k', 'a/hj/k']], + ['a/{x,z}{b,{c,d}/{e,f},g}/h', {}, ['a/xb/h', 'a/xc/e/h', 'a/xc/f/h', 'a/xd/e/h', 'a/xd/f/h', 'a/xg/h', 'a/zb/h', 'a/zc/e/h', 'a/zc/f/h', 'a/zd/e/h', 'a/zd/f/h', 'a/zg/h']], + ['a/{{a,b}/{c,d}}/z', {}, ['a/{a/c}/z', 'a/{a/d}/z', 'a/{b/c}/z', 'a/{b/d}/z']], + ['a/{{b,c}/{d,e}}', {}, ['a/{b/d}', 'a/{b/e}', 'a/{c/d}', 'a/{c/e}']], + ['a/{{b,c}/{d,e}}/f', {}, ['a/{b/d}/f', 'a/{b/e}/f', 'a/{c/d}/f', 'a/{c/e}/f']], + ['a{b}c', {}, ['a{b}c']], + ['foo {1,2} bar', {}, ['foo 1 bar', 'foo 2 bar']], + ['{ }', {}, ['{ }']], + ['{', {}, ['{']], + ['{a,b,{c,d},e}', {}, ['a', 'b', 'c', 'd', 'e']], + ['{a,b,{c,d}e}', {}, ['a', 'b', 'ce', 'de']], + ['{a,b,{c,d}}', {}, ['a', 'b', 'c', 'd']], + ['{a,b{c,d}}', {}, ['a', 'bc', 'bd']], + ['{a,b}/{c,d}', {}, ['a/c', 'a/d', 'b/c', 'b/d']], + ['{a,b}c,d\\}', {}, ['ac,d}', 'bc,d}']], + ['{a,b\\}c,d}', {}, ['a', 'b}c', 'd']], + ['{a,b}{c,d}', {}, ['ac', 'ad', 'bc', 'bd']], + ['{abc}', {}, ['{abc}']], + ['{b{c,d},e}', {}, ['bc', 'bd', 'e']], + ['{b{c,d},e}/f', {}, ['bc/f', 'bd/f', 'e/f']], + ['x,y,{abc},trie', {}, ['x,y,{abc},trie']], + ['{{a,b},{c,d}}', {}, ['a', 'b', 'c', 'd']], + ['{{a,b}/{c,d}}', {}, ['{a/c}', '{a/d}', '{b/c}', '{b/d}']], + ['{{a,b}/{c,d}}/z', {}, ['{a/c}/z', '{a/d}/z', '{b/c}/z', '{b/d}/z']], + ['{}', {}, ['{}']], + + // // should ignore globs + ['}', {}, ['}']], + + // 'should ignore globs', + + ['{generate,{assemble,update,verb}{file,-generate-*},generator}.js', {}, ['generate.js', 'assemblefile.js', 'assemble-generate-*.js', 'updatefile.js', 'update-generate-*.js', 'verbfile.js', 'verb-generate-*.js', 'generator.js']], + ['**/{foo,bar}.js', {}, ['**/foo.js', '**/bar.js']], + ['**/{a,b,c}/*.js', {}, ['**/a/*.js', '**/b/*.js', '**/c/*.js']], + ['**/{a,b,*}/*.js', {}, ['**/a/*.js', '**/b/*.js', '**/*/*.js']], + ['**/{**,b,*}/*.js', {}, ['**/**/*.js', '**/b/*.js', '**/*/*.js']], + ['/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, ['/usr/ucb/ex', '/usr/ucb/edit', '/usr/lib/ex', '/usr/lib/how_ex']], + ['ff{c,b,a}', {}, ['ffc', 'ffb', 'ffa']], + ['f{d,e,f}g', {}, ['fdg', 'feg', 'ffg']], + ['{a,b,c}', {}, ['a', 'b', 'c']], + ['{l,m,n}xyz', {}, ['lxyz', 'mxyz', 'nxyz']], + ['a/{a,b}/{c,d}/e', {}, ['a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e']], + ['a{b,c}d{e,f}g', {}, ['abdeg', 'abdfg', 'acdeg', 'acdfg']], + ['a/{x,y}/c{d,e}f.{md,txt}', {}, ['a/x/cdf.md', 'a/x/cdf.txt', 'a/x/cef.md', 'a/x/cef.txt', 'a/y/cdf.md', 'a/y/cdf.txt', 'a/y/cef.md', 'a/y/cef.txt']], + ['{a,b}{{a,b},a,b}', {}, ['aa', 'ab', 'aa', 'ab', 'ba', 'bb', 'ba', 'bb']], + ['a{b,c{d,e}f}g', {}, ['abg', 'acdfg', 'acefg']], + ['a{{x,y},z}b', {}, ['axb', 'ayb', 'azb']], + ['f{x,y{g,z}}h', {}, ['fxh', 'fygh', 'fyzh']], + ['a{b,c{d,e},h}x/z', {}, ['abx/z', 'acdx/z', 'acex/z', 'ahx/z']], + ['a{b,c{d,e},h}x{y,z}', {}, ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'ahxy', 'ahxz']], + ['a{b,c{d,e},{f,g}h}x{y,z}', {}, ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']], + + // 'should not expand escaped braces', + + ['\\{a,b,c,d,e}', {}, ['{a,b,c,d,e}']], + ['a/\\{b,c}/{d,e}/f', {}, ['a/{b,c}/d/f', 'a/{b,c}/e/f']], + ['a/\\{x,y}/cde', {}, ['a/{x,y}/cde']], + ['a/b/c/{x,y\\}', {}, ['a/b/c/{x,y}']], + ['a/{z,\\{a,b,c,d,e}/d', {}, ['a/z/d', 'a/{a/d', 'a/b/d', 'a/c/d', 'a/d/d', 'a/e/d']], + ['abcd{efgh', {}, ['abcd{efgh']], + ['{a,b\\}c,d}', {}, ['a', 'b}c', 'd']], + ['{abc}', {}, ['{abc}']], + ['{x,y,\\{a,b,c\\}}', {}, ['x', 'y', '{a', 'b', 'c}']], + ['{x,y,{abc},trie}', {}, ['x', 'y', '{abc}', 'trie']], + ['{x,y,{a,b,c\\}}', {}, ['{x,y,a', '{x,y,b', '{x,y,c}']], + + 'should not expand escaped commas', + + ['{x\\,y,\\{abc\\},trie}', {}, ['x,y', '{abc}', 'trie']], + ['a{b\\,c\\,d}e', {}, ['a{b,c,d}e']], + ['a{b\\,c}d', {}, ['a{b,c}d']], + ['{abc\\,def}', {}, ['{abc,def}']], + ['{abc\\,def,ghi}', {}, ['abc,def', 'ghi']], + ['a/{b,c}/{x\\,y}/d/e', {}, ['a/b/{x,y}/d/e', 'a/c/{x,y}/d/e']], + + 'should handle empty braces', + + ['{ }', {}, ['{ }']], + ['{', {}, ['{']], + ['{}', {}, ['{}']], + ['}', {}, ['}']], + + 'should escape braces when only one value is defined', + + ['a{b}c', {}, ['a{b}c']], + ['a/b/c{d}e', {}, ['a/b/c{d}e']], + + 'should escape closing braces when open is not defined', + + ['{a,b}c,d}', {}, ['ac,d}', 'bc,d}']], + + 'should not expand braces in sets with es6/bash-like variables', + + ['abc/${ddd}/xyz', {}, ['abc/${ddd}/xyz']], + ['a${b}c', {}, ['a${b}c']], + ['a/{${b},c}/d', {}, ['a/${b}/d', 'a/c/d']], + ['a${b,d}/{foo,bar}c', {}, ['a${b,d}/fooc', 'a${b,d}/barc']], + + 'should support sequence brace operators', + + ['ff{a,b,c}', {}, ['ffa', 'ffb', 'ffc']], + ['f{d,e,f}g', {}, ['fdg', 'feg', 'ffg']], + ['{a,b,c}', {}, ['a', 'b', 'c']], + ['{l,m,n}xyz', {}, ['lxyz', 'mxyz', 'nxyz']], + + 'should expand multiple sets', + + ['a/{a,b}/{c,d}/e', {}, ['a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e']], + ['a{b,c}d{e,f}g', {}, ['abdeg', 'abdfg', 'acdeg', 'acdfg']], + ['a/{x,y}/c{d,e}f.{md,txt}', {}, ['a/x/cdf.md', 'a/x/cdf.txt', 'a/x/cef.md', 'a/x/cef.txt', 'a/y/cdf.md', 'a/y/cdf.txt', 'a/y/cef.md', 'a/y/cef.txt']], + + 'should expand nested sets', + + ['a{b,c{d,e}f}g', {}, ['abg', 'acdfg', 'acefg']], + ['a{{x,y},z}b', {}, ['axb', 'ayb', 'azb']], + ['f{x,y{g,z}}h', {}, ['fxh', 'fygh', 'fyzh']], + ['a{b,c{d,e},h}x/z', {}, ['abx/z', 'acdx/z', 'acex/z', 'ahx/z']], + ['a{b,c{d,e},h}x{y,z}', {}, ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'ahxy', 'ahxz']], + ['a{b,c{d,e},{f,g}h}x{y,z}', {}, ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']], + ['a-{b{d,e}}-c', {}, ['a-{bd}-c', 'a-{be}-c']], + + 'should do nothing to glob characters', + + ['a/b/{d,e}/*.js', {}, ['a/b/d/*.js', 'a/b/e/*.js']], + ['a/**/c/{d,e}/f*.js', {}, ['a/**/c/d/f*.js', 'a/**/c/e/f*.js']], + ['a/**/c/{d,e}/f*.{md,txt}', {}, ['a/**/c/d/f*.md', 'a/**/c/d/f*.txt', 'a/**/c/e/f*.md', 'a/**/c/e/f*.txt']], + ['a/b/{d,e,[1-5]}/*.js', {}, ['a/b/d/*.js', 'a/b/e/*.js', 'a/b/[1-5]/*.js']], + + 'should work with leading and trailing commas', + ['a{b,}c', {}, ['abc', 'ac']], + ['a{,b}c', {}, ['ac', 'abc']], + + 'should handle spaces', + ['a{ ,c{d, },h}x', {}, ['a x', 'acdx', 'ac x', 'ahx']], + ['a{ ,c{d, },h} ', {}, ['a ', 'acd ', 'ac ', 'ah ']], + + 'see https://github.com/jonschlinkert/microequal/issues/66', + ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', {}, ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.html', '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.ejs']], + + 'should handle weirdly-formed brace expansions (fixed in post-bash-3.1)', + + ['a-{b{d,e}}-c', {}, ['a-{bd}-c', 'a-{be}-c']], + ['a-{bdef-{g,i}-c', {}, ['a-{bdef-g-c', 'a-{bdef-i-c']], + + // 'should not expand quoted strings', + + ['{"foo"}{1,2,3}', {}, ['{foo}1', '{foo}2', '{foo}3']], + ['{"foo"}{1,2,3}', { keepQuotes: true }, ['{"foo"}1', '{"foo"}2', '{"foo"}3']], + ['{"x,x"}', { keepQuotes: true }, ['{"x,x"}']], + ['{\'x,x\'}', { keepQuotes: true }, ['{\'x,x\'}']], + + 'should escape outer braces in nested non-sets', + + ['{a-{b,c,d}}', {}, ['{a-b}', '{a-c}', '{a-d}']], + ['{a,{a-{b,c,d}}}', {}, ['a', '{a-b}', '{a-c}', '{a-d}']], + + 'should escape imbalanced braces', + + ['abc{', {}, ['abc{']], + ['{abc{', {}, ['{abc{']], + ['{abc', {}, ['{abc']], + ['}abc', {}, ['}abc']], + ['ab{c', {}, ['ab{c']], + ['ab{c', {}, ['ab{c']], + ['{{a,b}', {}, ['{a', '{b']], + ['{a,b}}', {}, ['a}', 'b}']], + ['a{b{c{d,e}f}gh', {}, ['a{b{cdf}gh', 'a{b{cef}gh']], + ['a{b{c{d,e}f}g}h', {}, ['a{b{cdf}g}h', 'a{b{cef}g}h']], + ['f{x,y{{g,z}}h}', {}, ['fx', 'fy{g}h', 'fy{z}h']], + ['z{a,b},c}d', {}, ['za,c}d', 'zb,c}d']], + ['a{b{c{d,e}f{x,y{{g}h', {}, ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']], + ['f{x,y{{g}h', {}, ['f{x,y{{g}h']], + ['f{x,y{{g}}h', {}, ['f{x,y{{g}}h']], + ['a{b{c{d,e}f{x,y{}g}h', {}, ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh']], + ['f{x,y{}g}h', {}, ['fxh', 'fy{}gh']], + ['z{a,b{,c}d', {}, ['z{a,bd', 'z{a,bcd']], + + 'should expand sets containing the range operator', + + ['{..a,b}', {}, ['..a', 'b']], + ['{b,..a}', {}, ['b', '..a']], + ['{a..,b}', {}, ['a..', 'b']], + ['{...,b}', {}, ['...', 'b']], + ['{a,..,b}', {}, ['a', '..', 'b']], + ['..{a,b}', {}, ['..a', '..b']], + ['{{..,..},a,b}', {}, ['..', '..', 'a', 'b']], + ['{{{..,..}a,b}c,d}', {}, ['..ac', '..ac', 'bc', 'd']], + ['..{..,{..,{..,..}a,b}c,d}', {}, ['....', '....c', '....ac', '....ac', '..bc', '..d']], + ['..', {}, ['..']], + ['{,..}', {}, ['', '..']], + ['{..,}', {}, ['..', '']], + ['{..,..,..}', {}, ['..', '..', '..']], + ['{...,..,..}', {}, ['...', '..', '..']], + ['{..,...,..}', {}, ['..', '...', '..']], + ['{./../.,../../}', {}, ['./../.', '../../']], + ['{../../../,../../}', {}, ['../../../', '../../']], + ['{...,...,...}', {}, ['...', '...', '...']], + ['{.,..,.}', {}, ['.', '..', '.']], + [',{..,..,.},', {}, [',..,', ',..,', ',.,']], + ['{1..2,3..4}', {}, ['1..2', '3..4']], + ['{..2,3..4}', {}, ['..2', '3..4']], + ['{1..,2}', {}, ['1..', '2']], + ['{1..,..2..,..3}', {}, ['1..', '..2..', '..3']], + ['..{1..3}', {}, ['..1', '..2', '..3']], + ['..{1..3}{..,..}', {}, ['..1..', '..1..', '..2..', '..2..', '..3..', '..3..']], + + 'should not expand invalid sets containing the range operator', + + ['{..a,b', {}, ['{..a,b']], + ['{b,..a', {}, ['{b,..a']], + ['{a..,b', {}, ['{a..,b']], + ['{...,b', {}, ['{...,b']], + ['{a,..,b', {}, ['{a,..,b']], + ['..{a,b', {}, ['..{a,b']], + ['{{..,..},a,b', {}, ['{..,a,b', '{..,a,b']], + ['{{{..,..}a,b}c,d', {}, ['{..ac,d', '{..ac,d', '{bc,d']], + ['{{{..,..}a,bc,d}', {}, ['{..a', '{..a', '{bc', '{d']], + ['..{..,{..,{..,..}a,b}c,d', {}, ['..{..,..c,d', '..{..,..ac,d', '..{..,..ac,d', '..{..,bc,d']], + ['..', {}, ['..']], + ['{,..', {}, ['{,..']], + ['{..,', {}, ['{..,']], + ['{..,..,..', {}, ['{..,..,..']], + ['{...,..,..', {}, ['{...,..,..']], + ['{..,...,..', {}, ['{..,...,..']], + ['{./../.,../../', {}, ['{./../.,../../']], + ['{../../../,../../', {}, ['{../../../,../../']], + ['{...,...,...', {}, ['{...,...,...']], + ['{.,..,.', {}, ['{.,..,.']], + [',{..,..,.},', {}, [',..,', ',..,', ',.,']], + ['{1..2,3..4', {}, ['{1..2,3..4']], + ['{..2,3..4', {}, ['{..2,3..4']], + ['{1..,2', {}, ['{1..,2']], + ['{1..,..2..,..3', {}, ['{1..,..2..,..3']], + ['..{1..3', {}, ['..{1..3']], + ['..{1..3}{..,..', {}, ['..1{..,..', '..2{..,..', '..3{..,..']], + + ['..a,b}', {}, ['..a,b}']], + ['b,..a}', {}, ['b,..a}']], + ['a..,b}', {}, ['a..,b}']], + ['...,b}', {}, ['...,b}']], + ['a,..,b}', {}, ['a,..,b}']], + ['..a,b}', {}, ['..a,b}']], + ['{..,..},a,b}', {}, ['..,a,b}', '..,a,b}']], + ['{{..,..}a,b}c,d}', {}, ['..ac,d}', '..ac,d}', 'bc,d}']], + ['....,{..,{..,..}a,b}c,d}', {}, ['....,..c,d}', '....,..ac,d}', '....,..ac,d}', '....,bc,d}']], + ['..', {}, ['..']], + [',..}', {}, [',..}']], + ['..,}', {}, ['..,}']], + ['..,..,..}', {}, ['..,..,..}']], + ['...,..,..}', {}, ['...,..,..}']], + ['..,...,..}', {}, ['..,...,..}']], + ['./../.,../../}', {}, ['./../.,../../}']], + ['../../../,../../}', {}, ['../../../,../../}']], + ['...,...,...}', {}, ['...,...,...}']], + ['.,..,.}', {}, ['.,..,.}']], + [',..,..,.},', {}, [',..,..,.},']], + ['1..2,3..4}', {}, ['1..2,3..4}']], + ['..2,3..4}', {}, ['..2,3..4}']], + ['1..,2}', {}, ['1..,2}']], + ['1..,..2..,..3}', {}, ['1..,..2..,..3}']], + ['..1..3}', {}, ['..1..3}']], + ['..1..3}{..,..}', {}, ['..1..3}..', '..1..3}..']], + + ]; + +describe('bash sets - braces.expand()', () => { + for (const arr of fixtures as Fixture[]) { + if (typeof arr === 'string') continue; + const [pattern, options, expected] = arr; + if ((options as any).skip === true) continue; + test('should compile: ' + pattern, () => { + expect(braces.expand(pattern, options)).toEqual(expected); + }); + } +}); diff --git a/packages/node-utils/test/braces/bash-spec.test.ts b/packages/node-utils/test/braces/bash-spec.test.ts new file mode 100644 index 0000000..c9c188d --- /dev/null +++ b/packages/node-utils/test/braces/bash-spec.test.ts @@ -0,0 +1,231 @@ +import { describe, expect, test } from 'bun:test'; +import braces from '../../src/braces/index.ts'; + +type Fixture = [string, any, string[]]; + +const fixtures = [ + // Single paired quotes + ["''{x,y}", {}, ["x", "y"]], + ["''{x,y}", { keepQuotes: true }, ["''x", "''y"]], + ["a'b'c{x,y}", {}, ['abcx', 'abcy']], + ["a'b'c{x,y}", { keepQuotes: true }, ["a'b'cx", "a'b'cy"]], + ["'{x,x}'", {}, ['{x,x}']], + ["'{x,x}'", { keepQuotes: true }, ["'{x,x}'"]], + ["{'x,x'}", {}, ['{x,x}']], + ["{'x,x'}", { keepQuotes: true }, ["{'x,x'}"]], + ["{x','x}", {}, ['{x,x}']], + ["{x','x}", { keepQuotes: true }, ["{x','x}"]], + // Single unpaired quotes + ["'{x,y}", {}, ["'x", "'y"]], + ["'{x,y}", { keepQuotes: true }, ["'x", "'y"]], + ["a'bc{x,y}", {}, ["a'bcx", "a'bcy"]], + ["a'bc{x,y}", { keepQuotes: true }, ["a'bcx", "a'bcy"]], + // Double paired quotes + ['""{x,y}', {}, ['x', 'y']], + ['""{x,y}', { keepQuotes: true }, ['""x', '""y']], + ['a"b"c{x,y}', {}, ['abcx', 'abcy']], + ['a"b"c{x,y}', { keepQuotes: true }, ['a"b"cx', 'a"b"cy']], + ['"{x,x}"', {}, ['{x,x}']], + ['"{x,x}"', { keepQuotes: true }, ['"{x,x}"']], + ['{"x,x"}', {}, ['{x,x}']], + ['{"x,x"}', { keepQuotes: true }, ['{"x,x"}']], + ['{x","x}', {}, ['{x,x}']], + ['{x","x}', { keepQuotes: true }, ['{x","x}']], + // Double unpaired quotes + ['"{x,y}', {}, ['"x', '"y']], + ['"{x,y}', { keepQuotes: true }, ['"x', '"y']], + ['a"bc{x,y}', {}, ['a"bcx', 'a"bcy']], + ['a"bc{x,y}', { keepQuotes: true }, ['a"bcx', 'a"bcy']], + // Paired backticks + ['``{x,y}', {}, ['x', 'y']], + ['``{x,y}', { keepQuotes: true }, ['``x', '``y']], + ['a`b`c{x,y}', {}, ['abcx', 'abcy']], + ['a`b`c{x,y}', { keepQuotes: true }, ['a`b`cx', 'a`b`cy']], + ['`{x,x}`', {}, ['{x,x}']], + ['`{x,x}`', { keepQuotes: true }, ['`{x,x}`']], + ['{`x,x`}', {}, ['{x,x}']], + ['{`x,x`}', { keepQuotes: true }, ['{`x,x`}']], + ['{x`,`x}', {}, ['{x,x}']], + ['{x`,`x}', { keepQuotes: true }, ['{x`,`x}']], + // Unpaired backticks + ['`{x,y}', {}, ['`x', '`y']], + ['`{x,y}', { keepQuotes: true }, ['`x', '`y']], + ['a`bc{x,y}', {}, ['a`bcx', 'a`bcy']], + ['a`bc{x,y}', { keepQuotes: true }, ['a`bcx', 'a`bcy']], + // Mixed unpaired quotes + ['a\'b"c`{x,y}', {}, ['a\'b"c`x', 'a\'b"c`y']], + ['a\'b"c`{x,y}', { keepQuotes: true }, ['a\'b"c`x', 'a\'b"c`y']], + // Mixed quotes + [`a\\'"'"b{x,y}`, {}, [`a''bx`, `a''by`]], + ['a"\'`b`\'"c{x,y}', {}, ['a\'`b`\'cx', 'a\'`b`\'cy']], + ['a"\'`b`\'"c{x,y}', { keepQuotes: true }, ['a"\'`b`\'"cx', 'a"\'`b`\'"cy']], + // Escaped quotes + ["\\'{x,y}", {}, ["'x", "'y"]], + ["\\'{x,y}", { keepEscaping: true }, ["\\'x", "\\'y"]], + ["a'b{x,y}\\'", {}, ["a'bx'", "a'by'"]], + ["a'b{x,y}\\'", { keepEscaping: true }, ["a'bx\\'", "a'by\\'"]], + ["a'b{x,y}\\'", { keepQuotes: true }, ["a'bx'", "a'by'"]], + ["a'b{x,y}\\'", { keepEscaping: true, keepQuotes: true }, ["a'bx\\'", "a'by\\'"]], + ["a'b{x,y}\\''", {}, ["ab{x,y}\\'"]], + ["a'b{x,y}\\''", { keepQuotes: true }, ["a'b{x,y}\\''"]], + ["a'\\'b{x,y}", { keepQuotes: true }, ["a''bx", "a''by"]], + // Common + ['{1\\.2}', {}, ['{1.2}']], + ['{1\\.2}', { keepEscaping: true }, ['{1\\.2}']], + ['\'{a,b}{{a,b},a,b}\'', {}, ['{a,b}{{a,b},a,b}']], + ['A{b,{d,e},{f,g}}Z', {}, ['AbZ', 'AdZ', 'AeZ', 'AfZ', 'AgZ']], + ['PRE-{a,b}{{a,b},a,b}-POST', {}, ['PRE-aa-POST', 'PRE-ab-POST', 'PRE-aa-POST', 'PRE-ab-POST', 'PRE-ba-POST', 'PRE-bb-POST', 'PRE-ba-POST', 'PRE-bb-POST']], + ['\\{a,b}{{a,b},a,b}', {}, ['{a,b}a', '{a,b}b', '{a,b}a', '{a,b}b']], + ['{{a,b}', {}, ['{a', '{b']], + ['{a,b}}', {}, ['a}', 'b}']], + ['{,}', {}, ['', '']], + ['a{,}', {}, ['a', 'a']], + ['{,}b', {}, ['b', 'b']], + ['a{,}b', {}, ['ab', 'ab']], + ['a{b}c', {}, ['a{b}c']], + ['a{1..5}b', {}, ['a1b', 'a2b', 'a3b', 'a4b', 'a5b']], + ['a{01..5}b', {}, ['a01b', 'a02b', 'a03b', 'a04b', 'a05b']], + ['a{-01..5}b', {}, ['a-01b', 'a000b', 'a001b', 'a002b', 'a003b', 'a004b', 'a005b']], + ['a{-01..5..3}b', {}, ['a-01b', 'a002b', 'a005b']], + ['a{001..9}b', {}, ['a001b', 'a002b', 'a003b', 'a004b', 'a005b', 'a006b', 'a007b', 'a008b', 'a009b']], + ['a{b,c{d,e},{f,g}h}x{y,z', {}, ['abx{y,z', 'acdx{y,z', 'acex{y,z', 'afhx{y,z', 'aghx{y,z']], + ['a{b,c{d,e},{f,g}h}x{y,z\\}', {}, ['abx{y,z}', 'acdx{y,z}', 'acex{y,z}', 'afhx{y,z}', 'aghx{y,z}']], + ['a{b,c{d,e},{f,g}h}x{y,z}', {}, ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']], + ['a{b{c{d,e}f{x,y{{g}h', {}, ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']], + ['a{b{c{d,e}f{x,y{}g}h', {}, ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh']], + ['a{b{c{d,e}f{x,y}}g}h', {}, ['a{b{cdfx}g}h', 'a{b{cdfy}g}h', 'a{b{cefx}g}h', 'a{b{cefy}g}h']], + ['a{b{c{d,e}f}g}h', {}, ['a{b{cdf}g}h', 'a{b{cef}g}h']], + ['a{{x,y},z}b', {}, ['axb', 'ayb', 'azb']], + ['f{x,y{g,z}}h', {}, ['fxh', 'fygh', 'fyzh']], + ['f{x,y{{g,z}}h', {}, ['f{x,y{g}h', 'f{x,y{z}h']], + ['f{x,y{{g,z}}h}', {}, ['fx', 'fy{g}h', 'fy{z}h']], + ['f{x,y{{g}h', {}, ['f{x,y{{g}h']], + ['f{x,y{{g}}h', {}, ['f{x,y{{g}}h']], + ['f{x,y{}g}h', {}, ['fxh', 'fy{}gh']], + ['z{a,b{,c}d', {}, ['z{a,bd', 'z{a,bcd']], + ['z{a,b},c}d', {}, ['za,c}d', 'zb,c}d']], + ['{-01..5}', {}, ['-01', '000', '001', '002', '003', '004', '005']], + ['{-05..100..5}', {}, ['-05', '000', '005', '010', '015', '020', '025', '030', '035', '040', '045', '050', '055', '060', '065', '070', '075', '080', '085', '090', '095', '100']], + ['{-05..100}', {}, ['-05', '-04', '-03', '-02', '-01', '000', '001', '002', '003', '004', '005', '006', '007', '008', '009', '010', '011', '012', '013', '014', '015', '016', '017', '018', '019', '020', '021', '022', '023', '024', '025', '026', '027', '028', '029', '030', '031', '032', '033', '034', '035', '036', '037', '038', '039', '040', '041', '042', '043', '044', '045', '046', '047', '048', '049', '050', '051', '052', '053', '054', '055', '056', '057', '058', '059', '060', '061', '062', '063', '064', '065', '066', '067', '068', '069', '070', '071', '072', '073', '074', '075', '076', '077', '078', '079', '080', '081', '082', '083', '084', '085', '086', '087', '088', '089', '090', '091', '092', '093', '094', '095', '096', '097', '098', '099', '100']], + ['{0..5..2}', {}, ['0', '2', '4']], + ['{0001..05..2}', {}, ['0001', '0003', '0005']], + ['{0001..-5..2}', {}, ['0001', '-001', '-003', '-005']], + ['{0001..-5..-2}', {}, ['0001', '-001', '-003', '-005']], + ['{0001..5..-2}', {}, ['0001', '0003', '0005']], + ['{01..5}', {}, ['01', '02', '03', '04', '05']], + ['{1..05}', {}, ['01', '02', '03', '04', '05']], + ['{1..05..3}', {}, ['01', '04']], + ['{05..100}', {}, ['005', '006', '007', '008', '009', '010', '011', '012', '013', '014', '015', '016', '017', '018', '019', '020', '021', '022', '023', '024', '025', '026', '027', '028', '029', '030', '031', '032', '033', '034', '035', '036', '037', '038', '039', '040', '041', '042', '043', '044', '045', '046', '047', '048', '049', '050', '051', '052', '053', '054', '055', '056', '057', '058', '059', '060', '061', '062', '063', '064', '065', '066', '067', '068', '069', '070', '071', '072', '073', '074', '075', '076', '077', '078', '079', '080', '081', '082', '083', '084', '085', '086', '087', '088', '089', '090', '091', '092', '093', '094', '095', '096', '097', '098', '099', '100']], + ['{0a..0z}', {}, ['{0a..0z}']], + ['{a,b\\}c,d}', {}, ['a', 'b}c', 'd']], + ['{a,b{c,d}', {}, ['{a,bc', '{a,bd']], + ['{a,b}c,d}', {}, ['ac,d}', 'bc,d}']], + ['{a..F}', {}, ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F']], + ['{A..f}', {}, ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f']], + ['{a..Z}', {}, ['a', '`', '_', '^', ']', '\\', '[', 'Z']], + ['{A..z}', {}, ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']], + ['{z..A}', {}, ['z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']], + ['{Z..a}', {}, ['Z', '[', '\\', ']', '^', '_', '`', 'a']], + ['{a..F..2}', {}, ['a', '_', ']', '[', 'Y', 'W', 'U', 'S', 'Q', 'O', 'M', 'K', 'I', 'G']], + ['{A..f..02}', {}, ['A', 'C', 'E', 'G', 'I', 'K', 'M', 'O', 'Q', 'S', 'U', 'W', 'Y', '[', ']', '_', 'a', 'c', 'e']], + ['{a..Z..5}', {}, ['a', '\\']], + ['d{a..Z..5}b', {}, ['dab', 'd\\b']], + ['{A..z..10}', {}, ['A', 'K', 'U', '_', 'i', 's']], + ['{z..A..-2}', {}, ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b', '`', '^', '\\', 'Z', 'X', 'V', 'T', 'R', 'P', 'N', 'L', 'J', 'H', 'F', 'D', 'B']], + ['{Z..a..20}', {}, ['Z']], + ['{a{,b}', {}, ['{a', '{ab']], + ['{a\\},b}', {}, ['a}', 'b']], + ['{x,y{,}g}', {}, ['x', 'yg', 'yg']], + ['{x,y{}g}', {}, ['x', 'y{}g']], + ['{{a,b}', {}, ['{a', '{b']], + ['{{a,b},c}', {}, ['a', 'b', 'c']], + ['{{a,b}c}', {}, ['{ac}', '{bc}']], + ['{{a,b},}', {}, ['a', 'b', '']], + ['X{{a,b},}X', {}, ['XaX', 'XbX', 'XX']], + ['{{a,b},}c', {}, ['ac', 'bc', 'c']], + ['{{a,b}.}', {}, ['{a.}', '{b.}']], + ['{{a,b}}', {}, ['{a}', '{b}']], + ['X{a..#}X', {}, ['XaX', 'X`X', 'X_X', 'X^X', 'X]X', 'X\\X', 'X[X', 'XZX', 'XYX', 'XXX', 'XWX', 'XVX', 'XUX', 'XTX', 'XSX', 'XRX', 'XQX', 'XPX', 'XOX', 'XNX', 'XMX', 'XLX', 'XKX', 'XJX', 'XIX', 'XHX', 'XGX', 'XFX', 'XEX', 'XDX', 'XCX', 'XBX', 'XAX', 'X@X', 'X?X', 'X>X', 'X=X', 'X { + for (const arr of fixtures as Fixture[]) { + if (typeof arr === 'string') continue; + const [pattern, options, expected] = arr; + if ((options as any).skip === true) continue; + test('should compile: ' + pattern, () => { + expect(braces.expand(pattern, options)).toEqual(expected); + }); + } +}); diff --git a/packages/node-utils/test/braces/compile.test.ts b/packages/node-utils/test/braces/compile.test.ts new file mode 100644 index 0000000..ab198a0 --- /dev/null +++ b/packages/node-utils/test/braces/compile.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, test } from "bun:test"; +import { compile } from "../../src/braces/compile.ts"; +import { parse } from "../../src/braces/parse.ts"; + +describe("braces.compile()", () => { + describe("errors", () => { + test("should throw an error when invalid args are passed", () => { + expect(() => compile(undefined as any)).toThrow(); + }); + }); + + describe("invalid characters", () => { + test("should escape invalid bracket characters", () => { + expect(compile(parse("]{a,b,c}"))).toBe("\\](a|b|c)"); + }); + }); + + describe("sets", () => { + test("should support empty sets", () => { + expect(compile(parse("{a,}"))).toBe("(a|)"); + expect(compile(parse("{a,,}"))).toBe("(a|)"); + expect(compile(parse("{a,,,}"))).toBe("(a|)"); + expect(compile(parse("{a,,,,}"))).toBe("(a|)"); + expect(compile(parse("{a,,,,,}"))).toBe("(a|)"); + }); + }); + + describe("ranges", () => { + test("should escape braces with invalid ranges", () => { + expect(compile(parse("{a...b}"))).toBe("{a...b}"); + expect(compile(parse("{a...b}"), { escapeInvalid: true })).toBe( + "\\{a...b\\}", + ); + }); + + test("should expand brace patterns with both sets and ranges", () => { + expect(compile(parse("{a..e,z}"))).toBe("(a..e|z)"); + expect(compile(parse("{a..e,a..z}"))).toBe("(a..e|a..z)"); + }); + + test("should escape braces with too many range expressions", () => { + expect(compile(parse("{a..e..x..z}"))).toBe("{a..e..x..z}"); + expect(compile(parse("{a..e..x..z}"), { escapeInvalid: true })).toBe( + "\\{a..e..x..z\\}", + ); + }); + + test("should compile very simple numeric ranges", () => { + expect(compile(parse("{1..5}"))).toBe("([1-5])"); + }); + + test("should compile numeric ranges with increments", () => { + expect(compile(parse("{1..5..2}"))).toBe("(1|3|5)"); + }); + + test("should compile zero-padded numeric ranges", () => { + expect(compile(parse("{01..05}"))).toBe("(0[1-5])"); + }); + + test("should compile zero-padded numeric ranges with increments", () => { + expect(compile(parse("{01..05..2}"))).toBe("(01|03|05)"); + expect(compile(parse("{01..05..3}"))).toBe("(01|04)"); + }); + }); + + describe("invalid", () => { + test("should escape incomplete brace patterns", () => { + expect(compile(parse("]{a/b"))).toBe("\\]{a/b"); + expect(compile(parse("]{a/b"), { escapeInvalid: true })).toBe( + "\\]\\{a/b", + ); + }); + + test("should escape non-brace patterns (no sets or ranges)", () => { + expect(compile(parse("]{a/b}"))).toBe("\\]{a/b}"); + expect(compile(parse("]{a/b}"), { escapeInvalid: true })).toBe( + "\\]\\{a/b\\}", + ); + }); + }); +}); diff --git a/packages/node-utils/test/braces/expand.test.ts b/packages/node-utils/test/braces/expand.test.ts new file mode 100644 index 0000000..675a8b8 --- /dev/null +++ b/packages/node-utils/test/braces/expand.test.ts @@ -0,0 +1,176 @@ +import { describe, expect, test } from "bun:test"; +import braces from "../../src/braces/index.ts"; +import { expand } from "../../src/braces/expand.ts"; +import { parse } from "../../src/braces/parse.ts"; + +const equal = (input: string, expected: string[], options?: any) => { + expect(braces.expand(input, options)).toEqual(expected); +}; + +describe("unit tests from brace-expand", () => { + describe("extglobs", () => { + test("should split on commas when braces are inside extglobs", () => { + equal("*(a|{b|c,d})", ["*(a|b|c)", "*(a|d)"]); + }); + + test("should not split on commas in extglobs when inside braces", () => { + equal("{a,@(b,c)}", ["a", "@(b,c)"]); + equal("{a,*(b|c,d)}", ["a", "*(b|c,d)"]); + }); + }); + + describe("expand", () => { + test("should expand an AST", () => { + expect(expand(parse("a/{b,c}/d"))).toEqual(["a/b/d", "a/c/d"]); + }); + + test("should support expanded nested empty sets", () => { + equal("{`foo,bar`}", ["{`foo,bar`}"], { keepQuotes: true }); + equal("{\\`foo,bar\\`}", ["`foo", "bar`"], { keepQuotes: true }); + equal("{`foo,bar`}", ["{`foo,bar`}"], { keepQuotes: true }); + equal("{`foo\\,bar`}", ["{`foo\\,bar`}"], { keepQuotes: true }); + + equal("{`foo,bar`}", ["{foo,bar}"]); + equal("{\\`foo,bar\\`}", ["`foo", "bar`"]); + equal("{`foo,bar`}", ["{foo,bar}"]); + equal("{`foo\\,bar`}", ["{foo\\,bar}"]); + + equal("{a,\\\\{a,b}c}", ["a", "\\ac", "\\bc"]); + equal("{a,\\{a,b}c}", ["ac}", "{ac}", "bc}"]); + equal("{,eno,thro,ro}ugh", ["ugh", "enough", "through", "rough"]); + equal("{,{,eno,thro,ro}ugh}{,out}", [ + "", "out", "ugh", "ughout", "enough", "enoughout", "through", + "throughout", "rough", "roughout", + ]); + equal("{{,eno,thro,ro}ugh,}{,out}", [ + "ugh", "ughout", "enough", "enoughout", "through", "throughout", + "rough", "roughout", "", "out", + ]); + equal("{,{,a,b}z}{,c}", ["", "c", "z", "zc", "az", "azc", "bz", "bzc"]); + equal("{,{,a,b}z}{c,}", ["c", "", "zc", "z", "azc", "az", "bzc", "bz"]); + equal("{,{,a,b}z}{,c,}", [ + "", "c", "", "z", "zc", "z", "az", "azc", "az", "bz", "bzc", "bz", + ]); + equal("{,{,a,b}z}{c,d}", ["c", "d", "zc", "zd", "azc", "azd", "bzc", "bzd"]); + equal("{{,a,b}z,}{,c}", ["z", "zc", "az", "azc", "bz", "bzc", "", "c"]); + equal("{,a{,b}z,}{,c}", [ + "", "c", "az", "azc", "abz", "abzc", "", "c", + ]); + equal("{,a{,b},}{,c}", ["", "c", "a", "ac", "ab", "abc", "", "c"]); + equal("{,a{,b}}{,c}", ["", "c", "a", "ac", "ab", "abc"]); + equal("{,b}{,d}", ["", "d", "b", "bd"]); + equal("{a,b}{,d}", ["a", "ad", "b", "bd"]); + equal("{,a}{z,c}", ["z", "c", "az", "ac"]); + equal("{,{a,}}{z,c}", ["z", "c", "az", "ac", "z", "c"]); + equal("{,{,a}}{z,c}", ["z", "c", "z", "c", "az", "ac"]); + equal("{,{,a},}{z,c}", ["z", "c", "z", "c", "az", "ac", "z", "c"]); + equal("{{,,a}}{z,c}", ["{}z", "{}c", "{}z", "{}c", "{a}z", "{a}c"]); + equal("{{,a},}{z,c}", ["z", "c", "az", "ac", "z", "c"]); + equal("{,,a}{z,c}", ["z", "c", "z", "c", "az", "ac"]); + equal("{,{,}}{z,c}", ["z", "c", "z", "c", "z", "c"]); + equal("{,{a,b}}{,c}", ["", "c", "a", "ac", "b", "bc"]); + equal("{,{a,}}{,c}", ["", "c", "a", "ac", "", "c"]); + equal("{,{,b}}{,c}", ["", "c", "", "c", "b", "bc"]); + equal("{,{,}}{,c}", ["", "c", "", "c", "", "c"]); + equal("{,a}{,c}", ["", "c", "a", "ac"]); + equal("{,{,a}b}", ["", "b", "ab"]); + equal("{,b}", ["", "b"]); + equal("{,b{,a}}", ["", "b", "ba"]); + equal("{b,{,a}}", ["b", "", "a"]); + equal("{,b}{,d}", ["", "d", "b", "bd"]); + equal("{a,b}{,d}", ["a", "ad", "b", "bd"]); + }); + }); + + describe("brace expansion unit tests from brace-expand", () => { + describe("sequences", () => { + test("numeric sequences", () => { + equal("a{1..2}b{2..3}c", ["a1b2c", "a1b3c", "a2b2c", "a2b3c"]); + equal("{1..2}{2..3}", ["12", "13", "22", "23"]); + }); + + test("numeric sequences with step count", () => { + equal("{0..8..2}", ["0", "2", "4", "6", "8"]); + equal("{1..8..2}", ["1", "3", "5", "7"]); + }); + + test("numeric sequence with negative x / y", () => { + equal("{3..-2}", ["3", "2", "1", "0", "-1", "-2"]); + }); + + test("alphabetic sequences", () => { + equal("1{a..b}2{b..c}3", ["1a2b3", "1a2c3", "1b2b3", "1b2c3"]); + equal("{a..b}{b..c}", ["ab", "ac", "bb", "bc"]); + }); + + test("alphabetic sequences with step count", () => { + equal("{a..k..2}", ["a", "c", "e", "g", "i", "k"]); + equal("{b..k..2}", ["b", "d", "f", "h", "j"]); + }); + }); + + describe("dollar", () => { + test("ignores ${", () => { + equal("${1..3}", ["${1..3}"]); + equal("${a,b}${c,d}", ["${a,b}${c,d}"]); + equal("x${a,b}x${c,d}x", ["x${a,b}x${c,d}x"]); + }); + }); + + describe("empty option", () => { + test("should support empty sets", () => { + equal("-v{,,,,}", ["-v", "-v", "-v", "-v", "-v"]); + }); + }); + + describe("negative increments", () => { + test("should support negative steps", () => { + equal("{3..1}", ["3", "2", "1"]); + equal("{10..8}", ["10", "9", "8"]); + equal("{10..08}", ["10", "09", "08"]); + equal("{c..a}", ["c", "b", "a"]); + + equal("{4..0..2}", ["4", "2", "0"]); + equal("{4..0..-2}", ["4", "2", "0"]); + equal("{e..a..2}", ["e", "c", "a"]); + }); + }); + + describe("nested", () => { + test("should support nested sets", () => { + equal("{a,b{1..3},c}", ["a", "b1", "b2", "b3", "c"]); + equal("{{A..E},{a..e}}", [ + "A", "B", "C", "D", "E", "a", "b", "c", "d", "e", + ]); + equal("ppp{,config,oe{,conf}}", [ + "ppp", "pppconfig", "pppoe", "pppoeconf", + ]); + }); + }); + + describe("order", () => { + test("should expand in given order", () => { + equal("a{d,c,b}e", ["ade", "ace", "abe"]); + }); + }); + + describe("pad", () => { + test("should support padding", () => { + equal("{9..11}", ["9", "10", "11"]); + equal("{09..11}", ["09", "10", "11"]); + }); + }); + }); + + describe("additional brace expansion test", () => { + describe("sequences", () => { + test("zero-padded numeric sequences", () => { + equal("{008..012}", ["008", "009", "010", "011", "012"]); + }); + + test("zero-padded numeric sequences with increments", () => { + equal("{008..012..2}", ["008", "010", "012"]); + }); + }); + }); +}); diff --git a/packages/node-utils/test/braces/minimatch.test.ts b/packages/node-utils/test/braces/minimatch.test.ts new file mode 100644 index 0000000..571630e --- /dev/null +++ b/packages/node-utils/test/braces/minimatch.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, test } from "bun:test"; +import braces from "../../src/braces/index.ts"; + +describe("brace expansion (minimatch v3.0.3 units)", () => { + const units: [string, string[]][] = [ + [ + "a{b,c{d,e},{f,g}h}x{y,z}", + [ + "abxy", "abxz", "acdxy", "acdxz", "acexy", "acexz", "afhxy", "afhxz", + "aghxy", "aghxz", + ], + ], + ["a{1..5}b", ["a1b", "a2b", "a3b", "a4b", "a5b"]], + ["a{b}c", ["a{b}c"]], + ["a{00..05}b", ["a00b", "a01b", "a02b", "a03b", "a04b", "a05b"]], + ["z{a,b},c}d", ["za,c}d", "zb,c}d"]], + ["z{a,b{,c}d", ["z{a,bd", "z{a,bcd"]], + ["a{b{c{d,e}f}g}h", ["a{b{cdf}g}h", "a{b{cef}g}h"]], + [ + "a{b{c{d,e}f{x,y}}g}h", + ["a{b{cdfx}g}h", "a{b{cdfy}g}h", "a{b{cefx}g}h", "a{b{cefy}g}h"], + ], + [ + "a{b{c{d,e}f{x,y{}g}h", + ["a{b{cdfxh", "a{b{cdfy{}gh", "a{b{cefxh", "a{b{cefy{}gh"], + ], + ]; + + for (const [input, expected] of units) { + test(`should expand: ${input}`, () => { + expect(braces.expand(input)).toEqual(expected); + }); + } +}); diff --git a/packages/node-utils/test/braces/multiples.test.ts b/packages/node-utils/test/braces/multiples.test.ts new file mode 100644 index 0000000..48363ad --- /dev/null +++ b/packages/node-utils/test/braces/multiples.test.ts @@ -0,0 +1,124 @@ +import { describe, expect, test } from "bun:test"; +import braces from "../../src/braces/index.ts"; + +const patterns: [string, string[]][] = [ + ["-v{,,,,}", ["-v", "-v", "-v", "-v", "-v"]], + ["-v{,,,,}{,}", ["-v", "-v", "-v", "-v", "-v", "-v", "-v", "-v", "-v", "-v"]], + ["a/b{,}", ["a/b", "a/b"]], + ["a/{,}/b", ["a//b", "a//b"]], + ["a/{,}{c,d}/e", ["a/c/e", "a/d/e", "a/c/e", "a/d/e"]], + [ + "a/{a,b,{,}{,}{,},c}/b", + [ + "a/a/b", "a/b/b", "a//b", "a//b", "a//b", "a//b", "a//b", "a//b", "a//b", + "a//b", "a/c/b", + ], + ], + ["a/{a,b,{,},c}/b", ["a/a/b", "a/b/b", "a//b", "a//b", "a/c/b"]], + [ + "a/{a,b,{,}{,}{,}}/b", + [ + "a/a/b", "a/b/b", "a//b", "a//b", "a//b", "a//b", "a//b", "a//b", "a//b", + "a//b", + ], + ], + [ + "a/{b,cz{,}}/{d{,},ef}{,}", + [ + "a/b/d", "a/b/d", "a/b/d", "a/b/d", "a/b/ef", "a/b/ef", "a/cz/d", + "a/cz/d", "a/cz/d", "a/cz/d", "a/cz/ef", "a/cz/ef", "a/cz/d", "a/cz/d", + "a/cz/d", "a/cz/d", "a/cz/ef", "a/cz/ef", + ], + ], + [ + "a/{b,cz}{,}/{d{,},ef}{,}", + [ + "a/b/d", "a/b/d", "a/b/d", "a/b/d", "a/b/ef", "a/b/ef", "a/b/d", "a/b/d", + "a/b/d", "a/b/d", "a/b/ef", "a/b/ef", "a/cz/d", "a/cz/d", "a/cz/d", + "a/cz/d", "a/cz/ef", "a/cz/ef", "a/cz/d", "a/cz/d", "a/cz/d", "a/cz/d", + "a/cz/ef", "a/cz/ef", + ], + ], + ["a/{b,c{,}}", ["a/b", "a/c", "a/c"]], + [ + "a/{b,c{,}}/{,}", + ["a/b/", "a/b/", "a/c/", "a/c/", "a/c/", "a/c/"], + ], + ["a/{b,c}/{,}", ["a/b/", "a/b/", "a/c/", "a/c/"]], + [ + "a/{b,c}{,}/d{,}", + ["a/b/d", "a/b/d", "a/b/d", "a/b/d", "a/c/d", "a/c/d", "a/c/d", "a/c/d"], + ], + [ + "a/{b,c}{,}/{d,e{,}}", + [ + "a/b/d", "a/b/e", "a/b/e", "a/b/d", "a/b/e", "a/b/e", "a/c/d", "a/c/e", + "a/c/e", "a/c/d", "a/c/e", "a/c/e", + ], + ], + [ + "a/{b,c}{,}/{d,e}{,}", + [ + "a/b/d", "a/b/d", "a/b/e", "a/b/e", "a/b/d", "a/b/d", "a/b/e", "a/b/e", + "a/c/d", "a/c/d", "a/c/e", "a/c/e", "a/c/d", "a/c/d", "a/c/e", "a/c/e", + ], + ], + [ + "a/{b,c}{,}/{d{,},e}{,}", + [ + "a/b/d", "a/b/d", "a/b/d", "a/b/d", "a/b/e", "a/b/e", "a/b/d", "a/b/d", + "a/b/d", "a/b/d", "a/b/e", "a/b/e", "a/c/d", "a/c/d", "a/c/d", "a/c/d", + "a/c/e", "a/c/e", "a/c/d", "a/c/d", "a/c/d", "a/c/d", "a/c/e", "a/c/e", + ], + ], + [ + "a/{c,d}/{x,y{,}}/e", + ["a/c/x/e", "a/c/y/e", "a/c/y/e", "a/d/x/e", "a/d/y/e", "a/d/y/e"], + ], + ["a/{c,d}{,}/e", ["a/c/e", "a/c/e", "a/d/e", "a/d/e"]], + ["a{,,,,,}", ["a", "a", "a", "a", "a", "a"]], + [ + "a{,,,,,}{,}", + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ], + [ + "a{,,,,,}{,,}", + Array(18).fill("a"), + ], + [ + "a{,,,,,}{,,}{,}", + Array(36).fill("a"), + ], + ["a{,,,,}", ["a", "a", "a", "a", "a"]], + ["a{,,,}", ["a", "a", "a", "a"]], + ["a{,,}", ["a", "a", "a"]], + ["a{,,}{,,}{,,}{,}/b", Array(54).fill("a/b")], + ["a{,,}{,}", ["a", "a", "a", "a", "a", "a"]], + ["a{,}", ["a", "a"]], + ["a{,}/{c,d}/e", ["a/c/e", "a/d/e", "a/c/e", "a/d/e"]], + ["a{,}b", ["ab", "ab"]], + ["a{,}{,}", ["a", "a", "a", "a"]], + ["a{,}{,}{,}", Array(8).fill("a")], + ["a{,}{,}{,}{,}", Array(16).fill("a")], + ["one/{a{,}{,}}/{b/c{,,}{,}{,,}{,}}/two", Array(144).fill("one/{a}/{b/c}/two")], + ["{,}", ["", ""]], + ["{,}a/{,}", ["a/", "a/", "a/", "a/"]], + ["{,}{,}", ["", "", "", ""]], + [ + "{a,b{,}{,}{,},c}d", + ["ad", "bd", "bd", "bd", "bd", "bd", "bd", "bd", "bd", "cd"], + ], + [ + "{a,b{,}{,}{,}}", + ["a", "b", "b", "b", "b", "b", "b", "b", "b"], + ], + ["{a{,,}b{,}}", ["{ab}", "{ab}", "{ab}", "{ab}", "{ab}", "{ab}"]], +]; + +describe("multiples", () => { + for (const [input, expected] of patterns) { + test(`should expand: ${input}`, () => { + expect(braces.expand(input)).toEqual(expected); + }); + } +}); diff --git a/packages/node-utils/test/braces/parse.test.ts b/packages/node-utils/test/braces/parse.test.ts new file mode 100644 index 0000000..005ea03 --- /dev/null +++ b/packages/node-utils/test/braces/parse.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, test } from "bun:test"; +import { parse } from "../../src/braces/parse.ts"; + +describe("braces.parse()", () => { + describe("errors", () => { + test("should throw an error when string exceeds max safe length", () => { + const MAX_LENGTH = 1024 * 64; + expect(() => parse(".".repeat(MAX_LENGTH + 2))).toThrow(); + }); + }); + + describe("valid", () => { + test("should return an AST", () => { + const ast = parse("a/{b,c}/d"); + const brace = ast.nodes.find((node: any) => node.type === "brace"); + expect(brace).toBeTruthy(); + expect(brace.nodes.length).toBe(5); + }); + + test("should ignore braces inside brackets", () => { + const ast = parse("a/[{b,c}]/d"); + expect(ast.nodes[1].type).toBe("text"); + expect(ast.nodes[1].value).toBe("a/[{b,c}]/d"); + }); + + test("should parse braces with brackets inside", () => { + const ast = parse("a/{a,b,[{c,d}]}/e"); + const brace = ast.nodes[2]; + const bracket = brace.nodes.find( + (node: any) => node.value && node.value[0] === "[", + ); + expect(bracket).toBeTruthy(); + expect(bracket.value).toBe("[{c,d}]"); + }); + }); + + describe("invalid", () => { + test("should escape standalone closing braces", () => { + const one = parse("}"); + expect(one.nodes[1].type).toBe("text"); + expect(one.nodes[1].value).toBe("}"); + + const two = parse("a}b"); + expect(two.nodes[1].type).toBe("text"); + expect(two.nodes[1].value).toBe("a}b"); + }); + }); +}); diff --git a/packages/node-utils/test/braces/readme.test.ts b/packages/node-utils/test/braces/readme.test.ts new file mode 100644 index 0000000..7bc2fa1 --- /dev/null +++ b/packages/node-utils/test/braces/readme.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, test } from "bun:test"; +import braces from "../../src/braces/index.ts"; + +describe("Examples from README.md", () => { + describe("Brace Expansion vs. Compilation", () => { + test("Compiled", () => { + expect(braces("a/{x,y,z}/b")).toEqual(["a/(x|y|z)/b"]); + expect(braces(["a/{01..20}/b", "a/{1..5}/b"])).toEqual([ + "a/(0[1-9]|1[0-9]|20)/b", + "a/([1-5])/b", + ]); + }); + + test("Expanded", () => { + expect(braces("a/{x,y,z}/b", { expand: true })).toEqual([ + "a/x/b", + "a/y/b", + "a/z/b", + ]); + expect(braces.expand("{01..10}")).toEqual([ + "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", + ]); + }); + }); + + describe("Sequences", () => { + test("first set of examples", () => { + expect(braces.expand("{1..3}")).toEqual(["1", "2", "3"]); + expect(braces.expand("a/{1..3}/b")).toEqual(["a/1/b", "a/2/b", "a/3/b"]); + expect(braces("{a..c}", { expand: true })).toEqual(["a", "b", "c"]); + expect(braces("foo/{a..c}", { expand: true })).toEqual([ + "foo/a", + "foo/b", + "foo/c", + ]); + }); + + test("zero-padding examples", () => { + expect(braces("a/{01..03}/b")).toEqual(["a/(0[1-3])/b"]); + expect(braces("a/{001..300}/b")).toEqual([ + "a/(00[1-9]|0[1-9][0-9]|[12][0-9]{2}|300)/b", + ]); + }); + }); +}); diff --git a/packages/node-utils/test/braces/regression.test.ts b/packages/node-utils/test/braces/regression.test.ts new file mode 100644 index 0000000..98821c6 --- /dev/null +++ b/packages/node-utils/test/braces/regression.test.ts @@ -0,0 +1,437 @@ +import { describe, expect, test } from 'bun:test'; +import braces from '../../src/braces/index.ts'; + +const equal = (input: string, expected: string[], options?: any) => { + expect(braces.expand(input, options)).toEqual(expected); +}; + + + + +describe('braces tests from 1.8.5', () => { + test('braces', () => { + equal('ff{c,b,a}', ['ffc', 'ffb', 'ffa']); + equal('f{d,e,f}g', ['fdg', 'feg', 'ffg']); + equal('{l,n,m}xyz', ['lxyz', 'nxyz', 'mxyz']); + equal('{abc\\,d,ef}', ['abc,d', 'ef']); + equal('{abc}', ['{abc}']); + + equal('\\{a,b,c,d,e}', ['{a,b,c,d,e}']); + equal('{x,y,\\{a,b,c}}', ['x}', 'y}', '{a}', 'b}', 'c}']); + equal('{x\\,y,\\{abc\\},trie}', ['x,y', '{abc}', 'trie']); + + equal('/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', ['/usr/ucb/ex', '/usr/ucb/edit', '/usr/lib/ex', '/usr/lib/how_ex']); + + equal('{}', ['{}']); + equal('{ }', ['{ }']); + equal('}', ['}']); + equal('{', ['{']); + equal('abcd{efgh', ['abcd{efgh']); + + equal('foo {1,2} bar', ['foo 1 bar', 'foo 2 bar']); + }); + + test('new sequence brace operators', () => { + equal('{1..10}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{0..10,braces}', ['0..10', 'braces']); + equal('{braces,{0..10}}', ['braces', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{{0..10},braces}', ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']); + equal('x{{0..10},braces}y', ['x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y', 'xbracesy']); + }); + + test('ranges', () => { + equal('{3..3}', ['3']); + equal('x{3..3}y', ['x3y']); + equal('{10..1}', ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1']); + equal('{10..1}y', ['10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y']); + equal('x{10..1}y', ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']); + equal('{a..f}', ['a', 'b', 'c', 'd', 'e', 'f']); + equal('{f..a}', ['f', 'e', 'd', 'c', 'b', 'a']); + + equal('{a..A}', ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']); + equal('{A..a}', ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a']); + + equal('{f..f}', ['f']); + equal('0{1..9} {10..20}', ['01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20']); + }); + + test('mixes are incorrectly-formed brace expansions', () => { + // the first one is valid, but Bash fails on it + equal('{1..f}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f']); + equal('{f..1}', ['f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '@', '?', '>', '=', '<', ';', ':', '9', '8', '7', '6', '5', '4', '3', '2', '1']); + }); + + test('do negative numbers work?', () => { + equal('{-1..-10}', ['-1', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); + equal('{-20..0}', ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']); + }); + + test('weirdly-formed brace expansions -- fixed in post-bash-3.1', () => { + equal('{-1..-10}', ['-1', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); + equal('{-20..0}', ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']); + equal('a-{b{d,e}}-c', ['a-{bd}-c', 'a-{be}-c']); + + equal('a-{bdef-{g,i}-c', ['a-{bdef-g-c', 'a-{bdef-i-c']); + + equal('{"klklkl"}{1,2,3}', ['{klklkl}1', '{klklkl}2', '{klklkl}3']); + equal('{"x,x"}', ['{x,x}']); + }); + + test('numerical ranges with steps', () => { + equal('{1..10..2}', ['1', '3', '5', '7', '9']); + equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); + + equal('{10..1..-2}', ['10', '8', '6', '4', '2']); + equal('{10..1..2}', ['10', '8', '6', '4', '2']); + + equal('{1..20..2}', ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']); + equal('{1..20..20}', ['1']); + + equal('{100..0..5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + equal('{100..0..-5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + }); + + test('alpha ranges with steps', () => { + equal('{a..z}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); + equal('{a..z..2}', ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']); + equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); + }); + + test('make sure brace expansion handles ints > 2**31 - 1 using intmax_t', () => { + equal('{2147483645..2147483649}', ['2147483645', '2147483646', '2147483647', '2147483648', '2147483649']); + }); + + test('unwanted zero-padding -- fixed post-bash-4.0', () => { + equal('{10..0..2}', ['10', '8', '6', '4', '2', '0']); + equal('{10..0..-2}', ['10', '8', '6', '4', '2', '0']); + equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); + }); + + test('bad', () => { + equal('{1..10.f}', ['{1..10.f}']); + equal('{1..ff}', ['{1..ff}']); + equal('{1..10..ff}', ['{1..10..ff}']); + equal('{1.20..2}', ['{1.20..2}']); + equal('{1..20..f2}', ['{1..20..f2}']); + equal('{1..20..2f}', ['{1..20..2f}']); + equal('{1..2f..2}', ['{1..2f..2}']); + equal('{1..ff..2}', ['{1..ff..2}']); + equal('{1..ff}', ['{1..ff}']); + equal('{1..0f}', ['{1..0f}']); + equal('{1..10f}', ['{1..10f}']); + equal('{1..10.f}', ['{1..10.f}']); + equal('{1..10.f}', ['{1..10.f}']); + }); +}); + +describe('bash tests', () => { + describe('brace expansion', () => { + test('should return an empty array when no braces are found', () => { + equal('', []); + }); + + test('should expand emty sets', () => { + equal('a{,}', ['a', 'a']); + equal('{,}b', ['b', 'b']); + equal('a{,}b', ['ab', 'ab']); + equal('a{,}', ['a', 'a']); + equal('a{,}{,}', ['a', 'a', 'a', 'a']); + equal('a{,}{,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']); + equal('{a,b{,}{,}{,}}', ['a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b']); + equal('a{,}/{c,d}/e', ['a/c/e', 'a/d/e', 'a/c/e', 'a/d/e']); + equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'cd']); + }); + + test('should eliminate dupes in repeated strings', () => { + equal('a{,}', ['a'], { nodupes: true }); + equal('a{,}{,}', ['a'], { nodupes: true }); + equal('a{,}{,}{,}', ['a'], { nodupes: true }); + equal('{a,b{,}{,}{,}}', ['a', 'b'], { nodupes: true }); + equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'cd'], { nodupes: true }); + equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'cd'], { nodupes: true }); + }); + + test('should work with no braces', () => { + equal('abc', ['abc']); + }); + + test('should work with no commas', () => { + equal('a{b}c', ['a{b}c']); + }); + + test('should work with no commas in `bash` mode', () => { + equal('a{b}c', ['a{b}c']); + }); + + test('should handle spaces', () => { + equal('a{ ,c{d, },h}x', ['a x', 'acdx', 'ac x', 'ahx']); + equal('a{ ,c{d, },h} ', ['a ', 'acd ', 'ac ', 'ah ']); + + // see https://github.com/jonschlinkert/micromatch/issues/66 + equal('/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', [ + '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.html', + '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.ejs' + ]); + }); + + test('should handle empty braces', () => { + equal('{ }', ['{ }']); + equal('{}', ['{}']); + equal('}', ['}']); + equal('{', ['{']); + equal('{,}', ['', '']); + }); + + test('should handle imbalanced braces', () => { + equal('a-{bdef-{g,i}-c', ['a-{bdef-g-c', 'a-{bdef-i-c']); + equal('abc{', ['abc{']); + equal('{abc{', ['{abc{']); + equal('{abc', ['{abc']); + equal('}abc', ['}abc']); + equal('ab{c', ['ab{c']); + equal('ab{c', ['ab{c']); + equal('{{a,b}', ['{a', '{b']); + equal('{a,b}}', ['a}', 'b}']); + equal('abcd{efgh', ['abcd{efgh']); + equal('a{b{c{d,e}f}g}h', ['a{b{cdf}g}h', 'a{b{cef}g}h']); + equal('f{x,y{{g,z}}h}', ['fx', 'fy{g}h', 'fy{z}h']); + equal('z{a,b},c}d', ['za,c}d', 'zb,c}d']); + equal('a{b{c{d,e}f{x,y{{g}h', ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']); + equal('f{x,y{{g}h', ['f{x,y{{g}h']); + equal('f{x,y{{g}}h', ['f{x,y{{g}}h']); + equal('a{b{c{d,e}f{x,y{}g}h', ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh']); + equal('f{x,y{}g}h', ['fxh', 'fy{}gh']); + equal('z{a,b{,c}d', ['z{a,bd', 'z{a,bcd']); + }); + + test('should handle invalid braces in `bash mode`:', () => { + equal('a{b{c{d,e}f}g}h', ['a{b{cdf}g}h', 'a{b{cef}g}h']); + equal('f{x,y{{g,z}}h}', ['fx', 'fy{g}h', 'fy{z}h']); + equal('z{a,b},c}d', ['za,c}d', 'zb,c}d']); + equal('a{b{c{d,e}f{x,y{{g}h', ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']); + equal('f{x,y{{g}h', ['f{x,y{{g}h']); + equal('f{x,y{{g}}h', ['f{x,y{{g}}h']); + }); + + test('should return invalid braces:', () => { + equal('{0..10,braces}', ['0..10', 'braces']); + }); + + test('should not expand quoted strings.', () => { + equal('{"x,x"}', ['{x,x}']); + equal('{"klklkl"}{1,2,3}', ['{klklkl}1', '{klklkl}2', '{klklkl}3']); + }); + + test('should work with one value', () => { + equal('a{b}c', ['a{b}c']); + equal('a/b/c{d}e', ['a/b/c{d}e']); + }); + + test('should work with one value in `bash` mode', () => { + equal('a{b}c', ['a{b}c']); + equal('a/b/c{d}e', ['a/b/c{d}e']); + }); + + test('should work with nested non-sets', () => { + equal('foo {1,2} bar', ['foo 1 bar', 'foo 2 bar']); + equal('{a-{b,c,d}}', ['{a-b}', '{a-c}', '{a-d}']); + equal('{a,{a-{b,c,d}}}', ['a', '{a-b}', '{a-c}', '{a-d}']); + }); + + test('should work with nested non-sets in `bash` mode', () => { + equal('{a-{b,c,d}}', ['{a-b}', '{a-c}', '{a-d}']); + equal('{a,{a-{b,c,d}}}', ['a', '{a-b}', '{a-c}', '{a-d}']); + }); + + test('should not expand dots with leading slashes (escaped or paths).', () => { + equal('a{b,c/*/../d}e', ['abe', 'ac/*/../de']); + equal('a{b,b,c/../b}d', ['abd', 'abd', 'ac/../bd']); + }); + + test('should work with commas.', () => { + equal('a{b,}c', ['abc', 'ac']); + equal('a{,b}c', ['ac', 'abc']); + }); + + test('should expand sets', () => { + equal('a/{x,y}/cde', ['a/x/cde', 'a/y/cde']); + equal('a/b/c/{x,y}', ['a/b/c/x', 'a/b/c/y']); + equal('ff{c,b,a}', ['ffc', 'ffb', 'ffa']); + equal('f{d,e,f}g', ['fdg', 'feg', 'ffg']); + equal('{l,n,m}xyz', ['lxyz', 'nxyz', 'mxyz']); + equal('{x,y,{abc},trie}', ['x', 'y', '{abc}', 'trie']); + }); + + test('should expand multiple sets', () => { + equal('a/{a,b}/{c,d}/e', ['a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e']); + equal('a{b,c}d{e,f}g', ['abdeg', 'abdfg', 'acdeg', 'acdfg']); + equal('a/{x,y}/c{d,e}f.{md,txt}', ['a/x/cdf.md', 'a/x/cdf.txt', 'a/x/cef.md', 'a/x/cef.txt', 'a/y/cdf.md', 'a/y/cdf.txt', 'a/y/cef.md', 'a/y/cef.txt']); + }); + + test('should expand nested sets', () => { + equal('a/{b,c,{d,e}}/g', ['a/b/g', 'a/c/g', 'a/d/g', 'a/e/g']); + equal('a/{a,b}/{c,d}/e', ['a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e']); + equal('{a,b}{{a,b},a,b}', ['aa', 'ab', 'aa', 'ab', 'ba', 'bb', 'ba', 'bb']); + equal('/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', ['/usr/ucb/ex', '/usr/ucb/edit', '/usr/lib/ex', '/usr/lib/how_ex']); + equal('a{b,c{d,e}f}g', ['abg', 'acdfg', 'acefg']); + equal('a{{x,y},z}b', ['axb', 'ayb', 'azb']); + equal('f{x,y{g,z}}h', ['fxh', 'fygh', 'fyzh']); + equal('a{b,c{d,e},h}x/z', ['abx/z', 'acdx/z', 'acex/z', 'ahx/z']); + equal('a{b,c{d,e},h}x{y,z}', ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'ahxy', 'ahxz']); + equal('a{b,c{d,e},{f,g}h}x{y,z}', ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']); + equal('a-{b{d,e}}-c', ['a-{bd}-c', 'a-{be}-c']); + }); + + test('should expand with globs.', () => { + equal('a/b/{d,e}/*.js', ['a/b/d/*.js', 'a/b/e/*.js']); + equal('a/**/c/{d,e}/f*.js', ['a/**/c/d/f*.js', 'a/**/c/e/f*.js']); + equal('a/**/c/{d,e}/f*.{md,txt}', ['a/**/c/d/f*.md', 'a/**/c/d/f*.txt', 'a/**/c/e/f*.md', 'a/**/c/e/f*.txt']); + }); + + test('should expand with brackets.', () => { + equal('a/b/{d,e,[1-5]}/*.js', ['a/b/d/*.js', 'a/b/e/*.js', 'a/b/[1-5]/*.js']); + }); + }); + + describe('escaping:', () => { + test('should not expand strings with es6/bash-like variables.', () => { + equal('abc/${ddd}/xyz', ['abc/${ddd}/xyz']); + equal('a${b}c', ['a${b}c']); + equal('a/{${b},c}/d', ['a/${b}/d', 'a/c/d']); + equal('a${b,d}/{foo,bar}c', ['a${b,d}/fooc', 'a${b,d}/barc']); + }); + + test('should not expand escaped commas.', () => { + equal('a{b\\,c}d', ['a{b,c}d']); + equal('a{b\\,c\\,d}e', ['a{b,c,d}e']); + equal('{abc\\,def}', ['{abc,def}']); + equal('{abc\\,def,ghi}', ['abc,def', 'ghi']); + equal('a/{b,c}/{x\\,y}/d/e', ['a/b/{x,y}/d/e', 'a/c/{x,y}/d/e']); + }); + + test('should return sets with escaped commas in `bash` mode.', () => { + equal('a/{b,c}/{x\\,y}/d/e', ['a/b/{x,y}/d/e', 'a/c/{x,y}/d/e']); + }); + + test('should not expand escaped braces.', () => { + equal('{a,b\\}c,d}', ['a', 'b}c', 'd']); + equal('\\{a,b,c,d,e}', ['{a,b,c,d,e}']); + equal('a/{b,\\{a,b,c,d,e}/d', ['a/b/d', 'a/{a/d', 'a/b/d', 'a/c/d', 'a/d/d', 'a/e/d']); + equal('a/\\{b,c}/{d,e}/f', ['a/{b,c}/d/f', 'a/{b,c}/e/f']); + }); + + test('should not expand escaped braces or commas.', () => { + equal('{x\\,y,\\{abc\\},trie}', ['x,y', '{abc}', 'trie']); + }); + }); +}); + +describe('range expansion', () => { + test('should expand numerical ranges', () => { + equal('a{0..3}d', ['a0d', 'a1d', 'a2d', 'a3d']); + equal('x{10..1}y', ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']); + equal('x{3..3}y', ['x3y']); + equal('{-1..-10}', ['-1', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); + equal('{-20..0}', ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']); + equal('{1..10}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{1..3}', ['1', '2', '3']); + equal('{1..9}', ['1', '2', '3', '4', '5', '6', '7', '8', '9']); + equal('{3..3}', ['3']); + equal('{5..8}', ['5', '6', '7', '8']); + }); + + test('should expand alphabetical ranges', () => { + equal('0{a..d}0', ['0a0', '0b0', '0c0', '0d0']); + equal('a/{b..d}/e', ['a/b/e', 'a/c/e', 'a/d/e']); + equal('{a..A}', ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']); + equal('{A..a}', ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a']); + equal('{a..e}', ['a', 'b', 'c', 'd', 'e']); + equal('{A..E}', ['A', 'B', 'C', 'D', 'E']); + equal('{a..f}', ['a', 'b', 'c', 'd', 'e', 'f']); + equal('{a..z}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); + equal('{E..A}', ['E', 'D', 'C', 'B', 'A']); + equal('{f..a}', ['f', 'e', 'd', 'c', 'b', 'a']); + equal('{f..f}', ['f']); + }); + + test('should use steps with alphabetical ranges', () => { + equal('{a..e..2}', ['a', 'c', 'e']); + equal('{E..A..2}', ['E', 'C', 'A']); + }); + + test('should not try to expand ranges with decimals', () => { + equal('{1.1..2.1}', ['{1.1..2.1}']); + }); + + test('should expand negative ranges', () => { + equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); + equal('{-10..-1}', ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1']); + equal('{0..-5}', ['0', '-1', '-2', '-3', '-4', '-5']); + equal('{9..-4}', ['9', '8', '7', '6', '5', '4', '3', '2', '1', '0', '-1', '-2', '-3', '-4']); + }); + + test('should expand multiple ranges:', () => { + equal('a/{b..d}/e/{f..h}', ['a/b/e/f', 'a/b/e/g', 'a/b/e/h', 'a/c/e/f', 'a/c/e/g', 'a/c/e/h', 'a/d/e/f', 'a/d/e/g', 'a/d/e/h']); + }); + + test('should work with dots in file paths', () => { + equal('../{1..3}/../foo', ['../1/../foo', '../2/../foo', '../3/../foo']); + }); + + test('should expand ranges using steps:', () => { + equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); + equal('{1..10..2}', ['1', '3', '5', '7', '9']); + equal('{1..20..20}', ['1']); + equal('{1..20..2}', ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']); + equal('{10..0..-2}', ['10', '8', '6', '4', '2', '0']); + equal('{10..0..2}', ['10', '8', '6', '4', '2', '0']); + equal('{10..1..-2}', ['10', '8', '6', '4', '2']); + equal('{10..1..2}', ['10', '8', '6', '4', '2']); + equal('{10..1}', ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1']); + equal('{10..1}y', ['10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y']); + equal('{100..0..-5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + equal('{100..0..5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + equal('{a..z..2}', ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']); + equal('{1..10..1}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{1..10..2}', ['1', '3', '5', '7', '9']); + equal('{1..20..20}', ['1']); + equal('{1..20..2}', ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']); + equal('{10..1..-2}', ['10', '8', '6', '4', '2']); + equal('{10..1..2}', ['10', '8', '6', '4', '2']); + equal('{2..10..1}', ['2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{2..10..2}', ['2', '4', '6', '8', '10']); + equal('{2..10..3}', ['2', '5', '8']); + }); + + test('should expand negative ranges using steps:', () => { + equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-10..-2..2}', ['-10', '-8', '-6', '-4', '-2']); + equal('{-2..-10..1}', ['-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); + equal('{-2..-10..2}', ['-2', '-4', '-6', '-8', '-10']); + equal('{-2..-10..3}', ['-2', '-5', '-8']); + equal('{-9..9..3}', ['-9', '-6', '-3', '0', '3', '6', '9']); + }); + + test('should expand mixed ranges and sets:', () => { + equal('x{{0..10},braces}y', ['x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y', 'xbracesy']); + equal('{{0..10},braces}', ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']); + equal('{2147483645..2147483649}', ['2147483645', '2147483646', '2147483647', '2147483648', '2147483649']); + }); + + test('should return invalid ranges:', () => { + equal('{1.20..2}', ['{1.20..2}']); + equal('{1..0f}', ['{1..0f}']); + equal('{1..10..ff}', ['{1..10..ff}']); + equal('{1..10.f}', ['{1..10.f}']); + equal('{1..10f}', ['{1..10f}']); + equal('{1..20..2f}', ['{1..20..2f}']); + equal('{1..20..f2}', ['{1..20..f2}']); + equal('{1..2f..2}', ['{1..2f..2}']); + equal('{1..ff..2}', ['{1..ff..2}']); + equal('{1..ff}', ['{1..ff}']); + }); +}); diff --git a/packages/node-utils/test/fast-glob/_test.ts b/packages/node-utils/test/fast-glob/_test.ts new file mode 100644 index 0000000..f0e9f82 --- /dev/null +++ b/packages/node-utils/test/fast-glob/_test.ts @@ -0,0 +1,34 @@ +/** + * `test()` wrapper that runs an `Effect.gen` body with `BunServices.layer` + * (FileSystem, Path, Stdio, Terminal, ChildProcessSpawner) provided. + */ +import { test as bunTest } from "bun:test"; +import { BunServices } from "@effect/platform-bun"; +import { Effect, Layer } from "effect"; + +type EffectTestBody = Effect.Effect; + +export function test(name: string, body: EffectTestBody, timeout?: number): void { + bunTest( + name, + async () => { + await Effect.runPromise(Effect.provide(body, BunServices.layer)); + }, + timeout, + ); +} + +test.skip = (name: string, _body: EffectTestBody, timeout?: number) => + bunTest.skip(name, () => {}, timeout); + +test.only = (name: string, body: EffectTestBody, timeout?: number) => + bunTest.only( + name, + async () => { + await Effect.runPromise(Effect.provide(body, BunServices.layer)); + }, + timeout, + ); + +// Re-export Layer so call sites can compose extra services on top. +export { Layer }; diff --git a/packages/node-utils/test/fast-glob/fixtures/.dotfile.txt b/packages/node-utils/test/fast-glob/fixtures/.dotfile.txt new file mode 100644 index 0000000..e69de29 diff --git a/packages/node-utils/test/fast-glob/fixtures/.hidden b/packages/node-utils/test/fast-glob/fixtures/.hidden new file mode 100644 index 0000000..e69de29 diff --git a/packages/node-utils/test/fast-glob/fixtures/a.txt b/packages/node-utils/test/fast-glob/fixtures/a.txt new file mode 100644 index 0000000..ce01362 --- /dev/null +++ b/packages/node-utils/test/fast-glob/fixtures/a.txt @@ -0,0 +1 @@ +hello diff --git a/packages/node-utils/test/fast-glob/fixtures/b.txt b/packages/node-utils/test/fast-glob/fixtures/b.txt new file mode 100644 index 0000000..14be0d4 --- /dev/null +++ b/packages/node-utils/test/fast-glob/fixtures/b.txt @@ -0,0 +1 @@ +hello2 diff --git a/packages/node-utils/test/fast-glob/fixtures/deep/deeper/y.txt b/packages/node-utils/test/fast-glob/fixtures/deep/deeper/y.txt new file mode 100644 index 0000000..e69de29 diff --git a/packages/node-utils/test/fast-glob/fixtures/deep/x.txt b/packages/node-utils/test/fast-glob/fixtures/deep/x.txt new file mode 100644 index 0000000..e69de29 diff --git a/packages/node-utils/test/fast-glob/fixtures/dot-dir/.x.txt b/packages/node-utils/test/fast-glob/fixtures/dot-dir/.x.txt new file mode 100644 index 0000000..e69de29 diff --git a/packages/node-utils/test/fast-glob/fixtures/sub/c.md b/packages/node-utils/test/fast-glob/fixtures/sub/c.md new file mode 100644 index 0000000..e69de29 diff --git a/packages/node-utils/test/fast-glob/fixtures/sub/c.txt b/packages/node-utils/test/fast-glob/fixtures/sub/c.txt new file mode 100644 index 0000000..587be6b --- /dev/null +++ b/packages/node-utils/test/fast-glob/fixtures/sub/c.txt @@ -0,0 +1 @@ +x diff --git a/packages/node-utils/test/fast-glob/glob.test.ts b/packages/node-utils/test/fast-glob/glob.test.ts new file mode 100644 index 0000000..33c461f --- /dev/null +++ b/packages/node-utils/test/fast-glob/glob.test.ts @@ -0,0 +1,251 @@ +import { expect } from "bun:test"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { Effect } from "effect"; +import { test } from "./_test.ts"; +import { + generateTasks, + glob, + isDynamicPattern, +} from "../../src/fast-glob/index.ts"; + +const FIXTURES = path.join( + path.dirname(fileURLToPath(import.meta.url)), + "fixtures", +); + +const sorted = (xs: any[]): string[] => + (xs.map((x) => (typeof x === "string" ? x : x.path)) as string[]).sort(); + +test( + "*.txt: only top-level txt files", + Effect.gen(function* () { + const out = yield* glob("*.txt", { cwd: FIXTURES }); + expect(sorted(out)).toEqual(["a.txt", "b.txt"]); + }), +); + +test( + "**/*.txt: recurses into all subdirs", + Effect.gen(function* () { + const out = yield* glob("**/*.txt", { cwd: FIXTURES }); + expect(sorted(out)).toEqual([ + "a.txt", + "b.txt", + "deep/deeper/y.txt", + "deep/x.txt", + "sub/c.txt", + ]); + }), +); + +test( + "**/*.{txt,md} with brace expansion", + Effect.gen(function* () { + const out = yield* glob("**/*.{txt,md}", { cwd: FIXTURES }); + expect(sorted(out)).toEqual([ + "a.txt", + "b.txt", + "deep/deeper/y.txt", + "deep/x.txt", + "sub/c.md", + "sub/c.txt", + ]); + }), +); + +test( + "ignore: excludes patterns", + Effect.gen(function* () { + const out = yield* glob("**/*.txt", { + cwd: FIXTURES, + ignore: ["sub/**", "deep/**"], + }); + expect(sorted(out)).toEqual(["a.txt", "b.txt"]); + }), +); + +test( + "onlyDirectories: returns dirs only", + Effect.gen(function* () { + const out = yield* glob("**", { + cwd: FIXTURES, + onlyDirectories: true, + }); + expect(sorted(out)).toEqual([ + "deep", + "deep/deeper", + "dot-dir", + "empty", + "sub", + ]); + }), +); + +test( + "onlyFiles (default): excludes dirs", + Effect.gen(function* () { + const out = yield* glob("**", { cwd: FIXTURES }); + expect(sorted(out)).toEqual([ + "a.txt", + "b.txt", + "deep/deeper/y.txt", + "deep/x.txt", + "sub/c.md", + "sub/c.txt", + ]); + }), +); + +test( + "dot:false (default) excludes dotfiles", + Effect.gen(function* () { + const out = yield* glob("**", { cwd: FIXTURES }); + for (const item of out) { + const p = typeof item === "string" ? item : item.path; + for (const seg of p.split("/")) { + expect(seg.startsWith(".")).toBe(false); + } + } + }), +); + +test( + "dot:true includes dotfiles", + Effect.gen(function* () { + const out = yield* glob("**", { cwd: FIXTURES, dot: true }); + expect(sorted(out)).toContain(".dotfile.txt"); + expect(sorted(out)).toContain("dot-dir/.x.txt"); + }), +); + +test( + "absolute:true returns absolute paths", + Effect.gen(function* () { + const out = yield* glob("*.txt", { + cwd: FIXTURES, + absolute: true, + }); + for (const item of out) { + const p = typeof item === "string" ? item : item.path; + expect(path.isAbsolute(p)).toBe(true); + } + }), +); + +test( + "markDirectories adds trailing /", + Effect.gen(function* () { + const out = yield* glob("**", { + cwd: FIXTURES, + onlyDirectories: true, + markDirectories: true, + }); + for (const item of out) { + const p = typeof item === "string" ? item : item.path; + expect(p.endsWith("/")).toBe(true); + } + }), +); + +test( + "objectMode returns Entry objects", + Effect.gen(function* () { + const out = yield* glob("*.txt", { cwd: FIXTURES, objectMode: true }); + expect(Array.isArray(out)).toBe(true); + for (const item of out) { + expect(typeof item).toBe("object"); + expect(typeof (item as any).name).toBe("string"); + expect(typeof (item as any).path).toBe("string"); + expect((item as any).dirent.isFile).toBe(true); + } + }), +); + +test( + "deep:1 limits recursion", + Effect.gen(function* () { + const out = yield* glob("**/*.txt", { cwd: FIXTURES, deep: 1 }); + expect(sorted(out)).toEqual(["a.txt", "b.txt"]); + }), +); + +test( + "deep:2 reaches deep/ but not deep/deeper/", + Effect.gen(function* () { + const out = yield* glob("**/*.txt", { cwd: FIXTURES, deep: 2 }); + expect(sorted(out)).toContain("deep/x.txt"); + expect(sorted(out)).toContain("a.txt"); + expect(sorted(out)).not.toContain("deep/deeper/y.txt"); + }), +); + +test( + "multiple patterns: union", + Effect.gen(function* () { + const out = yield* glob(["*.txt", "sub/*.md"], { cwd: FIXTURES }); + expect(sorted(out)).toEqual(["a.txt", "b.txt", "sub/c.md"]); + }), +); + +test( + "negative pattern: subtracts matches", + Effect.gen(function* () { + const out = yield* glob(["**/*.txt", "!sub/**"], { cwd: FIXTURES }); + expect(sorted(out)).toEqual([ + "a.txt", + "b.txt", + "deep/deeper/y.txt", + "deep/x.txt", + ]); + }), +); + +test( + "unique:true (default) deduplicates", + Effect.gen(function* () { + const out = yield* glob(["*.txt", "*.txt"], { cwd: FIXTURES }); + expect(sorted(out)).toEqual(["a.txt", "b.txt"]); + }), +); + +test( + "empty results when nothing matches", + Effect.gen(function* () { + const out = yield* glob("*.nope", { cwd: FIXTURES }); + expect(out).toEqual([]); + }), +); + +// Static (synchronous) API surface — no IO. + +import { describe, test as syncTest } from "bun:test"; + +describe("generateTasks", () => { + syncTest("groups patterns by base", () => { + const tasks = generateTasks(["a/**", "a/*.js", "b/**"]); + expect(tasks.length).toBeGreaterThan(0); + expect(tasks.every((t) => typeof t.base === "string")).toBe(true); + }); + + syncTest("rejects non-string patterns", () => { + expect(() => generateTasks([1 as any])).toThrow(/non empty/); + expect(() => generateTasks([""])).toThrow(/non empty/); + }); +}); + +describe("isDynamicPattern", () => { + syncTest("true for glob symbols", () => { + expect(isDynamicPattern("*.js")).toBe(true); + expect(isDynamicPattern("a/**/b")).toBe(true); + expect(isDynamicPattern("{a,b}.js")).toBe(true); + expect(isDynamicPattern("[ab].js")).toBe(true); + }); + syncTest("false for static patterns", () => { + expect(isDynamicPattern("a/b/c.js")).toBe(false); + expect(isDynamicPattern("foo.txt")).toBe(false); + }); + syncTest("respects braceExpansion option", () => { + expect(isDynamicPattern("{a,b}.js", { braceExpansion: false })).toBe(false); + }); +}); diff --git a/packages/node-utils/test/fill-range.test.ts b/packages/node-utils/test/fill-range.test.ts new file mode 100644 index 0000000..3d53401 --- /dev/null +++ b/packages/node-utils/test/fill-range.test.ts @@ -0,0 +1,560 @@ +import { describe, expect, test } from "bun:test"; +import util from "node:util"; +import { fill } from "../src/fill-range.ts"; + +const exact = (actual: unknown, expected: unknown[]) => { + expect(Array.isArray(actual)).toBe(true); + expect(util.inspect(actual)).toBe(util.inspect(expected)); +}; + +const expand = (start: number, stop: number, step = 1): number[] => { + const arr: number[] = []; + for (let i = start; i <= stop; i += step) arr.push(i); + return arr; +}; + +const toRegex = (...args: any[]) => new RegExp(`^(${fill(...(args as [any]))})$`); +const isMatch = (...args: any[]) => { + const input = args.pop(); + return toRegex(...args).test(input); +}; + +describe("ranges", () => { + describe("alphabetical", () => { + test("should increment alphabetical ranges", () => { + exact(fill("a"), ["a"]); + exact(fill("a", "a"), ["a"]); + exact(fill("a", "b"), ["a", "b"]); + exact(fill("a", "e"), ["a", "b", "c", "d", "e"]); + exact(fill("A", "E"), ["A", "B", "C", "D", "E"]); + }); + + test("should decrement alphabetical ranges", () => { + exact(fill("E", "A"), ["E", "D", "C", "B", "A"]); + exact( + fill("a", "C"), + [ + "a", "`", "_", "^", "]", "\\", "[", "Z", "Y", "X", "W", "V", "U", "T", + "S", "R", "Q", "P", "O", "N", "M", "L", "K", "J", "I", "H", "G", "F", + "E", "D", "C", + ], + ); + exact(fill("z", "m"), [ + "z", "y", "x", "w", "v", "u", "t", "s", "r", "q", "p", "o", "n", "m", + ]); + }); + }); + + describe("alphanumeric", () => { + test("should increment alphanumeric ranges", () => { + exact(fill("9", "B"), ["9", ":", ";", "<", "=", ">", "?", "@", "A", "B"]); + exact( + fill("A", "10"), + [ + "A", "@", "?", ">", "=", "<", ";", ":", "9", "8", "7", "6", "5", "4", + "3", "2", "1", + ], + ); + exact( + fill("a", "10"), + [ + "a", "`", "_", "^", "]", "\\", "[", "Z", "Y", "X", "W", "V", "U", "T", + "S", "R", "Q", "P", "O", "N", "M", "L", "K", "J", "I", "H", "G", "F", + "E", "D", "C", "B", "A", "@", "?", ">", "=", "<", ";", ":", "9", "8", + "7", "6", "5", "4", "3", "2", "1", + ], + ); + }); + + test("should step alphanumeric ranges", () => { + exact(fill("9", "B", 3), ["9", "<", "?", "B"]); + }); + + test("should decrement alphanumeric ranges", () => { + exact(fill("C", "9"), ["C", "B", "A", "@", "?", ">", "=", "<", ";", ":", "9"]); + }); + }); + + describe("numbers", () => { + test("should increment numerical *string* ranges", () => { + exact(fill("1"), ["1"]); + exact(fill("1", "1"), ["1"]); + exact(fill("1", "2"), ["1", "2"]); + exact(fill("1", "10"), ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]); + exact(fill("1", "3"), ["1", "2", "3"]); + exact(fill("5", "8"), ["5", "6", "7", "8"]); + exact(fill("1", "9"), ["1", "2", "3", "4", "5", "6", "7", "8", "9"]); + }); + + test("should increment numerical *number* ranges", () => { + exact(fill(1, 3), [1, 2, 3]); + exact(fill(1, 9), [1, 2, 3, 4, 5, 6, 7, 8, 9]); + exact(fill(5, 8), [5, 6, 7, 8]); + }); + + test("should increment combo number/string ranges", () => { + exact(fill("1", 9), ["1", "2", "3", "4", "5", "6", "7", "8", "9"]); + exact(fill("2", 5), ["2", "3", "4", "5"]); + }); + + test("should decrement numerical *string* ranges", () => { + exact(fill("0", "-5"), ["0", "-1", "-2", "-3", "-4", "-5"]); + exact(fill("-1", "-5"), ["-1", "-2", "-3", "-4", "-5"]); + }); + + test("should decrement numerical *number* ranges", () => { + exact(fill(-10, -1), [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1]); + exact(fill(0, -5), [0, -1, -2, -3, -4, -5]); + }); + + test("should handle *string* ranges that span positive and negative", () => { + exact(fill("9", "-4"), [ + "9", "8", "7", "6", "5", "4", "3", "2", "1", "0", "-1", "-2", "-3", "-4", + ]); + exact(fill("-5", "5"), [ + "-5", "-4", "-3", "-2", "-1", "0", "1", "2", "3", "4", "5", + ]); + }); + + test("should handle *number* ranges that span positive and negative", () => { + exact(fill(9, -4), [9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4]); + exact(fill(-5, 5), [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]); + }); + }); +}); + +describe("steps", () => { + test("should increment numerical ranges using the given step", () => { + exact(fill("1", "10", "1"), ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]); + exact(fill("1", "10", "2"), ["1", "3", "5", "7", "9"]); + exact(fill("0", "1000", "200"), ["0", "200", "400", "600", "800", "1000"]); + exact(fill("1", "10", 2), ["1", "3", "5", "7", "9"]); + exact(fill("1", "20", "2"), [ + "1", "3", "5", "7", "9", "11", "13", "15", "17", "19", + ]); + exact(fill("1", "20", "20"), ["1"]); + exact(fill("10", "1", "2"), ["10", "8", "6", "4", "2"]); + exact(fill("10", "1", "-2"), ["10", "8", "6", "4", "2"]); + exact(fill(2, 10, "2"), [2, 4, 6, 8, 10]); + exact(fill(2, 10, 1), [2, 3, 4, 5, 6, 7, 8, 9, 10]); + exact(fill(2, 10, 2), [2, 4, 6, 8, 10]); + exact(fill(2, 10, 3), [2, 5, 8]); + exact(fill(0, 5, 2), [0, 2, 4]); + exact(fill(5, 0, 2), [5, 3, 1]); + exact(fill(1, 5, 2), [1, 3, 5]); + exact(fill(2, "10", 2), ["2", "4", "6", "8", "10"]); + exact(fill(2, "10", 1), ["2", "3", "4", "5", "6", "7", "8", "9", "10"]); + exact(fill(2, "10", "2"), ["2", "4", "6", "8", "10"]); + exact(fill("2", 10, "3"), ["2", "5", "8"]); + }); + + test("should fill in negative ranges using the given step (strings)", () => { + exact(fill("0", "-10", "-2"), ["0", "-2", "-4", "-6", "-8", "-10"]); + exact(fill("-0", "-10", "-2"), ["0", "-2", "-4", "-6", "-8", "-10"]); + exact(fill("-1", "-10", "-2"), ["-1", "-3", "-5", "-7", "-9"]); + exact(fill("-1", "-10", "2"), ["-1", "-3", "-5", "-7", "-9"]); + exact(fill("1", "10", "2"), ["1", "3", "5", "7", "9"]); + exact(fill("1", "20", "2"), [ + "1", "3", "5", "7", "9", "11", "13", "15", "17", "19", + ]); + exact(fill("1", "20", "20"), ["1"]); + exact(fill("10", "1", "-2"), ["10", "8", "6", "4", "2"]); + exact(fill("-10", "0", "2"), ["-10", "-8", "-6", "-4", "-2", "0"]); + exact(fill("-10", "-0", "2"), ["-10", "-8", "-6", "-4", "-2", "0"]); + exact(fill("-0", "-10", "0"), [ + "0", "-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-9", "-10", + ]); + exact(fill("0", "-10", "-0"), [ + "0", "-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-9", "-10", + ]); + }); + + test("should fill in negative ranges using the given step (numbers)", () => { + exact(fill(-10, 0, 2), [-10, -8, -6, -4, -2, 0]); + exact(fill(-10, -2, 2), [-10, -8, -6, -4, -2]); + exact(fill(-2, -10, 1), [-2, -3, -4, -5, -6, -7, -8, -9, -10]); + exact(fill(0, -10, 2), [0, -2, -4, -6, -8, -10]); + exact(fill(-2, -10, 2), [-2, -4, -6, -8, -10]); + exact(fill(-2, -10, 3), [-2, -5, -8]); + exact(fill(-9, 9, 3), [-9, -6, -3, 0, 3, 6, 9]); + }); + + test("should fill in negative ranges when negative zero is passed", () => { + exact(fill(-10, -0, 2), [-10, -8, -6, -4, -2, 0]); + exact(fill(-0, -10, 2), [0, -2, -4, -6, -8, -10]); + }); + + test("steps: letters", () => { + exact(fill("z", "a", -2), [ + "z", "x", "v", "t", "r", "p", "n", "l", "j", "h", "f", "d", "b", + ]); + exact(fill("a", "e", 2), ["a", "c", "e"]); + exact(fill("E", "A", 2), ["E", "C", "A"]); + }); + + test("options.step", () => { + const options = { step: 2 }; + exact(fill("a", "e", options), ["a", "c", "e"]); + exact(fill("E", "A", options), ["E", "C", "A"]); + }); +}); + +describe("padding", () => { + test("should pad incremented numbers", () => { + exact(fill("01", "03"), ["01", "02", "03"]); + exact(fill("01", "3"), ["01", "02", "03"]); + exact(fill("1", "03"), ["01", "02", "03"]); + exact(fill("0001", "0003"), ["0001", "0002", "0003"]); + exact(fill("-10", "00"), [ + "-10", "-09", "-08", "-07", "-06", "-05", "-04", "-03", "-02", "-01", "000", + ]); + exact(fill("05", "010"), ["005", "006", "007", "008", "009", "010"]); + }); + + test("should pad decremented numbers", () => { + exact(fill("03", "01"), ["03", "02", "01"]); + exact(fill("3", "01"), ["03", "02", "01"]); + exact(fill("003", "1"), ["003", "002", "001"]); + exact(fill("003", "001"), ["003", "002", "001"]); + exact(fill("3", "001"), ["003", "002", "001"]); + exact(fill("03", "001"), ["003", "002", "001"]); + }); + + test("should pad decremented numbers with regex source string", () => { + expect(fill("03", "01", { toRegex: true })).toBe("0?[1-3]"); + expect(fill("3", "01", { toRegex: true })).toBe("0?[1-3]"); + expect(fill("003", "1", { toRegex: true })).toBe("0{0,2}[1-3]"); + expect(fill("003", "001", { toRegex: true })).toBe("0{0,2}[1-3]"); + expect(fill("3", "001", { toRegex: true })).toBe("0{0,2}[1-3]"); + expect(fill("03", "001", { toRegex: true })).toBe("0{0,2}[1-3]"); + expect(fill("001", "020", { toRegex: true })).toBe("0{0,2}[1-9]|0?1[0-9]|0?20"); + }); + + test("should pad with strict zeros", () => { + expect(fill("03", "01", { toRegex: true, strictZeros: true })).toBe("0[1-3]"); + expect(fill("3", "01", { toRegex: true, strictZeros: true })).toBe("0[1-3]"); + expect(fill("003", "1", { toRegex: true, strictZeros: true })).toBe("00[1-3]"); + expect(fill("003", "001", { toRegex: true, strictZeros: true })).toBe("00[1-3]"); + expect(fill("3", "001", { toRegex: true, strictZeros: true })).toBe("00[1-3]"); + expect(fill("03", "001", { toRegex: true, strictZeros: true })).toBe("00[1-3]"); + expect(fill("001", "020", { toRegex: true, strictZeros: true })).toBe( + "00[1-9]|01[0-9]|020", + ); + }); + + test("should pad stepped numbers", () => { + exact(fill("1", "05", "3"), ["01", "04"]); + exact(fill("1", "5", "03"), ["01", "04"]); + exact(fill("1", "5", "0003"), ["0001", "0004"]); + exact(fill("1", "005", "3"), ["001", "004"]); + exact(fill("00", "1000", "200"), [ + "0000", "0200", "0400", "0600", "0800", "1000", + ]); + exact(fill("0", "01000", "200"), [ + "00000", "00200", "00400", "00600", "00800", "01000", + ]); + exact(fill("001", "5", "3"), ["001", "004"]); + exact(fill("02", "10", 2), ["02", "04", "06", "08", "10"]); + exact(fill("002", "10", 2), ["002", "004", "006", "008", "010"]); + exact(fill("002", "010", 2), ["002", "004", "006", "008", "010"]); + exact(fill("-04", 4, 2), ["-04", "-02", "000", "002", "004"]); + }); +}); + +describe("invalid ranges", () => { + test("should return an empty array when options.strict is not true", () => { + expect(fill("1", "0f")).toEqual([]); + expect(fill("1", "10", "ff")).toEqual([]); + expect(fill("1", "10.f")).toEqual([]); + expect(fill("1", "10f")).toEqual([]); + expect(fill("1", "20", "2f")).toEqual([]); + expect(fill("1", "20", "f2")).toEqual([]); + expect(fill("1", "2f")).toEqual([]); + expect(fill("1", "2f", "2")).toEqual([]); + expect(fill("1", "f2")).toEqual([]); + expect(fill("1", "ff")).toEqual([]); + expect(fill("1", "ff", "2")).toEqual([]); + expect(fill("1.1", "2.1")).toEqual([]); + expect(fill("1.2", "2")).toEqual([]); + expect(fill("1.20", "2")).toEqual([]); + }); +}); + +describe("error handling", () => { + test("should throw when range arguments are invalid and strictRanges is true", () => { + expect(() => fill("0a", "0z", { strictRanges: true })).toThrow( + /Invalid range arguments: \[ '0a', '0z' \]/, + ); + expect(() => fill("", "*", 2, { strictRanges: true })).toThrow( + /Invalid range arguments: \[ '', '\*' \]/, + ); + }); + + test("should throw when args are incompatible", () => { + expect(() => fill("a8", 10, { strictRanges: true })).toThrow( + /Invalid range arguments: \[ 'a8', 10 \]/, + ); + expect(() => fill(1, "zz", { strictRanges: true })).toThrow( + /Invalid range arguments: \[ 1, 'zz' \]/, + ); + }); + + test("should throw when the step is bad", () => { + const opts = { strictRanges: true }; + expect(() => fill("1", "10", "z", opts)).toThrow(/Expected step "z" to be a number/); + expect(() => fill("a", "z", "a", opts)).toThrow(/Expected step "a" to be a number/); + expect(() => fill("a", "z", "0a", opts)).toThrow(/Expected step "0a" to be a number/); + }); +}); + +describe("special cases", () => { + test("negative zero", () => { + exact(fill("-5", "-0", "-1"), ["-5", "-4", "-3", "-2", "-1", "0"]); + exact(fill("1", "-0", 1), ["1", "0"]); + exact(fill("1", "-0", 0), ["1", "0"]); + exact(fill("1", "-0", "0"), ["1", "0"]); + exact(fill("1", "-0", "1"), ["1", "0"]); + exact(fill("-0", "-0", "1"), ["0"]); + exact(fill("-0", "0", "1"), ["0"]); + exact(fill("-0", "5", "1"), ["0", "1", "2", "3", "4", "5"]); + exact(fill(-0, 5), [0, 1, 2, 3, 4, 5]); + exact(fill(5, -0, 5), [5, 0]); + exact(fill(5, -0, 2), [5, 3, 1]); + exact(fill(0, 5, 2), [0, 2, 4]); + }); + + test("should adjust padding for negative numbers", () => { + exact(fill("-01", "5"), ["-01", "000", "001", "002", "003", "004", "005"]); + }); +}); + +describe("custom expand function", () => { + test("exposes the current value", () => { + exact(fill(1, 5, (value: any) => value), [1, 2, 3, 4, 5]); + }); + test("exposes the character code for non-integers", () => { + const arr = fill("a", "e", (code: any) => String.fromCharCode(code)); + exact(arr, ["a", "b", "c", "d", "e"]); + }); + test("can pad in the transform", () => { + const arr = fill("01", "05", (value: any) => + String(value).padStart(String(value).length + 3, "0"), + ); + exact(arr, ["0001", "0002", "0003", "0004", "0005"]); + }); + test("exposes the index", () => { + const arr = fill("a", "e", (code: any, index: any) => + String.fromCharCode(code) + index, + ); + exact(arr, ["a0", "b1", "c2", "d3", "e4"]); + }); +}); + +describe("options.stringify", () => { + test("should cast values to strings", () => { + const opts = { stringify: true }; + exact(fill("1", "10", "1", opts), [ + "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", + ]); + exact(fill(2, 10, "2", opts), ["2", "4", "6", "8", "10"]); + exact(fill(2, 10, 1, opts), ["2", "3", "4", "5", "6", "7", "8", "9", "10"]); + exact(fill(2, 10, 3, opts), ["2", "5", "8"]); + }); +}); + +describe("options.transform", () => { + test("should cast values to strings", () => { + const transform = (value: any) => String(value); + exact(fill("1", "10", "1", { transform }), [ + "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", + ]); + exact(fill(2, 10, "2", { transform }), ["2", "4", "6", "8", "10"]); + exact(fill(2, 10, 1, { transform }), [ + "2", "3", "4", "5", "6", "7", "8", "9", "10", + ]); + exact(fill(2, 10, 3, { transform }), ["2", "5", "8"]); + }); +}); + +describe("options.toRegex", () => { + const opts = { toRegex: true }; + + test("ascending numbers", () => { + expect(fill(2, 8, opts)).toBe("[2-8]"); + expect(fill(2, 10, opts)).toBe("[2-9]|10"); + expect(fill(2, 100, opts)).toBe("[2-9]|[1-9][0-9]|100"); + }); + + test("positive + negative numbers", () => { + expect(fill(-10, 10, opts)).toBe("-[1-9]|-?10|[0-9]"); + expect(fill(-10, 10, 2, opts)).toBe("0|2|4|6|8|10|-(?:2|4|6|8|10)"); + expect(fill(-10, 0, 2, opts)).toBe("0|-(?:2|4|6|8|10)"); + expect(fill(-10, -2, 2, opts)).toBe("-(?:2|4|6|8|10)"); + }); + + test("descending numbers", () => { + expect(fill(8, 2, opts)).toBe("[2-8]"); + }); + + test("with a step", () => { + expect(fill(8, 2, { toRegex: true, step: 2 })).toBe("2|4|6|8"); + expect(fill(2, 8, { toRegex: true, step: 2 })).toBe("2|4|6|8"); + }); + + test("zero-padding", () => { + expect(fill("002", "008", opts)).toBe("0{0,2}[2-8]"); + expect(fill("02", "08", opts)).toBe("0?[2-8]"); + expect(fill("02", "10", opts)).toBe("0?[2-9]|10"); + expect(fill("002", "100", opts)).toBe("0{0,2}[2-9]|0?[1-9][0-9]|100"); + }); + + test("negative zero-padding", () => { + expect(fill("-002", "-100", opts)).toBe( + "-0{0,3}[2-9]|-0{0,2}[1-9][0-9]|-0?100", + ); + expect(fill("-02", "-08", opts)).toBe("-0{0,2}[2-8]"); + expect(fill("-02", "-100", opts)).toBe( + "-0{0,3}[2-9]|-0{0,2}[1-9][0-9]|-0?100", + ); + expect(fill("-02", "100", opts)).toBe( + "-0{0,2}[12]|0{0,2}[0-9]|0?[1-9][0-9]|100", + ); + }); + + test("alpha ascending", () => { + expect(fill("a", "b", opts)).toBe("[a-b]"); + expect(fill("A", "b", opts)).toBe("[A-b]"); + expect(fill("Z", "a", opts)).toBe("[Z-a]"); + }); + + test("alpha descending", () => { + expect(fill("z", "A", opts)).toBe("[A-z]"); + }); +}); + +describe("options.wrap", () => { + const opts = { toRegex: true, wrap: true }; + test("should not wrap single", () => { + expect(fill(2, 8, opts)).toBe("[2-8]"); + }); + test("should wrap", () => { + expect(fill(2, 10, opts)).toBe("(?:[2-9]|10)"); + expect(fill(2, 100, opts)).toBe("(?:[2-9]|[1-9][0-9]|100)"); + }); + test("wrap pos+neg", () => { + expect(fill(-10, -2, 2, opts)).toBe("(?:-(?:2|4|6|8|10))"); + expect(fill(-10, 0, 2, opts)).toBe("(?:0|-(?:2|4|6|8|10))"); + expect(fill(-10, 10, 2, opts)).toBe("(?:0|2|4|6|8|10|-(?:2|4|6|8|10))"); + expect(fill(-10, 10, opts)).toBe("(?:-[1-9]|-?10|[0-9])"); + }); +}); + +describe("options.capture", () => { + test("wraps result in parens", () => { + const opts = { toRegex: true, capture: true }; + expect(fill(-10, 10, 2, opts)).toBe("(0|2|4|6|8|10|-(2|4|6|8|10))"); + expect(fill(-10, 10, opts)).toBe("(-[1-9]|-?10|[0-9])"); + }); +}); + +describe("matching via generated regex", () => { + test("ascending numbers", () => { + expect(isMatch(2, 8, { toRegex: true }, "10")).toBe(false); + expect(isMatch(2, 8, { toRegex: true }, "3")).toBe(true); + expect(isMatch(2, 10, { toRegex: true }, "10")).toBe(true); + expect(isMatch(2, 100, { toRegex: true }, "10")).toBe(true); + expect(isMatch(2, 100, { toRegex: true }, "101")).toBe(false); + }); + test("positive + negative", () => { + expect(isMatch(-10, 10, { toRegex: true }, "10")).toBe(true); + expect(isMatch(-10, 10, 2, { toRegex: true }, "10")).toBe(true); + }); + test("descending", () => { + expect(isMatch(8, 2, { toRegex: true }, "2")).toBe(true); + expect(isMatch(8, 2, { toRegex: true }, "8")).toBe(true); + expect(isMatch(8, 2, { toRegex: true }, "10")).toBe(false); + }); + test("with step", () => { + expect(isMatch(8, 2, { toRegex: true, step: 2 }, "10")).toBe(false); + expect(isMatch(8, 2, { toRegex: true, step: 2 }, "3")).toBe(false); + expect(isMatch(8, 2, { toRegex: true, step: 2 }, "5")).toBe(false); + expect(isMatch(8, 2, { toRegex: true, step: 2 }, "8")).toBe(true); + expect(isMatch(2, 8, { toRegex: true, step: 2 }, "10")).toBe(false); + expect(isMatch(2, 8, { toRegex: true, step: 2 }, "3")).toBe(false); + expect(isMatch(2, 8, { toRegex: true, step: 2 }, "8")).toBe(true); + }); +}); + +describe("validate ranges (verify-matches)", () => { + const matcher = (...args: any[]) => { + const regex = toRegex(...args); + return (num: any) => regex.test(String(num)); + }; + + const verifyRange = (min: number, max: number, from: number, to: number) => { + const fn = matcher(min, max, { toRegex: true }); + const range = expand(from, to); + for (const num of range) { + if (min <= num && num <= max) { + expect(fn(num)).toBe(true); + } else { + expect(fn(num)).toBe(false); + } + } + }; + + test("supports equal numbers", () => { + verifyRange(1, 1, 0, 100); + verifyRange(65443, 65443, 65000, 66000); + verifyRange(192, 1000, 0, 1000); + }); + test("supports large numbers", () => { + verifyRange( + 100019999300000, + 100020000300000, + 100019999999999, + 100020000100000, + ); + }); + test("supports repeated digits", () => { + verifyRange(10331, 20381, 0, 99999); + }); + test("supports repeated zeros", () => { + verifyRange(10031, 20081, 0, 59999); + verifyRange(10000, 20000, 0, 59999); + }); + test("supports zero one", () => { + verifyRange(10301, 20101, 0, 99999); + }); + test("supports repeated ones", () => { + verifyRange(102, 111, 0, 1000); + }); + test("supports small diffs", () => { + verifyRange(102, 110, 0, 1000); + verifyRange(102, 130, 0, 1000); + }); + test("supports random ranges", () => { + verifyRange(4173, 7981, 0, 99999); + }); + test("supports one digit numbers", () => { + verifyRange(3, 7, 0, 99); + }); + test("supports one digit at bounds", () => { + verifyRange(1, 9, 0, 1000); + }); + test("supports power of ten", () => { + verifyRange(1000, 8632, 0, 99999); + }); + test("varying lengths", () => { + verifyRange(1030, 20101, 0, 99999); + verifyRange(13, 8632, 0, 10000); + }); + test("small ranges", () => { + verifyRange(9, 11, 0, 100); + verifyRange(19, 21, 0, 100); + }); + test("big ranges", () => { + verifyRange(90, 98009, 0, 98999); + verifyRange(999, 10000, 1, 20000); + }); +}); diff --git a/packages/node-utils/test/is-number.test.ts b/packages/node-utils/test/is-number.test.ts new file mode 100644 index 0000000..c937890 --- /dev/null +++ b/packages/node-utils/test/is-number.test.ts @@ -0,0 +1,142 @@ +// @ts-nocheck — fixtures intentionally include unary-plus on null/undefined. +import { describe, expect, test } from "bun:test"; +import { isNumber } from "../src/is-number.ts"; + +describe("is a number", () => { + const fixtures: unknown[] = [ + 0xff, + 5e3, + 0, + 0.1, + -0.1, + -1.1, + 37, + 3.14, + + 1, + 1.1, + 10, + 10.1, + 100, + -100, + + "0.1", + "-0.1", + "-1.1", + "0", + "012", + "0xff", + "1", + "1.1", + "10", + "10.10", + "100", + "5e3", + " 56\r\n ", + + Math.LN2, + parseInt("012"), + parseFloat("012"), + Math.abs(1), + Math.acos(1), + Math.asin(1), + Math.atan(1), + Math.atan2(1, 2), + Math.ceil(1), + Math.cos(1), + Math.E, + Math.exp(1), + Math.floor(1), + Math.LN10, + Math.LN2, + Math.log(1), + Math.LOG10E, + Math.LOG2E, + Math.max(1, 2), + Math.min(1, 2), + Math.PI, + Math.pow(1, 2), + Math.pow(5, 5), + Math.random(), + Math.round(1), + Math.sin(1), + Math.sqrt(1), + Math.SQRT1_2, + Math.SQRT2, + Math.tan(1), + + Number.MAX_VALUE, + Number.MIN_VALUE, + + "0.0", + "0x0", + "0e+5", + "000", + "0.0e-5", + "0.0E5", + + +"", + +1, + +3.14, + +37, + +5, + +[], + +false, + +Math.LN2, + +true, + +null, + +new Date(), + ]; + + for (const num of fixtures) { + test(`${JSON.stringify(num)} should be a number`, () => { + expect(isNumber(num)).toBe(true); + }); + } +}); + +describe("is not a number", () => { + const fixtures: unknown[] = [ + " ", + "\r\n\t", + "", + "", + "3a", + "abc", + "false", + "null", + "true", + "undefined", + +"abc", + +/foo/, + +[1, 2, 4], + +Infinity, + +Math.sin, + +NaN, + +undefined, + +{ a: 1 }, + +{}, + /foo/, + [1, 2, 3], + [1], + [], + true, + false, + +(() => {}), + () => {}, + Infinity, + -Infinity, + Math.sin, + NaN, + new Date(), + null, + undefined, + {}, + ]; + + for (const num of fixtures) { + test(`${JSON.stringify(num)} should not be a number`, () => { + expect(isNumber(num)).toBe(false); + }); + } +}); diff --git a/packages/node-utils/test/micromatch/_fixtures.ts b/packages/node-utils/test/micromatch/_fixtures.ts new file mode 100644 index 0000000..cdaa363 --- /dev/null +++ b/packages/node-utils/test/micromatch/_fixtures.ts @@ -0,0 +1,86 @@ + + + +export default [ + 'a', + 'a.md', + 'a.js', + 'a/', + 'a/b', + 'a/b/.c.md', + 'a/b/c', + 'a/b/c.md', + 'a/b/c/', + 'a/b/c/d', + 'a/b/c/d/', + 'a/b/c/d/e/f/z.js', + 'a/b/c/z.js', + 'a/bb', + 'a/cb', + 'abbbz', + 'abc', + 'abd', + 'z.js', + 'za.js', + + // literal "!" + '!a.js', + '!a/b', + '!a/b/', + '!a/b/c', + '!a/b/c/', + '!a/!b', + '!a/!b/c', + '!a/!b/c/d', + '!a/b/.c.md', + + // root + '/a/', + '/a/b', + '/a/cb', + '/a/bb', + '/a/b/c', + '/a/b/c/', + '/a/b/c/d', + '/a/b/c/d/', + + // cwd + '.', + './', + + // ancestor directories + '..', + '../c', + '../c', + './../c', + './a/../c', + '/..', + '/../c', + '/../.c', + '/../.c/', + '/a/../c', + 'a/../c', + + // dot files + '../.b/.c', + '../b/.c', + './.b/.c', + './b/.c', + '.b', + '.b.c', + '.b.c/', + '.b/', + '.b/.c', + '.b/c', + 'b/.c', + 'b/.c/', + + // wildcards in filepaths + 'a/+b/c', + '+a/+b/c', + 'a (foo)', + 'a (foo)/(bar)', + 'a/b/c (1)', + 'a/b (2)/c (1)', + 'a/b/c [def]' +]; diff --git a/packages/node-utils/test/micromatch/_patterns.ts b/packages/node-utils/test/micromatch/_patterns.ts new file mode 100644 index 0000000..77e03f6 --- /dev/null +++ b/packages/node-utils/test/micromatch/_patterns.ts @@ -0,0 +1,196 @@ + +export default [ + '!**', + '!**/*', + '!**/*.md', + '!*.*', + '!*.js', + '!*/**/*', + '!*/**/*/', + '!*/*/*', + '!/**', + '!/**/', + '!/*/', + '!/*/**/*/', + '!/*/*/', + '!a/!b*', + '!a/!b/*', + '!a/*?b', + '!a/?', + '!a/?*b', + '!a/??b', + '!a/?b', + '!a/b/!*', + '!a/b/*', + '!a/b/c*', + '*', + '**', + '***', + '**********', + '**/', + '**/*', + '**/**', + '**/**/**', + '**/*.md', + '**/*?.md', + '**/.?.md', + '**/?.md', + '**/z*.js', + '*.js', + '*/', + '*/*', + '*/*/*', + '*/*/*/*', + '/*', + '/**', + '/**/', + '/**/*', + '/*/', + '/*/*', + '/*/**/', + '/*/**/*', + '/*/*/', + '/*/*/*', + '/*/*/*/*', + '/*/*/*/*/', + '?', + '?*?******?', + '?*?***?', + '?*?***?***????', + '?*?*?', + '?/', + '?/.?', + '?/.?*', + '?/?', + '?/?/?', + '??', + '??/??', + '???', + '????', + 'a/*', + 'a/*/', + 'a/b/*', + 'a/**/', + 'a/**/b', + 'a/**b', + 'a/*/', + 'a/*?b', + 'a/?', + 'a/?*b', + 'a/??b', + 'a/?b', + 'a/b', + 'a/b*', + 'a/b/*', + 'a/b/c', + 'a/b/c/*', + 'a/b/c/**/*.js', + 'a/b/c/*.js' +]; + +module.exports.other = [ + 'code/{for,while,*-{test,exec}}*.js', + 'code/{for-*,while*}.js', + '**/a/*/b/c/.js', + '**/a/*/b/c.d/.js', + '**/*.{*,gitignore}', + '**/*.{js,gitignore}', + '**/{a,/.gitignore}', + '**/{a..z..2}/*.js', + '**/{a..c}/*.js', + '**/{1..10}/*.js', + '**/{1..10..2}/*.js', + 'a/{b..s}/xyz/*-{01..10}.js', + 'a', + 'a/', + 'a/*', + '.*', + '**/**/.*', + '**/**/.*', + '**/.*/.*', + '**/.*', + '**/*.*', + '**/*.', + '**/*.a', + '**/*.js', + '**/*.md', + '**/.*', + '**/.*.js', + '**/.*.md', + '**/.a', + '**/.a.js', + '**/.gitignore', + '*.*', + '*.a', + '*.gitignore', + '*.{gitignore,*}', + '*.{*,gitignore,js}', + '*.{*,gitignore}', + '.{*,gitignore}', + '**/.{*,gitignore}', + '**/.{js,gitignore}', + '**/.{js,md}', + '**/*.{js,md}', + '**/(a|b)/*.{js,md}', + '**/[a-z]/*.{js,md}', + '*.js', + '*.md', + '*.{js,txt}', + '*/*.gitignore', + '*/.gitignore', + '.a', + '.gitignore', + '.js', + '.md', + 'a/**/c/*.js', + 'a/**/c/*.md', + 'a/**/j/**/z/*.js', + 'a/**/j/**/z/*.md', + 'a/**/z/*.js', + 'a/**/z/*.md', + 'a/*.js', + 'a/*.md', + 'a/*.txt', + 'a/*/.b', + 'a/*/.b.a', + 'a/*/?/**/e.js', + 'a/*/?/**/e.md', + 'a/*/b', + 'a/*/c/*.js', + 'a/*/c/*.md', + 'a/.*/b', + 'a/?/**/e.js', + 'a/?/**/e.md', + 'a/?/c.js', + 'a/?/c.md', + 'a/?/c/?/*/e.js', + 'a/?/c/?/*/e.md', + 'a/?/c/?/e.js', + 'a/?/c/?/e.md', + 'a/?/c/???/e.js', + 'a/?/c/???/e.md', + 'a/??/c.js', + 'a/??/c.md', + 'a/???/c.js', + 'a/???/c.md', + 'a/????/c.js', + 'a/????/c.md', + 'a/b/**/c{d,e}/**/xyz.js', + 'a/b/**/c{d,e}/**/xyz.md', + 'a/b/c/*.js', + 'A/b/C/*.js', + 'a/b/c/*.md', + 'A/b/C/*.md', + 'A/b/C/*.MD', + 'a/b/c{d,e{f,g}}/*.js', + 'a/b/c{d,e{f,g}}/*.md', + 'a/b/c{d,e}/*.js', + 'a/b/c{d,e}/*.md', + 'a/b/c{d,e}/xyz.js', + 'a/b/c{d,e}/xyz.md', + 'a/{c..e}.js', + 'E:**/*.js', + 'E:**/*.md', + 'E:\\\\**/*.js', + 'E:\\\\**/*.md' +]; diff --git a/packages/node-utils/test/micromatch/api.all.test.ts b/packages/node-utils/test/micromatch/api.all.test.ts new file mode 100644 index 0000000..8aef4a2 --- /dev/null +++ b/packages/node-utils/test/micromatch/api.all.test.ts @@ -0,0 +1,64 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +process.env.PICOMATCH_NO_CACHE = 'true'; + +const { all } = micromatch; + +if (!process.env.ORIGINAL_PATH_SEP) { + process.env.ORIGINAL_PATH_SEP = path.sep; +} + +describe('.all()', () => {test('should throw an error when value is not a string', () => { + expect_throws(() => all()); + }); + + test('should return true when all patterns match the given string', () => { + expect_truthy(all('z', ['z', '*', '[a-z]'])); + expect_truthy(all('b', 'b')); + expect_truthy(all('b', '*')); + }); + + test('should return false when some patterns do not match', () => { + expect_truthy(!all('a', ['a', 'b', '*'])); + expect_truthy(!all('a', ['a*', 'z*'])); + }); + + test('should arrayify a string pattern', () => { + expect_truthy(all('a', '*')); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.braceExpand.test.ts b/packages/node-utils/test/micromatch/api.braceExpand.test.ts new file mode 100644 index 0000000..905166d --- /dev/null +++ b/packages/node-utils/test/micromatch/api.braceExpand.test.ts @@ -0,0 +1,51 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { braceExpand } = micromatch; + +if (!process.env.ORIGINAL_PATH_SEP) { + process.env.ORIGINAL_PATH_SEP = path.sep +} + +describe('.braceExpand()', () => {test('should throw an error when arguments are invalid', () => { + expect_throws(() => braceExpand()); + }); + + test('should expand a brace pattern', () => { + expect_deepEqual(braceExpand('{a,b}'), ['a', 'b']); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.braces.test.ts b/packages/node-utils/test/micromatch/api.braces.test.ts new file mode 100644 index 0000000..af41ce9 --- /dev/null +++ b/packages/node-utils/test/micromatch/api.braces.test.ts @@ -0,0 +1,55 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { braces } = micromatch; + +if (!process.env.ORIGINAL_PATH_SEP) { + process.env.ORIGINAL_PATH_SEP = path.sep +} + +describe('.braces()', () => {test('should throw an error when arguments are invalid', () => { + expect_throws(() => braces()); + }); + + test('should create a regex source string from a brace pattern', () => { + expect_deepEqual(braces('{a,b}'), ['(a|b)']); + }); + + test('should expand a brace pattern', () => { + expect_deepEqual(braces('{a,b}', {expand: true}), ['a', 'b']); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.capture.test.ts b/packages/node-utils/test/micromatch/api.capture.test.ts new file mode 100644 index 0000000..7ccfd27 --- /dev/null +++ b/packages/node-utils/test/micromatch/api.capture.test.ts @@ -0,0 +1,104 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { capture } = micromatch; + +describe('.capture()', () => { + test('should return null if no match', () => { + expect_loose_equal(capture('test/*', 'hi/123'), null); + }); + + test('should return an empty array if there are no captures', () => { + expect_deepEqual(capture('test/hi', 'test/hi'), []); + }); + + test('should capture stars', () => { + expect_deepEqual(capture('test/*', 'test/foo'), ['foo']); + expect_deepEqual(capture('test/*/bar', 'test/foo/bar'), ['foo']); + expect_deepEqual(capture('test/*/bar/*', 'test/foo/bar/baz'), ['foo', 'baz']); + expect_deepEqual(capture('test/*.js', 'test/foo.js'), ['foo']); + expect_deepEqual(capture('test/*-controller.js', 'test/foo-controller.js'), ['foo']); + }); + + test('should capture globstars', () => { + expect_deepEqual(capture('test/**/*.js', 'test/a.js'), ['', 'a']); + expect_deepEqual(capture('test/**/*.js', 'test/dir/a.js'), ['dir', 'a']); + expect_deepEqual(capture('test/**/*.js', 'test/dir/test/a.js'), ['dir/test', 'a']); + expect_deepEqual(capture('**/*.js', 'test/dir/a.js'), ['test/dir', 'a']); + }); + + test('should capture extglobs', () => { + expect_deepEqual(capture('test/+(a|b)/*.js', 'test/a/x.js'), ['a', 'x']); + expect_deepEqual(capture('test/+(a|b)/*.js', 'test/b/x.js'), ['b', 'x']); + expect_deepEqual(capture('test/+(a|b)/*.js', 'test/ab/x.js'), ['ab', 'x']); + }); + + test('should capture paren groups', () => { + expect_deepEqual(capture('test/(a|b)/x.js', 'test/a/x.js'), ['a']); + expect_deepEqual(capture('test/(a|b)/x.js', 'test/b/x.js'), ['b']); + }); + + test('should capture star groups', () => { + expect_deepEqual(capture('test/a*(a|b)/x.js', 'test/a/x.js'), ['']); + expect_deepEqual(capture('test/a*(a|b)/x.js', 'test/aa/x.js'), ['a']); + expect_deepEqual(capture('test/a*(a|b)/x.js', 'test/ab/x.js'), ['b']); + expect_deepEqual(capture('test/a*(a|b)/x.js', 'test/aba/x.js'), ['ba']); + }); + + test('should capture plus groups', () => { + expect_deepEqual(capture('test/+(a|b)/x.js', 'test/a/x.js'), ['a']); + expect_deepEqual(capture('test/+(a|b)/x.js', 'test/b/x.js'), ['b']); + expect_deepEqual(capture('test/+(a|b)/x.js', 'test/ab/x.js'), ['ab']); + expect_deepEqual(capture('test/+(a|b)/x.js', 'test/aba/x.js'), ['aba']); + }); + + test('should capture optional groups', () => { + expect_deepEqual(capture('test/a?(a|b)/x.js', 'test/a/x.js'), ['']); + expect_deepEqual(capture('test/a?(a|b)/x.js', 'test/ab/x.js'), ['b']); + expect_deepEqual(capture('test/a?(a|b)/x.js', 'test/aa/x.js'), ['a']); + }); + + test('should capture @ groups', () => { + expect_deepEqual(capture('test/@(a|b)/x.js', 'test/a/x.js'), ['a']); + expect_deepEqual(capture('test/@(a|b)/x.js', 'test/b/x.js'), ['b']); + }); + + test('should capture negated groups', () => { + expect_deepEqual(capture('test/!(a|b)/x.js', 'test/x/x.js'), ['x']); + expect_deepEqual(capture('test/!(a|b)/x.js', 'test/y/x.js'), ['y']); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.contains.test.ts b/packages/node-utils/test/micromatch/api.contains.test.ts new file mode 100644 index 0000000..1d9c203 --- /dev/null +++ b/packages/node-utils/test/micromatch/api.contains.test.ts @@ -0,0 +1,357 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; +const sep = path.sep; + +describe('.contains()', () => { + afterEach(() => ((path as any).sep = sep)); + after(() => ((path as any).sep = sep)); + + describe('errors', () => { + test('should throw an error arguments are invalid', () => { + expect_throws(() => mm.contains()); + }); + }); + + describe('patterns', () => { + test('should correctly deal with empty patterns', () => { + expect_truthy(!mm.contains('ab', '')); + expect_truthy(!mm.contains('a', '')); + expect_truthy(!mm.contains('.', '')); + }); + + test('should return true when the path contains the pattern', () => { + expect_truthy(mm.contains('ab', 'b')); + expect_truthy(mm.contains('.', '.')); + expect_truthy(mm.contains('a/b/c', 'a/b')); + expect_truthy(mm.contains('/ab', '/a')); + expect_truthy(mm.contains('a', 'a')); + expect_truthy(mm.contains('ab', 'a')); + expect_truthy(mm.contains('ab', 'ab')); + expect_truthy(mm.contains('abcd', 'd')); + expect_truthy(mm.contains('abcd', 'c')); + expect_truthy(mm.contains('abcd', 'cd')); + expect_truthy(mm.contains('abcd', 'bc')); + expect_truthy(mm.contains('abcd', 'ab')); + }); + + test('should be true when a glob pattern partially matches the path', () => { + expect_truthy(mm.contains('a/b/c', 'a/*')); + expect_truthy(mm.contains('/ab', '/a')); + expect_truthy(mm.contains('/ab', '/*')); + expect_truthy(mm.contains('/cd', '/*')); + expect_truthy(mm.contains('ab', '*')); + expect_truthy(mm.contains('ab', 'ab')); + expect_truthy(mm.contains('/ab', '*/a')); + expect_truthy(mm.contains('/ab', '*/')); + expect_truthy(mm.contains('/ab', '*/*')); + expect_truthy(mm.contains('/ab', '/')); + expect_truthy(mm.contains('/ab', '/??')); + expect_truthy(mm.contains('/ab', '/?b')); + expect_truthy(mm.contains('/ab', '/?')); + expect_truthy(mm.contains('a/b', '?/?')); + }); + + test('should return false when the path does not contain the pattern', () => { + expect_truthy(!mm.contains('/ab', '?/?')); + expect_truthy(!mm.contains('ab', '*/*')); + expect_truthy(!mm.contains('abcd', 'f')); + expect_truthy(!mm.contains('ab', 'c')); + expect_truthy(!mm.contains('ab', '/a')); + expect_truthy(!mm.contains('/ab', 'a/*')); + expect_truthy(!mm.contains('ef', '/*')); + }); + + test('should match files that contain the given extension', () => { + expect_truthy(mm.contains('ab', './*')); + expect_truthy(mm.contains('.c.md', '*.md')); + expect_truthy(mm.contains('.c.md', '.*.md')); + expect_truthy(mm.contains('.c.md', '.c.')); + expect_truthy(mm.contains('.c.md', '.md')); + expect_truthy(mm.contains('.md', '.m')); + expect_truthy(mm.contains('a/b/c.md', '**/*.md')); + expect_truthy(mm.contains('a/b/c.md', '*.md')); + expect_truthy(mm.contains('a/b/c.md', '.md')); + expect_truthy(mm.contains('a/b/c.md', 'a/*/*.md')); + expect_truthy(mm.contains('a/b/c/c.md', '*.md')); + expect_truthy(mm.contains('c.md', '*.md')); + }); + + test('should not match files that do not contain the given extension', () => { + expect_truthy(!mm.contains('.md', '*.md')); + expect_truthy(!mm.contains('a/b/c/c.md', 'c.js')); + expect_truthy(!mm.contains('a/b/c.md', 'a/*.md')); + }); + + test('should match dotfiles when a dot is explicitly defined in the pattern', () => { + expect_truthy(mm.contains('.a', '.a')); + expect_truthy(mm.contains('.ab', '.*')); + expect_truthy(mm.contains('.ab', '.a*')); + expect_truthy(mm.contains('.abc', '.a')); + expect_truthy(mm.contains('.b', '.b*')); + expect_truthy(mm.contains('.c.md', '*.md')); + expect_truthy(mm.contains('.md', '.md')); + expect_truthy(mm.contains('a/.c.md', '*.md')); + expect_truthy(mm.contains('a/.c.md', 'a/.c.md')); + expect_truthy(mm.contains('a/b/c/.xyz.md', 'a/b/c/.*.md')); + expect_truthy(mm.contains('a/b/c/d.a.md', 'a/b/c/*.md')); + }); + + test('should match dotfiles when `dot` or `dotfiles` is set', () => { + expect_truthy(mm.contains('.c.md', '*.md', { dot: true })); + expect_truthy(mm.contains('.c.md', '.*', { dot: true })); + expect_truthy(mm.contains('a/b/c/.xyz.md', '**/*.md', { dot: true })); + expect_truthy(mm.contains('a/b/c/.xyz.md', '**/.*.md', { dot: true })); + expect_truthy(mm.contains('a/b/c/.xyz.md', '.*.md', { dot: true })); + expect_truthy(mm.contains('a/b/c/.xyz.md', 'a/b/c/*.md', { dot: true })); + expect_truthy(mm.contains('a/b/c/.xyz.md', 'a/b/c/.*.md', { dot: true })); + }); + + test('should not match dotfiles when `dot` or `dotfiles` is not set', () => { + expect_truthy(!mm.contains('.a', '*.md')); + expect_truthy(!mm.contains('.ba', '.a')); + expect_truthy(!mm.contains('.a.md', 'a/b/c/*.md')); + expect_truthy(!mm.contains('.ab', '*.*')); + expect_truthy(!mm.contains('.md', 'a/b/c/*.md')); + expect_truthy(!mm.contains('.txt', '.md')); + expect_truthy(!mm.contains('.verb.txt', '*.md')); + expect_truthy(!mm.contains('a/b/d/.md', 'a/b/c/*.md')); + }); + + test('should match file paths', () => { + expect_truthy(mm.contains('a/b/c/xyz.md', 'a/b/c/*.md')); + expect_truthy(mm.contains('a/bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(mm.contains('a/bbbb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(mm.contains('a/bb.bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(mm.contains('a/bb.bb/aa/bb/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(mm.contains('a/bb.bb/aa/b.b/aa/c/xyz.md', 'a/**/c/*.md')); + }); + + test('should return true when full file paths are matched', () => { + expect_truthy(mm.contains('a/.b', 'a/.*')); + expect_truthy(mm.contains('a/.b', 'a/')); + expect_truthy(mm.contains('a/b/z/.a', 'b/z')); + expect_truthy(mm.contains('a/b/z/.a', 'a/*/z/.a')); + expect_truthy(mm.contains('a/b/c/d/e/z/c.md', 'a/**/z/*.md')); + expect_truthy(mm.contains('a/b/c/d/e/z/c.md', 'b/c/d/e')); + expect_truthy(mm.contains('a/b/c/d/e/j/n/p/o/z/c.md', 'a/**/j/**/z/*.md')); + }); + + test('should match path segments', () => { + expect_truthy(mm.contains('aaa', 'aaa')); + expect_truthy(mm.contains('aaa', 'aa')); + expect_truthy(mm.contains('aaa/bbb', 'aaa/bbb')); + expect_truthy(mm.contains('aaa/bbb', 'aaa/*')); + expect_truthy(mm.contains('aaa/bba/ccc', '**/*/ccc')); + expect_truthy(mm.contains('aaa/bba/ccc', '*/*a')); + expect_truthy(mm.contains('aaa/bba/ccc', 'aaa*')); + expect_truthy(mm.contains('aaa/bba/ccc', 'aaa**')); + expect_truthy(mm.contains('aaa/bba/ccc', 'aaa/*')); + expect_truthy(mm.contains('aaa/bba/ccc', 'aaa/**')); + expect_truthy(mm.contains('aaa/bba/ccc', 'aaa/*/ccc')); + expect_truthy(mm.contains('aaa/bba/ccc', 'bb')); + expect_truthy(mm.contains('aaa/bba/ccc', 'bb*')); + expect_truthy(!mm.contains('aaa/bba/ccc', 'aaa/*ccc')); + expect_truthy(!mm.contains('aaa/bba/ccc', 'aaa/**ccc')); + expect_truthy(!mm.contains('aaa/bba/ccc', 'aaa/*z')); + expect_truthy(!mm.contains('aaa/bba/ccc', 'aaa/**z')); + expect_truthy(mm.contains('aaa/bbb', 'aaa[/]bbb')); + expect_truthy(!mm.contains('aaa', '*/*/*')); + expect_truthy(!mm.contains('aaa/bbb', '*/*/*')); + expect_truthy(mm.contains('aaa/bba/ccc', '*/*/*')); + expect_truthy(mm.contains('aaa/bb/aa/rr', '*/*/*')); + expect_truthy(mm.contains('abzzzejklhi', '*j*i')); + expect_truthy(mm.contains('ab/zzz/ejkl/hi', '*/*z*/*/*i')); + expect_truthy(mm.contains('ab/zzz/ejkl/hi', '*/*jk*/*i')); + }); + + test('should return false when full file paths are not matched', () => { + expect_truthy(!mm.contains('a/b/z/.a', 'b/a')); + expect_truthy(!mm.contains('a/.b', 'a/**/z/*.md')); + expect_truthy(!mm.contains('a/b/z/.a', 'a/**/z/*.a')); + expect_truthy(!mm.contains('a/b/z/.a', 'a/*/z/*.a')); + expect_truthy(!mm.contains('a/b/c/j/e/z/c.txt', 'a/**/j/**/z/*.md')); + }); + + test('should match paths with leading `./`', () => { + expect_truthy(!mm.contains('./.a', 'a/**/z/*.md')); + expect_truthy(mm.contains('./a/b/z/.a', 'a/**/z/.a')); + expect_truthy(mm.contains('./a/b/z/.a', './a/**/z/.a')); + expect_truthy(mm.contains('./a/b/c/d/e/z/c.md', 'a/**/z/*.md')); + expect_truthy(mm.contains('./a/b/c/d/e/z/c.md', './a/**/z/*.md')); + expect_truthy(!mm.contains('./a/b/c/d/e/z/c.md', './a/**/j/**/z/*.md')); + expect_truthy(mm.contains('./a/b/c/j/e/z/c.md', './a/**/j/**/z/*.md')); + expect_truthy(mm.contains('./a/b/c/j/e/z/c.md', 'a/**/j/**/z/*.md')); + expect_truthy(mm.contains('./a/b/c/d/e/j/n/p/o/z/c.md', './a/**/j/**/z/*.md')); + expect_truthy(!mm.contains('./a/b/c/j/e/z/c.txt', './a/**/j/**/z/*.md')); + }); + }); + + describe('windows paths', () => { + beforeEach(() => { + (path as any).sep = '\\'; + }); + afterEach(() => { + (path as any).sep = sep; + }); + + test('should match with common glob patterns', () => { + expect_truthy(mm.contains('\\ab', '*/')); + expect_truthy(mm.contains('ab\\', '*/')); + expect_truthy(mm.contains('\\ab', '*/*')); + expect_truthy(mm.contains('\\ab', '*/[a-z]*')); + expect_truthy(mm.contains('\\ab', '*/*[a-z]')); + expect_truthy(mm.contains('\\ab', '*/a')); + expect_truthy(mm.contains('\\ab', '/')); + expect_truthy(mm.contains('\\ab', '/*')); + expect_truthy(mm.contains('\\ab', '/?')); + expect_truthy(mm.contains('\\ab', '/??')); + expect_truthy(mm.contains('\\ab', '/?b')); + expect_truthy(mm.contains('\\ab', '/a')); + expect_truthy(mm.contains('\\cd', '/*')); + expect_truthy(mm.contains('a\\b', '?/?')); + expect_truthy(mm.contains('a\\b\\c', 'a/*')); + + expect_truthy(!mm.contains('\\ab', '*/', { windows: false })); + expect_truthy(!mm.contains('\\ab', '*/*', { windows: false })); + expect_truthy(!mm.contains('\\ab', '*/[a-z]*', { windows: false })); + expect_truthy(!mm.contains('\\ab', '*/a', { windows: false })); + expect_truthy(!mm.contains('\\ab', '/', { windows: false })); + expect_truthy(!mm.contains('\\ab', '/*', { windows: false })); + expect_truthy(!mm.contains('\\ab', '/?', { windows: false })); + expect_truthy(!mm.contains('\\ab', '/??', { windows: false })); + expect_truthy(!mm.contains('\\ab', '/?b', { windows: false })); + expect_truthy(!mm.contains('\\ab', '/a', { windows: false })); + expect_truthy(!mm.contains('\\cd', '/*', { windows: false })); + expect_truthy(!mm.contains('a\\b', '?/?', { windows: false })); + expect_truthy(!mm.contains('a\\b\\c', 'a/*', { windows: false })); + }); + + test('should match files that contain the given extension', () => { + expect_truthy(mm.contains('a\\b\\c.md', '**/*.md')); + expect_truthy(mm.contains('a\\b\\c.md', '*.md')); + expect_truthy(mm.contains('a\\b\\c.md', '.md')); + expect_truthy(mm.contains('a\\b\\c.md', 'a/*/*.md')); + expect_truthy(mm.contains('a\\b\\c\\c.md', '*.md')); + }); + + test('should match dotfiles when `dot` is true', () => { + expect_truthy(mm.contains('a\\b\\c\\.xyz.md', '.*.md', { windows: true, dot: true })); + expect_truthy(mm.contains('a\\b\\c\\.xyz.md', '**/*.md', { windows: true, dot: true })); + expect_truthy(mm.contains('a\\b\\c\\.xyz.md', '**/.*.md', { windows: true, dot: true })); + expect_truthy(mm.contains('a\\b\\c\\.xyz.md', 'a/b/c/*.md', { windows: true, dot: true })); + expect_truthy(mm.contains('a\\b\\c\\.xyz.md', 'a/b/c/.*.md', { windows: true, dot: true })); + }); + + test('should not match dotfiles when `dot` or `dotfiles` is not set', () => { + expect_truthy(!mm.contains('a\\b\\d\\.md', 'a/b/c/*.md')); + }); + + test('should match file paths', () => { + expect_truthy(mm.contains('a\\b\\c\\xyz.md', 'a/b/c/*.md')); + expect_truthy(mm.contains('a\\bb\\c\\xyz.md', 'a/*/c/*.md')); + expect_truthy(mm.contains('a\\bbbb\\c\\xyz.md', 'a/*/c/*.md')); + expect_truthy(mm.contains('a\\bb.bb\\c\\xyz.md', 'a/*/c/*.md')); + expect_truthy(mm.contains('a\\bb.bb\\aa\\bb\\aa\\c\\xyz.md', 'a/**/c/*.md')); + expect_truthy(mm.contains('a\\bb.bb\\aa\\b.b\\aa\\c\\xyz.md', 'a/**/c/*.md')); + }); + + test('should return true when full file paths are matched', () => { + expect_truthy(mm.contains('a\\.b', 'a/.*')); + expect_truthy(mm.contains('a\\.b', 'a/')); + expect_truthy(mm.contains('a\\b\\z\\.a', 'b/z')); + expect_truthy(mm.contains('a\\b\\z\\.a', 'a/*/z/.a')); + expect_truthy(mm.contains('a\\b\\c\\d\\e\\z\\c.md', 'a/**/z/*.md')); + expect_truthy(mm.contains('a\\b\\c\\d\\e\\z\\c.md', 'b/c/d/e')); + expect_truthy(mm.contains('a\\b\\c\\d\\e\\j\\n\\p\\o\\z\\c.md', 'a/**/j/**/z/*.md')); + }); + + test('should match path segments', () => { + expect_truthy(mm.contains('aaa\\bbb', 'aaa/bbb')); + expect_truthy(mm.contains('aaa\\bbb', 'aaa/*')); + expect_truthy(mm.contains('aaa\\bba\\ccc', '**/*/ccc')); + expect_truthy(mm.contains('aaa\\bba\\ccc', '*/*a')); + expect_truthy(mm.contains('aaa\\bba\\ccc', 'aaa*')); + expect_truthy(mm.contains('aaa\\bba\\ccc', 'aaa**')); + expect_truthy(mm.contains('aaa\\bba\\ccc', 'aaa/*')); + expect_truthy(mm.contains('aaa\\bba\\ccc', 'aaa/**')); + expect_truthy(mm.contains('aaa\\bba\\ccc', 'aaa/*/ccc')); + expect_truthy(mm.contains('aaa\\bba\\ccc', 'bb')); + expect_truthy(mm.contains('aaa\\bba\\ccc', 'bb*')); + expect_truthy(mm.contains('aaa\\bbb', 'aaa[/]bbb')); + expect_truthy(mm.contains('aaa\\bbb', 'aaa[\\\\/]bbb')); + expect_truthy(!mm.contains('aaa\\bba\\ccc', 'aaa/*ccc')); + expect_truthy(!mm.contains('aaa\\bba\\ccc', 'aaa/**ccc')); + expect_truthy(!mm.contains('aaa\\bba\\ccc', 'aaa/*z')); + expect_truthy(!mm.contains('aaa\\bba\\ccc', 'aaa/**z')); + expect_truthy(!mm.contains('\\aaa', '*/*/*')); + expect_truthy(!mm.contains('aaa\\bbb', '*/*/*')); + expect_truthy(mm.contains('aaa\\bba\\ccc', '*/*/*')); + expect_truthy(mm.contains('aaa\\bb\\aa\\rr', '*/*/*')); + expect_truthy(mm.contains('ab\\zzz\\ejkl\\hi', '*/*z*/*/*i')); + expect_truthy(mm.contains('ab\\zzz\\ejkl\\hi', '*/*jk*/*i')); + }); + + test('should return false when full file paths are not matched', () => { + expect_truthy(!mm.contains('a\\b\\z\\.a', 'b/a')); + expect_truthy(!mm.contains('a\\.b', 'a/**/z/*.md')); + expect_truthy(!mm.contains('a\\b\\z\\.a', 'a/**/z/*.a')); + expect_truthy(!mm.contains('a\\b\\z\\.a', 'a/*/z/*.a')); + expect_truthy(!mm.contains('a\\b\\c\\j\\e\\z\\c.txt', 'a/**/j/**/z/*.md')); + }); + + test('should match dotfiles when a dot is explicitly defined in the pattern', () => { + expect_truthy(mm.contains('a\\.c.md', 'a/.c.md')); + expect_truthy(mm.contains('a\\b\\c\\.xyz.md', 'a/b/c/.*.md')); + expect_truthy(mm.contains('a\\.c.md', '*.md')); + expect_truthy(mm.contains('a\\b\\c\\d.a.md', 'a/b/c/*.md')); + }); + + test('should match paths with leading `./`', () => { + expect_truthy(!mm.contains('.\\.a', 'a/**/z/*.md')); + expect_truthy(!mm.contains('.\\a\\b\\c\\d\\e\\z\\c.md', './a/**/j/**/z/*.md')); + expect_truthy(mm.contains('.\\a\\b\\c\\d\\e\\j\\n\\p\\o\\z\\c.md', './a/**/j/**/z/*.md')); + expect_truthy(mm.contains('.\\a\\b\\c\\d\\e\\z\\c.md', './a/**/z/*.md')); + expect_truthy(mm.contains('.\\a\\b\\c\\d\\e\\z\\c.md', 'a/**/z/*.md')); + expect_truthy(mm.contains('.\\a\\b\\c\\j\\e\\z\\c.md', './a/**/j/**/z/*.md')); + expect_truthy(mm.contains('.\\a\\b\\c\\j\\e\\z\\c.md', 'a/**/j/**/z/*.md')); + expect_truthy(mm.contains('.\\a\\b\\z\\.a', './a/**/z/.a')); + expect_truthy(mm.contains('.\\a\\b\\z\\.a', 'a/**/z/.a')); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.every.test.ts b/packages/node-utils/test/micromatch/api.every.test.ts new file mode 100644 index 0000000..a5dea85 --- /dev/null +++ b/packages/node-utils/test/micromatch/api.every.test.ts @@ -0,0 +1,56 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; + +describe('.every()', () => { + + test('should return true if every string matches', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_truthy(!mm.every(fixtures, ['z', '*/*'])); + }); + + test('should return false when not all strings match', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_truthy(!mm.every(fixtures, ['a/*', 'x/*'])); + expect_truthy(mm.every(fixtures, ['(a|b)/*', '*/*'])); + }); + + test('should arrayify a string value', () => { + expect_truthy(mm.every('a', '*')); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.isMatch.test.ts b/packages/node-utils/test/micromatch/api.isMatch.test.ts new file mode 100644 index 0000000..98f4cdb --- /dev/null +++ b/packages/node-utils/test/micromatch/api.isMatch.test.ts @@ -0,0 +1,598 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch, any } = micromatch; + +describe('.isMatch():', () => { + describe('error handling:', () => { + test('should throw on bad args', () => { + expect_throws(() => isMatch({}), /Expected/i); + }); + }); + + describe('alias:', () => { + test('should have the alias .any(...)', () => { + expect_equal(isMatch, any); + }); + }); + + describe('matching:', () => { + test('should escape plus signs to match string literals', () => { + expect_truthy(isMatch('a+b/src/glimini.js', 'a+b/src/*.js')); + expect_truthy(isMatch('+b/src/glimini.js', '+b/src/*.js')); + expect_truthy(isMatch('coffee+/src/glimini.js', 'coffee+/src/*.js')); + expect_truthy(isMatch('coffee+/src/glimini.js', 'coffee+/src/*.js')); + expect_truthy(isMatch('coffee+/src/glimini.js', 'coffee+/src/*')); + }); + + test('should not escape plus signs that follow brackets', () => { + expect_truthy(isMatch('a', '[a]+')); + expect_truthy(isMatch('aa', '[a]+')); + expect_truthy(isMatch('aaa', '[a]+')); + expect_truthy(isMatch('az', '[a-z]+')); + expect_truthy(isMatch('zzz', '[a-z]+')); + }); + + test('should support stars following brackets', () => { + expect_truthy(isMatch('a', '[a]*')); + expect_truthy(isMatch('aa', '[a]*')); + expect_truthy(isMatch('aaa', '[a]*')); + expect_truthy(isMatch('az', '[a-z]*')); + expect_truthy(isMatch('zzz', '[a-z]*')); + }); + + test('should not escape plus signs that follow parens', () => { + expect_truthy(isMatch('a', '(a)+')); + expect_truthy(isMatch('ab', '(a|b)+')); + expect_truthy(isMatch('aa', '(a)+')); + expect_truthy(isMatch('aaab', '(a|b)+')); + expect_truthy(isMatch('aaabbb', '(a|b)+')); + }); + + test('should support stars following parens', () => { + expect_truthy(isMatch('a', '(a)*')); + expect_truthy(isMatch('ab', '(a|b)*')); + expect_truthy(isMatch('aa', '(a)*')); + expect_truthy(isMatch('aaab', '(a|b)*')); + expect_truthy(isMatch('aaabbb', '(a|b)*')); + }); + + test('should not match slashes with single stars', () => { + expect_truthy(!isMatch('a/b', '(a)*')); + expect_truthy(!isMatch('a/b', '[a]*')); + expect_truthy(!isMatch('a/b', 'a*')); + expect_truthy(!isMatch('a/b', '(a|b)*')); + }); + + test('should not match dots with stars by default', () => { + expect_truthy(!isMatch('.a', '(a)*')); + expect_truthy(!isMatch('.a', '*[a]*')); + expect_truthy(!isMatch('.a', '*[a]')); + expect_truthy(!isMatch('.a', '*a*')); + expect_truthy(!isMatch('.a', '*a')); + expect_truthy(!isMatch('.a', '*(a|b)')); + }); + + test('should match with non-glob patterns', () => { + expect_truthy(isMatch('.', '.')); + expect_truthy(isMatch('/a', '/a')); + expect_truthy(!isMatch('/ab', '/a')); + expect_truthy(isMatch('a', 'a')); + expect_truthy(!isMatch('ab', '/a')); + expect_truthy(!isMatch('ab', 'a')); + expect_truthy(isMatch('ab', 'ab')); + expect_truthy(!isMatch('abcd', 'cd')); + expect_truthy(!isMatch('abcd', 'bc')); + expect_truthy(!isMatch('abcd', 'ab')); + }); + + test('should match non-leading dots', () => { + expect_truthy(isMatch('a.b', 'a.b')); + expect_truthy(isMatch('a.b', '*.b')); + expect_truthy(isMatch('a.b', 'a.*')); + expect_truthy(isMatch('a.b', '*.*')); + expect_truthy(isMatch('a-b.c-d', 'a*.c*')); + expect_truthy(isMatch('a-b.c-d', '*b.*d')); + expect_truthy(isMatch('a-b.c-d', '*.*')); + expect_truthy(isMatch('a-b.c-d', '*.*-*')); + expect_truthy(isMatch('a-b.c-d', '*-*.*-*')); + expect_truthy(isMatch('a-b.c-d', '*.c-*')); + expect_truthy(isMatch('a-b.c-d', '*.*-d')); + expect_truthy(isMatch('a-b.c-d', 'a-*.*-d')); + expect_truthy(isMatch('a-b.c-d', '*-b.c-*')); + expect_truthy(isMatch('a-b.c-d', '*-b*c-*')); + + // false + expect_truthy(!isMatch('a-b.c-d', '*-bc-*')); + }); + + test('should match with common glob patterns', () => { + expect_truthy(!isMatch('/ab', './*/')); + expect_truthy(!isMatch('/ef', '*')); + expect_truthy(!isMatch('ab', './*/')); + expect_truthy(!isMatch('ef', '/*')); + expect_truthy(isMatch('/ab', '/*')); + expect_truthy(isMatch('/cd', '/*')); + expect_truthy(isMatch('ab', '*')); + expect_truthy(isMatch('ab', './*')); + expect_truthy(isMatch('ab', 'ab')); + expect_truthy(isMatch('ab/', './*/')); + }); + + test('should exactly match leading slash', () => { + expect_truthy(!isMatch('ef', '/*')); + expect_truthy(isMatch('/ef', '/*')); + }); + + test('should match files with the given extension', () => { + expect_truthy(!isMatch('.c.md', '*.md')); + expect_truthy(!isMatch('.md', '*.md')); + expect_truthy(!isMatch('a/b/c.md', 'a/*.md')); + expect_truthy(!isMatch('a/b/c/c.md', '*.md')); + expect_truthy(isMatch('.c.md', '.*.md')); + expect_truthy(isMatch('.md', '.md')); + expect_truthy(isMatch('a/b/c.js', 'a/**/*.*')); + expect_truthy(isMatch('a/b/c.md', '**/*.md')); + expect_truthy(isMatch('a/b/c.md', 'a/*/*.md')); + expect_truthy(isMatch('c.md', '*.md')); + expect_truthy(isMatch('c.md', '*.md')); + }); + + test('should match wildcards', () => { + expect_truthy(!isMatch('a/b/c/z.js', '*.js')); + expect_truthy(!isMatch('a/b/z.js', '*.js')); + expect_truthy(!isMatch('a/z.js', '*.js')); + expect_truthy(isMatch('z.js', '*.js')); + + expect_truthy(isMatch('z.js', 'z*.js')); + expect_truthy(isMatch('a/z.js', 'a/z*.js')); + expect_truthy(isMatch('a/z.js', '*/z*.js')); + expect_truthy(isMatch('a/b', 'a/b*')); + expect_truthy(isMatch('a/b', 'a/b*', { dot: true })); + }); + + test('should match globstars', () => { + expect_truthy(isMatch('a/b/c/z.js', '**/*.js')); + expect_truthy(isMatch('a/b/z.js', '**/*.js')); + expect_truthy(isMatch('a/z.js', '**/*.js')); + expect_truthy(isMatch('a/b/c/d/e/z.js', 'a/b/**/*.js')); + expect_truthy(isMatch('a/b/c/d/z.js', 'a/b/**/*.js')); + expect_truthy(isMatch('a/b/c/z.js', 'a/b/c/**/*.js')); + expect_truthy(isMatch('a/b/c/z.js', 'a/b/c**/*.js')); + expect_truthy(isMatch('a/b/c/z.js', 'a/b/**/*.js')); + expect_truthy(isMatch('a/b/z.js', 'a/b/**/*.js')); + + expect_truthy(!isMatch('a/z.js', 'a/b/**/*.js')); + expect_truthy(!isMatch('z.js', 'a/b/**/*.js')); + + // https://github.com/micromatch/micromatch/issues/15 + expect_truthy(isMatch('z.js', 'z*')); + expect_truthy(isMatch('z.js', '**/z*')); + expect_truthy(isMatch('z.js', '**/z*.js')); + expect_truthy(isMatch('z.js', '**/*.js')); + expect_truthy(isMatch('foo', '**/foo')); + }); + + test('issue #23', () => { + expect_truthy(!isMatch('zzjs', 'z*.js')); + expect_truthy(!isMatch('zzjs', '*z.js')); + }); + + test('issue #24', () => { + expect_truthy(isMatch('a', '**')); + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(isMatch('a/', '**')); + expect_truthy(isMatch('a/b/c/d', '**')); + expect_truthy(isMatch('a/b/c/d/', '**')); + expect_truthy(isMatch('a/b/c/d/', '**/**')); + expect_truthy(isMatch('a/b/c/d/', '**/b/**')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**/')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**/c/**/')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**/c/**/d/')); + expect_truthy(!isMatch('a/b/c/d/', 'a/b/**/f')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/**/*.*')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/*.*')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/c/**/d/*.*')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/d/**/*.*')); + expect_truthy(isMatch('a/b/c/d/g/e.f', 'a/b/**/d/**/*.*')); + expect_truthy(isMatch('a/b/c/d/g/g/e.f', 'a/b/**/d/**/*.*')); + expect_truthy(isMatch('a/b-c/z.js', 'a/b-*/**/z.js')); + expect_truthy(isMatch('a/b-c/d/e/z.js', 'a/b-*/**/z.js')); + }); + + test('should match slashes', () => { + expect_truthy(!isMatch('bar/baz/foo', '*/foo')); + expect_truthy(!isMatch('deep/foo/bar', '**/bar/*')); + expect_truthy(!isMatch('deep/foo/bar/baz/x', '*/bar/**')); + expect_truthy(!isMatch('foo/bar', 'foo?bar')); + expect_truthy(!isMatch('foo/bar/baz', '**/bar*')); + expect_truthy(!isMatch('foo/bar/baz', '**/bar**')); + expect_truthy(!isMatch('foo/baz/bar', 'foo**bar')); + expect_truthy(!isMatch('foo/baz/bar', 'foo*bar')); + expect_truthy(isMatch('foo', 'foo/**')); + expect_truthy(isMatch('a/b/j/c/z/x.md', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('a/j/z/x.md', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('bar/baz/foo', '**/foo')); + expect_truthy(isMatch('deep/foo/bar/', '**/bar/**')); + expect_truthy(isMatch('deep/foo/bar/baz', '**/bar/*')); + expect_truthy(isMatch('deep/foo/bar/baz/', '**/bar/*/')); + expect_truthy(isMatch('deep/foo/bar/baz/', '**/bar/**')); + expect_truthy(isMatch('deep/foo/bar/baz/x', '**/bar/*/*')); + expect_truthy(isMatch('foo/b/a/z/bar', 'foo/**/**/bar')); + expect_truthy(isMatch('foo/b/a/z/bar', 'foo/**/bar')); + expect_truthy(isMatch('foo/bar', 'foo/**/**/bar')); + expect_truthy(isMatch('foo/bar', 'foo/**/bar')); + expect_truthy(isMatch('foo/bar', 'foo[/]bar')); + expect_truthy(isMatch('foo/bar/baz/x', '*/bar/**')); + expect_truthy(isMatch('foo/baz/bar', 'foo/**/**/bar')); + expect_truthy(isMatch('foo/baz/bar', 'foo/**/bar')); + expect_truthy(isMatch('foobazbar', 'foo**bar')); + expect_truthy(isMatch('XXX/foo', '**/foo')); + + // https://github.com/micromatch/micromatch/issues/89 + expect_truthy(isMatch('foo//baz.md', 'foo//baz.md')); + expect_truthy(isMatch('foo//baz.md', 'foo//*baz.md')); + expect_truthy(!isMatch('foo//baz.md', 'foo/baz.md')); + expect_truthy(!isMatch('foo/baz.md', 'foo//baz.md')); + }); + + test('question marks should not match slashes', () => { + expect_truthy(!isMatch('aaa/bbb', 'aaa?bbb')); + }); + + test('should not match dotfiles when `dot` or `dotfiles` are not set', () => { + expect_truthy(!isMatch('.c.md', '*.md')); + expect_truthy(!isMatch('a/.c.md', '*.md')); + expect_truthy(isMatch('a/.c.md', 'a/.c.md')); + expect_truthy(!isMatch('.a', '*.md')); + expect_truthy(!isMatch('.verb.txt', '*.md')); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/.*.md')); + expect_truthy(isMatch('.md', '.md')); + expect_truthy(!isMatch('.txt', '.md')); + expect_truthy(isMatch('.md', '.md')); + expect_truthy(isMatch('.a', '.a')); + expect_truthy(isMatch('.b', '.b*')); + expect_truthy(isMatch('.ab', '.a*')); + expect_truthy(isMatch('.ab', '.*')); + expect_truthy(!isMatch('.ab', '*.*')); + expect_truthy(!isMatch('.md', 'a/b/c/*.md')); + expect_truthy(!isMatch('.a.md', 'a/b/c/*.md')); + expect_truthy(isMatch('a/b/c/d.a.md', 'a/b/c/*.md')); + expect_truthy(!isMatch('a/b/d/.md', 'a/b/c/*.md')); + }); + + test('should match dotfiles when `dot` or `dotfiles` is set', () => { + expect_truthy(isMatch('.c.md', '*.md', { dot: true })); + expect_truthy(isMatch('.c.md', '.*', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/.*.md', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/*.md', { dot: true })); + }); + + test('should match file paths', () => { + expect_truthy(isMatch('a/b/c/xyz.md', 'a/b/c/*.md')); + expect_truthy(isMatch('a/bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bbbb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bb.bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bb.bb/aa/bb/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(isMatch('a/bb.bb/aa/b.b/aa/c/xyz.md', 'a/**/c/*.md')); + }); + + test('should match full file paths', () => { + expect_truthy(!isMatch('a/.b', 'a/**/z/*.md')); + expect_truthy(isMatch('a/.b', 'a/.*')); + expect_truthy(!isMatch('a/b/z/.a', 'a/**/z/*.a')); + expect_truthy(!isMatch('a/b/z/.a', 'a/*/z/*.a')); + expect_truthy(isMatch('a/b/z/.a', 'a/*/z/.a')); + expect_truthy(isMatch('a/b/c/d/e/z/c.md', 'a/**/z/*.md')); + expect_truthy(isMatch('a/b/c/d/e/j/n/p/o/z/c.md', 'a/**/j/**/z/*.md')); + expect_truthy(!isMatch('a/b/c/j/e/z/c.txt', 'a/**/j/**/z/*.md')); + }); + + test('should match paths with leading `./` when pattern has `./`', () => { + let format = str => str.replace(/^\.\//, ''); + expect_truthy(isMatch('./a/b/c/d/e/j/n/p/o/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', './a/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/j/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/z/.a', './a/**/z/.a', { format })); + expect_truthy(!isMatch('./a/b/c/d/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(!isMatch('./a/b/c/j/e/z/c.txt', './a/**/j/**/z/*.md', { format })); + }); + + test('should match paths with leading `./`', () => { + let format = str => str.replace(/^\.\//, ''); + expect_truthy(!isMatch('./.a', '*.a', { format })); + expect_truthy(!isMatch('./.a', './*.a', { format })); + expect_truthy(!isMatch('./.a', 'a/**/z/*.md', { format })); + expect_truthy(!isMatch('./a/b/c/d/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(!isMatch('./a/b/c/j/e/z/c.txt', './a/**/j/**/z/*.md', { format })); + expect_truthy(!isMatch('a/b/c/d/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./.a', './.a', { format })); + expect_truthy(isMatch('./a/b/c.md', 'a/**/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/j/n/p/o/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', '**/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', './a/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', 'a/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/j/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/j/e/z/c.md', '?(./)a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/z/.a', './a/**/z/.a', { format })); + expect_truthy(isMatch('./a/b/z/.a', '?(./)a/**/z/.a', { format })); + expect_truthy(isMatch('.a', './.a', { format })); + expect_truthy(isMatch('a/b/c.md', './a/**/*.md', { format })); + expect_truthy(isMatch('a/b/c.md', 'a/**/*.md', { format })); + expect_truthy(isMatch('a/b/c/d/e/z/c.md', 'a/**/z/*.md', { format })); + expect_truthy(isMatch('a/b/c/j/e/z/c.md', 'a/**/j/**/z/*.md', { format })); + }); + }); + + describe('errors', () => { + test('should throw an error when value is not a string', () => { + expect_throws(() => isMatch()); + }); + }); + + describe('empty patterns', () => { + test('should throw an error when empty patterns are defined', () => { + expect_throws(() => isMatch('', '')); + expect_throws(() => isMatch('', [''])); + expect_throws(() => isMatch('.', '')); + expect_throws(() => isMatch('.', [''])); + expect_throws(() => isMatch('a', '')); + expect_throws(() => isMatch('a', [''])); + expect_throws(() => isMatch('ab', '')); + expect_throws(() => isMatch('ab', [''])); + expect_throws(() => isMatch('./', '')); + expect_throws(() => isMatch('./', [''])); + }); + }); + + describe('non-globs', () => { + test('should match literal paths', () => { + expect_truthy(!isMatch('aaa', 'aa')); + expect_truthy(isMatch('aaa', 'aaa')); + expect_truthy(isMatch('aaa', ['aa', 'aaa'])); + expect_truthy(isMatch('aaa/bbb', 'aaa/bbb')); + expect_truthy(isMatch('aaa/bbb', 'aaa[/]bbb')); + expect_truthy(isMatch('aaa/bbb', ['aaa\\bbb', 'aaa/bbb'])); + expect_truthy(isMatch('aaa\\bbb', ['aaa\\bbb', 'aaa/bbb'])); + }); + }); + + describe('dots', () => { + test('should match a dots with dots in the pattern', () => { + expect_truthy(isMatch('.', '.')); + }); + }); + + describe('stars (single pattern)', () => { + test('should return true when one of the given patterns matches the string', () => { + expect_truthy(!isMatch('a/.b', 'a/')); + expect_truthy(!isMatch('a/b/c/d/e/z/c.md', 'b/c/d/e')); + expect_truthy(!isMatch('a/b/z/.a', 'b/z')); + expect_truthy(isMatch('/ab', '*/*')); + expect_truthy(isMatch('/ab', '*/*')); + expect_truthy(isMatch('/ab', '/*')); + expect_truthy(isMatch('/cd', '/*')); + expect_truthy(isMatch('a', 'a')); + expect_truthy(isMatch('a/.b', 'a/.*')); + expect_truthy(isMatch('a/b/c/d/e/j/n/p/o/z/c.md', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('a/b/c/d/e/z/c.md', 'a/**/z/*.md')); + expect_truthy(isMatch('a/b/c/xyz.md', 'a/b/c/*.md')); + expect_truthy(isMatch('a/b/c/xyz.md', ['foo', 'a/b/c/*.md'])); + expect_truthy(isMatch('a/b/z/.a', 'a/*/z/.a')); + expect_truthy(isMatch('a/bb.bb/aa/b.b/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(isMatch('a/bb.bb/aa/bb/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(isMatch('a/bb.bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bbbb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('aaa', ['foo', '*'])); + expect_truthy(isMatch('ab', '*')); + expect_truthy(isMatch('ab', './*')); + expect_truthy(isMatch('ab', 'ab')); + expect_truthy(isMatch('ab/', './*/')); + }); + + test('should return false when the path does not match the pattern', () => { + expect_truthy(!isMatch('ab/', '*/*')); + expect_truthy(!isMatch('/ab', '*/')); + expect_truthy(!isMatch('/ab', '*/a')); + expect_truthy(!isMatch('/ab', '/')); + expect_truthy(!isMatch('/ab', '/a')); + expect_truthy(!isMatch('/ab', 'a/*')); + expect_truthy(!isMatch('a/.b', 'a/')); + expect_truthy(!isMatch('a/b/c', 'a/*')); + expect_truthy(!isMatch('a/b/c', 'a/b')); + expect_truthy(!isMatch('a/b/c/d/e/z/c.md', 'b/c/d/e')); + expect_truthy(!isMatch('a/b/z/.a', 'b/z')); + expect_truthy(!isMatch('ab', '*/*')); + expect_truthy(!isMatch('ab', '/a')); + expect_truthy(!isMatch('ab', 'a')); + expect_truthy(!isMatch('ab', 'b')); + expect_truthy(!isMatch('ab', 'c')); + expect_truthy(!isMatch('abcd', 'ab')); + expect_truthy(!isMatch('abcd', 'bc')); + expect_truthy(!isMatch('abcd', 'c')); + expect_truthy(!isMatch('abcd', 'cd')); + expect_truthy(!isMatch('abcd', 'd')); + expect_truthy(!isMatch('abcd', 'f')); + expect_truthy(!isMatch('ef', '/*')); + }); + + test('should match a path segment for each single star', () => { + expect_truthy(!isMatch('aaa', '*/*/*')); + expect_truthy(!isMatch('aaa/bb/aa/rr', '*/*/*')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa*')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa**')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/*')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/*ccc')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/*z')); + expect_truthy(!isMatch('aaa/bbb', '*/*/*')); + expect_truthy(!isMatch('ab/zzz/ejkl/hi', '*/*jk*/*i')); + expect_truthy(isMatch('aaa/bba/ccc', '*/*/*')); + expect_truthy(isMatch('aaa/bba/ccc', 'aaa/**')); + expect_truthy(isMatch('aaa/bbb', 'aaa/*')); + expect_truthy(isMatch('ab/zzz/ejkl/hi', '*/*z*/*/*i')); + expect_truthy(isMatch('abzzzejklhi', '*j*i')); + }); + + test('should regard non-exclusive double-stars as single stars', () => { + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/**ccc')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/**z')); + }); + + test('should return false when full file paths are not matched', () => { + expect_truthy(!isMatch('a/b/z/.a', 'a/**/z/*.a')); + expect_truthy(!isMatch('a/b/z/.a', 'a/*/z/*.a')); + expect_truthy(!isMatch('a/.b', 'a/**/z/*.md')); + expect_truthy(!isMatch('a/b/c/j/e/z/c.txt', 'a/**/j/**/z/*.md')); + expect_truthy(!isMatch('a/b/c/xyz.md', 'a/b/**/c{d,e}/**/xyz.md')); + expect_truthy(!isMatch('a/b/d/xyz.md', 'a/b/**/c{d,e}/**/xyz.md')); + expect_truthy(!isMatch('a/b/z/.a', 'b/a')); + }); + }); + + describe('stars (multiple patterns)', () => { + test('should return true when any of the patterns match', () => { + expect_truthy(isMatch('.', ['.', 'foo'])); + expect_truthy(isMatch('a', ['a', 'foo'])); + expect_truthy(isMatch('ab', ['*', 'foo', 'bar'])); + expect_truthy(isMatch('ab', ['*b', 'foo', 'bar'])); + expect_truthy(isMatch('ab', ['./*', 'foo', 'bar'])); + expect_truthy(isMatch('ab', ['a*', 'foo', 'bar'])); + expect_truthy(isMatch('ab', ['ab', 'foo'])); + }); + + test('should return false when none of the patterns match', () => { + expect_truthy(!isMatch('/ab', ['/a', 'foo'])); + expect_truthy(!isMatch('a/b/c', ['a/b', 'foo'])); + expect_truthy(!isMatch('ab', ['*/*', 'foo', 'bar'])); + expect_truthy(!isMatch('ab', ['/a', 'foo', 'bar'])); + expect_truthy(!isMatch('ab', ['a', 'foo'])); + expect_truthy(!isMatch('ab', ['b', 'foo'])); + expect_truthy(!isMatch('ab', ['c', 'foo', 'bar'])); + expect_truthy(!isMatch('abcd', ['ab', 'foo'])); + expect_truthy(!isMatch('abcd', ['bc', 'foo'])); + expect_truthy(!isMatch('abcd', ['c', 'foo'])); + expect_truthy(!isMatch('abcd', ['cd', 'foo'])); + expect_truthy(!isMatch('abcd', ['d', 'foo'])); + expect_truthy(!isMatch('abcd', ['f', 'foo', 'bar'])); + expect_truthy(!isMatch('ef', ['/*', 'foo', 'bar'])); + }); + }); + + describe('file extensions', () => { + test('should match files that contain the given extension', () => { + expect_truthy(isMatch('.c.md', '.*.md')); + expect_truthy(isMatch('a/b/c.md', '**/*.md')); + expect_truthy(isMatch('a/b/c.md', 'a/*/*.md')); + expect_truthy(isMatch('c.md', '*.md')); + }); + + test('should not match files that do not contain the given extension', () => { + expect_truthy(!isMatch('.c.md', '*.md')); + expect_truthy(!isMatch('.c.md', '.c.')); + expect_truthy(!isMatch('.c.md', '.md')); + expect_truthy(!isMatch('.md', '*.md')); + expect_truthy(!isMatch('.md', '.m')); + expect_truthy(!isMatch('a/b/c.md', '*.md')); + expect_truthy(!isMatch('a/b/c.md', '.md')); + expect_truthy(!isMatch('a/b/c.md', 'a/*.md')); + expect_truthy(!isMatch('a/b/c/c.md', '*.md')); + expect_truthy(!isMatch('a/b/c/c.md', 'c.js')); + }); + }); + + describe('dot files', () => { + test('should match dotfiles when a dot is explicitly defined in the pattern', () => { + expect_truthy(isMatch('.a', '.a')); + expect_truthy(isMatch('.ab', '.*')); + expect_truthy(isMatch('.ab', '.a*')); + expect_truthy(isMatch('.b', '.b*')); + expect_truthy(isMatch('.md', '.md')); + expect_truthy(isMatch('a/.c.md', 'a/.c.md')); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/.*.md')); + expect_truthy(isMatch('a/b/c/d.a.md', 'a/b/c/*.md')); + }); + + test('should not match dotfiles when a dot is not defined in the pattern', () => { + expect_truthy(!isMatch('.abc', '.a')); + expect_truthy(!isMatch('.c.md', '*.md')); + expect_truthy(!isMatch('a/.c.md', '*.md')); + }); + + test('should match dotfiles when `dot` is set', () => { + expect_truthy(!isMatch('a/b/c/.xyz.md', '.*.md', { dot: true })); + expect_truthy(isMatch('.c.md', '*.md', { dot: true })); + expect_truthy(isMatch('.c.md', '.*', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', '**/*.md', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', '**/.*.md', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/*.md', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/.*.md', { dot: true })); + }); + + test('should not match dotfiles when `dot` is not set', () => { + expect_truthy(!isMatch('.a', '*.md')); + expect_truthy(!isMatch('.ba', '.a')); + expect_truthy(!isMatch('.a.md', 'a/b/c/*.md')); + expect_truthy(!isMatch('.ab', '*.*')); + expect_truthy(!isMatch('.md', 'a/b/c/*.md')); + expect_truthy(!isMatch('.txt', '.md')); + expect_truthy(!isMatch('.verb.txt', '*.md')); + expect_truthy(!isMatch('a/b/d/.md', 'a/b/c/*.md')); + }); + }); + + describe('dot-slash', () => { + test('should match paths with leading `./`', () => { + let format = str => str.replace(/^\.\//, ''); + + expect_truthy(isMatch('./a', ['a', '?(./)*'], { format })); + expect_truthy(isMatch('a', ['a', '?(./)*'], { format })); + expect_truthy(isMatch('a', ['?(./)*'], { format })); + expect_truthy(!isMatch('./.a', 'a/**/z/*.md', { format })); + expect_truthy(!isMatch('./a/b/c/d/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(!isMatch('./a/b/c/j/e/z/c.txt', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/j/n/p/o/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', './a/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', '?(./)a/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/j/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/j/e/z/c.md', '?(./)a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/z/.a', './a/**/z/.a', { format })); + expect_truthy(isMatch('./a/b/z/.a', '?(./)a/**/z/.a', { format })); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.makeRe.test.ts b/packages/node-utils/test/micromatch/api.makeRe.test.ts new file mode 100644 index 0000000..755365b --- /dev/null +++ b/packages/node-utils/test/micromatch/api.makeRe.test.ts @@ -0,0 +1,48 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; + +describe('.makeRe()', () => { + test('should throw an error when value is not a string', () => { + expect_throws(() => mm.makeRe()); + }); + + test('should create a regex for a glob pattern', () => { + expect_truthy(mm.makeRe('*') instanceof RegExp); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.match.test.ts b/packages/node-utils/test/micromatch/api.match.test.ts new file mode 100644 index 0000000..699a1f5 --- /dev/null +++ b/packages/node-utils/test/micromatch/api.match.test.ts @@ -0,0 +1,130 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; +const sep = path.sep; + +describe('.match()', () => { + afterEach(() => ((path as any).sep = sep)); + after(() => ((path as any).sep = sep)); + + describe('posix paths', () => { + test('should return an array of matches for a literal string', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_deepEqual(mm(fixtures, '(a/b)'), ['a/b']); + expect_deepEqual(mm(fixtures, 'a/b'), ['a/b']); + }); + + test('should support regex logical or', () => { + let fixtures = ['a/a', 'a/b', 'a/c']; + expect_deepEqual(mm(fixtures, 'a/(a|c)'), ['a/a', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/(a|b|c)'), ['a/a', 'a/b', 'a/c']); + }); + + test('should support regex ranges', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'a/x/y', 'a/x']; + expect_deepEqual(mm(fixtures, 'a/[b-c]'), ['a/b', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/[a-z]'), ['a/a', 'a/b', 'a/c', 'a/x']); + }); + + test('should support negation patterns', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_deepEqual(mm(fixtures, '!*/*'), []); + expect_deepEqual(mm(fixtures, '!*/b'), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/*'), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/b'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/(b)'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/(*)'), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!(*/b)'), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, '!(a/b)'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + }); + }); + + describe('windows paths', () => { + beforeEach(() => { + (path as any).sep = '\\'; + }); + + afterEach(() => { + (path as any).sep = sep; + }); + + test('should return an array of matches for a literal string', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']; + expect_deepEqual(mm(fixtures, '(a/b)', { windows: false }), []); + expect_deepEqual(mm(fixtures, '(a/b)'), ['a/b']); + expect_deepEqual(mm(fixtures, 'a/b', { windows: false }), []); + expect_deepEqual(mm(fixtures, 'a/b'), ['a/b']); + }); + + test('should support regex logical or', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c']; + expect_deepEqual(mm(fixtures, 'a/(a|c)', { windows: false }), []); + expect_deepEqual(mm(fixtures, 'a\\\\(a|c)', { windows: false }), ['a\\a', 'a\\c']); + expect_deepEqual(mm(fixtures, 'a/(a|c)'), ['a/a', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/(a|b|c)', { windows: false }), []); + expect_deepEqual(mm(fixtures, 'a/(a|b|c)'), ['a/a', 'a/b', 'a/c']); + }); + + test('should support regex ranges', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'a\\x\\y', 'a\\x']; + expect_deepEqual(mm(fixtures, 'a/[b-c]', { windows: false }), []); + expect_deepEqual(mm(fixtures, 'a/[b-c]'), ['a/b', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/[a-z]', { windows: false }), []); + expect_deepEqual(mm(fixtures, 'a/[a-z]'), ['a/a', 'a/b', 'a/c', 'a/x']); + }); + + test('should support negation patterns', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']; + expect_deepEqual(mm(fixtures, '!*/*'), []); + expect_deepEqual(mm(fixtures, '!*/b', { windows: false }), ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + expect_deepEqual(mm(fixtures, '!*/b'), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/*', { windows: false }), ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + expect_deepEqual(mm(fixtures, '!a/*'), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/b', { windows: false }), ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + expect_deepEqual(mm(fixtures, '!a/b'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/(b)', { windows: false }), ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + expect_deepEqual(mm(fixtures, '!a/(b)'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/(*)', { windows: false }), ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + expect_deepEqual(mm(fixtures, '!a/(*)'), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!(*/b)', { windows: false }), ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + expect_deepEqual(mm(fixtures, '!(*/b)'), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, '!(a/b)', { windows: false }), ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + expect_deepEqual(mm(fixtures, '!(a/b)'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.matchKeys.test.ts b/packages/node-utils/test/micromatch/api.matchKeys.test.ts new file mode 100644 index 0000000..6894e46 --- /dev/null +++ b/packages/node-utils/test/micromatch/api.matchKeys.test.ts @@ -0,0 +1,58 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; + +describe('.matchKeys()', () => { + describe('error handling', () => { + test('should throw when the first argument is not an object', () => { + expect_throws(() => mm.matchKeys(), /Expected the first argument to be an object/); + expect_throws(() => mm.matchKeys('foo'), /Expected the first argument to be an object/); + expect_throws(() => mm.matchKeys(['foo']), /Expected the first argument to be an object/); + }); + }); + + describe('match object keys', () => { + test('should return a new object with only keys that match the given glob pattern', () => { + expect_deepEqual(mm.matchKeys({ a: 'a', b: 'b', c: 'c' }, '*'), { a: 'a', b: 'b', c: 'c' }); + expect_deepEqual(mm.matchKeys({ a: 'a', b: 'b', c: 'c' }, 'a'), { a: 'a' }); + expect_deepEqual(mm.matchKeys({ a: 'a', b: 'b', c: 'c' }, '[a-b]'), { a: 'a', b: 'b' }); + expect_deepEqual(mm.matchKeys({ a: 'a', b: 'b', c: 'c' }, '(a|c)'), { a: 'a', c: 'c' }); + expect_notDeepEqual(mm.matchKeys({ a: 'a', b: 'b', c: 'c' }, 'a'), { b: 'b' }); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.matcher.test.ts b/packages/node-utils/test/micromatch/api.matcher.test.ts new file mode 100644 index 0000000..d0b9c19 --- /dev/null +++ b/packages/node-utils/test/micromatch/api.matcher.test.ts @@ -0,0 +1,203 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; +const sep = path.sep; + +describe('.matcher()', () => { + afterEach(() => ((path as any).sep = sep)); + after(() => ((path as any).sep = sep)); + + describe('errors', () => { + test('should throw an error when arguments are invalid', () => { + expect_throws(() => mm.matcher({})); + expect_throws(() => mm.matcher(null)); + expect_throws(() => mm.matcher()); + }); + }); + + describe('posix paths', () => { + test('should return an array of matches for a literal string', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_deepEqual(mm(fixtures, '(a/b)'), ['a/b']); + expect_deepEqual(mm(fixtures, 'a/b'), ['a/b']); + }); + + test('should support regex logical or', () => { + let fixtures = ['a/a', 'a/b', 'a/c']; + expect_deepEqual(mm(fixtures, 'a/(a|c)'), ['a/a', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/(a|b|c)'), ['a/a', 'a/b', 'a/c']); + }); + + test('should support regex ranges', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'a/x/y', 'a/x']; + expect_deepEqual(mm(fixtures, 'a/[b-c]'), ['a/b', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/[a-z]'), ['a/a', 'a/b', 'a/c', 'a/x']); + }); + + test('should support negation patterns', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_deepEqual(mm(fixtures, '!*/*'), []); + expect_deepEqual(mm(fixtures, '!*/b'), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/*'), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/b'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/(b)'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/(*)'), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!(*/b)'), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, '!(a/b)'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + }); + }); + + describe('posix paths (array of patterns)', () => { + test('should return an array of matches for a literal string', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_deepEqual(mm(fixtures, ['(a/b)']), ['a/b']); + expect_deepEqual(mm(fixtures, ['a/b']), ['a/b']); + }); + + test('should support regex logical or', () => { + let fixtures = ['a/a', 'a/b', 'a/c']; + expect_deepEqual(mm(fixtures, ['a/(a|c)']), ['a/a', 'a/c']); + expect_deepEqual(mm(fixtures, ['a/(a|b|c)']), ['a/a', 'a/b', 'a/c']); + }); + + test('should support regex ranges', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'a/x/y', 'a/x']; + expect_deepEqual(mm(fixtures, ['a/[b-c]']), ['a/b', 'a/c']); + expect_deepEqual(mm(fixtures, ['a/[a-z]']), ['a/a', 'a/b', 'a/c', 'a/x']); + }); + + test('should support negation patterns', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_deepEqual(mm(fixtures, ['!*/*']), []); + expect_deepEqual(mm(fixtures, ['!*/*']), []); + expect_deepEqual(mm(fixtures, ['!*/b']), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, ['!a/*']), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['!a/b']), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['!a/(b)']), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['!a/(*)']), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['!(*/b)']), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, ['!(a/b)']), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + }); + }); + + describe('backlashes for path separators, on posix', () => { + if (process.platform === 'win32') return; + let format = str => str; + + test('should return an array of matches for a literal string', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']; + expect_deepEqual(mm(fixtures, '(a/b)', { format }), []); + expect_deepEqual(mm(fixtures, 'a/b', { format }), []); + }); + + test('should support regex logical or', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c']; + expect_deepEqual(mm(fixtures, 'a/(a|c)', { format }), []); + expect_deepEqual(mm(fixtures, 'a/(a|b|c)', { format }), []); + }); + + test('should support regex ranges', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'a\\x\\y', 'a\\x']; + expect_deepEqual(mm(fixtures, 'a/[b-c]', { format }), []); + expect_deepEqual(mm(fixtures, 'a/[a-z]', { format }), []); + }); + + test('should support negation patterns', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']; + expect_deepEqual(mm(fixtures, '!*/*', { format }), fixtures); + expect_deepEqual(mm(fixtures, '!*/b', { format }), fixtures); + expect_deepEqual(mm(fixtures, '!a/*', { format }), fixtures); + expect_deepEqual(mm(fixtures, '!a/b', { format }), fixtures); + expect_deepEqual(mm(fixtures, '!a/(b)', { format }), fixtures); + expect_deepEqual(mm(fixtures, '!a/(*)', { format }), fixtures); + expect_deepEqual(mm(fixtures, '!(*/b)', { format }), fixtures); + expect_deepEqual(mm(fixtures, '!(a/b)', { format }), fixtures); + + expect_deepEqual(mm(fixtures, '!*/*', { windows: true }), []); + expect_deepEqual(mm(fixtures, ['!*/b'], { windows: true }), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, ['!a/*'], { windows: true }), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['!a/b'], { windows: true }), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['!a/(b)'], { windows: true }), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['!a/(*)'], { windows: true }), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['!(*/b)'], { windows: true }), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, ['!(a/b)'], { windows: true }), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + }); + }); + + describe('windows paths', () => { + beforeEach(() => { + (path as any).sep = '\\'; + }); + + afterEach(() => { + (path as any).sep = sep; + }); + + test('should return an array of matches for a literal string', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']; + expect_deepEqual(mm(fixtures, '(a/b)'), ['a/b']); + expect_deepEqual(mm(fixtures, '(a/b)', { windows: false }), []); + expect_deepEqual(mm(fixtures, 'a/b'), ['a/b']); + expect_deepEqual(mm(fixtures, 'a/b', { windows: false }), []); + }); + + test('should support regex logical or', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c']; + expect_deepEqual(mm(fixtures, 'a/(a|c)'), ['a/a', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/(a|b|c)'), ['a/a', 'a/b', 'a/c']); + }); + + test('should support regex ranges', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'a\\x\\y', 'a\\x']; + expect_deepEqual(mm(fixtures, 'a/[b-c]'), ['a/b', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/[a-z]'), ['a/a', 'a/b', 'a/c', 'a/x']); + }); + + test('should support negation patterns', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']; + expect_deepEqual(mm(fixtures, '!*/*'), []); + expect_deepEqual(mm(fixtures, '!*/b'), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/*'), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/b'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/(b)'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!a/(*)'), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, '!(*/b)'), ['a/a', 'a/c', 'b/a', 'b/c']); + expect_deepEqual(mm(fixtures, '!(a/b)'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.not.test.ts b/packages/node-utils/test/micromatch/api.not.test.ts new file mode 100644 index 0000000..c41487e --- /dev/null +++ b/packages/node-utils/test/micromatch/api.not.test.ts @@ -0,0 +1,147 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const sep = path.sep; +const { not } = micromatch; + +describe('.not()', () => { + beforeEach(() => { + (path as any).sep = '\\'; + }); + afterEach(() => { + (path as any).sep = sep; + }); + + describe('posix paths', () => { + test('should return an array of matches for a literal string', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_deepEqual(not(fixtures, '(a/b)'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(not(fixtures, 'a/b'), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + }); + + test('should support regex logical or', () => { + let fixtures = ['a/a', 'a/b', 'a/c']; + expect_deepEqual(not(fixtures, 'a/(a|c)'), ['a/b']); + expect_deepEqual(not(fixtures, 'a/(a|b|c)'), []); + }); + + test('should support regex ranges', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'a/x/y', 'a/x']; + expect_deepEqual(not(fixtures, 'a/[b-c]'), ['a/a', 'a/x/y', 'a/x']); + expect_deepEqual(not(fixtures, 'a/[a-z]'), ['a/x/y']); + }); + + test('should support globs (*)', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'a/x', 'a/a/a', 'a/a/b', 'a/a/a/a', 'a/a/a/a/a']; + expect_deepEqual(not(fixtures, 'a/*'), ['a/a/a', 'a/a/b', 'a/a/a/a', 'a/a/a/a/a']); + expect_deepEqual(not(fixtures, 'a/*/a'), ['a/a', 'a/b', 'a/c', 'a/x', 'a/a/b', 'a/a/a/a', 'a/a/a/a/a']); + expect_deepEqual(not(fixtures, 'a/*/*'), ['a/a', 'a/b', 'a/c', 'a/x', 'a/a/a/a', 'a/a/a/a/a']); + expect_deepEqual(not(fixtures, 'a/*/*/*'), ['a/a', 'a/b', 'a/c', 'a/x', 'a/a/a', 'a/a/b', 'a/a/a/a/a']); + expect_deepEqual(not(fixtures, 'a/*/*/*/*'), ['a/a', 'a/b', 'a/c', 'a/x', 'a/a/a', 'a/a/b', 'a/a/a/a']); + }); + + test('should support globstars (**)', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'a/x', 'a/x/y', 'a/x/y/z']; + expect_deepEqual(not(fixtures, 'a/**'), []); + expect_deepEqual(not(fixtures, 'a/**/*'), []); + expect_deepEqual(not(fixtures, 'a/**/**/*'), []); + }); + + test('should support negation patterns', () => { + let fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_deepEqual(not(fixtures, '!a/b'), ['a/b']); + expect_deepEqual(not(fixtures, '!a/(b)'), ['a/b']); + expect_deepEqual(not(fixtures, '!(a/b)'), ['a/b']); + }); + }); + + describe('windows paths', () => { + test('should return an array of matches for a literal string', () => { + let fixtures = ['a', 'a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']; + expect_deepEqual(not(fixtures, '(a/b)'), ['a', 'a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(not(fixtures, 'a/b'), ['a', 'a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + }); + + test('should support regex logical or', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c']; + expect_deepEqual(not(fixtures, 'a/(a|c)'), ['a/b']); + expect_deepEqual(not(fixtures, 'a/(a|b|c)'), []); + }); + + test('should support regex ranges', () => { + let format = str => str.replace(/\\/g, '/').replace(/^\.\//, ''); + let fixtures = ['.\\a\\a', 'a\\a', 'a\\b', 'a\\c', 'a\\x', 'a\\x\\y']; + expect_deepEqual(not(fixtures, '[a-c]/[a-c]', { format }), ['a/x', 'a/x/y']); + expect_deepEqual(not(fixtures, 'a/[b-c]', { format }), ['a/a', 'a/x', 'a/x/y']); + expect_deepEqual(not(fixtures, 'a/[a-z]', { format }), ['a/x/y']); + }); + + test('should support globs (*)', () => { + let format = str => str.replace(/\\/g, '/').replace(/^\.\//, ''); + let fixtures = ['a\\a', 'a/a', 'a\\b', '.\\a\\b', 'a\\c', 'a\\x', 'a\\a\\a', 'a\\a\\b', 'a\\a\\a\\a', 'a\\a\\a\\a\\a']; + expect_deepEqual(not(fixtures, 'a/*', { format }), ['a/a/a', 'a/a/b', 'a/a/a/a', 'a/a/a/a/a']); + expect_deepEqual(not(fixtures, 'a/*/a', { format }), ['a/a', 'a/b', 'a/c', 'a/x', 'a/a/b', 'a/a/a/a', 'a/a/a/a/a']); + expect_deepEqual(not(fixtures, 'a/*/*', { format }), ['a/a', 'a/b', 'a/c', 'a/x', 'a/a/a/a', 'a/a/a/a/a']); + expect_deepEqual(not(fixtures, 'a/*/*/*', { format }), ['a/a', 'a/b', 'a/c', 'a/x', 'a/a/a', 'a/a/b', 'a/a/a/a/a']); + expect_deepEqual(not(fixtures, 'a/*/*/*/*', { format }), ['a/a', 'a/b', 'a/c', 'a/x', 'a/a/a', 'a/a/b', 'a/a/a/a']); + }); + + test('should support globstars (**)', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'a\\x', 'a\\x\\y', 'a\\x\\y\\z']; + let expected = ['a/a', 'a/b', 'a/c', 'a/x', 'a/x/y', 'a/x/y/z']; + expect_deepEqual(not(fixtures, '*'), expected); + expect_deepEqual(not(fixtures, '**'), []); + expect_deepEqual(not(fixtures, '*/*'), ['a/x/y', 'a/x/y/z']); + expect_deepEqual(not(fixtures, 'a/**'), []); + expect_deepEqual(not(fixtures, 'a/x/**'), ['a/a', 'a/b', 'a/c']); + expect_deepEqual(not(fixtures, 'a/**/*'), []); + expect_deepEqual(not(fixtures, 'a/**/**/*'), []); + }); + + test('should support negation patterns', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']; + let expected = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_deepEqual(not(fixtures, '!**'), expected); + expect_deepEqual(not(fixtures, '!*/*'), expected); + expect_deepEqual(not(fixtures, '!*'), []); + expect_deepEqual(not(fixtures, '!a/b'), ['a/b']); + expect_deepEqual(not(fixtures, '!a/(b)'), ['a/b']); + expect_deepEqual(not(fixtures, '!(a/b)'), ['a/b']); + }); + }); +}); + diff --git a/packages/node-utils/test/micromatch/api.parse.test.ts b/packages/node-utils/test/micromatch/api.parse.test.ts new file mode 100644 index 0000000..f6c33b8 --- /dev/null +++ b/packages/node-utils/test/micromatch/api.parse.test.ts @@ -0,0 +1,59 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; + +describe('.parse()', () => { + test('should parse a glob', function() { + if (process.platform === 'win32') return this.skip(); + + let results = mm.parse('a/*'); + let { tokens } = results[0]; + + tokens.forEach(token => { + delete token.prev; + }); + + expect_deepEqual(tokens, [ + { type: 'bos', value: '', output: '' }, + { type: 'text', value: 'a' }, + { type: 'slash', value: '/', output: '\\/(?!\\.)(?=.)' }, + { type: 'star', value: '*', output: '[^/]*?' }, + { type: 'maybe_slash', value: '', output: '\\/?' } + ]); + }); +}); diff --git a/packages/node-utils/test/micromatch/api.some.test.ts b/packages/node-utils/test/micromatch/api.some.test.ts new file mode 100644 index 0000000..9487212 --- /dev/null +++ b/packages/node-utils/test/micromatch/api.some.test.ts @@ -0,0 +1,54 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; + +describe('.some()', () => { + test('should return true if any matches are found', () => { + var fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_truthy(mm.some(fixtures, ['z', 'b/*'])); + }); + + test('should return false if no matches are found', () => { + var fixtures = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + expect_truthy(!mm.some(fixtures, ['z', 'x/*'])); + }); + + test('should arrayify a string value', () => { + expect_truthy(mm.some('a', ['*'])); + }); +}); diff --git a/packages/node-utils/test/micromatch/bash.spec.test.ts b/packages/node-utils/test/micromatch/bash.spec.test.ts new file mode 100644 index 0000000..a717613 --- /dev/null +++ b/packages/node-utils/test/micromatch/bash.spec.test.ts @@ -0,0 +1,493 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const util = require('util'); +const { isMatch } = micromatch; +let units = []; + +describe('bash.spec', () => { + describe('dotglob', () => { + test('"a/b/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x', '**/.x/**', { bash: true })); + }); + + test('".x" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x', '**/.x/**', { bash: true })); + }); + + test('".x/" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/', '**/.x/**', { bash: true })); + }); + + test('".x/a" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/a', '**/.x/**', { bash: true })); + }); + + test('".x/a/b" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/a/b', '**/.x/**', { bash: true })); + }); + + test('".x/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/.x', '**/.x/**', { bash: true })); + }); + + test('"a/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/.x', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c/d" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c/d', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c/d/e" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c/d/e', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/', '**/.x/**', { bash: true })); + }); + + test('"a/.x/b" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/.x/b', '**/.x/**', { bash: true })); + }); + + test('"a/.x/b/.x/c" should not match "**/.x/**"', () => { + expect_truthy(!isMatch('a/.x/b/.x/c', '**/.x/**', { bash: true })); + }); + + test('".bashrc" should not match "?bashrc"', () => { + expect_truthy(!isMatch('.bashrc', '?bashrc', { bash: true })); + }); + + test('should match trailing slashes with stars', () => { + expect_truthy(isMatch('.bar.baz/', '.*.*', { bash: true })); + }); + + test('".bar.baz/" should match ".*.*/"', () => { + expect_truthy(isMatch('.bar.baz/', '.*.*/', { bash: true })); + }); + + test('".bar.baz" should match ".*.*"', () => { + expect_truthy(isMatch('.bar.baz', '.*.*', { bash: true })); + }); + }); + + describe('glob', () => { + test('"a/b/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x', '**/.x/**', { bash: true })); + }); + + test('".x" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x', '**/.x/**', { bash: true })); + }); + + test('".x/" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/', '**/.x/**', { bash: true })); + }); + + test('".x/a" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/a', '**/.x/**', { bash: true })); + }); + + test('".x/a/b" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/a/b', '**/.x/**', { bash: true })); + }); + + test('".x/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/.x', '**/.x/**', { bash: true })); + }); + + test('"a/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/.x', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c/d" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c/d', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c/d/e" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c/d/e', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/', '**/.x/**', { bash: true })); + }); + + test('"a/.x/b" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/.x/b', '**/.x/**', { bash: true })); + }); + + test('"a/.x/b/.x/c" should not match "**/.x/**"', () => { + expect_truthy(!isMatch('a/.x/b/.x/c', '**/.x/**', { bash: true })); + }); + + test('"a/c/b" should match "a/*/b"', () => { + expect_truthy(isMatch('a/c/b', 'a/*/b', { bash: true })); + }); + + test('"a/.d/b" should not match "a/*/b"', () => { + expect_truthy(!isMatch('a/.d/b', 'a/*/b', { bash: true })); + }); + + test('"a/./b" should not match "a/*/b"', () => { + expect_truthy(!isMatch('a/./b', 'a/*/b', { bash: true })); + }); + + test('"a/../b" should not match "a/*/b"', () => { + expect_truthy(!isMatch('a/../b', 'a/*/b', { bash: true })); + }); + + test('"ab" should match "ab**"', () => { + expect_truthy(isMatch('ab', 'ab**', { bash: true })); + }); + + test('"abcdef" should match "ab**"', () => { + expect_truthy(isMatch('abcdef', 'ab**', { bash: true })); + }); + + test('"abef" should match "ab**"', () => { + expect_truthy(isMatch('abef', 'ab**', { bash: true })); + }); + + test('"abcfef" should match "ab**"', () => { + expect_truthy(isMatch('abcfef', 'ab**', { bash: true })); + }); + + test('"ab" should not match "ab***ef"', () => { + expect_truthy(!isMatch('ab', 'ab***ef', { bash: true })); + }); + + test('"abcdef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcdef', 'ab***ef', { bash: true })); + }); + + test('"abef" should match "ab***ef"', () => { + expect_truthy(isMatch('abef', 'ab***ef', { bash: true })); + }); + + test('"abcfef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcfef', 'ab***ef', { bash: true })); + }); + + test('".bashrc" should not match "?bashrc"', () => { + expect_truthy(!isMatch('.bashrc', '?bashrc', { bash: true })); + }); + + test('"abbc" should not match "ab?bc"', () => { + expect_truthy(!isMatch('abbc', 'ab?bc', { bash: true })); + }); + + test('"abc" should not match "ab?bc"', () => { + expect_truthy(!isMatch('abc', 'ab?bc', { bash: true })); + }); + + test('"a.a" should match "[a-d]*.[a-b]"', () => { + expect_truthy(isMatch('a.a', '[a-d]*.[a-b]', { bash: true })); + }); + + test('"a.b" should match "[a-d]*.[a-b]"', () => { + expect_truthy(isMatch('a.b', '[a-d]*.[a-b]', { bash: true })); + }); + + test('"c.a" should match "[a-d]*.[a-b]"', () => { + expect_truthy(isMatch('c.a', '[a-d]*.[a-b]', { bash: true })); + }); + + test('"a.a.a" should match "[a-d]*.[a-b]"', () => { + expect_truthy(isMatch('a.a.a', '[a-d]*.[a-b]', { bash: true })); + }); + + test('"a.a.a" should match "[a-d]*.[a-b]*.[a-b]"', () => { + expect_truthy(isMatch('a.a.a', '[a-d]*.[a-b]*.[a-b]', { bash: true })); + }); + + test('"a.a" should match "*.[a-b]"', () => { + expect_truthy(isMatch('a.a', '*.[a-b]', { bash: true })); + }); + + test('"a.b" should match "*.[a-b]"', () => { + expect_truthy(isMatch('a.b', '*.[a-b]', { bash: true })); + }); + + test('"a.a.a" should match "*.[a-b]"', () => { + expect_truthy(isMatch('a.a.a', '*.[a-b]', { bash: true })); + }); + + test('"c.a" should match "*.[a-b]"', () => { + expect_truthy(isMatch('c.a', '*.[a-b]', { bash: true })); + }); + + test('"d.a.d" should not match "*.[a-b]"', () => { + expect_truthy(!isMatch('d.a.d', '*.[a-b]', { bash: true })); + }); + + test('"a.bb" should not match "*.[a-b]"', () => { + expect_truthy(!isMatch('a.bb', '*.[a-b]', { bash: true })); + }); + + test('"a.ccc" should not match "*.[a-b]"', () => { + expect_truthy(!isMatch('a.ccc', '*.[a-b]', { bash: true })); + }); + + test('"c.ccc" should not match "*.[a-b]"', () => { + expect_truthy(!isMatch('c.ccc', '*.[a-b]', { bash: true })); + }); + + test('"a.a" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('a.a', '*.[a-b]*', { bash: true })); + }); + + test('"a.b" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('a.b', '*.[a-b]*', { bash: true })); + }); + + test('"a.a.a" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('a.a.a', '*.[a-b]*', { bash: true })); + }); + + test('"c.a" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('c.a', '*.[a-b]*', { bash: true })); + }); + + test('"d.a.d" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('d.a.d', '*.[a-b]*', { bash: true })); + }); + + test('"d.a.d" should not match "*.[a-b]*.[a-b]*"', () => { + expect_truthy(!isMatch('d.a.d', '*.[a-b]*.[a-b]*', { bash: true })); + }); + + test('"d.a.d" should match "*.[a-d]*.[a-d]*"', () => { + expect_truthy(isMatch('d.a.d', '*.[a-d]*.[a-d]*', { bash: true })); + }); + + test('"a.bb" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('a.bb', '*.[a-b]*', { bash: true })); + }); + + test('"a.ccc" should not match "*.[a-b]*"', () => { + expect_truthy(!isMatch('a.ccc', '*.[a-b]*', { bash: true })); + }); + + test('"c.ccc" should not match "*.[a-b]*"', () => { + expect_truthy(!isMatch('c.ccc', '*.[a-b]*', { bash: true })); + }); + + test('"a.a" should match "*[a-b].[a-b]*"', () => { + expect_truthy(isMatch('a.a', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"a.b" should match "*[a-b].[a-b]*"', () => { + expect_truthy(isMatch('a.b', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"a.a.a" should match "*[a-b].[a-b]*"', () => { + expect_truthy(isMatch('a.a.a', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"c.a" should not match "*[a-b].[a-b]*"', () => { + expect_truthy(!isMatch('c.a', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"d.a.d" should not match "*[a-b].[a-b]*"', () => { + expect_truthy(!isMatch('d.a.d', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"a.bb" should match "*[a-b].[a-b]*"', () => { + expect_truthy(isMatch('a.bb', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"a.ccc" should not match "*[a-b].[a-b]*"', () => { + expect_truthy(!isMatch('a.ccc', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"c.ccc" should not match "*[a-b].[a-b]*"', () => { + expect_truthy(!isMatch('c.ccc', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"abd" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('abd', '[a-y]*[^c]', { bash: true })); + }); + + test('"abe" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('abe', '[a-y]*[^c]', { bash: true })); + }); + + test('"bb" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('bb', '[a-y]*[^c]', { bash: true })); + }); + + test('"bcd" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('bcd', '[a-y]*[^c]', { bash: true })); + }); + + test('"ca" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('ca', '[a-y]*[^c]', { bash: true })); + }); + + test('"cb" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('cb', '[a-y]*[^c]', { bash: true })); + }); + + test('"dd" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('dd', '[a-y]*[^c]', { bash: true })); + }); + + test('"de" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('de', '[a-y]*[^c]', { bash: true })); + }); + + test('"bdir/" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('bdir/', '[a-y]*[^c]', { bash: true })); + }); + + test('"abd" should match "**/*"', () => { + expect_truthy(isMatch('abd', '**/*', { bash: true })); + }); + }); + + + describe('globstar', () => { + test('"a.js" should match "**/*.js"', () => { + expect_truthy(isMatch('a.js', '**/*.js', { bash: true })); + }); + + test('"a/a.js" should match "**/*.js"', () => { + expect_truthy(isMatch('a/a.js', '**/*.js', { bash: true })); + }); + + test('"a/a/b.js" should match "**/*.js"', () => { + expect_truthy(isMatch('a/a/b.js', '**/*.js', { bash: true })); + }); + + test('"a/b/z.js" should match "a/b/**/*.js"', () => { + expect_truthy(isMatch('a/b/z.js', 'a/b/**/*.js', { bash: true })); + }); + + test('"a/b/c/z.js" should match "a/b/**/*.js"', () => { + expect_truthy(isMatch('a/b/c/z.js', 'a/b/**/*.js', { bash: true })); + }); + + test('"foo.md" should match "**/*.md"', () => { + expect_truthy(isMatch('foo.md', '**/*.md', { bash: true })); + }); + + test('"foo/bar.md" should match "**/*.md"', () => { + expect_truthy(isMatch('foo/bar.md', '**/*.md', { bash: true })); + }); + + test('"foo/bar" should match "foo/**/bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo/**/bar', { bash: true })); + }); + + test('"foo/bar" should match "foo/**bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo/**bar', { bash: true })); + }); + + test('"ab/a/d" should match "**/*"', () => { + expect_truthy(isMatch('ab/a/d', '**/*', { bash: true })); + }); + + test('"ab/b" should match "**/*"', () => { + expect_truthy(isMatch('ab/b', '**/*', { bash: true })); + }); + + test('"a/b/c/d/a.js" should match "**/*"', () => { + expect_truthy(isMatch('a/b/c/d/a.js', '**/*', { bash: true })); + }); + + test('"a/b/c.js" should match "**/*"', () => { + expect_truthy(isMatch('a/b/c.js', '**/*', { bash: true })); + }); + + test('"a/b/c.txt" should match "**/*"', () => { + expect_truthy(isMatch('a/b/c.txt', '**/*', { bash: true })); + }); + + test('"a/b/.js/c.txt" should match "**/*"', () => { + expect_truthy(isMatch('a/b/.js/c.txt', '**/*', { bash: true })); + }); + + test('"a.js" should match "**/*"', () => { + expect_truthy(isMatch('a.js', '**/*', { bash: true })); + }); + + test('"za.js" should match "**/*"', () => { + expect_truthy(isMatch('za.js', '**/*', { bash: true })); + }); + + test('"ab" should match "**/*"', () => { + expect_truthy(isMatch('ab', '**/*', { bash: true })); + }); + + test('"a.b" should match "**/*"', () => { + expect_truthy(isMatch('a.b', '**/*', { bash: true })); + }); + + test('"foo/" should match "foo/**/"', () => { + expect_truthy(isMatch('foo/', 'foo/**/', { bash: true })); + }); + + test('"foo/bar" should not match "foo/**/"', () => { + expect_truthy(!isMatch('foo/bar', 'foo/**/', { bash: true })); + }); + + test('"foo/bazbar" should not match "foo/**/"', () => { + expect_truthy(!isMatch('foo/bazbar', 'foo/**/', { bash: true })); + }); + + test('"foo/barbar" should not match "foo/**/"', () => { + expect_truthy(!isMatch('foo/barbar', 'foo/**/', { bash: true })); + }); + + test('"foo/bar/baz/qux" should not match "foo/**/"', () => { + expect_truthy(!isMatch('foo/bar/baz/qux', 'foo/**/', { bash: true })); + }); + + test('"foo/bar/baz/qux/" should match "foo/**/"', () => { + expect_truthy(isMatch('foo/bar/baz/qux/', 'foo/**/', { bash: true })); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/bash.test.ts b/packages/node-utils/test/micromatch/bash.test.ts new file mode 100644 index 0000000..93f1feb --- /dev/null +++ b/packages/node-utils/test/micromatch/bash.test.ts @@ -0,0 +1,254 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; + +const isWindows = () => process.platform === 'win32' || (path as any).sep === '\\'; +const format = str => str.replace(/\\/g, '/').replace(/^\.\//, ''); + +// from the Bash 4.3 specification/unit tests +const fixtures = ['\\\\', '*', '**', '\\*', 'a', 'a/*', 'abc', 'abd', 'abe', 'b', 'bb', 'bcd', 'bdir/', 'Beware', 'c', 'ca', 'cb', 'd', 'dd', 'de']; + +describe('bash options and features:', () => { + // $echo a/{1..3}/b + describe('bash', () => { + test('should handle "regular globbing":', () => { + expect_deepEqual(mm(fixtures, 'a*'), ['a', 'abc', 'abd', 'abe']); + expect_deepEqual(mm(fixtures, '\\a*'), ['a', 'abc', 'abd', 'abe']); + }); + + test('should match directories:', () => { + expect_deepEqual(mm(fixtures, 'b*/'), ['bdir/']); + }); + + test('should use quoted characters as literals:', () => { + expect_deepEqual(mm(fixtures, '\\*', { windows: false }), ['*', '\\*']); + expect_deepEqual(mm(fixtures, '\\^', { windows: false }), []); + expect_deepEqual(mm(fixtures, 'a\\*', { windows: false }), []); + expect_deepEqual(mm(fixtures, ['a\\*', '\\*'], { windows: false }), ['*', '\\*']); + expect_deepEqual(mm(fixtures, ['a\\*'], { windows: false }), []); + expect_deepEqual(mm(fixtures, ['c*', 'a\\*', '*q*'], { windows: false }), ['c', 'ca', 'cb']); + }); + + test('should support quoted characters', () => { + expect_deepEqual(mm(['***'], '"***"'), ['***']); + expect_deepEqual(mm(['"***"'], '"***"'), ['"***"']); + expect_deepEqual(mm(['*', '**', '*foo', 'bar'], '"*"*'), ['*', '**', '*foo']); + }); + + test('should respect escaped characters', () => { + expect_deepEqual(mm(fixtures, '\\**', { windows: false }), ['*', '**']); + }); + + test('should respect escaped paths/dots:', () => { + let format = str => str.replace(/\\/g, ''); + expect_deepEqual(mm(['"\\.\\./*/"'], '"\\.\\./*/"', { windows: false }), ['"\\.\\./*/"']); + expect_deepEqual(mm(['"\\.\\./*/"'], '"\\.\\./*/"', { format, windows: false }), ['"../*/"']); + expect_deepEqual(mm(['s/\\..*//'], 's/\\..*//', { windows: false }), ['s/\\..*//']); + }); + + test("Pattern from Larry Wall's Configure that caused bash to blow up:", () => { + expect_deepEqual(mm(['"/^root:/{s/^[^:]*:[^:]*:\\([^:]*\\).*"\'$\'"/\\1/"'], '"/^root:/{s/^[^:]*:[^:]*:\\([^:]*\\).*"\'$\'"/\\1/"', { windows: false }), ['"/^root:/{s/^[^:]*:[^:]*:\\([^:]*\\).*"\'$\'"/\\1/"']); + expect_deepEqual(mm(fixtures, '[a-c]b*'), ['abc', 'abd', 'abe', 'bb', 'cb']); + }); + + test('should support character classes', () => { + let f = fixtures.slice(); + f.push('baz', 'bzz', 'BZZ', 'beware', 'BewAre'); + f.sort(); + + expect_deepEqual(mm(f, 'a*[^c]'), ['abd', 'abe']); + expect_deepEqual(mm(['a-b', 'aXb'], 'a[X-]b'), ['a-b', 'aXb']); + expect_deepEqual(mm(f, '[a-y]*[^c]'), ['abd', 'abe', 'baz', 'bb', 'bcd', 'bdir/', 'beware', 'bzz', 'ca', 'cb', 'dd', 'de']); + expect_deepEqual(mm(['a*b/ooo'], 'a\\*b/*'), ['a*b/ooo']); + expect_deepEqual(mm(['a*b/ooo'], 'a\\*?/*'), ['a*b/ooo']); + expect_deepEqual(mm(f, 'a[b]c'), ['abc']); + expect_deepEqual(mm(f, 'a["b"]c'), ['abc']); + expect_deepEqual(mm(f, 'a[\\\\b]c'), ['abc']); //<= backslash and a "b" + expect_deepEqual(mm(f, 'a[\\b]c'), []); //<= word boundary in a character class + expect_deepEqual(mm(f, 'a[b-d]c'), ['abc']); + expect_deepEqual(mm(f, 'a?c'), ['abc']); + expect_deepEqual(mm(['a-b'], 'a[]-]b'), ['a-b']); + expect_deepEqual(mm(['man/man1/bash.1'], '*/man*/bash.*'), ['man/man1/bash.1']); + + if (isWindows()) { + // should not match backslashes on windows, since backslashes are path + // separators and negation character classes should not match path separators + // unless it's explicitly defined in the character class + expect_deepEqual(mm(f, '[^a-c]*'), ['d', 'dd', 'de', 'Beware', 'BewAre', 'BZZ', '*', '**', '\\*'].sort()); + expect_deepEqual(mm(f, '[^a-c]*', { bash: false }), ['d', 'dd', 'de', 'BewAre', 'Beware', 'BZZ', '*', '**', '\\*'].sort()); + expect_deepEqual(mm(f, '[^a-c]*', { nocase: true }), ['d', 'dd', 'de', '*', '**', '\\*'].sort()); + } else { + expect_deepEqual(mm(f, '[^a-c]*'), ['*', '**', 'BZZ', 'BewAre', 'Beware', '\\*', 'd', 'dd', 'de', '\\\\'].sort()); + expect_deepEqual(mm(f, '[^a-c]*', { bash: false }), ['*', '**', 'BZZ', 'BewAre', 'Beware', '\\*', 'd', 'dd', 'de', '\\\\'].sort()); + expect_deepEqual(mm(f, '[^a-c]*', { nocase: true }), ['*', '**', '\\*', 'd', 'dd', 'de', '\\\\'].sort()); + } + }); + + test('should support basic wildmatch (brackets) features', () => { + expect_truthy(!mm.isMatch('aab', 'a[]-]b')); + expect_truthy(!mm.isMatch('ten', '[ten]')); + expect_truthy(!mm.isMatch('ten', 't[!a-g]n', { posix: true })); + expect_truthy(mm.isMatch(']', ']')); + expect_truthy(mm.isMatch('a-b', 'a[]-]b')); + expect_truthy(mm.isMatch('a]b', 'a[]-]b')); + expect_truthy(mm.isMatch('a]b', 'a[]]b')); + expect_truthy(mm.isMatch('aab', 'a[\\]a\\-]b')); + expect_truthy(mm.isMatch('ten', 't[a-g]n')); + expect_truthy(mm.isMatch('ton', 't[!a-g]n', { posix: true })); + expect_truthy(mm.isMatch('ton', 't[^a-g]n')); + }); + + test('should support extended slash-matching features', () => { + expect_truthy(!mm.isMatch('foo/bar', 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r')); + expect_truthy(mm.isMatch('foo/bar', 'foo[/]bar')); + expect_truthy(mm.isMatch('foo-bar', 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r')); + }); + + test('should match literal parens', () => { + expect_truthy(mm.isMatch('foo(bar)baz', 'foo[bar()]+baz')); + }); + + test('should match escaped characters', () => { + expect_truthy(!mm.isMatch('', '\\')); + + if (isWindows()) { + expect_truthy(!mm.isMatch('XXX/\\', '[A-Z]+/\\')); + expect_truthy(!mm.isMatch('XXX/\\', '[A-Z]+/\\\\')); + } else { + expect_truthy(mm.isMatch('XXX/\\', '[A-Z]+/\\')); + expect_truthy(mm.isMatch('XXX/\\', '[A-Z]+/\\\\')); + } + + expect_truthy(mm.isMatch('\\', '\\')); + expect_truthy(mm.isMatch('[ab]', '\\[ab]')); + expect_truthy(mm.isMatch('[ab]', '[\\[:]ab]')); + }); + + test('should match brackets', () => { + expect_truthy(!mm.isMatch(']', '[^]-]')); + expect_truthy(!mm.isMatch(']', '[!]-]')); + expect_truthy(mm.isMatch('a', '[^]-]')); + expect_truthy(mm.isMatch('a', '[!]-]', { posix: true })); + expect_truthy(mm.isMatch('[ab]', '[[]ab]')); + }); + + test('should regard multiple consecutive stars as a single star', () => { + expect_deepEqual(mm(['bbc', 'abc', 'bbd'], 'a**c'), ['abc']); + expect_deepEqual(mm(['bbc', 'abc', 'bbd'], 'a***c'), ['abc']); + expect_deepEqual(mm(['bbc', 'abc', 'bbc'], 'a*****?c'), ['abc']); + expect_deepEqual(mm(['bbc', 'abc'], '?*****??'), ['bbc', 'abc']); + expect_deepEqual(mm(['bbc', 'abc'], '*****??'), ['bbc', 'abc']); + expect_deepEqual(mm(['bbc', 'abc'], '?*****?c'), ['bbc', 'abc']); + expect_deepEqual(mm(['bbc', 'abc', 'bbd'], '?***?****c'), ['bbc', 'abc']); + expect_deepEqual(mm(['bbc', 'abc'], '?***?****?'), ['bbc', 'abc']); + expect_deepEqual(mm(['bbc', 'abc'], '?***?****'), ['bbc', 'abc']); + expect_deepEqual(mm(['bbc', 'abc'], '*******c'), ['bbc', 'abc']); + expect_deepEqual(mm(['bbc', 'abc'], '*******?'), ['bbc', 'abc']); + expect_deepEqual(mm(['abcdecdhjk'], 'a*cd**?**??k'), ['abcdecdhjk']); + expect_deepEqual(mm(['abcdecdhjk'], 'a**?**cd**?**??k'), ['abcdecdhjk']); + expect_deepEqual(mm(['abcdecdhjk'], 'a**?**cd**?**??k***'), ['abcdecdhjk']); + expect_deepEqual(mm(['abcdecdhjk'], 'a**?**cd**?**??***k'), ['abcdecdhjk']); + expect_deepEqual(mm(['abcdecdhjk'], 'a**?**cd**?**??***k**'), ['abcdecdhjk']); + expect_deepEqual(mm(['abcdecdhjk'], 'a****c**?**??*****'), ['abcdecdhjk']); + }); + + test('none of these should output anything:', () => { + expect_deepEqual(mm(['abc'], '??**********?****?'), []); + expect_deepEqual(mm(['abc'], '??**********?****c'), []); + expect_deepEqual(mm(['abc'], '?************c****?****'), []); + expect_deepEqual(mm(['abc'], '*c*?**'), []); + expect_deepEqual(mm(['abc'], 'a*****c*?**'), []); + expect_deepEqual(mm(['abc'], 'a********???*******'), []); + expect_deepEqual(mm(['a'], '[]'), []); + expect_deepEqual(mm(['['], '[abc'), []); + }); + }); + + describe('wildmat', () => { + test('Basic wildmat features', () => { + expect_truthy(!mm.isMatch('foo', '*f')); + expect_truthy(!mm.isMatch('foo', '??')); + expect_truthy(!mm.isMatch('foo', 'bar')); + expect_truthy(!mm.isMatch('foobar', 'foo\\*bar')); + expect_truthy(mm.isMatch('?a?b', '\\??\\?b')); + expect_truthy(mm.isMatch('aaaaaaabababab', '*ab')); + expect_truthy(mm.isMatch('f\\oo', 'f\\oo')); + expect_truthy(mm.isMatch('foo', '*')); + expect_truthy(mm.isMatch('foo', '*foo*')); + expect_truthy(mm.isMatch('foo', '???')); + expect_truthy(mm.isMatch('foo', 'f*')); + expect_truthy(mm.isMatch('foo', 'foo')); + expect_truthy(mm.isMatch('foo*', 'foo\\*', { toPosixSlashes: false })); + expect_truthy(mm.isMatch('foobar', '*ob*a*r*')); + }); + + test('should support recursion', () => { + expect_truthy(!mm.isMatch('-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*')); + expect_truthy(!mm.isMatch('-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*')); + expect_truthy(!mm.isMatch('ab/cXd/efXg/hi', '*X*i')); + expect_truthy(!mm.isMatch('ab/cXd/efXg/hi', '*Xg*i')); + expect_truthy(!mm.isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz', '**/*a*b*g*n*t')); + expect_truthy(!mm.isMatch('foo', '*/*/*')); + expect_truthy(!mm.isMatch('foo', 'fo')); + expect_truthy(!mm.isMatch('foo/bar', '*/*/*')); + expect_truthy(!mm.isMatch('foo/bar', 'foo?bar')); + expect_truthy(!mm.isMatch('foo/bb/aa/rr', '*/*/*')); + expect_truthy(!mm.isMatch('foo/bba/arr', 'foo*')); + expect_truthy(!mm.isMatch('foo/bba/arr', 'foo**')); + expect_truthy(!mm.isMatch('foo/bba/arr', 'foo/*')); + expect_truthy(!mm.isMatch('foo/bba/arr', 'foo/**arr')); + expect_truthy(!mm.isMatch('foo/bba/arr', 'foo/**z')); + expect_truthy(!mm.isMatch('foo/bba/arr', 'foo/*arr')); + expect_truthy(!mm.isMatch('foo/bba/arr', 'foo/*z')); + expect_truthy(!mm.isMatch('XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1', 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*')); + expect_truthy(mm.isMatch('-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*')); + expect_truthy(mm.isMatch('ab/cXd/efXg/hi', '**/*X*/**/*i')); + expect_truthy(mm.isMatch('ab/cXd/efXg/hi', '*/*X*/*/*i')); + expect_truthy(mm.isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt', '**/*a*b*g*n*t')); + expect_truthy(mm.isMatch('abcXdefXghi', '*X*i')); + expect_truthy(mm.isMatch('foo', 'foo')); + expect_truthy(mm.isMatch('foo/bar', 'foo/*')); + expect_truthy(mm.isMatch('foo/bar', 'foo/bar')); + expect_truthy(mm.isMatch('foo/bar', 'foo[/]bar')); + expect_truthy(mm.isMatch('foo/bb/aa/rr', '**/**/**')); + expect_truthy(mm.isMatch('foo/bba/arr', '*/*/*')); + expect_truthy(mm.isMatch('foo/bba/arr', 'foo/**')); + expect_truthy(mm.isMatch('XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1', 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*', { toPosixSlashes: false })); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/braces-compiled.test.ts b/packages/node-utils/test/micromatch/braces-compiled.test.ts new file mode 100644 index 0000000..9c16153 --- /dev/null +++ b/packages/node-utils/test/micromatch/braces-compiled.test.ts @@ -0,0 +1,388 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; + +const optimize = (pattern, options) => { + return mm.braces(pattern, Object.assign({ optimize: true }, options)); +}; + +describe('braces - compiled', () => { + describe('extglob characters', () => { + test('should expand braces (in extglobs) when preceded by an extglob character', () => { + let actual = mm.braces('abc/*!(-v@{1,2}.0).js'); + expect_deepEqual(actual, ['abc/*!(-v@(1|2).0).js']); + }); + + test('should expand braces when preceded by an extglob character', () => { + let actual = mm.braces('abc/*-v@{1,2}.0.js'); + expect_deepEqual(actual, ['abc/*-v@(1|2).0.js']); + }); + }); + + describe('sets', () => { + describe('invalid sets', () => { + test('should handle invalid sets:', () => { + optimize('{0..10,braces}', ['(0..10|braces)']); + optimize('{1..10,braces}', ['(1..10|braces)']); + }); + }); + + describe('escaping', () => { + test('should not expand escaped braces', () => { + optimize('\\{a,b,c,d,e}', ['{a,b,c,d,e}']); + optimize('a/b/c/{x,y\\}', ['a/b/c/{x,y}']); + optimize('a/\\{x,y}/cde', ['a/{x,y}/cde']); + optimize('abcd{efgh', ['abcd{efgh']); + optimize('{abc}', ['{abc}']); + optimize('{x,y,\\{a,b,c\\}}', ['(x|y|\\{a|b|c\\})']); + optimize('{x,y,{a,b,c\\}}', ['\\{x,y,(a|b|c\\})']); + optimize('{x,y,{abc},trie}', ['(x|y|\\{abc\\}|trie)']); + optimize('{x\\,y,\\{abc\\},trie}', ['(x,y|\\{abc\\}|trie)']); + }); + + test('should handle spaces', () => { + // Bash 4.3 says the following should be equivalent to `foo|(1|2)|bar`, + // That makes sense in Bash, since ' ' is a separator, but not here. + optimize('foo {1,2} bar', ['foo (1|2) bar']); + }); + + test('should handle empty braces', () => { + optimize('{ }', ['\\{ \\}']); + optimize('{', ['\\{']); + optimize('{}', ['\\{\\}']); + optimize('}', ['\\}']); + }); + + test('should escape braces when only one value is defined', () => { + optimize('a{b}c', ['a\\{b\\}c']); + optimize('a/b/c{d}e', ['a/b/c\\{d\\}e']); + }); + + test('should not expand braces in sets with es6/bash-like variables', () => { + optimize('abc/${ddd}/xyz', ['abc/\\$\\{ddd\\}/xyz']); + optimize('a${b}c', ['a\\$\\{b\\}c']); + optimize('a/{${b},c}/d', ['a/(\\$\\{b\\}|c)/d']); + optimize('a${b,d}/{foo,bar}c', ['a\\$\\{b,d\\}/(foo|bar)c']); + }); + + test('should not expand escaped commas.', () => { + optimize('a{b\\,c\\,d}e', ['a\\{b,c,d\\}e']); + optimize('a{b\\,c}d', ['a\\{b,c\\}d']); + optimize('{abc\\,def}', ['\\{abc,def\\}']); + optimize('{abc\\,def,ghi}', ['(abc,def|ghi)']); + optimize('a/{b,c}/{x\\,y}/d/e', ['a/(b|c)/\\{x,y\\}/d/e']); + }); + + test('should return sets with escaped commas', () => { + optimize('a/{b,c}/{x\\,y}/d/e', ['a/(b|c)/\\{x,y\\}/d/e']); + }); + + test('should not expand escaped braces.', () => { + optimize('{a,b\\}c,d}', ['(a|b\\}c|d)']); + optimize('\\{a,b,c,d,e}', ['\\{a,b,c,d,e\\}']); + optimize('a/{z,\\{a,b,c,d,e}/d', ['a/(z|\\{a|b|c|d|e)/d']); + optimize('a/\\{b,c}/{d,e}/f', ['a/\\{b,c\\}/(d|e)/f']); + optimize('./\\{x,y}/{a..z..3}/', ['./\\{x,y\\}/(a|d|g|j|m|p|s|v|y)/']); + }); + + test('should not expand escaped braces or commas.', () => { + optimize('{x\\,y,\\{abc\\},trie}', ['(x,y|\\{abc\\}|trie)']); + }); + }); + + describe('set expansion', () => { + test('should support sequence brace operators', () => { + optimize('/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', ['/usr/(ucb/(ex|edit)|lib/(ex|how_ex))']); + optimize('ff{c,b,a}', ['ff(c|b|a)']); + optimize('f{d,e,f}g', ['f(d|e|f)g']); + optimize('x{{0..10},braces}y', ['x(([0-9]|10)|braces)y']); + optimize('{1..10}', ['([1-9]|10)']); + optimize('{a,b,c}', ['(a|b|c)']); + optimize('{braces,{0..10}}', ['(braces|([0-9]|10))']); + optimize('{l,n,m}xyz', ['(l|n|m)xyz']); + optimize('{{0..10},braces}', ['(([0-9]|10)|braces)']); + optimize('{{1..10..2},braces}', ['((1|3|5|7|9)|braces)']); + optimize('{{1..10},braces}', ['(([1-9]|10)|braces)']); + }); + + test('should expand multiple sets', () => { + optimize('a/{a,b}/{c,d}/e', ['a/(a|b)/(c|d)/e']); + optimize('a{b,c}d{e,f}g', ['a(b|c)d(e|f)g']); + optimize('a/{x,y}/c{d,e}f.{md,txt}', ['a/(x|y)/c(d|e)f.(md|txt)']); + }); + + test('should expand nested sets', () => { + optimize('{a,b}{{a,b},a,b}', ['(a|b)((a|b)|a|b)']); + optimize('a{b,c{d,e}f}g', ['a(b|c(d|e)f)g']); + optimize('a{{x,y},z}b', ['a((x|y)|z)b']); + optimize('f{x,y{g,z}}h', ['f(x|y(g|z))h']); + optimize('a{b,c}{d,e}/hx/z', ['a(b|c)(d|e)/hx/z']); + optimize('a{b,c{d,e},h}x/z', ['a(b|c(d|e)|h)x/z']); + optimize('a{b,c{d,e},h}x{y,z}', ['a(b|c(d|e)|h)x(y|z)']); + optimize('a{b,c{d,e},{f,g}h}x{y,z}', ['a(b|c(d|e)|(f|g)h)x(y|z)']); + optimize('a-{b{d,e}}-c', ['a-\\{b(d|e)\\}-c']); + }); + + test('should expand not modify non-brace characters', () => { + optimize('a/b/{d,e}/*.js', ['a/b/(d|e)/*.js']); + optimize('a/**/c/{d,e}/f*.js', ['a/**/c/(d|e)/f*.js']); + optimize('a/**/c/{d,e}/f*.{md,txt}', ['a/**/c/(d|e)/f*.(md|txt)']); + }); + }); + + describe('commas', () => { + test('should work with leading and trailing commas.', () => { + optimize('a{b,}c', ['a(b|)c']); + optimize('a{,b}c', ['a(|b)c']); + }); + }); + + describe('spaces', () => { + test('should handle spaces', () => { + optimize('0{1..9} {10..20}', ['0([1-9]) (1[0-9]|20)']); + optimize('a{ ,c{d, },h}x', ['a( |c(d| )|h)x']); + optimize('a{ ,c{d, },h} ', ['a( |c(d| )|h) ']); + + // see https://github.com/jonschlinkert/micromatch/issues/66 + optimize('/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.(html|ejs)']); + }); + }); + }); + + /** + * Ranges + */ + + describe('ranges', () => { + describe('escaping / invalid ranges', () => { + test('should not try to expand ranges with decimals', () => { + optimize('{1.1..2.1}', ['\\{1.1..2.1\\}']); + optimize('{1.1..~2.1}', ['\\{1.1..~2.1\\}']); + }); + + test('should escape invalid ranges:', () => { + optimize('{1..0f}', ['{1..0f}']); + optimize('{1..10..ff}', ['{1..10..ff}']); + optimize('{1..10.f}', ['{1..10.f}']); + optimize('{1..10f}', ['{1..10f}']); + optimize('{1..20..2f}', ['{1..20..2f}']); + optimize('{1..20..f2}', ['{1..20..f2}']); + optimize('{1..2f..2}', ['{1..2f..2}']); + optimize('{1..ff..2}', ['{1..ff..2}']); + optimize('{1..ff}', ['{1..ff}']); + optimize('{1..f}', ['([1-f])']); + optimize('{1.20..2}', ['{1.20..2}']); + }); + + test('weirdly-formed brace expansions -- fixed in post-bash-3.1', () => { + optimize('a-{b{d,e}}-c', ['a-\\{b(d|e)\\}-c']); + optimize('a-{bdef-{g,i}-c', ['a-\\{bdef-(g|i)-c']); + }); + + test('should not expand quoted strings.', () => { + optimize('{"klklkl"}{1,2,3}', ['\\{klklkl\\}(1|2|3)']); + optimize('{"x,x"}', ['\\{x,x\\}']); + }); + + test('should escaped outer braces in nested non-sets', () => { + optimize('{a-{b,c,d}}', ['{a-(b|c|d)}']); + optimize('{a,{a-{b,c,d}}}', ['(a|{a-(b|c|d)})']); + }); + + test('should escape imbalanced braces', () => { + optimize('a-{bdef-{g,i}-c', ['a-\\{bdef-(g|i)-c']); + optimize('abc{', ['abc\\{']); + optimize('{abc{', ['\\{abc\\{']); + optimize('{abc', ['\\{abc']); + optimize('}abc', ['\\}abc']); + optimize('ab{c', ['ab\\{c']); + optimize('{{a,b}', ['\\{(a|b)']); + optimize('{a,b}}', ['(a|b)\\}']); + optimize('abcd{efgh', ['abcd\\{efgh']); + optimize('a{b{c{d,e}f}g}h', ['a(b(c(d|e)f)g)h']); + optimize('f{x,y{{g,z}}h}', ['f(x|y((g|z))h)']); + optimize('z{a,b},c}d', ['z(a|b),c\\}d']); + optimize('a{b{c{d,e}f{x,y{{g}h', ['a\\{b\\{c(d|e)f\\{x,y\\{\\{g\\}h']); + optimize('f{x,y{{g}h', ['f\\{x,y\\{\\{g\\}h']); + optimize('f{x,y{{g}}h', ['f{x,y{{g}}h']); + optimize('a{b{c{d,e}f{x,y{}g}h', ['a{b{c(d|e)f(x|y{}g)h']); + optimize('f{x,y{}g}h', ['f(x|y\\{\\}g)h']); + optimize('z{a,b{,c}d', ['z\\{a,b(|c)d']); + }); + }); + + describe('positive numeric ranges', () => { + test('should expand numeric ranges', () => { + optimize('a{0..3}d', ['a([0-3])d']); + optimize('x{10..1}y', ['x([1-9]|10)y']); + optimize('x{3..3}y', ['x3y']); + optimize('{1..10}', ['([1-9]|10)']); + optimize('{1..3}', ['([1-3])']); + optimize('{1..9}', ['([1-9])']); + optimize('{10..1}', ['([1-9]|10)']); + optimize('{10..1}y', ['([1-9]|10)y']); + optimize('{3..3}', ['3']); + optimize('{5..8}', ['([5-8])']); + }); + }); + + describe('negative ranges', () => { + test('should expand ranges with negative numbers', () => { + optimize('{-1..-10}', ['(-[1-9]|-10)']); + optimize('{-10..-1}', ['(-[1-9]|-10)']); + optimize('{-20..0}', ['(-[1-9]|-1[0-9]|-20|0)']); + optimize('{0..-5}', ['(-[1-5]|0)']); + optimize('{9..-4}', ['(-[1-4]|[0-9])']); + }); + }); + + describe('alphabetical ranges', () => { + test('should expand alphabetical ranges', () => { + optimize('0{1..9}/{10..20}', ['0([1-9])/(1[0-9]|20)']); + optimize('0{a..d}0', ['0([a-d])0']); + optimize('a/{b..d}/e', ['a/([b-d])/e']); + optimize('{1..f}', ['([1-f])']); + optimize('{a..A}', ['([A-a])']); + optimize('{A..a}', ['([A-a])']); + optimize('{a..e}', ['([a-e])']); + optimize('{A..E}', ['([A-E])']); + optimize('{a..f}', ['([a-f])']); + optimize('{a..z}', ['([a-z])']); + optimize('{E..A}', ['([A-E])']); + optimize('{f..1}', ['([1-f])']); + optimize('{f..a}', ['([a-f])']); + optimize('{f..f}', ['f']); + }); + + test('should expand multiple ranges:', () => { + optimize('a/{b..d}/e/{f..h}', ['a/([b-d])/e/([f-h])']); + }); + }); + + describe('combo', () => { + test('should expand numerical ranges - positive and negative', () => { + optimize('{-10..10}', ['(-[1-9]|-?10|[0-9])']); + }); + }); + + // HEADS UP! If you're using the `--mm` flag minimatch freezes on these + describe('large numbers', () => { + test('should expand large numbers', () => { + optimize('{2147483645..2147483649}', ['(214748364[5-9])']); + optimize('{214748364..2147483649}', ['(21474836[4-9]|2147483[7-9][0-9]|214748[4-9][0-9]{2}|214749[0-9]{3}|2147[5-9][0-9]{4}|214[8-9][0-9]{5}|21[5-9][0-9]{6}|2[2-9][0-9]{7}|[3-9][0-9]{8}|1[0-9]{9}|20[0-9]{8}|21[0-3][0-9]{7}|214[0-6][0-9]{6}|2147[0-3][0-9]{5}|21474[0-7][0-9]{4}|214748[0-2][0-9]{3}|2147483[0-5][0-9]{2}|21474836[0-4][0-9])']); + }); + }); + + describe('steps > positive ranges', () => { + test('should expand ranges using steps:', () => { + optimize('{1..10..1}', ['([1-9]|10)']); + optimize('{1..10..2}', ['(1|3|5|7|9)']); + optimize('{1..20..20}', ['1']); + optimize('{1..20..20}', ['1']); + optimize('{1..20..20}', ['1']); + optimize('{1..20..2}', ['(1|3|5|7|9|11|13|15|17|19)']); + optimize('{10..0..2}', ['(10|8|6|4|2|0)']); + optimize('{10..1..2}', ['(10|8|6|4|2)']); + optimize('{100..0..5}', ['(100|95|90|85|80|75|70|65|60|55|50|45|40|35|30|25|20|15|10|5|0)']); + optimize('{2..10..1}', ['([2-9]|10)']); + optimize('{2..10..2}', ['(2|4|6|8|10)']); + optimize('{2..10..3}', ['(2|5|8)']); + optimize('{a..z..2}', ['(a|c|e|g|i|k|m|o|q|s|u|w|y)']); + }); + + test('should expand positive ranges with negative steps:', () => { + optimize('{10..0..-2}', ['(10|8|6|4|2|0)']); + }); + }); + + describe('steps > negative ranges', () => { + test('should expand negative ranges using steps:', () => { + optimize('{-1..-10..-2}', ['(-(1|3|5|7|9))']); + optimize('{-1..-10..2}', ['(-(1|3|5|7|9))']); + optimize('{-10..-2..2}', ['(-(10|8|6|4|2))']); + optimize('{-2..-10..1}', ['(-[2-9]|-10)']); + optimize('{-2..-10..2}', ['(-(2|4|6|8|10))']); + optimize('{-2..-10..3}', ['(-(2|5|8))']); + optimize('{-50..-0..5}', ['(0|-(50|45|40|35|30|25|20|15|10|5))']); + optimize('{-9..9..3}', ['(0|3|6|9|-(9|6|3))']); + optimize('{10..1..-2}', ['(10|8|6|4|2)']); + optimize('{100..0..-5}', ['(100|95|90|85|80|75|70|65|60|55|50|45|40|35|30|25|20|15|10|5|0)']); + }); + }); + + describe('steps > alphabetical ranges', () => { + test('should expand alpha ranges with steps', () => { + optimize('{a..e..2}', ['(a|c|e)']); + optimize('{E..A..2}', ['(E|C|A)']); + optimize('{a..z}', ['([a-z])']); + optimize('{a..z..2}', ['(a|c|e|g|i|k|m|o|q|s|u|w|y)']); + optimize('{z..a..-2}', ['(z|x|v|t|r|p|n|l|j|h|f|d|b)']); + }); + + test('should expand alpha ranges with negative steps', () => { + optimize('{z..a..-2}', ['(z|x|v|t|r|p|n|l|j|h|f|d|b)']); + }); + }); + + describe('padding', () => { + test('unwanted zero-padding -- fixed post-bash-4.0', () => { + optimize('{10..0..2}', ['(10|8|6|4|2|0)']); + optimize('{10..0..-2}', ['(10|8|6|4|2|0)']); + optimize('{-50..-0..5}', ['(0|-(50|45|40|35|30|25|20|15|10|5))']); + }); + }); + }); + + describe('integration', () => { + test('should work with dots in file paths', () => { + optimize('../{1..3}/../foo', '../([1-3])/../foo'); + optimize('../{2..10..2}/../foo', '../(2|4|6|8|10)/../foo'); + optimize('../{1..3}/../{a,b,c}/foo', '../([1-3])/../(a|b|c)/foo'); + optimize('./{a..z..3}/', './(a|d|g|j|m|p|s|v|y)/'); + optimize('./{"x,y"}/{a..z..3}/', './\\{x,y\\}/(a|d|g|j|m|p|s|v|y)/'); + }); + + test('should expand a complex combination of ranges and sets:', () => { + optimize('a/{x,y}/{1..5}c{d,e}f.{md,txt}', 'a/(x|y)/([1-5])c(d|e)f.(md|txt)'); + }); + + test('should expand complex sets and ranges in `bash` mode:', () => { + optimize('a/{x,{1..5},y}/c{d}e', 'a/(x|([1-5])|y)/c\\{d\\}e'); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/braces.test.ts b/packages/node-utils/test/micromatch/braces.test.ts new file mode 100644 index 0000000..f794a01 --- /dev/null +++ b/packages/node-utils/test/micromatch/braces.test.ts @@ -0,0 +1,274 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; +const { isMatch, hasBraces } = mm; + +describe('braces', () => { + test('should return true when braces are found', () => { + expect_loose_equal(hasBraces('{foo}'), true); + expect_loose_equal(hasBraces('foo}'), false); + expect_loose_equal(hasBraces('{foo'), false); + expect_loose_equal(hasBraces('a{}b'), true); + expect_loose_equal(hasBraces('abc {foo} xyz'), true); + expect_loose_equal(hasBraces('abc {foo xyz'), false); + expect_loose_equal(hasBraces('abc {foo} xyz'), true); + expect_loose_equal(hasBraces('abc foo} xyz'), false); + expect_loose_equal(hasBraces('abc foo xyz'), false); + expect_loose_equal(hasBraces('abc {foo} xyz {bar} pqr'), true); + expect_loose_equal(hasBraces('abc {foo xyz {bar} pqr'), true); + expect_loose_equal(hasBraces('abc foo} xyz {bar pqr'), false); + }); + + test('should handle extglobs in braces', () => { + let fixtures = ['a', 'b', 'c', 'd', 'ab', 'ac', 'ad', 'bc', 'cb', 'bc,d', 'c,db', 'c,d', 'd)', '(b|c', '*(b|c', 'b|c', 'b|cc', 'cb|c', 'x(a|b|c)', 'x(a|c)', '(a|b|c)', '(a|c)']; + + expect_deepEqual(mm(fixtures, ['a', '*(b|c,d)']), ['a', 'b', 'bc,d', 'c,db', 'c,d']); + expect_deepEqual(mm(fixtures, '{a,*(b|c,d)}'), ['a', 'b', 'bc,d', 'c,db', 'c,d']); + expect_deepEqual(mm(fixtures, ['a', '*(b|c,d)'], { expand: true }), ['a', 'b', 'bc,d', 'c,db', 'c,d']); + expect_deepEqual(mm(fixtures, '{a,*(b|c,d)}', { expand: true }), ['a', 'b', 'bc,d', 'c,db', 'c,d']); + + let expected = ['a', 'b', 'c', 'ab', 'ac', 'bc', 'cb']; + expect_deepEqual(mm(fixtures, '*(a|b|c)'), expected); + expect_deepEqual(mm(fixtures, '*(a|{b|c,c})'), expected); + expect_deepEqual(mm(fixtures, '*(a|{b|c,c})', { expand: true }), expected); + }); + + test('should not match with brace sets when disabled', () => { + expect_truthy(!isMatch('a/a', 'a/{a,b}', { nobrace: true })); + expect_truthy(!isMatch('a/b', 'a/{a,b}', { nobrace: true })); + expect_truthy(!isMatch('a/c', 'a/{a,b}', { nobrace: true })); + expect_truthy(!isMatch('b/b', 'a/{a,b}', { nobrace: true })); + expect_truthy(!isMatch('b/b', 'a/{a,b,c}', { nobrace: true })); + expect_truthy(!isMatch('a/c', 'a/{a,b,c}', { nobrace: true })); + }); + + test('should not match with brace ranges when disabled', () => { + expect_truthy(!isMatch('a/a', 'a/{a..c}', { nobrace: true })); + expect_truthy(!isMatch('a/b', 'a/{a..c}', { nobrace: true })); + expect_truthy(!isMatch('a/c', 'a/{a..c}', { nobrace: true })); + }); + + test('should match with brace sets', () => { + expect_truthy(isMatch('a/a', 'a/{a,b}')); + expect_truthy(isMatch('a/b', 'a/{a,b}')); + expect_truthy(!isMatch('a/c', 'a/{a,b}')); + expect_truthy(!isMatch('b/b', 'a/{a,b}')); + expect_truthy(!isMatch('b/b', 'a/{a,b,c}')); + expect_truthy(isMatch('a/c', 'a/{a,b,c}')); + }); + + test('should match with brace ranges', () => { + expect_truthy(isMatch('a/a', 'a/{a..c}')); + expect_truthy(isMatch('a/b', 'a/{a..c}')); + expect_truthy(isMatch('a/c', 'a/{a..c}')); + }); + + test('should not convert braces inside brackets', () => { + expect_truthy(isMatch('foo{}baz', 'foo[{a,b}]+baz')); + expect_truthy(isMatch('{a}{b}{c}', '[abc{}]+')); + }); + + test('should support braces with empty elements', () => { + expect_truthy(!isMatch('abc.txt', 'a{,b}.txt')); + expect_truthy(!isMatch('abc.txt', 'a{a,b,}.txt')); + expect_truthy(!isMatch('abc.txt', 'a{b,}.txt')); + expect_truthy(isMatch('a.txt', 'a{,b}.txt')); + expect_truthy(isMatch('a.txt', 'a{b,}.txt')); + expect_truthy(isMatch('aa.txt', 'a{a,b,}.txt')); + expect_truthy(isMatch('aa.txt', 'a{a,b,}.txt')); + expect_truthy(isMatch('ab.txt', 'a{,b}.txt')); + expect_truthy(isMatch('ab.txt', 'a{b,}.txt')); + }); + + test('should support braces containing slashes', () => { + expect_truthy(isMatch('a', '{a/,}a/**')); + expect_truthy(isMatch('aa.txt', 'a{a,b/}*.txt')); + expect_truthy(isMatch('ab/.txt', 'a{a,b/}*.txt')); + expect_truthy(isMatch('ab/a.txt', 'a{a,b/}*.txt')); + expect_truthy(isMatch('a/', '{a/,}a/**')); + expect_truthy(isMatch('a/a/', '{a/,}a/**')); + expect_truthy(isMatch('a/a', '{a/,}a/**')); + expect_truthy(isMatch('a/a/a', '{a/,}a/**')); + expect_truthy(isMatch('a/a/', '{a/,}a/**')); + expect_truthy(isMatch('a/a/a/', '{a/,}a/**')); + expect_truthy(isMatch('a/b/a/', '{a/,}b/**')); + expect_truthy(isMatch('b/a/', '{a/,}b/**')); + }); + + test('should support braces with slashes and empty elements', () => { + expect_truthy(isMatch('a.txt', 'a{,/}*.txt')); + expect_truthy(isMatch('ab.txt', 'a{,/}*.txt')); + expect_truthy(isMatch('a/b.txt', 'a{,/}*.txt')); + expect_truthy(isMatch('a/ab.txt', 'a{,/}*.txt')); + }); + + test('should support braces with escaped parens and stars', () => { + expect_truthy(isMatch('a.txt', 'a{,.*{foo,db},\\(bar\\)}.txt')); + expect_truthy(!isMatch('adb.txt', 'a{,.*{foo,db},\\(bar\\)}.txt')); + expect_truthy(isMatch('a.db.txt', 'a{,.*{foo,db},\\(bar\\)}.txt')); + + expect_truthy(isMatch('a.txt', 'a{,*.{foo,db},\\(bar\\)}.txt')); + expect_truthy(!isMatch('adb.txt', 'a{,*.{foo,db},\\(bar\\)}.txt')); + expect_truthy(isMatch('a.db.txt', 'a{,*.{foo,db},\\(bar\\)}.txt')); + + expect_truthy(isMatch('a', 'a{,.*{foo,db},\\(bar\\)}')); + expect_truthy(!isMatch('adb', 'a{,.*{foo,db},\\(bar\\)}')); + expect_truthy(isMatch('a.db', 'a{,.*{foo,db},\\(bar\\)}')); + + expect_truthy(isMatch('a', 'a{,*.{foo,db},\\(bar\\)}')); + expect_truthy(!isMatch('adb', 'a{,*.{foo,db},\\(bar\\)}')); + expect_truthy(isMatch('a.db', 'a{,*.{foo,db},\\(bar\\)}')); + + expect_truthy(!isMatch('a', '{,.*{foo,db},\\(bar\\)}')); + expect_truthy(!isMatch('adb', '{,.*{foo,db},\\(bar\\)}')); + expect_truthy(!isMatch('a.db', '{,.*{foo,db},\\(bar\\)}')); + expect_truthy(isMatch('.db', '{,.*{foo,db},\\(bar\\)}')); + + expect_truthy(!isMatch('a', '{,*.{foo,db},\\(bar\\)}')); + expect_truthy(isMatch('a', '{*,*.{foo,db},\\(bar\\)}')); + expect_truthy(!isMatch('adb', '{,*.{foo,db},\\(bar\\)}')); + expect_truthy(isMatch('a.db', '{,*.{foo,db},\\(bar\\)}')); + }); + + test('should support braces in patterns with globstars', () => { + expect_truthy(!isMatch('a/b/c/xyz.md', 'a/b/**/c{d,e}/**/xyz.md')); + expect_truthy(!isMatch('a/b/d/xyz.md', 'a/b/**/c{d,e}/**/xyz.md')); + expect_truthy(isMatch('a/b/cd/xyz.md', 'a/b/**/c{d,e}/**/xyz.md')); + expect_truthy(isMatch('a/b/c/xyz.md', 'a/b/**/{c,d,e}/**/xyz.md')); + expect_truthy(isMatch('a/b/d/xyz.md', 'a/b/**/{c,d,e}/**/xyz.md')); + }); + + test('should support globstars enclosed in braces, with slashes and empty elements', () => { + expect_truthy(isMatch('a.txt', 'a{,/**/}*.txt')); + expect_truthy(isMatch('a/b.txt', 'a{,/**/,/}*.txt')); + expect_truthy(isMatch('a/x/y.txt', 'a{,/**/}*.txt')); + expect_truthy(!isMatch('a/x/y/z', 'a{,/**/}*.txt')); + }); + + test('should support braces with globstars and empty elements', () => { + expect_truthy(isMatch('a/b/foo/bar/baz.qux', 'a/b{,/**}/bar{,/**}/*.*')); + expect_truthy(isMatch('a/b/bar/baz.qux', 'a/b{,/**}/bar{,/**}/*.*')); + }); + + test('should support Kleene stars', () => { + expect_truthy(isMatch('ab', '{ab,c}*')); + expect_truthy(isMatch('abab', '{ab,c}*')); + expect_truthy(isMatch('ababab', '{ab,c}*')); + expect_truthy(isMatch('ababc', '{ab,c}*')); + expect_truthy(isMatch('abc', '{ab,c}*')); + expect_truthy(isMatch('abcab', '{ab,c}*')); + expect_truthy(isMatch('abcc', '{ab,c}*')); + expect_truthy(isMatch('c', '{ab,c}*')); + expect_truthy(isMatch('cab', '{ab,c}*')); + expect_truthy(isMatch('cabab', '{ab,c}*')); + expect_truthy(isMatch('cabc', '{ab,c}*')); + expect_truthy(isMatch('cc', '{ab,c}*')); + expect_truthy(isMatch('ccab', '{ab,c}*')); + expect_truthy(isMatch('ccc', '{ab,c}*')); + }); + + test('should support Kleene plus', () => { + expect_truthy(isMatch('ab', '{ab,c}+')); + expect_truthy(isMatch('abab', '{ab,c}+')); + expect_truthy(isMatch('abc', '{ab,c}+')); + expect_truthy(isMatch('c', '{ab,c}+')); + expect_truthy(isMatch('cab', '{ab,c}+')); + expect_truthy(isMatch('cc', '{ab,c}+')); + expect_truthy(isMatch('ababab', '{ab,c}+')); + expect_truthy(isMatch('ababc', '{ab,c}+')); + expect_truthy(isMatch('abcab', '{ab,c}+')); + expect_truthy(isMatch('abcc', '{ab,c}+')); + expect_truthy(isMatch('cabab', '{ab,c}+')); + expect_truthy(isMatch('cabc', '{ab,c}+')); + expect_truthy(isMatch('ccab', '{ab,c}+')); + expect_truthy(isMatch('ccc', '{ab,c}+')); + expect_truthy(isMatch('ccc', '{a,b,c}+')); + + expect_truthy(isMatch('a', '{a,b,c}+')); + expect_truthy(isMatch('b', '{a,b,c}+')); + expect_truthy(isMatch('c', '{a,b,c}+')); + expect_truthy(isMatch('aa', '{a,b,c}+')); + expect_truthy(isMatch('ab', '{a,b,c}+')); + expect_truthy(isMatch('ac', '{a,b,c}+')); + expect_truthy(isMatch('ba', '{a,b,c}+')); + expect_truthy(isMatch('bb', '{a,b,c}+')); + expect_truthy(isMatch('bc', '{a,b,c}+')); + expect_truthy(isMatch('ca', '{a,b,c}+')); + expect_truthy(isMatch('cb', '{a,b,c}+')); + expect_truthy(isMatch('cc', '{a,b,c}+')); + expect_truthy(isMatch('aaa', '{a,b,c}+')); + expect_truthy(isMatch('aab', '{a,b,c}+')); + expect_truthy(isMatch('abc', '{a,b,c}+')); + }); + + test('should support braces', () => { + expect_truthy(isMatch('a', '{a,b,c}')); + expect_truthy(isMatch('b', '{a,b,c}')); + expect_truthy(isMatch('c', '{a,b,c}')); + expect_truthy(!isMatch('aa', '{a,b,c}')); + expect_truthy(!isMatch('bb', '{a,b,c}')); + expect_truthy(!isMatch('cc', '{a,b,c}')); + }); + + test('should support regex quantifiers by escaping braces', () => { + expect_truthy(!isMatch('a ', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('a ', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('a', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('aa', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('aaa', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('b', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('bb', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('bbb', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(isMatch(' a ', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(isMatch('b ', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(isMatch('b ', '@(!(a) \\{1,2\\})*', { unescape: true })); + + expect_truthy(isMatch('a ', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('a b', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('a b', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('a ', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('a ', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('a', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('aa', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('b', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('bb', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch(' a ', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('b ', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('b ', '@(!(a \\{1,2\\}))*')); + }); +}); diff --git a/packages/node-utils/test/micromatch/dotfiles.test.ts b/packages/node-utils/test/micromatch/dotfiles.test.ts new file mode 100644 index 0000000..ffbc404 --- /dev/null +++ b/packages/node-utils/test/micromatch/dotfiles.test.ts @@ -0,0 +1,381 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mi = require('minimatch'); +const mm = micromatch; +const { isMatch } = mm; + +describe('dotfiles', () => { + describe('file name matching', () => { + test('should not match a dot when the dot is not explicitly defined', () => { + expect_truthy(!isMatch('.dot', '*dot')); + expect_truthy(!isMatch('a/.dot', 'a/*dot')); + }); + + test('should not match leading dots with question marks', () => { + expect_truthy(!isMatch('.dot', '?dot')); + expect_truthy(!isMatch('/.dot', '/?dot')); + expect_truthy(!isMatch('a/.dot', 'a/?dot')); + }); + + test('should match double dots with double dots', () => { + let fixtures = ['a/../a', 'ab/../ac', '../a', 'a', '../../b', '../c', '../c/d']; + expect_deepEqual(mm(fixtures, '../*'), ['../a', '../c']); + expect_deepEqual(mm(fixtures, '*/../*'), ['a/../a', 'ab/../ac']); + expect_deepEqual(mm(fixtures, '**/../*'), ['a/../a', 'ab/../ac', '../a', '../c']); + }); + + test('should not match exclusive double or single dots', () => { + let fixtures = ['a/./b', 'a/../b', 'a/c/b', 'a/.d/b']; + let opts = { dot: true }; + expect_deepEqual(mm(fixtures, 'a/.*/b'), ['a/.d/b']); + expect_deepEqual(mm(fixtures, 'a/.*/b', opts), ['a/.d/b']); + expect_deepEqual(mm(fixtures, 'a/*/b', opts), ['a/c/b', 'a/.d/b']); + expect_truthy(!isMatch('../c', '**/**/**', opts)); + expect_truthy(!isMatch('../c', '**/**/**')); + }); + + test('should match dotfiles when there is a leading dot:', () => { + let files = ['a/b', 'a/.b', '.a/b', '.a/.b']; + let dotfiles = ['.dotfile', '.dotfile.md']; + let opts = { dot: true }; + expect_deepEqual(mm(dotfiles, '.*.md', opts), ['.dotfile.md']); + expect_deepEqual(mm(dotfiles, '.dotfile', opts), ['.dotfile']); + expect_deepEqual(mm(dotfiles, '.dotfile*', opts), dotfiles); + expect_deepEqual(mm(files, 'a/{.*,**}', opts), ['a/b', 'a/.b']); + expect_deepEqual(mm(files, '{.*,**}', opts), files); + expect_deepEqual(mm(files, '*/.*', opts), ['a/.b', '.a/.b']); + }); + + test('should match dotfiles when there is not a leading dot:', () => { + let files = ['.a', 'a', 'a/b', 'a/.b', '.a/b', '.a/.b']; + let opts = { dot: true }; + + expect_deepEqual(mm(files, '*', opts), ['.a', 'a']); + expect_deepEqual(mm(files, '*/*', opts), ['a/b', 'a/.b', '.a/b', '.a/.b']); + expect_deepEqual(mm(files, '**', opts), files); + expect_deepEqual(mm(['.dotfile'], '*.*', opts), ['.dotfile']); + expect_deepEqual(mm(['.a', '.b', 'c', 'c.md'], '*.*', opts), ['.a', '.b', 'c.md']); + expect_deepEqual(mm(['.dotfile'], '*.md', opts), []); + expect_deepEqual(mm(['.verb.txt'], '*.md', opts), []); + expect_deepEqual(mm(['a/b/c/.dotfile'], '*.md', opts), []); + expect_deepEqual(mm(['a/b/c/.dotfile.md'], '*.md', opts), []); + expect_deepEqual(mm(['a/b/c/.verb.md'], '**/*.md', opts), ['a/b/c/.verb.md']); + expect_deepEqual(mm(['foo.md'], '*.md', opts), ['foo.md']); + expect_truthy(isMatch('b/.c', '**/**/**', opts)); + expect_truthy(!isMatch('b/.c', '**/**/**')); + }); + + test('should use negation patterns on dotfiles:', () => { + expect_deepEqual(mm(['.a', '.b', 'c', 'c.md'], '!.*'), ['c', 'c.md']); + expect_deepEqual(mm(['.a', '.b', 'c', 'c.md'], '!(.*)'), ['c', 'c.md']); + expect_deepEqual(mm(['.a', '.b', 'c', 'c.md'], '!(.*)*'), ['c', 'c.md']); + expect_deepEqual(mm(['.a', '.b', 'c', 'c.md'], '!*.*'), ['.a', '.b', 'c']); + }); + + test('should match dotfiles when `options.dot` is true:', () => { + expect_deepEqual(mm(['.dotfile'], '*.*', { dot: true }), ['.dotfile']); + expect_deepEqual(mm(['.dotfile'], '*.md', { dot: true }), []); + expect_deepEqual(mm(['.dotfile'], '.dotfile', { dot: true }), ['.dotfile']); + expect_deepEqual(mm(['.dotfile.md'], '.*.md', { dot: true }), ['.dotfile.md']); + expect_deepEqual(mm(['.verb.txt'], '*.md', { dot: true }), []); + expect_deepEqual(mm(['.verb.txt'], '*.md', { dot: true }), []); + expect_deepEqual(mm(['a/b/c/.dotfile'], '*.md', { dot: true }), []); + expect_deepEqual(mm(['a/b/c/.dotfile.md'], '**/*.md', { dot: true }), ['a/b/c/.dotfile.md']); + expect_deepEqual(mm(['a/b/c/.dotfile.md'], '**/.*', { dot: false }), ['a/b/c/.dotfile.md']); + expect_deepEqual(mm(['a/b/c/.dotfile.md'], '**/.*.md', { dot: false }), ['a/b/c/.dotfile.md']); + expect_deepEqual(mm(['a/b/c/.dotfile.md'], '*.md', { dot: false }), []); + expect_deepEqual(mm(['a/b/c/.dotfile.md'], '*.md', { dot: true }), []); + expect_deepEqual(mm(['a/b/c/.verb.md'], '**/*.md', { dot: true }), ['a/b/c/.verb.md']); + expect_deepEqual(mm(['d.md'], '*.md', { dot: true }), ['d.md']); + }); + + test('should not match a dot when the dot is not explicitly defined', () => { + let fixtures = ['a/b/.x', '.x', '.x/', '.x/a', '.x/a/b', '.x/.x', 'a/.x', 'a/b/.x/c', 'a/b/.x/c/d', 'a/b/.x/c/d/e', 'a/b/.x/', 'a/.x/b', 'a/.x/b/.x/c']; + expect_deepEqual(mm(fixtures, '**'), []); + expect_deepEqual(mm(fixtures, 'a/**/c'), []); + }); + + test('should match a dot when the dot is explicitly defined', () => { + let fixtures = ['.x', '.x/', '.x/.x', '.x/a', '.x/a/b', 'a/.x/.x/c', 'a/.x/.x/.x/c', 'a/.x/b', 'a/.x/b/.x/c', 'a/b/.x', 'a/b/.x/', 'a/b/.x/c', 'a/b/.x/c/d', 'a/b/.x/c/d/e']; + let expected = ['.x', '.x/', '.x/.x', '.x/a', '.x/a/b', 'a/.x/.x/c', 'a/.x/b', 'a/b/.x', 'a/b/.x/', 'a/b/.x/c', 'a/b/.x/c/d', 'a/b/.x/c/d/e']; + + expect_deepEqual(mm(fixtures, '**/.x/.x/**'), ['.x/.x', 'a/.x/.x/c']); + expect_deepEqual(mm(fixtures, '**/.x/*/.x/**'), ['a/.x/b/.x/c']); + expect_deepEqual(mm(fixtures, '**/.x/**'), expected.filter(ele => !ele.includes('.x/.x'))); + expect_truthy(isMatch('.bar.baz', '.*.*')); + expect_truthy(isMatch('.bar.baz', '.*.*')); + expect_truthy(!isMatch('.bar.baz', '.*.*/')); + expect_truthy(isMatch('.bar.baz', '.*.baz')); + expect_truthy(!isMatch('.bar.baz/', '.*.*')); + expect_truthy(isMatch('.bar.baz/', '.*.*{,/}')); + expect_truthy(isMatch('.bar.baz/', '.*.*/')); + expect_truthy(isMatch('.dot', '.*ot')); + expect_truthy(isMatch('.dot', '.[d]ot')); + expect_truthy(isMatch('.dot.foo.bar', '.*ot.*.*')); + expect_truthy(isMatch('.dotfile.js', '.*.js')); + expect_truthy(isMatch('/.dot', '**/.[d]ot')); + expect_truthy(isMatch('/.dot', '**/.dot*')); + expect_truthy(isMatch('/.dot', '/.[d]ot')); + expect_truthy(isMatch('/.dot', '/.dot*')); + expect_truthy(isMatch('a/.dot', '**/.[d]ot')); + expect_truthy(isMatch('a/.dot', '*/.[d]ot')); + expect_truthy(isMatch('a/.dot', '*/.dot*')); + expect_truthy(isMatch('a/b/.dot', '**/.[d]ot')); + expect_truthy(isMatch('a/b/.dot', '**/.dot*')); + expect_truthy(isMatch('.dot', '.[d]ot')); + expect_truthy(isMatch('.dot', '.d?t')); + expect_truthy(isMatch('.dot', '.dot*')); + + expect_deepEqual(mm('.dot', '.[d]ot'), ['.dot']); + expect_deepEqual(mm('.dot', '.dot*'), ['.dot']); + expect_deepEqual(mm('.dot', '.d?t'), ['.dot']); + + expect_truthy(!isMatch('.bar.baz', '.*.*/')); + expect_truthy(isMatch('.bar.baz/', '.*.*{,/}')); + expect_truthy(isMatch('.bar.baz', '.*.*')); + expect_truthy(isMatch('.bar.baz', '.*.baz')); + expect_truthy(isMatch('.bar.baz/', '.*.*/')); + expect_truthy(isMatch('.dot', '.*ot')); + expect_truthy(isMatch('.dot', '.[d]ot')); + expect_truthy(isMatch('.dot.foo.bar', '.*ot.*.*')); + expect_truthy(isMatch('.dotfile.js', '.*.js')); + expect_truthy(isMatch('/.dot', '**/.[d]ot')); + expect_truthy(isMatch('/.dot', '**/.dot*')); + expect_truthy(isMatch('/.dot', '**/[.]dot')); + expect_truthy(isMatch('/.dot', '/[.]dot')); + expect_truthy(isMatch('a/.dot', '**/.[d]ot')); + expect_truthy(isMatch('a/.dot', '*/.[d]ot')); + expect_truthy(isMatch('a/.dot', '*/.dot*')); + expect_truthy(isMatch('a/.dot', '*/[.]dot')); + expect_truthy(isMatch('a/b/.dot', '**/.[d]ot')); + expect_truthy(isMatch('a/b/.dot', '**/.dot*')); + expect_truthy(isMatch('a/b/.dot', '**/[.]dot')); + }); + + test('should match dots in root path when glob is prefixed with **/', () => { + expect_truthy(isMatch('.x', '**/.x/**')); + expect_truthy(!isMatch('.x/.x', '**/.x/**')); + expect_truthy(isMatch('.x/.x', '**/.x/.x/**')); + expect_truthy(isMatch('a/b/.x', '**/.x/**')); + expect_truthy(isMatch('.x/', '**/.x/**')); + expect_truthy(isMatch('.x/a', '**/.x/**')); + expect_truthy(isMatch('.x/a/b', '**/.x/**')); + expect_truthy(isMatch('a/.x/b', '**/.x/**')); + expect_truthy(isMatch('a/b/.x', '**/.x')); + expect_truthy(isMatch('a/b/.x/', '**/.x/**')); + expect_truthy(isMatch('a/b/.x/c', '**/.x/**')); + expect_truthy(isMatch('a/b/.x/c/d', '**/.x/**')); + expect_truthy(isMatch('a/b/.x/c/d/e', '**/.x/**')); + }); + + test('should not match dotfiles with single stars by default', () => { + expect_truthy(isMatch('foo', '*')); + expect_truthy(isMatch('foo/bar', '*/*')); + expect_truthy(!isMatch('.foo', '*')); + expect_truthy(!isMatch('.foo/bar', '*/*')); + expect_truthy(!isMatch('.foo/.bar', '*/*')); + expect_truthy(!isMatch('foo/.bar', '*/*')); + expect_truthy(!isMatch('foo/.bar/baz', '*/*/*')); + }); + + test('should work with dots in the path', () => { + expect_truthy(isMatch('../test.js', '../*.js')); + expect_truthy(!isMatch('../.test.js', '../*.js')); + }); + + test('should not match dotfiles with globstars by default', () => { + expect_truthy(!isMatch('.foo', '**/**')); + expect_truthy(!isMatch('.foo', '**')); + expect_truthy(!isMatch('.foo', '**/*')); + expect_truthy(!isMatch('bar/.foo', '**/*')); + expect_truthy(!isMatch('.bar', '**/*')); + expect_truthy(!isMatch('foo/.bar', '**/*')); + expect_truthy(!isMatch('foo/.bar', '**/*a*')); + }); + + test('should match dotfiles when a leading dot is in the pattern', () => { + expect_truthy(!isMatch('foo', '**/.*a*')); + expect_truthy(isMatch('.bar', '**/.*a*')); + expect_truthy(isMatch('foo/.bar', '**/.*a*')); + expect_truthy(isMatch('.foo', '**/.*')); + + expect_truthy(!isMatch('foo', '.*a*')); + expect_truthy(isMatch('.bar', '.*a*')); + expect_truthy(!isMatch('bar', '.*a*')); + + expect_truthy(!isMatch('foo', '.b*')); + expect_truthy(isMatch('.bar', '.b*')); + expect_truthy(!isMatch('bar', '.b*')); + + expect_truthy(!isMatch('foo', '.*r')); + expect_truthy(isMatch('.bar', '.*r')); + expect_truthy(!isMatch('bar', '.*r')); + }); + + test('should not match a dot when the dot is not explicitly defined', () => { + expect_truthy(!isMatch('.dot', '**/*dot')); + expect_truthy(!isMatch('.dot', '**/?dot')); + expect_truthy(!isMatch('.dot', '*/*dot')); + expect_truthy(!isMatch('.dot', '*/?dot')); + expect_truthy(!isMatch('.dot', '*dot')); + expect_truthy(!isMatch('.dot', '/*dot')); + expect_truthy(!isMatch('.dot', '/?dot')); + expect_truthy(!isMatch('/.dot', '**/*dot')); + expect_truthy(!isMatch('/.dot', '**/?dot')); + expect_truthy(!isMatch('/.dot', '*/*dot')); + expect_truthy(!isMatch('/.dot', '*/?dot')); + expect_truthy(!isMatch('/.dot', '/*dot')); + expect_truthy(!isMatch('/.dot', '/?dot')); + expect_truthy(!isMatch('a/.dot', '*/*dot')); + expect_truthy(!isMatch('a/.dot', '*/?dot')); + expect_truthy(!isMatch('a/.dot', 'a/*dot')); + expect_truthy(!isMatch('a/b/.dot', '**/*dot')); + expect_truthy(!isMatch('a/b/.dot', '**/?dot')); + }); + + test('should not match leading dots with question marks', () => { + expect_truthy(!isMatch('.dot', '?dot')); + expect_truthy(!isMatch('/.dot', '/?dot')); + expect_truthy(!isMatch('a/.dot', 'a/?dot')); + }); + + test('should match with double dots', () => { + expect_truthy(!isMatch('../../b', '**/../*')); + expect_truthy(!isMatch('../../b', '*/../*')); + expect_truthy(!isMatch('../../b', '../*')); + expect_truthy(!isMatch('../a', '*/../*')); + expect_truthy(!isMatch('../c', '*/../*')); + expect_truthy(!isMatch('../c/d', '**/../*')); + expect_truthy(!isMatch('../c/d', '*/../*')); + expect_truthy(!isMatch('../c/d', '../*')); + expect_truthy(!isMatch('a', '**/../*')); + expect_truthy(!isMatch('a', '*/../*')); + expect_truthy(!isMatch('a', '../*')); + expect_truthy(!isMatch('a/../a', '../*')); + expect_truthy(!isMatch('ab/../ac', '../*')); + expect_truthy(!isMatch('a/../', '**/../*')); + + expect_truthy(isMatch('../a', '**/../*')); + expect_truthy(isMatch('../a', '../*')); + expect_truthy(isMatch('a/../a', '**/../*')); + expect_truthy(isMatch('a/../a', '*/../*')); + expect_truthy(isMatch('ab/../ac', '**/../*')); + expect_truthy(isMatch('ab/../ac', '*/../*')); + }); + }); + + describe('multiple directories', () => { + test('should not match a dot when the dot is not explicitly defined', () => { + expect_truthy(!isMatch('.dot', '*dot')); + expect_truthy(!isMatch('/.dot', '*/*dot')); + expect_truthy(!isMatch('.dot', '**/*dot')); + expect_truthy(!isMatch('.dot', '**/?dot')); + expect_truthy(!isMatch('.dot', '*/*dot')); + expect_truthy(!isMatch('.dot', '*/?dot')); + expect_truthy(!isMatch('.dot', '/*dot')); + expect_truthy(!isMatch('.dot', '/?dot')); + expect_truthy(!isMatch('/.dot', '**/*dot')); + expect_truthy(!isMatch('/.dot', '**/?dot')); + expect_truthy(!isMatch('/.dot', '*/?dot')); + expect_truthy(!isMatch('/.dot', '/*dot')); + expect_truthy(!isMatch('/.dot', '/?dot')); + expect_truthy(!isMatch('a/.dot', '*/*dot')); + expect_truthy(!isMatch('a/.dot', '*/?dot')); + expect_truthy(!isMatch('a/b/.dot', '**/*dot')); + expect_truthy(!isMatch('a/b/.dot', '**/?dot')); + + // related https://github.com/jonschlinkert/micromatch/issues/63 + expect_truthy(!isMatch('/aaa/bbb/.git', '/aaa/bbb/**')); + expect_truthy(!isMatch('aaa/bbb/.git', 'aaa/bbb/**')); + expect_truthy(!isMatch('/aaa/bbb/ccc/.git', '/aaa/bbb/**')); + expect_truthy(isMatch('/aaa/bbb/.git', '/aaa/bbb/**', { dot: true })); + expect_truthy(isMatch('aaa/bbb/.git', 'aaa/bbb/**', { dot: true })); + expect_truthy(isMatch('/aaa/bbb/ccc/.git', '/aaa/bbb/**', { dot: true })); + }); + }); + + describe('options.dot', () => { + test('should match dotfiles when `options.dot` is true', () => { + expect_truthy(isMatch('.dotfile.js', '.*.js', { dot: true })); + expect_truthy(isMatch('.dot', '*dot', { dot: true })); + expect_truthy(isMatch('.dot', '?dot', { dot: true })); + expect_truthy(isMatch('.dot', '[.]dot', { dot: true })); + expect_truthy(isMatch('/a/b/.dot', '**/*dot', { dot: true })); + expect_truthy(isMatch('/a/b/.dot', '**/.[d]ot', { dot: true })); + expect_truthy(isMatch('/a/b/.dot', '**/?dot', { dot: true })); + expect_truthy(isMatch('/a/b/.dot', '**/[.]dot', { dot: false })); + expect_truthy(isMatch('/a/b/.dot', '**/[.]dot', { dot: true })); + expect_truthy(isMatch('a/b/.dot', '**/*dot', { dot: true })); + expect_truthy(isMatch('a/b/.dot', '**/.[d]ot', { dot: true })); + expect_truthy(isMatch('a/b/.dot', '**/?dot', { dot: true })); + expect_truthy(isMatch('a/b/.dot', '**/[.]dot', { dot: false })); + expect_truthy(isMatch('a/b/.dot', '**/[.]dot', { dot: true })); + }); + + test('should match dotfiles when `.dot` and `.matchBase` both defined', () => { + expect_truthy(isMatch('a/b/.dot', '*dot', { dot: true, matchBase: true })); + expect_truthy(isMatch('a/b/.dot', '[.]dot', { dot: true, matchBase: true })); + expect_truthy(isMatch('a/b/.dot', '[.]dot', { dot: false, matchBase: true })); + expect_truthy(isMatch('a/b/.dot', '?dot', { dot: true, matchBase: true })); + }); + + test('should work when the path has leading `./`', () => { + let format = str => str.replace(/^\.\//, ''); + expect_truthy(!isMatch('./b/.c', '**', { format })); + expect_truthy(isMatch('./b/.c', '**', { format, dot: true })); + expect_truthy(isMatch('./b/.c', '**', { format, dot: true, matchBase: true })); + }); + + test('should not match dotfiles when `options.dot` is false', () => { + expect_truthy(!isMatch('a/b/.dot', '**/*dot', { dot: false })); + expect_truthy(!isMatch('a/b/.dot', '**/?dot', { dot: false })); + }); + + test('should not match dotfiles when `.dot` is false and `.matchBase` is true', () => { + expect_truthy(!isMatch('a/b/.dot', '*dot', { dot: false, matchBase: true })); + expect_truthy(!isMatch('a/b/.dot', '?dot', { dot: false, matchBase: true })); + }); + + test('should not match dotfiles when `.dot` is not defined and a dot is not in the glob pattern', () => { + expect_truthy(!isMatch('a/b/.dot', '*dot', { matchBase: true })); + expect_truthy(!isMatch('a/b/.dot', '?dot', { matchBase: true })); + expect_truthy(!isMatch('a/b/.dot', '**/*dot')); + expect_truthy(!isMatch('a/b/.dot', '**/?dot')); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/extglobs-bash.test.ts b/packages/node-utils/test/micromatch/extglobs-bash.test.ts new file mode 100644 index 0000000..8184f80 --- /dev/null +++ b/packages/node-utils/test/micromatch/extglobs-bash.test.ts @@ -0,0 +1,2645 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch, makeRe } = micromatch; + +if (!process.env.ORIGINAL_PATH_SEP) { + process.env.ORIGINAL_PATH_SEP = path.sep +} + +/** + * Some of tests were converted from bash 4.3, 4.4, and minimatch unit tests. + */ + +describe('extglobs (bash)', () => { + beforeEach(() => ((path as any).sep = '\\')); + afterEach(() => ((path as any).sep = process.env.ORIGINAL_PATH_SEP)); + + test('should not match empty string iwth "*(0|1|3|5|7|9)"', () => { + expect_truthy(!isMatch('', '*(0|1|3|5|7|9)', { bash: true })); + }); + + test('"*(a|b[)" should not match "*(a|b\\[)"', () => { + expect_truthy(!isMatch('*(a|b[)', '*(a|b\\[)', { bash: true })); + }); + + test('"*(a|b[)" should not match "\\*\\(a|b\\[\\)"', () => { + expect_truthy(!isMatch('*(a|b[)', '\\*\\(a|b\\[\\)', { bash: true })); + }); + + test('"***" should match "\\*\\*\\*"', () => { + expect_truthy(isMatch('***', '\\*\\*\\*', { bash: true })); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1" should not match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(!isMatch('-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*', { bash: true })); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1" should match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(isMatch('-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*', { bash: true })); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1" should not match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(!isMatch('-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*', { bash: true })); + }); + + test('"/dev/udp/129.22.8.102/45" should match "/dev\\/@(tcp|udp)\\/*\\/*"', () => { + expect_truthy(isMatch('/dev/udp/129.22.8.102/45', '/dev\\/@(tcp|udp)\\/*\\/*', { bash: true })); + }); + + test('"/x/y/z" should match "/x/y/z"', () => { + expect_truthy(isMatch('/x/y/z', '/x/y/z', { bash: true })); + }); + + test('"0377" should match "+([0-7])"', () => { + expect_truthy(isMatch('0377', '+([0-7])', { bash: true })); + }); + + test('"07" should match "+([0-7])"', () => { + expect_truthy(isMatch('07', '+([0-7])', { bash: true })); + }); + + test('"09" should not match "+([0-7])"', () => { + expect_truthy(!isMatch('09', '+([0-7])', { bash: true })); + }); + + test('"1" should match "0|[1-9]*([0-9])"', () => { + expect_truthy(isMatch('1', '0|[1-9]*([0-9])', { bash: true })); + }); + + test('"12" should match "0|[1-9]*([0-9])"', () => { + expect_truthy(isMatch('12', '0|[1-9]*([0-9])', { bash: true })); + }); + + test('"123abc" should not match "(a+|b)*"', () => { + expect_truthy(!isMatch('123abc', '(a+|b)*', { bash: true })); + }); + + test('"123abc" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('123abc', '(a+|b)+', { bash: true })); + }); + + test('"123abc" should match "*?(a)bc"', () => { + expect_truthy(isMatch('123abc', '*?(a)bc', { bash: true })); + }); + + test('"123abc" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('123abc', 'a(b*(foo|bar))d', { bash: true })); + }); + + test('"123abc" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*(e|f)', { bash: true })); + }); + + test('"123abc" should not match "ab**"', () => { + expect_truthy(!isMatch('123abc', 'ab**', { bash: true })); + }); + + test('"123abc" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab**(e|f)', { bash: true })); + }); + + test('"123abc" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('123abc', 'ab**(e|f)g', { bash: true })); + }); + + test('"123abc" should not match "ab***ef"', () => { + expect_truthy(!isMatch('123abc', 'ab***ef', { bash: true })); + }); + + test('"123abc" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*+(e|f)', { bash: true })); + }); + + test('"123abc" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*d+(e|f)', { bash: true })); + }); + + test('"123abc" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab?*(e|f)', { bash: true })); + }); + + test('"12abc" should not match "0|[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('12abc', '0|[1-9]*([0-9])', { bash: true })); + }); + + test('"137577991" should match "*(0|1|3|5|7|9)"', () => { + expect_truthy(isMatch('137577991', '*(0|1|3|5|7|9)', { bash: true })); + }); + + test('"2468" should not match "*(0|1|3|5|7|9)"', () => { + expect_truthy(!isMatch('2468', '*(0|1|3|5|7|9)', { bash: true })); + }); + + test('"?a?b" should match "\\??\\?b"', () => { + expect_truthy(isMatch('?a?b', '\\??\\?b', { bash: true })); + }); + + test('"\\a\\b\\c" should not match "abc"', () => { + expect_truthy(!isMatch('\\a\\b\\c', 'abc', { bash: true })); + }); + + test('"a" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"a" should not match "!(a)"', () => { + expect_truthy(!isMatch('a', '!(a)', { bash: true })); + }); + + test('"a" should not match "!(a)*"', () => { + expect_truthy(!isMatch('a', '!(a)*', { bash: true })); + }); + + test('"a" should match "(a)"', () => { + expect_truthy(isMatch('a', '(a)', { bash: true })); + }); + + test('"a" should not match "(b)"', () => { + expect_truthy(!isMatch('a', '(b)', { bash: true })); + }); + + test('"a" should match "*(a)"', () => { + expect_truthy(isMatch('a', '*(a)', { bash: true })); + }); + + test('"a" should match "+(a)"', () => { + expect_truthy(isMatch('a', '+(a)', { bash: true })); + }); + + test('"a" should match "?"', () => { + expect_truthy(isMatch('a', '?', { bash: true })); + }); + + test('"a" should match "?(a|b)"', () => { + expect_truthy(isMatch('a', '?(a|b)', { bash: true })); + }); + + test('"a" should not match "??"', () => { + expect_truthy(!isMatch('a', '??', { bash: true })); + }); + + test('"a" should match "a!(b)*"', () => { + expect_truthy(isMatch('a', 'a!(b)*', { bash: true })); + }); + + test('"a" should match "a?(a|b)"', () => { + expect_truthy(isMatch('a', 'a?(a|b)', { bash: true })); + }); + + test('"a" should match "a?(x)"', () => { + expect_truthy(isMatch('a', 'a?(x)', { bash: true })); + }); + + test('"a" should not match "a??b"', () => { + expect_truthy(!isMatch('a', 'a??b', { bash: true })); + }); + + test('"a" should not match "b?(a|b)"', () => { + expect_truthy(!isMatch('a', 'b?(a|b)', { bash: true })); + }); + + test('"a((((b" should match "a(*b"', () => { + expect_truthy(isMatch('a((((b', 'a(*b', { bash: true })); + }); + + test('"a((((b" should not match "a(b"', () => { + expect_truthy(!isMatch('a((((b', 'a(b', { bash: true })); + }); + + test('"a((((b" should not match "a\\(b"', () => { + expect_truthy(!isMatch('a((((b', 'a\\(b', { bash: true })); + }); + + test('"a((b" should match "a(*b"', () => { + expect_truthy(isMatch('a((b', 'a(*b', { bash: true })); + }); + + test('"a((b" should not match "a(b"', () => { + expect_truthy(!isMatch('a((b', 'a(b', { bash: true })); + }); + + test('"a((b" should not match "a\\(b"', () => { + expect_truthy(!isMatch('a((b', 'a\\(b', { bash: true })); + }); + + test('"a(b" should match "a(*b"', () => { + expect_truthy(isMatch('a(b', 'a(*b', { bash: true })); + }); + + test('"a(b" should match "a(b"', () => { + expect_truthy(isMatch('a(b', 'a(b', { bash: true })); + }); + + test('"a\\(b" should match "a\\(b"', () => { + expect_truthy(isMatch('a\\(b', 'a\\(b', { bash: true })); + }); + + test('"a(b" should match "a\\(b"', () => { + expect_truthy(isMatch('a(b', 'a\\(b', { bash: true })); + }); + + test('"a." should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"a." should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.', '*!(.a|.b|.c)', { bash: true })); + }); + + test('"a." should match "*.!(a)"', () => { + expect_truthy(isMatch('a.', '*.!(a)', { bash: true })); + }); + + test('"a." should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.', '*.!(a|b|c)', { bash: true })); + }); + + test('"a." should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true })); + }); + + test('"a." should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.', '*.+(b|d)', { bash: true })); + }); + + test('"a.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.a', '!(*.[a-b]*)', { bash: true })); + }); + + test('"a.a" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.a', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"a.a" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.a', '!(*[a-b].[a-b]*)', { bash: true })); + }); + + test('"a.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.a', '!*.(a|b)', { bash: true })); + }); + + test('"a.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.a', '!*.(a|b)*', { bash: true })); + }); + + test('"a.a" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.a', '(a|d).(a|b)*', { bash: true })); + }); + + test('"a.a" should match "(b|a).(a)"', () => { + expect_truthy(isMatch('a.a', '(b|a).(a)', { bash: true })); + }); + + test('"a.a" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.a', '*!(.a|.b|.c)', { bash: true })); + }); + + test('"a.a" should not match "*.!(a)"', () => { + expect_truthy(!isMatch('a.a', '*.!(a)', { bash: true })); + }); + + test('"a.a" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.a', '*.!(a|b|c)', { bash: true })); + }); + + test('"a.a" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.a', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true })); + }); + + test('"a.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.a', '*.+(b|d)', { bash: true })); + }); + + test('"a.a" should match "@(b|a).@(a)"', () => { + expect_truthy(isMatch('a.a', '@(b|a).@(a)', { bash: true })); + }); + + test('"a.a.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.a.a', '!(*.[a-b]*)', { bash: true })); + }); + + test('"a.a.a" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.a.a', '!(*[a-b].[a-b]*)', { bash: true })); + }); + + test('"a.a.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.a.a', '!*.(a|b)', { bash: true })); + }); + + test('"a.a.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.a.a', '!*.(a|b)*', { bash: true })); + }); + + test('"a.a.a" should match "*.!(a)"', () => { + expect_truthy(isMatch('a.a.a', '*.!(a)', { bash: true })); + }); + + test('"a.a.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.a.a', '*.+(b|d)', { bash: true })); + }); + + test('"a.aa.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('a.aa.a', '(b|a).(a)', { bash: true })); + }); + + test('"a.aa.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('a.aa.a', '@(b|a).@(a)', { bash: true })); + }); + + test('"a.abcd" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.abcd', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"a.abcd" should not match "!(*.a|*.b|*.c)*"', () => { + expect_truthy(!isMatch('a.abcd', '!(*.a|*.b|*.c)*', { bash: true })); + }); + + test('"a.abcd" should match "*!(*.a|*.b|*.c)*"', () => { + expect_truthy(isMatch('a.abcd', '*!(*.a|*.b|*.c)*', { bash: true })); + }); + + test('"a.abcd" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.abcd', '*!(.a|.b|.c)', { bash: true })); + }); + + test('"a.abcd" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.abcd', '*.!(a|b|c)', { bash: true })); + }); + + test('"a.abcd" should not match "*.!(a|b|c)*"', () => { + expect_truthy(!isMatch('a.abcd', '*.!(a|b|c)*', { bash: true })); + }); + + test('"a.abcd" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.abcd', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true })); + }); + + test('"a.b" should not match "!(*.*)"', () => { + expect_truthy(!isMatch('a.b', '!(*.*)', { bash: true })); + }); + + test('"a.b" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.b', '!(*.[a-b]*)', { bash: true })); + }); + + test('"a.b" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.b', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"a.b" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.b', '!(*[a-b].[a-b]*)', { bash: true })); + }); + + test('"a.b" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.b', '!*.(a|b)', { bash: true })); + }); + + test('"a.b" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.b', '!*.(a|b)*', { bash: true })); + }); + + test('"a.b" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.b', '(a|d).(a|b)*', { bash: true })); + }); + + test('"a.b" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.b', '*!(.a|.b|.c)', { bash: true })); + }); + + test('"a.b" should match "*.!(a)"', () => { + expect_truthy(isMatch('a.b', '*.!(a)', { bash: true })); + }); + + test('"a.b" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.b', '*.!(a|b|c)', { bash: true })); + }); + + test('"a.b" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.b', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true })); + }); + + test('"a.b" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('a.b', '*.+(b|d)', { bash: true })); + }); + + test('"a.bb" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.bb', '!(*.[a-b]*)', { bash: true })); + }); + + test('"a.bb" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.bb', '!(*[a-b].[a-b]*)', { bash: true })); + }); + + test('"a.bb" should match "!*.(a|b)"', () => { + expect_truthy(isMatch('a.bb', '!*.(a|b)', { bash: true })); + }); + + test('"a.bb" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.bb', '!*.(a|b)*', { bash: true })); + }); + + test('"a.bb" should not match "!*.*(a|b)"', () => { + expect_truthy(!isMatch('a.bb', '!*.*(a|b)', { bash: true })); + }); + + test('"a.bb" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.bb', '(a|d).(a|b)*', { bash: true })); + }); + + test('"a.bb" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('a.bb', '(b|a).(a)', { bash: true })); + }); + + test('"a.bb" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('a.bb', '*.+(b|d)', { bash: true })); + }); + + test('"a.bb" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('a.bb', '@(b|a).@(a)', { bash: true })); + }); + + test('"a.c" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.c', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"a.c" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.c', '*!(.a|.b|.c)', { bash: true })); + }); + + test('"a.c" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.c', '*.!(a|b|c)', { bash: true })); + }); + + test('"a.c" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.c', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true })); + }); + + test('"a.c.d" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.c.d', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"a.c.d" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.c.d', '*!(.a|.b|.c)', { bash: true })); + }); + + test('"a.c.d" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.c.d', '*.!(a|b|c)', { bash: true })); + }); + + test('"a.c.d" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.c.d', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true })); + }); + + test('"a.ccc" should match "!(*.[a-b]*)"', () => { + expect_truthy(isMatch('a.ccc', '!(*.[a-b]*)', { bash: true })); + }); + + test('"a.ccc" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('a.ccc', '!(*[a-b].[a-b]*)', { bash: true })); + }); + + test('"a.ccc" should match "!*.(a|b)"', () => { + expect_truthy(isMatch('a.ccc', '!*.(a|b)', { bash: true })); + }); + + test('"a.ccc" should match "!*.(a|b)*"', () => { + expect_truthy(isMatch('a.ccc', '!*.(a|b)*', { bash: true })); + }); + + test('"a.ccc" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.ccc', '*.+(b|d)', { bash: true })); + }); + + test('"a.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('a.js', '!(*.js)', { bash: true })); + }); + + test('"a.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.js', '*!(.js)', { bash: true })); + }); + + test('"a.js" should not match "*.!(js)"', () => { + expect_truthy(!isMatch('a.js', '*.!(js)', { bash: true })); + }); + + test('"a.js" should not match "a.!(js)"', () => { + expect_truthy(!isMatch('a.js', 'a.!(js)', { bash: true })); + }); + + test('"a.js" should not match "a.!(js)*"', () => { + expect_truthy(!isMatch('a.js', 'a.!(js)*', { bash: true })); + }); + + test('"a.js.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('a.js.js', '!(*.js)', { bash: true })); + }); + + test('"a.js.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.js.js', '*!(.js)', { bash: true })); + }); + + test('"a.js.js" should match "*.!(js)"', () => { + expect_truthy(isMatch('a.js.js', '*.!(js)', { bash: true })); + }); + + test('"a.js.js" should match "*.*(js).js"', () => { + expect_truthy(isMatch('a.js.js', '*.*(js).js', { bash: true })); + }); + + test('"a.md" should match "!(*.js)"', () => { + expect_truthy(isMatch('a.md', '!(*.js)', { bash: true })); + }); + + test('"a.md" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.md', '*!(.js)', { bash: true })); + }); + + test('"a.md" should match "*.!(js)"', () => { + expect_truthy(isMatch('a.md', '*.!(js)', { bash: true })); + }); + + test('"a.md" should match "a.!(js)"', () => { + expect_truthy(isMatch('a.md', 'a.!(js)', { bash: true })); + }); + + test('"a.md" should match "a.!(js)*"', () => { + expect_truthy(isMatch('a.md', 'a.!(js)*', { bash: true })); + }); + + test('"a.md.js" should not match "*.*(js).js"', () => { + expect_truthy(!isMatch('a.md.js', '*.*(js).js', { bash: true })); + }); + + test('"a.txt" should match "a.!(js)"', () => { + expect_truthy(isMatch('a.txt', 'a.!(js)', { bash: true })); + }); + + test('"a.txt" should match "a.!(js)*"', () => { + expect_truthy(isMatch('a.txt', 'a.!(js)*', { bash: true })); + }); + + test('"a/!(z)" should match "a/!(z)"', () => { + expect_truthy(isMatch('a/!(z)', 'a/!(z)', { bash: true })); + }); + + test('"a/b" should match "a/!(z)"', () => { + expect_truthy(isMatch('a/b', 'a/!(z)', { bash: true })); + }); + + test('"a/b/c.txt" should not match "*/b/!(*).txt"', () => { + expect_truthy(!isMatch('a/b/c.txt', '*/b/!(*).txt', { bash: true })); + }); + + test('"a/b/c.txt" should not match "*/b/!(c).txt"', () => { + expect_truthy(!isMatch('a/b/c.txt', '*/b/!(c).txt', { bash: true })); + }); + + test('"a/b/c.txt" should match "*/b/!(cc).txt"', () => { + expect_truthy(isMatch('a/b/c.txt', '*/b/!(cc).txt', { bash: true })); + }); + + test('"a/b/cc.txt" should not match "*/b/!(*).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(*).txt', { bash: true })); + }); + + test('"a/b/cc.txt" should not match "*/b/!(c).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(c).txt', { bash: true })); + }); + + test('"a/b/cc.txt" should not match "*/b/!(cc).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(cc).txt', { bash: true })); + }); + + test('"a/dir/foo.txt" should match "*/dir/**/!(bar).txt"', () => { + expect_truthy(isMatch('a/dir/foo.txt', '*/dir/**/!(bar).txt', { bash: true })); + }); + + test('"a/z" should not match "a/!(z)"', () => { + expect_truthy(!isMatch('a/z', 'a/!(z)', { bash: true })); + }); + + test('"a\\(b" should not match "a(*b"', () => { + expect_truthy(!isMatch('a\\(b', 'a(*b', { bash: true })); + }); + + test('"a\\(b" should not match "a(b"', () => { + expect_truthy(!isMatch('a\\(b', 'a(b', { bash: true })); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\\\z', 'a\\\\z', { bash: true, windows: false })); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\\\z', 'a\\\\z', { bash: true })); + }); + + test('"a\\b" should match "a/b"', () => { + expect_truthy(isMatch('a\\b', 'a/b', { windows: true })); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\\\z', 'a\\\\z', { bash: true })); + expect_truthy(isMatch('a\\z', 'a\\z', { bash: true })); + }); + + test('"a\\z" should not match "a\\z"', () => { + expect_truthy(isMatch('a\\z', 'a\\z', { bash: true })); + }); + + test('"aa" should not match "!(a!(b))"', () => { + expect_truthy(!isMatch('aa', '!(a!(b))', { bash: true })); + }); + + test('"aa" should match "!(a)"', () => { + expect_truthy(isMatch('aa', '!(a)', { bash: true })); + }); + + test('"aa" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aa', '!(a)*', { bash: true })); + }); + + test('"aa" should not match "?"', () => { + expect_truthy(!isMatch('aa', '?', { bash: true })); + }); + + test('"aa" should not match "@(a)b"', () => { + expect_truthy(!isMatch('aa', '@(a)b', { bash: true })); + }); + + test('"aa" should match "a!(b)*"', () => { + expect_truthy(isMatch('aa', 'a!(b)*', { bash: true })); + }); + + test('"aa" should not match "a??b"', () => { + expect_truthy(!isMatch('aa', 'a??b', { bash: true })); + }); + + test('"aa.aa" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('aa.aa', '(b|a).(a)', { bash: true })); + }); + + test('"aa.aa" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('aa.aa', '@(b|a).@(a)', { bash: true })); + }); + + test('"aaa" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aaa', '!(a)*', { bash: true })); + }); + + test('"aaa" should match "a!(b)*"', () => { + expect_truthy(isMatch('aaa', 'a!(b)*', { bash: true })); + }); + + test('"aaaaaaabababab" should match "*ab"', () => { + expect_truthy(isMatch('aaaaaaabababab', '*ab', { bash: true })); + }); + + test('"aaac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('aaac', '*(@(a))a@(c)', { bash: true })); + }); + + test('"aaaz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('aaaz', '[a*(]*z', { bash: true })); + }); + + test('"aab" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aab', '!(a)*', { bash: true })); + }); + + test('"aab" should not match "?"', () => { + expect_truthy(!isMatch('aab', '?', { bash: true })); + }); + + test('"aab" should not match "??"', () => { + expect_truthy(!isMatch('aab', '??', { bash: true })); + }); + + test('"aab" should not match "@(c)b"', () => { + expect_truthy(!isMatch('aab', '@(c)b', { bash: true })); + }); + + test('"aab" should match "a!(b)*"', () => { + expect_truthy(isMatch('aab', 'a!(b)*', { bash: true })); + }); + + test('"aab" should not match "a??b"', () => { + expect_truthy(!isMatch('aab', 'a??b', { bash: true })); + }); + + test('"aac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('aac', '*(@(a))a@(c)', { bash: true })); + }); + + test('"aac" should not match "*(@(a))b@(c)"', () => { + expect_truthy(!isMatch('aac', '*(@(a))b@(c)', { bash: true })); + }); + + test('"aax" should not match "a!(a*|b)"', () => { + expect_truthy(!isMatch('aax', 'a!(a*|b)', { bash: true })); + }); + + test('"aax" should match "a!(x*|b)"', () => { + expect_truthy(isMatch('aax', 'a!(x*|b)', { bash: true })); + }); + + test('"aax" should match "a?(a*|b)"', () => { + expect_truthy(isMatch('aax', 'a?(a*|b)', { bash: true })); + }); + + test('"aaz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('aaz', '[a*(]*z', { bash: true })); + }); + + test('"ab" should match "!(*.*)"', () => { + expect_truthy(isMatch('ab', '!(*.*)', { bash: true })); + }); + + test('"ab" should match "!(a!(b))"', () => { + expect_truthy(isMatch('ab', '!(a!(b))', { bash: true })); + }); + + test('"ab" should not match "!(a)*"', () => { + expect_truthy(!isMatch('ab', '!(a)*', { bash: true })); + }); + + test('"ab" should match "@(a+|b)*"', () => { + expect_truthy(isMatch('ab', '@(a+|b)*', { bash: true })); + }); + + test('"ab" should match "(a+|b)+"', () => { + expect_truthy(isMatch('ab', '(a+|b)+', { bash: true })); + }); + + test('"ab" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('ab', '*?(a)bc', { bash: true })); + }); + + test('"ab" should not match "a!(*(b|B))"', () => { + expect_truthy(!isMatch('ab', 'a!(*(b|B))', { bash: true })); + }); + + test('"ab" should not match "a!(@(b|B))"', () => { + expect_truthy(!isMatch('ab', 'a!(@(b|B))', { bash: true })); + }); + + test('"aB" should not match "a!(@(b|B))"', () => { + expect_truthy(!isMatch('aB', 'a!(@(b|B))', { bash: true })); + }); + + test('"ab" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('ab', 'a!(b)*', { bash: true })); + }); + + test('"ab" should not match "a(*b"', () => { + expect_truthy(!isMatch('ab', 'a(*b', { bash: true })); + }); + + test('"ab" should not match "a(b"', () => { + expect_truthy(!isMatch('ab', 'a(b', { bash: true })); + }); + + test('"ab" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('ab', 'a(b*(foo|bar))d', { bash: true })); + }); + + test('"ab" should not match "a/b"', () => { + expect_truthy(!isMatch('ab', 'a/b', { windows: true })); + }); + + test('"ab" should not match "a\\(b"', () => { + expect_truthy(!isMatch('ab', 'a\\(b', { bash: true })); + }); + + test('"ab" should match "ab*(e|f)"', () => { + expect_truthy(isMatch('ab', 'ab*(e|f)', { bash: true })); + }); + + test('"ab" should match "ab**"', () => { + expect_truthy(isMatch('ab', 'ab**', { bash: true })); + }); + + test('"ab" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('ab', 'ab**(e|f)', { bash: true })); + }); + + test('"ab" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('ab', 'ab**(e|f)g', { bash: true })); + }); + + test('"ab" should not match "ab***ef"', () => { + expect_truthy(!isMatch('ab', 'ab***ef', { bash: true })); + }); + + test('"ab" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab*+(e|f)', { bash: true })); + }); + + test('"ab" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab*d+(e|f)', { bash: true })); + }); + + test('"ab" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab?*(e|f)', { bash: true })); + }); + + test('"ab/cXd/efXg/hi" should match "**/*X*/**/*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '**/*X*/**/*i', { bash: true })); + }); + + test('"ab/cXd/efXg/hi" should match "*/*X*/*/*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '*/*X*/*/*i', { bash: true })); + }); + + test('"ab/cXd/efXg/hi" should match "*X*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '*X*i', { bash: true })); + }); + + test('"ab/cXd/efXg/hi" should match "*Xg*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '*Xg*i', { bash: true })); + }); + + test('"ab]" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('ab]', 'a!(@(b|B))', { bash: true })); + }); + + test('"abab" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abab', '(a+|b)*', { bash: true })); + }); + + test('"abab" should match "(a+|b)+"', () => { + expect_truthy(isMatch('abab', '(a+|b)+', { bash: true })); + }); + + test('"abab" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abab', '*?(a)bc', { bash: true })); + }); + + test('"abab" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abab', 'a(b*(foo|bar))d', { bash: true })); + }); + + test('"abab" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*(e|f)', { bash: true })); + }); + + test('"abab" should match "ab**"', () => { + expect_truthy(isMatch('abab', 'ab**', { bash: true })); + }); + + test('"abab" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abab', 'ab**(e|f)', { bash: true })); + }); + + test('"abab" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abab', 'ab**(e|f)g', { bash: true })); + }); + + test('"abab" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abab', 'ab***ef', { bash: true })); + }); + + test('"abab" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*+(e|f)', { bash: true })); + }); + + test('"abab" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*d+(e|f)', { bash: true })); + }); + + test('"abab" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab?*(e|f)', { bash: true })); + }); + + test('"abb" should match "!(*.*)"', () => { + expect_truthy(isMatch('abb', '!(*.*)', { bash: true })); + }); + + test('"abb" should not match "!(a)*"', () => { + expect_truthy(!isMatch('abb', '!(a)*', { bash: true })); + }); + + test('"abb" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('abb', 'a!(b)*', { bash: true })); + }); + + test('"abbcd" should match "@(ab|a*(b))*(c)d"', () => { + expect_truthy(isMatch('abbcd', '@(ab|a*(b))*(c)d', { bash: true })); + }); + + test('"abc" should not match "\\a\\b\\c"', () => { + expect_truthy(!isMatch('abc', '\\a\\b\\c', { bash: true })); + }); + + test('"aBc" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('aBc', 'a!(@(b|B))', { bash: true })); + }); + + test('"abcd" should match "?@(a|b)*@(c)d"', () => { + expect_truthy(isMatch('abcd', '?@(a|b)*@(c)d', { bash: true })); + }); + + test('"abcd" should match "@(ab|a*@(b))*(c)d"', () => { + expect_truthy(isMatch('abcd', '@(ab|a*@(b))*(c)d', { bash: true })); + }); + + test('"abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt" should match "**/*a*b*g*n*t"', () => { + expect_truthy(isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt', '**/*a*b*g*n*t', { bash: true })); + }); + + test('"abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz" should not match "**/*a*b*g*n*t"', () => { + expect_truthy(!isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz', '**/*a*b*g*n*t', { bash: true })); + }); + + test('"abcdef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcdef', '(a+|b)*', { bash: true })); + }); + + test('"abcdef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcdef', '(a+|b)+', { bash: true })); + }); + + test('"abcdef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcdef', '*?(a)bc', { bash: true })); + }); + + test('"abcdef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcdef', 'a(b*(foo|bar))d', { bash: true })); + }); + + test('"abcdef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcdef', 'ab*(e|f)', { bash: true })); + }); + + test('"abcdef" should match "ab**"', () => { + expect_truthy(isMatch('abcdef', 'ab**', { bash: true })); + }); + + test('"abcdef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab**(e|f)', { bash: true })); + }); + + test('"abcdef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abcdef', 'ab**(e|f)g', { bash: true })); + }); + + test('"abcdef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcdef', 'ab***ef', { bash: true })); + }); + + test('"abcdef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab*+(e|f)', { bash: true })); + }); + + test('"abcdef" should match "ab*d+(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab*d+(e|f)', { bash: true })); + }); + + test('"abcdef" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abcdef', 'ab?*(e|f)', { bash: true })); + }); + + test('"abcfef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcfef', '(a+|b)*', { bash: true })); + }); + + test('"abcfef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcfef', '(a+|b)+', { bash: true })); + }); + + test('"abcfef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcfef', '*?(a)bc', { bash: true })); + }); + + test('"abcfef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcfef', 'a(b*(foo|bar))d', { bash: true })); + }); + + test('"abcfef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcfef', 'ab*(e|f)', { bash: true })); + }); + + test('"abcfef" should match "ab**"', () => { + expect_truthy(isMatch('abcfef', 'ab**', { bash: true })); + }); + + test('"abcfef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab**(e|f)', { bash: true })); + }); + + test('"abcfef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abcfef', 'ab**(e|f)g', { bash: true })); + }); + + test('"abcfef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcfef', 'ab***ef', { bash: true })); + }); + + test('"abcfef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab*+(e|f)', { bash: true })); + }); + + test('"abcfef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abcfef', 'ab*d+(e|f)', { bash: true })); + }); + + test('"abcfef" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab?*(e|f)', { bash: true })); + }); + + test('"abcfefg" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcfefg', '(a+|b)*', { bash: true })); + }); + + test('"abcfefg" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcfefg', '(a+|b)+', { bash: true })); + }); + + test('"abcfefg" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcfefg', '*?(a)bc', { bash: true })); + }); + + test('"abcfefg" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcfefg', 'a(b*(foo|bar))d', { bash: true })); + }); + + test('"abcfefg" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*(e|f)', { bash: true })); + }); + + test('"abcfefg" should match "ab**"', () => { + expect_truthy(isMatch('abcfefg', 'ab**', { bash: true })); + }); + + test('"abcfefg" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcfefg', 'ab**(e|f)', { bash: true })); + }); + + test('"abcfefg" should match "ab**(e|f)g"', () => { + expect_truthy(isMatch('abcfefg', 'ab**(e|f)g', { bash: true })); + }); + + test('"abcfefg" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abcfefg', 'ab***ef', { bash: true })); + }); + + test('"abcfefg" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*+(e|f)', { bash: true })); + }); + + test('"abcfefg" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*d+(e|f)', { bash: true })); + }); + + test('"abcfefg" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab?*(e|f)', { bash: true })); + }); + + test('"abcx" should match "!([[*])*"', () => { + expect_truthy(isMatch('abcx', '!([[*])*', { bash: true })); + }); + + test('"abcx" should match "+(a|b\\[)*"', () => { + expect_truthy(isMatch('abcx', '+(a|b\\[)*', { bash: true })); + }); + + test('"abcx" should not match "[a*(]*z"', () => { + expect_truthy(!isMatch('abcx', '[a*(]*z', { bash: true })); + }); + + test('"abcXdefXghi" should match "*X*i"', () => { + expect_truthy(isMatch('abcXdefXghi', '*X*i', { bash: true })); + }); + + test('"abcz" should match "!([[*])*"', () => { + expect_truthy(isMatch('abcz', '!([[*])*', { bash: true })); + }); + + test('"abcz" should match "+(a|b\\[)*"', () => { + expect_truthy(isMatch('abcz', '+(a|b\\[)*', { bash: true })); + }); + + test('"abcz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('abcz', '[a*(]*z', { bash: true })); + }); + + test('"abd" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abd', '(a+|b)*', { bash: true })); + }); + + test('"abd" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abd', '(a+|b)+', { bash: true })); + }); + + test('"abd" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abd', '*?(a)bc', { bash: true })); + }); + + test('"abd" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('abd', 'a!(*(b|B))', { bash: true })); + }); + + test('"abd" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('abd', 'a!(@(b|B))', { bash: true })); + }); + + test('"abd" should not match "a!(@(b|B))d"', () => { + expect_truthy(!isMatch('abd', 'a!(@(b|B))d', { bash: true })); + }); + + test('"abd" should match "a(b*(foo|bar))d"', () => { + expect_truthy(isMatch('abd', 'a(b*(foo|bar))d', { bash: true })); + }); + + test('"abd" should match "a+(b|c)d"', () => { + expect_truthy(isMatch('abd', 'a+(b|c)d', { bash: true })); + }); + + test('"abd" should match "a[b*(foo|bar)]d"', () => { + expect_truthy(isMatch('abd', 'a[b*(foo|bar)]d', { bash: true })); + }); + + test('"abd" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*(e|f)', { bash: true })); + }); + + test('"abd" should match "ab**"', () => { + expect_truthy(isMatch('abd', 'ab**', { bash: true })); + }); + + test('"abd" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abd', 'ab**(e|f)', { bash: true })); + }); + + test('"abd" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abd', 'ab**(e|f)g', { bash: true })); + }); + + test('"abd" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abd', 'ab***ef', { bash: true })); + }); + + test('"abd" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*+(e|f)', { bash: true })); + }); + + test('"abd" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*d+(e|f)', { bash: true })); + }); + + test('"abd" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abd', 'ab?*(e|f)', { bash: true })); + }); + + test('"abef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abef', '(a+|b)*', { bash: true })); + }); + + test('"abef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abef', '(a+|b)+', { bash: true })); + }); + + test('"abef" should not match "*(a+|b)"', () => { + expect_truthy(!isMatch('abef', '*(a+|b)', { bash: true })); + }); + + test('"abef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abef', '*?(a)bc', { bash: true })); + }); + + test('"abef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abef', 'a(b*(foo|bar))d', { bash: true })); + }); + + test('"abef" should match "ab*(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab*(e|f)', { bash: true })); + }); + + test('"abef" should match "ab**"', () => { + expect_truthy(isMatch('abef', 'ab**', { bash: true })); + }); + + test('"abef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab**(e|f)', { bash: true })); + }); + + test('"abef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abef', 'ab**(e|f)g', { bash: true })); + }); + + test('"abef" should match "ab***ef"', () => { + expect_truthy(isMatch('abef', 'ab***ef', { bash: true })); + }); + + test('"abef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab*+(e|f)', { bash: true })); + }); + + test('"abef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abef', 'ab*d+(e|f)', { bash: true })); + }); + + test('"abef" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab?*(e|f)', { bash: true })); + }); + + test('"abz" should not match "a!(*)"', () => { + expect_truthy(!isMatch('abz', 'a!(*)', { bash: true })); + }); + + test('"abz" should match "a!(z)"', () => { + expect_truthy(isMatch('abz', 'a!(z)', { bash: true })); + }); + + test('"abz" should match "a*!(z)"', () => { + expect_truthy(isMatch('abz', 'a*!(z)', { bash: true })); + }); + + test('"abz" should not match "a*(z)"', () => { + expect_truthy(!isMatch('abz', 'a*(z)', { bash: true })); + }); + + test('"abz" should match "a**(z)"', () => { + expect_truthy(isMatch('abz', 'a**(z)', { bash: true })); + }); + + test('"abz" should match "a*@(z)"', () => { + expect_truthy(isMatch('abz', 'a*@(z)', { bash: true })); + }); + + test('"abz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('abz', 'a+(z)', { bash: true })); + }); + + test('"abz" should not match "a?(z)"', () => { + expect_truthy(!isMatch('abz', 'a?(z)', { bash: true })); + }); + + test('"abz" should not match "a@(z)"', () => { + expect_truthy(!isMatch('abz', 'a@(z)', { bash: true })); + }); + + test('"ac" should not match "!(a)*"', () => { + expect_truthy(!isMatch('ac', '!(a)*', { bash: true })); + }); + + test('"ac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('ac', '*(@(a))a@(c)', { bash: true })); + }); + + test('"ac" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('ac', 'a!(*(b|B))', { bash: true })); + }); + + test('"ac" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('ac', 'a!(@(b|B))', { bash: true })); + }); + + test('"ac" should match "a!(b)*"', () => { + expect_truthy(isMatch('ac', 'a!(b)*', { bash: true })); + }); + + test('"accdef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('accdef', '(a+|b)*', { bash: true })); + }); + + test('"accdef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('accdef', '(a+|b)+', { bash: true })); + }); + + test('"accdef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('accdef', '*?(a)bc', { bash: true })); + }); + + test('"accdef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('accdef', 'a(b*(foo|bar))d', { bash: true })); + }); + + test('"accdef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*(e|f)', { bash: true })); + }); + + test('"accdef" should not match "ab**"', () => { + expect_truthy(!isMatch('accdef', 'ab**', { bash: true })); + }); + + test('"accdef" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab**(e|f)', { bash: true })); + }); + + test('"accdef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('accdef', 'ab**(e|f)g', { bash: true })); + }); + + test('"accdef" should not match "ab***ef"', () => { + expect_truthy(!isMatch('accdef', 'ab***ef', { bash: true })); + }); + + test('"accdef" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*+(e|f)', { bash: true })); + }); + + test('"accdef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*d+(e|f)', { bash: true })); + }); + + test('"accdef" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab?*(e|f)', { bash: true })); + }); + + test('"acd" should match "(a+|b)*"', () => { + expect_truthy(isMatch('acd', '(a+|b)*', { bash: true })); + }); + + test('"acd" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('acd', '(a+|b)+', { bash: true })); + }); + + test('"acd" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('acd', '*?(a)bc', { bash: true })); + }); + + test('"acd" should match "@(ab|a*(b))*(c)d"', () => { + expect_truthy(isMatch('acd', '@(ab|a*(b))*(c)d', { bash: true })); + }); + + test('"acd" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('acd', 'a!(*(b|B))', { bash: true })); + }); + + test('"acd" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('acd', 'a!(@(b|B))', { bash: true })); + }); + + test('"acd" should match "a!(@(b|B))d"', () => { + expect_truthy(isMatch('acd', 'a!(@(b|B))d', { bash: true })); + }); + + test('"acd" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('acd', 'a(b*(foo|bar))d', { bash: true })); + }); + + test('"acd" should match "a+(b|c)d"', () => { + expect_truthy(isMatch('acd', 'a+(b|c)d', { bash: true })); + }); + + test('"acd" should not match "a[b*(foo|bar)]d"', () => { + expect_truthy(!isMatch('acd', 'a[b*(foo|bar)]d', { bash: true })); + }); + + test('"acd" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*(e|f)', { bash: true })); + }); + + test('"acd" should not match "ab**"', () => { + expect_truthy(!isMatch('acd', 'ab**', { bash: true })); + }); + + test('"acd" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab**(e|f)', { bash: true })); + }); + + test('"acd" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('acd', 'ab**(e|f)g', { bash: true })); + }); + + test('"acd" should not match "ab***ef"', () => { + expect_truthy(!isMatch('acd', 'ab***ef', { bash: true })); + }); + + test('"acd" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*+(e|f)', { bash: true })); + }); + + test('"acd" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*d+(e|f)', { bash: true })); + }); + + test('"acd" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab?*(e|f)', { bash: true })); + }); + + test('"ax" should match "?(a*|b)"', () => { + expect_truthy(isMatch('ax', '?(a*|b)', { bash: true })); + }); + + test('"ax" should not match "a?(b*)"', () => { + expect_truthy(!isMatch('ax', 'a?(b*)', { bash: true })); + }); + + test('"axz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('axz', 'a+(z)', { bash: true })); + }); + + test('"az" should not match "a!(*)"', () => { + expect_truthy(!isMatch('az', 'a!(*)', { bash: true })); + }); + + test('"az" should not match "a!(z)"', () => { + expect_truthy(!isMatch('az', 'a!(z)', { bash: true })); + }); + + test('"az" should match "a*!(z)"', () => { + expect_truthy(isMatch('az', 'a*!(z)', { bash: true })); + }); + + test('"az" should match "a*(z)"', () => { + expect_truthy(isMatch('az', 'a*(z)', { bash: true })); + }); + + test('"az" should match "a**(z)"', () => { + expect_truthy(isMatch('az', 'a**(z)', { bash: true })); + }); + + test('"az" should match "a*@(z)"', () => { + expect_truthy(isMatch('az', 'a*@(z)', { bash: true })); + }); + + test('"az" should match "a+(z)"', () => { + expect_truthy(isMatch('az', 'a+(z)', { bash: true })); + }); + + test('"az" should match "a?(z)"', () => { + expect_truthy(isMatch('az', 'a?(z)', { bash: true })); + }); + + test('"az" should match "a@(z)"', () => { + expect_truthy(isMatch('az', 'a@(z)', { bash: true })); + }); + + test('"az" should not match "a\\z"', () => { + expect_truthy(!isMatch('az', 'a\\\\z', { bash: true, windows: false })); + }); + + test('"az" should not match "a\\z"', () => { + expect_truthy(!isMatch('az', 'a\\\\z', { bash: true })); + }); + + test('"b" should match "!(a)*"', () => { + expect_truthy(isMatch('b', '!(a)*', { bash: true })); + }); + + test('"b" should match "(a+|b)*"', () => { + expect_truthy(isMatch('b', '(a+|b)*', { bash: true })); + }); + + test('"b" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('b', 'a!(b)*', { bash: true })); + }); + + test('"b.a" should match "(b|a).(a)"', () => { + expect_truthy(isMatch('b.a', '(b|a).(a)', { bash: true })); + }); + + test('"b.a" should match "@(b|a).@(a)"', () => { + expect_truthy(isMatch('b.a', '@(b|a).@(a)', { bash: true })); + }); + + test('"b/a" should not match "!(b/a)"', () => { + expect_truthy(!isMatch('b/a', '!(b/a)', { bash: true })); + }); + + test('"b/b" should match "!(b/a)"', () => { + expect_truthy(isMatch('b/b', '!(b/a)', { bash: true })); + }); + + test('"b/c" should match "!(b/a)"', () => { + expect_truthy(isMatch('b/c', '!(b/a)', { bash: true })); + }); + + test('"b/c" should not match "b/!(c)"', () => { + expect_truthy(!isMatch('b/c', 'b/!(c)', { bash: true })); + }); + + test('"b/c" should match "b/!(cc)"', () => { + expect_truthy(isMatch('b/c', 'b/!(cc)', { bash: true })); + }); + + test('"b/c.txt" should not match "b/!(c).txt"', () => { + expect_truthy(!isMatch('b/c.txt', 'b/!(c).txt', { bash: true })); + }); + + test('"b/c.txt" should match "b/!(cc).txt"', () => { + expect_truthy(isMatch('b/c.txt', 'b/!(cc).txt', { bash: true })); + }); + + test('"b/cc" should match "b/!(c)"', () => { + expect_truthy(isMatch('b/cc', 'b/!(c)', { bash: true })); + }); + + test('"b/cc" should not match "b/!(cc)"', () => { + expect_truthy(!isMatch('b/cc', 'b/!(cc)', { bash: true })); + }); + + test('"b/cc.txt" should not match "b/!(c).txt"', () => { + expect_truthy(!isMatch('b/cc.txt', 'b/!(c).txt', { bash: true })); + }); + + test('"b/cc.txt" should not match "b/!(cc).txt"', () => { + expect_truthy(!isMatch('b/cc.txt', 'b/!(cc).txt', { bash: true })); + }); + + test('"b/ccc" should match "b/!(c)"', () => { + expect_truthy(isMatch('b/ccc', 'b/!(c)', { bash: true })); + }); + + test('"ba" should match "!(a!(b))"', () => { + expect_truthy(isMatch('ba', '!(a!(b))', { bash: true })); + }); + + test('"ba" should match "b?(a|b)"', () => { + expect_truthy(isMatch('ba', 'b?(a|b)', { bash: true })); + }); + + test('"baaac" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('baaac', '*(@(a))a@(c)', { bash: true })); + }); + + test('"bar" should match "!(foo)"', () => { + expect_truthy(isMatch('bar', '!(foo)', { bash: true })); + }); + + test('"bar" should match "!(foo)*"', () => { + expect_truthy(isMatch('bar', '!(foo)*', { bash: true })); + }); + + test('"bar" should match "!(foo)b*"', () => { + expect_truthy(isMatch('bar', '!(foo)b*', { bash: true })); + }); + + test('"bar" should match "*(!(foo))"', () => { + expect_truthy(isMatch('bar', '*(!(foo))', { bash: true })); + }); + + test('"baz" should match "!(foo)*"', () => { + expect_truthy(isMatch('baz', '!(foo)*', { bash: true })); + }); + + test('"baz" should match "!(foo)b*"', () => { + expect_truthy(isMatch('baz', '!(foo)b*', { bash: true })); + }); + + test('"baz" should match "*(!(foo))"', () => { + expect_truthy(isMatch('baz', '*(!(foo))', { bash: true })); + }); + + test('"bb" should match "!(a!(b))"', () => { + expect_truthy(isMatch('bb', '!(a!(b))', { bash: true })); + }); + + test('"bb" should match "!(a)*"', () => { + expect_truthy(isMatch('bb', '!(a)*', { bash: true })); + }); + + test('"bb" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('bb', 'a!(b)*', { bash: true })); + }); + + test('"bb" should not match "a?(a|b)"', () => { + expect_truthy(!isMatch('bb', 'a?(a|b)', { bash: true })); + }); + + test('"bbc" should match "!([[*])*"', () => { + expect_truthy(isMatch('bbc', '!([[*])*', { bash: true })); + }); + + test('"bbc" should not match "+(a|b\\[)*"', () => { + expect_truthy(!isMatch('bbc', '+(a|b\\[)*', { bash: true })); + }); + + test('"bbc" should not match "[a*(]*z"', () => { + expect_truthy(!isMatch('bbc', '[a*(]*z', { bash: true })); + }); + + test('"bz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('bz', 'a+(z)', { bash: true })); + }); + + test('"c" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('c', '*(@(a))a@(c)', { bash: true })); + }); + + test('"c.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('c.a', '!(*.[a-b]*)', { bash: true })); + }); + + test('"c.a" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('c.a', '!(*[a-b].[a-b]*)', { bash: true })); + }); + + test('"c.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('c.a', '!*.(a|b)', { bash: true })); + }); + + test('"c.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('c.a', '!*.(a|b)*', { bash: true })); + }); + + test('"c.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('c.a', '(b|a).(a)', { bash: true })); + }); + + test('"c.a" should not match "*.!(a)"', () => { + expect_truthy(!isMatch('c.a', '*.!(a)', { bash: true })); + }); + + test('"c.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('c.a', '*.+(b|d)', { bash: true })); + }); + + test('"c.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('c.a', '@(b|a).@(a)', { bash: true })); + }); + + test('"c.c" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('c.c', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"c.c" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('c.c', '*!(.a|.b|.c)', { bash: true })); + }); + + test('"c.c" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('c.c', '*.!(a|b|c)', { bash: true })); + }); + + test('"c.c" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('c.c', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true })); + }); + + test('"c.ccc" should match "!(*.[a-b]*)"', () => { + expect_truthy(isMatch('c.ccc', '!(*.[a-b]*)', { bash: true })); + }); + + test('"c.ccc" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('c.ccc', '!(*[a-b].[a-b]*)', { bash: true })); + }); + + test('"c.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('c.js', '!(*.js)', { bash: true })); + }); + + test('"c.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('c.js', '*!(.js)', { bash: true })); + }); + + test('"c.js" should not match "*.!(js)"', () => { + expect_truthy(!isMatch('c.js', '*.!(js)', { bash: true })); + }); + + test('"c/a/v" should match "c/!(z)/v"', () => { + expect_truthy(isMatch('c/a/v', 'c/!(z)/v', { bash: true })); + }); + + test('"c/a/v" should not match "c/*(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/*(z)/v', { bash: true })); + }); + + test('"c/a/v" should not match "c/+(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/+(z)/v', { bash: true })); + }); + + test('"c/a/v" should not match "c/@(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/@(z)/v', { bash: true })); + }); + + test('"c/z/v" should not match "*(z)"', () => { + expect_truthy(!isMatch('c/z/v', '*(z)', { bash: true })); + }); + + test('"c/z/v" should not match "+(z)"', () => { + expect_truthy(!isMatch('c/z/v', '+(z)', { bash: true })); + }); + + test('"c/z/v" should not match "?(z)"', () => { + expect_truthy(!isMatch('c/z/v', '?(z)', { bash: true })); + }); + + test('"c/z/v" should not match "c/!(z)/v"', () => { + expect_truthy(!isMatch('c/z/v', 'c/!(z)/v', { bash: true })); + }); + + test('"c/z/v" should match "c/*(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/*(z)/v', { bash: true })); + }); + + test('"c/z/v" should match "c/+(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/+(z)/v', { bash: true })); + }); + + test('"c/z/v" should match "c/@(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/@(z)/v', { bash: true })); + }); + + test('"c/z/v" should match "c/z/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/z/v', { bash: true })); + }); + + test('"cc.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('cc.a', '(b|a).(a)', { bash: true })); + }); + + test('"cc.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('cc.a', '@(b|a).@(a)', { bash: true })); + }); + + test('"ccc" should match "!(a)*"', () => { + expect_truthy(isMatch('ccc', '!(a)*', { bash: true })); + }); + + test('"ccc" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('ccc', 'a!(b)*', { bash: true })); + }); + + test('"cow" should match "!(*.*)"', () => { + expect_truthy(isMatch('cow', '!(*.*)', { bash: true })); + }); + + test('"cow" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('cow', '!(*.*).', { bash: true })); + }); + + test('"cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('cow', '.!(*.*)', { bash: true })); + }); + + test('"cz" should not match "a!(*)"', () => { + expect_truthy(!isMatch('cz', 'a!(*)', { bash: true })); + }); + + test('"cz" should not match "a!(z)"', () => { + expect_truthy(!isMatch('cz', 'a!(z)', { bash: true })); + }); + + test('"cz" should not match "a*!(z)"', () => { + expect_truthy(!isMatch('cz', 'a*!(z)', { bash: true })); + }); + + test('"cz" should not match "a*(z)"', () => { + expect_truthy(!isMatch('cz', 'a*(z)', { bash: true })); + }); + + test('"cz" should not match "a**(z)"', () => { + expect_truthy(!isMatch('cz', 'a**(z)', { bash: true })); + }); + + test('"cz" should not match "a*@(z)"', () => { + expect_truthy(!isMatch('cz', 'a*@(z)', { bash: true })); + }); + + test('"cz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('cz', 'a+(z)', { bash: true })); + }); + + test('"cz" should not match "a?(z)"', () => { + expect_truthy(!isMatch('cz', 'a?(z)', { bash: true })); + }); + + test('"cz" should not match "a@(z)"', () => { + expect_truthy(!isMatch('cz', 'a@(z)', { bash: true })); + }); + + test('"d.a.d" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('d.a.d', '!(*.[a-b]*)', { bash: true })); + }); + + test('"d.a.d" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('d.a.d', '!(*[a-b].[a-b]*)', { bash: true })); + }); + + test('"d.a.d" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('d.a.d', '!*.(a|b)*', { bash: true })); + }); + + test('"d.a.d" should match "!*.*(a|b)"', () => { + expect_truthy(isMatch('d.a.d', '!*.*(a|b)', { bash: true })); + }); + + test('"d.a.d" should not match "!*.{a,b}*"', () => { + expect_truthy(!isMatch('d.a.d', '!*.{a,b}*', { bash: true })); + }); + + test('"d.a.d" should match "*.!(a)"', () => { + expect_truthy(isMatch('d.a.d', '*.!(a)', { bash: true })); + }); + + test('"d.a.d" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('d.a.d', '*.+(b|d)', { bash: true })); + }); + + test('"d.d" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('d.d', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"d.d" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('d.d', '*!(.a|.b|.c)', { bash: true })); + }); + + test('"d.d" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('d.d', '*.!(a|b|c)', { bash: true })); + }); + + test('"d.d" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('d.d', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true })); + }); + + test('"d.js.d" should match "!(*.js)"', () => { + expect_truthy(isMatch('d.js.d', '!(*.js)', { bash: true })); + }); + + test('"d.js.d" should match "*!(.js)"', () => { + expect_truthy(isMatch('d.js.d', '*!(.js)', { bash: true })); + }); + + test('"d.js.d" should match "*.!(js)"', () => { + expect_truthy(isMatch('d.js.d', '*.!(js)', { bash: true })); + }); + + test('"dd.aa.d" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('dd.aa.d', '(b|a).(a)', { bash: true })); + }); + + test('"dd.aa.d" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('dd.aa.d', '@(b|a).@(a)', { bash: true })); + }); + + test('"def" should not match "()ef"', () => { + expect_truthy(!isMatch('def', '()ef', { bash: true })); + }); + + test('"e.e" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('e.e', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"e.e" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('e.e', '*!(.a|.b|.c)', { bash: true })); + }); + + test('"e.e" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('e.e', '*.!(a|b|c)', { bash: true })); + }); + + test('"e.e" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('e.e', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true })); + }); + + test('"ef" should match "()ef"', () => { + expect_truthy(isMatch('ef', '()ef', { bash: true })); + }); + + test('"effgz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('effgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { bash: true })); + }); + + test('"efgz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('efgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { bash: true })); + }); + + test('"egz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('egz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { bash: true })); + }); + + test('"egz" should not match "@(b+(c)d|e+(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(!isMatch('egz', '@(b+(c)d|e+(f)g?|?(h)i@(j|k))', { bash: true })); + }); + + test('"egzefffgzbcdij" should match "*(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('egzefffgzbcdij', '*(b+(c)d|e*(f)g?|?(h)i@(j|k))', { bash: true })); + }); + + test('"f" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('f', '!(f!(o))', { bash: true })); + }); + + test('"f" should match "!(f(o))"', () => { + expect_truthy(isMatch('f', '!(f(o))', { bash: true })); + }); + + test('"f" should not match "!(f)"', () => { + expect_truthy(!isMatch('f', '!(f)', { bash: true })); + }); + + test('"f" should not match "*(!(f))"', () => { + expect_truthy(!isMatch('f', '*(!(f))', { bash: true })); + }); + + test('"f" should not match "+(!(f))"', () => { + expect_truthy(!isMatch('f', '+(!(f))', { bash: true })); + }); + + test('"f.a" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('f.a', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"f.a" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('f.a', '*!(.a|.b|.c)', { bash: true })); + }); + + test('"f.a" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('f.a', '*.!(a|b|c)', { bash: true })); + }); + + test('"f.f" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('f.f', '!(*.a|*.b|*.c)', { bash: true })); + }); + + test('"f.f" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('f.f', '*!(.a|.b|.c)', { bash: true })); + }); + + test('"f.f" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('f.f', '*.!(a|b|c)', { bash: true })); + }); + + test('"f.f" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('f.f', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true })); + }); + + test('"fa" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('fa', '!(f!(o))', { bash: true })); + }); + + test('"fa" should match "!(f(o))"', () => { + expect_truthy(isMatch('fa', '!(f(o))', { bash: true })); + }); + + test('"fb" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('fb', '!(f!(o))', { bash: true })); + }); + + test('"fb" should match "!(f(o))"', () => { + expect_truthy(isMatch('fb', '!(f(o))', { bash: true })); + }); + + test('"fff" should match "!(f)"', () => { + expect_truthy(isMatch('fff', '!(f)', { bash: true })); + }); + + test('"fff" should match "*(!(f))"', () => { + expect_truthy(isMatch('fff', '*(!(f))', { bash: true })); + }); + + test('"fff" should match "+(!(f))"', () => { + expect_truthy(isMatch('fff', '+(!(f))', { bash: true })); + }); + + test('"fffooofoooooffoofffooofff" should match "*(*(f)*(o))"', () => { + expect_truthy(isMatch('fffooofoooooffoofffooofff', '*(*(f)*(o))', { bash: true })); + }); + + test('"ffo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('ffo', '*(f*(o))', { bash: true })); + }); + + test('"file.C" should not match "*.c?(c)"', () => { + expect_truthy(!isMatch('file.C', '*.c?(c)', { bash: true })); + }); + + test('"file.c" should match "*.c?(c)"', () => { + expect_truthy(isMatch('file.c', '*.c?(c)', { bash: true })); + }); + + test('"file.cc" should match "*.c?(c)"', () => { + expect_truthy(isMatch('file.cc', '*.c?(c)', { bash: true })); + }); + + test('"file.ccc" should not match "*.c?(c)"', () => { + expect_truthy(!isMatch('file.ccc', '*.c?(c)', { bash: true })); + }); + + test('"fo" should match "!(f!(o))"', () => { + expect_truthy(isMatch('fo', '!(f!(o))', { bash: true })); + }); + + test('"fo" should not match "!(f(o))"', () => { + expect_truthy(!isMatch('fo', '!(f(o))', { bash: true })); + }); + + test('"fofo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('fofo', '*(f*(o))', { bash: true })); + }); + + test('"fofoofoofofoo" should match "*(fo|foo)"', () => { + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)', { bash: true })); + }); + + test('"fofoofoofofoo" should match "*(fo|foo)"', () => { + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)', { bash: true })); + }); + + test('"foo" should match "!(!(foo))"', () => { + expect_truthy(isMatch('foo', '!(!(foo))', { bash: true })); + }); + + test('"foo" should match "!(f)"', () => { + expect_truthy(isMatch('foo', '!(f)', { bash: true })); + }); + + test('"foo" should not match "!(foo)"', () => { + expect_truthy(!isMatch('foo', '!(foo)', { bash: true })); + }); + + test('"foo" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foo', '!(foo)*', { bash: true })); + }); + + test('"foo" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foo', '!(foo)*', { bash: true })); + }); + + test('"foo" should not match "!(foo)+"', () => { + expect_truthy(!isMatch('foo', '!(foo)+', { bash: true })); + }); + + test('"foo" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foo', '!(foo)b*', { bash: true })); + }); + + test('"foo" should match "!(x)"', () => { + expect_truthy(isMatch('foo', '!(x)', { bash: true })); + }); + + test('"foo" should match "!(x)*"', () => { + expect_truthy(isMatch('foo', '!(x)*', { bash: true })); + }); + + test('"foo" should match "*"', () => { + expect_truthy(isMatch('foo', '*', { bash: true })); + }); + + test('"foo" should match "*(!(f))"', () => { + expect_truthy(isMatch('foo', '*(!(f))', { bash: true })); + }); + + test('"foo" should not match "*(!(foo))"', () => { + expect_truthy(!isMatch('foo', '*(!(foo))', { bash: true })); + }); + + test('"foo" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('foo', '*(@(a))a@(c)', { bash: true })); + }); + + test('"foo" should match "*(@(foo))"', () => { + expect_truthy(isMatch('foo', '*(@(foo))', { bash: true })); + }); + + test('"foo" should not match "*(a|b\\[)"', () => { + expect_truthy(!isMatch('foo', '*(a|b\\[)', { bash: true })); + }); + + test('"foo" should match "*(a|b\\[)|f*"', () => { + expect_truthy(isMatch('foo', '*(a|b\\[)|f*', { bash: true })); + }); + + test('"foo" should match "@(*(a|b\\[)|f*)"', () => { + expect_truthy(isMatch('foo', '@(*(a|b\\[)|f*)', { bash: true })); + }); + + test('"foo" should not match "*/*/*"', () => { + expect_truthy(!isMatch('foo', '*/*/*', { bash: true })); + }); + + test('"foo" should not match "*f"', () => { + expect_truthy(!isMatch('foo', '*f', { bash: true })); + }); + + test('"foo" should match "*foo*"', () => { + expect_truthy(isMatch('foo', '*foo*', { bash: true })); + }); + + test('"foo" should match "+(!(f))"', () => { + expect_truthy(isMatch('foo', '+(!(f))', { bash: true })); + }); + + test('"foo" should not match "??"', () => { + expect_truthy(!isMatch('foo', '??', { bash: true })); + }); + + test('"foo" should match "???"', () => { + expect_truthy(isMatch('foo', '???', { bash: true })); + }); + + test('"foo" should not match "bar"', () => { + expect_truthy(!isMatch('foo', 'bar', { bash: true })); + }); + + test('"foo" should match "f*"', () => { + expect_truthy(isMatch('foo', 'f*', { bash: true })); + }); + + test('"foo" should not match "fo"', () => { + expect_truthy(!isMatch('foo', 'fo', { bash: true })); + }); + + test('"foo" should match "foo"', () => { + expect_truthy(isMatch('foo', 'foo', { bash: true })); + }); + + test('"foo" should match "{*(a|b\\[),f*}"', () => { + expect_truthy(isMatch('foo', '{*(a|b\\[),f*}', { bash: true })); + }); + + test('"foo*" should match "foo\\*"', () => { + expect_truthy(isMatch('foo*', 'foo\\*', { bash: true, windows: false })); + }); + + test('"foo*bar" should match "foo\\*bar"', () => { + expect_truthy(isMatch('foo*bar', 'foo\\*bar', { bash: true })); + }); + + test('"foo.js" should not match "!(foo).js"', () => { + expect_truthy(!isMatch('foo.js', '!(foo).js', { bash: true })); + }); + + test('"foo.js.js" should match "*.!(js)"', () => { + expect_truthy(isMatch('foo.js.js', '*.!(js)', { bash: true })); + }); + + test('"foo.js.js" should not match "*.!(js)*"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)*', { bash: true })); + }); + + test('"foo.js.js" should not match "*.!(js)*.!(js)"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)*.!(js)', { bash: true })); + }); + + test('"foo.js.js" should not match "*.!(js)+"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)+', { bash: true })); + }); + + test('"foo.txt" should match "**/!(bar).txt"', () => { + expect_truthy(isMatch('foo.txt', '**/!(bar).txt', { bash: true })); + }); + + test('"foo/bar" should not match "*/*/*"', () => { + expect_truthy(!isMatch('foo/bar', '*/*/*', { bash: true })); + }); + + test('"foo/bar" should match "foo/!(foo)"', () => { + expect_truthy(isMatch('foo/bar', 'foo/!(foo)', { bash: true })); + }); + + test('"foo/bar" should match "foo/*"', () => { + expect_truthy(isMatch('foo/bar', 'foo/*', { bash: true })); + }); + + test('"foo/bar" should match "foo/bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo/bar', { bash: true })); + }); + + test('"foo/bar" should not match "foo?bar"', () => { + expect_truthy(!isMatch('foo/bar', 'foo?bar', { bash: true })); + }); + + test('"foo/bar" should match "foo[/]bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo[/]bar', { bash: true })); + }); + + test('"foo/bar/baz.jsx" should match "foo/bar/**/*.+(js|jsx)"', () => { + expect_truthy(isMatch('foo/bar/baz.jsx', 'foo/bar/**/*.+(js|jsx)', { bash: true })); + }); + + test('"foo/bar/baz.jsx" should match "foo/bar/*.+(js|jsx)"', () => { + expect_truthy(isMatch('foo/bar/baz.jsx', 'foo/bar/*.+(js|jsx)', { bash: true })); + }); + + test('"foo/bb/aa/rr" should match "**/**/**"', () => { + expect_truthy(isMatch('foo/bb/aa/rr', '**/**/**', { bash: true })); + }); + + test('"foo/bb/aa/rr" should match "*/*/*"', () => { + expect_truthy(isMatch('foo/bb/aa/rr', '*/*/*', { bash: true })); + }); + + test('"foo/bba/arr" should match "*/*/*"', () => { + expect_truthy(isMatch('foo/bba/arr', '*/*/*', { bash: true })); + }); + + test('"foo/bba/arr" should match "foo*"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo*', { bash: true })); + }); + + test('"foo/bba/arr" should match "foo**"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo**', { bash: true })); + }); + + test('"foo/bba/arr" should match "foo/*"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo/*', { bash: true })); + }); + + test('"foo/bba/arr" should match "foo/**"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo/**', { bash: true })); + }); + + test('"foo/bba/arr" should match "foo/**arr"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo/**arr', { bash: true })); + }); + + test('"foo/bba/arr" should not match "foo/**z"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/**z', { bash: true })); + }); + + test('"foo/bba/arr" should match "foo/*arr"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo/*arr', { bash: true })); + }); + + test('"foo/bba/arr" should not match "foo/*z"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/*z', { bash: true })); + }); + + test('"foob" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foob', '!(foo)b*', { bash: true })); + }); + + test('"foob" should not match "(foo)bb"', () => { + expect_truthy(!isMatch('foob', '(foo)bb', { bash: true })); + }); + + test('"foobar" should match "!(foo)"', () => { + expect_truthy(isMatch('foobar', '!(foo)', { bash: true })); + }); + + test('"foobar" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foobar', '!(foo)*', { bash: true })); + }); + + test('"foobar" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foobar', '!(foo)*', { bash: true })); + }); + + test('"foobar" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foobar', '!(foo)b*', { bash: true })); + }); + + test('"foobar" should match "*(!(foo))"', () => { + expect_truthy(isMatch('foobar', '*(!(foo))', { bash: true })); + }); + + test('"foobar" should match "*ob*a*r*"', () => { + expect_truthy(isMatch('foobar', '*ob*a*r*', { bash: true })); + }); + + test('"foobar" should match "foo\\*bar"', () => { + expect_truthy(isMatch('foobar', 'foo*bar', { bash: true })); + }); + + test('"foobb" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foobb', '!(foo)b*', { bash: true })); + }); + + test('"foobb" should match "(foo)bb"', () => { + expect_truthy(isMatch('foobb', '(foo)bb', { bash: true })); + }); + + test('"(foo)bb" should match "\\(foo\\)bb"', () => { + expect_truthy(isMatch('(foo)bb', '\\(foo\\)bb', { bash: true })); + }); + + test('"foofoofo" should match "@(foo|f|fo)*(f|of+(o))"', () => { + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))', { bash: true })); + }); + + test('"foofoofo" should match "@(foo|f|fo)*(f|of+(o))"', () => { + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))', { bash: true })); + }); + + test('"fooofoofofooo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('fooofoofofooo', '*(f*(o))', { bash: true })); + }); + + test('"foooofo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('foooofo', '*(f*(o))', { bash: true })); + }); + + test('"foooofof" should match "*(f*(o))"', () => { + expect_truthy(isMatch('foooofof', '*(f*(o))', { bash: true })); + }); + + test('"foooofof" should not match "*(f+(o))"', () => { + expect_truthy(!isMatch('foooofof', '*(f+(o))', { bash: true })); + }); + + test('"foooofofx" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('foooofofx', '*(f*(o))', { bash: true })); + }); + + test('"foooxfooxfoxfooox" should match "*(f*(o)x)"', () => { + expect_truthy(isMatch('foooxfooxfoxfooox', '*(f*(o)x)', { bash: true })); + }); + + test('"foooxfooxfxfooox" should match "*(f*(o)x)"', () => { + expect_truthy(isMatch('foooxfooxfxfooox', '*(f*(o)x)', { bash: true })); + }); + + test('"foooxfooxofoxfooox" should not match "*(f*(o)x)"', () => { + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f*(o)x)', { bash: true })); + }); + + test('"foot" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('foot', '@(!(z*)|*x)', { bash: true })); + }); + + test('"foox" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('foox', '@(!(z*)|*x)', { bash: true })); + }); + + test('"fz" should not match "*(z)"', () => { + expect_truthy(!isMatch('fz', '*(z)', { bash: true })); + }); + + test('"fz" should not match "+(z)"', () => { + expect_truthy(!isMatch('fz', '+(z)', { bash: true })); + }); + + test('"fz" should not match "?(z)"', () => { + expect_truthy(!isMatch('fz', '?(z)', { bash: true })); + }); + + test('"moo.cow" should not match "!(moo).!(cow)"', () => { + expect_truthy(!isMatch('moo.cow', '!(moo).!(cow)', { bash: true })); + }); + + test('"moo.cow" should not match "!(*).!(*)"', () => { + expect_truthy(!isMatch('moo.cow', '!(*).!(*)', { bash: true })); + }); + + test('"moo.cow" should not match "!(*.*).!(*.*)"', () => { + expect_truthy(!isMatch('moo.cow', '!(*.*).!(*.*)', { bash: true })); + }); + + test('"mad.moo.cow" should not match "!(*.*).!(*.*)"', () => { + expect_truthy(!isMatch('mad.moo.cow', '!(*.*).!(*.*)', { bash: true })); + }); + + test('"mad.moo.cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('mad.moo.cow', '.!(*.*)', { bash: true })); + }); + + test('"Makefile" should match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(isMatch('Makefile', '!(*.c|*.h|Makefile.in|config*|README)', { bash: true })); + }); + + test('"Makefile.in" should not match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(!isMatch('Makefile.in', '!(*.c|*.h|Makefile.in|config*|README)', { bash: true })); + }); + + test('"moo" should match "!(*.*)"', () => { + expect_truthy(isMatch('moo', '!(*.*)', { bash: true })); + }); + + test('"moo" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('moo', '!(*.*).', { bash: true })); + }); + + test('"moo" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('moo', '.!(*.*)', { bash: true })); + }); + + test('"moo.cow" should not match "!(*.*)"', () => { + expect_truthy(!isMatch('moo.cow', '!(*.*)', { bash: true })); + }); + + test('"moo.cow" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('moo.cow', '!(*.*).', { bash: true })); + }); + + test('"moo.cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('moo.cow', '.!(*.*)', { bash: true })); + }); + + test('"mucca.pazza" should not match "mu!(*(c))?.pa!(*(z))?"', () => { + expect_truthy(!isMatch('mucca.pazza', 'mu!(*(c))?.pa!(*(z))?', { bash: true })); + }); + + test('"ofoofo" should match "*(of+(o))"', () => { + expect_truthy(isMatch('ofoofo', '*(of+(o))', { bash: true })); + }); + + test('"ofoofo" should match "*(of+(o)|f)"', () => { + expect_truthy(isMatch('ofoofo', '*(of+(o)|f)', { bash: true })); + }); + + test('"ofooofoofofooo" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('ofooofoofofooo', '*(f*(o))', { bash: true })); + }); + + test('"ofoooxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxo', '*(*(of*(o)x)o)', { bash: true })); + }); + + test('"ofoooxoofxoofoooxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxo', '*(*(of*(o)x)o)', { bash: true })); + }); + + test('"ofoooxoofxoofoooxoofxofo" should not match "*(*(of*(o)x)o)"', () => { + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(*(of*(o)x)o)', { bash: true })); + }); + + test('"ofoooxoofxoofoooxoofxoo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxoo', '*(*(of*(o)x)o)', { bash: true })); + }); + + test('"ofoooxoofxoofoooxoofxooofxofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(*(of*(o)x)o)', { bash: true })); + }); + + test('"ofxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofxoofxo', '*(*(of*(o)x)o)', { bash: true })); + }); + + test('"oofooofo" should match "*(of|oof+(o))"', () => { + expect_truthy(isMatch('oofooofo', '*(of|oof+(o))', { bash: true })); + }); + + test('"ooo" should match "!(f)"', () => { + expect_truthy(isMatch('ooo', '!(f)', { bash: true })); + }); + + test('"ooo" should match "*(!(f))"', () => { + expect_truthy(isMatch('ooo', '*(!(f))', { bash: true })); + }); + + test('"ooo" should match "+(!(f))"', () => { + expect_truthy(isMatch('ooo', '+(!(f))', { bash: true })); + }); + + test('"oxfoxfox" should not match "*(oxf+(ox))"', () => { + expect_truthy(!isMatch('oxfoxfox', '*(oxf+(ox))', { bash: true })); + }); + + test('"oxfoxoxfox" should match "*(oxf+(ox))"', () => { + expect_truthy(isMatch('oxfoxoxfox', '*(oxf+(ox))', { bash: true })); + }); + + test('"para" should match "para*([0-9])"', () => { + expect_truthy(isMatch('para', 'para*([0-9])', { bash: true })); + }); + + test('"para" should not match "para+([0-9])"', () => { + expect_truthy(!isMatch('para', 'para+([0-9])', { bash: true })); + }); + + test('"para.38" should match "para!(*.[00-09])"', () => { + expect_truthy(isMatch('para.38', 'para!(*.[00-09])', { bash: true })); + }); + + test('"para.graph" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('para.graph', 'para!(*.[0-9])', { bash: true })); + }); + + test('"para13829383746592" should match "para*([0-9])"', () => { + expect_truthy(isMatch('para13829383746592', 'para*([0-9])', { bash: true })); + }); + + test('"para381" should not match "para?([345]|99)1"', () => { + expect_truthy(!isMatch('para381', 'para?([345]|99)1', { bash: true })); + }); + + test('"para39" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('para39', 'para!(*.[0-9])', { bash: true })); + }); + + test('"para987346523" should match "para+([0-9])"', () => { + expect_truthy(isMatch('para987346523', 'para+([0-9])', { bash: true })); + }); + + test('"para991" should match "para?([345]|99)1"', () => { + expect_truthy(isMatch('para991', 'para?([345]|99)1', { bash: true })); + }); + + test('"paragraph" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('paragraph', 'para!(*.[0-9])', { bash: true })); + }); + + test('"paragraph" should not match "para*([0-9])"', () => { + expect_truthy(!isMatch('paragraph', 'para*([0-9])', { bash: true })); + }); + + test('"paragraph" should match "para@(chute|graph)"', () => { + expect_truthy(isMatch('paragraph', 'para@(chute|graph)', { bash: true })); + }); + + test('"paramour" should not match "para@(chute|graph)"', () => { + expect_truthy(!isMatch('paramour', 'para@(chute|graph)', { bash: true })); + }); + + test('"parse.y" should match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(isMatch('parse.y', '!(*.c|*.h|Makefile.in|config*|README)', { bash: true })); + }); + + test('"shell.c" should not match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(!isMatch('shell.c', '!(*.c|*.h|Makefile.in|config*|README)', { bash: true })); + }); + + test('"VMS.FILE;" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;', '*\\;[1-9]*([0-9])', { bash: true })); + }); + + test('"VMS.FILE;0" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;0', '*\\;[1-9]*([0-9])', { bash: true })); + }); + + test('"VMS.FILE;9" should match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;9', '*\\;[1-9]*([0-9])', { bash: true })); + }); + + test('"VMS.FILE;1" should match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;1', '*\\;[1-9]*([0-9])', { bash: true })); + }); + + test('"VMS.FILE;1" should match "*;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;1', '*;[1-9]*([0-9])', { bash: true })); + }); + + test('"VMS.FILE;139" should match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;139', '*\\;[1-9]*([0-9])', { bash: true })); + }); + + test('"VMS.FILE;1N" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;1N', '*\\;[1-9]*([0-9])', { bash: true })); + }); + + test('"xfoooofof" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('xfoooofof', '*(f*(o))', { bash: true })); + }); + + test('"XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1" should match "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*"', () => { + expect_truthy(isMatch('XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1', 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*', { bash: true, windows: false })); + }); + + test('"XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1" should not match "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*"', () => { + expect_truthy(!isMatch('XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1', 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*', { bash: true })); + }); + + test('"z" should match "*(z)"', () => { + expect_truthy(isMatch('z', '*(z)', { bash: true })); + }); + + test('"z" should match "+(z)"', () => { + expect_truthy(isMatch('z', '+(z)', { bash: true })); + }); + + test('"z" should match "?(z)"', () => { + expect_truthy(isMatch('z', '?(z)', { bash: true })); + }); + + test('"zf" should not match "*(z)"', () => { + expect_truthy(!isMatch('zf', '*(z)', { bash: true })); + }); + + test('"zf" should not match "+(z)"', () => { + expect_truthy(!isMatch('zf', '+(z)', { bash: true })); + }); + + test('"zf" should not match "?(z)"', () => { + expect_truthy(!isMatch('zf', '?(z)', { bash: true })); + }); + + test('"zoot" should not match "@(!(z*)|*x)"', () => { + expect_truthy(!isMatch('zoot', '@(!(z*)|*x)', { bash: true })); + }); + + test('"zoox" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('zoox', '@(!(z*)|*x)', { bash: true })); + }); + + test('"zz" should not match "(a+|b)*"', () => { + expect_truthy(!isMatch('zz', '(a+|b)*', { bash: true })); + }); +}); + diff --git a/packages/node-utils/test/micromatch/extglobs-minimatch.test.ts b/packages/node-utils/test/micromatch/extglobs-minimatch.test.ts new file mode 100644 index 0000000..66cfc19 --- /dev/null +++ b/packages/node-utils/test/micromatch/extglobs-minimatch.test.ts @@ -0,0 +1,2644 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch, makeRe } = micromatch; + +if (!process.env.ORIGINAL_PATH_SEP) { + process.env.ORIGINAL_PATH_SEP = path.sep +} + +/** + * Some of tests were converted from bash 4.3, 4.4, and minimatch unit tests. + */ + +describe('extglobs (minimatch)', () => { + let setup = { + before: () => ((path as any).sep = '\\'), + after: () => ((path as any).sep = process.env.ORIGINAL_PATH_SEP) + }; + + afterEach(() => setup.after()); + beforeEach(() => setup.before()); + + test('should not match empty string with "*(0|1|3|5|7|9)"', () => { + expect_truthy(!isMatch('', '*(0|1|3|5|7|9)')); + }); + + test('"*(a|b[)" should not match "*(a|b\\[)"', () => { + expect_truthy(!isMatch('*(a|b[)', '*(a|b\\[)')); + }); + + test('"*(a|b[)" should not match "\\*\\(a|b\\[\\)"', () => { + expect_truthy(isMatch('*(a|b[)', '\\*\\(a\\|b\\[\\)')); + }); + + test('"***" should match "\\*\\*\\*"', () => { + expect_truthy(isMatch('***', '\\*\\*\\*')); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1" should not match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(!isMatch('-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*')); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1" should match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(isMatch('-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*')); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1" should not match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(!isMatch('-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*')); + }); + + test('"/dev/udp/129.22.8.102/45" should not match "/dev\\/@(tcp|udp)\\/*\\/*"', () => { + expect_truthy(isMatch('/dev/udp/129.22.8.102/45', '/dev\\/@(tcp|udp)\\/*\\/*')); + }); + + test('"/x/y/z" should match "/x/y/z"', () => { + expect_truthy(isMatch('/x/y/z', '/x/y/z')); + }); + + test('"0377" should match "+([0-7])"', () => { + expect_truthy(isMatch('0377', '+([0-7])')); + }); + + test('"07" should match "+([0-7])"', () => { + expect_truthy(isMatch('07', '+([0-7])')); + }); + + test('"09" should not match "+([0-7])"', () => { + expect_truthy(!isMatch('09', '+([0-7])')); + }); + + test('"1" should match "0|[1-9]*([0-9])"', () => { + expect_truthy(isMatch('1', '0|[1-9]*([0-9])')); + }); + + test('"12" should match "0|[1-9]*([0-9])"', () => { + expect_truthy(isMatch('12', '0|[1-9]*([0-9])')); + }); + + test('"123abc" should not match "(a+|b)*"', () => { + expect_truthy(!isMatch('123abc', '(a+|b)*')); + }); + + test('"123abc" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('123abc', '(a+|b)+')); + }); + + test('"123abc" should match "*?(a)bc"', () => { + expect_truthy(isMatch('123abc', '*?(a)bc')); + }); + + test('"123abc" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('123abc', 'a(b*(foo|bar))d')); + }); + + test('"123abc" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*(e|f)')); + }); + + test('"123abc" should not match "ab**"', () => { + expect_truthy(!isMatch('123abc', 'ab**')); + }); + + test('"123abc" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab**(e|f)')); + }); + + test('"123abc" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('123abc', 'ab**(e|f)g')); + }); + + test('"123abc" should not match "ab***ef"', () => { + expect_truthy(!isMatch('123abc', 'ab***ef')); + }); + + test('"123abc" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*+(e|f)')); + }); + + test('"123abc" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*d+(e|f)')); + }); + + test('"123abc" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab?*(e|f)')); + }); + + test('"12abc" should not match "0|[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('12abc', '0|[1-9]*([0-9])')); + }); + + test('"137577991" should match "*(0|1|3|5|7|9)"', () => { + expect_truthy(isMatch('137577991', '*(0|1|3|5|7|9)')); + }); + + test('"2468" should not match "*(0|1|3|5|7|9)"', () => { + expect_truthy(!isMatch('2468', '*(0|1|3|5|7|9)')); + }); + + test('"?a?b" should match "\\??\\?b"', () => { + expect_truthy(isMatch('?a?b', '\\??\\?b')); + }); + + test('"\\a\\b\\c" should not match "abc"', () => { + expect_truthy(!isMatch('\\a\\b\\c', 'abc')); + }); + + test('"a" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a', '!(*.a|*.b|*.c)')); + }); + + test('"a" should not match "!(a)"', () => { + expect_truthy(!isMatch('a', '!(a)')); + }); + + test('"a" should not match "!(a)*"', () => { + expect_truthy(!isMatch('a', '!(a)*')); + }); + + test('"a" should match "(a)"', () => { + expect_truthy(isMatch('a', '(a)')); + }); + + test('"a" should not match "(b)"', () => { + expect_truthy(!isMatch('a', '(b)')); + }); + + test('"a" should match "*(a)"', () => { + expect_truthy(isMatch('a', '*(a)')); + }); + + test('"a" should match "+(a)"', () => { + expect_truthy(isMatch('a', '+(a)')); + }); + + test('"a" should match "?"', () => { + expect_truthy(isMatch('a', '?')); + }); + + test('"a" should match "?(a|b)"', () => { + expect_truthy(isMatch('a', '?(a|b)')); + }); + + test('"a" should not match "??"', () => { + expect_truthy(!isMatch('a', '??')); + }); + + test('"a" should match "a!(b)*"', () => { + expect_truthy(isMatch('a', 'a!(b)*')); + }); + + test('"a" should match "a?(a|b)"', () => { + expect_truthy(isMatch('a', 'a?(a|b)')); + }); + + test('"a" should match "a?(x)"', () => { + expect_truthy(isMatch('a', 'a?(x)')); + }); + + test('"a" should not match "a??b"', () => { + expect_truthy(!isMatch('a', 'a??b')); + }); + + test('"a" should not match "b?(a|b)"', () => { + expect_truthy(!isMatch('a', 'b?(a|b)')); + }); + + test('"a((((b" should match "a(*b"', () => { + expect_truthy(isMatch('a((((b', 'a(*b')); + }); + + test('"a((((b" should not match "a(b"', () => { + expect_truthy(!isMatch('a((((b', 'a(b')); + }); + + test('"a((((b" should not match "a\\(b"', () => { + expect_truthy(!isMatch('a((((b', 'a\\(b')); + }); + + test('"a((b" should match "a(*b"', () => { + expect_truthy(isMatch('a((b', 'a(*b')); + }); + + test('"a((b" should not match "a(b"', () => { + expect_truthy(!isMatch('a((b', 'a(b')); + }); + + test('"a((b" should not match "a\\(b"', () => { + expect_truthy(!isMatch('a((b', 'a\\(b')); + }); + + test('"a(b" should match "a(*b"', () => { + expect_truthy(isMatch('a(b', 'a(*b')); + }); + + test('"a(b" should match "a(b"', () => { + expect_truthy(isMatch('a(b', 'a(b')); + }); + + test('"a(b" should match "a\\(b"', () => { + expect_truthy(isMatch('a(b', 'a\\(b')); + }); + + test('"a." should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.', '!(*.a|*.b|*.c)')); + }); + + test('"a." should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.', '*!(.a|.b|.c)')); + }); + + test('"a." should match "*.!(a)"', () => { + expect_truthy(isMatch('a.', '*.!(a)')); + }); + + test('"a." should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.', '*.!(a|b|c)')); + }); + + test('"a." should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.', '*.(a|b|@(ab|a*@(b))*(c)d)')); + }); + + test('"a." should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.', '*.+(b|d)')); + }); + + test('"a.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.a', '!(*.[a-b]*)')); + }); + + test('"a.a" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.a', '!(*.a|*.b|*.c)')); + }); + + test('"a.a" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.a', '!(*[a-b].[a-b]*)')); + }); + + test('"a.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.a', '!*.(a|b)')); + }); + + test('"a.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.a', '!*.(a|b)*')); + }); + + test('"a.a" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.a', '(a|d).(a|b)*')); + }); + + test('"a.a" should match "(b|a).(a)"', () => { + expect_truthy(isMatch('a.a', '(b|a).(a)')); + }); + + test('"a.a" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.a', '*!(.a|.b|.c)')); + }); + + test('"a.a" should not match "*.!(a)"', () => { + expect_truthy(!isMatch('a.a', '*.!(a)')); + }); + + test('"a.a" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.a', '*.!(a|b|c)')); + }); + + test('"a.a" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.a', '*.(a|b|@(ab|a*@(b))*(c)d)')); + }); + + test('"a.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.a', '*.+(b|d)')); + }); + + test('"a.a" should match "@(b|a).@(a)"', () => { + expect_truthy(isMatch('a.a', '@(b|a).@(a)')); + }); + + test('"a.a.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.a.a', '!(*.[a-b]*)')); + }); + + test('"a.a.a" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.a.a', '!(*[a-b].[a-b]*)')); + }); + + test('"a.a.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.a.a', '!*.(a|b)')); + }); + + test('"a.a.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.a.a', '!*.(a|b)*')); + }); + + test('"a.a.a" should match "*.!(a)"', () => { + expect_truthy(isMatch('a.a.a', '*.!(a)')); + }); + + test('"a.a.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.a.a', '*.+(b|d)')); + }); + + test('"a.aa.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('a.aa.a', '(b|a).(a)')); + }); + + test('"a.aa.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('a.aa.a', '@(b|a).@(a)')); + }); + + test('"a.abcd" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.abcd', '!(*.a|*.b|*.c)')); + }); + + test('"a.abcd" should not match "!(*.a|*.b|*.c)*"', () => { + expect_truthy(!isMatch('a.abcd', '!(*.a|*.b|*.c)*')); + }); + + test('"a.abcd" should match "*!(*.a|*.b|*.c)*"', () => { + expect_truthy(isMatch('a.abcd', '*!(*.a|*.b|*.c)*')); + }); + + test('"a.abcd" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.abcd', '*!(.a|.b|.c)')); + }); + + test('"a.abcd" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.abcd', '*.!(a|b|c)')); + }); + + test('"a.abcd" should not match "*.!(a|b|c)*"', () => { + expect_truthy(!isMatch('a.abcd', '*.!(a|b|c)*')); + }); + + test('"a.abcd" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.abcd', '*.(a|b|@(ab|a*@(b))*(c)d)')); + }); + + test('"a.b" should not match "!(*.*)"', () => { + expect_truthy(!isMatch('a.b', '!(*.*)')); + }); + + test('"a.b" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.b', '!(*.[a-b]*)')); + }); + + test('"a.b" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.b', '!(*.a|*.b|*.c)')); + }); + + test('"a.b" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.b', '!(*[a-b].[a-b]*)')); + }); + + test('"a.b" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.b', '!*.(a|b)')); + }); + + test('"a.b" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.b', '!*.(a|b)*')); + }); + + test('"a.b" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.b', '(a|d).(a|b)*')); + }); + + test('"a.b" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.b', '*!(.a|.b|.c)')); + }); + + test('"a.b" should match "*.!(a)"', () => { + expect_truthy(isMatch('a.b', '*.!(a)')); + }); + + test('"a.b" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.b', '*.!(a|b|c)')); + }); + + test('"a.b" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.b', '*.(a|b|@(ab|a*@(b))*(c)d)')); + }); + + test('"a.b" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('a.b', '*.+(b|d)')); + }); + + test('"a.bb" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.bb', '!(*.[a-b]*)')); + }); + + test('"a.bb" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.bb', '!(*[a-b].[a-b]*)')); + }); + + test('"a.bb" should match "!*.(a|b)"', () => { + expect_truthy(isMatch('a.bb', '!*.(a|b)')); + }); + + test('"a.bb" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.bb', '!*.(a|b)*')); + }); + + test('"a.bb" should not match "!*.*(a|b)"', () => { + expect_truthy(!isMatch('a.bb', '!*.*(a|b)')); + }); + + test('"a.bb" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.bb', '(a|d).(a|b)*')); + }); + + test('"a.bb" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('a.bb', '(b|a).(a)')); + }); + + test('"a.bb" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('a.bb', '*.+(b|d)')); + }); + + test('"a.bb" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('a.bb', '@(b|a).@(a)')); + }); + + test('"a.c" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.c', '!(*.a|*.b|*.c)')); + }); + + test('"a.c" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.c', '*!(.a|.b|.c)')); + }); + + test('"a.c" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.c', '*.!(a|b|c)')); + }); + + test('"a.c" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.c', '*.(a|b|@(ab|a*@(b))*(c)d)')); + }); + + test('"a.c.d" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.c.d', '!(*.a|*.b|*.c)')); + }); + + test('"a.c.d" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.c.d', '*!(.a|.b|.c)')); + }); + + test('"a.c.d" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.c.d', '*.!(a|b|c)')); + }); + + test('"a.c.d" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.c.d', '*.(a|b|@(ab|a*@(b))*(c)d)')); + }); + + test('"a.ccc" should match "!(*.[a-b]*)"', () => { + expect_truthy(isMatch('a.ccc', '!(*.[a-b]*)')); + }); + + test('"a.ccc" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('a.ccc', '!(*[a-b].[a-b]*)')); + }); + + test('"a.ccc" should match "!*.(a|b)"', () => { + expect_truthy(isMatch('a.ccc', '!*.(a|b)')); + }); + + test('"a.ccc" should match "!*.(a|b)*"', () => { + expect_truthy(isMatch('a.ccc', '!*.(a|b)*')); + }); + + test('"a.ccc" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.ccc', '*.+(b|d)')); + }); + + test('"a.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('a.js', '!(*.js)')); + }); + + test('"a.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.js', '*!(.js)')); + }); + + test('"a.js" should not match "*.!(js)"', () => { + expect_truthy(!isMatch('a.js', '*.!(js)')); + }); + + test('"a.js" should not match "a.!(js)"', () => { + expect_truthy(!isMatch('a.js', 'a.!(js)')); + }); + + test('"a.js" should not match "a.!(js)*"', () => { + expect_truthy(!isMatch('a.js', 'a.!(js)*')); + }); + + test('"a.js.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('a.js.js', '!(*.js)')); + }); + + test('"a.js.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.js.js', '*!(.js)')); + }); + + test('"a.js.js" should match "*.!(js)"', () => { + expect_truthy(isMatch('a.js.js', '*.!(js)')); + }); + + test('"a.js.js" should match "*.*(js).js"', () => { + expect_truthy(isMatch('a.js.js', '*.*(js).js')); + }); + + test('"a.md" should match "!(*.js)"', () => { + expect_truthy(isMatch('a.md', '!(*.js)')); + }); + + test('"a.md" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.md', '*!(.js)')); + }); + + test('"a.md" should match "*.!(js)"', () => { + expect_truthy(isMatch('a.md', '*.!(js)')); + }); + + test('"a.md" should match "a.!(js)"', () => { + expect_truthy(isMatch('a.md', 'a.!(js)')); + }); + + test('"a.md" should match "a.!(js)*"', () => { + expect_truthy(isMatch('a.md', 'a.!(js)*')); + }); + + test('"a.md.js" should not match "*.*(js).js"', () => { + expect_truthy(!isMatch('a.md.js', '*.*(js).js')); + }); + + test('"a.txt" should match "a.!(js)"', () => { + expect_truthy(isMatch('a.txt', 'a.!(js)')); + }); + + test('"a.txt" should match "a.!(js)*"', () => { + expect_truthy(isMatch('a.txt', 'a.!(js)*')); + }); + + test('"a/!(z)" should match "a/!(z)"', () => { + expect_truthy(isMatch('a/!(z)', 'a/!(z)')); + }); + + test('"a/b" should match "a/!(z)"', () => { + expect_truthy(isMatch('a/b', 'a/!(z)')); + }); + + test('"a/b/c.txt" should not match "*/b/!(*).txt"', () => { + expect_truthy(!isMatch('a/b/c.txt', '*/b/!(*).txt')); + }); + + test('"a/b/c.txt" should not match "*/b/!(c).txt"', () => { + expect_truthy(!isMatch('a/b/c.txt', '*/b/!(c).txt')); + }); + + test('"a/b/c.txt" should match "*/b/!(cc).txt"', () => { + expect_truthy(isMatch('a/b/c.txt', '*/b/!(cc).txt')); + }); + + test('"a/b/cc.txt" should not match "*/b/!(*).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(*).txt')); + }); + + test('"a/b/cc.txt" should not match "*/b/!(c).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(c).txt')); + }); + + test('"a/b/cc.txt" should not match "*/b/!(cc).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(cc).txt')); + }); + + test('"a/dir/foo.txt" should match "*/dir/**/!(bar).txt"', () => { + expect_truthy(isMatch('a/dir/foo.txt', '*/dir/**/!(bar).txt')); + }); + + test('"a/z" should not match "a/!(z)"', () => { + expect_truthy(!isMatch('a/z', 'a/!(z)')); + }); + + test('"a\\(b" should not match "a(*b"', () => { + expect_truthy(!isMatch('a\\(b', 'a(*b')); + }); + + test('"a\\(b" should not match "a(b"', () => { + expect_truthy(!isMatch('a\\(b', 'a(b')); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\\\z', 'a\\\\z', { windows: false })); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\\\z', 'a\\\\z')); + }); + + test('"a\\b" should match "a/b"', () => { + expect_truthy(isMatch('a\\b', 'a/b', { windows: true })); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\z', 'a\\\\z', { windows: false })); + }); + + test('"a\\z" should not match "a\\z"', () => { + expect_truthy(!isMatch('a\\z', 'a\\\\z')); + }); + + test('"aa" should not match "!(a!(b))"', () => { + expect_truthy(!isMatch('aa', '!(a!(b))')); + }); + + test('"aa" should match "!(a)"', () => { + expect_truthy(isMatch('aa', '!(a)')); + }); + + test('"aa" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aa', '!(a)*')); + }); + + test('"aa" should not match "?"', () => { + expect_truthy(!isMatch('aa', '?')); + }); + + test('"aa" should not match "@(a)b"', () => { + expect_truthy(!isMatch('aa', '@(a)b')); + }); + + test('"aa" should match "a!(b)*"', () => { + expect_truthy(isMatch('aa', 'a!(b)*')); + }); + + test('"aa" should not match "a??b"', () => { + expect_truthy(!isMatch('aa', 'a??b')); + }); + + test('"aa.aa" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('aa.aa', '(b|a).(a)')); + }); + + test('"aa.aa" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('aa.aa', '@(b|a).@(a)')); + }); + + test('"aaa" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aaa', '!(a)*')); + }); + + test('"aaa" should match "a!(b)*"', () => { + expect_truthy(isMatch('aaa', 'a!(b)*')); + }); + + test('"aaaaaaabababab" should match "*ab"', () => { + expect_truthy(isMatch('aaaaaaabababab', '*ab')); + }); + + test('"aaac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('aaac', '*(@(a))a@(c)')); + }); + + test('"aaaz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('aaaz', '[a*(]*z')); + }); + + test('"aab" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aab', '!(a)*')); + }); + + test('"aab" should not match "?"', () => { + expect_truthy(!isMatch('aab', '?')); + }); + + test('"aab" should not match "??"', () => { + expect_truthy(!isMatch('aab', '??')); + }); + + test('"aab" should not match "@(c)b"', () => { + expect_truthy(!isMatch('aab', '@(c)b')); + }); + + test('"aab" should match "a!(b)*"', () => { + expect_truthy(isMatch('aab', 'a!(b)*')); + }); + + test('"aab" should not match "a??b"', () => { + expect_truthy(!isMatch('aab', 'a??b')); + }); + + test('"aac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('aac', '*(@(a))a@(c)')); + }); + + test('"aac" should not match "*(@(a))b@(c)"', () => { + expect_truthy(!isMatch('aac', '*(@(a))b@(c)')); + }); + + test('"aax" should not match "a!(a*|b)"', () => { + expect_truthy(!isMatch('aax', 'a!(a*|b)')); + }); + + test('"aax" should match "a!(x*|b)"', () => { + expect_truthy(isMatch('aax', 'a!(x*|b)')); + }); + + test('"aax" should match "a?(a*|b)"', () => { + expect_truthy(isMatch('aax', 'a?(a*|b)')); + }); + + test('"aaz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('aaz', '[a*(]*z')); + }); + + test('"ab" should match "!(*.*)"', () => { + expect_truthy(isMatch('ab', '!(*.*)')); + }); + + test('"ab" should match "!(a!(b))"', () => { + expect_truthy(isMatch('ab', '!(a!(b))')); + }); + + test('"ab" should not match "!(a)*"', () => { + expect_truthy(!isMatch('ab', '!(a)*')); + }); + + test('"ab" should match "(a+|b)*"', () => { + expect_truthy(isMatch('ab', '(a+|b)*')); + }); + + test('"ab" should match "(a+|b)+"', () => { + expect_truthy(isMatch('ab', '(a+|b)+')); + }); + + test('"ab" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('ab', '*?(a)bc')); + }); + + test('"ab" should not match "a!(*(b|B))"', () => { + expect_truthy(!isMatch('ab', 'a!(*(b|B))')); + }); + + test('"ab" should not match "a!(@(b|B))"', () => { + expect_truthy(!isMatch('ab', 'a!(@(b|B))')); + }); + + test('"aB" should not match "a!(@(b|B))"', () => { + expect_truthy(!isMatch('aB', 'a!(@(b|B))')); + }); + + test('"ab" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('ab', 'a!(b)*')); + }); + + test('"ab" should not match "a(*b"', () => { + expect_truthy(!isMatch('ab', 'a(*b')); + }); + + test('"ab" should not match "a(b"', () => { + expect_truthy(!isMatch('ab', 'a(b')); + }); + + test('"ab" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('ab', 'a(b*(foo|bar))d')); + }); + + test('"ab" should not match "a/b"', () => { + expect_truthy(!isMatch('ab', 'a/b', { windows: true })); + }); + + test('"ab" should not match "a\\(b"', () => { + expect_truthy(!isMatch('ab', 'a\\(b')); + }); + + test('"ab" should match "ab*(e|f)"', () => { + expect_truthy(isMatch('ab', 'ab*(e|f)')); + }); + + test('"ab" should match "ab**"', () => { + expect_truthy(isMatch('ab', 'ab**')); + }); + + test('"ab" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('ab', 'ab**(e|f)')); + }); + + test('"ab" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('ab', 'ab**(e|f)g')); + }); + + test('"ab" should not match "ab***ef"', () => { + expect_truthy(!isMatch('ab', 'ab***ef')); + }); + + test('"ab" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab*+(e|f)')); + }); + + test('"ab" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab*d+(e|f)')); + }); + + test('"ab" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab?*(e|f)')); + }); + + test('"ab/cXd/efXg/hi" should match "**/*X*/**/*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '**/*X*/**/*i')); + }); + + test('"ab/cXd/efXg/hi" should match "*/*X*/*/*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '*/*X*/*/*i')); + }); + + test('"ab/cXd/efXg/hi" should not match "*X*i"', () => { + expect_truthy(!isMatch('ab/cXd/efXg/hi', '*X*i')); + }); + + test('"ab/cXd/efXg/hi" should not match "*Xg*i"', () => { + expect_truthy(!isMatch('ab/cXd/efXg/hi', '*Xg*i')); + }); + + test('"ab]" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('ab]', 'a!(@(b|B))')); + }); + + test('"abab" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abab', '(a+|b)*')); + }); + + test('"abab" should match "(a+|b)+"', () => { + expect_truthy(isMatch('abab', '(a+|b)+')); + }); + + test('"abab" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abab', '*?(a)bc')); + }); + + test('"abab" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abab', 'a(b*(foo|bar))d')); + }); + + test('"abab" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*(e|f)')); + }); + + test('"abab" should match "ab**"', () => { + expect_truthy(isMatch('abab', 'ab**')); + }); + + test('"abab" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abab', 'ab**(e|f)')); + }); + + test('"abab" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abab', 'ab**(e|f)g')); + }); + + test('"abab" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abab', 'ab***ef')); + }); + + test('"abab" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*+(e|f)')); + }); + + test('"abab" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*d+(e|f)')); + }); + + test('"abab" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab?*(e|f)')); + }); + + test('"abb" should match "!(*.*)"', () => { + expect_truthy(isMatch('abb', '!(*.*)')); + }); + + test('"abb" should not match "!(a)*"', () => { + expect_truthy(!isMatch('abb', '!(a)*')); + }); + + test('"abb" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('abb', 'a!(b)*')); + }); + + test('"abbcd" should match "@(ab|a*(b))*(c)d"', () => { + expect_truthy(isMatch('abbcd', '@(ab|a*(b))*(c)d')); + }); + + test('"abc" should not match "\\a\\b\\c"', () => { + expect_truthy(!isMatch('abc', '\\a\\b\\c')); + }); + + test('"aBc" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('aBc', 'a!(@(b|B))')); + }); + + test('"abcd" should match "?@(a|b)*@(c)d"', () => { + expect_truthy(isMatch('abcd', '?@(a|b)*@(c)d')); + }); + + test('"abcd" should match "@(ab|a*@(b))*(c)d"', () => { + expect_truthy(isMatch('abcd', '@(ab|a*@(b))*(c)d')); + }); + + test('"abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt" should match "**/*a*b*g*n*t"', () => { + expect_truthy(isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt', '**/*a*b*g*n*t')); + }); + + test('"abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz" should not match "**/*a*b*g*n*t"', () => { + expect_truthy(!isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz', '**/*a*b*g*n*t')); + }); + + test('"abcdef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcdef', '(a+|b)*')); + }); + + test('"abcdef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcdef', '(a+|b)+')); + }); + + test('"abcdef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcdef', '*?(a)bc')); + }); + + test('"abcdef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcdef', 'a(b*(foo|bar))d')); + }); + + test('"abcdef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcdef', 'ab*(e|f)')); + }); + + test('"abcdef" should match "ab**"', () => { + expect_truthy(isMatch('abcdef', 'ab**')); + }); + + test('"abcdef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab**(e|f)')); + }); + + test('"abcdef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abcdef', 'ab**(e|f)g')); + }); + + test('"abcdef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcdef', 'ab***ef')); + }); + + test('"abcdef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab*+(e|f)')); + }); + + test('"abcdef" should match "ab*d+(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab*d+(e|f)')); + }); + + test('"abcdef" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abcdef', 'ab?*(e|f)')); + }); + + test('"abcfef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcfef', '(a+|b)*')); + }); + + test('"abcfef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcfef', '(a+|b)+')); + }); + + test('"abcfef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcfef', '*?(a)bc')); + }); + + test('"abcfef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcfef', 'a(b*(foo|bar))d')); + }); + + test('"abcfef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcfef', 'ab*(e|f)')); + }); + + test('"abcfef" should match "ab**"', () => { + expect_truthy(isMatch('abcfef', 'ab**')); + }); + + test('"abcfef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab**(e|f)')); + }); + + test('"abcfef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abcfef', 'ab**(e|f)g')); + }); + + test('"abcfef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcfef', 'ab***ef')); + }); + + test('"abcfef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab*+(e|f)')); + }); + + test('"abcfef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abcfef', 'ab*d+(e|f)')); + }); + + test('"abcfef" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab?*(e|f)')); + }); + + test('"abcfefg" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcfefg', '(a+|b)*')); + }); + + test('"abcfefg" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcfefg', '(a+|b)+')); + }); + + test('"abcfefg" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcfefg', '*?(a)bc')); + }); + + test('"abcfefg" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcfefg', 'a(b*(foo|bar))d')); + }); + + test('"abcfefg" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*(e|f)')); + }); + + test('"abcfefg" should match "ab**"', () => { + expect_truthy(isMatch('abcfefg', 'ab**')); + }); + + test('"abcfefg" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcfefg', 'ab**(e|f)')); + }); + + test('"abcfefg" should match "ab**(e|f)g"', () => { + expect_truthy(isMatch('abcfefg', 'ab**(e|f)g')); + }); + + test('"abcfefg" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abcfefg', 'ab***ef')); + }); + + test('"abcfefg" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*+(e|f)')); + }); + + test('"abcfefg" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*d+(e|f)')); + }); + + test('"abcfefg" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab?*(e|f)')); + }); + + test('"abcx" should match "!([[*])*"', () => { + expect_truthy(isMatch('abcx', '!([[*])*')); + }); + + test('"abcx" should match "+(a|b\\[)*"', () => { + expect_truthy(isMatch('abcx', '+(a|b\\[)*')); + }); + + test('"abcx" should not match "[a*(]*z"', () => { + expect_truthy(!isMatch('abcx', '[a*(]*z')); + }); + + test('"abcXdefXghi" should match "*X*i"', () => { + expect_truthy(isMatch('abcXdefXghi', '*X*i')); + }); + + test('"abcz" should match "!([[*])*"', () => { + expect_truthy(isMatch('abcz', '!([[*])*')); + }); + + test('"abcz" should match "+(a|b\\[)*"', () => { + expect_truthy(isMatch('abcz', '+(a|b\\[)*')); + }); + + test('"abcz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('abcz', '[a*(]*z')); + }); + + test('"abd" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abd', '(a+|b)*')); + }); + + test('"abd" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abd', '(a+|b)+')); + }); + + test('"abd" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abd', '*?(a)bc')); + }); + + test('"abd" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('abd', 'a!(*(b|B))')); + }); + + test('"abd" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('abd', 'a!(@(b|B))')); + }); + + test('"abd" should not match "a!(@(b|B))d"', () => { + expect_truthy(!isMatch('abd', 'a!(@(b|B))d')); + }); + + test('"abd" should match "a(b*(foo|bar))d"', () => { + expect_truthy(isMatch('abd', 'a(b*(foo|bar))d')); + }); + + test('"abd" should match "a+(b|c)d"', () => { + expect_truthy(isMatch('abd', 'a+(b|c)d')); + }); + + test('"abd" should match "a[b*(foo|bar)]d"', () => { + expect_truthy(isMatch('abd', 'a[b*(foo|bar)]d')); + }); + + test('"abd" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*(e|f)')); + }); + + test('"abd" should match "ab**"', () => { + expect_truthy(isMatch('abd', 'ab**')); + }); + + test('"abd" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abd', 'ab**(e|f)')); + }); + + test('"abd" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abd', 'ab**(e|f)g')); + }); + + test('"abd" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abd', 'ab***ef')); + }); + + test('"abd" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*+(e|f)')); + }); + + test('"abd" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*d+(e|f)')); + }); + + test('"abd" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abd', 'ab?*(e|f)')); + }); + + test('"abef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abef', '(a+|b)*')); + }); + + test('"abef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abef', '(a+|b)+')); + }); + + test('"abef" should not match "*(a+|b)"', () => { + expect_truthy(!isMatch('abef', '*(a+|b)')); + }); + + test('"abef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abef', '*?(a)bc')); + }); + + test('"abef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abef', 'a(b*(foo|bar))d')); + }); + + test('"abef" should match "ab*(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab*(e|f)')); + }); + + test('"abef" should match "ab**"', () => { + expect_truthy(isMatch('abef', 'ab**')); + }); + + test('"abef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab**(e|f)')); + }); + + test('"abef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abef', 'ab**(e|f)g')); + }); + + test('"abef" should match "ab***ef"', () => { + expect_truthy(isMatch('abef', 'ab***ef')); + }); + + test('"abef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab*+(e|f)')); + }); + + test('"abef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abef', 'ab*d+(e|f)')); + }); + + test('"abef" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab?*(e|f)')); + }); + + test('"abz" should not match "a!(*)"', () => { + expect_truthy(!isMatch('abz', 'a!(*)')); + }); + + test('"abz" should match "a!(z)"', () => { + expect_truthy(isMatch('abz', 'a!(z)')); + }); + + test('"abz" should match "a*!(z)"', () => { + expect_truthy(isMatch('abz', 'a*!(z)')); + }); + + test('"abz" should not match "a*(z)"', () => { + expect_truthy(!isMatch('abz', 'a*(z)')); + }); + + test('"abz" should match "a**(z)"', () => { + expect_truthy(isMatch('abz', 'a**(z)')); + }); + + test('"abz" should match "a*@(z)"', () => { + expect_truthy(isMatch('abz', 'a*@(z)')); + }); + + test('"abz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('abz', 'a+(z)')); + }); + + test('"abz" should not match "a?(z)"', () => { + expect_truthy(!isMatch('abz', 'a?(z)')); + }); + + test('"abz" should not match "a@(z)"', () => { + expect_truthy(!isMatch('abz', 'a@(z)')); + }); + + test('"ac" should not match "!(a)*"', () => { + expect_truthy(!isMatch('ac', '!(a)*')); + }); + + test('"ac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('ac', '*(@(a))a@(c)')); + }); + + test('"ac" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('ac', 'a!(*(b|B))')); + }); + + test('"ac" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('ac', 'a!(@(b|B))')); + }); + + test('"ac" should match "a!(b)*"', () => { + expect_truthy(isMatch('ac', 'a!(b)*')); + }); + + test('"accdef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('accdef', '(a+|b)*')); + }); + + test('"accdef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('accdef', '(a+|b)+')); + }); + + test('"accdef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('accdef', '*?(a)bc')); + }); + + test('"accdef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('accdef', 'a(b*(foo|bar))d')); + }); + + test('"accdef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*(e|f)')); + }); + + test('"accdef" should not match "ab**"', () => { + expect_truthy(!isMatch('accdef', 'ab**')); + }); + + test('"accdef" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab**(e|f)')); + }); + + test('"accdef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('accdef', 'ab**(e|f)g')); + }); + + test('"accdef" should not match "ab***ef"', () => { + expect_truthy(!isMatch('accdef', 'ab***ef')); + }); + + test('"accdef" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*+(e|f)')); + }); + + test('"accdef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*d+(e|f)')); + }); + + test('"accdef" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab?*(e|f)')); + }); + + test('"acd" should match "(a+|b)*"', () => { + expect_truthy(isMatch('acd', '(a+|b)*')); + }); + + test('"acd" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('acd', '(a+|b)+')); + }); + + test('"acd" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('acd', '*?(a)bc')); + }); + + test('"acd" should match "@(ab|a*(b))*(c)d"', () => { + expect_truthy(isMatch('acd', '@(ab|a*(b))*(c)d')); + }); + + test('"acd" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('acd', 'a!(*(b|B))')); + }); + + test('"acd" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('acd', 'a!(@(b|B))')); + }); + + test('"acd" should match "a!(@(b|B))d"', () => { + expect_truthy(isMatch('acd', 'a!(@(b|B))d')); + }); + + test('"acd" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('acd', 'a(b*(foo|bar))d')); + }); + + test('"acd" should match "a+(b|c)d"', () => { + expect_truthy(isMatch('acd', 'a+(b|c)d')); + }); + + test('"acd" should not match "a[b*(foo|bar)]d"', () => { + expect_truthy(!isMatch('acd', 'a[b*(foo|bar)]d')); + }); + + test('"acd" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*(e|f)')); + }); + + test('"acd" should not match "ab**"', () => { + expect_truthy(!isMatch('acd', 'ab**')); + }); + + test('"acd" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab**(e|f)')); + }); + + test('"acd" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('acd', 'ab**(e|f)g')); + }); + + test('"acd" should not match "ab***ef"', () => { + expect_truthy(!isMatch('acd', 'ab***ef')); + }); + + test('"acd" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*+(e|f)')); + }); + + test('"acd" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*d+(e|f)')); + }); + + test('"acd" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab?*(e|f)')); + }); + + test('"ax" should match "?(a*|b)"', () => { + expect_truthy(isMatch('ax', '?(a*|b)')); + }); + + test('"ax" should not match "a?(b*)"', () => { + expect_truthy(!isMatch('ax', 'a?(b*)')); + }); + + test('"axz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('axz', 'a+(z)')); + }); + + test('"az" should not match "a!(*)"', () => { + expect_truthy(!isMatch('az', 'a!(*)')); + }); + + test('"az" should not match "a!(z)"', () => { + expect_truthy(!isMatch('az', 'a!(z)')); + }); + + test('"az" should match "a*!(z)"', () => { + expect_truthy(isMatch('az', 'a*!(z)')); + }); + + test('"az" should match "a*(z)"', () => { + expect_truthy(isMatch('az', 'a*(z)')); + }); + + test('"az" should match "a**(z)"', () => { + expect_truthy(isMatch('az', 'a**(z)')); + }); + + test('"az" should match "a*@(z)"', () => { + expect_truthy(isMatch('az', 'a*@(z)')); + }); + + test('"az" should match "a+(z)"', () => { + expect_truthy(isMatch('az', 'a+(z)')); + }); + + test('"az" should match "a?(z)"', () => { + expect_truthy(isMatch('az', 'a?(z)')); + }); + + test('"az" should match "a@(z)"', () => { + expect_truthy(isMatch('az', 'a@(z)')); + }); + + test('"az" should not match "a\\z"', () => { + expect_truthy(!isMatch('az', 'a\\\\z', { windows: false })); + }); + + test('"az" should not match "a\\z"', () => { + expect_truthy(!isMatch('az', 'a\\\\z')); + }); + + test('"b" should match "!(a)*"', () => { + expect_truthy(isMatch('b', '!(a)*')); + }); + + test('"b" should match "(a+|b)*"', () => { + expect_truthy(isMatch('b', '(a+|b)*')); + }); + + test('"b" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('b', 'a!(b)*')); + }); + + test('"b.a" should match "(b|a).(a)"', () => { + expect_truthy(isMatch('b.a', '(b|a).(a)')); + }); + + test('"b.a" should match "@(b|a).@(a)"', () => { + expect_truthy(isMatch('b.a', '@(b|a).@(a)')); + }); + + test('"b/a" should not match "!(b/a)"', () => { + expect_truthy(!isMatch('b/a', '!(b/a)')); + }); + + test('"b/b" should match "!(b/a)"', () => { + expect_truthy(isMatch('b/b', '!(b/a)')); + }); + + test('"b/c" should match "!(b/a)"', () => { + expect_truthy(isMatch('b/c', '!(b/a)')); + }); + + test('"b/c" should not match "b/!(c)"', () => { + expect_truthy(!isMatch('b/c', 'b/!(c)')); + }); + + test('"b/c" should match "b/!(cc)"', () => { + expect_truthy(isMatch('b/c', 'b/!(cc)')); + }); + + test('"b/c.txt" should not match "b/!(c).txt"', () => { + expect_truthy(!isMatch('b/c.txt', 'b/!(c).txt')); + }); + + test('"b/c.txt" should match "b/!(cc).txt"', () => { + expect_truthy(isMatch('b/c.txt', 'b/!(cc).txt')); + }); + + test('"b/cc" should match "b/!(c)"', () => { + expect_truthy(isMatch('b/cc', 'b/!(c)')); + }); + + test('"b/cc" should not match "b/!(cc)"', () => { + expect_truthy(!isMatch('b/cc', 'b/!(cc)')); + }); + + test('"b/cc.txt" should not match "b/!(c).txt"', () => { + expect_truthy(!isMatch('b/cc.txt', 'b/!(c).txt')); + }); + + test('"b/cc.txt" should not match "b/!(cc).txt"', () => { + expect_truthy(!isMatch('b/cc.txt', 'b/!(cc).txt')); + }); + + test('"b/ccc" should match "b/!(c)"', () => { + expect_truthy(isMatch('b/ccc', 'b/!(c)')); + }); + + test('"ba" should match "!(a!(b))"', () => { + expect_truthy(isMatch('ba', '!(a!(b))')); + }); + + test('"ba" should match "b?(a|b)"', () => { + expect_truthy(isMatch('ba', 'b?(a|b)')); + }); + + test('"baaac" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('baaac', '*(@(a))a@(c)')); + }); + + test('"bar" should match "!(foo)"', () => { + expect_truthy(isMatch('bar', '!(foo)')); + }); + + test('"bar" should match "!(foo)*"', () => { + expect_truthy(isMatch('bar', '!(foo)*')); + }); + + test('"bar" should match "!(foo)b*"', () => { + expect_truthy(isMatch('bar', '!(foo)b*')); + }); + + test('"bar" should match "*(!(foo))"', () => { + expect_truthy(isMatch('bar', '*(!(foo))')); + }); + + test('"baz" should match "!(foo)*"', () => { + expect_truthy(isMatch('baz', '!(foo)*')); + }); + + test('"baz" should match "!(foo)b*"', () => { + expect_truthy(isMatch('baz', '!(foo)b*')); + }); + + test('"baz" should match "*(!(foo))"', () => { + expect_truthy(isMatch('baz', '*(!(foo))')); + }); + + test('"bb" should match "!(a!(b))"', () => { + expect_truthy(isMatch('bb', '!(a!(b))')); + }); + + test('"bb" should match "!(a)*"', () => { + expect_truthy(isMatch('bb', '!(a)*')); + }); + + test('"bb" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('bb', 'a!(b)*')); + }); + + test('"bb" should not match "a?(a|b)"', () => { + expect_truthy(!isMatch('bb', 'a?(a|b)')); + }); + + test('"bbc" should match "!([[*])*"', () => { + expect_truthy(isMatch('bbc', '!([[*])*')); + }); + + test('"bbc" should not match "+(a|b\\[)*"', () => { + expect_truthy(!isMatch('bbc', '+(a|b\\[)*')); + }); + + test('"bbc" should not match "[a*(]*z"', () => { + expect_truthy(!isMatch('bbc', '[a*(]*z')); + }); + + test('"bz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('bz', 'a+(z)')); + }); + + test('"c" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('c', '*(@(a))a@(c)')); + }); + + test('"c.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('c.a', '!(*.[a-b]*)')); + }); + + test('"c.a" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('c.a', '!(*[a-b].[a-b]*)')); + }); + + test('"c.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('c.a', '!*.(a|b)')); + }); + + test('"c.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('c.a', '!*.(a|b)*')); + }); + + test('"c.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('c.a', '(b|a).(a)')); + }); + + test('"c.a" should not match "*.!(a)"', () => { + expect_truthy(!isMatch('c.a', '*.!(a)')); + }); + + test('"c.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('c.a', '*.+(b|d)')); + }); + + test('"c.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('c.a', '@(b|a).@(a)')); + }); + + test('"c.c" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('c.c', '!(*.a|*.b|*.c)')); + }); + + test('"c.c" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('c.c', '*!(.a|.b|.c)')); + }); + + test('"c.c" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('c.c', '*.!(a|b|c)')); + }); + + test('"c.c" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('c.c', '*.(a|b|@(ab|a*@(b))*(c)d)')); + }); + + test('"c.ccc" should match "!(*.[a-b]*)"', () => { + expect_truthy(isMatch('c.ccc', '!(*.[a-b]*)')); + }); + + test('"c.ccc" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('c.ccc', '!(*[a-b].[a-b]*)')); + }); + + test('"c.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('c.js', '!(*.js)')); + }); + + test('"c.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('c.js', '*!(.js)')); + }); + + test('"c.js" should not match "*.!(js)"', () => { + expect_truthy(!isMatch('c.js', '*.!(js)')); + }); + + test('"c/a/v" should match "c/!(z)/v"', () => { + expect_truthy(isMatch('c/a/v', 'c/!(z)/v')); + }); + + test('"c/a/v" should not match "c/*(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/*(z)/v')); + }); + + test('"c/a/v" should not match "c/+(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/+(z)/v')); + }); + + test('"c/a/v" should not match "c/@(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/@(z)/v')); + }); + + test('"c/z/v" should not match "*(z)"', () => { + expect_truthy(!isMatch('c/z/v', '*(z)')); + }); + + test('"c/z/v" should not match "+(z)"', () => { + expect_truthy(!isMatch('c/z/v', '+(z)')); + }); + + test('"c/z/v" should not match "?(z)"', () => { + expect_truthy(!isMatch('c/z/v', '?(z)')); + }); + + test('"c/z/v" should not match "c/!(z)/v"', () => { + expect_truthy(!isMatch('c/z/v', 'c/!(z)/v')); + }); + + test('"c/z/v" should match "c/*(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/*(z)/v')); + }); + + test('"c/z/v" should match "c/+(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/+(z)/v')); + }); + + test('"c/z/v" should match "c/@(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/@(z)/v')); + }); + + test('"c/z/v" should match "c/z/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/z/v')); + }); + + test('"cc.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('cc.a', '(b|a).(a)')); + }); + + test('"cc.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('cc.a', '@(b|a).@(a)')); + }); + + test('"ccc" should match "!(a)*"', () => { + expect_truthy(isMatch('ccc', '!(a)*')); + }); + + test('"ccc" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('ccc', 'a!(b)*')); + }); + + test('"cow" should match "!(*.*)"', () => { + expect_truthy(isMatch('cow', '!(*.*)')); + }); + + test('"cow" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('cow', '!(*.*).')); + }); + + test('"cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('cow', '.!(*.*)')); + }); + + test('"cz" should not match "a!(*)"', () => { + expect_truthy(!isMatch('cz', 'a!(*)')); + }); + + test('"cz" should not match "a!(z)"', () => { + expect_truthy(!isMatch('cz', 'a!(z)')); + }); + + test('"cz" should not match "a*!(z)"', () => { + expect_truthy(!isMatch('cz', 'a*!(z)')); + }); + + test('"cz" should not match "a*(z)"', () => { + expect_truthy(!isMatch('cz', 'a*(z)')); + }); + + test('"cz" should not match "a**(z)"', () => { + expect_truthy(!isMatch('cz', 'a**(z)')); + }); + + test('"cz" should not match "a*@(z)"', () => { + expect_truthy(!isMatch('cz', 'a*@(z)')); + }); + + test('"cz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('cz', 'a+(z)')); + }); + + test('"cz" should not match "a?(z)"', () => { + expect_truthy(!isMatch('cz', 'a?(z)')); + }); + + test('"cz" should not match "a@(z)"', () => { + expect_truthy(!isMatch('cz', 'a@(z)')); + }); + + test('"d.a.d" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('d.a.d', '!(*.[a-b]*)')); + }); + + test('"d.a.d" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('d.a.d', '!(*[a-b].[a-b]*)')); + }); + + test('"d.a.d" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('d.a.d', '!*.(a|b)*')); + }); + + test('"d.a.d" should match "!*.*(a|b)"', () => { + expect_truthy(isMatch('d.a.d', '!*.*(a|b)')); + }); + + test('"d.a.d" should not match "!*.{a,b}*"', () => { + expect_truthy(!isMatch('d.a.d', '!*.{a,b}*')); + }); + + test('"d.a.d" should match "*.!(a)"', () => { + expect_truthy(isMatch('d.a.d', '*.!(a)')); + }); + + test('"d.a.d" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('d.a.d', '*.+(b|d)')); + }); + + test('"d.d" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('d.d', '!(*.a|*.b|*.c)')); + }); + + test('"d.d" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('d.d', '*!(.a|.b|.c)')); + }); + + test('"d.d" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('d.d', '*.!(a|b|c)')); + }); + + test('"d.d" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('d.d', '*.(a|b|@(ab|a*@(b))*(c)d)')); + }); + + test('"d.js.d" should match "!(*.js)"', () => { + expect_truthy(isMatch('d.js.d', '!(*.js)')); + }); + + test('"d.js.d" should match "*!(.js)"', () => { + expect_truthy(isMatch('d.js.d', '*!(.js)')); + }); + + test('"d.js.d" should match "*.!(js)"', () => { + expect_truthy(isMatch('d.js.d', '*.!(js)')); + }); + + test('"dd.aa.d" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('dd.aa.d', '(b|a).(a)')); + }); + + test('"dd.aa.d" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('dd.aa.d', '@(b|a).@(a)')); + }); + + test('"def" should not match "()ef"', () => { + expect_truthy(!isMatch('def', '()ef')); + }); + + test('"e.e" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('e.e', '!(*.a|*.b|*.c)')); + }); + + test('"e.e" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('e.e', '*!(.a|.b|.c)')); + }); + + test('"e.e" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('e.e', '*.!(a|b|c)')); + }); + + test('"e.e" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('e.e', '*.(a|b|@(ab|a*@(b))*(c)d)')); + }); + + test('"ef" should match "()ef"', () => { + expect_truthy(isMatch('ef', '()ef')); + }); + + test('"effgz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('effgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + }); + + test('"efgz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('efgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + }); + + test('"egz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('egz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + }); + + test('"egz" should not match "@(b+(c)d|e+(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(!isMatch('egz', '@(b+(c)d|e+(f)g?|?(h)i@(j|k))')); + }); + + test('"egzefffgzbcdij" should match "*(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('egzefffgzbcdij', '*(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + }); + + test('"f" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('f', '!(f!(o))')); + }); + + test('"f" should match "!(f(o))"', () => { + expect_truthy(isMatch('f', '!(f(o))')); + }); + + test('"f" should not match "!(f)"', () => { + expect_truthy(!isMatch('f', '!(f)')); + }); + + test('"f" should not match "*(!(f))"', () => { + expect_truthy(!isMatch('f', '*(!(f))')); + }); + + test('"f" should not match "+(!(f))"', () => { + expect_truthy(!isMatch('f', '+(!(f))')); + }); + + test('"f.a" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('f.a', '!(*.a|*.b|*.c)')); + }); + + test('"f.a" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('f.a', '*!(.a|.b|.c)')); + }); + + test('"f.a" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('f.a', '*.!(a|b|c)')); + }); + + test('"f.f" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('f.f', '!(*.a|*.b|*.c)')); + }); + + test('"f.f" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('f.f', '*!(.a|.b|.c)')); + }); + + test('"f.f" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('f.f', '*.!(a|b|c)')); + }); + + test('"f.f" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('f.f', '*.(a|b|@(ab|a*@(b))*(c)d)')); + }); + + test('"fa" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('fa', '!(f!(o))')); + }); + + test('"fa" should match "!(f(o))"', () => { + expect_truthy(isMatch('fa', '!(f(o))')); + }); + + test('"fb" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('fb', '!(f!(o))')); + }); + + test('"fb" should match "!(f(o))"', () => { + expect_truthy(isMatch('fb', '!(f(o))')); + }); + + test('"fff" should match "!(f)"', () => { + expect_truthy(isMatch('fff', '!(f)')); + }); + + test('"fff" should match "*(!(f))"', () => { + expect_truthy(isMatch('fff', '*(!(f))')); + }); + + test('"fff" should match "+(!(f))"', () => { + expect_truthy(isMatch('fff', '+(!(f))')); + }); + + test('"fffooofoooooffoofffooofff" should match "*(*(f)*(o))"', () => { + expect_truthy(isMatch('fffooofoooooffoofffooofff', '*(*(f)*(o))')); + }); + + test('"ffo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('ffo', '*(f*(o))')); + }); + + test('"file.C" should not match "*.c?(c)"', () => { + expect_truthy(!isMatch('file.C', '*.c?(c)')); + }); + + test('"file.c" should match "*.c?(c)"', () => { + expect_truthy(isMatch('file.c', '*.c?(c)')); + }); + + test('"file.cc" should match "*.c?(c)"', () => { + expect_truthy(isMatch('file.cc', '*.c?(c)')); + }); + + test('"file.ccc" should not match "*.c?(c)"', () => { + expect_truthy(!isMatch('file.ccc', '*.c?(c)')); + }); + + test('"fo" should match "!(f!(o))"', () => { + expect_truthy(isMatch('fo', '!(f!(o))')); + }); + + test('"fo" should not match "!(f(o))"', () => { + expect_truthy(!isMatch('fo', '!(f(o))')); + }); + + test('"fofo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('fofo', '*(f*(o))')); + }); + + test('"fofoofoofofoo" should match "*(fo|foo)"', () => { + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)')); + }); + + test('"fofoofoofofoo" should match "*(fo|foo)"', () => { + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)')); + }); + + test('"foo" should match "!(!(foo))"', () => { + expect_truthy(isMatch('foo', '!(!(foo))')); + }); + + test('"foo" should match "!(f)"', () => { + expect_truthy(isMatch('foo', '!(f)')); + }); + + test('"foo" should not match "!(foo)"', () => { + expect_truthy(!isMatch('foo', '!(foo)')); + }); + + test('"foo" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foo', '!(foo)*')); + }); + + test('"foo" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foo', '!(foo)*')); + }); + + test('"foo" should not match "!(foo)+"', () => { + expect_truthy(!isMatch('foo', '!(foo)+')); + }); + + test('"foo" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foo', '!(foo)b*')); + }); + + test('"foo" should match "!(x)"', () => { + expect_truthy(isMatch('foo', '!(x)')); + }); + + test('"foo" should match "!(x)*"', () => { + expect_truthy(isMatch('foo', '!(x)*')); + }); + + test('"foo" should match "*"', () => { + expect_truthy(isMatch('foo', '*')); + }); + + test('"foo" should match "*(!(f))"', () => { + expect_truthy(isMatch('foo', '*(!(f))')); + }); + + test('"foo" should not match "*(!(foo))"', () => { + expect_truthy(!isMatch('foo', '*(!(foo))')); + }); + + test('"foo" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('foo', '*(@(a))a@(c)')); + }); + + test('"foo" should match "*(@(foo))"', () => { + expect_truthy(isMatch('foo', '*(@(foo))')); + }); + + test('"foo" should not match "*(a|b\\[)"', () => { + expect_truthy(!isMatch('foo', '*(a|b\\[)')); + }); + + test('"foo" should match "*(a|b\\[)|f*"', () => { + expect_truthy(isMatch('foo', '*(a|b\\[)|f*')); + }); + + test('"foo" should match "@(*(a|b\\[)|f*)"', () => { + expect_truthy(isMatch('foo', '@(*(a|b\\[)|f*)')); + }); + + test('"foo" should not match "*/*/*"', () => { + expect_truthy(!isMatch('foo', '*/*/*')); + }); + + test('"foo" should not match "*f"', () => { + expect_truthy(!isMatch('foo', '*f')); + }); + + test('"foo" should match "*foo*"', () => { + expect_truthy(isMatch('foo', '*foo*')); + }); + + test('"foo" should match "+(!(f))"', () => { + expect_truthy(isMatch('foo', '+(!(f))')); + }); + + test('"foo" should not match "??"', () => { + expect_truthy(!isMatch('foo', '??')); + }); + + test('"foo" should match "???"', () => { + expect_truthy(isMatch('foo', '???')); + }); + + test('"foo" should not match "bar"', () => { + expect_truthy(!isMatch('foo', 'bar')); + }); + + test('"foo" should match "f*"', () => { + expect_truthy(isMatch('foo', 'f*')); + }); + + test('"foo" should not match "fo"', () => { + expect_truthy(!isMatch('foo', 'fo')); + }); + + test('"foo" should match "foo"', () => { + expect_truthy(isMatch('foo', 'foo')); + }); + + test('"foo" should match "{*(a|b\\[),f*}"', () => { + expect_truthy(isMatch('foo', '{*(a|b\\[),f*}')); + }); + + test('"foo*" should match "foo\\*"', () => { + expect_truthy(isMatch('foo*', 'foo\\*', { windows: false })); + }); + + test('"foo*bar" should match "foo\\*bar"', () => { + expect_truthy(isMatch('foo*bar', 'foo\\*bar')); + }); + + test('"foo.js" should not match "!(foo).js"', () => { + expect_truthy(!isMatch('foo.js', '!(foo).js')); + }); + + test('"foo.js.js" should match "*.!(js)"', () => { + expect_truthy(isMatch('foo.js.js', '*.!(js)')); + }); + + test('"foo.js.js" should not match "*.!(js)*"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)*')); + }); + + test('"foo.js.js" should not match "*.!(js)*.!(js)"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)*.!(js)')); + }); + + test('"foo.js.js" should not match "*.!(js)+"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)+')); + }); + + test('"foo.txt" should match "**/!(bar).txt"', () => { + expect_truthy(isMatch('foo.txt', '**/!(bar).txt')); + }); + + test('"foo/bar" should not match "*/*/*"', () => { + expect_truthy(!isMatch('foo/bar', '*/*/*')); + }); + + test('"foo/bar" should match "foo/!(foo)"', () => { + expect_truthy(isMatch('foo/bar', 'foo/!(foo)')); + }); + + test('"foo/bar" should match "foo/*"', () => { + expect_truthy(isMatch('foo/bar', 'foo/*')); + }); + + test('"foo/bar" should match "foo/bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo/bar')); + }); + + test('"foo/bar" should not match "foo?bar"', () => { + expect_truthy(!isMatch('foo/bar', 'foo?bar')); + }); + + test('"foo/bar" should match "foo[/]bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo[/]bar')); + }); + + test('"foo/bar/baz.jsx" should match "foo/bar/**/*.+(js|jsx)"', () => { + expect_truthy(isMatch('foo/bar/baz.jsx', 'foo/bar/**/*.+(js|jsx)')); + }); + + test('"foo/bar/baz.jsx" should match "foo/bar/*.+(js|jsx)"', () => { + expect_truthy(isMatch('foo/bar/baz.jsx', 'foo/bar/*.+(js|jsx)')); + }); + + test('"foo/bb/aa/rr" should match "**/**/**"', () => { + expect_truthy(isMatch('foo/bb/aa/rr', '**/**/**')); + }); + + test('"foo/bb/aa/rr" should not match "*/*/*"', () => { + expect_truthy(!isMatch('foo/bb/aa/rr', '*/*/*')); + }); + + test('"foo/bba/arr" should match "*/*/*"', () => { + expect_truthy(isMatch('foo/bba/arr', '*/*/*')); + }); + + test('"foo/bba/arr" should not match "foo*"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo*')); + }); + + test('"foo/bba/arr" should not match "foo**"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo**')); + }); + + test('"foo/bba/arr" should not match "foo/*"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/*')); + }); + + test('"foo/bba/arr" should match "foo/**"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo/**')); + }); + + test('"foo/bba/arr" should not match "foo/**arr"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/**arr')); + }); + + test('"foo/bba/arr" should not match "foo/**z"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/**z')); + }); + + test('"foo/bba/arr" should not match "foo/*arr"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/*arr')); + }); + + test('"foo/bba/arr" should not match "foo/*z"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/*z')); + }); + + test('"foob" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foob', '!(foo)b*')); + }); + + test('"foob" should not match "(foo)bb"', () => { + expect_truthy(!isMatch('foob', '(foo)bb')); + }); + + test('"foobar" should match "!(foo)"', () => { + expect_truthy(isMatch('foobar', '!(foo)')); + }); + + test('"foobar" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foobar', '!(foo)*')); + }); + + test('"foobar" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foobar', '!(foo)*')); + }); + + test('"foobar" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foobar', '!(foo)b*')); + }); + + test('"foobar" should match "*(!(foo))"', () => { + expect_truthy(isMatch('foobar', '*(!(foo))')); + }); + + test('"foobar" should match "*ob*a*r*"', () => { + expect_truthy(isMatch('foobar', '*ob*a*r*')); + }); + + test('"foobar" should not match "foo\\*bar"', () => { + expect_truthy(!isMatch('foobar', 'foo\\*bar')); + }); + + test('"foobb" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foobb', '!(foo)b*')); + }); + + test('"foobb" should match "(foo)bb"', () => { + expect_truthy(isMatch('foobb', '(foo)bb')); + }); + + test('"(foo)bb" should match "\\(foo\\)bb"', () => { + expect_truthy(isMatch('(foo)bb', '\\(foo\\)bb')); + }); + + test('"foofoofo" should match "@(foo|f|fo)*(f|of+(o))"', () => { + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))')); + }); + + test('"foofoofo" should match "@(foo|f|fo)*(f|of+(o))"', () => { + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))')); + }); + + test('"fooofoofofooo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('fooofoofofooo', '*(f*(o))')); + }); + + test('"foooofo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('foooofo', '*(f*(o))')); + }); + + test('"foooofof" should match "*(f*(o))"', () => { + expect_truthy(isMatch('foooofof', '*(f*(o))')); + }); + + test('"foooofof" should not match "*(f+(o))"', () => { + expect_truthy(!isMatch('foooofof', '*(f+(o))')); + }); + + test('"foooofofx" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('foooofofx', '*(f*(o))')); + }); + + test('"foooxfooxfoxfooox" should match "*(f*(o)x)"', () => { + expect_truthy(isMatch('foooxfooxfoxfooox', '*(f*(o)x)')); + }); + + test('"foooxfooxfxfooox" should match "*(f*(o)x)"', () => { + expect_truthy(isMatch('foooxfooxfxfooox', '*(f*(o)x)')); + }); + + test('"foooxfooxofoxfooox" should not match "*(f*(o)x)"', () => { + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f*(o)x)')); + }); + + test('"foot" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('foot', '@(!(z*)|*x)')); + }); + + test('"foox" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('foox', '@(!(z*)|*x)')); + }); + + test('"fz" should not match "*(z)"', () => { + expect_truthy(!isMatch('fz', '*(z)')); + }); + + test('"fz" should not match "+(z)"', () => { + expect_truthy(!isMatch('fz', '+(z)')); + }); + + test('"fz" should not match "?(z)"', () => { + expect_truthy(!isMatch('fz', '?(z)')); + }); + + test('"moo.cow" should not match "!(moo).!(cow)"', () => { + expect_truthy(!isMatch('moo.cow', '!(moo).!(cow)')); + }); + + test('"moo.cow" should not match "!(*).!(*)"', () => { + expect_truthy(!isMatch('moo.cow', '!(*).!(*)')); + }); + + test('"moo.cow" should not match "!(*.*).!(*.*)"', () => { + expect_truthy(!isMatch('moo.cow', '!(*.*).!(*.*)')); + }); + + test('"mad.moo.cow" should not match "!(*.*).!(*.*)"', () => { + expect_truthy(!isMatch('mad.moo.cow', '!(*.*).!(*.*)')); + }); + + test('"mad.moo.cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('mad.moo.cow', '.!(*.*)')); + }); + + test('"Makefile" should match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(isMatch('Makefile', '!(*.c|*.h|Makefile.in|config*|README)')); + }); + + test('"Makefile.in" should not match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(!isMatch('Makefile.in', '!(*.c|*.h|Makefile.in|config*|README)')); + }); + + test('"moo" should match "!(*.*)"', () => { + expect_truthy(isMatch('moo', '!(*.*)')); + }); + + test('"moo" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('moo', '!(*.*).')); + }); + + test('"moo" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('moo', '.!(*.*)')); + }); + + test('"moo.cow" should not match "!(*.*)"', () => { + expect_truthy(!isMatch('moo.cow', '!(*.*)')); + }); + + test('"moo.cow" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('moo.cow', '!(*.*).')); + }); + + test('"moo.cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('moo.cow', '.!(*.*)')); + }); + + test('"mucca.pazza" should not match "mu!(*(c))?.pa!(*(z))?"', () => { + expect_truthy(!isMatch('mucca.pazza', 'mu!(*(c))?.pa!(*(z))?')); + }); + + test('"ofoofo" should match "*(of+(o))"', () => { + expect_truthy(isMatch('ofoofo', '*(of+(o))')); + }); + + test('"ofoofo" should match "*(of+(o)|f)"', () => { + expect_truthy(isMatch('ofoofo', '*(of+(o)|f)')); + }); + + test('"ofooofoofofooo" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('ofooofoofofooo', '*(f*(o))')); + }); + + test('"ofoooxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxo', '*(*(of*(o)x)o)')); + }); + + test('"ofoooxoofxoofoooxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxo', '*(*(of*(o)x)o)')); + }); + + test('"ofoooxoofxoofoooxoofxofo" should not match "*(*(of*(o)x)o)"', () => { + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(*(of*(o)x)o)')); + }); + + test('"ofoooxoofxoofoooxoofxoo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxoo', '*(*(of*(o)x)o)')); + }); + + test('"ofoooxoofxoofoooxoofxooofxofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(*(of*(o)x)o)')); + }); + + test('"ofxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofxoofxo', '*(*(of*(o)x)o)')); + }); + + test('"oofooofo" should match "*(of|oof+(o))"', () => { + expect_truthy(isMatch('oofooofo', '*(of|oof+(o))')); + }); + + test('"ooo" should match "!(f)"', () => { + expect_truthy(isMatch('ooo', '!(f)')); + }); + + test('"ooo" should match "*(!(f))"', () => { + expect_truthy(isMatch('ooo', '*(!(f))')); + }); + + test('"ooo" should match "+(!(f))"', () => { + expect_truthy(isMatch('ooo', '+(!(f))')); + }); + + test('"oxfoxfox" should not match "*(oxf+(ox))"', () => { + expect_truthy(!isMatch('oxfoxfox', '*(oxf+(ox))')); + }); + + test('"oxfoxoxfox" should match "*(oxf+(ox))"', () => { + expect_truthy(isMatch('oxfoxoxfox', '*(oxf+(ox))')); + }); + + test('"para" should match "para*([0-9])"', () => { + expect_truthy(isMatch('para', 'para*([0-9])')); + }); + + test('"para" should not match "para+([0-9])"', () => { + expect_truthy(!isMatch('para', 'para+([0-9])')); + }); + + test('"para.38" should match "para!(*.[00-09])"', () => { + expect_truthy(isMatch('para.38', 'para!(*.[00-09])')); + }); + + test('"para.graph" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('para.graph', 'para!(*.[0-9])')); + }); + + test('"para13829383746592" should match "para*([0-9])"', () => { + expect_truthy(isMatch('para13829383746592', 'para*([0-9])')); + }); + + test('"para381" should not match "para?([345]|99)1"', () => { + expect_truthy(!isMatch('para381', 'para?([345]|99)1')); + }); + + test('"para39" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('para39', 'para!(*.[0-9])')); + }); + + test('"para987346523" should match "para+([0-9])"', () => { + expect_truthy(isMatch('para987346523', 'para+([0-9])')); + }); + + test('"para991" should match "para?([345]|99)1"', () => { + expect_truthy(isMatch('para991', 'para?([345]|99)1')); + }); + + test('"paragraph" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('paragraph', 'para!(*.[0-9])')); + }); + + test('"paragraph" should not match "para*([0-9])"', () => { + expect_truthy(!isMatch('paragraph', 'para*([0-9])')); + }); + + test('"paragraph" should match "para@(chute|graph)"', () => { + expect_truthy(isMatch('paragraph', 'para@(chute|graph)')); + }); + + test('"paramour" should not match "para@(chute|graph)"', () => { + expect_truthy(!isMatch('paramour', 'para@(chute|graph)')); + }); + + test('"parse.y" should match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(isMatch('parse.y', '!(*.c|*.h|Makefile.in|config*|README)')); + }); + + test('"shell.c" should not match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(!isMatch('shell.c', '!(*.c|*.h|Makefile.in|config*|README)')); + }); + + test('"VMS.FILE;" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;', '*\\;[1-9]*([0-9])')); + }); + + test('"VMS.FILE;0" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;0', '*\\;[1-9]*([0-9])')); + }); + + test('"VMS.FILE;9" should match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;9', '*\\;[1-9]*([0-9])')); + }); + + test('"VMS.FILE;1" should match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;1', '*\\;[1-9]*([0-9])')); + }); + + test('"VMS.FILE;1" should match "*;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;1', '*;[1-9]*([0-9])')); + }); + + test('"VMS.FILE;139" should match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;139', '*\\;[1-9]*([0-9])')); + }); + + test('"VMS.FILE;1N" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;1N', '*\\;[1-9]*([0-9])')); + }); + + test('"xfoooofof" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('xfoooofof', '*(f*(o))')); + }); + + test('"XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1" should match "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*"', () => { + expect_truthy(isMatch('XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1', 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*', { windows: false })); + }); + + test('"XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1" should not match "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*"', () => { + expect_truthy(!isMatch('XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1', 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*')); + }); + + test('"z" should match "*(z)"', () => { + expect_truthy(isMatch('z', '*(z)')); + }); + + test('"z" should match "+(z)"', () => { + expect_truthy(isMatch('z', '+(z)')); + }); + + test('"z" should match "?(z)"', () => { + expect_truthy(isMatch('z', '?(z)')); + }); + + test('"zf" should not match "*(z)"', () => { + expect_truthy(!isMatch('zf', '*(z)')); + }); + + test('"zf" should not match "+(z)"', () => { + expect_truthy(!isMatch('zf', '+(z)')); + }); + + test('"zf" should not match "?(z)"', () => { + expect_truthy(!isMatch('zf', '?(z)')); + }); + + test('"zoot" should not match "@(!(z*)|*x)"', () => { + expect_truthy(!isMatch('zoot', '@(!(z*)|*x)')); + }); + + test('"zoox" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('zoox', '@(!(z*)|*x)')); + }); + + test('"zz" should not match "(a+|b)*"', () => { + expect_truthy(!isMatch('zz', '(a+|b)*')); + }); +}); diff --git a/packages/node-utils/test/micromatch/extglobs.test.ts b/packages/node-utils/test/micromatch/extglobs.test.ts new file mode 100644 index 0000000..b081305 --- /dev/null +++ b/packages/node-utils/test/micromatch/extglobs.test.ts @@ -0,0 +1,1885 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; +const { isMatch } = mm; + +/** + * Most of these tests were converted directly from bash 4.3 and 4.4 unit tests. + */ + +describe('extglobs', () => { + test('should throw on imbalanced sets when `options.strictBrackets` is true', () => { + expect_throws(() => mm.makeRe('a(b', { strictBrackets: true }), /missing closing: "\)"/i); + expect_throws(() => mm.makeRe('a)b', { strictBrackets: true }), /missing opening: "\("/i); + }); + + test('should match extglobs ending with statechar', () => { + expect_truthy(!isMatch('ax', 'a?(b*)')); + expect_truthy(isMatch('ax', '?(a*|b)')); + }); + + test('should not choke on non-extglobs', () => { + expect_truthy(isMatch('c/z/v', 'c/z/v')); + }); + + test('should work with file extensions', () => { + expect_truthy(!isMatch('.md', '@(a|b).md')); + expect_truthy(!isMatch('a.js', '@(a|b).md')); + expect_truthy(isMatch('a.md', '@(a|b).md')); + expect_truthy(isMatch('b.md', '@(a|b).md')); + expect_truthy(!isMatch('c.md', '@(a|b).md')); + + expect_truthy(!isMatch('.md', '+(a|b).md')); + expect_truthy(!isMatch('a.js', '+(a|b).md')); + expect_truthy(isMatch('a.md', '+(a|b).md')); + expect_truthy(isMatch('aa.md', '+(a|b).md')); + expect_truthy(isMatch('ab.md', '+(a|b).md')); + expect_truthy(isMatch('b.md', '+(a|b).md')); + expect_truthy(isMatch('bb.md', '+(a|b).md')); + expect_truthy(!isMatch('c.md', '+(a|b).md')); + + expect_truthy(isMatch('.md', '*(a|b).md')); + expect_truthy(!isMatch('a.js', '*(a|b).md')); + expect_truthy(isMatch('a.md', '*(a|b).md')); + expect_truthy(isMatch('aa.md', '*(a|b).md')); + expect_truthy(isMatch('ab.md', '*(a|b).md')); + expect_truthy(isMatch('b.md', '*(a|b).md')); + expect_truthy(isMatch('bb.md', '*(a|b).md')); + expect_truthy(!isMatch('c.md', '*(a|b).md')); + }); + + test('should support !(...)', () => { + // these are correct, since * is greedy and matches before ! can negate + expect_truthy(isMatch('file.txt', '*!(.jpg|.gif)')); + expect_truthy(isMatch('file.jpg', '*!(.jpg|.gif)')); + expect_truthy(isMatch('file.gif', '*!(.jpg|.gif)')); + + // this is how you negate extensions + expect_truthy(!isMatch('file.jpg', '!(*.jpg|*.gif)')); + expect_truthy(!isMatch('file.gif', '!(*.jpg|*.gif)')); + + expect_truthy(!isMatch('moo.cow', '!(moo).!(cow)')); + expect_truthy(!isMatch('foo.cow', '!(moo).!(cow)')); + expect_truthy(!isMatch('moo.bar', '!(moo).!(cow)')); + expect_truthy(isMatch('foo.bar', '!(moo).!(cow)')); + expect_truthy(isMatch('moo.cow', '!(!(moo)).!(!(cow))')); + expect_truthy(isMatch('moo.bar', '@(moo).!(cow)')); + expect_truthy(isMatch('moomoo.bar', '+(moo).!(cow)')); + expect_truthy(isMatch('moomoo.bar', '+(moo)*(foo).!(cow)')); + expect_truthy(isMatch('moomoofoo.bar', '+(moo)*(foo).!(cow)')); + expect_truthy(isMatch('moomoofoofoo.bar', '+(moo)*(foo).!(cow)')); + expect_truthy(!isMatch('c/z/v', 'c/!(z)/v')); + expect_truthy(isMatch('c/a/v', 'c/!(z)/v')); + + expect_truthy(!isMatch('c/z', 'a!(z)')); + expect_truthy(isMatch('abz', 'a!(z)')); + expect_truthy(!isMatch('az', 'a!(z)')); + + expect_truthy(!isMatch('a/z', 'a/!(z)')); + expect_truthy(isMatch('a/b', 'a/!(z)')); + + expect_truthy(!isMatch('c/z', 'a*!(z)')); + expect_truthy(isMatch('abz', 'a*!(z)')); + expect_truthy(isMatch('az', 'a*!(z)')); + + expect_truthy(isMatch('a/a', '!(b/a)')); + expect_truthy(isMatch('a/b', '!(b/a)')); + expect_truthy(isMatch('a/c', '!(b/a)')); + expect_truthy(!isMatch('b/a', '!(b/a)')); + expect_truthy(isMatch('b/b', '!(b/a)')); + expect_truthy(isMatch('b/c', '!(b/a)')); + + expect_truthy(isMatch('a/a', '!(b/a)')); + expect_truthy(isMatch('a/b', '!(b/a)')); + expect_truthy(isMatch('a/c', '!(b/a)')); + expect_truthy(!isMatch('b/a', '!(b/a)')); + expect_truthy(isMatch('b/b', '!(b/a)')); + expect_truthy(isMatch('b/c', '!(b/a)')); + + expect_truthy(isMatch('a/a', '!((b/a))')); + expect_truthy(isMatch('a/b', '!((b/a))')); + expect_truthy(isMatch('a/c', '!((b/a))')); + expect_truthy(!isMatch('b/a', '!((b/a))')); + expect_truthy(isMatch('b/b', '!((b/a))')); + expect_truthy(isMatch('b/c', '!((b/a))')); + + expect_truthy(isMatch('a/a', '!((?:b/a))')); + expect_truthy(isMatch('a/b', '!((?:b/a))')); + expect_truthy(isMatch('a/c', '!((?:b/a))')); + expect_truthy(!isMatch('b/a', '!((?:b/a))')); + expect_truthy(isMatch('b/b', '!((?:b/a))')); + expect_truthy(isMatch('b/c', '!((?:b/a))')); + + expect_truthy(isMatch('a/a', '!(b/(a))')); + expect_truthy(isMatch('a/b', '!(b/(a))')); + expect_truthy(isMatch('a/c', '!(b/(a))')); + expect_truthy(!isMatch('b/a', '!(b/(a))')); + expect_truthy(isMatch('b/b', '!(b/(a))')); + expect_truthy(isMatch('b/c', '!(b/(a))')); + + expect_truthy(isMatch('a/a', '!(b/a)')); + expect_truthy(isMatch('a/b', '!(b/a)')); + expect_truthy(isMatch('a/c', '!(b/a)')); + expect_truthy(!isMatch('b/a', '!(b/a)')); + expect_truthy(isMatch('b/b', '!(b/a)')); + expect_truthy(isMatch('b/c', '!(b/a)')); + + expect_truthy(!isMatch('a ', '@(!(a) )*')); + expect_truthy(!isMatch('a b', '@(!(a) )*')); + expect_truthy(!isMatch('a b', '@(!(a) )*')); + expect_truthy(!isMatch('a ', '@(!(a) )*')); + expect_truthy(!isMatch('a ', '@(!(a) )*')); + expect_truthy(!isMatch('a', '@(!(a) )*')); + expect_truthy(!isMatch('aa', '@(!(a) )*')); + expect_truthy(!isMatch('b', '@(!(a) )*')); + expect_truthy(!isMatch('bb', '@(!(a) )*')); + expect_truthy(isMatch(' a ', '@(!(a) )*')); + expect_truthy(isMatch('b ', '@(!(a) )*')); + expect_truthy(isMatch('b ', '@(!(a) )*')); + + expect_truthy(!isMatch('a', '!(a)')); + expect_truthy(isMatch('aa', '!(a)')); + expect_truthy(isMatch('b', '!(a)')); + + expect_truthy(!isMatch('a', '!(a*)')); + expect_truthy(!isMatch('aa', '!(a*)')); + expect_truthy(!isMatch('ab', '!(a*)')); + expect_truthy(isMatch('b', '!(a*)')); + + expect_truthy(!isMatch('a', '!(*a*)')); + expect_truthy(!isMatch('aa', '!(*a*)')); + expect_truthy(!isMatch('ab', '!(*a*)')); + expect_truthy(!isMatch('ac', '!(*a*)')); + expect_truthy(isMatch('b', '!(*a*)')); + + expect_truthy(!isMatch('a', '!(*a)')); + expect_truthy(!isMatch('aa', '!(*a)')); + expect_truthy(!isMatch('bba', '!(*a)')); + expect_truthy(isMatch('ab', '!(*a)')); + expect_truthy(isMatch('ac', '!(*a)')); + expect_truthy(isMatch('b', '!(*a)')); + + expect_truthy(!isMatch('a', '!(*a)*')); + expect_truthy(!isMatch('aa', '!(*a)*')); + expect_truthy(!isMatch('bba', '!(*a)*')); + expect_truthy(!isMatch('ab', '!(*a)*')); + expect_truthy(!isMatch('ac', '!(*a)*')); + expect_truthy(isMatch('b', '!(*a)*')); + + expect_truthy(!isMatch('a', '!(a)*')); + expect_truthy(!isMatch('abb', '!(a)*')); + expect_truthy(isMatch('ba', '!(a)*')); + + expect_truthy(isMatch('aa', 'a!(b)*')); + expect_truthy(!isMatch('ab', 'a!(b)*')); + expect_truthy(!isMatch('aba', 'a!(b)*')); + expect_truthy(isMatch('ac', 'a!(b)*')); + + expect_truthy(isMatch('aac', 'a!(b)c')); + expect_truthy(!isMatch('abc', 'a!(b)c')); + expect_truthy(isMatch('acc', 'a!(b)c')); + + expect_truthy(!isMatch('a.c', 'a!(.)c')); + expect_truthy(isMatch('abc', 'a!(.)c')); + }); + + test('should support logical-or inside negation !(...) extglobs', () => { + expect_truthy(!isMatch('ac', '!(a|b)c')); + expect_truthy(!isMatch('bc', '!(a|b)c')); + expect_truthy(isMatch('cc', '!(a|b)c')); + }); + + test('should support multiple negation !(...) extglobs in one expression', () => { + expect_truthy(!isMatch('ac.d', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('bc.d', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('cc.d', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('ac.e', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('bc.e', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('cc.e', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('ac.f', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('bc.f', '!(a|b)c.!(d|e)')); + expect_truthy(isMatch('cc.f', '!(a|b)c.!(d|e)')); + expect_truthy(isMatch('dc.g', '!(a|b)c.!(d|e)')); + }); + + test('should support nested negation !(...) extglobs', () => { + expect_truthy(isMatch('ac.d', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('bc.d', '!(!(a|b)c.!(d|e))')); + expect_truthy(!isMatch('cc.d', '!(a|b)c.!(d|e)')); + expect_truthy(isMatch('cc.d', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('cc.d', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('ac.e', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('bc.e', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('cc.e', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('ac.f', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('bc.f', '!(!(a|b)c.!(d|e))')); + expect_truthy(!isMatch('cc.f', '!(!(a|b)c.!(d|e))')); + expect_truthy(!isMatch('dc.g', '!(!(a|b)c.!(d|e))')); + }); + + test('should support *(...)', () => { + expect_truthy(isMatch('a', 'a*(z)')); + expect_truthy(isMatch('az', 'a*(z)')); + expect_truthy(isMatch('azz', 'a*(z)')); + expect_truthy(isMatch('azzz', 'a*(z)')); + expect_truthy(!isMatch('abz', 'a*(z)')); + expect_truthy(!isMatch('cz', 'a*(z)')); + + expect_truthy(!isMatch('a/a', '*(b/a)')); + expect_truthy(!isMatch('a/b', '*(b/a)')); + expect_truthy(!isMatch('a/c', '*(b/a)')); + expect_truthy(isMatch('b/a', '*(b/a)')); + expect_truthy(!isMatch('b/b', '*(b/a)')); + expect_truthy(!isMatch('b/c', '*(b/a)')); + + expect_truthy(!isMatch('cz', 'a**(z)')); + expect_truthy(isMatch('abz', 'a**(z)')); + expect_truthy(isMatch('az', 'a**(z)')); + + expect_truthy(!isMatch('c/z/v', '*(z)')); + expect_truthy(isMatch('z', '*(z)')); + expect_truthy(!isMatch('zf', '*(z)')); + expect_truthy(!isMatch('fz', '*(z)')); + + expect_truthy(!isMatch('c/a/v', 'c/*(z)/v')); + expect_truthy(isMatch('c/z/v', 'c/*(z)/v')); + + expect_truthy(!isMatch('a.md.js', '*.*(js).js')); + expect_truthy(isMatch('a.js.js', '*.*(js).js')); + }); + + test('should support +(...) extglobs', () => { + expect_truthy(!isMatch('a', 'a+(z)')); + expect_truthy(isMatch('az', 'a+(z)')); + expect_truthy(!isMatch('cz', 'a+(z)')); + expect_truthy(!isMatch('abz', 'a+(z)')); + expect_truthy(!isMatch('a+z', 'a+(z)')); + expect_truthy(isMatch('a+z', 'a++(z)')); + expect_truthy(!isMatch('c+z', 'a+(z)')); + expect_truthy(!isMatch('a+bz', 'a+(z)')); + expect_truthy(!isMatch('az', '+(z)')); + expect_truthy(!isMatch('cz', '+(z)')); + expect_truthy(!isMatch('abz', '+(z)')); + expect_truthy(!isMatch('fz', '+(z)')); + expect_truthy(isMatch('z', '+(z)')); + expect_truthy(isMatch('zz', '+(z)')); + expect_truthy(isMatch('c/z/v', 'c/+(z)/v')); + expect_truthy(isMatch('c/zz/v', 'c/+(z)/v')); + expect_truthy(!isMatch('c/a/v', 'c/+(z)/v')); + }); + + test('should support ?(...) extglobs', () => { + expect_truthy(isMatch('a?z', 'a??(z)')); + + expect_truthy(!isMatch('a?z', 'a?(z)')); + expect_truthy(!isMatch('abz', 'a?(z)')); + expect_truthy(!isMatch('z', 'a?(z)')); + expect_truthy(isMatch('a', 'a?(z)')); + expect_truthy(isMatch('az', 'a?(z)')); + + expect_truthy(!isMatch('abz', '?(z)')); + expect_truthy(!isMatch('az', '?(z)')); + expect_truthy(!isMatch('cz', '?(z)')); + expect_truthy(!isMatch('fz', '?(z)')); + expect_truthy(!isMatch('zz', '?(z)')); + expect_truthy(isMatch('z', '?(z)')); + + expect_truthy(!isMatch('c/a/v', 'c/?(z)/v')); + expect_truthy(!isMatch('c/zz/v', 'c/?(z)/v')); + expect_truthy(isMatch('c/z/v', 'c/?(z)/v')); + }); + + test('should support @(...) extglobs', () => { + expect_truthy(isMatch('c/z/v', 'c/@(z)/v')); + expect_truthy(!isMatch('c/a/v', 'c/@(z)/v')); + expect_truthy(isMatch('moo.cow', '@(*.*)')); + + expect_truthy(!isMatch('cz', 'a*@(z)')); + expect_truthy(isMatch('abz', 'a*@(z)')); + expect_truthy(isMatch('az', 'a*@(z)')); + + expect_truthy(!isMatch('cz', 'a@(z)')); + expect_truthy(!isMatch('abz', 'a@(z)')); + expect_truthy(isMatch('az', 'a@(z)')); + }); + + test('should support qmark matching', () => { + expect_truthy(isMatch('a', '?')); + expect_truthy(!isMatch('aa', '?')); + expect_truthy(!isMatch('ab', '?')); + expect_truthy(!isMatch('aaa', '?')); + expect_truthy(!isMatch('abcdefg', '?')); + + expect_truthy(!isMatch('a', '??')); + expect_truthy(isMatch('aa', '??')); + expect_truthy(isMatch('ab', '??')); + expect_truthy(!isMatch('aaa', '??')); + expect_truthy(!isMatch('abcdefg', '??')); + + expect_truthy(!isMatch('a', '???')); + expect_truthy(!isMatch('aa', '???')); + expect_truthy(!isMatch('ab', '???')); + expect_truthy(isMatch('aaa', '???')); + expect_truthy(!isMatch('abcdefg', '???')); + }); + + test('should match exactly one of the given pattern:', () => { + expect_truthy(!isMatch('aa.aa', '(b|a).(a)')); + expect_truthy(!isMatch('a.bb', '(b|a).(a)')); + expect_truthy(!isMatch('a.aa.a', '(b|a).(a)')); + expect_truthy(!isMatch('cc.a', '(b|a).(a)')); + expect_truthy(isMatch('a.a', '(b|a).(a)')); + expect_truthy(!isMatch('c.a', '(b|a).(a)')); + expect_truthy(!isMatch('dd.aa.d', '(b|a).(a)')); + expect_truthy(isMatch('b.a', '(b|a).(a)')); + + expect_truthy(!isMatch('aa.aa', '@(b|a).@(a)')); + expect_truthy(!isMatch('a.bb', '@(b|a).@(a)')); + expect_truthy(!isMatch('a.aa.a', '@(b|a).@(a)')); + expect_truthy(!isMatch('cc.a', '@(b|a).@(a)')); + expect_truthy(isMatch('a.a', '@(b|a).@(a)')); + expect_truthy(!isMatch('c.a', '@(b|a).@(a)')); + expect_truthy(!isMatch('dd.aa.d', '@(b|a).@(a)')); + expect_truthy(isMatch('b.a', '@(b|a).@(a)')); + }); + + test('should pass tests from rosenblatt\'s korn shell book', () => { + expect_truthy(!isMatch('', '*(0|1|3|5|7|9)')); // only one that disagrees, since we don't match empty strings + expect_truthy(isMatch('137577991', '*(0|1|3|5|7|9)')); + expect_truthy(!isMatch('2468', '*(0|1|3|5|7|9)')); + + expect_truthy(isMatch('file.c', '*.c?(c)')); + expect_truthy(!isMatch('file.C', '*.c?(c)')); + expect_truthy(isMatch('file.cc', '*.c?(c)')); + expect_truthy(!isMatch('file.ccc', '*.c?(c)')); + + expect_truthy(isMatch('parse.y', '!(*.c|*.h|Makefile.in|config*|README)')); + expect_truthy(!isMatch('shell.c', '!(*.c|*.h|Makefile.in|config*|README)')); + expect_truthy(isMatch('Makefile', '!(*.c|*.h|Makefile.in|config*|README)')); + expect_truthy(!isMatch('Makefile.in', '!(*.c|*.h|Makefile.in|config*|README)')); + + expect_truthy(!isMatch('VMS.FILE;', '*\\;[1-9]*([0-9])')); + expect_truthy(!isMatch('VMS.FILE;0', '*\\;[1-9]*([0-9])')); + expect_truthy(isMatch('VMS.FILE;1', '*\\;[1-9]*([0-9])')); + expect_truthy(isMatch('VMS.FILE;139', '*\\;[1-9]*([0-9])')); + expect_truthy(!isMatch('VMS.FILE;1N', '*\\;[1-9]*([0-9])')); + }); + + test('tests derived from the pd-ksh test suite', () => { + expect_truthy(isMatch('abcx', '!([*)*')); + expect_truthy(isMatch('abcz', '!([*)*')); + expect_truthy(isMatch('bbc', '!([*)*')); + + expect_truthy(isMatch('abcx', '!([[*])*')); + expect_truthy(isMatch('abcz', '!([[*])*')); + expect_truthy(isMatch('bbc', '!([[*])*')); + + expect_truthy(isMatch('abcx', '+(a|b\\[)*')); + expect_truthy(isMatch('abcz', '+(a|b\\[)*')); + expect_truthy(!isMatch('bbc', '+(a|b\\[)*')); + + expect_truthy(isMatch('abcx', '+(a|b[)*')); + expect_truthy(isMatch('abcz', '+(a|b[)*')); + expect_truthy(!isMatch('bbc', '+(a|b[)*')); + + expect_truthy(!isMatch('abcx', '[a*(]*z')); + expect_truthy(isMatch('abcz', '[a*(]*z')); + expect_truthy(!isMatch('bbc', '[a*(]*z')); + expect_truthy(isMatch('aaz', '[a*(]*z')); + expect_truthy(isMatch('aaaz', '[a*(]*z')); + + expect_truthy(!isMatch('abcx', '[a*(]*)z')); + expect_truthy(!isMatch('abcz', '[a*(]*)z')); + expect_truthy(!isMatch('bbc', '[a*(]*)z')); + + expect_truthy(!isMatch('abc', '+()c')); + expect_truthy(!isMatch('abc', '+()x')); + expect_truthy(isMatch('abc', '+(*)c')); + expect_truthy(!isMatch('abc', '+(*)x')); + expect_truthy(!isMatch('abc', 'no-file+(a|b)stuff')); + expect_truthy(!isMatch('abc', 'no-file+(a*(c)|b)stuff')); + + expect_truthy(isMatch('abd', 'a+(b|c)d')); + expect_truthy(isMatch('acd', 'a+(b|c)d')); + + expect_truthy(!isMatch('abc', 'a+(b|c)d')); + + expect_truthy(isMatch('abd', 'a!(@(b|B))')); + expect_truthy(isMatch('acd', 'a!(@(b|B))')); + expect_truthy(isMatch('ac', 'a!(@(b|B))')); + expect_truthy(!isMatch('ab', 'a!(@(b|B))')); + + expect_truthy(!isMatch('abc', 'a!(@(b|B))d')); + expect_truthy(!isMatch('abd', 'a!(@(b|B))d')); + expect_truthy(isMatch('acd', 'a!(@(b|B))d')); + + expect_truthy(isMatch('abd', 'a[b*(foo|bar)]d')); + expect_truthy(!isMatch('abc', 'a[b*(foo|bar)]d')); + expect_truthy(!isMatch('acd', 'a[b*(foo|bar)]d')); + }); + + test('stuff from korn\'s book', () => { + expect_truthy(!isMatch('para', 'para+([0-9])')); + expect_truthy(!isMatch('para381', 'para?([345]|99)1')); + expect_truthy(!isMatch('paragraph', 'para*([0-9])')); + expect_truthy(!isMatch('paramour', 'para@(chute|graph)')); + expect_truthy(isMatch('para', 'para*([0-9])')); + expect_truthy(isMatch('para.38', 'para!(*.[0-9])')); + expect_truthy(isMatch('para.38', 'para!(*.[00-09])')); + expect_truthy(isMatch('para.graph', 'para!(*.[0-9])')); + expect_truthy(isMatch('para13829383746592', 'para*([0-9])')); + expect_truthy(isMatch('para39', 'para!(*.[0-9])')); + expect_truthy(isMatch('para987346523', 'para+([0-9])')); + expect_truthy(isMatch('para991', 'para?([345]|99)1')); + expect_truthy(isMatch('paragraph', 'para!(*.[0-9])')); + expect_truthy(isMatch('paragraph', 'para@(chute|graph)')); + }); + + test('simple kleene star tests', () => { + expect_truthy(!isMatch('foo', '*(a|b[)')); + expect_truthy(!isMatch('(', '*(a|b[)')); + expect_truthy(!isMatch(')', '*(a|b[)')); + expect_truthy(!isMatch('|', '*(a|b[)')); + expect_truthy(isMatch('a', '*(a|b)')); + expect_truthy(isMatch('b', '*(a|b)')); + expect_truthy(isMatch('b[', '*(a|b\\[)')); + expect_truthy(isMatch('ab[', '+(a|b\\[)')); + expect_truthy(!isMatch('ab[cde', '+(a|b\\[)')); + expect_truthy(isMatch('ab[cde', '+(a|b\\[)*')); + + expect_truthy(isMatch('foo', '*(a|b|f)*')); + expect_truthy(isMatch('foo', '*(a|b|o)*')); + expect_truthy(isMatch('foo', '*(a|b|f|o)')); + expect_truthy(isMatch('*(a|b[)', '\\*\\(a\\|b\\[\\)')); + expect_truthy(!isMatch('foo', '*(a|b)')); + expect_truthy(!isMatch('foo', '*(a|b\\[)')); + expect_truthy(isMatch('foo', '*(a|b\\[)|f*')); + }); + + test('should support multiple extglobs:', () => { + expect_truthy(isMatch('moo.cow', '@(*).@(*)')); + expect_truthy(isMatch('a.a', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(isMatch('a.b', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('a.c', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('a.c.d', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('c.c', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('a.', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('d.d', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('e.e', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('f.f', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(isMatch('a.abcd', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + + expect_truthy(!isMatch('a.a', '!(*.a|*.b|*.c)')); + expect_truthy(!isMatch('a.b', '!(*.a|*.b|*.c)')); + expect_truthy(!isMatch('a.c', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('a.c.d', '!(*.a|*.b|*.c)')); + expect_truthy(!isMatch('c.c', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('a.', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('d.d', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('e.e', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('f.f', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('a.abcd', '!(*.a|*.b|*.c)')); + + expect_truthy(isMatch('a.a', '!(*.[^a-c])')); + expect_truthy(isMatch('a.b', '!(*.[^a-c])')); + expect_truthy(isMatch('a.c', '!(*.[^a-c])')); + expect_truthy(!isMatch('a.c.d', '!(*.[^a-c])')); + expect_truthy(isMatch('c.c', '!(*.[^a-c])')); + expect_truthy(isMatch('a.', '!(*.[^a-c])')); + expect_truthy(!isMatch('d.d', '!(*.[^a-c])')); + expect_truthy(!isMatch('e.e', '!(*.[^a-c])')); + expect_truthy(!isMatch('f.f', '!(*.[^a-c])')); + expect_truthy(isMatch('a.abcd', '!(*.[^a-c])')); + + expect_truthy(!isMatch('a.a', '!(*.[a-c])')); + expect_truthy(!isMatch('a.b', '!(*.[a-c])')); + expect_truthy(!isMatch('a.c', '!(*.[a-c])')); + expect_truthy(isMatch('a.c.d', '!(*.[a-c])')); + expect_truthy(!isMatch('c.c', '!(*.[a-c])')); + expect_truthy(isMatch('a.', '!(*.[a-c])')); + expect_truthy(isMatch('d.d', '!(*.[a-c])')); + expect_truthy(isMatch('e.e', '!(*.[a-c])')); + expect_truthy(isMatch('f.f', '!(*.[a-c])')); + expect_truthy(isMatch('a.abcd', '!(*.[a-c])')); + + expect_truthy(!isMatch('a.a', '!(*.[a-c]*)')); + expect_truthy(!isMatch('a.b', '!(*.[a-c]*)')); + expect_truthy(!isMatch('a.c', '!(*.[a-c]*)')); + expect_truthy(!isMatch('a.c.d', '!(*.[a-c]*)')); + expect_truthy(!isMatch('c.c', '!(*.[a-c]*)')); + expect_truthy(isMatch('a.', '!(*.[a-c]*)')); + expect_truthy(isMatch('d.d', '!(*.[a-c]*)')); + expect_truthy(isMatch('e.e', '!(*.[a-c]*)')); + expect_truthy(isMatch('f.f', '!(*.[a-c]*)')); + expect_truthy(!isMatch('a.abcd', '!(*.[a-c]*)')); + + expect_truthy(!isMatch('a.a', '*.!(a|b|c)')); + expect_truthy(!isMatch('a.b', '*.!(a|b|c)')); + expect_truthy(!isMatch('a.c', '*.!(a|b|c)')); + expect_truthy(isMatch('a.c.d', '*.!(a|b|c)')); + expect_truthy(!isMatch('c.c', '*.!(a|b|c)')); + expect_truthy(isMatch('a.', '*.!(a|b|c)')); + expect_truthy(isMatch('d.d', '*.!(a|b|c)')); + expect_truthy(isMatch('e.e', '*.!(a|b|c)')); + expect_truthy(isMatch('f.f', '*.!(a|b|c)')); + expect_truthy(isMatch('a.abcd', '*.!(a|b|c)')); + + expect_truthy(isMatch('a.a', '*!(.a|.b|.c)')); + expect_truthy(isMatch('a.b', '*!(.a|.b|.c)')); + expect_truthy(isMatch('a.c', '*!(.a|.b|.c)')); + expect_truthy(isMatch('a.c.d', '*!(.a|.b|.c)')); + expect_truthy(isMatch('c.c', '*!(.a|.b|.c)')); + expect_truthy(isMatch('a.', '*!(.a|.b|.c)')); + expect_truthy(isMatch('d.d', '*!(.a|.b|.c)')); + expect_truthy(isMatch('e.e', '*!(.a|.b|.c)')); + expect_truthy(isMatch('f.f', '*!(.a|.b|.c)')); + expect_truthy(isMatch('a.abcd', '*!(.a|.b|.c)')); + + expect_truthy(!isMatch('a.a', '!(*.[a-c])*')); + expect_truthy(!isMatch('a.b', '!(*.[a-c])*')); + expect_truthy(!isMatch('a.c', '!(*.[a-c])*')); + expect_truthy(!isMatch('a.c.d', '!(*.[a-c])*')); + expect_truthy(!isMatch('c.c', '!(*.[a-c])*')); + expect_truthy(isMatch('a.', '!(*.[a-c])*')); + expect_truthy(isMatch('d.d', '!(*.[a-c])*')); + expect_truthy(isMatch('e.e', '!(*.[a-c])*')); + expect_truthy(isMatch('f.f', '!(*.[a-c])*')); + expect_truthy(!isMatch('a.abcd', '!(*.[a-c])*')); + + expect_truthy(isMatch('a.a', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('a.b', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('a.c', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('a.c.d', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('c.c', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('a.', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('d.d', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('e.e', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('f.f', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('a.abcd', '*!(.a|.b|.c)*')); + + expect_truthy(!isMatch('a.a', '*.!(a|b|c)*')); + expect_truthy(!isMatch('a.b', '*.!(a|b|c)*')); + expect_truthy(!isMatch('a.c', '*.!(a|b|c)*')); + expect_truthy(isMatch('a.c.d', '*.!(a|b|c)*')); + expect_truthy(!isMatch('c.c', '*.!(a|b|c)*')); + expect_truthy(isMatch('a.', '*.!(a|b|c)*')); + expect_truthy(isMatch('d.d', '*.!(a|b|c)*')); + expect_truthy(isMatch('e.e', '*.!(a|b|c)*')); + expect_truthy(isMatch('f.f', '*.!(a|b|c)*')); + expect_truthy(!isMatch('a.abcd', '*.!(a|b|c)*')); + }); + + test('should correctly match empty parens', () => { + expect_truthy(!isMatch('def', '@()ef')); + expect_truthy(isMatch('ef', '@()ef')); + + expect_truthy(!isMatch('def', '()ef')); + expect_truthy(isMatch('ef', '()ef')); + }); + + test('should match escaped parens', () => { + if (process.platform !== 'win32') { + expect_truthy(isMatch('a\\(b', 'a\\\\\\(b')); + } + expect_truthy(isMatch('a(b', 'a(b')); + expect_truthy(isMatch('a(b', 'a\\(b')); + expect_truthy(!isMatch('a((b', 'a(b')); + expect_truthy(!isMatch('a((((b', 'a(b')); + expect_truthy(!isMatch('ab', 'a(b')); + + expect_truthy(isMatch('a(b', 'a\\(b')); + expect_truthy(!isMatch('a((b', 'a\\(b')); + expect_truthy(!isMatch('a((((b', 'a\\(b')); + expect_truthy(!isMatch('ab', 'a\\(b')); + + expect_truthy(isMatch('a(b', 'a(*b')); + expect_truthy(isMatch('a(ab', 'a\\(*b')); + expect_truthy(isMatch('a((b', 'a(*b')); + expect_truthy(isMatch('a((((b', 'a(*b')); + expect_truthy(!isMatch('ab', 'a(*b')); + }); + + test('should match escaped backslashes', () => { + if (process.platform !== 'win32') { + expect_truthy(isMatch('a\\(b', 'a\\(b')); + expect_truthy(isMatch('a\\b', 'a\\b')); + } + + expect_truthy(isMatch('a\\\\(b', 'a\\\\(b')); + expect_truthy(!isMatch('a(b', 'a\\\\(b')); + expect_truthy(!isMatch('a\\(b', 'a\\\\(b')); + expect_truthy(!isMatch('a((b', 'a\\(b')); + expect_truthy(!isMatch('a((((b', 'a\\(b')); + expect_truthy(!isMatch('ab', 'a\\(b')); + + expect_truthy(!isMatch('a/b', 'a\\b')); + expect_truthy(!isMatch('ab', 'a\\b')); + }); + + // these are not extglobs, and do not need to pass, but they are included + // to test integration with other features + test('should support regex characters', () => { + let fixtures = ['a c', 'a.c', 'a.xy.zc', 'a.zc', 'a123c', 'a1c', 'abbbbc', 'abbbc', 'abbc', 'abc', 'abq', 'axy zc', 'axy', 'axy.zc', 'axyzc']; + + if (process.platform !== 'win32') { + expect_deepEqual(mm(['a\\b', 'a/b', 'ab'], 'a/b'), ['a/b']); + } + + expect_deepEqual(mm(['a/b', 'ab'], 'a/b'), ['a/b']); + expect_deepEqual(mm(fixtures, 'ab?bc'), ['abbbc']); + expect_deepEqual(mm(fixtures, 'ab*c'), ['abbbbc', 'abbbc', 'abbc', 'abc']); + expect_deepEqual(mm(fixtures, 'a+(b)bc'), ['abbbbc', 'abbbc', 'abbc']); + expect_deepEqual(mm(fixtures, '^abc$'), []); + expect_deepEqual(mm(fixtures, 'a.c'), ['a.c']); + expect_deepEqual(mm(fixtures, 'a.*c'), ['a.c', 'a.xy.zc', 'a.zc']); + expect_deepEqual(mm(fixtures, 'a*c'), ['a c', 'a.c', 'a.xy.zc', 'a.zc', 'a123c', 'a1c', 'abbbbc', 'abbbc', 'abbc', 'abc', 'axy zc', 'axy.zc', 'axyzc']); + expect_deepEqual(mm(fixtures, 'a[\\w]+c'), ['a123c', 'a1c', 'abbbbc', 'abbbc', 'abbc', 'abc', 'axyzc'], 'Should match word characters'); + expect_deepEqual(mm(fixtures, 'a[\\W]+c'), ['a c', 'a.c'], 'Should match non-word characters'); + expect_deepEqual(mm(fixtures, 'a[\\d]+c'), ['a123c', 'a1c'], 'Should match numbers'); + expect_deepEqual(mm(['foo@#$%123ASD #$$%^&', 'foo!@#$asdfl;', '123'], '[\\d]+'), ['123']); + expect_deepEqual(mm(['a123c', 'abbbc'], 'a[\\D]+c'), ['abbbc'], 'Should match non-numbers'); + expect_deepEqual(mm(['foo', ' foo '], '(f|o)+\\b'), ['foo'], 'Should match word boundaries'); + }); +}); + +describe('extglobs from the bash spec', () => { + test('should match negation extglobs', () => { + expect_truthy(isMatch('bar', '!(foo)')); + expect_truthy(isMatch('f', '!(foo)')); + expect_truthy(isMatch('fa', '!(foo)')); + expect_truthy(isMatch('fb', '!(foo)')); + expect_truthy(isMatch('ff', '!(foo)')); + expect_truthy(isMatch('fff', '!(foo)')); + expect_truthy(isMatch('fo', '!(foo)')); + expect_truthy(!isMatch('foo', '!(foo)')); + expect_truthy(!isMatch('foo/bar', '!(foo)')); + expect_truthy(!isMatch('a/b/c/bar', '**/!(bar)')); + expect_truthy(isMatch('a/b/c/foo/bar', '**/!(baz)/bar')); + expect_truthy(isMatch('foobar', '!(foo)')); + expect_truthy(isMatch('foot', '!(foo)')); + expect_truthy(isMatch('foox', '!(foo)')); + expect_truthy(isMatch('o', '!(foo)')); + expect_truthy(isMatch('of', '!(foo)')); + expect_truthy(isMatch('ooo', '!(foo)')); + expect_truthy(isMatch('ox', '!(foo)')); + expect_truthy(isMatch('x', '!(foo)')); + expect_truthy(isMatch('xx', '!(foo)')); + + expect_truthy(!isMatch('bar', '!(!(foo))')); + expect_truthy(!isMatch('f', '!(!(foo))')); + expect_truthy(!isMatch('fa', '!(!(foo))')); + expect_truthy(!isMatch('fb', '!(!(foo))')); + expect_truthy(!isMatch('ff', '!(!(foo))')); + expect_truthy(!isMatch('fff', '!(!(foo))')); + expect_truthy(!isMatch('fo', '!(!(foo))')); + expect_truthy(isMatch('foo', '!(!(foo))')); + expect_truthy(!isMatch('foo/bar', '!(!(foo))')); + expect_truthy(!isMatch('foobar', '!(!(foo))')); + expect_truthy(!isMatch('foot', '!(!(foo))')); + expect_truthy(!isMatch('foox', '!(!(foo))')); + expect_truthy(!isMatch('o', '!(!(foo))')); + expect_truthy(!isMatch('of', '!(!(foo))')); + expect_truthy(!isMatch('ooo', '!(!(foo))')); + expect_truthy(!isMatch('ox', '!(!(foo))')); + expect_truthy(!isMatch('x', '!(!(foo))')); + expect_truthy(!isMatch('xx', '!(!(foo))')); + + expect_truthy(isMatch('bar', '!(!(!(foo)))')); + expect_truthy(isMatch('f', '!(!(!(foo)))')); + expect_truthy(isMatch('fa', '!(!(!(foo)))')); + expect_truthy(isMatch('fb', '!(!(!(foo)))')); + expect_truthy(isMatch('ff', '!(!(!(foo)))')); + expect_truthy(isMatch('fff', '!(!(!(foo)))')); + expect_truthy(isMatch('fo', '!(!(!(foo)))')); + expect_truthy(!isMatch('foo', '!(!(!(foo)))')); + expect_truthy(!isMatch('foo/bar', '!(!(!(foo)))')); + expect_truthy(isMatch('foobar', '!(!(!(foo)))')); + expect_truthy(isMatch('foot', '!(!(!(foo)))')); + expect_truthy(isMatch('foox', '!(!(!(foo)))')); + expect_truthy(isMatch('o', '!(!(!(foo)))')); + expect_truthy(isMatch('of', '!(!(!(foo)))')); + expect_truthy(isMatch('ooo', '!(!(!(foo)))')); + expect_truthy(isMatch('ox', '!(!(!(foo)))')); + expect_truthy(isMatch('x', '!(!(!(foo)))')); + expect_truthy(isMatch('xx', '!(!(!(foo)))')); + + expect_truthy(!isMatch('bar', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('f', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('fa', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('fb', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('ff', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('fff', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('fo', '!(!(!(!(foo))))')); + expect_truthy(isMatch('foo', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('foo/bar', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('foot', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('o', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('of', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('ooo', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('ox', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('x', '!(!(!(!(foo))))')); + expect_truthy(!isMatch('xx', '!(!(!(!(foo))))')); + + expect_truthy(!isMatch('bar', '!(!(foo))*')); + expect_truthy(!isMatch('f', '!(!(foo))*')); + expect_truthy(!isMatch('fa', '!(!(foo))*')); + expect_truthy(!isMatch('fb', '!(!(foo))*')); + expect_truthy(!isMatch('ff', '!(!(foo))*')); + expect_truthy(!isMatch('fff', '!(!(foo))*')); + expect_truthy(!isMatch('fo', '!(!(foo))*')); + expect_truthy(isMatch('foo', '!(!(foo))*')); + expect_truthy(!isMatch('foo/bar', '!(!(foo))*')); + expect_truthy(isMatch('foobar', '!(!(foo))*')); + expect_truthy(isMatch('foot', '!(!(foo))*')); + expect_truthy(isMatch('foox', '!(!(foo))*')); + expect_truthy(!isMatch('o', '!(!(foo))*')); + expect_truthy(!isMatch('of', '!(!(foo))*')); + expect_truthy(!isMatch('ooo', '!(!(foo))*')); + expect_truthy(!isMatch('ox', '!(!(foo))*')); + expect_truthy(!isMatch('x', '!(!(foo))*')); + expect_truthy(!isMatch('xx', '!(!(foo))*')); + + expect_truthy(isMatch('bar', '!(f!(o))')); + expect_truthy(!isMatch('f', '!(f!(o))')); + expect_truthy(!isMatch('fa', '!(f!(o))')); + expect_truthy(!isMatch('fb', '!(f!(o))')); + expect_truthy(!isMatch('ff', '!(f!(o))')); + expect_truthy(!isMatch('fff', '!(f!(o))')); + expect_truthy(isMatch('fo', '!(f!(o))')); + expect_truthy(isMatch('foo', '!(!(foo))')); + expect_truthy(isMatch('go', '!(f!(o))')); + expect_truthy(!isMatch('foo', '!(f!(o))')); + expect_truthy(!isMatch('foo/bar', '!(f!(o))')); + expect_truthy(!isMatch('foobar', '!(f)!(o)')); + expect_truthy(!isMatch('foobar', '!(f)!(o)!(o)bar')); + expect_truthy(isMatch('barbar', '!(f)!(o)!(o)bar')); + expect_truthy(!isMatch('foobar', '!(f!(o))')); + expect_truthy(!isMatch('foot', '!(f!(o))')); + expect_truthy(!isMatch('foox', '!(f!(o))')); + expect_truthy(isMatch('o', '!(f!(o))')); + expect_truthy(isMatch('of', '!(f!(o))')); + expect_truthy(isMatch('ooo', '!(f!(o))')); + expect_truthy(isMatch('ox', '!(f!(o))')); + expect_truthy(isMatch('x', '!(f!(o))')); + expect_truthy(isMatch('xx', '!(f!(o))')); + + expect_truthy(isMatch('bar', '!(f(o))')); + expect_truthy(isMatch('f', '!(f(o))')); + expect_truthy(isMatch('fa', '!(f(o))')); + expect_truthy(isMatch('fb', '!(f(o))')); + expect_truthy(isMatch('ff', '!(f(o))')); + expect_truthy(isMatch('fff', '!(f(o))')); + expect_truthy(!isMatch('fo', '!(f(o))')); + expect_truthy(isMatch('foo', '!(f(o))')); + expect_truthy(!isMatch('foo/bar', '!(f(o))')); + expect_truthy(isMatch('foobar', '!(f(o))')); + expect_truthy(isMatch('foot', '!(f(o))')); + expect_truthy(isMatch('foox', '!(f(o))')); + expect_truthy(isMatch('o', '!(f(o))')); + expect_truthy(isMatch('of', '!(f(o))')); + expect_truthy(isMatch('ooo', '!(f(o))')); + expect_truthy(isMatch('ox', '!(f(o))')); + expect_truthy(isMatch('x', '!(f(o))')); + expect_truthy(isMatch('xx', '!(f(o))')); + + expect_truthy(isMatch('bar', '!(f)')); + expect_truthy(!isMatch('f', '!(f)')); + expect_truthy(isMatch('fa', '!(f)')); + expect_truthy(isMatch('fb', '!(f)')); + expect_truthy(isMatch('ff', '!(f)')); + expect_truthy(isMatch('fff', '!(f)')); + expect_truthy(isMatch('fo', '!(f)')); + expect_truthy(isMatch('foo', '!(f)')); + expect_truthy(!isMatch('foo/bar', '!(f)')); + expect_truthy(isMatch('foobar', '!(f)')); + expect_truthy(isMatch('foot', '!(f)')); + expect_truthy(isMatch('foox', '!(f)')); + expect_truthy(isMatch('o', '!(f)')); + expect_truthy(isMatch('of', '!(f)')); + expect_truthy(isMatch('ooo', '!(f)')); + expect_truthy(isMatch('ox', '!(f)')); + expect_truthy(isMatch('x', '!(f)')); + expect_truthy(isMatch('xx', '!(f)')); + + expect_truthy(isMatch('bar', '!(f)')); + expect_truthy(!isMatch('f', '!(f)')); + expect_truthy(isMatch('fa', '!(f)')); + expect_truthy(isMatch('fb', '!(f)')); + expect_truthy(isMatch('ff', '!(f)')); + expect_truthy(isMatch('fff', '!(f)')); + expect_truthy(isMatch('fo', '!(f)')); + expect_truthy(isMatch('foo', '!(f)')); + expect_truthy(!isMatch('foo/bar', '!(f)')); + expect_truthy(isMatch('foobar', '!(f)')); + expect_truthy(isMatch('foot', '!(f)')); + expect_truthy(isMatch('foox', '!(f)')); + expect_truthy(isMatch('o', '!(f)')); + expect_truthy(isMatch('of', '!(f)')); + expect_truthy(isMatch('ooo', '!(f)')); + expect_truthy(isMatch('ox', '!(f)')); + expect_truthy(isMatch('x', '!(f)')); + expect_truthy(isMatch('xx', '!(f)')); + + expect_truthy(isMatch('bar', '!(foo)')); + expect_truthy(isMatch('f', '!(foo)')); + expect_truthy(isMatch('fa', '!(foo)')); + expect_truthy(isMatch('fb', '!(foo)')); + expect_truthy(isMatch('ff', '!(foo)')); + expect_truthy(isMatch('fff', '!(foo)')); + expect_truthy(isMatch('fo', '!(foo)')); + expect_truthy(!isMatch('foo', '!(foo)')); + expect_truthy(!isMatch('foo/bar', '!(foo)')); + expect_truthy(isMatch('foobar', '!(foo)')); + expect_truthy(isMatch('foot', '!(foo)')); + expect_truthy(isMatch('foox', '!(foo)')); + expect_truthy(isMatch('o', '!(foo)')); + expect_truthy(isMatch('of', '!(foo)')); + expect_truthy(isMatch('ooo', '!(foo)')); + expect_truthy(isMatch('ox', '!(foo)')); + expect_truthy(isMatch('x', '!(foo)')); + expect_truthy(isMatch('xx', '!(foo)')); + + expect_truthy(isMatch('bar', '!(foo)*')); + expect_truthy(isMatch('f', '!(foo)*')); + expect_truthy(isMatch('fa', '!(foo)*')); + expect_truthy(isMatch('fb', '!(foo)*')); + expect_truthy(isMatch('ff', '!(foo)*')); + expect_truthy(isMatch('fff', '!(foo)*')); + expect_truthy(isMatch('fo', '!(foo)*')); + expect_truthy(!isMatch('foo', '!(foo)*')); + expect_truthy(!isMatch('foo/bar', '!(foo)*')); + expect_truthy(!isMatch('foobar', '!(foo)*')); + expect_truthy(!isMatch('foot', '!(foo)*')); + expect_truthy(!isMatch('foox', '!(foo)*')); + expect_truthy(isMatch('o', '!(foo)*')); + expect_truthy(isMatch('of', '!(foo)*')); + expect_truthy(isMatch('ooo', '!(foo)*')); + expect_truthy(isMatch('ox', '!(foo)*')); + expect_truthy(isMatch('x', '!(foo)*')); + expect_truthy(isMatch('xx', '!(foo)*')); + + expect_truthy(isMatch('bar', '!(x)')); + expect_truthy(isMatch('f', '!(x)')); + expect_truthy(isMatch('fa', '!(x)')); + expect_truthy(isMatch('fb', '!(x)')); + expect_truthy(isMatch('ff', '!(x)')); + expect_truthy(isMatch('fff', '!(x)')); + expect_truthy(isMatch('fo', '!(x)')); + expect_truthy(isMatch('foo', '!(x)')); + expect_truthy(!isMatch('foo/bar', '!(x)')); + expect_truthy(isMatch('foobar', '!(x)')); + expect_truthy(isMatch('foot', '!(x)')); + expect_truthy(isMatch('foox', '!(x)')); + expect_truthy(isMatch('o', '!(x)')); + expect_truthy(isMatch('of', '!(x)')); + expect_truthy(isMatch('ooo', '!(x)')); + expect_truthy(isMatch('ox', '!(x)')); + expect_truthy(!isMatch('x', '!(x)')); + expect_truthy(isMatch('xx', '!(x)')); + + expect_truthy(isMatch('bar', '!(x)*')); + expect_truthy(isMatch('f', '!(x)*')); + expect_truthy(isMatch('fa', '!(x)*')); + expect_truthy(isMatch('fb', '!(x)*')); + expect_truthy(isMatch('ff', '!(x)*')); + expect_truthy(isMatch('fff', '!(x)*')); + expect_truthy(isMatch('fo', '!(x)*')); + expect_truthy(isMatch('foo', '!(x)*')); + expect_truthy(!isMatch('foo/bar', '!(x)*')); + expect_truthy(isMatch('foobar', '!(x)*')); + expect_truthy(isMatch('foot', '!(x)*')); + expect_truthy(isMatch('foox', '!(x)*')); + expect_truthy(isMatch('o', '!(x)*')); + expect_truthy(isMatch('of', '!(x)*')); + expect_truthy(isMatch('ooo', '!(x)*')); + expect_truthy(isMatch('ox', '!(x)*')); + expect_truthy(!isMatch('x', '!(x)*')); + expect_truthy(!isMatch('xx', '!(x)*')); + + expect_truthy(isMatch('bar', '*(!(f))')); + expect_truthy(!isMatch('f', '*(!(f))')); + expect_truthy(isMatch('fa', '*(!(f))')); + expect_truthy(isMatch('fb', '*(!(f))')); + expect_truthy(isMatch('ff', '*(!(f))')); + expect_truthy(isMatch('fff', '*(!(f))')); + expect_truthy(isMatch('fo', '*(!(f))')); + expect_truthy(isMatch('foo', '*(!(f))')); + expect_truthy(!isMatch('foo/bar', '*(!(f))')); + expect_truthy(isMatch('foobar', '*(!(f))')); + expect_truthy(isMatch('foot', '*(!(f))')); + expect_truthy(isMatch('foox', '*(!(f))')); + expect_truthy(isMatch('o', '*(!(f))')); + expect_truthy(isMatch('of', '*(!(f))')); + expect_truthy(isMatch('ooo', '*(!(f))')); + expect_truthy(isMatch('ox', '*(!(f))')); + expect_truthy(isMatch('x', '*(!(f))')); + expect_truthy(isMatch('xx', '*(!(f))')); + + expect_truthy(!isMatch('bar', '*((foo))')); + expect_truthy(!isMatch('f', '*((foo))')); + expect_truthy(!isMatch('fa', '*((foo))')); + expect_truthy(!isMatch('fb', '*((foo))')); + expect_truthy(!isMatch('ff', '*((foo))')); + expect_truthy(!isMatch('fff', '*((foo))')); + expect_truthy(!isMatch('fo', '*((foo))')); + expect_truthy(isMatch('foo', '*((foo))')); + expect_truthy(!isMatch('foo/bar', '*((foo))')); + expect_truthy(!isMatch('foobar', '*((foo))')); + expect_truthy(!isMatch('foot', '*((foo))')); + expect_truthy(!isMatch('foox', '*((foo))')); + expect_truthy(!isMatch('o', '*((foo))')); + expect_truthy(!isMatch('of', '*((foo))')); + expect_truthy(!isMatch('ooo', '*((foo))')); + expect_truthy(!isMatch('ox', '*((foo))')); + expect_truthy(!isMatch('x', '*((foo))')); + expect_truthy(!isMatch('xx', '*((foo))')); + + expect_truthy(isMatch('bar', '+(!(f))')); + expect_truthy(!isMatch('f', '+(!(f))')); + expect_truthy(isMatch('fa', '+(!(f))')); + expect_truthy(isMatch('fb', '+(!(f))')); + expect_truthy(isMatch('ff', '+(!(f))')); + expect_truthy(isMatch('fff', '+(!(f))')); + expect_truthy(isMatch('fo', '+(!(f))')); + expect_truthy(isMatch('foo', '+(!(f))')); + expect_truthy(!isMatch('foo/bar', '+(!(f))')); + expect_truthy(isMatch('foobar', '+(!(f))')); + expect_truthy(isMatch('foot', '+(!(f))')); + expect_truthy(isMatch('foox', '+(!(f))')); + expect_truthy(isMatch('o', '+(!(f))')); + expect_truthy(isMatch('of', '+(!(f))')); + expect_truthy(isMatch('ooo', '+(!(f))')); + expect_truthy(isMatch('ox', '+(!(f))')); + expect_truthy(isMatch('x', '+(!(f))')); + expect_truthy(isMatch('xx', '+(!(f))')); + + expect_truthy(isMatch('bar', '@(!(z*)|*x)')); + expect_truthy(isMatch('f', '@(!(z*)|*x)')); + expect_truthy(isMatch('fa', '@(!(z*)|*x)')); + expect_truthy(isMatch('fb', '@(!(z*)|*x)')); + expect_truthy(isMatch('ff', '@(!(z*)|*x)')); + expect_truthy(isMatch('fff', '@(!(z*)|*x)')); + expect_truthy(isMatch('fo', '@(!(z*)|*x)')); + expect_truthy(isMatch('foo', '@(!(z*)|*x)')); + expect_truthy(isMatch('foo/bar', '@(!(z*/*)|*x)')); + expect_truthy(!isMatch('foo/bar', '@(!(z*)|*x)')); + expect_truthy(isMatch('foobar', '@(!(z*)|*x)')); + expect_truthy(isMatch('foot', '@(!(z*)|*x)')); + expect_truthy(isMatch('foox', '@(!(z*)|*x)')); + expect_truthy(isMatch('o', '@(!(z*)|*x)')); + expect_truthy(isMatch('of', '@(!(z*)|*x)')); + expect_truthy(isMatch('ooo', '@(!(z*)|*x)')); + expect_truthy(isMatch('ox', '@(!(z*)|*x)')); + expect_truthy(isMatch('x', '@(!(z*)|*x)')); + expect_truthy(isMatch('xx', '@(!(z*)|*x)')); + + expect_truthy(!isMatch('bar', 'foo/!(foo)')); + expect_truthy(!isMatch('f', 'foo/!(foo)')); + expect_truthy(!isMatch('fa', 'foo/!(foo)')); + expect_truthy(!isMatch('fb', 'foo/!(foo)')); + expect_truthy(!isMatch('ff', 'foo/!(foo)')); + expect_truthy(!isMatch('fff', 'foo/!(foo)')); + expect_truthy(!isMatch('fo', 'foo/!(foo)')); + expect_truthy(!isMatch('foo', 'foo/!(foo)')); + expect_truthy(isMatch('foo/bar', 'foo/!(foo)')); + expect_truthy(!isMatch('foobar', 'foo/!(foo)')); + expect_truthy(!isMatch('foot', 'foo/!(foo)')); + expect_truthy(!isMatch('foox', 'foo/!(foo)')); + expect_truthy(!isMatch('o', 'foo/!(foo)')); + expect_truthy(!isMatch('of', 'foo/!(foo)')); + expect_truthy(!isMatch('ooo', 'foo/!(foo)')); + expect_truthy(!isMatch('ox', 'foo/!(foo)')); + expect_truthy(!isMatch('x', 'foo/!(foo)')); + expect_truthy(!isMatch('xx', 'foo/!(foo)')); + + expect_truthy(!isMatch('ffffffo', '(foo)bb')); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '(foo)bb')); + expect_truthy(!isMatch('ffo', '(foo)bb')); + expect_truthy(!isMatch('fofo', '(foo)bb')); + expect_truthy(!isMatch('fofoofoofofoo', '(foo)bb')); + expect_truthy(!isMatch('foo', '(foo)bb')); + expect_truthy(!isMatch('foob', '(foo)bb')); + expect_truthy(isMatch('foobb', '(foo)bb')); + expect_truthy(!isMatch('foofoofo', '(foo)bb')); + expect_truthy(!isMatch('fooofoofofooo', '(foo)bb')); + expect_truthy(!isMatch('foooofo', '(foo)bb')); + expect_truthy(!isMatch('foooofof', '(foo)bb')); + expect_truthy(!isMatch('foooofofx', '(foo)bb')); + expect_truthy(!isMatch('foooxfooxfoxfooox', '(foo)bb')); + expect_truthy(!isMatch('foooxfooxfxfooox', '(foo)bb')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '(foo)bb')); + expect_truthy(!isMatch('foot', '(foo)bb')); + expect_truthy(!isMatch('foox', '(foo)bb')); + expect_truthy(!isMatch('ofoofo', '(foo)bb')); + expect_truthy(!isMatch('ofooofoofofooo', '(foo)bb')); + expect_truthy(!isMatch('ofoooxoofxo', '(foo)bb')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '(foo)bb')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '(foo)bb')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '(foo)bb')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '(foo)bb')); + expect_truthy(!isMatch('ofxoofxo', '(foo)bb')); + expect_truthy(!isMatch('oofooofo', '(foo)bb')); + expect_truthy(!isMatch('ooo', '(foo)bb')); + expect_truthy(!isMatch('oxfoxfox', '(foo)bb')); + expect_truthy(!isMatch('oxfoxoxfox', '(foo)bb')); + expect_truthy(!isMatch('xfoooofof', '(foo)bb')); + + expect_truthy(isMatch('ffffffo', '*(*(f)*(o))')); + expect_truthy(isMatch('fffooofoooooffoofffooofff', '*(*(f)*(o))')); + expect_truthy(isMatch('ffo', '*(*(f)*(o))')); + expect_truthy(isMatch('fofo', '*(*(f)*(o))')); + expect_truthy(isMatch('fofoofoofofoo', '*(*(f)*(o))')); + expect_truthy(isMatch('foo', '*(*(f)*(o))')); + expect_truthy(!isMatch('foob', '*(*(f)*(o))')); + expect_truthy(!isMatch('foobb', '*(*(f)*(o))')); + expect_truthy(isMatch('foofoofo', '*(*(f)*(o))')); + expect_truthy(isMatch('fooofoofofooo', '*(*(f)*(o))')); + expect_truthy(isMatch('foooofo', '*(*(f)*(o))')); + expect_truthy(isMatch('foooofof', '*(*(f)*(o))')); + expect_truthy(!isMatch('foooofofx', '*(*(f)*(o))')); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(*(f)*(o))')); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(*(f)*(o))')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(*(f)*(o))')); + expect_truthy(!isMatch('foot', '*(*(f)*(o))')); + expect_truthy(!isMatch('foox', '*(*(f)*(o))')); + expect_truthy(isMatch('ofoofo', '*(*(f)*(o))')); + expect_truthy(isMatch('ofooofoofofooo', '*(*(f)*(o))')); + expect_truthy(!isMatch('ofoooxoofxo', '*(*(f)*(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(*(f)*(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(*(f)*(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(*(f)*(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(*(f)*(o))')); + expect_truthy(!isMatch('ofxoofxo', '*(*(f)*(o))')); + expect_truthy(isMatch('oofooofo', '*(*(f)*(o))')); + expect_truthy(isMatch('ooo', '*(*(f)*(o))')); + expect_truthy(!isMatch('oxfoxfox', '*(*(f)*(o))')); + expect_truthy(!isMatch('oxfoxoxfox', '*(*(f)*(o))')); + expect_truthy(!isMatch('xfoooofof', '*(*(f)*(o))')); + + expect_truthy(!isMatch('ffffffo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('ffo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('fofo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('fofoofoofofoo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foob', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foobb', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foofoofo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('fooofoofofooo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foooofo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foooofof', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foooofofx', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foot', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('foox', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('ofoofo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('ofooofoofofooo', '*(*(of*(o)x)o)')); + expect_truthy(isMatch('ofoooxoofxo', '*(*(of*(o)x)o)')); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(*(of*(o)x)o)')); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxoo', '*(*(of*(o)x)o)')); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(*(of*(o)x)o)')); + expect_truthy(isMatch('ofxoofxo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('oofooofo', '*(*(of*(o)x)o)')); + expect_truthy(isMatch('ooo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('oxfoxfox', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('oxfoxoxfox', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('xfoooofof', '*(*(of*(o)x)o)')); + + expect_truthy(isMatch('ffffffo', '*(f*(o))')); + expect_truthy(isMatch('fffooofoooooffoofffooofff', '*(f*(o))')); + expect_truthy(isMatch('ffo', '*(f*(o))')); + expect_truthy(isMatch('fofo', '*(f*(o))')); + expect_truthy(isMatch('fofoofoofofoo', '*(f*(o))')); + expect_truthy(isMatch('foo', '*(f*(o))')); + expect_truthy(!isMatch('foob', '*(f*(o))')); + expect_truthy(!isMatch('foobb', '*(f*(o))')); + expect_truthy(isMatch('foofoofo', '*(f*(o))')); + expect_truthy(isMatch('fooofoofofooo', '*(f*(o))')); + expect_truthy(isMatch('foooofo', '*(f*(o))')); + expect_truthy(isMatch('foooofof', '*(f*(o))')); + expect_truthy(!isMatch('foooofofx', '*(f*(o))')); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(f*(o))')); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(f*(o))')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f*(o))')); + expect_truthy(!isMatch('foot', '*(f*(o))')); + expect_truthy(!isMatch('foox', '*(f*(o))')); + expect_truthy(!isMatch('ofoofo', '*(f*(o))')); + expect_truthy(!isMatch('ofooofoofofooo', '*(f*(o))')); + expect_truthy(!isMatch('ofoooxoofxo', '*(f*(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(f*(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(f*(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(f*(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(f*(o))')); + expect_truthy(!isMatch('ofxoofxo', '*(f*(o))')); + expect_truthy(!isMatch('oofooofo', '*(f*(o))')); + expect_truthy(!isMatch('ooo', '*(f*(o))')); + expect_truthy(!isMatch('oxfoxfox', '*(f*(o))')); + expect_truthy(!isMatch('oxfoxoxfox', '*(f*(o))')); + expect_truthy(!isMatch('xfoooofof', '*(f*(o))')); + + expect_truthy(!isMatch('ffffffo', '*(f*(o)x)')); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(f*(o)x)')); + expect_truthy(!isMatch('ffo', '*(f*(o)x)')); + expect_truthy(!isMatch('fofo', '*(f*(o)x)')); + expect_truthy(!isMatch('fofoofoofofoo', '*(f*(o)x)')); + expect_truthy(!isMatch('foo', '*(f*(o)x)')); + expect_truthy(!isMatch('foob', '*(f*(o)x)')); + expect_truthy(!isMatch('foobb', '*(f*(o)x)')); + expect_truthy(!isMatch('foofoofo', '*(f*(o)x)')); + expect_truthy(!isMatch('fooofoofofooo', '*(f*(o)x)')); + expect_truthy(!isMatch('foooofo', '*(f*(o)x)')); + expect_truthy(!isMatch('foooofof', '*(f*(o)x)')); + expect_truthy(!isMatch('foooofofx', '*(f*(o)x)')); + expect_truthy(isMatch('foooxfooxfoxfooox', '*(f*(o)x)')); + expect_truthy(isMatch('foooxfooxfxfooox', '*(f*(o)x)')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f*(o)x)')); + expect_truthy(!isMatch('foot', '*(f*(o)x)')); + expect_truthy(isMatch('foox', '*(f*(o)x)')); + expect_truthy(!isMatch('ofoofo', '*(f*(o)x)')); + expect_truthy(!isMatch('ofooofoofofooo', '*(f*(o)x)')); + expect_truthy(!isMatch('ofoooxoofxo', '*(f*(o)x)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(f*(o)x)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(f*(o)x)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(f*(o)x)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(f*(o)x)')); + expect_truthy(!isMatch('ofxoofxo', '*(f*(o)x)')); + expect_truthy(!isMatch('oofooofo', '*(f*(o)x)')); + expect_truthy(!isMatch('ooo', '*(f*(o)x)')); + expect_truthy(!isMatch('oxfoxfox', '*(f*(o)x)')); + expect_truthy(!isMatch('oxfoxoxfox', '*(f*(o)x)')); + expect_truthy(!isMatch('xfoooofof', '*(f*(o)x)')); + + expect_truthy(!isMatch('ffffffo', '*(f+(o))')); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(f+(o))')); + expect_truthy(!isMatch('ffo', '*(f+(o))')); + expect_truthy(isMatch('fofo', '*(f+(o))')); + expect_truthy(isMatch('fofoofoofofoo', '*(f+(o))')); + expect_truthy(isMatch('foo', '*(f+(o))')); + expect_truthy(!isMatch('foob', '*(f+(o))')); + expect_truthy(!isMatch('foobb', '*(f+(o))')); + expect_truthy(isMatch('foofoofo', '*(f+(o))')); + expect_truthy(isMatch('fooofoofofooo', '*(f+(o))')); + expect_truthy(isMatch('foooofo', '*(f+(o))')); + expect_truthy(!isMatch('foooofof', '*(f+(o))')); + expect_truthy(!isMatch('foooofofx', '*(f+(o))')); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(f+(o))')); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(f+(o))')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f+(o))')); + expect_truthy(!isMatch('foot', '*(f+(o))')); + expect_truthy(!isMatch('foox', '*(f+(o))')); + expect_truthy(!isMatch('ofoofo', '*(f+(o))')); + expect_truthy(!isMatch('ofooofoofofooo', '*(f+(o))')); + expect_truthy(!isMatch('ofoooxoofxo', '*(f+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(f+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(f+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(f+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(f+(o))')); + expect_truthy(!isMatch('ofxoofxo', '*(f+(o))')); + expect_truthy(!isMatch('oofooofo', '*(f+(o))')); + expect_truthy(!isMatch('ooo', '*(f+(o))')); + expect_truthy(!isMatch('oxfoxfox', '*(f+(o))')); + expect_truthy(!isMatch('oxfoxoxfox', '*(f+(o))')); + expect_truthy(!isMatch('xfoooofof', '*(f+(o))')); + + expect_truthy(!isMatch('ffffffo', '*(of+(o))')); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(of+(o))')); + expect_truthy(!isMatch('ffo', '*(of+(o))')); + expect_truthy(!isMatch('fofo', '*(of+(o))')); + expect_truthy(!isMatch('fofoofoofofoo', '*(of+(o))')); + expect_truthy(!isMatch('foo', '*(of+(o))')); + expect_truthy(!isMatch('foob', '*(of+(o))')); + expect_truthy(!isMatch('foobb', '*(of+(o))')); + expect_truthy(!isMatch('foofoofo', '*(of+(o))')); + expect_truthy(!isMatch('fooofoofofooo', '*(of+(o))')); + expect_truthy(!isMatch('foooofo', '*(of+(o))')); + expect_truthy(!isMatch('foooofof', '*(of+(o))')); + expect_truthy(!isMatch('foooofofx', '*(of+(o))')); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(of+(o))')); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(of+(o))')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(of+(o))')); + expect_truthy(!isMatch('foot', '*(of+(o))')); + expect_truthy(!isMatch('foox', '*(of+(o))')); + expect_truthy(isMatch('ofoofo', '*(of+(o))')); + expect_truthy(!isMatch('ofooofoofofooo', '*(of+(o))')); + expect_truthy(!isMatch('ofoooxoofxo', '*(of+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(of+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(of+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(of+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(of+(o))')); + expect_truthy(!isMatch('ofxoofxo', '*(of+(o))')); + expect_truthy(!isMatch('oofooofo', '*(of+(o))')); + expect_truthy(!isMatch('ooo', '*(of+(o))')); + expect_truthy(!isMatch('oxfoxfox', '*(of+(o))')); + expect_truthy(!isMatch('oxfoxoxfox', '*(of+(o))')); + expect_truthy(!isMatch('xfoooofof', '*(of+(o))')); + + expect_truthy(!isMatch('ffffffo', '*(of+(o)|f)')); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(of+(o)|f)')); + expect_truthy(!isMatch('ffo', '*(of+(o)|f)')); + expect_truthy(isMatch('fofo', '*(of+(o)|f)')); + expect_truthy(isMatch('fofoofoofofoo', '*(of+(o)|f)')); + expect_truthy(!isMatch('foo', '*(of+(o)|f)')); + expect_truthy(!isMatch('foob', '*(of+(o)|f)')); + expect_truthy(!isMatch('foobb', '*(of+(o)|f)')); + expect_truthy(!isMatch('foofoofo', '*(of+(o)|f)')); + expect_truthy(!isMatch('fooofoofofooo', '*(of+(o)|f)')); + expect_truthy(!isMatch('foooofo', '*(of+(o)|f)')); + expect_truthy(!isMatch('foooofof', '*(of+(o)|f)')); + expect_truthy(!isMatch('foooofofx', '*(of+(o)|f)')); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(of+(o)|f)')); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(of+(o)|f)')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(of+(o)|f)')); + expect_truthy(!isMatch('foot', '*(of+(o)|f)')); + expect_truthy(!isMatch('foox', '*(of+(o)|f)')); + expect_truthy(isMatch('ofoofo', '*(of+(o)|f)')); + expect_truthy(isMatch('ofooofoofofooo', '*(of+(o)|f)')); + expect_truthy(!isMatch('ofoooxoofxo', '*(of+(o)|f)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(of+(o)|f)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(of+(o)|f)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(of+(o)|f)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(of+(o)|f)')); + expect_truthy(!isMatch('ofxoofxo', '*(of+(o)|f)')); + expect_truthy(!isMatch('oofooofo', '*(of+(o)|f)')); + expect_truthy(!isMatch('ooo', '*(of+(o)|f)')); + expect_truthy(!isMatch('oxfoxfox', '*(of+(o)|f)')); + expect_truthy(!isMatch('oxfoxoxfox', '*(of+(o)|f)')); + expect_truthy(!isMatch('xfoooofof', '*(of+(o)|f)')); + + expect_truthy(!isMatch('ffffffo', '*(of|oof+(o))')); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(of|oof+(o))')); + expect_truthy(!isMatch('ffo', '*(of|oof+(o))')); + expect_truthy(!isMatch('fofo', '*(of|oof+(o))')); + expect_truthy(!isMatch('fofoofoofofoo', '*(of|oof+(o))')); + expect_truthy(!isMatch('foo', '*(of|oof+(o))')); + expect_truthy(!isMatch('foob', '*(of|oof+(o))')); + expect_truthy(!isMatch('foobb', '*(of|oof+(o))')); + expect_truthy(!isMatch('foofoofo', '*(of|oof+(o))')); + expect_truthy(!isMatch('fooofoofofooo', '*(of|oof+(o))')); + expect_truthy(!isMatch('foooofo', '*(of|oof+(o))')); + expect_truthy(!isMatch('foooofof', '*(of|oof+(o))')); + expect_truthy(!isMatch('foooofofx', '*(of|oof+(o))')); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(of|oof+(o))')); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(of|oof+(o))')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(of|oof+(o))')); + expect_truthy(!isMatch('foot', '*(of|oof+(o))')); + expect_truthy(!isMatch('foox', '*(of|oof+(o))')); + expect_truthy(isMatch('ofoofo', '*(of|oof+(o))')); + expect_truthy(!isMatch('ofooofoofofooo', '*(of|oof+(o))')); + expect_truthy(!isMatch('ofoooxoofxo', '*(of|oof+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(of|oof+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(of|oof+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(of|oof+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(of|oof+(o))')); + expect_truthy(!isMatch('ofxoofxo', '*(of|oof+(o))')); + expect_truthy(isMatch('oofooofo', '*(of|oof+(o))')); + expect_truthy(!isMatch('ooo', '*(of|oof+(o))')); + expect_truthy(!isMatch('oxfoxfox', '*(of|oof+(o))')); + expect_truthy(!isMatch('oxfoxoxfox', '*(of|oof+(o))')); + expect_truthy(!isMatch('xfoooofof', '*(of|oof+(o))')); + + expect_truthy(!isMatch('ffffffo', '*(oxf+(ox))')); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(oxf+(ox))')); + expect_truthy(!isMatch('ffo', '*(oxf+(ox))')); + expect_truthy(!isMatch('fofo', '*(oxf+(ox))')); + expect_truthy(!isMatch('fofoofoofofoo', '*(oxf+(ox))')); + expect_truthy(!isMatch('foo', '*(oxf+(ox))')); + expect_truthy(!isMatch('foob', '*(oxf+(ox))')); + expect_truthy(!isMatch('foobb', '*(oxf+(ox))')); + expect_truthy(!isMatch('foofoofo', '*(oxf+(ox))')); + expect_truthy(!isMatch('fooofoofofooo', '*(oxf+(ox))')); + expect_truthy(!isMatch('foooofo', '*(oxf+(ox))')); + expect_truthy(!isMatch('foooofof', '*(oxf+(ox))')); + expect_truthy(!isMatch('foooofofx', '*(oxf+(ox))')); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(oxf+(ox))')); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(oxf+(ox))')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(oxf+(ox))')); + expect_truthy(!isMatch('foot', '*(oxf+(ox))')); + expect_truthy(!isMatch('foox', '*(oxf+(ox))')); + expect_truthy(!isMatch('ofoofo', '*(oxf+(ox))')); + expect_truthy(!isMatch('ofooofoofofooo', '*(oxf+(ox))')); + expect_truthy(!isMatch('ofoooxoofxo', '*(oxf+(ox))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(oxf+(ox))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(oxf+(ox))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(oxf+(ox))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(oxf+(ox))')); + expect_truthy(!isMatch('ofxoofxo', '*(oxf+(ox))')); + expect_truthy(!isMatch('oofooofo', '*(oxf+(ox))')); + expect_truthy(!isMatch('ooo', '*(oxf+(ox))')); + expect_truthy(!isMatch('oxfoxfox', '*(oxf+(ox))')); + expect_truthy(isMatch('oxfoxoxfox', '*(oxf+(ox))')); + expect_truthy(!isMatch('xfoooofof', '*(oxf+(ox))')); + + expect_truthy(isMatch('ffffffo', '@(!(z*)|*x)')); + expect_truthy(isMatch('fffooofoooooffoofffooofff', '@(!(z*)|*x)')); + expect_truthy(isMatch('ffo', '@(!(z*)|*x)')); + expect_truthy(isMatch('fofo', '@(!(z*)|*x)')); + expect_truthy(isMatch('fofoofoofofoo', '@(!(z*)|*x)')); + expect_truthy(isMatch('foo', '@(!(z*)|*x)')); + expect_truthy(isMatch('foob', '@(!(z*)|*x)')); + expect_truthy(isMatch('foobb', '@(!(z*)|*x)')); + expect_truthy(isMatch('foofoofo', '@(!(z*)|*x)')); + expect_truthy(isMatch('fooofoofofooo', '@(!(z*)|*x)')); + expect_truthy(isMatch('foooofo', '@(!(z*)|*x)')); + expect_truthy(isMatch('foooofof', '@(!(z*)|*x)')); + expect_truthy(isMatch('foooofofx', '@(!(z*)|*x)')); + expect_truthy(isMatch('foooxfooxfoxfooox', '@(!(z*)|*x)')); + expect_truthy(isMatch('foooxfooxfxfooox', '@(!(z*)|*x)')); + expect_truthy(isMatch('foooxfooxofoxfooox', '@(!(z*)|*x)')); + expect_truthy(isMatch('foot', '@(!(z*)|*x)')); + expect_truthy(isMatch('foox', '@(!(z*)|*x)')); + expect_truthy(isMatch('ofoofo', '@(!(z*)|*x)')); + expect_truthy(isMatch('ofooofoofofooo', '@(!(z*)|*x)')); + expect_truthy(isMatch('ofoooxoofxo', '@(!(z*)|*x)')); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxo', '@(!(z*)|*x)')); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxofo', '@(!(z*)|*x)')); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxoo', '@(!(z*)|*x)')); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxooofxofxo', '@(!(z*)|*x)')); + expect_truthy(isMatch('ofxoofxo', '@(!(z*)|*x)')); + expect_truthy(isMatch('oofooofo', '@(!(z*)|*x)')); + expect_truthy(isMatch('ooo', '@(!(z*)|*x)')); + expect_truthy(isMatch('oxfoxfox', '@(!(z*)|*x)')); + expect_truthy(isMatch('oxfoxoxfox', '@(!(z*)|*x)')); + expect_truthy(isMatch('xfoooofof', '@(!(z*)|*x)')); + + expect_truthy(!isMatch('ffffffo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('ffo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(isMatch('fofo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(isMatch('fofoofoofofoo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(isMatch('foo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('foob', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('foobb', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(isMatch('fooofoofofooo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('foooofo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('foooofof', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('foooofofx', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('foooxfooxfoxfooox', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('foooxfooxfxfooox', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('foot', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('foox', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('ofoofo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('ofooofoofofooo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('ofoooxoofxo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('ofxoofxo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('oofooofo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('ooo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('oxfoxfox', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('oxfoxoxfox', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(!isMatch('xfoooofof', '@(foo|f|fo)*(f|of+(o))')); + + expect_truthy(isMatch('aaac', '*(@(a))a@(c)')); + expect_truthy(isMatch('aac', '*(@(a))a@(c)')); + expect_truthy(isMatch('ac', '*(@(a))a@(c)')); + expect_truthy(!isMatch('abbcd', '*(@(a))a@(c)')); + expect_truthy(!isMatch('abcd', '*(@(a))a@(c)')); + expect_truthy(!isMatch('acd', '*(@(a))a@(c)')); + expect_truthy(!isMatch('baaac', '*(@(a))a@(c)')); + expect_truthy(!isMatch('c', '*(@(a))a@(c)')); + expect_truthy(!isMatch('foo', '*(@(a))a@(c)')); + + expect_truthy(!isMatch('aaac', '@(ab|a*(b))*(c)d')); + expect_truthy(!isMatch('aac', '@(ab|a*(b))*(c)d')); + expect_truthy(!isMatch('ac', '@(ab|a*(b))*(c)d')); + expect_truthy(isMatch('abbcd', '@(ab|a*(b))*(c)d')); + expect_truthy(isMatch('abcd', '@(ab|a*(b))*(c)d')); + expect_truthy(isMatch('acd', '@(ab|a*(b))*(c)d')); + expect_truthy(!isMatch('baaac', '@(ab|a*(b))*(c)d')); + expect_truthy(!isMatch('c', '@(ab|a*(b))*(c)d')); + expect_truthy(!isMatch('foo', '@(ab|a*(b))*(c)d')); + + expect_truthy(!isMatch('aaac', '?@(a|b)*@(c)d')); + expect_truthy(!isMatch('aac', '?@(a|b)*@(c)d')); + expect_truthy(!isMatch('ac', '?@(a|b)*@(c)d')); + expect_truthy(isMatch('abbcd', '?@(a|b)*@(c)d')); + expect_truthy(isMatch('abcd', '?@(a|b)*@(c)d')); + expect_truthy(!isMatch('acd', '?@(a|b)*@(c)d')); + expect_truthy(!isMatch('baaac', '?@(a|b)*@(c)d')); + expect_truthy(!isMatch('c', '?@(a|b)*@(c)d')); + expect_truthy(!isMatch('foo', '?@(a|b)*@(c)d')); + + expect_truthy(!isMatch('aaac', '@(ab|a*@(b))*(c)d')); + expect_truthy(!isMatch('aac', '@(ab|a*@(b))*(c)d')); + expect_truthy(!isMatch('ac', '@(ab|a*@(b))*(c)d')); + expect_truthy(isMatch('abbcd', '@(ab|a*@(b))*(c)d')); + expect_truthy(isMatch('abcd', '@(ab|a*@(b))*(c)d')); + expect_truthy(!isMatch('acd', '@(ab|a*@(b))*(c)d')); + expect_truthy(!isMatch('baaac', '@(ab|a*@(b))*(c)d')); + expect_truthy(!isMatch('c', '@(ab|a*@(b))*(c)d')); + expect_truthy(!isMatch('foo', '@(ab|a*@(b))*(c)d')); + + expect_truthy(!isMatch('aac', '*(@(a))b@(c)')); + }); + + test('should backtrack in alternation matches', () => { + expect_truthy(!isMatch('ffffffo', '*(fo|foo)')); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(fo|foo)')); + expect_truthy(!isMatch('ffo', '*(fo|foo)')); + expect_truthy(isMatch('fofo', '*(fo|foo)')); + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)')); + expect_truthy(isMatch('foo', '*(fo|foo)')); + expect_truthy(!isMatch('foob', '*(fo|foo)')); + expect_truthy(!isMatch('foobb', '*(fo|foo)')); + expect_truthy(isMatch('foofoofo', '*(fo|foo)')); + expect_truthy(!isMatch('fooofoofofooo', '*(fo|foo)')); + expect_truthy(!isMatch('foooofo', '*(fo|foo)')); + expect_truthy(!isMatch('foooofof', '*(fo|foo)')); + expect_truthy(!isMatch('foooofofx', '*(fo|foo)')); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(fo|foo)')); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(fo|foo)')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(fo|foo)')); + expect_truthy(!isMatch('foot', '*(fo|foo)')); + expect_truthy(!isMatch('foox', '*(fo|foo)')); + expect_truthy(!isMatch('ofoofo', '*(fo|foo)')); + expect_truthy(!isMatch('ofooofoofofooo', '*(fo|foo)')); + expect_truthy(!isMatch('ofoooxoofxo', '*(fo|foo)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(fo|foo)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(fo|foo)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(fo|foo)')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(fo|foo)')); + expect_truthy(!isMatch('ofxoofxo', '*(fo|foo)')); + expect_truthy(!isMatch('oofooofo', '*(fo|foo)')); + expect_truthy(!isMatch('ooo', '*(fo|foo)')); + expect_truthy(!isMatch('oxfoxfox', '*(fo|foo)')); + expect_truthy(!isMatch('oxfoxoxfox', '*(fo|foo)')); + expect_truthy(!isMatch('xfoooofof', '*(fo|foo)')); + }); + + test('should support exclusions', () => { + expect_truthy(!isMatch('foob', '!(foo)b*')); + expect_truthy(!isMatch('foobb', '!(foo)b*')); + expect_truthy(!isMatch('foo', '!(foo)b*')); + expect_truthy(isMatch('bar', '!(foo)b*')); + expect_truthy(isMatch('baz', '!(foo)b*')); + expect_truthy(!isMatch('foobar', '!(foo)b*')); + + expect_truthy(!isMatch('foo', '*(!(foo))')); + expect_truthy(isMatch('bar', '*(!(foo))')); + expect_truthy(isMatch('baz', '*(!(foo))')); + expect_truthy(isMatch('foobar', '*(!(foo))')); + + // Bash 4.3 says this should match `foo` and `foobar`, which makes no sense + expect_truthy(!isMatch('foo', '!(foo)*')); + expect_truthy(!isMatch('foobar', '!(foo)*')); + expect_truthy(isMatch('bar', '!(foo)*')); + expect_truthy(isMatch('baz', '!(foo)*')); + + expect_truthy(!isMatch('moo.cow', '!(*.*)')); + expect_truthy(isMatch('moo', '!(*.*)')); + expect_truthy(isMatch('cow', '!(*.*)')); + + expect_truthy(isMatch('moo.cow', '!(a*).!(b*)')); + expect_truthy(!isMatch('moo.cow', '!(*).!(*)')); + expect_truthy(!isMatch('moo.cow.moo.cow', '!(*.*).!(*.*)')); + expect_truthy(!isMatch('mad.moo.cow', '!(*.*).!(*.*)')); + + expect_truthy(!isMatch('moo.cow', '!(*.*).')); + expect_truthy(!isMatch('moo', '!(*.*).')); + expect_truthy(!isMatch('cow', '!(*.*).')); + + expect_truthy(!isMatch('moo.cow', '.!(*.*)')); + expect_truthy(!isMatch('moo', '.!(*.*)')); + expect_truthy(!isMatch('cow', '.!(*.*)')); + + expect_truthy(!isMatch('mucca.pazza', 'mu!(*(c))?.pa!(*(z))?')); + + expect_truthy(isMatch('effgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + expect_truthy(isMatch('efgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + expect_truthy(isMatch('egz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + expect_truthy(!isMatch('egz', '@(b+(c)d|e+(f)g?|?(h)i@(j|k))')); + expect_truthy(isMatch('egzefffgzbcdij', '*(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + }); + + test('valid numbers', () => { + expect_truthy(isMatch('/dev/udp/129.22.8.102/45', '/dev/@(tcp|udp)/*/*')); + + expect_truthy(!isMatch('0', '[1-6]([0-9])')); + expect_truthy(isMatch('12', '[1-6]([0-9])')); + expect_truthy(!isMatch('1', '[1-6]([0-9])')); + expect_truthy(!isMatch('12abc', '[1-6]([0-9])')); + expect_truthy(!isMatch('555', '[1-6]([0-9])')); + + expect_truthy(!isMatch('0', '[1-6]*([0-9])')); + expect_truthy(isMatch('12', '[1-6]*([0-9])')); + expect_truthy(isMatch('1', '[1-6]*([0-9])')); + expect_truthy(!isMatch('12abc', '[1-6]*([0-9])')); + expect_truthy(isMatch('555', '[1-6]*([0-9])')); + + expect_truthy(!isMatch('0', '[1-5]*([6-9])')); + expect_truthy(!isMatch('12', '[1-5]*([6-9])')); + expect_truthy(isMatch('1', '[1-5]*([6-9])')); + expect_truthy(!isMatch('12abc', '[1-5]*([6-9])')); + expect_truthy(!isMatch('555', '[1-5]*([6-9])')); + + expect_truthy(isMatch('0', '0|[1-6]*([0-9])')); + expect_truthy(isMatch('12', '0|[1-6]*([0-9])')); + expect_truthy(isMatch('1', '0|[1-6]*([0-9])')); + expect_truthy(!isMatch('12abc', '0|[1-6]*([0-9])')); + expect_truthy(isMatch('555', '0|[1-6]*([0-9])')); + + expect_truthy(isMatch('07', '+([0-7])')); + expect_truthy(isMatch('0377', '+([0-7])')); + expect_truthy(!isMatch('09', '+([0-7])')); + }); + + test('check extended globbing in pattern removal', () => { + expect_truthy(isMatch('a', '+(a|abc)')); + expect_truthy(isMatch('abc', '+(a|abc)')); + + expect_truthy(!isMatch('abcd', '+(a|abc)')); + expect_truthy(!isMatch('abcde', '+(a|abc)')); + expect_truthy(!isMatch('abcedf', '+(a|abc)')); + + expect_truthy(isMatch('f', '+(def|f)')); + expect_truthy(isMatch('def', '+(f|def)')); + + expect_truthy(!isMatch('cdef', '+(f|def)')); + expect_truthy(!isMatch('bcdef', '+(f|def)')); + expect_truthy(!isMatch('abcedf', '+(f|def)')); + + expect_truthy(isMatch('abcd', '*(a|b)cd')); + + expect_truthy(!isMatch('a', '*(a|b)cd')); + expect_truthy(!isMatch('ab', '*(a|b)cd')); + expect_truthy(!isMatch('abc', '*(a|b)cd')); + + expect_truthy(!isMatch('a', '"*(a|b)cd"')); + expect_truthy(!isMatch('ab', '"*(a|b)cd"')); + expect_truthy(!isMatch('abc', '"*(a|b)cd"')); + expect_truthy(!isMatch('abcde', '"*(a|b)cd"')); + expect_truthy(!isMatch('abcdef', '"*(a|b)cd"')); + }); + + test('More tests derived from a bug report (in bash) concerning extended glob patterns following a *', () => { + expect_truthy(isMatch('/dev/udp/129.22.8.102/45', '/dev\\/@(tcp|udp)\\/*\\/*')); + expect_truthy(!isMatch('123abc', '(a+|b)*')); + expect_truthy(isMatch('ab', '(a+|b)*')); + expect_truthy(isMatch('abab', '(a+|b)*')); + expect_truthy(isMatch('abcdef', '(a+|b)*')); + expect_truthy(isMatch('accdef', '(a+|b)*')); + expect_truthy(isMatch('abcfefg', '(a+|b)*')); + expect_truthy(isMatch('abef', '(a+|b)*')); + expect_truthy(isMatch('abcfef', '(a+|b)*')); + expect_truthy(isMatch('abd', '(a+|b)*')); + expect_truthy(isMatch('acd', '(a+|b)*')); + + expect_truthy(!isMatch('123abc', '(a+|b)+')); + expect_truthy(isMatch('ab', '(a+|b)+')); + expect_truthy(isMatch('abab', '(a+|b)+')); + expect_truthy(!isMatch('abcdef', '(a+|b)+')); + expect_truthy(!isMatch('accdef', '(a+|b)+')); + expect_truthy(!isMatch('abcfefg', '(a+|b)+')); + expect_truthy(!isMatch('abef', '(a+|b)+')); + expect_truthy(!isMatch('abcfef', '(a+|b)+')); + expect_truthy(!isMatch('abd', '(a+|b)+')); + expect_truthy(!isMatch('acd', '(a+|b)+')); + + expect_truthy(!isMatch('123abc', 'a(b*(foo|bar))d')); + expect_truthy(!isMatch('ab', 'a(b*(foo|bar))d')); + expect_truthy(!isMatch('abab', 'a(b*(foo|bar))d')); + expect_truthy(!isMatch('abcdef', 'a(b*(foo|bar))d')); + expect_truthy(!isMatch('accdef', 'a(b*(foo|bar))d')); + expect_truthy(!isMatch('abcfefg', 'a(b*(foo|bar))d')); + expect_truthy(!isMatch('abef', 'a(b*(foo|bar))d')); + expect_truthy(!isMatch('abcfef', 'a(b*(foo|bar))d')); + expect_truthy(isMatch('abd', 'a(b*(foo|bar))d')); + expect_truthy(!isMatch('acd', 'a(b*(foo|bar))d')); + + expect_truthy(!isMatch('123abc', 'ab*(e|f)')); + expect_truthy(isMatch('ab', 'ab*(e|f)')); + expect_truthy(!isMatch('abab', 'ab*(e|f)')); + expect_truthy(!isMatch('abcdef', 'ab*(e|f)')); + expect_truthy(!isMatch('accdef', 'ab*(e|f)')); + expect_truthy(!isMatch('abcfefg', 'ab*(e|f)')); + expect_truthy(isMatch('abef', 'ab*(e|f)')); + expect_truthy(!isMatch('abcfef', 'ab*(e|f)')); + expect_truthy(!isMatch('abd', 'ab*(e|f)')); + expect_truthy(!isMatch('acd', 'ab*(e|f)')); + + expect_truthy(!isMatch('123abc', 'ab**(e|f)')); + expect_truthy(isMatch('ab', 'ab**(e|f)')); + expect_truthy(isMatch('abab', 'ab**(e|f)')); + expect_truthy(isMatch('abcdef', 'ab**(e|f)')); + expect_truthy(!isMatch('accdef', 'ab**(e|f)')); + expect_truthy(isMatch('abcfefg', 'ab**(e|f)')); + expect_truthy(isMatch('abef', 'ab**(e|f)')); + expect_truthy(isMatch('abcfef', 'ab**(e|f)')); + expect_truthy(isMatch('abd', 'ab**(e|f)')); + expect_truthy(!isMatch('acd', 'ab**(e|f)')); + + expect_truthy(!isMatch('123abc', 'ab**(e|f)g')); + expect_truthy(!isMatch('ab', 'ab**(e|f)g')); + expect_truthy(!isMatch('abab', 'ab**(e|f)g')); + expect_truthy(!isMatch('abcdef', 'ab**(e|f)g')); + expect_truthy(!isMatch('accdef', 'ab**(e|f)g')); + expect_truthy(isMatch('abcfefg', 'ab**(e|f)g')); + expect_truthy(!isMatch('abef', 'ab**(e|f)g')); + expect_truthy(!isMatch('abcfef', 'ab**(e|f)g')); + expect_truthy(!isMatch('abd', 'ab**(e|f)g')); + expect_truthy(!isMatch('acd', 'ab**(e|f)g')); + + expect_truthy(!isMatch('123abc', 'ab***ef')); + expect_truthy(!isMatch('ab', 'ab***ef')); + expect_truthy(!isMatch('abab', 'ab***ef')); + expect_truthy(isMatch('abcdef', 'ab***ef')); + expect_truthy(!isMatch('accdef', 'ab***ef')); + expect_truthy(!isMatch('abcfefg', 'ab***ef')); + expect_truthy(isMatch('abef', 'ab***ef')); + expect_truthy(isMatch('abcfef', 'ab***ef')); + expect_truthy(!isMatch('abd', 'ab***ef')); + expect_truthy(!isMatch('acd', 'ab***ef')); + + expect_truthy(!isMatch('123abc', 'ab*+(e|f)')); + expect_truthy(!isMatch('ab', 'ab*+(e|f)')); + expect_truthy(!isMatch('abab', 'ab*+(e|f)')); + expect_truthy(isMatch('abcdef', 'ab*+(e|f)')); + expect_truthy(!isMatch('accdef', 'ab*+(e|f)')); + expect_truthy(!isMatch('abcfefg', 'ab*+(e|f)')); + expect_truthy(isMatch('abef', 'ab*+(e|f)')); + expect_truthy(isMatch('abcfef', 'ab*+(e|f)')); + expect_truthy(!isMatch('abd', 'ab*+(e|f)')); + expect_truthy(!isMatch('acd', 'ab*+(e|f)')); + + expect_truthy(!isMatch('123abc', 'ab*d*(e|f)')); + expect_truthy(!isMatch('ab', 'ab*d*(e|f)')); + expect_truthy(!isMatch('abab', 'ab*d*(e|f)')); + expect_truthy(isMatch('abcdef', 'ab*d*(e|f)')); + expect_truthy(!isMatch('accdef', 'ab*d*(e|f)')); + expect_truthy(!isMatch('abcfefg', 'ab*d*(e|f)')); + expect_truthy(!isMatch('abef', 'ab*d*(e|f)')); + expect_truthy(!isMatch('abcfef', 'ab*d*(e|f)')); + expect_truthy(isMatch('abd', 'ab*d*(e|f)')); + expect_truthy(!isMatch('acd', 'ab*d*(e|f)')); + + expect_truthy(!isMatch('123abc', 'ab*d+(e|f)')); + expect_truthy(!isMatch('ab', 'ab*d+(e|f)')); + expect_truthy(!isMatch('abab', 'ab*d+(e|f)')); + expect_truthy(isMatch('abcdef', 'ab*d+(e|f)')); + expect_truthy(!isMatch('accdef', 'ab*d+(e|f)')); + expect_truthy(!isMatch('abcfefg', 'ab*d+(e|f)')); + expect_truthy(!isMatch('abef', 'ab*d+(e|f)')); + expect_truthy(!isMatch('abcfef', 'ab*d+(e|f)')); + expect_truthy(!isMatch('abd', 'ab*d+(e|f)')); + expect_truthy(!isMatch('acd', 'ab*d+(e|f)')); + + expect_truthy(!isMatch('123abc', 'ab?*(e|f)')); + expect_truthy(!isMatch('ab', 'ab?*(e|f)')); + expect_truthy(!isMatch('abab', 'ab?*(e|f)')); + expect_truthy(!isMatch('abcdef', 'ab?*(e|f)')); + expect_truthy(!isMatch('accdef', 'ab?*(e|f)')); + expect_truthy(!isMatch('abcfefg', 'ab?*(e|f)')); + expect_truthy(isMatch('abef', 'ab?*(e|f)')); + expect_truthy(isMatch('abcfef', 'ab?*(e|f)')); + expect_truthy(isMatch('abd', 'ab?*(e|f)')); + expect_truthy(!isMatch('acd', 'ab?*(e|f)')); + }); + + test('bug in all versions up to and including bash-2.05b', () => { + expect_truthy(isMatch('123abc', '*?(a)bc')); + }); + + test('should work with character classes', () => { + let opts = { posix: true }; + expect_truthy(isMatch('a.b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a,b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a:b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a-b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a;b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a_b', 'a[^[:alnum:]]b', opts)); + + expect_truthy(isMatch('a.b', 'a[-.,:\\;\\ _]b')); + expect_truthy(isMatch('a,b', 'a[-.,:\\;\\ _]b')); + expect_truthy(isMatch('a:b', 'a[-.,:\\;\\ _]b')); + expect_truthy(isMatch('a-b', 'a[-.,:\\;\\ _]b')); + expect_truthy(isMatch('a;b', 'a[-.,:\\;\\ _]b')); + expect_truthy(isMatch('a b', 'a[-.,:\\;\\ _]b')); + expect_truthy(isMatch('a_b', 'a[-.,:\\;\\ _]b')); + + expect_truthy(isMatch('a.b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a,b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a:b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a-b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a;b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a_b', 'a@([^[:alnum:]])b', opts)); + + expect_truthy(isMatch('a.b', 'a@([-.,:; _])b')); + expect_truthy(isMatch('a,b', 'a@([-.,:; _])b')); + expect_truthy(isMatch('a:b', 'a@([-.,:; _])b')); + expect_truthy(isMatch('a-b', 'a@([-.,:; _])b')); + expect_truthy(isMatch('a;b', 'a@([-.,:; _])b')); + expect_truthy(isMatch('a b', 'a@([-.,:; _])b')); + expect_truthy(isMatch('a_b', 'a@([-.,:; _])b')); + + expect_truthy(isMatch('a.b', 'a@([.])b')); + expect_truthy(!isMatch('a,b', 'a@([.])b')); + expect_truthy(!isMatch('a:b', 'a@([.])b')); + expect_truthy(!isMatch('a-b', 'a@([.])b')); + expect_truthy(!isMatch('a;b', 'a@([.])b')); + expect_truthy(!isMatch('a b', 'a@([.])b')); + expect_truthy(!isMatch('a_b', 'a@([.])b')); + + expect_truthy(!isMatch('a.b', 'a@([^.])b')); + expect_truthy(isMatch('a,b', 'a@([^.])b')); + expect_truthy(isMatch('a:b', 'a@([^.])b')); + expect_truthy(isMatch('a-b', 'a@([^.])b')); + expect_truthy(isMatch('a;b', 'a@([^.])b')); + expect_truthy(isMatch('a b', 'a@([^.])b')); + expect_truthy(isMatch('a_b', 'a@([^.])b')); + + expect_truthy(isMatch('a.b', 'a@([^x])b')); + expect_truthy(isMatch('a,b', 'a@([^x])b')); + expect_truthy(isMatch('a:b', 'a@([^x])b')); + expect_truthy(isMatch('a-b', 'a@([^x])b')); + expect_truthy(isMatch('a;b', 'a@([^x])b')); + expect_truthy(isMatch('a b', 'a@([^x])b')); + expect_truthy(isMatch('a_b', 'a@([^x])b')); + + expect_truthy(isMatch('a.b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a,b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a:b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a-b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a;b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a_b', 'a+([^[:alnum:]])b', opts)); + + expect_truthy(isMatch('a.b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a,b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a:b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a-b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a;b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a_b', 'a@(.|[^[:alnum:]])b', opts)); + }); + + test('should support POSIX character classes in extglobs', () => { + let opts = { posix: true }; + expect_truthy(isMatch('a.c', '+([[:alpha:].])', opts)); + expect_truthy(isMatch('a.c', '+([[:alpha:].])+([[:alpha:].])', opts)); + expect_truthy(isMatch('a.c', '*([[:alpha:].])', opts)); + expect_truthy(isMatch('a.c', '*([[:alpha:].])*([[:alpha:].])', opts)); + expect_truthy(isMatch('a.c', '?([[:alpha:].])?([[:alpha:].])?([[:alpha:].])', opts)); + expect_truthy(isMatch('a.c', '@([[:alpha:].])@([[:alpha:].])@([[:alpha:].])', opts)); + expect_truthy(!isMatch('.', '!(\\.)', opts)); + expect_truthy(!isMatch('.', '!([[:alpha:].])', opts)); + expect_truthy(isMatch('.', '?([[:alpha:].])', opts)); + expect_truthy(isMatch('.', '@([[:alpha:].])', opts)); + }); + + // ported from http://www.bashcookbook.com/bashinfo/source/bash-4.3/tests/extglob2.tests + test('should pass extglob2 tests', () => { + expect_truthy(!isMatch('baaac', '*(@(a))a@(c)')); + expect_truthy(!isMatch('c', '*(@(a))a@(c)')); + expect_truthy(!isMatch('egz', '@(b+(c)d|e+(f)g?|?(h)i@(j|k))')); + expect_truthy(!isMatch('foooofof', '*(f+(o))')); + expect_truthy(!isMatch('foooofofx', '*(f*(o))')); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f*(o)x)')); + expect_truthy(!isMatch('ofooofoofofooo', '*(f*(o))')); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(*(of*(o)x)o)')); + expect_truthy(!isMatch('oxfoxfox', '*(oxf+(ox))')); + expect_truthy(!isMatch('xfoooofof', '*(f*(o))')); + expect_truthy(isMatch('aaac', '*(@(a))a@(c)')); + expect_truthy(isMatch('aac', '*(@(a))a@(c)')); + expect_truthy(isMatch('abbcd', '@(ab|a*(b))*(c)d')); + expect_truthy(isMatch('abcd', '?@(a|b)*@(c)d')); + expect_truthy(isMatch('abcd', '@(ab|a*@(b))*(c)d')); + expect_truthy(isMatch('ac', '*(@(a))a@(c)')); + expect_truthy(isMatch('acd', '@(ab|a*(b))*(c)d')); + expect_truthy(isMatch('effgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + expect_truthy(isMatch('efgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + expect_truthy(isMatch('egz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + expect_truthy(isMatch('egzefffgzbcdij', '*(b+(c)d|e*(f)g?|?(h)i@(j|k))')); + expect_truthy(isMatch('fffooofoooooffoofffooofff', '*(*(f)*(o))')); + expect_truthy(isMatch('ffo', '*(f*(o))')); + expect_truthy(isMatch('fofo', '*(f*(o))')); + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))')); + expect_truthy(isMatch('fooofoofofooo', '*(f*(o))')); + expect_truthy(isMatch('foooofo', '*(f*(o))')); + expect_truthy(isMatch('foooofof', '*(f*(o))')); + expect_truthy(isMatch('foooxfooxfoxfooox', '*(f*(o)x)')); + expect_truthy(isMatch('foooxfooxfxfooox', '*(f*(o)x)')); + expect_truthy(isMatch('ofoofo', '*(of+(o))')); + expect_truthy(isMatch('ofoofo', '*(of+(o)|f)')); + expect_truthy(isMatch('ofoooxoofxo', '*(*(of*(o)x)o)')); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxo', '*(*(of*(o)x)o)')); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxoo', '*(*(of*(o)x)o)')); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(*(of*(o)x)o)')); + expect_truthy(isMatch('ofxoofxo', '*(*(of*(o)x)o)')); + expect_truthy(isMatch('oofooofo', '*(of|oof+(o))')); + expect_truthy(isMatch('oxfoxoxfox', '*(oxf+(ox))')); + }); + + test('should support backtracking in alternation matches', () => { + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)')); + }); + + test('should support exclusions', () => { + expect_truthy(!isMatch('f', '!(f)')); + expect_truthy(!isMatch('f', '*(!(f))')); + expect_truthy(!isMatch('f', '+(!(f))')); + expect_truthy(!isMatch('foo', '!(foo)')); + expect_truthy(!isMatch('foob', '!(foo)b*')); + expect_truthy(!isMatch('mad.moo.cow', '!(*.*).!(*.*)')); + expect_truthy(!isMatch('mucca.pazza', 'mu!(*(c))?.pa!(*(z))?')); + expect_truthy(!isMatch('zoot', '@(!(z*)|*x)')); + expect_truthy(isMatch('fff', '!(f)')); + expect_truthy(isMatch('fff', '*(!(f))')); + expect_truthy(isMatch('fff', '+(!(f))')); + expect_truthy(isMatch('foo', '!(f)')); + expect_truthy(isMatch('foo', '!(x)')); + expect_truthy(isMatch('foo', '!(x)*')); + expect_truthy(isMatch('foo', '*(!(f))')); + expect_truthy(isMatch('foo', '+(!(f))')); + expect_truthy(isMatch('foobar', '!(foo)')); + expect_truthy(isMatch('foot', '@(!(z*)|*x)')); + expect_truthy(isMatch('foox', '@(!(z*)|*x)')); + expect_truthy(isMatch('ooo', '!(f)')); + expect_truthy(isMatch('ooo', '*(!(f))')); + expect_truthy(isMatch('ooo', '+(!(f))')); + expect_truthy(isMatch('zoox', '@(!(z*)|*x)')); + }); +}); diff --git a/packages/node-utils/test/micromatch/fixtures/patterns.ts b/packages/node-utils/test/micromatch/fixtures/patterns.ts new file mode 100644 index 0000000..0483181 --- /dev/null +++ b/packages/node-utils/test/micromatch/fixtures/patterns.ts @@ -0,0 +1,271 @@ + +/** + * The contents of this file was copied (and modified) from: + * minimatch v3.0.3, ISC LICENSE, Copyright (c) Isaac Z. Schlueter and Contributors + * https://github.com/isaacs/minimatch + */ + +let fixtures = ['a', 'b', 'c', 'd', 'abc', 'abd', 'abe', 'bb', 'bcd', 'ca', 'cb', 'dd', 'de', 'bdir/', 'bdir/cfile']; + +// pattern | expected | options | fixtures +const patterns: any[] = [ + 'http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test', + ['a*', ['a', 'abc', 'abd', 'abe']], + ['X*', ['X*'], {nonull: true}], + + 'allow null glob expansion', + ['X*', []], + + 'micromatch has same results as Bash. Minimatch does this differently', + ['\\*', ['\\*'], {nonull: true}], + ['\\**', ['\\**'], {nonull: true}], + ['\\*\\*', ['\\*\\*'], {nonull: true}], + + ['b*/', ['bdir/']], + ['c*', ['c', 'ca', 'cb']], + ['**', fixtures], + + ['\\.\\./*/', ['\\.\\./*/'], {nonull: true}], + ['s/\\..*//', ['s/\\..*//'], {nonull: true}], + + 'legendary larry crashes bashes', + ['/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/', + ['/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/'], {nonull: true, bash: {skip: true}}], + ['/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\u0001/', + ['/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\u0001/'], {nonull: true, bash: {skip: true}}], + + /** + * Character classes + */ + + 'character classes', + ['[a-c]b*', ['abc', 'abd', 'abe', 'bb', 'cb']], + ['[a-y]*[^c]', ['abd', 'abe', 'bb', 'bcd', 'bdir/', 'ca', 'cb', 'dd', 'de'], {bash: true}], + ['a*[^c]', ['abd', 'abe']], + () => { + fixtures.push('a-b', 'aXb'); + }, + ['a[X-]b', ['a-b', 'aXb']], + () => { + fixtures.push('.x', '.y'); + }, + ['[^a-c]*', ['d', 'dd', 'de']], + () => { + fixtures.push('a*b/', 'a*b/ooo', 'ab/ooo'); + }, + + 'trailing slashes', + ['a*b/*', ['ab/ooo', 'a*b/ooo']], + ['a*?/*', ['ab/ooo', 'a*b/ooo']], + ['a\\*b/*', ['a*b/ooo']], + ['a\\*?/*', ['a*b/ooo']], + ['*\\\\!*', [], {null: true}, ['echo !7']], + ['*\\!*', ['echo !7'], null, ['echo !7']], + ['*.\\*', ['r.*'], null, ['r.*']], + ['a[b]c', ['abc']], + ['a[\\\\b]c', ['abc']], + ['a?c', ['abc']], + ['a\\*c', [], {null: true}, ['abc']], + + /** + * Bash tests + */ + + 'http://www.opensource.apple.com/source/bash/bash-23/bash/tests/glob-test', + () => { + fixtures.push('man/', 'man/man1/', 'man/man1/bash.1'); + }, + ['*/man*/bash.*', ['man/man1/bash.1']], + ['man/man1/bash.1', ['man/man1/bash.1']], + ['a***c', ['abc'], null, ['abc']], + ['a*****?c', ['abc'], null, ['abc']], + ['?*****??', ['abc'], null, ['abc']], + ['*****??', ['abc'], null, ['abc']], + ['?*****?c', ['abc'], null, ['abc']], + ['?***?****c', ['abc'], null, ['abc']], + ['?***?****?', ['abc'], null, ['abc']], + ['?***?****', ['abc'], null, ['abc']], + ['*******c', ['abc'], null, ['abc']], + ['*******?', ['abc'], null, ['abc']], + ['a*cd**?**??k', ['abcdecdhjk'], null, ['abcdecdhjk']], + ['a**?**cd**?**??k', ['abcdecdhjk'], null, ['abcdecdhjk']], + ['a**?**cd**?**??k***', ['abcdecdhjk'], null, ['abcdecdhjk']], + ['a**?**cd**?**??***k', ['abcdecdhjk'], null, ['abcdecdhjk']], + ['a**?**cd**?**??***k**', ['abcdecdhjk'], null, ['abcdecdhjk']], + ['a****c**?**??*****', ['abcdecdhjk'], null, ['abcdecdhjk']], + ['[-abc]', ['-'], null, ['-']], + ['[abc-]', ['-'], null, ['-']], + ['[[]', ['['], null, ['[']], + ['[', ['['], null, ['[']], + ['[*', ['[abc'], null, ['[abc']], + + 'a right bracket shall lose its special meaning and represent itself in a bracket expression if it occurs first in the list. -- POSIX.2 2.8.3.2', + ['[]]', [']'], null, [']']], + ['[]-]', [']'], null, [']']], + ['[a-\z]', ['p'], null, ['p']], + ['??**********?****?', [], { null: true }, ['abc']], + ['??**********?****c', [], { null: true }, ['abc']], + ['?************c****?****', [], { null: true }, ['abc']], + ['*c*?**', [], { null: true }, ['abc']], + ['a*****c*?**', [], { null: true }, ['abc']], + ['a********???*******', [], { null: true }, ['abc']], + ['[]', [], { null: true }, ['a']], + ['[abc', [], { null: true }, ['[']], + + 'nocase tests', + ['XYZ', ['xYz'], { nocase: true, null: true }, + ['xYz', 'ABC', 'IjK']], + [ + 'ab*', + ['ABC'], + { nocase: true, null: true }, + ['xYz', 'ABC', 'IjK'] + ], + [ + '[ia]?[ck]', + ['ABC', 'IjK'], + { nocase: true, null: true }, + ['xYz', 'ABC', 'IjK'] + ], + + 'braces: onestar/twostar', + ['{/*,*}', [], {null: true}, ['/asdf/asdf/asdf']], + ['{/?,*}', ['/a', 'bb'], {null: true}, ['/a', '/b/b', '/a/b/c', 'bb']], + + 'dots should not match unless requested', + ['**', ['a/b'], {}, ['a/b', 'a/.d', '.a/.d']], + + // .. and . can only match patterns starting with ., + // even when options.dot is set. + () => { + fixtures = ['a/./b', 'a/../b', 'a/c/b', 'a/.d/b']; + }, + ['a/*/b', ['a/c/b', 'a/.d/b'], {dot: true}], + ['a/*/b', ['a/c/b'], {dot: false}], + + ['a/.*/b', ['a/./b', 'a/../b', 'a/.d/b'], {dot: true}], + ['a/.*/b', ['a/./b', 'a/../b', 'a/.d/b'], {dot: false}], + + // this also tests that changing the options needs + // to change the cache key, even if the pattern is + // the same! + ['**', + ['a/b', 'a/.d', '.a/.d'], + { dot: true }, + [ '.a/.d', 'a/.d', 'a/b'] + ], + + // '~~paren sets cannot contain slashes~~', + // 'paren sets _can_ contain slashes', + ['*(a/b)', ['a/b'], {}, ['a/b']], + + // brace sets trump all else. + // + // invalid glob pattern. fails on bash4 and bsdglob. + // however, in this implementation, it's easier just + // to do the intuitive thing, and let brace-expansion + // actually come before parsing any extglob patterns, + // like the documentation seems to say. + // + // XXX: if anyone complains about this, either fix it + // or tell them to grow up and stop complaining. + // + // bash/bsdglob says this: + // ["*(a|{b),c)}", ["*(a|{b),c)}"], {}, ["a", "ab", "ac", "ad"]], + // but we do this instead: + ['*(a|{b),c)}', ['a', 'ab', 'ac'], {expand: true}, ['a', 'ab', 'ac', 'ad']], + + // test partial parsing in the presence of comment/negation chars + ['[!a*', ['[!ab'], {}, ['[!ab', '[ab']], + + // like: {a,b|c\\,d\\\|e} except it's unclosed, so it has to be escaped. + // [ + // '+(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g', + // ['+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g'], + // {}, + // ['+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g', 'a', 'b\\c'] + // ], + + // crazy nested {,,} and *(||) tests. + () => { + fixtures = [ + 'a', 'b', 'c', 'd', 'ab', 'ac', 'ad', 'bc', 'cb', 'bc,d', + 'c,db', 'c,d', 'd)', '(b|c', '*(b|c', 'b|c', 'b|cc', 'cb|c', + 'x(a|b|c)', 'x(a|c)', '(a|b|c)', '(a|c)' + ]; + }, + ['*(a|{b,c})', ['a', 'b', 'c', 'ab', 'ac'], {expand: true}], + // ['{a,*(b|c,d)}', ['a', '(b|c', '*(b|c', 'd)'], {expand: true}], //<= minimatch (wrong) + ['{a,*(b|c,d)}', ['a', 'b', 'bc,d', 'c,db', 'c,d'], {expand: true}], + + // a + // *(b|c) + // *(b|d) + ['{a,*(b|{c,d})}', ['a', 'b', 'bc', 'cb', 'c', 'd'], {expand: true}], + ['*(a|{b|c,c})', ['a', 'b', 'c', 'ab', 'ac', 'bc', 'cb']], + ['*(a|{b|c,c})', ['a', 'b', 'c', 'ab', 'ac', 'bc', 'cb'], {expand: true}], + + // test various flag settings. + + ['a?b', ['acb', 'acb/'], {}, ['x/y/acb', 'acb', 'acb/', 'acb/d/e']], + ['a?b', ['x/y/acb', 'acb/', 'acb'], {matchBase: true}, ['x/y/acb', 'acb', 'acb/', 'acb/d/e']], + + // begin channelling Boole and deMorgan... + 'negation tests', + () => { + fixtures = ['d', 'e', '!ab', '!abc', 'a!b']; + }, + + // anything that is NOT a* matches. + ['!a*', ['d', 'e', '!ab', '!abc']], + + // anything that IS !a* matches. + ['!a*', ['!ab', '!abc'], {nonegate: true}], + + // anything that IS a* matches + ['a*', ['a!b']], + + // anything that is NOT !a* matches + ['!\\!a*', ['a!b', 'd', 'e']], + + // negation nestled within a pattern + () => { + fixtures = [ + 'foo.js', + 'foo.bar', + 'foo.js.js', + 'blar.js', + 'foo.', + 'boo.js.boo' + ]; + }, + // last one is tricky! * matches foo, . matches ., and 'js.js' != 'js' + // copy bash 4.3 behavior on this. + + 'https://github.com/isaacs/minimatch/issues/5', + () => { + fixtures = [ + 'a/b/.x/c', 'a/b/.x/c/d', 'a/b/.x/c/d/e', 'a/b/.x', 'a/b/.x/', + 'a/.x/b', '.x', '.x/', '.x/a', '.x/a/b', 'a/.x/b/.x/c', '.x/.x' + ]; + }, + [ + '**/.x/**', + [ + '.x/', '.x/a', '.x/a/b', 'a/.x/b', 'a/b/.x/', 'a/b/.x/c', + 'a/b/.x/c/d', 'a/b/.x/c/d/e' + ] + ], + + 'https://github.com/isaacs/minimatch/issues/59', + ['[z-a]', []], + ['a/[2015-03-10T00:23:08.647Z]/z', []], + ['[a-0][a-\u0100]', []] +]; + +// Attach fixtures as a property of the default-exported array (matches upstream's +// `Object.defineProperty(module.exports, 'fixtures', ...)`). +(patterns as any).fixtures = fixtures; +export { fixtures }; +export { patterns }; +export default patterns; diff --git a/packages/node-utils/test/micromatch/globstars.test.ts b/packages/node-utils/test/micromatch/globstars.test.ts new file mode 100644 index 0000000..7c70c6f --- /dev/null +++ b/packages/node-utils/test/micromatch/globstars.test.ts @@ -0,0 +1,43 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; +const { isMatch } = micromatch; + +describe('globstars - "**"', () => { + +}); diff --git a/packages/node-utils/test/micromatch/issue-related.test.ts b/packages/node-utils/test/micromatch/issue-related.test.ts new file mode 100644 index 0000000..86c3964 --- /dev/null +++ b/packages/node-utils/test/micromatch/issue-related.test.ts @@ -0,0 +1,106 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; + +describe('issue-related tests', () => { + test('micromatch issue #140', () => { + let a = ['a/b/some/c.md', 'a/b/c.md', 'a/b-b/c.md', 'a/bb/c.md', 'a/bbc/c.md']; + expect_deepEqual(mm(a, '**/b/**/c.md'), ['a/b/some/c.md', 'a/b/c.md']); + + let b = ['packages/foo-foo/package.json', 'packages/foo/package.json']; + expect_deepEqual(mm(b, '**/foo/**/package.json'), ['packages/foo/package.json']); + }); + + test('micromatch issue#15', () => { + expect_truthy(mm.isMatch('a/b-c/d/e/z.js', 'a/b-*/**/z.js')); + expect_truthy(mm.isMatch('z.js', 'z*')); + expect_truthy(mm.isMatch('z.js', '**/z*')); + expect_truthy(mm.isMatch('z.js', '**/z*.js')); + expect_truthy(mm.isMatch('z.js', '**/*.js')); + expect_truthy(mm.isMatch('foo', '**/foo')); + }); + + test('micromatch issue#23', () => { + expect_truthy(!mm.isMatch('zzjs', 'z*.js')); + expect_truthy(!mm.isMatch('zzjs', '*z.js')); + }); + + test('micromatch issue#24', () => { + expect_truthy(!mm.isMatch('a/b/c/d/', 'a/b/**/f')); + expect_truthy(mm.isMatch('a', 'a/**')); + expect_truthy(mm.isMatch('a', '**')); + expect_truthy(mm.isMatch('a/', '**')); + expect_truthy(mm.isMatch('a/b/c/d', '**')); + expect_truthy(mm.isMatch('a/b/c/d/', '**')); + expect_truthy(mm.isMatch('a/b/c/d/', '**/**')); + expect_truthy(mm.isMatch('a/b/c/d/', '**/b/**')); + expect_truthy(mm.isMatch('a/b/c/d/', 'a/b/**')); + expect_truthy(mm.isMatch('a/b/c/d/', 'a/b/**/')); + expect_truthy(mm.isMatch('a/b/c/d/e.f', 'a/b/**/**/*.*')); + expect_truthy(mm.isMatch('a/b/c/d/e.f', 'a/b/**/*.*')); + expect_truthy(mm.isMatch('a/b/c/d/g/e.f', 'a/b/**/d/**/*.*')); + expect_truthy(mm.isMatch('a/b/c/d/g/g/e.f', 'a/b/**/d/**/*.*')); + }); + + test('micromatch issue#58 - only match nested dirs when `**` is the only thing in a segment', () => { + expect_truthy(!mm.isMatch('a/b/c', 'a/b**')); + expect_truthy(!mm.isMatch('a/c/b', 'a/**b')); + }); + + test('micromatch issue#63 (dots)', () => { + expect_truthy(!mm.isMatch('/aaa/.git/foo', '/aaa/**/*')); + expect_truthy(!mm.isMatch('/aaa/bbb/.git', '/aaa/bbb/*')); + expect_truthy(!mm.isMatch('/aaa/bbb/.git', '/aaa/bbb/**')); + expect_truthy(!mm.isMatch('/aaa/bbb/ccc/.git', '/aaa/bbb/**')); + expect_truthy(!mm.isMatch('aaa/bbb/.git', 'aaa/bbb/**')); + expect_truthy(mm.isMatch('/aaa/.git/foo', '/aaa/**/*', { dot: true })); + expect_truthy(mm.isMatch('/aaa/bbb/', '/aaa/bbb/**')); + expect_truthy(mm.isMatch('/aaa/bbb/.git', '/aaa/bbb/*', { dot: true })); + expect_truthy(mm.isMatch('/aaa/bbb/.git', '/aaa/bbb/**', { dot: true })); + expect_truthy(mm.isMatch('/aaa/bbb/ccc/.git', '/aaa/bbb/**', { dot: true })); + expect_truthy(mm.isMatch('/aaa/bbb/foo', '/aaa/bbb/**')); + expect_truthy(mm.isMatch('aaa/bbb/.git', 'aaa/bbb/**', { dot: true })); + }); + + test('micromatch issue#79', () => { + expect_truthy(mm.isMatch('a/foo.js', '**/foo.js')); + expect_truthy(mm.isMatch('foo.js', '**/foo.js')); + expect_truthy(mm.isMatch('a/foo.js', '**/foo.js', { dot: true })); + expect_truthy(mm.isMatch('foo.js', '**/foo.js', { dot: true })); + }); +}); diff --git a/packages/node-utils/test/micromatch/malicious.test.ts b/packages/node-utils/test/micromatch/malicious.test.ts new file mode 100644 index 0000000..38b752f --- /dev/null +++ b/packages/node-utils/test/micromatch/malicious.test.ts @@ -0,0 +1,63 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; +const generate = n => '\\'.repeat(n); + +/** + * These tests are based on minimatch unit tests + */ + +describe('handling of potential regex exploits', () => { + + test('should support long escape sequences', () => { + expect_truthy(mm.isMatch('A', `!(${generate(65500)}A)`), 'within the limits, and valid match'); + expect_truthy(!mm.isMatch('A', `[!(${generate(65500)}A`), 'within the limits, but invalid regex'); + }); + + test('should throw an error when the pattern is too long', () => { + expect_throws(() => { + expect_truthy(!mm.isMatch('A', `!(${generate(65536)}A)`)); + }, /Input length: 65540, exceeds maximum allowed length: 65536/); + }); + + test('should allow max bytes to be customized', () => { + expect_throws(() => { + expect_truthy(!mm.isMatch('A', `!(${generate(500)}A)`, { maxLength: 499 })); + }, /Input length: 504, exceeds maximum allowed length: 499/); + }); +}); diff --git a/packages/node-utils/test/micromatch/micromatch.test.ts b/packages/node-utils/test/micromatch/micromatch.test.ts new file mode 100644 index 0000000..cbf4b1f --- /dev/null +++ b/packages/node-utils/test/micromatch/micromatch.test.ts @@ -0,0 +1,229 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; +const sep = path.sep; + +describe('micromatch', () => { + afterEach(() => ((path as any).sep = sep)); + after(() => ((path as any).sep = sep)); + + describe('empty list', () => { + test('should return an empty array', () => { + expect_deepEqual(mm([], '*'), []); + }); + }); + + describe('posix paths', () => { + test('should return an array of matches', () => { + expect_deepEqual(mm(['a', 'a', 'a'], ['*', 'a*']), ['a']); + }); + + test('should return an array of matches for a literal string', () => { + expect_deepEqual(mm(['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c'], '(a/b)'), ['a/b']); + expect_deepEqual(mm(['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c'], 'a/b'), ['a/b']); + }); + + test('should return an array of matches for an array of literal strings', () => { + expect_deepEqual(mm(['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c'], ['(a/b)', 'a/c']), ['a/b', 'a/c']); + expect_deepEqual(mm(['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c'], ['a/b', 'b/b']), ['a/b', 'b/b']); + }); + + test('should support regex logical or', () => { + expect_deepEqual(mm(['a/a', 'a/b', 'a/c'], ['a/(a|c)']), ['a/a', 'a/c']); + expect_deepEqual(mm(['a/a', 'a/b', 'a/c'], ['a/(a|b|c)', 'a/b']), ['a/a', 'a/b', 'a/c']); + }); + + test('should support regex ranges', () => { + expect_deepEqual(mm(['a/a', 'a/b', 'a/c'], 'a/[b-c]'), ['a/b', 'a/c']); + expect_deepEqual(mm(['a/a', 'a/b', 'a/c', 'a/x/y', 'a/x'], 'a/[a-z]'), ['a/a', 'a/b', 'a/c', 'a/x']); + }); + + test('should support single globs (*)', () => { + let fixtures = ['a', 'b', 'a/a', 'a/b', 'a/c', 'a/x', 'a/a/a', 'a/a/b', 'a/a/a/a', 'a/a/a/a/a', 'x/y', 'z/z']; + expect_deepEqual(mm(fixtures, ['*']), ['a', 'b']); + expect_deepEqual(mm(fixtures, ['*/*']), ['a/a', 'a/b', 'a/c', 'a/x', 'x/y', 'z/z']); + expect_deepEqual(mm(fixtures, ['*/*/*']), ['a/a/a', 'a/a/b']); + expect_deepEqual(mm(fixtures, ['*/*/*/*']), ['a/a/a/a']); + expect_deepEqual(mm(fixtures, ['*/*/*/*/*']), ['a/a/a/a/a']); + expect_deepEqual(mm(fixtures, ['a/*']), ['a/a', 'a/b', 'a/c', 'a/x']); + expect_deepEqual(mm(fixtures, ['a/*/*']), ['a/a/a', 'a/a/b']); + expect_deepEqual(mm(fixtures, ['a/*/*/*']), ['a/a/a/a']); + expect_deepEqual(mm(fixtures, ['a/*/*/*/*']), ['a/a/a/a/a']); + expect_deepEqual(mm(fixtures, ['a/*/a']), ['a/a/a']); + expect_deepEqual(mm(fixtures, ['a/*/b']), ['a/a/b']); + }); + + test('should support globstars (**)', () => { + let fixtures = ['a', 'a/', 'a/a', 'a/b', 'a/c', 'a/x', 'a/x/y', 'a/x/y/z']; + expect_deepEqual(mm(fixtures, ['*{,/}']), ['a', 'a/']); + expect_deepEqual(mm(fixtures, ['*/']), ['a/']); + expect_deepEqual(mm(fixtures, ['*/*']), ['a/a', 'a/b', 'a/c', 'a/x']); + expect_deepEqual(mm(fixtures, ['**']), fixtures); + expect_deepEqual(mm(fixtures, ['**/a']), ['a', 'a/a']); + expect_deepEqual(mm(fixtures, ['a/*']), ['a/a', 'a/b', 'a/c', 'a/x']); + expect_deepEqual(mm(fixtures, ['a/**']), ['a', 'a/', 'a/a', 'a/b', 'a/c', 'a/x', 'a/x/y', 'a/x/y/z']); + expect_deepEqual(mm(fixtures, ['a/**/*']), ['a/a', 'a/b', 'a/c', 'a/x', 'a/x/y', 'a/x/y/z']); + expect_deepEqual(mm(fixtures, ['a/**/**/*']), ['a/a', 'a/b', 'a/c', 'a/x', 'a/x/y', 'a/x/y/z']); + expect_deepEqual(mm(['a/b/foo/bar/baz.qux'], 'a/b/**/bar/**/*.*'), ['a/b/foo/bar/baz.qux']); + expect_deepEqual(mm(['a/b/bar/baz.qux'], 'a/b/**/bar/**/*.*'), ['a/b/bar/baz.qux']); + }); + + test('should work with file extensions', () => { + let fixtures = ['a.txt', 'a/b.txt', 'a/x/y.txt', 'a/x/y/z']; + expect_deepEqual(mm(fixtures, ['a/**/*.txt']), ['a/b.txt', 'a/x/y.txt']); + expect_deepEqual(mm(fixtures, ['a/*.txt']), ['a/b.txt']); + expect_deepEqual(mm(fixtures, ['a*.txt']), ['a.txt']); + expect_deepEqual(mm(fixtures, ['*.txt']), ['a.txt']); + }); + + test('should match literal brackets', () => { + expect_deepEqual(mm(['a [b]'], 'a \\[b\\]'), ['a [b]']); + expect_deepEqual(mm(['a [b] c'], 'a [b] c'), ['a [b] c']); + expect_deepEqual(mm(['a [b]'], 'a \\[b\\]*'), ['a [b]']); + expect_deepEqual(mm(['a [bc]'], 'a \\[bc\\]*'), ['a [bc]']); + expect_deepEqual(mm(['a [b]', 'a [b].js'], 'a \\[b\\].*'), ['a [b].js']); + }); + }); + + describe('windows paths', () => { + beforeEach(() => { + (path as any).sep = '\\'; + }); + afterEach(() => { + (path as any).sep = sep; + }); + + test('should return an array of matches for a literal string', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']; + expect_deepEqual(mm(fixtures, '(a/b)'), ['a/b']); + expect_deepEqual(mm(fixtures, 'a/b'), ['a/b']); + expect_deepEqual(mm(fixtures, '(a/b)', { windows: false }), []); + expect_deepEqual(mm(fixtures, 'a/b', { windows: false }), []); + }); + + test('should return an array of matches for an array of literal strings', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']; + expect_deepEqual(mm(fixtures, ['(a/b)', 'a/c']), ['a/b', 'a/c']); + expect_deepEqual(mm(fixtures, ['a/b', 'b/b']), ['a/b', 'b/b']); + expect_deepEqual(mm(fixtures, ['(a/b)', 'a/c'], { windows: false }), []); + expect_deepEqual(mm(fixtures, ['a/b', 'b/b'], { windows: false }), []); + }); + + test('should support regex logical or', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c']; + expect_deepEqual(mm(fixtures, ['a/(a|c)']), ['a/a', 'a/c']); + expect_deepEqual(mm(fixtures, ['a/(a|b|c)', 'a/b']), ['a/a', 'a/b', 'a/c']); + expect_deepEqual(mm(fixtures, ['a/(a|c)'], { windows: false }), []); + expect_deepEqual(mm(fixtures, ['a/(a|b|c)', 'a/b'], { windows: false }), []); + }); + + test('should support regex ranges', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'a\\x\\y', 'a\\x']; + expect_deepEqual(mm(fixtures, 'a/[b-c]'), ['a/b', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/[a-z]'), ['a/a', 'a/b', 'a/c', 'a/x']); + expect_deepEqual(mm(fixtures, 'a/[b-c]', { windows: false }), []); + expect_deepEqual(mm(fixtures, 'a\\\\[b-c]', { windows: false }), ['a\\b', 'a\\c']); + expect_deepEqual(mm(fixtures, 'a/[a-z]', { windows: false }), []); + }); + + test('should support single globs (*)', () => { + let fixtures = [ + 'a', + 'b', + 'a\\a', + 'a\\b', + 'a\\c', + 'a\\x', + 'a\\a\\a', + 'a\\a\\b', + 'a\\a\\a\\a', + 'a\\a\\a\\a\\a', + 'x\\y', + 'z\\z' + ]; + + expect_deepEqual(mm(fixtures, ['*']), ['a', 'b']); + expect_deepEqual(mm(fixtures, ['*/*']), ['a/a', 'a/b', 'a/c', 'a/x', 'x/y', 'z/z']); + expect_deepEqual(mm(fixtures, ['*/*/*']), ['a/a/a', 'a/a/b']); + expect_deepEqual(mm(fixtures, ['*/*/*/*']), ['a/a/a/a']); + expect_deepEqual(mm(fixtures, ['*/*/*/*/*']), ['a/a/a/a/a']); + expect_deepEqual(mm(fixtures, ['a/*']), ['a/a', 'a/b', 'a/c', 'a/x']); + expect_deepEqual(mm(fixtures, ['a/*/*']), ['a/a/a', 'a/a/b']); + expect_deepEqual(mm(fixtures, ['a/*/*/*']), ['a/a/a/a']); + expect_deepEqual(mm(fixtures, ['a/*/*/*/*']), ['a/a/a/a/a']); + expect_deepEqual(mm(fixtures, ['a/*/a']), ['a/a/a']); + expect_deepEqual(mm(fixtures, ['a/*/b']), ['a/a/b']); + + let opts = { windows: false }; + expect_deepEqual(mm(fixtures, ['*/*'], opts), []); + expect_deepEqual(mm(fixtures, ['*/*/*'], opts), []); + expect_deepEqual(mm(fixtures, ['*/*/*/*'], opts), []); + expect_deepEqual(mm(fixtures, ['*/*/*/*/*'], opts), []); + expect_deepEqual(mm(fixtures, ['a/*'], opts), []); + expect_deepEqual(mm(fixtures, ['a/*/*'], opts), []); + expect_deepEqual(mm(fixtures, ['a/*/*/*'], opts), []); + expect_deepEqual(mm(fixtures, ['a/*/*/*/*'], opts), []); + expect_deepEqual(mm(fixtures, ['a/*/a'], opts), []); + expect_deepEqual(mm(fixtures, ['a/*/b'], opts), []); + }); + + test('should support globstars (**)', () => { + let fixtures = ['a\\a', 'a\\b', 'a\\c', 'a\\x', 'a\\x\\y', 'a\\x\\y\\z']; + let expected = ['a/a', 'a/b', 'a/c', 'a/x', 'a/x/y', 'a/x/y/z']; + expect_deepEqual(mm(fixtures, ['a/**']), expected); + expect_deepEqual(mm(fixtures, ['a/**/*']), expected); + expect_deepEqual(mm(fixtures, ['a/**/**/*']), expected); + + expect_deepEqual(mm(fixtures, ['a/**'], { windows: false }), []); + expect_deepEqual(mm(fixtures, ['a/**/*'], { windows: false }), []); + expect_deepEqual(mm(fixtures, ['a/**/**/*'], { windows: false }), []); + }); + + test('should work with file extensions', () => { + let fixtures = ['a.txt', 'a\\b.txt', 'a\\x\\y.txt', 'a\\x\\y\\z']; + expect_deepEqual(mm(fixtures, ['a\\\\**\\\\*.txt']), []); + expect_deepEqual(mm(fixtures, ['a\\\\*\\\\*.txt']), []); + expect_deepEqual(mm(fixtures, ['a\\\\*.txt']), []); + expect_deepEqual(mm(fixtures, ['a/**/*.txt']), ['a/b.txt', 'a/x/y.txt']); + expect_deepEqual(mm(fixtures, ['a/*/*.txt']), ['a/x/y.txt']); + expect_deepEqual(mm(fixtures, ['a/*.txt']), ['a/b.txt']); + expect_deepEqual(mm(fixtures, ['a*.txt']), ['a.txt']); + expect_deepEqual(mm(fixtures, ['a.txt']), ['a.txt']); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/minimatch.test.ts b/packages/node-utils/test/micromatch/minimatch.test.ts new file mode 100644 index 0000000..0fc5606 --- /dev/null +++ b/packages/node-utils/test/micromatch/minimatch.test.ts @@ -0,0 +1,161 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const isWindows = () => process.platform === 'win32' || (path as any).sep === '\\'; +const patterns = (await import("./fixtures/patterns.ts")).default; +const mm = micromatch; +let sep = path.sep; + +/** + * Minimatch comparison tests + */ + +describe('basic tests', () => { + afterEach(() => ((path as any).sep = sep)); + after(() => ((path as any).sep = sep)); + + describe('minimatch parity', () => { + patterns.forEach(function(unit, i) { + test(i + ': ' + unit[0], () => { + if (typeof unit === 'string') { + console.log(); + console.log(' ', unit); + return; + } + + // update fixtures list + if (typeof unit === 'function') { + unit(); + return; + } + + let pattern = unit[0]; + let expected = (unit[1] || []).sort(compare); + let options = Object.assign({}, unit[2]); + let fixtures = unit[3] || patterns.fixtures; + mm(fixtures, pattern, expected, options); + }); + }); + }); + + describe('backslashes', () => { + test('should match literal backslashes', () => { + if (isWindows()) { + mm(['\\'], '\\', ['/']); + } else { + mm(['\\'], '\\', ['\\']); + } + }); + }); + + /** + * Issues that minimatch fails on but micromatch passes + */ + + describe('minimatch issues (as of 12/7/2016)', () => { + test('https://github.com/isaacs/minimatch/issues/29', () => { + expect_truthy(mm.isMatch('foo/bar.txt', 'foo/**/*.txt')); + expect_truthy(mm.makeRe('foo/**/*.txt').test('foo/bar.txt')); + expect_truthy(!mm.isMatch('n/!(axios)/**', 'n/axios/a.js')); + expect_truthy(!mm.makeRe('n/!(axios)/**').test('n/axios/a.js')); + }); + + test('https://github.com/isaacs/minimatch/issues/30', () => { + let format = str => str.replace(/^\.\//, ''); + + expect_truthy(mm.isMatch('foo/bar.js', '**/foo/**')); + expect_truthy(mm.isMatch('./foo/bar.js', './**/foo/**', { format })); + expect_truthy(mm.isMatch('./foo/bar.js', '**/foo/**', { format })); + expect_truthy(mm.isMatch('./foo/bar.txt', 'foo/**/*.txt', { format })); + expect_truthy(mm.makeRe('./foo/**/*.txt').test('foo/bar.txt')); + expect_truthy(!mm.isMatch('./foo/!(bar)/**', 'foo/bar/a.js')); + expect_truthy(!mm.makeRe('./foo/!(bar)/**').test('foo/bar/a.js')); + }); + + test('https://github.com/isaacs/minimatch/issues/50', () => { + expect_truthy(mm.isMatch('foo/bar-[ABC].txt', 'foo/**/*-\\[ABC\\].txt')); + expect_truthy(!mm.isMatch('foo/bar-[ABC].txt', 'foo/**/*-\\[abc\\].txt')); + expect_truthy(mm.isMatch('foo/bar-[ABC].txt', 'foo/**/*-\\[abc\\].txt', {nocase: true})); + }); + + test('https://github.com/isaacs/minimatch/issues/67 (should work consistently with `makeRe` and matcher functions)', () => { + var re = mm.makeRe('node_modules/foobar/**/*.bar'); + expect_truthy(re.test('node_modules/foobar/foo.bar')); + expect_truthy(mm.isMatch('node_modules/foobar/foo.bar', 'node_modules/foobar/**/*.bar')); + mm(['node_modules/foobar/foo.bar'], 'node_modules/foobar/**/*.bar', ['node_modules/foobar/foo.bar']); + }); + + test('https://github.com/isaacs/minimatch/issues/75', () => { + expect_truthy(mm.isMatch('foo/baz.qux.js', 'foo/@(baz.qux).js')); + expect_truthy(mm.isMatch('foo/baz.qux.js', 'foo/+(baz.qux).js')); + expect_truthy(mm.isMatch('foo/baz.qux.js', 'foo/*(baz.qux).js')); + expect_truthy(!mm.isMatch('foo/baz.qux.js', 'foo/!(baz.qux).js')); + expect_truthy(!mm.isMatch('foo/bar/baz.qux.js', 'foo/*/!(baz.qux).js')); + expect_truthy(!mm.isMatch('foo/bar/bazqux.js', '**/!(bazqux).js')); + expect_truthy(!mm.isMatch('foo/bar/bazqux.js', '**/bar/!(bazqux).js')); + expect_truthy(!mm.isMatch('foo/bar/bazqux.js', 'foo/**/!(bazqux).js')); + expect_truthy(!mm.isMatch('foo/bar/bazqux.js', 'foo/**/!(bazqux)*.js')); + expect_truthy(!mm.isMatch('foo/bar/baz.qux.js', 'foo/**/!(baz.qux)*.js')); + expect_truthy(!mm.isMatch('foo/bar/baz.qux.js', 'foo/**/!(baz.qux).js')); + expect_truthy(!mm.isMatch('foobar.js', '!(foo)*.js')); + expect_truthy(!mm.isMatch('foo.js', '!(foo).js')); + expect_truthy(!mm.isMatch('foo.js', '!(foo)*.js')); + }); + + test('https://github.com/isaacs/minimatch/issues/78', () => { + (path as any).sep = '\\'; + expect_truthy(mm.isMatch('a\\b\\c.txt', 'a/**/*.txt')); + expect_truthy(mm.isMatch('a/b/c.txt', 'a/**/*.txt')); + (path as any).sep = sep; + }); + + test('https://github.com/isaacs/minimatch/issues/82', () => { + let format = str => str.replace(/^\.\//, ''); + expect_truthy(mm.isMatch('./src/test/a.js', '**/test/**', { format })); + expect_truthy(mm.isMatch('src/test/a.js', '**/test/**')); + }); + + test('https://github.com/isaacs/minimatch/issues/83', () => { + expect_truthy(!mm.makeRe('foo/!(bar)/**').test('foo/bar/a.js')); + expect_truthy(!mm.isMatch('foo/!(bar)/**', 'foo/bar/a.js')); + }); + }); +}); + +function compare(a, b) { + return a === b ? 0 : a > b ? 1 : -1; +} diff --git a/packages/node-utils/test/micromatch/negation.test.ts b/packages/node-utils/test/micromatch/negation.test.ts new file mode 100644 index 0000000..e0db36b --- /dev/null +++ b/packages/node-utils/test/micromatch/negation.test.ts @@ -0,0 +1,176 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const sep = path.sep; +const isWindows = () => process.platform === 'win32' || (path as any).sep === '\\'; +const mm = micromatch; + +describe('negation', () => { + describe('posix paths', () => { + test('should support negating with single *', () => { + expect_deepEqual(mm(['a', 'b', 'c.md'], '!*.md'), ['a', 'b']); + expect_deepEqual(mm(['a/a/a', 'a/b/a', 'a/c/a'], '!a/*/a'), []); + expect_deepEqual(mm(['a/a/a/a', 'b/a/b/a', 'c/a/c/a'], '!a/*/*/a'), ['b/a/b/a', 'c/a/c/a']); + expect_deepEqual(mm(['a/a', 'a/b', 'a/c'], '!a/a*'), ['a/b', 'a/c']); + expect_deepEqual(mm(['a.a', 'a.b', 'a.c'], '!a.a*'), ['a.b', 'a.c']); + expect_deepEqual(mm(['a/a', 'a/b', 'a/c'], '!a/*'), []); + }); + + test('should support negation patterns', () => { + let fixtures1 = ['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + + expect_deepEqual(mm(fixtures1, ['!a/b']), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures1, ['*/*', '!a/b', '!*/c']), ['a/a', 'b/a', 'b/b']); + expect_deepEqual(mm(fixtures1, ['*/*', '!a/b', '!*/c']), ['a/a', 'b/a', 'b/b']); + expect_deepEqual(mm(fixtures1, ['*/*', '!a/b', '!a/c']), ['a/a', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures1, ['!a/(b)']), ['a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(['bar', 'baz', 'foo'], ['!bar', '*']), ['bar', 'baz', 'foo']); + expect_deepEqual(mm(['bar', 'baz', 'foo'], ['*', '!bar']), ['baz', 'foo']); + + let fixtures2 = ['foo', 'bar', 'baz', 'main', 'other', 'foo/a/b/c', 'bar/a/b/d', 'baz/a/b/e', 'a/a/a', 'a/a/b', 'a/a/c', 'a/a/file']; + + expect_deepEqual(mm(fixtures2, ['a/**', '!a/a/file', 'main']), ['a/a/a', 'a/a/b', 'a/a/c', 'main']); + expect_deepEqual(mm('foo', ['a/**', '!a/a/file', 'main']), []); + expect_deepEqual(mm(['foo'], ['a/**', '!a/a/file', 'main']), []); + }); + + test('should support negating with literal non-globs', () => { + let fixtures = ['a', 'b', 'a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'b/c']; + + // expect_deepEqual(mm(fixtures, ['!a/a', '!a']), []); + expect_deepEqual(mm(['bar', 'baz', 'foo'], '!foo'), ['bar', 'baz']); + expect_deepEqual(mm(['bar', 'baz', 'foo'], ['!bar', 'bar']), ['bar']); + expect_deepEqual(mm(['bar', 'baz', 'foo'], ['!foo', 'bar']), ['bar']); + expect_deepEqual(mm(['bar', 'baz', 'foo'], ['!foo']), ['bar', 'baz']); + expect_deepEqual(mm(['bar', 'baz', 'foo'], ['bar', '!foo', '!bar']), []); + expect_deepEqual(mm(['foo!.md', 'bar.md'], 'foo!.md'), ['foo!.md']); + expect_deepEqual(mm(['foo.md'], '!.md'), ['foo.md']); + }); + + test('should negate files with extensions:', () => { + expect_deepEqual(mm(['a.js', 'b.md', 'c.txt'], '!**/*.md'), ['a.js', 'c.txt']); + expect_deepEqual(mm(['a.js', 'b.md', 'c.txt'], '!*.md'), ['a.js', 'c.txt']); + expect_deepEqual(mm(['abc.md', 'abc.txt'], '!*.md'), ['abc.txt']); + expect_deepEqual(mm(['foo.md'], '!*.md'), []); + }); + + test('should only treat leading exclamation as special', () => { + expect_deepEqual(mm(['foo!.md', 'bar.md'], '*.md'), ['foo!.md', 'bar.md']); + expect_deepEqual(mm(['foo!.md', 'bar.md'], '*!.md'), ['foo!.md']); + expect_deepEqual(mm(['foobar.md'], '*b*.md'), ['foobar.md']); + expect_deepEqual(mm(['foo!bar.md', 'foo!.md', '!foo!.md'], '*!*.md'), ['foo!bar.md', 'foo!.md', '!foo!.md']); + expect_deepEqual(mm(['foo!bar.md', 'foo!.md', '!foo!.md'], '\\!*!*.md'), ['!foo!.md']); + expect_deepEqual(mm(['foo!.md', 'ba!r.js'], '**/*!*.*'), ['foo!.md', 'ba!r.js']); + }); + + test('should support negated globstars ("**")', () => { + expect_deepEqual(mm(['a.js', 'b.txt', 'c.md'], '!*.md'), ['a.js', 'b.txt']); + expect_deepEqual(mm(['a/a/a.js', 'a/b/a.js', 'a/c/a.js', 'a/a/b.js'], '!**/a.js'), ['a/a/b.js']); + expect_deepEqual(mm(['a/a/a/a.js', 'b/a/b/a.js', 'c/a/c/a.js'], '!a/**/a.js'), ['b/a/b/a.js', 'c/a/c/a.js']); + expect_deepEqual(mm(['a/a.txt', 'a/b.txt', 'a/c.txt'], '!a/b.txt'), ['a/a.txt', 'a/c.txt']); + expect_deepEqual(mm(['a/b.js', 'a.js', 'a/b.md', 'a.md'], '!**/*.md'), ['a/b.js', 'a.js']); + expect_deepEqual(mm(['a/b.js', 'a.js', 'a/b.md', 'a.md'], '**/*.md'), ['a/b.md', 'a.md']); + + expect_deepEqual(mm(['a/b.js'], '!**/*.md'), ['a/b.js']); + expect_deepEqual(mm(['a.js'], '!**/*.md'), ['a.js']); + expect_deepEqual(mm(['a/b.md'], '!**/*.md'), []); + expect_deepEqual(mm(['a.md'], '!**/*.md'), []); + + expect_deepEqual(mm(['a/b.js'], '!*.md'), ['a/b.js']); + expect_deepEqual(mm(['a.js'], '!*.md'), ['a.js']); + expect_deepEqual(mm(['a/b.md'], '!*.md'), ['a/b.md']); + expect_deepEqual(mm(['a.md'], '!*.md'), []); + + expect_deepEqual(mm(['a.js'], '!**/*.md'), ['a.js']); + expect_deepEqual(mm(['b.md'], '!**/*.md'), []); + expect_deepEqual(mm(['c.txt'], '!**/*.md'), ['c.txt']); + }); + + test('should negate dotfiles:', () => { + expect_deepEqual(mm(['.dotfile.md'], '!*.md', { dot: true }), []); + expect_deepEqual(mm(['.dotfile'], '!*.md'), ['.dotfile']); + expect_deepEqual(mm(['.dotfile.txt'], '!*.md'), ['.dotfile.txt']); + expect_deepEqual(mm(['.dotfile.txt', 'a/b/.dotfile'], '!*.md'), ['.dotfile.txt', 'a/b/.dotfile']); + expect_deepEqual(mm(['.gitignore', 'a', 'b'], '!.gitignore'), ['a', 'b']); + }); + + test('should negate files in the immediate directory:', () => { + expect_deepEqual(mm(['a/b.js', 'a.js', 'a/b.md', 'a.md'], '!*.md'), ['a/b.js', 'a.js', 'a/b.md']); + }); + + test('should not give special meaning to non-leading exclamations', () => { + expect_deepEqual(mm(['a', 'aa', 'a/b', 'a!b', 'a!!b', 'a/!!/b'], 'a!!b'), ['a!!b']); + }); + + test('should negate files in any directory:', () => { + expect_deepEqual(mm(['a/a.txt', 'a/b.txt', 'a/c.txt'], '!a/b.txt'), ['a/a.txt', 'a/c.txt']); + }); + }); + + describe('windows paths', () => { + beforeEach(() => { + (path as any).sep = '\\'; + }); + afterEach(() => { + (path as any).sep = sep; + }); + + test('should support negation patterns', () => { + let fixtures = ['a', 'a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']; + expect_deepEqual(mm(fixtures, ['!a/b']), ['a', 'a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['*/*', '!a/b', '!*/c']), ['a/a', 'b/a', 'b/b']); + expect_deepEqual(mm(fixtures, ['!*/c']), ['a', 'a/a', 'a/b', 'b/a', 'b/b']); + expect_deepEqual(mm(fixtures, ['**', '!a/b', '!*/c']), ['a', 'a/a', 'b/a', 'b/b']); + expect_deepEqual(mm(fixtures, ['**', '!a/b', '!a/c']), ['a', 'a/a', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['!a/(b)']), ['a', 'a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['!(a/b)']), ['a', 'a/a', 'a/c', 'b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['!(a)**']), ['b/a', 'b/b', 'b/c']); + expect_deepEqual(mm(fixtures, ['**', '!(a)**']), ['b/a', 'b/b', 'b/c']); + + expect_deepEqual(mm(fixtures, ['!a/b'], { windows: false }), ['a', 'a\\a', 'a\\b', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + expect_deepEqual(mm(fixtures, ['!a\\\\b'], { windows: false }), ['a', 'a\\a', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + expect_deepEqual(mm(fixtures, ['*/*', '!a/b', '!*/c'], { windows: false }), []); + expect_deepEqual(mm(fixtures, ['!*\\\\c'], { windows: false }), ['a', 'a\\a', 'a\\b', 'b\\a', 'b\\b']); + expect_deepEqual(mm(fixtures, ['**', '!a\\\\b', '!*\\\\c'], { windows: false }), ['a', 'a\\a', 'b\\a', 'b\\b']); + expect_deepEqual(mm(fixtures, ['**', '!a\\\\b', '!a\\\\c'], { windows: false }), ['a', 'a\\a', 'b\\a', 'b\\b', 'b\\c']); + expect_deepEqual(mm(fixtures, ['**', '!a\\\\b'], { windows: false }), ['a', 'a\\a', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + expect_deepEqual(mm(fixtures, ['**', '!a\\\\(b)'], { windows: false }), ['a', 'a\\a', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + + expect_deepEqual(mm(fixtures, ['**', '!(a\\\\b)'], { windows: false }), ['a', 'a\\a', 'a\\c', 'b\\a', 'b\\b', 'b\\c']); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/options.test.ts b/packages/node-utils/test/micromatch/options.test.ts new file mode 100644 index 0000000..8c98b34 --- /dev/null +++ b/packages/node-utils/test/micromatch/options.test.ts @@ -0,0 +1,319 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; +const mi = require('minimatch'); + +if (!process.env.ORIGINAL_PATH_SEP) { + process.env.ORIGINAL_PATH_SEP = path.sep +} + +describe('options', () => { + beforeEach(() => ((path as any).sep = '\\')); + afterEach(() => ((path as any).sep = process.env.ORIGINAL_PATH_SEP)); + after(() => ((path as any).sep = process.env.ORIGINAL_PATH_SEP)); + + describe('options.failglob (from Bash 4.3 tests)', () => { + test('should throw an error when no matches are found:', () => { + expect_throws(() => mm(['foo'], '\\^', { failglob: true }), /No matches found for/); + }); + }); + + describe('options.ignore', () => { + let negations = ['a/a', 'a/b', 'a/c', 'a/d', 'a/e', 'b/a', 'b/b', 'b/c']; + let globs = ['.a', '.a/a', '.a/a/a', '.a/a/a/a', 'a', 'a/.a', 'a/a', 'a/a/.a', 'a/a/a', 'a/a/a/a', 'a/a/a/a/a', 'a/a/b', 'a/b', 'a/b/c', 'a/c', 'a/x', 'b', 'b/b/b', 'b/b/c', 'c/c/c', 'e/f/g', 'h/i/a', 'x/x/x', 'x/y', 'z/z', 'z/z/z']; + + test('should filter out ignored patterns', () => { + let opts = { ignore: ['a/**'], strictSlashes: true }; + let dotOpts = { ...opts, dot: true }; + + expect_deepEqual(mm(globs, '*', opts), ['a', 'b']); + expect_deepEqual(mm(globs, '*', { ...opts, strictSlashes: false }), ['b']); + expect_deepEqual(mm(globs, '*', { ignore: '**/a' }), ['b']); + expect_deepEqual(mm(globs, '*/*', opts), ['x/y', 'z/z']); + expect_deepEqual(mm(globs, '*/*/*', opts), ['b/b/b', 'b/b/c', 'c/c/c', 'e/f/g', 'h/i/a', 'x/x/x', 'z/z/z']); + expect_deepEqual(mm(globs, '*/*/*/*', opts), []); + expect_deepEqual(mm(globs, '*/*/*/*/*', opts), []); + expect_deepEqual(mm(globs, 'a/*', opts), []); + expect_deepEqual(mm(globs, '**/*/x', opts), ['x/x/x']); + expect_deepEqual(mm(globs, '**/*/[b-z]', opts), ['b/b/b', 'b/b/c', 'c/c/c', 'e/f/g', 'x/x/x', 'x/y', 'z/z', 'z/z/z']); + + expect_deepEqual(mm(globs, '*', { ignore: '**/a', dot: true }), ['.a', 'b']); + expect_deepEqual(mm(globs, '*', dotOpts), ['.a', 'a', 'b']); + expect_deepEqual(mm(globs, '*/*', dotOpts), ['.a/a', 'x/y', 'z/z']); + expect_deepEqual(mm(globs, '*/*/*', dotOpts), ['.a/a/a', 'b/b/b', 'b/b/c', 'c/c/c', 'e/f/g', 'h/i/a', 'x/x/x', 'z/z/z']); + expect_deepEqual(mm(globs, '*/*/*/*', dotOpts), ['.a/a/a/a']); + expect_deepEqual(mm(globs, '*/*/*/*/*', dotOpts), []); + expect_deepEqual(mm(globs, 'a/*', dotOpts), []); + expect_deepEqual(mm(globs, '**/*/x', dotOpts), ['x/x/x']); + + // see https://github.com/jonschlinkert/micromatch/issues/79 + expect_deepEqual(mm(['foo.js', 'a/foo.js'], '**/foo.js'), ['foo.js', 'a/foo.js']); + expect_deepEqual(mm(['foo.js', 'a/foo.js'], '**/foo.js', { dot: true }), ['foo.js', 'a/foo.js']); + + expect_deepEqual(mm(negations, '!b/a', opts), ['b/b', 'b/c']); + expect_deepEqual(mm(negations, '!b/(a)', opts), ['b/b', 'b/c']); + expect_deepEqual(mm(negations, '!(b/(a))', opts), ['b/b', 'b/c']); + expect_deepEqual(mm(negations, '!(b/a)', opts), ['b/b', 'b/c']); + + expect_deepEqual(mm(negations, '**'), negations, 'nothing is ignored'); + expect_deepEqual(mm(negations, '**', { ignore: ['*/b', '*/a'] }), ['a/c', 'a/d', 'a/e', 'b/c']); + expect_deepEqual(mm(negations, '**', { ignore: ['**'] }), []); + }); + }); + + describe('options.matchBase', () => { + test('should match the basename of file paths when `options.matchBase` is true', () => { + expect_deepEqual(mm(['a/b/c/d.md'], '*.md'), [], 'should not match multiple levels'); + expect_deepEqual(mm(['a/b/c/foo.md'], '*.md'), [], 'should not match multiple levels'); + expect_deepEqual(mm(['ab', 'acb', 'acb/', 'acb/d/e', 'x/y/acb', 'x/y/acb/d'], 'a?b'), ['acb'], 'should not match multiple levels'); + expect_deepEqual(mm(['a/b/c/d.md'], '*.md', { matchBase: true }), ['a/b/c/d.md']); + expect_deepEqual(mm(['a/b/c/foo.md'], '*.md', { matchBase: true }), ['a/b/c/foo.md']); + expect_deepEqual(mm(['x/y/acb', 'acb/', 'acb/d/e', 'x/y/acb/d'], 'a?b', { matchBase: true }), ['x/y/acb', 'acb/']); + }); + + test('should work with negation patterns', () => { + expect_truthy(mm.isMatch('./x/y.js', '*.js', { matchBase: true })); + expect_truthy(!mm.isMatch('./x/y.js', '!*.js', { matchBase: true })); + expect_truthy(mm.isMatch('./x/y.js', '**/*.js', { matchBase: true })); + expect_truthy(!mm.isMatch('./x/y.js', '!**/*.js', { matchBase: true })); + }); + }); + + describe('options.flags', () => { + test('should be case-sensitive by default', () => { + expect_deepEqual(mm(['a/b/d/e.md'], 'a/b/D/*.md'), [], 'should not match a dirname'); + expect_deepEqual(mm(['a/b/c/e.md'], 'A/b/*/E.md'), [], 'should not match a basename'); + expect_deepEqual(mm(['a/b/c/e.md'], 'A/b/C/*.MD'), [], 'should not match a file extension'); + }); + + test('should not be case-sensitive when `i` is set on `options.flags`', () => { + expect_deepEqual(mm(['a/b/d/e.md'], 'a/b/D/*.md', { flags: 'i' }), ['a/b/d/e.md']); + expect_deepEqual(mm(['a/b/c/e.md'], 'A/b/*/E.md', { flags: 'i' }), ['a/b/c/e.md']); + expect_deepEqual(mm(['a/b/c/e.md'], 'A/b/C/*.MD', { flags: 'i' }), ['a/b/c/e.md']); + }); + }); + + describe('options.nobrace', () => { + test('should not expand braces when disabled', () => { + expect_deepEqual(mm(['a', 'b', 'c'], '{a,b,c,d}'), ['a', 'b', 'c']); + expect_deepEqual(mm(['a', 'b', 'c'], '{a,b,c,d}', { nobrace: true }), []); + expect_deepEqual(mm(['1', '2', '3'], '{1..2}', { nobrace: true }), []); + }); + }); + + describe('options.nocase', () => { + test('should not be case-sensitive when `options.nocase` is true', () => { + expect_deepEqual(mm(['a/b/c/e.md'], 'A/b/*/E.md', { nocase: true }), ['a/b/c/e.md']); + expect_deepEqual(mm(['a/b/c/e.md'], 'A/b/C/*.MD', { nocase: true }), ['a/b/c/e.md']); + expect_deepEqual(mm(['a/b/c/e.md'], 'A/b/C/*.md', { nocase: true }), ['a/b/c/e.md']); + expect_deepEqual(mm(['a/b/d/e.md'], 'a/b/D/*.md', { nocase: true }), ['a/b/d/e.md']); + }); + + test('should not double-set `i` when both `nocase` and the `i` flag are set', () => { + let opts = { nocase: true, flags: 'i' }; + expect_deepEqual(mm(['a/b/d/e.md'], 'a/b/D/*.md', opts), ['a/b/d/e.md']); + expect_deepEqual(mm(['a/b/c/e.md'], 'A/b/*/E.md', opts), ['a/b/c/e.md']); + expect_deepEqual(mm(['a/b/c/e.md'], 'A/b/C/*.MD', opts), ['a/b/c/e.md']); + }); + }); + + describe('options.noextglob', () => { + test('should match literal parens when noextglob is true (issue #116)', () => { + expect_truthy(mm.isMatch('a/(dir)', 'a/(dir)', { noextglob: true })); + }); + + test('should not match extglobs when noextglob is true', () => { + expect_truthy(!mm.isMatch('ax', '?(a*|b)', { noextglob: true })); + expect_deepEqual(mm(['a.j.js', 'a.md.js'], '*.*(j).js', { noextglob: true }), ['a.j.js']); + expect_deepEqual(mm(['a/z', 'a/b', 'a/!(z)'], 'a/!(z)', { noextglob: true }), ['a/!(z)']); + expect_deepEqual(mm(['a/z', 'a/b'], 'a/!(z)', { noextglob: true }), []); + expect_deepEqual(mm(['c/a/v'], 'c/!(z)/v', { noextglob: true }), []); + expect_deepEqual(mm(['c/z/v', 'c/a/v'], 'c/!(z)/v', { noextglob: true }), []); + expect_deepEqual(mm(['c/z/v', 'c/a/v'], 'c/@(z)/v', { noextglob: true }), []); + expect_deepEqual(mm(['c/z/v', 'c/a/v'], 'c/+(z)/v', { noextglob: true }), []); + expect_deepEqual(mm(['c/z/v', 'c/a/v'], 'c/*(z)/v', { noextglob: true }), ['c/z/v']); + expect_deepEqual(mm(['c/z/v', 'z', 'zf', 'fz'], '?(z)', { noextglob: true }), ['fz']); + expect_deepEqual(mm(['c/z/v', 'z', 'zf', 'fz'], '+(z)', { noextglob: true }), []); + expect_deepEqual(mm(['c/z/v', 'z', 'zf', 'fz'], '*(z)', { noextglob: true }), ['z', 'fz']); + expect_deepEqual(mm(['cz', 'abz', 'az'], 'a@(z)', { noextglob: true }), []); + expect_deepEqual(mm(['cz', 'abz', 'az'], 'a*@(z)', { noextglob: true }), []); + expect_deepEqual(mm(['cz', 'abz', 'az'], 'a!(z)', { noextglob: true }), []); + expect_deepEqual(mm(['cz', 'abz', 'az', 'azz'], 'a?(z)', { noextglob: true }), ['abz', 'azz']); + expect_deepEqual(mm(['cz', 'abz', 'az', 'azz', 'a+z'], 'a+(z)', { noextglob: true }), ['a+z']); + expect_deepEqual(mm(['cz', 'abz', 'az'], 'a*(z)', { noextglob: true }), ['abz', 'az']); + expect_deepEqual(mm(['cz', 'abz', 'az'], 'a**(z)', { noextglob: true }), ['abz', 'az']); + expect_deepEqual(mm(['cz', 'abz', 'az'], 'a*!(z)', { noextglob: true }), []); + }); + }); + + describe('options.nodupes', () => { + beforeEach(() => { + (path as any).sep = '\\'; + }); + afterEach(() => { + (path as any).sep = process.env.ORIGINAL_PATH_SEP; + }); + + test('should remove duplicate elements from the result array:', () => { + let fixtures = ['.editorconfig', '.git', '.gitignore', '.nyc_output', '.travis.yml', '.verb.md', 'CHANGELOG.md', 'CONTRIBUTING.md', 'LICENSE', 'coverage', 'example.js', 'example.md', 'example.css', 'index.js', 'node_modules', 'package.json', 'test.js', 'utils.js']; + expect_deepEqual(mm(['abc', '/a/b/c', '\\a\\b\\c'], '/a/b/c', { windows: true }), ['/a/b/c']); + expect_deepEqual(mm(['abc', '/a/b/c', '\\a\\b\\c'], '\\a\\b\\c', { windows: true }), ['/a/b/c']); + expect_deepEqual(mm(['abc', '/a/b/c', '\\a\\b\\c'], '/a/b/c', { windows: true, nodupes: true }), ['/a/b/c']); + expect_deepEqual(mm(['abc', '/a/b/c', '\\a\\b\\c'], '\\a\\b\\c', { windows: true, nodupes: true }), ['/a/b/c']); + expect_deepEqual(mm(fixtures, ['example.*', '*.js'], { windows: true, nodupes: true }), ['example.js', 'example.md', 'example.css', 'index.js', 'test.js', 'utils.js']); + }); + + test('should not remove duplicates', () => { + expect_deepEqual(mm(['abc', '/a/b/c', '\\a\\b\\c'], '/a/b/c'), ['/a/b/c']); + expect_deepEqual(mm(['abc', '/a/b/c', '\\a\\b\\c'], '/a/b/c', { nodupes: true }), ['/a/b/c']); + expect_deepEqual(mm(['abc', '/a/b/c', '\\a\\b\\c'], '/a/b/c', { windows: true, nodupes: true }), ['/a/b/c']); + }); + }); + + describe('options.nonegate', () => { + test('should support the `nonegate` option:', () => { + expect_deepEqual(mm(['a/a/a', 'a/b/a', 'b/b/a', 'c/c/a', 'c/c/b'], '!**/a'), ['c/c/b']); + expect_deepEqual(mm(['a.md', '!a.md', 'a.txt'], '!*.md', { nonegate: true }), ['!a.md']); + + // this should not return more than one nested directory, since "!**/a" is + // collapsed to "!*/a", given that "**" is not the only thing in the segment. + expect_deepEqual(mm(['!a/a/a', 'a/b/a', 'b/b/a', '!c/c/a', '!a/a'], '!**/a', { nonegate: true }), ['!a/a']); + expect_deepEqual(mm(['!*.md', '.dotfile.txt', 'a/b/.dotfile'], '!*.md', { nonegate: true }), ['!*.md']); + }); + }); + + describe('options.nonull', () => { + test('should support the `nonull` option:', () => { + expect_deepEqual(mm(['*', '\\*'], '\\*', { nonull: true }), ['*', '\\*']); + expect_deepEqual(mm(['*', '\\^'], '\\^', { nonull: true }), ['\\^']); + expect_deepEqual(mm(['*', 'a\\*'], 'a\\*', { nonull: true }), ['a\\*']); + }); + }); + + describe('options.windows', () => { + test('should windows file paths by default', () => { + expect_deepEqual(mm(['a\\b\\c.md'], '**/*.md'), ['a/b/c.md']); + expect_deepEqual(mm(['a\\b\\c.md'], '**\\\\*.md', { windows: false }), ['a\\b\\c.md']); + }); + + test('should windows absolute paths', () => { + expect_deepEqual(mm(['E:\\a\\b\\c.md'], 'E:/**/*.md'), ['E:/a/b/c.md']); + expect_deepEqual(mm(['E:\\a\\b\\c.md'], 'E:/**/*.md', { windows: false }), []); + }); + + test('should strip leading `./`', () => { + let fixtures = ['./a', './a/a/a', './a/a/a/a', './a/a/a/a/a', './a/b', './a/x', './z/z', 'a', 'a/a', 'a/a/b', 'a/c', 'b', 'x/y'].sort(); + let format = str => str.replace(/^\.\//, ''); + let opts = { format }; + expect_deepEqual(mm(fixtures, '*', opts), ['a', 'b']); + expect_deepEqual(mm(fixtures, '**/a/**', opts), ['a', 'a/a/a', 'a/a/a/a', 'a/a/a/a/a', 'a/b', 'a/x', 'a/a', 'a/a/b', 'a/c']); + expect_deepEqual(mm(fixtures, '*/*', opts), ['a/b', 'a/x', 'z/z', 'a/a', 'a/c', 'x/y']); + expect_deepEqual(mm(fixtures, '*/*/*', opts), ['a/a/a', 'a/a/b']); + expect_deepEqual(mm(fixtures, '*/*/*/*', opts), ['a/a/a/a']); + expect_deepEqual(mm(fixtures, '*/*/*/*/*', opts), ['a/a/a/a/a']); + expect_deepEqual(mm(fixtures, './*', opts), ['a', 'b']); + expect_deepEqual(mm(fixtures, './**/a/**', opts), ['a', 'a/a/a', 'a/a/a/a', 'a/a/a/a/a', 'a/b', 'a/x', 'a/a', 'a/a/b', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/*/a', opts), ['a/a/a']); + expect_deepEqual(mm(fixtures, 'a/*', opts), ['a/b', 'a/x', 'a/a', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/*/*', opts), ['a/a/a', 'a/a/b']); + expect_deepEqual(mm(fixtures, 'a/*/*/*', opts), ['a/a/a/a']); + expect_deepEqual(mm(fixtures, 'a/*/*/*/*', opts), ['a/a/a/a/a']); + expect_deepEqual(mm(fixtures, 'a/*/a', opts), ['a/a/a']); + + expect_deepEqual(mm(fixtures, '*', { ...opts, windows: false }), ['a', 'b']); + expect_deepEqual(mm(fixtures, '**/a/**', { ...opts, windows: false }), ['a', 'a/a/a', 'a/a/a/a', 'a/a/a/a/a', 'a/b', 'a/x', 'a/a', 'a/a/b', 'a/c']); + expect_deepEqual(mm(fixtures, '*/*', { ...opts, windows: false }), ['a/b', 'a/x', 'z/z', 'a/a', 'a/c', 'x/y']); + expect_deepEqual(mm(fixtures, '*/*/*', { ...opts, windows: false }), ['a/a/a', 'a/a/b']); + expect_deepEqual(mm(fixtures, '*/*/*/*', { ...opts, windows: false }), ['a/a/a/a']); + expect_deepEqual(mm(fixtures, '*/*/*/*/*', { ...opts, windows: false }), ['a/a/a/a/a']); + expect_deepEqual(mm(fixtures, './*', { ...opts, windows: false }), ['a', 'b']); + expect_deepEqual(mm(fixtures, './**/a/**', { ...opts, windows: false }), ['a', 'a/a/a', 'a/a/a/a', 'a/a/a/a/a', 'a/b', 'a/x', 'a/a', 'a/a/b', 'a/c']); + expect_deepEqual(mm(fixtures, './a/*/a', { ...opts, windows: false }), ['a/a/a']); + expect_deepEqual(mm(fixtures, 'a/*', { ...opts, windows: false }), ['a/b', 'a/x', 'a/a', 'a/c']); + expect_deepEqual(mm(fixtures, 'a/*/*', { ...opts, windows: false }), ['a/a/a', 'a/a/b']); + expect_deepEqual(mm(fixtures, 'a/*/*/*', { ...opts, windows: false }), ['a/a/a/a']); + expect_deepEqual(mm(fixtures, 'a/*/*/*/*', { ...opts, windows: false }), ['a/a/a/a/a']); + expect_deepEqual(mm(fixtures, 'a/*/a', { ...opts, windows: false }), ['a/a/a']); + }); + }); + + describe('options.dot', () => { + describe('when `dot` or `dotfile` is NOT true:', () => { + test('should not match dotfiles by default:', () => { + let format = str => str.replace(/^\.\//, ''); + let opts = { format, result: format }; + + expect_deepEqual(mm(['.dotfile'], '*'), []); + expect_deepEqual(mm(['.dotfile'], '**'), []); + expect_deepEqual(mm(['a/b/c/.dotfile.md'], '*.md'), []); + expect_deepEqual(mm(['a/b', 'a/.b', '.a/b', '.a/.b'], '**'), ['a/b']); + expect_deepEqual(mm(['a/b/c/.dotfile'], '*.*'), []); + + // https://github.com/isaacs/minimatch/issues/30 + expect_deepEqual(mm(['foo/bar.js'], '**/foo/**', opts), ['foo/bar.js']); + expect_deepEqual(mm(['./foo/bar.js'], './**/foo/**', opts), ['foo/bar.js']); + expect_deepEqual(mm(['./foo/bar.js'], '**/foo/**', opts), ['foo/bar.js']); + expect_deepEqual(mm(['./foo/bar.js'], './**/foo/**', { ...opts, windows: false }), ['foo/bar.js']); + expect_deepEqual(mm(['./foo/bar.js'], '**/foo/**', { ...opts, windows: false }), ['foo/bar.js']); + }); + + test('should match dotfiles when a leading dot is defined in the path:', () => { + expect_deepEqual(mm(['a/b/c/.dotfile.md'], '**/.*'), ['a/b/c/.dotfile.md']); + expect_deepEqual(mm(['a/b/c/.dotfile.md'], '**/.*.md'), ['a/b/c/.dotfile.md']); + }); + + test('should use negation patterns on dotfiles:', () => { + expect_deepEqual(mm(['.a', '.b', 'c', 'c.md'], '!.*'), ['c', 'c.md']); + expect_deepEqual(mm(['.a', '.b', 'c', 'c.md'], '!.b'), ['.a', 'c', 'c.md']); + }); + }); + }); + + describe('windows', () => { + test('should windows file paths', () => { + expect_deepEqual(mm(['a\\b\\c.md'], '**/*.md'), ['a/b/c.md']); + expect_deepEqual(mm(['a\\b\\c.md'], '**/*.md', { windows: false }), ['a\\b\\c.md']); + expect_deepEqual(mm(['a\\b\\c.md'], '**\\\\*.md', { windows: false }), ['a\\b\\c.md']); + }); + + test('should windows absolute paths', () => { + expect_deepEqual(mm(['E:\\a\\b\\c.md'], 'E:/**/*.md'), ['E:/a/b/c.md']); + expect_deepEqual(mm(['E:\\a\\b\\c.md'], 'E:/**/*.md', { windows: false }), []); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/posix-classes.test.ts b/packages/node-utils/test/micromatch/posix-classes.test.ts new file mode 100644 index 0000000..216c452 --- /dev/null +++ b/packages/node-utils/test/micromatch/posix-classes.test.ts @@ -0,0 +1,474 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; +const opts = { strictSlashes: true, posix: true }; + +const isMatch = (...args) => { + return mm.isMatch(...args, opts); +}; +const convert = (...args) => { + let res = mm.parse(...args, opts); + let str = ''; + res.forEach(state => (str += state.output)); + return str; +}; + +describe('posix classes', () => { + describe('posix bracket type conversion', () => { + test('should create regex character classes from POSIX bracket expressions:', () => { + expect_loose_equal(convert('foo[[:lower:]]bar'), 'foo[a-z]bar'); + expect_loose_equal(convert('foo[[:lower:][:upper:]]bar'), 'foo[a-zA-Z]bar'); + expect_loose_equal(convert('[[:alpha:]123]'), '(?=.)[a-zA-Z123]'); + expect_loose_equal(convert('[[:lower:]]'), '(?=.)[a-z]'); + expect_loose_equal(convert('[![:lower:]]'), '(?=.)[^a-z]'); + expect_loose_equal(convert('[[:digit:][:upper:][:space:]]'), '(?=.)[0-9A-Z \\t\\r\\n\\v\\f]'); + expect_loose_equal(convert('[[:xdigit:]]'), '(?=.)[A-Fa-f0-9]'); + expect_loose_equal(convert('[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]'), '(?=.)[a-zA-Z0-9a-zA-Z \\t\\x00-\\x1F\\x7F0-9\\x21-\\x7Ea-z\\x20-\\x7E \\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~ \\t\\r\\n\\v\\fA-ZA-Fa-f0-9]'); + expect_loose_equal(convert('[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]'), '(?=.)[^a-zA-Z0-9a-zA-Z \\t\\x00-\\x1F\\x7F0-9a-z \\t\\r\\n\\v\\fA-ZA-Fa-f0-9]'); + expect_loose_equal(convert('[a-c[:digit:]x-z]'), '(?=.)[a-c0-9x-z]'); + if (process.platform !== 'win32') { + expect_loose_equal(convert('[_[:alpha:]][_[:alnum:]][_[:alnum:]]*'), '(?=.)[_a-zA-Z][_a-zA-Z0-9][_a-zA-Z0-9][^/]*?', []); + } + }); + }); + + describe('integration: posix classes with globs', () => { + test('should work with globs', () => { + let fixtures = ['a.b', 'a,b', 'a:b', 'a-b', 'a;b', 'a b', 'a_b']; + expect_deepEqual(mm(fixtures, 'a[^[:alnum:]]b', { posix: true }), fixtures); + expect_deepEqual(mm(fixtures, 'a@([^[:alnum:]])b', { posix: true }), fixtures); + expect_deepEqual(mm(fixtures, 'a@([-.,:; _])b', { posix: true }), fixtures); + + expect_deepEqual(mm(fixtures, 'a@([^x])b', { posix: true }), ['a.b', 'a,b', 'a:b', 'a-b', 'a;b', 'a b', 'a_b']); + expect_deepEqual(mm(fixtures, 'a+([^[:alnum:]])b', { posix: true }), fixtures); + }); + }); + + describe('.isMatch', () => { + test('should support POSIX.2 character classes', () => { + expect_truthy(isMatch('e', '[[:xdigit:]]')); + + expect_truthy(isMatch('a', '[[:alpha:]123]')); + expect_truthy(isMatch('1', '[[:alpha:]123]')); + expect_truthy(!isMatch('5', '[[:alpha:]123]')); + expect_truthy(isMatch('A', '[[:alpha:]123]')); + + expect_truthy(isMatch('A', '[[:alpha:]]')); + expect_truthy(!isMatch('9', '[[:alpha:]]')); + expect_truthy(isMatch('b', '[[:alpha:]]')); + + expect_truthy(!isMatch('A', '[![:alpha:]]')); + expect_truthy(isMatch('9', '[![:alpha:]]')); + expect_truthy(!isMatch('b', '[![:alpha:]]')); + + expect_truthy(!isMatch('A', '[^[:alpha:]]')); + expect_truthy(isMatch('9', '[^[:alpha:]]')); + expect_truthy(!isMatch('b', '[^[:alpha:]]')); + + expect_truthy(!isMatch('A', '[[:digit:]]')); + expect_truthy(isMatch('9', '[[:digit:]]')); + expect_truthy(!isMatch('b', '[[:digit:]]')); + + expect_truthy(isMatch('A', '[^[:digit:]]')); + expect_truthy(!isMatch('9', '[^[:digit:]]')); + expect_truthy(isMatch('b', '[^[:digit:]]')); + + expect_truthy(isMatch('A', '[![:digit:]]')); + expect_truthy(!isMatch('9', '[![:digit:]]')); + expect_truthy(isMatch('b', '[![:digit:]]')); + + expect_truthy(isMatch('a', '[[:lower:]]')); + expect_truthy(!isMatch('A', '[[:lower:]]')); + expect_truthy(!isMatch('9', '[[:lower:]]')); + + expect_truthy(isMatch('a', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + expect_truthy(isMatch('l', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + expect_truthy(isMatch('p', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + expect_truthy(isMatch('h', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + expect_truthy(isMatch(':', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + expect_truthy(!isMatch('b', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + }); + + test('should support multiple posix brackets in one character class', () => { + expect_truthy(isMatch('9', '[[:lower:][:digit:]]')); + expect_truthy(isMatch('a', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('A', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('aa', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('99', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('a9', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('9a', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('aA', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('9A', '[[:lower:][:digit:]]')); + expect_truthy(isMatch('aa', '[[:lower:][:digit:]]+')); + expect_truthy(isMatch('99', '[[:lower:][:digit:]]+')); + expect_truthy(isMatch('a9', '[[:lower:][:digit:]]+')); + expect_truthy(isMatch('9a', '[[:lower:][:digit:]]+')); + expect_truthy(!isMatch('aA', '[[:lower:][:digit:]]+')); + expect_truthy(!isMatch('9A', '[[:lower:][:digit:]]+')); + expect_truthy(isMatch('a', '[[:lower:][:digit:]]*')); + expect_truthy(!isMatch('A', '[[:lower:][:digit:]]*')); + expect_truthy(!isMatch('AA', '[[:lower:][:digit:]]*')); + expect_truthy(isMatch('aa', '[[:lower:][:digit:]]*')); + expect_truthy(isMatch('aaa', '[[:lower:][:digit:]]*')); + expect_truthy(isMatch('999', '[[:lower:][:digit:]]*')); + }); + + test('should match word characters', () => { + expect_truthy(!isMatch('a c', 'a[[:word:]]+c')); + expect_truthy(!isMatch('a.c', 'a[[:word:]]+c')); + expect_truthy(!isMatch('a.xy.zc', 'a[[:word:]]+c')); + expect_truthy(!isMatch('a.zc', 'a[[:word:]]+c')); + expect_truthy(!isMatch('abq', 'a[[:word:]]+c')); + expect_truthy(!isMatch('axy zc', 'a[[:word:]]+c')); + expect_truthy(!isMatch('axy', 'a[[:word:]]+c')); + expect_truthy(!isMatch('axy.zc', 'a[[:word:]]+c')); + expect_truthy(isMatch('a123c', 'a[[:word:]]+c')); + expect_truthy(isMatch('a1c', 'a[[:word:]]+c')); + expect_truthy(isMatch('abbbbc', 'a[[:word:]]+c')); + expect_truthy(isMatch('abbbc', 'a[[:word:]]+c')); + expect_truthy(isMatch('abbc', 'a[[:word:]]+c')); + expect_truthy(isMatch('abc', 'a[[:word:]]+c')); + + expect_truthy(!isMatch('a c', 'a[[:word:]]+')); + expect_truthy(!isMatch('a.c', 'a[[:word:]]+')); + expect_truthy(!isMatch('a.xy.zc', 'a[[:word:]]+')); + expect_truthy(!isMatch('a.zc', 'a[[:word:]]+')); + expect_truthy(!isMatch('axy zc', 'a[[:word:]]+')); + expect_truthy(!isMatch('axy.zc', 'a[[:word:]]+')); + expect_truthy(isMatch('a123c', 'a[[:word:]]+')); + expect_truthy(isMatch('a1c', 'a[[:word:]]+')); + expect_truthy(isMatch('abbbbc', 'a[[:word:]]+')); + expect_truthy(isMatch('abbbc', 'a[[:word:]]+')); + expect_truthy(isMatch('abbc', 'a[[:word:]]+')); + expect_truthy(isMatch('abc', 'a[[:word:]]+')); + expect_truthy(isMatch('abq', 'a[[:word:]]+')); + expect_truthy(isMatch('axy', 'a[[:word:]]+')); + expect_truthy(isMatch('axyzc', 'a[[:word:]]+')); + expect_truthy(isMatch('axyzc', 'a[[:word:]]+')); + }); + + test('should match character classes', () => { + expect_truthy(!isMatch('abc', 'a[bc]d')); + expect_truthy(isMatch('abd', 'a[bc]d')); + }); + + test('should match character class alphabetical ranges', () => { + expect_truthy(!isMatch('abc', 'a[b-d]e')); + expect_truthy(!isMatch('abd', 'a[b-d]e')); + expect_truthy(isMatch('abe', 'a[b-d]e')); + expect_truthy(!isMatch('ac', 'a[b-d]e')); + expect_truthy(!isMatch('a-', 'a[b-d]e')); + + expect_truthy(!isMatch('abc', 'a[b-d]')); + expect_truthy(!isMatch('abd', 'a[b-d]')); + expect_truthy(isMatch('abd', 'a[b-d]+')); + expect_truthy(!isMatch('abe', 'a[b-d]')); + expect_truthy(isMatch('ac', 'a[b-d]')); + expect_truthy(!isMatch('a-', 'a[b-d]')); + }); + + test('should match character classes with leading dashes', () => { + expect_truthy(!isMatch('abc', 'a[-c]')); + expect_truthy(isMatch('ac', 'a[-c]')); + expect_truthy(isMatch('a-', 'a[-c]')); + }); + + test('should match character classes with trailing dashes', () => { + expect_truthy(!isMatch('abc', 'a[c-]')); + expect_truthy(isMatch('ac', 'a[c-]')); + expect_truthy(isMatch('a-', 'a[c-]')); + }); + + test('should match bracket literals', () => { + expect_truthy(isMatch('a]c', 'a[]]c')); + expect_truthy(isMatch('a]c', 'a]c')); + expect_truthy(isMatch('a]', 'a]')); + + expect_truthy(isMatch('a[c', 'a[\\[]c')); + expect_truthy(isMatch('a[c', 'a[c')); + expect_truthy(isMatch('a[', 'a[')); + }); + + test('should support negated character classes', () => { + expect_truthy(!isMatch('a]', 'a[^bc]d')); + expect_truthy(!isMatch('acd', 'a[^bc]d')); + expect_truthy(isMatch('aed', 'a[^bc]d')); + expect_truthy(isMatch('azd', 'a[^bc]d')); + expect_truthy(!isMatch('ac', 'a[^bc]d')); + expect_truthy(!isMatch('a-', 'a[^bc]d')); + }); + + test('should match negated dashes', () => { + expect_truthy(!isMatch('abc', 'a[^-b]c')); + expect_truthy(isMatch('adc', 'a[^-b]c')); + expect_truthy(!isMatch('a-c', 'a[^-b]c')); + }); + + test('should match negated mm', () => { + expect_truthy(isMatch('a-c', 'a[^\\]b]c')); + expect_truthy(!isMatch('abc', 'a[^\\]b]c')); + expect_truthy(!isMatch('a]c', 'a[^\\]b]c')); + expect_truthy(isMatch('adc', 'a[^\\]b]c')); + }); + + test('should match alpha-numeric characters', () => { + expect_truthy(!isMatch('0123e45g78', '[\\de]+')); + expect_truthy(isMatch('0123e456', '[\\de]+')); + expect_truthy(isMatch('01234', '[\\de]+')); + }); + + test('should not create an invalid posix character class:', () => { + expect_loose_equal(convert('[:al:]'), '(?:\\[:al:\\]|[:al:])'); + expect_loose_equal(convert('[abc[:punct:][0-9]'), '(?=.)[abc\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~\\[0-9]'); + }); + + test('should return `true` when the pattern matches:', () => { + expect_truthy(isMatch('a', '[[:lower:]]')); + expect_truthy(isMatch('A', '[[:upper:]]')); + expect_truthy(isMatch('A', '[[:digit:][:upper:][:space:]]')); + expect_truthy(isMatch('1', '[[:digit:][:upper:][:space:]]')); + expect_truthy(isMatch(' ', '[[:digit:][:upper:][:space:]]')); + expect_truthy(isMatch('5', '[[:xdigit:]]')); + expect_truthy(isMatch('f', '[[:xdigit:]]')); + expect_truthy(isMatch('D', '[[:xdigit:]]')); + expect_truthy(isMatch('_', '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]')); + expect_truthy(isMatch('_', '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]')); + expect_truthy(isMatch('.', '[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]')); + expect_truthy(isMatch('5', '[a-c[:digit:]x-z]')); + expect_truthy(isMatch('b', '[a-c[:digit:]x-z]')); + expect_truthy(isMatch('y', '[a-c[:digit:]x-z]')); + }); + + test('should return `false` when the pattern does not match:', () => { + expect_truthy(!isMatch('A', '[[:lower:]]')); + expect_truthy(isMatch('A', '[![:lower:]]')); + expect_truthy(!isMatch('a', '[[:upper:]]')); + expect_truthy(!isMatch('a', '[[:digit:][:upper:][:space:]]')); + expect_truthy(!isMatch('.', '[[:digit:][:upper:][:space:]]')); + expect_truthy(!isMatch('.', '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]')); + expect_truthy(!isMatch('q', '[a-c[:digit:]x-z]')); + }); + }); + + describe('literals', () => { + test('should match literal brackets when escaped', () => { + expect_truthy(isMatch('a [b]', 'a [b]')); + expect_truthy(isMatch('a b', 'a [b]')); + + expect_truthy(isMatch('a [b] c', 'a [b] c')); + expect_truthy(isMatch('a b c', 'a [b] c')); + + expect_truthy(isMatch('a [b]', 'a \\[b\\]')); + expect_truthy(!isMatch('a b', 'a \\[b\\]')); + + expect_truthy(isMatch('a [b]', 'a ([b])')); + expect_truthy(isMatch('a b', 'a ([b])')); + + expect_truthy(isMatch('a b', 'a (\\[b\\]|[b])')); + expect_truthy(isMatch('a [b]', 'a (\\[b\\]|[b])')); + }); + }); + + describe('.makeRe()', () => { + test('should make a regular expression for the given pattern:', () => { + expect_deepEqual(mm.makeRe('[[:alpha:]123]', opts), /^(?:(?=.)[a-zA-Z123])$/); + expect_deepEqual(mm.makeRe('[![:lower:]]', opts), /^(?:(?=.)[^a-z])$/); + }); + }); + + describe('POSIX: From the test suite for the POSIX.2 (BRE) pattern matching code:', () => { + test('First, test POSIX.2 character classes', () => { + expect_truthy(isMatch('e', '[[:xdigit:]]')); + expect_truthy(isMatch('1', '[[:xdigit:]]')); + expect_truthy(isMatch('a', '[[:alpha:]123]')); + expect_truthy(isMatch('1', '[[:alpha:]123]')); + }); + + test('should match using POSIX.2 negation patterns', () => { + expect_truthy(isMatch('9', '[![:alpha:]]')); + expect_truthy(isMatch('9', '[^[:alpha:]]')); + }); + + test('should match word characters', () => { + expect_truthy(isMatch('A', '[[:word:]]')); + expect_truthy(isMatch('B', '[[:word:]]')); + expect_truthy(isMatch('a', '[[:word:]]')); + expect_truthy(isMatch('b', '[[:word:]]')); + }); + + test('should match digits with word class', () => { + expect_truthy(isMatch('1', '[[:word:]]')); + expect_truthy(isMatch('2', '[[:word:]]')); + }); + + test('should not digits', () => { + expect_truthy(isMatch('1', '[[:digit:]]')); + expect_truthy(isMatch('2', '[[:digit:]]')); + }); + + test('should not match word characters with digit class', () => { + expect_truthy(!isMatch('a', '[[:digit:]]')); + expect_truthy(!isMatch('A', '[[:digit:]]')); + }); + + test('should match uppercase alpha characters', () => { + expect_truthy(isMatch('A', '[[:upper:]]')); + expect_truthy(isMatch('B', '[[:upper:]]')); + }); + + test('should not match lowercase alpha characters', () => { + expect_truthy(!isMatch('a', '[[:upper:]]')); + expect_truthy(!isMatch('b', '[[:upper:]]')); + }); + + test('should not match digits with upper class', () => { + expect_truthy(!isMatch('1', '[[:upper:]]')); + expect_truthy(!isMatch('2', '[[:upper:]]')); + }); + + test('should match lowercase alpha characters', () => { + expect_truthy(isMatch('a', '[[:lower:]]')); + expect_truthy(isMatch('b', '[[:lower:]]')); + }); + + test('should not match uppercase alpha characters', () => { + expect_truthy(!isMatch('A', '[[:lower:]]')); + expect_truthy(!isMatch('B', '[[:lower:]]')); + }); + + test('should match one lower and one upper character', () => { + expect_truthy(isMatch('aA', '[[:lower:]][[:upper:]]')); + expect_truthy(!isMatch('AA', '[[:lower:]][[:upper:]]')); + expect_truthy(!isMatch('Aa', '[[:lower:]][[:upper:]]')); + }); + + test('should match hexidecimal digits', () => { + expect_truthy(isMatch('ababab', '[[:xdigit:]]*')); + expect_truthy(isMatch('020202', '[[:xdigit:]]*')); + expect_truthy(isMatch('900', '[[:xdigit:]]*')); + }); + + test('should match punctuation characters (\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~)', () => { + expect_truthy(isMatch('!', '[[:punct:]]')); + expect_truthy(isMatch('?', '[[:punct:]]')); + expect_truthy(isMatch('#', '[[:punct:]]')); + expect_truthy(isMatch('&', '[[:punct:]]')); + expect_truthy(isMatch('@', '[[:punct:]]')); + expect_truthy(isMatch('+', '[[:punct:]]')); + expect_truthy(isMatch('*', '[[:punct:]]')); + expect_truthy(isMatch(':', '[[:punct:]]')); + expect_truthy(isMatch('=', '[[:punct:]]')); + expect_truthy(isMatch('|', '[[:punct:]]')); + expect_truthy(isMatch('|++', '[[:punct:]]*')); + }); + + test('should only match one character', () => { + expect_truthy(!isMatch('?*+', '[[:punct:]]')); + }); + + test('should only match one or more punctuation characters', () => { + expect_truthy(isMatch('?*+', '[[:punct:]]*')); + expect_truthy(!isMatch('foo', 'foo[[:punct:]]*')); + expect_truthy(isMatch('foo?*+', 'foo[[:punct:]]*')); + }); + + test('invalid character class expressions are just characters to be matched', () => { + expect_truthy(isMatch('a', '[:al:]')); + expect_truthy(isMatch('a', '[[:al:]')); + expect_truthy(isMatch('!', '[abc[:punct:][0-9]')); + }); + + test('should match the start of a valid sh identifier', () => { + expect_truthy(isMatch('PATH', '[_[:alpha:]]*')); + }); + + test('should match the first two characters of a valid sh identifier', () => { + expect_truthy(isMatch('PATH', '[_[:alpha:]][_[:alnum:]]*')); + }); + + test('should match multiple posix classses', () => { + expect_truthy(isMatch('a1B', '[[:alpha:]][[:digit:]][[:upper:]]')); + expect_truthy(!isMatch('a1b', '[[:alpha:]][[:digit:]][[:upper:]]')); + expect_truthy(isMatch('.', '[[:digit:][:punct:][:space:]]')); + expect_truthy(!isMatch('a', '[[:digit:][:punct:][:space:]]')); + expect_truthy(isMatch('!', '[[:digit:][:punct:][:space:]]')); + expect_truthy(!isMatch('!', '[[:digit:]][[:punct:]][[:space:]]')); + expect_truthy(isMatch('1! ', '[[:digit:]][[:punct:]][[:space:]]')); + expect_truthy(!isMatch('1! ', '[[:digit:]][[:punct:]][[:space:]]')); + }); + + /** + * Some of these tests (and their descriptions) were ported directly + * from the Bash 4.3 unit tests. + */ + + test('how about A?', () => { + expect_truthy(isMatch('9', '[[:digit:]]')); + expect_truthy(!isMatch('X', '[[:digit:]]')); + expect_truthy(isMatch('aB', '[[:lower:]][[:upper:]]')); + expect_truthy(isMatch('a', '[[:alpha:][:digit:]]')); + expect_truthy(isMatch('3', '[[:alpha:][:digit:]]')); + expect_truthy(!isMatch('aa', '[[:alpha:][:digit:]]')); + expect_truthy(!isMatch('a3', '[[:alpha:][:digit:]]')); + expect_truthy(!isMatch('a', '[[:alpha:]\\]')); + expect_truthy(!isMatch('b', '[[:alpha:]\\]')); + }); + + test('OK, what\'s a tab? is it a blank? a space?', () => { + expect_truthy(isMatch('\t', '[[:blank:]]')); + expect_truthy(isMatch('\t', '[[:space:]]')); + expect_truthy(isMatch(' ', '[[:space:]]')); + }); + + test('let\'s check out characters in the ASCII range', () => { + expect_truthy(!isMatch('\\377', '[[:ascii:]]')); + expect_truthy(!isMatch('9', '[1[:alpha:]123]')); + }); + + test('punctuation', () => { + expect_truthy(!isMatch(' ', '[[:punct:]]')); + }); + + test('graph', () => { + expect_truthy(isMatch('A', '[[:graph:]]')); + expect_truthy(!isMatch('\b', '[[:graph:]]')); + expect_truthy(!isMatch('\n', '[[:graph:]]')); + expect_truthy(isMatch('\s', '[[:graph:]]')); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/qmarks.test.ts b/packages/node-utils/test/micromatch/qmarks.test.ts new file mode 100644 index 0000000..e1b0086 --- /dev/null +++ b/packages/node-utils/test/micromatch/qmarks.test.ts @@ -0,0 +1,150 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const mm = micromatch; +const { isMatch } = mm; + +describe('qmarks and stars', () => { + test('should match with qmarks', () => { + expect_truthy(!isMatch('/ab', '/?')); + expect_truthy(!isMatch('/ab', '?/?')); + expect_truthy(isMatch('a/b', '?/?')); + expect_truthy(isMatch('/ab', '/??')); + expect_truthy(isMatch('/ab', '/?b')); + expect_truthy(!isMatch('/ab', ['?/?', 'foo', 'bar'])); + expect_truthy(!isMatch('/ab', ['a/*', 'foo', 'bar'])); + }); + + test('should support qmark matching', () => { + let arr = ['a', 'aa', 'ab', 'aaa', 'abcdefg']; + expect_deepEqual(mm(arr, '?'), ['a']); + expect_deepEqual(mm(arr, '??'), ['aa', 'ab']); + expect_deepEqual(mm(arr, '???'), ['aaa']); + }); + + test('should correctly handle question marks in globs', () => { + expect_deepEqual(mm(['?', '??', '???'], '?'), ['?']); + expect_deepEqual(mm(['?', '??', '???'], '??'), ['??']); + expect_deepEqual(mm(['?', '??', '???'], '???'), ['???']); + expect_deepEqual(mm(['/a/', '/a/b/', '/a/b/c/', '/a/b/c/d/'], '??'), []); + mm(['/a/', '/a/b/', '/a/b/c/', '/a/b/c/d/'], '??', { dot: true }, []); + expect_deepEqual(mm(['x/y/acb', 'acb', 'acb/', 'acb/d/e'], 'a?b'), ['acb']); + expect_deepEqual(mm(['aaa', 'aac', 'abc'], 'a?c'), ['aac', 'abc']); + expect_deepEqual(mm(['aaa', 'aac', 'abc'], 'a*?c'), ['aac', 'abc']); + expect_deepEqual(mm(['a', 'aa', 'ab', 'ab?', 'ac', 'ac?', 'abcd', 'abbb'], 'ab?'), ['ab?']); + expect_deepEqual(mm(['abc', 'abb', 'acc'], 'a**?c'), ['abc', 'acc']); + expect_deepEqual(mm(['abc'], 'a*****?c'), ['abc']); + expect_deepEqual(mm(['a', 'aa', 'abc', 'zzz', 'bbb', 'aaaa'], '*****?'), ['a', 'aa', 'abc', 'zzz', 'bbb', 'aaaa']); + expect_deepEqual(mm(['a', 'aa', 'abc', 'zzz', 'bbb', 'aaaa'], '*****??'), ['aa', 'abc', 'zzz', 'bbb', 'aaaa']); + expect_deepEqual(mm(['a', 'aa', 'abc', 'zzz', 'bbb', 'aaaa'], '?*****??'), ['abc', 'zzz', 'bbb', 'aaaa']); + expect_deepEqual(mm(['abc', 'abb', 'zzz'], '?*****?c'), ['abc']); + expect_deepEqual(mm(['abc', 'bbb', 'zzz'], '?***?****c'), ['abc']); + expect_deepEqual(mm(['abc', 'bbb', 'zzz'], '?***?****?'), ['abc', 'bbb', 'zzz']); + expect_deepEqual(mm(['abc'], '?***?****'), ['abc']); + expect_deepEqual(mm(['abc'], '*******c'), ['abc']); + expect_deepEqual(mm(['abc'], '*******?'), ['abc']); + expect_deepEqual(mm(['abcdecdhjk'], 'a*cd**?**??k'), ['abcdecdhjk']); + expect_deepEqual(mm(['abcdecdhjk'], 'a**?**cd**?**??k'), ['abcdecdhjk']); + expect_deepEqual(mm(['abcdecdhjk'], 'a**?**cd**?**??k***'), ['abcdecdhjk']); + expect_deepEqual(mm(['abcdecdhjk'], 'a**?**cd**?**??***k'), ['abcdecdhjk']); + expect_deepEqual(mm(['abcdecdhjk'], 'a**?**cd**?**??***k**'), ['abcdecdhjk']); + expect_deepEqual(mm(['abcdecdhjk'], 'a****c**?**??*****'), ['abcdecdhjk']); + }); + + test('should match one character per question mark', () => { + expect_deepEqual(mm(['a/b/c.md'], 'a/?/c.md'), ['a/b/c.md']); + expect_deepEqual(mm(['a/bb/c.md'], 'a/?/c.md'), []); + expect_deepEqual(mm(['a/bb/c.md'], 'a/??/c.md'), ['a/bb/c.md']); + expect_deepEqual(mm(['a/bbb/c.md'], 'a/??/c.md'), []); + expect_deepEqual(mm(['a/bbb/c.md'], 'a/???/c.md'), ['a/bbb/c.md']); + expect_deepEqual(mm(['a/bbbb/c.md'], 'a/????/c.md'), ['a/bbbb/c.md']); + }); + + test('should match multiple groups of question marks', () => { + expect_deepEqual(mm(['a/bb/c/dd/e.md'], 'a/?/c/?/e.md'), []); + expect_deepEqual(mm(['a/b/c/d/e.md'], 'a/?/c/?/e.md'), ['a/b/c/d/e.md']); + expect_deepEqual(mm(['a/b/c/d/e.md'], 'a/?/c/???/e.md'), []); + expect_deepEqual(mm(['a/b/c/zzz/e.md'], 'a/?/c/???/e.md'), ['a/b/c/zzz/e.md']); + }); + + test('should use qmarks with other special characters', () => { + expect_deepEqual(mm(['a/b/c/d/e.md'], 'a/?/c/?/*/e.md'), []); + expect_deepEqual(mm(['a/b/c/d/e/e.md'], 'a/?/c/?/*/e.md'), ['a/b/c/d/e/e.md']); + expect_deepEqual(mm(['a/b/c/d/efghijk/e.md'], 'a/?/c/?/*/e.md'), ['a/b/c/d/efghijk/e.md']); + expect_deepEqual(mm(['a/b/c/d/efghijk/e.md'], 'a/?/**/e.md'), ['a/b/c/d/efghijk/e.md']); + expect_deepEqual(mm(['a/bb/e.md'], 'a/?/e.md'), []); + expect_deepEqual(mm(['a/bb/e.md'], 'a/?/**/e.md'), []); + expect_deepEqual(mm(['a/b/c/d/efghijk/e.md'], 'a/*/?/**/e.md'), ['a/b/c/d/efghijk/e.md']); + expect_deepEqual(mm(['a/b/c/d/efgh.ijk/e.md'], 'a/*/?/**/e.md'), ['a/b/c/d/efgh.ijk/e.md']); + expect_deepEqual(mm(['a/b.bb/c/d/efgh.ijk/e.md'], 'a/*/?/**/e.md'), ['a/b.bb/c/d/efgh.ijk/e.md']); + expect_deepEqual(mm(['a/bbb/c/d/efgh.ijk/e.md'], 'a/*/?/**/e.md'), ['a/bbb/c/d/efgh.ijk/e.md']); + }); + + test('question marks should not match slashes', () => { + expect_truthy(!isMatch('aaa/bbb', 'aaa?bbb')); + expect_truthy(!isMatch('aaa//bbb', 'aaa?bbb')); + if (process.platform === 'win32') { + expect_truthy(!isMatch('aaa\\bbb', 'aaa?bbb')); + expect_truthy(!isMatch('aaa\\\\bbb', 'aaa??bbb')); + } else { + expect_truthy(isMatch('aaa\\bbb', 'aaa?bbb')); + expect_truthy(!isMatch('aaa\\\\bbb', 'aaa?bbb')); + expect_truthy(isMatch('aaa\\\\bbb', 'aaa??bbb')); + } + expect_truthy(!isMatch('aaa/bbb', 'aaa?bbb')); + }); + + test('question marks should match arbitrary dots', () => { + expect_truthy(isMatch('aaa.bbb', 'aaa?bbb')); + }); + + test('question marks should not match leading dots', () => { + expect_truthy(!isMatch('.aaa/bbb', '?aaa/bbb')); + expect_truthy(!isMatch('aaa/.bbb', 'aaa/?bbb')); + }); + + test('question marks should match leading dots when options.dot is true', () => { + expect_truthy(isMatch('aaa/.bbb', 'aaa/?bbb', {dot: true})); + expect_truthy(isMatch('.aaa/bbb', '?aaa/bbb', {dot: true})); + }); + + test('question marks should match characters preceding a dot', () => { + expect_truthy(isMatch('a/bbb/abcd.md', 'a/*/ab??.md')); + expect_truthy(isMatch('a/bbb/abcd.md', 'a/bbb/ab??.md')); + expect_truthy(isMatch('a/bbb/abcd.md', 'a/bbb/ab???md')); + }); +}); diff --git a/packages/node-utils/test/micromatch/regex-features.test.ts b/packages/node-utils/test/micromatch/regex-features.test.ts new file mode 100644 index 0000000..5d2db8b --- /dev/null +++ b/packages/node-utils/test/micromatch/regex-features.test.ts @@ -0,0 +1,205 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const version = process.version; +const mm = micromatch; + +describe('regex features', () => { + + describe('back-references', () => { + test('should support regex backreferences', () => { + expect_truthy(!mm.isMatch('1/2', '(*)/\\1')); + expect_truthy(mm.isMatch('1/1', '(*)/\\1')); + expect_truthy(mm.isMatch('1/1/1/1', '(*)/\\1/\\1/\\1')); + expect_truthy(!mm.isMatch('1/11/111/1111', '(*)/\\1/\\1/\\1')); + expect_truthy(mm.isMatch('1/11/111/1111', '(*)/(\\1)+/(\\1)+/(\\1)+')); + expect_truthy(!mm.isMatch('1/2/1/1', '(*)/\\1/\\1/\\1')); + expect_truthy(!mm.isMatch('1/1/2/1', '(*)/\\1/\\1/\\1')); + expect_truthy(!mm.isMatch('1/1/1/2', '(*)/\\1/\\1/\\1')); + expect_truthy(mm.isMatch('1/1/1/1', '(*)/\\1/(*)/\\2')); + expect_truthy(!mm.isMatch('1/1/2/1', '(*)/\\1/(*)/\\2')); + expect_truthy(!mm.isMatch('1/1/2/1', '(*)/\\1/(*)/\\2')); + expect_truthy(mm.isMatch('1/1/2/2', '(*)/\\1/(*)/\\2')); + }); + }); + + describe('character classes', () => { + test('should match regex character classes', () => { + expect_truthy(!mm.isMatch('foo/bar', '**/[jkl]*')); + expect_truthy(mm.isMatch('foo/jar', '**/[jkl]*')); + + expect_truthy(mm.isMatch('foo/bar', '**/[^jkl]*')); + expect_truthy(!mm.isMatch('foo/jar', '**/[^jkl]*')); + + expect_truthy(mm.isMatch('foo/bar', '**/[abc]*')); + expect_truthy(!mm.isMatch('foo/jar', '**/[abc]*')); + + expect_truthy(!mm.isMatch('foo/bar', '**/[^abc]*')); + expect_truthy(mm.isMatch('foo/jar', '**/[^abc]*')); + + expect_truthy(mm.isMatch('foo/bar', '**/[abc]ar')); + expect_truthy(!mm.isMatch('foo/jar', '**/[abc]ar')); + }); + + test('should support valid regex ranges', () => { + expect_truthy(!mm.isMatch('a/a', 'a/[b-c]')); + expect_truthy(!mm.isMatch('a/z', 'a/[b-c]')); + expect_truthy(mm.isMatch('a/b', 'a/[b-c]')); + expect_truthy(mm.isMatch('a/c', 'a/[b-c]')); + expect_truthy(mm.isMatch('a/b', '[a-z]/[a-z]')); + expect_truthy(mm.isMatch('a/z', '[a-z]/[a-z]')); + expect_truthy(mm.isMatch('z/z', '[a-z]/[a-z]')); + expect_truthy(!mm.isMatch('a/x/y', 'a/[a-z]')); + + expect_truthy(mm.isMatch('a.a', '[a-b].[a-b]')); + expect_truthy(mm.isMatch('a.b', '[a-b].[a-b]')); + expect_truthy(!mm.isMatch('a.a.a', '[a-b].[a-b]')); + expect_truthy(!mm.isMatch('c.a', '[a-b].[a-b]')); + expect_truthy(!mm.isMatch('d.a.d', '[a-b].[a-b]')); + expect_truthy(!mm.isMatch('a.bb', '[a-b].[a-b]')); + expect_truthy(!mm.isMatch('a.ccc', '[a-b].[a-b]')); + + expect_truthy(mm.isMatch('a.a', '[a-d].[a-b]')); + expect_truthy(mm.isMatch('a.b', '[a-d].[a-b]')); + expect_truthy(!mm.isMatch('a.a.a', '[a-d].[a-b]')); + expect_truthy(mm.isMatch('c.a', '[a-d].[a-b]')); + expect_truthy(!mm.isMatch('d.a.d', '[a-d].[a-b]')); + expect_truthy(!mm.isMatch('a.bb', '[a-d].[a-b]')); + expect_truthy(!mm.isMatch('a.ccc', '[a-d].[a-b]')); + + expect_truthy(mm.isMatch('a.a', '[a-d]*.[a-b]')); + expect_truthy(mm.isMatch('a.b', '[a-d]*.[a-b]')); + expect_truthy(mm.isMatch('a.a.a', '[a-d]*.[a-b]')); + expect_truthy(mm.isMatch('c.a', '[a-d]*.[a-b]')); + expect_truthy(!mm.isMatch('d.a.d', '[a-d]*.[a-b]')); + expect_truthy(!mm.isMatch('a.bb', '[a-d]*.[a-b]')); + expect_truthy(!mm.isMatch('a.ccc', '[a-d]*.[a-b]')); + }); + + test('should support valid regex ranges with glob negation patterns', () => { + expect_truthy(!mm.isMatch('a.a', '!*.[a-b]')); + expect_truthy(!mm.isMatch('a.b', '!*.[a-b]')); + expect_truthy(!mm.isMatch('a.a.a', '!*.[a-b]')); + expect_truthy(!mm.isMatch('c.a', '!*.[a-b]')); + expect_truthy(mm.isMatch('d.a.d', '!*.[a-b]')); + expect_truthy(mm.isMatch('a.bb', '!*.[a-b]')); + expect_truthy(mm.isMatch('a.ccc', '!*.[a-b]')); + + expect_truthy(!mm.isMatch('a.a', '!*.[a-b]*')); + expect_truthy(!mm.isMatch('a.b', '!*.[a-b]*')); + expect_truthy(!mm.isMatch('a.a.a', '!*.[a-b]*')); + expect_truthy(!mm.isMatch('c.a', '!*.[a-b]*')); + expect_truthy(!mm.isMatch('d.a.d', '!*.[a-b]*')); + expect_truthy(!mm.isMatch('a.bb', '!*.[a-b]*')); + expect_truthy(mm.isMatch('a.ccc', '!*.[a-b]*')); + + expect_truthy(!mm.isMatch('a.a', '![a-b].[a-b]')); + expect_truthy(!mm.isMatch('a.b', '![a-b].[a-b]')); + expect_truthy(mm.isMatch('a.a.a', '![a-b].[a-b]')); + expect_truthy(mm.isMatch('c.a', '![a-b].[a-b]')); + expect_truthy(mm.isMatch('d.a.d', '![a-b].[a-b]')); + expect_truthy(mm.isMatch('a.bb', '![a-b].[a-b]')); + expect_truthy(mm.isMatch('a.ccc', '![a-b].[a-b]')); + + expect_truthy(!mm.isMatch('a.a', '![a-b]+.[a-b]+')); + expect_truthy(!mm.isMatch('a.b', '![a-b]+.[a-b]+')); + expect_truthy(mm.isMatch('a.a.a', '![a-b]+.[a-b]+')); + expect_truthy(mm.isMatch('c.a', '![a-b]+.[a-b]+')); + expect_truthy(mm.isMatch('d.a.d', '![a-b]+.[a-b]+')); + expect_truthy(!mm.isMatch('a.bb', '![a-b]+.[a-b]+')); + expect_truthy(mm.isMatch('a.ccc', '![a-b]+.[a-b]+')); + }); + + test('should support valid regex ranges in negated character classes', () => { + expect_truthy(!mm.isMatch('a.a', '*.[^a-b]')); + expect_truthy(!mm.isMatch('a.b', '*.[^a-b]')); + expect_truthy(!mm.isMatch('a.a.a', '*.[^a-b]')); + expect_truthy(!mm.isMatch('c.a', '*.[^a-b]')); + expect_truthy(mm.isMatch('d.a.d', '*.[^a-b]')); + expect_truthy(!mm.isMatch('a.bb', '*.[^a-b]')); + expect_truthy(!mm.isMatch('a.ccc', '*.[^a-b]')); + + expect_truthy(!mm.isMatch('a.a', 'a.[^a-b]*')); + expect_truthy(!mm.isMatch('a.b', 'a.[^a-b]*')); + expect_truthy(!mm.isMatch('a.a.a', 'a.[^a-b]*')); + expect_truthy(!mm.isMatch('c.a', 'a.[^a-b]*')); + expect_truthy(!mm.isMatch('d.a.d', 'a.[^a-b]*')); + expect_truthy(!mm.isMatch('a.bb', 'a.[^a-b]*')); + expect_truthy(mm.isMatch('a.ccc', 'a.[^a-b]*')); + }); + }); + + describe('capture groups', () => { + test('should support regex capture groups', () => { + expect_truthy(mm.isMatch('a/bb/c/dd/e.md', 'a/??/?/(dd)/e.md')); + expect_truthy(mm.isMatch('a/b/c/d/e.md', 'a/?/c/?/(e|f).md')); + expect_truthy(mm.isMatch('a/b/c/d/f.md', 'a/?/c/?/(e|f).md')); + }); + + test('should support regex capture groups with slashes', () => { + expect_truthy(!mm.isMatch('a/a', '(a/b)')); + expect_truthy(mm.isMatch('a/b', '(a/b)')); + expect_truthy(!mm.isMatch('a/c', '(a/b)')); + expect_truthy(!mm.isMatch('b/a', '(a/b)')); + expect_truthy(!mm.isMatch('b/b', '(a/b)')); + expect_truthy(!mm.isMatch('b/c', '(a/b)')); + }); + + test('should support regex non-capture groups', () => { + expect_truthy(mm.isMatch('a/bb/c/dd/e.md', 'a/**/(?:dd)/e.md')); + expect_truthy(mm.isMatch('a/b/c/d/e.md', 'a/?/c/?/(?:e|f).md')); + expect_truthy(mm.isMatch('a/b/c/d/f.md', 'a/?/c/?/(?:e|f).md')); + }); + }); + + describe('lookarounds', () => { + test('should support regex lookbehinds', () => { + if (parseInt(version.slice(1), 10) >= 10) { + expect_truthy(mm.isMatch('foo/cbaz', 'foo/*(? { + Reflect.defineProperty(process, 'version', { value: 'v6.0.0' }); + expect_throws(() => mm.isMatch('foo/cbaz', 'foo/*(? { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const isWindows = () => process.platform === 'win32' || (path as any).sep === '\\'; +const mm = micromatch; +const { isMatch, makeRe } = mm; + +if (!process.env.ORIGINAL_PATH_SEP) { + process.env.ORIGINAL_PATH_SEP = path.sep; +} + +describe('special characters', () => { + // See micromatch#127 + describe('unicode', () => { + test('should match Japanese characters', () => { + expect_truthy(isMatch('フォルダ/aaa.js', 'フ*/**/*')); + expect_truthy(isMatch('フォルダ/aaa.js', 'フォ*/**/*')); + expect_truthy(isMatch('フォルダ/aaa.js', 'フォル*/**/*')); + expect_truthy(isMatch('フォルダ/aaa.js', 'フ*ル*/**/*')); + expect_truthy(isMatch('フォルダ/aaa.js', 'フォルダ/**/*')); + }); + }); + + describe('regex', () => { + test('should match common regex characters', () => { + let fixtures = ['a c', 'a1c', 'a123c', 'a.c', 'a.xy.zc', 'a.zc', 'abbbbc', 'abbbc', 'abbc', 'abc', 'abq', 'axy zc', 'axy', 'axy.zc', 'axyzc', '^abc$']; + + expect_deepEqual(mm(fixtures, 'ab?bc'), ['abbbc']); + expect_deepEqual(mm(fixtures, 'ab*c'), ['abbbbc', 'abbbc', 'abbc', 'abc']); + expect_deepEqual(mm(fixtures, '^abc$'), ['^abc$']); + expect_deepEqual(mm(fixtures, 'a.c'), ['a.c']); + expect_deepEqual(mm(fixtures, 'a.*c'), ['a.c', 'a.xy.zc', 'a.zc']); + expect_deepEqual(mm(fixtures, 'a*c'), ['a c', 'a1c', 'a123c', 'a.c', 'a.xy.zc', 'a.zc', 'abbbbc', 'abbbc', 'abbc', 'abc', 'axy zc', 'axy.zc', 'axyzc']); + expect_deepEqual(mm(fixtures, 'a(\\w)+c'), ['a1c', 'a123c', 'abbbbc', 'abbbc', 'abbc', 'abc', 'axyzc'], 'Should match word characters'); + expect_deepEqual(mm(fixtures, 'a(\\W)+c'), ['a c', 'a.c'], 'Should match non-word characters'); + expect_deepEqual(mm(fixtures, 'a(\\d)+c'), ['a1c', 'a123c'], 'Should match numbers'); + expect_deepEqual(mm(['foo@#$%123ASD #$$%^&', 'foo!@#$asdfl;', '123'], '(\\d)+'), ['123']); + expect_deepEqual(mm(['a123c', 'abbbc'], 'a(\\D)+c'), ['abbbc'], 'Should match non-numbers'); + expect_deepEqual(mm(['foo', ' foo '], '(f|o)+\\b'), ['foo'], 'Should match word boundaries'); + }); + }); + + describe('slashes', () => { + test('should match forward slashes', () => { + expect_truthy(mm.isMatch('/', '/')); + }); + + test('should match backslashes', () => { + expect_truthy(mm.isMatch('\\', '[\\\\/]')); + expect_truthy(mm.isMatch('\\', '[\\\\/]+')); + expect_truthy(mm.isMatch('\\\\', '[\\\\/]+')); + expect_truthy(mm.isMatch('\\\\\\', '[\\\\/]+')); + + if (isWindows()) { + mm(['\\'], '[\\\\/]', ['/']); + mm(['\\', '\\\\', '\\\\\\'], '[\\\\/]+', ['/']); + } else { + mm(['\\'], '[\\\\/]', ['\\']); + mm(['\\', '\\\\', '\\\\\\'], '[\\\\/]+', ['\\', '\\\\', '\\\\\\']); + } + + (path as any).sep = '\\'; + expect_truthy(mm.isMatch('\\', '[\\\\/]')); + expect_truthy(mm.isMatch('\\', '[\\\\/]+')); + expect_truthy(mm.isMatch('\\\\', '[\\\\/]+')); + expect_truthy(mm.isMatch('\\\\\\', '[\\\\/]+')); + mm(['\\'], '[\\\\/]', ['/']); + mm(['\\', '\\\\', '\\\\\\'], '[\\\\/]+', ['/']); + (path as any).sep = process.env.ORIGINAL_PATH_SEP; + }); + }); + + describe('colons and drive letters', () => { + test('should treat common URL characters as literals', () => { + expect_truthy(mm.isMatch(':', ':')); + expect_truthy(mm.isMatch(':/foo', ':/*')); + expect_truthy(mm.isMatch('D://foo', 'D://*')); + expect_truthy(mm.isMatch('D://foo', 'D:\\/\\/*')); + }); + }); + + describe('[ab] - brackets:', () => { + test('should support regex character classes:', () => { + expect_deepEqual(mm(['a/b.md', 'a/c.md', 'a/d.md', 'a/E.md'], 'a/[A-Z].md'), ['a/E.md']); + expect_deepEqual(mm(['a/b.md', 'a/c.md', 'a/d.md'], 'a/[bd].md'), ['a/b.md', 'a/d.md']); + expect_deepEqual(mm(['a-1.md', 'a-2.md', 'a-3.md', 'a-4.md', 'a-5.md'], 'a-[2-4].md'), ['a-2.md', 'a-3.md', 'a-4.md']); + expect_deepEqual(mm(['a/b.md', 'b/b.md', 'c/b.md', 'b/c.md', 'a/d.md'], '[bc]/[bd].md'), ['b/b.md', 'c/b.md']); + }); + + test('should handle brackets', () => { + expect_deepEqual(mm(['ab', 'ac', 'ad', 'a*', '*'], '[a*]*', { regex: true }), ['a*', '*']); + expect_deepEqual(mm(['ab', 'ac', 'ad', 'a*', '*'], '[a*]*'), ['ab', 'ac', 'ad', 'a*', '*']); + }); + + test('should handle unclosed brackets', () => { + expect_deepEqual(mm(['[!ab', '[ab'], '[!a*'), ['[!ab']); + }); + }); + + describe('(a|b) - logical OR:', () => { + test('should support regex logical OR:', () => { + expect_deepEqual(mm(['a/a', 'a/b', 'a/c', 'b/a', 'b/b'], '(a|b)/b'), ['a/b', 'b/b']); + expect_deepEqual(mm(['a/a', 'a/b', 'a/c', 'b/a', 'b/b', 'c/b'], '((a|b)|c)/b'), ['a/b', 'b/b', 'c/b']); + expect_deepEqual(mm(['a/b.md', 'a/c.md', 'a/d.md'], 'a/(b|d).md'), ['a/b.md', 'a/d.md']); + expect_deepEqual(mm(['a-1.md', 'a-2.md', 'a-3.md', 'a-4.md', 'a-5.md'], 'a-(2|3|4).md'), ['a-2.md', 'a-3.md', 'a-4.md']); + expect_deepEqual(mm(['a/b.md', 'b/b.md', 'c/b.md', 'b/c.md', 'a/d.md'], '(b|c)/(b|d).md'), ['b/b.md', 'c/b.md']); + }); + }); + + describe('dollar $', () => { + test('should match dollar signs', () => { + expect_truthy(!isMatch('$', '!($)')); + expect_truthy(!isMatch('$', '!$')); + expect_truthy(isMatch('$$', '!$')); + expect_truthy(isMatch('$$', '!($)')); + expect_truthy(isMatch('$$$', '!($)')); + expect_truthy(isMatch('^', '!($)')); + + expect_truthy(isMatch('$', '!($$)')); + expect_truthy(!isMatch('$$', '!($$)')); + expect_truthy(isMatch('$$$', '!($$)')); + expect_truthy(isMatch('^', '!($$)')); + + expect_truthy(!isMatch('$', '!($*)')); + expect_truthy(!isMatch('$$', '!($*)')); + expect_truthy(!isMatch('$$$', '!($*)')); + expect_truthy(isMatch('^', '!($*)')); + + expect_truthy(isMatch('$', '*')); + expect_truthy(isMatch('$$', '*')); + expect_truthy(isMatch('$$$', '*')); + expect_truthy(isMatch('^', '*')); + + expect_truthy(isMatch('$', '$*')); + expect_truthy(isMatch('$$', '$*')); + expect_truthy(isMatch('$$$', '$*')); + expect_truthy(!isMatch('^', '$*')); + + expect_truthy(isMatch('$', '*$*')); + expect_truthy(isMatch('$$', '*$*')); + expect_truthy(isMatch('$$$', '*$*')); + expect_truthy(!isMatch('^', '*$*')); + + expect_truthy(isMatch('$', '*$')); + expect_truthy(isMatch('$$', '*$')); + expect_truthy(isMatch('$$$', '*$')); + expect_truthy(!isMatch('^', '*$')); + + expect_truthy(!isMatch('$', '?$')); + expect_truthy(isMatch('$$', '?$')); + expect_truthy(!isMatch('$$$', '?$')); + expect_truthy(!isMatch('^', '?$')); + }); + }); + + describe('caret ^', () => { + test('should match carets', () => { + expect_truthy(isMatch('^', '^')); + expect_truthy(isMatch('^/foo', '^/*')); + expect_truthy(isMatch('^/foo', '^/*')); + expect_truthy(isMatch('foo^', '*^')); + expect_truthy(isMatch('^foo/foo', '^foo/*')); + expect_truthy(isMatch('foo^/foo', 'foo^/*')); + + expect_truthy(!isMatch('^', '!(^)')); + expect_truthy(isMatch('^^', '!(^)')); + expect_truthy(isMatch('^^^', '!(^)')); + expect_truthy(isMatch('&', '!(^)')); + + expect_truthy(isMatch('^', '!(^^)')); + expect_truthy(!isMatch('^^', '!(^^)')); + expect_truthy(isMatch('^^^', '!(^^)')); + expect_truthy(isMatch('&', '!(^^)')); + + expect_truthy(!isMatch('^', '!(^*)')); + expect_truthy(!isMatch('^^', '!(^*)')); + expect_truthy(!isMatch('^^^', '!(^*)')); + expect_truthy(isMatch('&', '!(^*)')); + + expect_truthy(isMatch('^', '*')); + expect_truthy(isMatch('^^', '*')); + expect_truthy(isMatch('^^^', '*')); + expect_truthy(isMatch('&', '*')); + + expect_truthy(isMatch('^', '^*')); + expect_truthy(isMatch('^^', '^*')); + expect_truthy(isMatch('^^^', '^*')); + expect_truthy(!isMatch('&', '^*')); + + expect_truthy(isMatch('^', '*^*')); + expect_truthy(isMatch('^^', '*^*')); + expect_truthy(isMatch('^^^', '*^*')); + expect_truthy(!isMatch('&', '*^*')); + + expect_truthy(isMatch('^', '*^')); + expect_truthy(isMatch('^^', '*^')); + expect_truthy(isMatch('^^^', '*^')); + expect_truthy(!isMatch('&', '*^')); + + expect_truthy(!isMatch('^', '?^')); + expect_truthy(isMatch('^^', '?^')); + expect_truthy(!isMatch('^^^', '?^')); + expect_truthy(!isMatch('&', '?^')); + }); + }); +}); diff --git a/packages/node-utils/test/micromatch/stars.test.ts b/packages/node-utils/test/micromatch/stars.test.ts new file mode 100644 index 0000000..5795684 --- /dev/null +++ b/packages/node-utils/test/micromatch/stars.test.ts @@ -0,0 +1,411 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test"; +import path from "node:path"; +import micromatch from "../../src/micromatch/index.ts"; + +const before = beforeAll; +const after = afterAll; + +// All helpers accept an optional message arg (matching Node's assert API) +// even though we don't surface it — Bun's expect() builds its own diagnostic. +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_loose_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(true); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_notDeepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).not.toEqual(expected as any); +}; +const expect_notEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual == expected).toBe(false); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = micromatch; + +describe('stars', () => { + describe('single stars', () => { + test('should match using one consecutive star', () => { + expect_truthy(!isMatch('a/b/c/z.js', '*.js')); + expect_truthy(!isMatch('a/b/z.js', '*.js')); + expect_truthy(!isMatch('a/z.js', '*.js')); + expect_truthy(isMatch('a/z.js', '*/z*.js')); + expect_truthy(isMatch('a/z.js', 'a/z*.js')); + expect_truthy(isMatch('ab', '*')); + expect_truthy(isMatch('abc', '*')); + expect_truthy(isMatch('abc', '*c')); + expect_truthy(isMatch('abc', 'a*')); + expect_truthy(isMatch('abc', 'a*c')); + expect_truthy(isMatch('abc', 'abc')); + expect_truthy(isMatch('one abc two', '*abc*')); + expect_truthy(isMatch('oneabctwo', '*abc*')); + expect_truthy(isMatch('z.js', '*.js')); + expect_truthy(isMatch('z.js', 'z*.js')); + }); + + test('should support multiple non-consecutive stars in a path segment', () => { + expect_truthy(!isMatch('a-b.c-d', '*-bc-*')); + expect_truthy(isMatch('a-b.c-d', '*-*.*-*')); + expect_truthy(isMatch('a-b.c-d', '*-b*c-*')); + expect_truthy(isMatch('a-b.c-d', '*-b.c-*')); + expect_truthy(isMatch('a-b.c-d', '*.*')); + expect_truthy(isMatch('a-b.c-d', '*.*-*')); + expect_truthy(isMatch('a-b.c-d', '*.*-d')); + expect_truthy(isMatch('a-b.c-d', '*.c-*')); + expect_truthy(isMatch('a-b.c-d', '*b.*d')); + expect_truthy(isMatch('a-b.c-d', 'a*.c*')); + expect_truthy(isMatch('a-b.c-d', 'a-*.*-d')); + expect_truthy(isMatch('a.b', '*.*')); + expect_truthy(isMatch('a.b', '*.b')); + expect_truthy(isMatch('a.b', 'a.*')); + expect_truthy(isMatch('a.b', 'a.b')); + }); + + test('should support stars following brackets', () => { + expect_truthy(isMatch('a', '[a]*')); + expect_truthy(isMatch('aa', '[a]*')); + expect_truthy(isMatch('aaa', '[a]*')); + expect_truthy(isMatch('az', '[a-z]*')); + expect_truthy(isMatch('zzz', '[a-z]*')); + }); + + test('should support stars following parens', () => { + expect_truthy(isMatch('a', '(a)*')); + expect_truthy(isMatch('ab', '(a|b)*')); + expect_truthy(isMatch('aa', '(a)*')); + expect_truthy(isMatch('aaab', '(a|b)*')); + expect_truthy(isMatch('aaabbb', '(a|b)*')); + }); + + test('should not match slashes with single stars', () => { + expect_truthy(!isMatch('a/b', '(a)*')); + expect_truthy(!isMatch('a/b', '[a]*')); + expect_truthy(!isMatch('a/b', 'a*')); + expect_truthy(!isMatch('a/b', '(a|b)*')); + }); + + test('should return true when one of the given patterns matches the string', () => { + expect_truthy(isMatch('/ab', '*/*')); + expect_truthy(isMatch('.', '.')); + expect_truthy(!isMatch('a/.b', 'a/')); + expect_truthy(isMatch('/ab', '/*')); + expect_truthy(isMatch('/ab', '/??')); + expect_truthy(isMatch('/ab', '/?b')); + expect_truthy(isMatch('/cd', '/*')); + expect_truthy(isMatch('a', 'a')); + expect_truthy(isMatch('a/.b', 'a/.*')); + expect_truthy(isMatch('a/b', '?/?')); + expect_truthy(isMatch('a/b/c/d/e/j/n/p/o/z/c.md', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('a/b/c/d/e/z/c.md', 'a/**/z/*.md')); + expect_truthy(isMatch('a/b/c/xyz.md', 'a/b/c/*.md')); + expect_truthy(isMatch('a/b/c/xyz.md', 'a/b/c/*.md')); + expect_truthy(isMatch('a/b/z/.a', 'a/*/z/.a')); + expect_truthy(!isMatch('a/b/z/.a', 'bz')); + expect_truthy(isMatch('a/bb.bb/aa/b.b/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(isMatch('a/bb.bb/aa/bb/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(isMatch('a/bb.bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bbbb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('aaa', '*')); + expect_truthy(isMatch('ab', '*')); + expect_truthy(isMatch('ab', './*')); + expect_truthy(isMatch('ab', 'ab')); + expect_truthy(isMatch('ab/', './*/')); + }); + + test('should return false when the path does not match the pattern', () => { + expect_truthy(!isMatch('/ab', ['*/'])); + expect_truthy(!isMatch('/ab', ['*/a'])); + expect_truthy(!isMatch('/ab', ['/'])); + expect_truthy(!isMatch('/ab', ['/?'])); + expect_truthy(!isMatch('/ab', ['/a'])); + expect_truthy(!isMatch('/ab', ['?/?'])); + expect_truthy(!isMatch('/ab', ['a/*'])); + expect_truthy(!isMatch('a/.b', ['a/'])); + expect_truthy(!isMatch('a/b/c', ['a/*'])); + expect_truthy(!isMatch('a/b/c', ['a/b'])); + expect_truthy(!isMatch('a/b/c/d/e/z/c.md', ['b/c/d/e'])); + expect_truthy(!isMatch('a/b/z/.a', ['b/z'])); + expect_truthy(!isMatch('ab', ['*/*'])); + expect_truthy(!isMatch('ab', ['/a'])); + expect_truthy(!isMatch('ab', ['a'])); + expect_truthy(!isMatch('ab', ['b'])); + expect_truthy(!isMatch('ab', ['c'])); + expect_truthy(!isMatch('abcd', ['ab'])); + expect_truthy(!isMatch('abcd', ['bc'])); + expect_truthy(!isMatch('abcd', ['c'])); + expect_truthy(!isMatch('abcd', ['cd'])); + expect_truthy(!isMatch('abcd', ['d'])); + expect_truthy(!isMatch('abcd', ['f'])); + expect_truthy(!isMatch('ef', ['/*'])); + }); + + test('should match a path segment for each single star', () => { + expect_truthy(!isMatch('aaa', '*/*/*')); + expect_truthy(!isMatch('aaa/bb/aa/rr', '*/*/*')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa*')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa**')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/*')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/*ccc')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/*z')); + expect_truthy(!isMatch('aaa/bbb', '*/*/*')); + expect_truthy(!isMatch('ab/zzz/ejkl/hi', '*/*jk*/*i')); + expect_truthy(isMatch('aaa/bba/ccc', '*/*/*')); + expect_truthy(isMatch('aaa/bba/ccc', 'aaa/**')); + expect_truthy(isMatch('aaa/bbb', 'aaa/*')); + expect_truthy(isMatch('ab/zzz/ejkl/hi', '*/*z*/*/*i')); + expect_truthy(isMatch('abzzzejklhi', '*j*i')); + }); + + test('should match any character besides "/" with a single "*"', () => { + expect_truthy(isMatch('foo', 'f*')); + expect_truthy(!isMatch('foo', 'b*')); + expect_truthy(!isMatch('bar', 'f*')); + expect_truthy(isMatch('bar', 'b*')); + }); + + test('should support single globs (*)', () => { + expect_truthy(isMatch('a', '*')); + expect_truthy(isMatch('b', '*')); + expect_truthy(!isMatch('a/a', '*')); + expect_truthy(!isMatch('a/a/a', '*')); + expect_truthy(!isMatch('a/a/b', '*')); + expect_truthy(!isMatch('a/a/a/a', '*')); + expect_truthy(!isMatch('a/a/a/a/a', '*')); + + expect_truthy(!isMatch('a', '*/*')); + expect_truthy(isMatch('a/a', '*/*')); + expect_truthy(!isMatch('a/a/a', '*/*')); + + expect_truthy(!isMatch('a', '*/*/*')); + expect_truthy(!isMatch('a/a', '*/*/*')); + expect_truthy(isMatch('a/a/a', '*/*/*')); + expect_truthy(!isMatch('a/a/a/a', '*/*/*')); + + expect_truthy(!isMatch('a', '*/*/*/*')); + expect_truthy(!isMatch('a/a', '*/*/*/*')); + expect_truthy(!isMatch('a/a/a', '*/*/*/*')); + expect_truthy(isMatch('a/a/a/a', '*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*/*/*')); + + expect_truthy(!isMatch('a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/b', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a', '*/*/*/*/*')); + expect_truthy(isMatch('a/a/a/a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a/a', '*/*/*/*/*')); + + expect_truthy(!isMatch('a', 'a/*')); + expect_truthy(isMatch('a/a', 'a/*')); + expect_truthy(!isMatch('a/a/a', 'a/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*')); + + expect_truthy(!isMatch('a', 'a/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/*')); + expect_truthy(isMatch('a/a/a', 'a/*/*')); + expect_truthy(!isMatch('b/a/a', 'a/*/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/*')); + + expect_truthy(!isMatch('a', 'a/*/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/*/*')); + expect_truthy(!isMatch('a/a/a', 'a/*/*/*')); + expect_truthy(isMatch('a/a/a/a', 'a/*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/*/*')); + + expect_truthy(!isMatch('a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/b', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/*/*/*')); + expect_truthy(isMatch('a/a/a/a/a', 'a/*/*/*/*')); + + expect_truthy(!isMatch('a', 'a/*/a')); + expect_truthy(!isMatch('a/a', 'a/*/a')); + expect_truthy(isMatch('a/a/a', 'a/*/a')); + expect_truthy(!isMatch('a/a/b', 'a/*/a')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/a')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/a')); + + expect_truthy(!isMatch('a', 'a/*/b')); + expect_truthy(!isMatch('a/a', 'a/*/b')); + expect_truthy(!isMatch('a/a/a', 'a/*/b')); + expect_truthy(isMatch('a/a/b', 'a/*/b')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/b')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/b')); + }); + + test('should only match a single folder per star when globstars are used', () => { + expect_truthy(!isMatch('a', '*/**/a')); + expect_truthy(!isMatch('a/a/b', '*/**/a')); + expect_truthy(isMatch('a/a', '*/**/a')); + expect_truthy(isMatch('a/a/a', '*/**/a')); + expect_truthy(isMatch('a/a/a/a', '*/**/a')); + expect_truthy(isMatch('a/a/a/a/a', '*/**/a')); + }); + + test('should optionally match a trailing slash when single star is last char', () => { + expect_truthy(isMatch('a', '*')); + expect_truthy(isMatch('a/', '*{,/}')); + expect_truthy(!isMatch('a/a', '*')); + expect_truthy(!isMatch('a/b', '*')); + expect_truthy(!isMatch('a/c', '*')); + expect_truthy(!isMatch('a/x', '*')); + expect_truthy(!isMatch('a/x/y', '*')); + expect_truthy(!isMatch('a/x/y/z', '*')); + + expect_truthy(!isMatch('a', '*/')); + expect_truthy(isMatch('a/', '*/')); + expect_truthy(!isMatch('a/a', '*/')); + expect_truthy(!isMatch('a/b', '*/')); + expect_truthy(!isMatch('a/c', '*/')); + expect_truthy(!isMatch('a/x', '*/')); + expect_truthy(!isMatch('a/x/y', '*/')); + expect_truthy(!isMatch('a/x/y/z', '*/')); + + expect_truthy(!isMatch('a', '*/*')); + expect_truthy(!isMatch('a/', '*/*')); + expect_truthy(isMatch('a/a', '*/*')); + expect_truthy(isMatch('a/b', '*/*')); + expect_truthy(isMatch('a/c', '*/*')); + expect_truthy(isMatch('a/x', '*/*')); + expect_truthy(!isMatch('a/x/y', '*/*')); + expect_truthy(!isMatch('a/x/y/z', '*/*')); + + expect_truthy(!isMatch('a', 'a/*')); + expect_truthy(!isMatch('a/', 'a/*')); + expect_truthy(isMatch('a/a', 'a/*')); + expect_truthy(isMatch('a/b', 'a/*')); + expect_truthy(isMatch('a/c', 'a/*')); + expect_truthy(isMatch('a/x', 'a/*')); + expect_truthy(!isMatch('a/x/y', 'a/*')); + expect_truthy(!isMatch('a/x/y/z', 'a/*')); + }); + + test('should support globstars (**)', () => { + expect_truthy(isMatch('a', '**')); + expect_truthy(isMatch('a/', '**')); + expect_truthy(isMatch('a/a', '**')); + expect_truthy(isMatch('a/b', '**')); + expect_truthy(isMatch('a/c', '**')); + expect_truthy(isMatch('a/x', '**')); + expect_truthy(isMatch('a/x/y', '**')); + expect_truthy(isMatch('a/x/y/z', '**')); + + expect_truthy(!isMatch('a/', '**/a')); + expect_truthy(!isMatch('a/b', '**/a')); + expect_truthy(!isMatch('a/c', '**/a')); + expect_truthy(!isMatch('a/x', '**/a')); + expect_truthy(!isMatch('a/x/y', '**/a')); + expect_truthy(!isMatch('a/x/y/z', '**/a')); + expect_truthy(isMatch('a', '**/a')); + expect_truthy(isMatch('a/a', '**/a')); + + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(isMatch('a/', 'a/**')); + expect_truthy(isMatch('a/a', 'a/**')); + expect_truthy(isMatch('a/b', 'a/**')); + expect_truthy(isMatch('a/c', 'a/**')); + expect_truthy(isMatch('a/x', 'a/**')); + expect_truthy(isMatch('a/x/y', 'a/**')); + expect_truthy(isMatch('a/x/y/z', 'a/**')); + + expect_truthy(!isMatch('a', 'a/**/*')); + expect_truthy(!isMatch('a/', 'a/**/*')); + expect_truthy(isMatch('a/a', 'a/**/*')); + expect_truthy(isMatch('a/b', 'a/**/*')); + expect_truthy(isMatch('a/c', 'a/**/*')); + expect_truthy(isMatch('a/x', 'a/**/*')); + expect_truthy(isMatch('a/x/y', 'a/**/*')); + expect_truthy(isMatch('a/x/y/z', 'a/**/*')); + + expect_truthy(!isMatch('a', 'a/**/**/*')); + expect_truthy(!isMatch('a/', 'a/**/**/*')); + expect_truthy(isMatch('a/a', 'a/**/**/*')); + expect_truthy(isMatch('a/b', 'a/**/**/*')); + expect_truthy(isMatch('a/c', 'a/**/**/*')); + expect_truthy(isMatch('a/x', 'a/**/**/*')); + expect_truthy(isMatch('a/x/y', 'a/**/**/*')); + expect_truthy(isMatch('a/x/y/z', 'a/**/**/*')); + + expect_truthy(!isMatch('a', 'a/**/**/**/*')); + expect_truthy(!isMatch('a/', 'a/**/**/**/*')); + expect_truthy(isMatch('a/a', 'a/**/**/**/*')); + expect_truthy(isMatch('a/b', 'a/**/**/**/*')); + expect_truthy(isMatch('a/c', 'a/**/**/**/*')); + expect_truthy(isMatch('a/x', 'a/**/**/**/*')); + expect_truthy(isMatch('a/x/y', 'a/**/**/**/*')); + expect_truthy(isMatch('a/x/y/z', 'a/**/**/**/*')); + + expect_truthy(isMatch('a/b/foo/bar/baz.qux', 'a/b/**/bar/**/*.*')); + expect_truthy(isMatch('a/b/bar/baz.qux', 'a/b/**/bar/**/*.*')); + }); + + test('should work with file extensions', () => { + expect_truthy(!isMatch('a.txt', 'a/**/*.txt')); + expect_truthy(isMatch('a/b.txt', 'a/**/*.txt')); + expect_truthy(isMatch('a/x/y.txt', 'a/**/*.txt')); + expect_truthy(!isMatch('a/x/y/z', 'a/**/*.txt')); + + expect_truthy(!isMatch('a.txt', 'a/*.txt')); + expect_truthy(isMatch('a/b.txt', 'a/*.txt')); + expect_truthy(!isMatch('a/x/y.txt', 'a/*.txt')); + expect_truthy(!isMatch('a/x/y/z', 'a/*.txt')); + + expect_truthy(isMatch('a.txt', 'a*.txt')); + expect_truthy(!isMatch('a/b.txt', 'a*.txt')); + expect_truthy(!isMatch('a/x/y.txt', 'a*.txt')); + expect_truthy(!isMatch('a/x/y/z', 'a*.txt')); + + expect_truthy(isMatch('a.txt', '*.txt')); + expect_truthy(!isMatch('a/b.txt', '*.txt')); + expect_truthy(!isMatch('a/x/y.txt', '*.txt')); + expect_truthy(!isMatch('a/x/y/z', '*.txt')); + }); + + test('should correctly match slashes', () => { + expect_truthy(!isMatch('a/a/bb', 'a/**/b')); + expect_truthy(!isMatch('a/bb', 'a/**/b')); + expect_truthy(!isMatch('foo', '*/**')); + expect_truthy(!isMatch('foo/bar', '**/')); + expect_truthy(!isMatch('foo/bar', '**/*/')); + expect_truthy(!isMatch('foo/bar', '*/*/')); + expect_truthy(!isMatch('foo/bar/', '**/*', { strictSlashes: true })); + expect_truthy(isMatch('/home/foo/..', '**/..')); + expect_truthy(isMatch('a/a', '*/**/a')); + expect_truthy(isMatch('foo/', '*/**')); + expect_truthy(isMatch('foo/bar', '**/*')); + expect_truthy(isMatch('foo/bar', '*/*')); + expect_truthy(isMatch('foo/bar', '*/**')); + expect_truthy(isMatch('foo/bar/', '**/')); + expect_truthy(isMatch('foo/bar/', '**/*')); + expect_truthy(isMatch('foo/bar/', '**/*/')); + expect_truthy(isMatch('foo/bar/', '*/**')); + expect_truthy(isMatch('foo/bar/', '*/*/')); + }); + + test('should optionally match trailing slashes with braces', () => { + expect_truthy(isMatch('foo', '**/*')); + expect_truthy(isMatch('foo', '**/*{,/}')); + expect_truthy(isMatch('foo/', '**/*{,/}')); + expect_truthy(isMatch('foo/bar', '**/*{,/}')); + expect_truthy(isMatch('foo/bar/', '**/*{,/}')); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/api.picomatch.test.ts b/packages/node-utils/test/picomatch/api.picomatch.test.ts new file mode 100644 index 0000000..bb25fa3 --- /dev/null +++ b/packages/node-utils/test/picomatch/api.picomatch.test.ts @@ -0,0 +1,409 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +const assertTokens = (actual, expected) => { + const keyValuePairs = actual.map(token => [token.type, token.value]); + expect_deepEqual(keyValuePairs, expected); +}; + +describe('picomatch', () => { + describe('validation', () => { + test('should throw an error when invalid arguments are given', () => { + expect_throws(() => isMatch('foo', ''), /Expected pattern to be a non-empty string/); + expect_throws(() => isMatch('foo', null), /Expected pattern to be a non-empty string/); + }); + }); + + describe('multiple patterns', () => { + test('should return true when any of the patterns match', () => { + expect_truthy(isMatch('.', ['.', 'foo'])); + expect_truthy(isMatch('a', ['a', 'foo'])); + expect_truthy(isMatch('ab', ['*', 'foo', 'bar'])); + expect_truthy(isMatch('ab', ['*b', 'foo', 'bar'])); + expect_truthy(isMatch('ab', ['./*', 'foo', 'bar'])); + expect_truthy(isMatch('ab', ['a*', 'foo', 'bar'])); + expect_truthy(isMatch('ab', ['ab', 'foo'])); + }); + + test('should return false when none of the patterns match', () => { + expect_truthy(!isMatch('/ab', ['/a', 'foo'])); + expect_truthy(!isMatch('/ab', ['?/?', 'foo', 'bar'])); + expect_truthy(!isMatch('/ab', ['a/*', 'foo', 'bar'])); + expect_truthy(!isMatch('a/b/c', ['a/b', 'foo'])); + expect_truthy(!isMatch('ab', ['*/*', 'foo', 'bar'])); + expect_truthy(!isMatch('ab', ['/a', 'foo', 'bar'])); + expect_truthy(!isMatch('ab', ['a', 'foo'])); + expect_truthy(!isMatch('ab', ['b', 'foo'])); + expect_truthy(!isMatch('ab', ['c', 'foo', 'bar'])); + expect_truthy(!isMatch('abcd', ['ab', 'foo'])); + expect_truthy(!isMatch('abcd', ['bc', 'foo'])); + expect_truthy(!isMatch('abcd', ['c', 'foo'])); + expect_truthy(!isMatch('abcd', ['cd', 'foo'])); + expect_truthy(!isMatch('abcd', ['d', 'foo'])); + expect_truthy(!isMatch('abcd', ['f', 'foo', 'bar'])); + expect_truthy(!isMatch('ef', ['/*', 'foo', 'bar'])); + }); + }); + + describe('file extensions', () => { + test('should match files that contain the given extension:', () => { + expect_truthy(!isMatch('.c.md', '*.md')); + expect_truthy(!isMatch('.c.md', '.c.')); + expect_truthy(!isMatch('.c.md', '.md')); + expect_truthy(!isMatch('.md', '*.md')); + expect_truthy(!isMatch('.md', '.m')); + expect_truthy(!isMatch('a/b/c.md', '*.md')); + expect_truthy(!isMatch('a/b/c.md', '.md')); + expect_truthy(!isMatch('a/b/c.md', 'a/*.md')); + expect_truthy(!isMatch('a/b/c/c.md', '*.md')); + expect_truthy(!isMatch('a/b/c/c.md', 'c.js')); + expect_truthy(isMatch('.c.md', '.*.md')); + expect_truthy(isMatch('.md', '.md')); + expect_truthy(isMatch('a/b/c.js', 'a/**/*.*')); + expect_truthy(isMatch('a/b/c.md', '**/*.md')); + expect_truthy(isMatch('a/b/c.md', 'a/*/*.md')); + expect_truthy(isMatch('c.md', '*.md')); + }); + }); + + describe('dot files', () => { + test('should not match dotfiles when a leading dot is not defined in a path segment', () => { + expect_truthy(!isMatch('.a', '(a)*')); + expect_truthy(!isMatch('.a', '*(a|b)')); + expect_truthy(!isMatch('.a', '*.md')); + expect_truthy(!isMatch('.a', '*[a]')); + expect_truthy(!isMatch('.a', '*[a]*')); + expect_truthy(!isMatch('.a', '*a')); + expect_truthy(!isMatch('.a', '*a*')); + expect_truthy(!isMatch('.a.md', 'a/b/c/*.md')); + expect_truthy(!isMatch('.ab', '*.*')); + expect_truthy(!isMatch('.abc', '.a')); + expect_truthy(!isMatch('.ba', '.a')); + expect_truthy(!isMatch('.c.md', '*.md')); + expect_truthy(!isMatch('.md', 'a/b/c/*.md')); + expect_truthy(!isMatch('.txt', '.md')); + expect_truthy(!isMatch('.verb.txt', '*.md')); + expect_truthy(!isMatch('a/.c.md', '*.md')); + expect_truthy(!isMatch('a/b/d/.md', 'a/b/c/*.md')); + expect_truthy(isMatch('.a', '.a')); + expect_truthy(isMatch('.ab', '.*')); + expect_truthy(isMatch('.ab', '.a*')); + expect_truthy(isMatch('.b', '.b*')); + expect_truthy(isMatch('.md', '.md')); + expect_truthy(isMatch('a/.c.md', 'a/.c.md')); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/.*.md')); + expect_truthy(isMatch('a/b/c/d.a.md', 'a/b/c/*.md')); + }); + + test('should match dotfiles when options.dot is true', () => { + expect_truthy(!isMatch('a/b/c/.xyz.md', '.*.md', { dot: true })); + expect_truthy(isMatch('.c.md', '*.md', { dot: true })); + expect_truthy(isMatch('.c.md', '.*', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', '**/*.md', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', '**/.*.md', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/*.md', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/.*.md', { dot: true })); + }); + }); + + describe('matching:', () => { + test('should escape plus signs to match string literals', () => { + expect_truthy(isMatch('a+b/src/glimini.js', 'a+b/src/*.js')); + expect_truthy(isMatch('+b/src/glimini.js', '+b/src/*.js')); + expect_truthy(isMatch('coffee+/src/glimini.js', 'coffee+/src/*.js')); + expect_truthy(isMatch('coffee+/src/glimini.js', 'coffee+/src/*')); + }); + + test('should match with non-glob patterns', () => { + expect_truthy(isMatch('.', '.')); + expect_truthy(isMatch('/a', '/a')); + expect_truthy(!isMatch('/ab', '/a')); + expect_truthy(isMatch('a', 'a')); + expect_truthy(!isMatch('ab', '/a')); + expect_truthy(!isMatch('ab', 'a')); + expect_truthy(isMatch('ab', 'ab')); + expect_truthy(!isMatch('abcd', 'cd')); + expect_truthy(!isMatch('abcd', 'bc')); + expect_truthy(!isMatch('abcd', 'ab')); + }); + + test('should match file names', () => { + expect_truthy(isMatch('a.b', 'a.b')); + expect_truthy(isMatch('a.b', '*.b')); + expect_truthy(isMatch('a.b', 'a.*')); + expect_truthy(isMatch('a.b', '*.*')); + expect_truthy(isMatch('a-b.c-d', 'a*.c*')); + expect_truthy(isMatch('a-b.c-d', '*b.*d')); + expect_truthy(isMatch('a-b.c-d', '*.*')); + expect_truthy(isMatch('a-b.c-d', '*.*-*')); + expect_truthy(isMatch('a-b.c-d', '*-*.*-*')); + expect_truthy(isMatch('a-b.c-d', '*.c-*')); + expect_truthy(isMatch('a-b.c-d', '*.*-d')); + expect_truthy(isMatch('a-b.c-d', 'a-*.*-d')); + expect_truthy(isMatch('a-b.c-d', '*-b.c-*')); + expect_truthy(isMatch('a-b.c-d', '*-b*c-*')); + expect_truthy(!isMatch('a-b.c-d', '*-bc-*')); + }); + + test('should match with common glob patterns', () => { + expect_truthy(!isMatch('/ab', './*/')); + expect_truthy(!isMatch('/ef', '*')); + expect_truthy(!isMatch('ab', './*/')); + expect_truthy(!isMatch('ef', '/*')); + expect_truthy(isMatch('/ab', '/*')); + expect_truthy(isMatch('/cd', '/*')); + expect_truthy(isMatch('ab', '*')); + expect_truthy(isMatch('ab', './*')); + expect_truthy(isMatch('ab', 'ab')); + expect_truthy(isMatch('ab/', './*/')); + }); + + test('should match files with the given extension', () => { + expect_truthy(!isMatch('.md', '*.md')); + expect_truthy(isMatch('.md', '.md')); + expect_truthy(!isMatch('.c.md', '*.md')); + expect_truthy(isMatch('.c.md', '.*.md')); + expect_truthy(isMatch('c.md', '*.md')); + expect_truthy(isMatch('c.md', '*.md')); + expect_truthy(!isMatch('a/b/c/c.md', '*.md')); + expect_truthy(!isMatch('a/b/c.md', 'a/*.md')); + expect_truthy(isMatch('a/b/c.md', 'a/*/*.md')); + expect_truthy(isMatch('a/b/c.md', '**/*.md')); + expect_truthy(isMatch('a/b/c.js', 'a/**/*.*')); + }); + + test('should match wildcards', () => { + expect_truthy(!isMatch('a/b/c/z.js', '*.js')); + expect_truthy(!isMatch('a/b/z.js', '*.js')); + expect_truthy(!isMatch('a/z.js', '*.js')); + expect_truthy(isMatch('z.js', '*.js')); + + expect_truthy(isMatch('z.js', 'z*.js')); + expect_truthy(isMatch('a/z.js', 'a/z*.js')); + expect_truthy(isMatch('a/z.js', '*/z*.js')); + }); + + test('should match globstars', () => { + expect_truthy(isMatch('a/b/c/z.js', '**/*.js')); + expect_truthy(isMatch('a/b/z.js', '**/*.js')); + expect_truthy(isMatch('a/z.js', '**/*.js')); + expect_truthy(isMatch('a/b/c/d/e/z.js', 'a/b/**/*.js')); + expect_truthy(isMatch('a/b/c/d/z.js', 'a/b/**/*.js')); + expect_truthy(isMatch('a/b/c/z.js', 'a/b/c/**/*.js')); + expect_truthy(isMatch('a/b/c/z.js', 'a/b/c**/*.js')); + expect_truthy(isMatch('a/b/c/z.js', 'a/b/**/*.js')); + expect_truthy(isMatch('a/b/z.js', 'a/b/**/*.js')); + + expect_truthy(!isMatch('a/z.js', 'a/b/**/*.js')); + expect_truthy(!isMatch('z.js', 'a/b/**/*.js')); + + // https://github.com/micromatch/micromatch/issues/15 + expect_truthy(isMatch('z.js', 'z*')); + expect_truthy(isMatch('z.js', '**/z*')); + expect_truthy(isMatch('z.js', '**/z*.js')); + expect_truthy(isMatch('z.js', '**/*.js')); + expect_truthy(isMatch('foo', '**/foo')); + }); + + test('issue #23', () => { + expect_truthy(!isMatch('zzjs', 'z*.js')); + expect_truthy(!isMatch('zzjs', '*z.js')); + }); + + test('issue #24 - should match zero or more directories', () => { + expect_truthy(!isMatch('a/b/c/d/', 'a/b/**/f')); + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(isMatch('a', '**')); + expect_truthy(isMatch('a/', '**')); + expect_truthy(isMatch('a/b-c/d/e/z.js', 'a/b-*/**/z.js')); + expect_truthy(isMatch('a/b-c/z.js', 'a/b-*/**/z.js')); + expect_truthy(isMatch('a/b/c/d', '**')); + expect_truthy(isMatch('a/b/c/d/', '**')); + expect_truthy(isMatch('a/b/c/d/', '**/**')); + expect_truthy(isMatch('a/b/c/d/', '**/b/**')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**/')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**/c/**/')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**/c/**/d/')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/**/*.*')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/*.*')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/c/**/d/*.*')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/d/**/*.*')); + expect_truthy(isMatch('a/b/c/d/g/e.f', 'a/b/**/d/**/*.*')); + expect_truthy(isMatch('a/b/c/d/g/g/e.f', 'a/b/**/d/**/*.*')); + }); + + test('should match slashes', () => { + expect_truthy(!isMatch('bar/baz/foo', '*/foo')); + expect_truthy(!isMatch('deep/foo/bar', '**/bar/*')); + expect_truthy(!isMatch('deep/foo/bar/baz/x', '*/bar/**')); + expect_truthy(!isMatch('foo/bar', 'foo?bar')); + expect_truthy(!isMatch('foo/bar/baz', '**/bar*')); + expect_truthy(!isMatch('foo/bar/baz', '**/bar**')); + expect_truthy(!isMatch('foo/baz/bar', 'foo**bar')); + expect_truthy(!isMatch('foo/baz/bar', 'foo*bar')); + expect_truthy(!isMatch('deep/foo/bar/baz', '**/bar/*/')); + expect_truthy(!isMatch('deep/foo/bar/baz/', '**/bar/*', { strictSlashes: true })); + expect_truthy(isMatch('deep/foo/bar/baz/', '**/bar/*')); + expect_truthy(isMatch('deep/foo/bar/baz', '**/bar/*')); + expect_truthy(isMatch('foo', 'foo/**')); + expect_truthy(isMatch('deep/foo/bar/baz/', '**/bar/*{,/}')); + expect_truthy(isMatch('a/b/j/c/z/x.md', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('a/j/z/x.md', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('bar/baz/foo', '**/foo')); + expect_truthy(isMatch('deep/foo/bar/', '**/bar/**')); + expect_truthy(isMatch('deep/foo/bar/baz', '**/bar/*')); + expect_truthy(isMatch('deep/foo/bar/baz/', '**/bar/*/')); + expect_truthy(isMatch('deep/foo/bar/baz/', '**/bar/**')); + expect_truthy(isMatch('deep/foo/bar/baz/x', '**/bar/*/*')); + expect_truthy(isMatch('foo/b/a/z/bar', 'foo/**/**/bar')); + expect_truthy(isMatch('foo/b/a/z/bar', 'foo/**/bar')); + expect_truthy(isMatch('foo/bar', 'foo/**/**/bar')); + expect_truthy(isMatch('foo/bar', 'foo/**/bar')); + expect_truthy(isMatch('foo/bar', 'foo[/]bar')); + expect_truthy(isMatch('foo/bar/baz/x', '*/bar/**')); + expect_truthy(isMatch('foo/baz/bar', 'foo/**/**/bar')); + expect_truthy(isMatch('foo/baz/bar', 'foo/**/bar')); + expect_truthy(isMatch('foobazbar', 'foo**bar')); + expect_truthy(isMatch('XXX/foo', '**/foo')); + + // https://github.com/micromatch/micromatch/issues/89 + expect_truthy(isMatch('foo//baz.md', 'foo//baz.md')); + expect_truthy(isMatch('foo//baz.md', 'foo//*baz.md')); + expect_truthy(isMatch('foo//baz.md', 'foo{/,//}baz.md')); + expect_truthy(isMatch('foo/baz.md', 'foo{/,//}baz.md')); + expect_truthy(!isMatch('foo//baz.md', 'foo/+baz.md')); + expect_truthy(!isMatch('foo//baz.md', 'foo//+baz.md')); + expect_truthy(!isMatch('foo//baz.md', 'foo/baz.md')); + expect_truthy(!isMatch('foo/baz.md', 'foo//baz.md')); + }); + + test('question marks should not match slashes', () => { + expect_truthy(!isMatch('aaa/bbb', 'aaa?bbb')); + }); + + test('should not match dotfiles when `dot` or `dotfiles` are not set', () => { + expect_truthy(!isMatch('.c.md', '*.md')); + expect_truthy(!isMatch('a/.c.md', '*.md')); + expect_truthy(isMatch('a/.c.md', 'a/.c.md')); + expect_truthy(!isMatch('.a', '*.md')); + expect_truthy(!isMatch('.verb.txt', '*.md')); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/.*.md')); + expect_truthy(isMatch('.md', '.md')); + expect_truthy(!isMatch('.txt', '.md')); + expect_truthy(isMatch('.md', '.md')); + expect_truthy(isMatch('.a', '.a')); + expect_truthy(isMatch('.b', '.b*')); + expect_truthy(isMatch('.ab', '.a*')); + expect_truthy(isMatch('.ab', '.*')); + expect_truthy(!isMatch('.ab', '*.*')); + expect_truthy(!isMatch('.md', 'a/b/c/*.md')); + expect_truthy(!isMatch('.a.md', 'a/b/c/*.md')); + expect_truthy(isMatch('a/b/c/d.a.md', 'a/b/c/*.md')); + expect_truthy(!isMatch('a/b/d/.md', 'a/b/c/*.md')); + }); + + test('should match dotfiles when `dot` or `dotfiles` is set', () => { + expect_truthy(isMatch('.c.md', '*.md', { dot: true })); + expect_truthy(isMatch('.c.md', '.*', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/*.md', { dot: true })); + expect_truthy(isMatch('a/b/c/.xyz.md', 'a/b/c/.*.md', { dot: true })); + }); + }); + + describe('.parse', () => { + describe('tokens', () => { + test('should return result for pattern that matched by fastpath', () => { + const { tokens } = picomatch.parse('a*.txt'); + + const expected = [ + ['bos', ''], + ['text', 'a'], + ['star', '*'], + ['text', '.txt'] + ]; + + assertTokens(tokens, expected); + }); + + test('should return result for pattern', () => { + const { tokens } = picomatch.parse('{a,b}*'); + + const expected = [ + ['bos', ''], + ['brace', '{'], + ['text', 'a'], + ['comma', ','], + ['text', 'b'], + ['brace', '}'], + ['star', '*'], + ['maybe_slash', ''] + ]; + + assertTokens(tokens, expected); + }); + + test('pictomatch issue#125, issue#100', () => { + const { tokens } = picomatch.parse('foo.(m|c|)js'); + + const expected = [ + ['bos', { output: '', value: '' }], + ['text', { output: 'foo.', value: 'foo.' }], + ['paren', { output: undefined, value: '(' }], + ['text', { output: 'm|c|', value: 'm|c|' }], + ['paren', { output: ')', value: ')' }], + ['text', { output: undefined, value: 'js' }] + ]; + + const keyValuePairs = tokens.map(token => [token.type, { output: token.output, value: token.value }]); + expect_deepEqual(keyValuePairs, expected); + }); + }); + }); + + describe('state', () => { + describe('negatedExtglob', () => { + test('should return true', () => { + expect_truthy(picomatch('!(abc)', {}, true).state.negatedExtglob); + expect_truthy(picomatch('!(abc)**', {}, true).state.negatedExtglob); + expect_truthy(picomatch('!(abc)/**', {}, true).state.negatedExtglob); + }); + + test('should return false', () => { + expect_truthy(!picomatch('(!(abc))', {}, true).state.negatedExtglob); + expect_truthy(!picomatch('**!(abc)', {}, true).state.negatedExtglob); + }); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/api.posix.test.ts b/packages/node-utils/test/picomatch/api.posix.test.ts new file mode 100644 index 0000000..73a11eb --- /dev/null +++ b/packages/node-utils/test/picomatch/api.posix.test.ts @@ -0,0 +1,45 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + + +describe('picomatch/posix', () => { + test('should use posix paths only by default', () => { + const match = picomatch('a/**'); + expect_truthy(match('a/b')); + expect_truthy(!match('a\\b')); + }); + test('should still be manually configurable to accept non-posix paths', () => { + const match = picomatch('a/**', { windows: true }); + expect_truthy(match('a\\b')); + expect_truthy(match('a/b')); + }); +}); diff --git a/packages/node-utils/test/picomatch/api.scan.test.ts b/packages/node-utils/test/picomatch/api.scan.test.ts new file mode 100644 index 0000000..c59dca1 --- /dev/null +++ b/packages/node-utils/test/picomatch/api.scan.test.ts @@ -0,0 +1,705 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const scan = picomatch.scan; +const base = (...args) => scan(...args).base; +const both = (...args) => { + const { base, glob } = scan(...args); + return [base, glob]; +}; + +/** + * @param {String} pattern + * @param {String[]} parts + */ +function assertParts(pattern, parts) { + const info = scan(pattern, { parts: true }); + + expect_deepEqual(info.parts, parts); +} + +/** + * Most of the unit tests in this file were from https://github.com/es128/glob-parent + * and https://github.com/jonschlinkert/glob-base. Both libraries use a completely + * different approach to separating the glob pattern from the "path" from picomatch, + * and both libraries use path.dirname. Picomatch does not. + */ + +describe('picomatch', () => { + describe('.scan', () => { + test('should get the "base" and "glob" from a pattern', () => { + expect_deepEqual(both('foo/bar'), ['foo/bar', '']); + expect_deepEqual(both('foo/@bar'), ['foo/@bar', '']); + expect_deepEqual(both('foo/@bar\\+'), ['foo/@bar\\+', '']); + expect_deepEqual(both('foo/bar+'), ['foo/bar+', '']); + expect_deepEqual(both('foo/bar*'), ['foo', 'bar*']); + }); + + test('should handle leading "./"', () => { + expect_deepEqual(scan('./foo/bar/*.js'), { + input: './foo/bar/*.js', + prefix: './', + start: 2, + base: 'foo/bar', + glob: '*.js', + isBrace: false, + isBracket: false, + isGlob: true, + isGlobstar: false, + isExtglob: false, + negated: false, + negatedExtglob: false + }); + }); + + test('should detect braces', () => { + expect_deepEqual(scan('foo/{a,b,c}/*.js', { scanToEnd: true }), { + input: 'foo/{a,b,c}/*.js', + prefix: '', + start: 0, + base: 'foo', + glob: '{a,b,c}/*.js', + isBrace: true, + isBracket: false, + isGlob: true, + isGlobstar: false, + isExtglob: false, + negated: false, + negatedExtglob: false + }); + }); + + test('should detect globstars', () => { + expect_deepEqual(scan('./foo/**/*.js', { scanToEnd: true }), { + input: './foo/**/*.js', + prefix: './', + start: 2, + base: 'foo', + glob: '**/*.js', + isBrace: false, + isBracket: false, + isGlob: true, + isGlobstar: true, + isExtglob: false, + negated: false, + negatedExtglob: false + }); + }); + + test('should detect extglobs', () => { + expect_deepEqual(scan('./foo/@(foo)/*.js'), { + input: './foo/@(foo)/*.js', + prefix: './', + start: 2, + base: 'foo', + glob: '@(foo)/*.js', + isBrace: false, + isBracket: false, + isGlob: true, + isGlobstar: false, + isExtglob: true, + negated: false, + negatedExtglob: false + }); + }); + + test('should detect extglobs and globstars', () => { + expect_deepEqual(scan('./foo/@(bar)/**/*.js', { parts: true }), { + input: './foo/@(bar)/**/*.js', + prefix: './', + start: 2, + base: 'foo', + glob: '@(bar)/**/*.js', + isBrace: false, + isBracket: false, + isGlob: true, + isGlobstar: true, + isExtglob: true, + negated: false, + negatedExtglob: false, + slashes: [1, 5, 12, 15], + parts: ['foo', '@(bar)', '**', '*.js'] + }); + }); + + test('should handle leading "!"', () => { + expect_deepEqual(scan('!foo/bar/*.js'), { + input: '!foo/bar/*.js', + prefix: '!', + start: 1, + base: 'foo/bar', + glob: '*.js', + isBrace: false, + isBracket: false, + isGlob: true, + isGlobstar: false, + isExtglob: false, + negated: true, + negatedExtglob: false + }); + }); + + test('should detect negated extglobs at the begining', () => { + expect_deepEqual(scan('!(foo)*'), { + input: '!(foo)*', + prefix: '', + start: 0, + base: '', + glob: '!(foo)*', + isBrace: false, + isBracket: false, + isGlob: true, + isGlobstar: false, + isExtglob: true, + negated: false, + negatedExtglob: true + }); + + expect_deepEqual(scan('!(foo)'), { + input: '!(foo)', + prefix: '', + start: 0, + base: '', + glob: '!(foo)', + isBrace: false, + isBracket: false, + isGlob: true, + isGlobstar: false, + isExtglob: true, + negated: false, + negatedExtglob: true + }); + }); + + test('should not detect negated extglobs in the middle', () => { + expect_deepEqual(scan('test/!(foo)/*'), { + input: 'test/!(foo)/*', + prefix: '', + start: 0, + base: 'test', + glob: '!(foo)/*', + isBrace: false, + isBracket: false, + isGlob: true, + isGlobstar: false, + isExtglob: true, + negated: false, + negatedExtglob: false + }); + }); + + test('should handle leading "./" when negated', () => { + expect_deepEqual(scan('./!foo/bar/*.js'), { + input: './!foo/bar/*.js', + prefix: './!', + start: 3, + base: 'foo/bar', + glob: '*.js', + isBrace: false, + isBracket: false, + isGlob: true, + isGlobstar: false, + isExtglob: false, + negated: true, + negatedExtglob: false + }); + + expect_deepEqual(scan('!./foo/bar/*.js'), { + input: '!./foo/bar/*.js', + prefix: '!./', + start: 3, + base: 'foo/bar', + glob: '*.js', + isBrace: false, + isBracket: false, + isGlob: true, + isGlobstar: false, + isExtglob: false, + negated: true, + negatedExtglob: false + }); + }); + + test('should recognize leading ./', () => { + expect_equal(base('./(a|b)'), ''); + }); + + test('should strip glob magic to return base path', () => { + expect_equal(base('.'), '.'); + expect_equal(base('.*'), ''); + expect_equal(base('/.*'), '/'); + expect_equal(base('/.*/'), '/'); + expect_equal(base('a/.*/b'), 'a'); + expect_equal(base('a*/.*/b'), ''); + expect_equal(base('*/a/b/c'), ''); + expect_equal(base('*'), ''); + expect_equal(base('*/'), ''); + expect_equal(base('*/*'), ''); + expect_equal(base('*/*/'), ''); + expect_equal(base('**'), ''); + expect_equal(base('**/'), ''); + expect_equal(base('**/*'), ''); + expect_equal(base('**/*/'), ''); + expect_equal(base('/*.js'), '/'); + expect_equal(base('*.js'), ''); + expect_equal(base('**/*.js'), ''); + expect_equal(base('/root/path/to/*.js'), '/root/path/to'); + expect_equal(base('[a-z]'), ''); + expect_equal(base('chapter/foo [bar]/'), 'chapter'); + expect_equal(base('path/!/foo'), 'path/!/foo'); + expect_equal(base('path/!/foo/'), 'path/!/foo/'); + expect_equal(base('path/!subdir/foo.js'), 'path/!subdir/foo.js'); + expect_equal(base('path/**/*'), 'path'); + expect_equal(base('path/**/subdir/foo.*'), 'path'); + expect_equal(base('path/*/foo'), 'path'); + expect_equal(base('path/*/foo/'), 'path'); + expect_equal(base('path/+/foo'), 'path/+/foo', 'plus sign must be escaped'); + expect_equal(base('path/+/foo/'), 'path/+/foo/', 'plus sign must be escaped'); + expect_equal(base('path/?/foo'), 'path', 'qmarks must be escaped'); + expect_equal(base('path/?/foo/'), 'path', 'qmarks must be escaped'); + expect_equal(base('path/@/foo'), 'path/@/foo'); + expect_equal(base('path/@/foo/'), 'path/@/foo/'); + expect_equal(base('path/[a-z]'), 'path'); + expect_equal(base('path/subdir/**/foo.js'), 'path/subdir'); + expect_equal(base('path/to/*.js'), 'path/to'); + }); + + test('should respect escaped characters', () => { + expect_equal(base('path/\\*\\*/subdir/foo.*'), 'path/\\*\\*/subdir'); + expect_equal(base('path/\\[\\*\\]/subdir/foo.*'), 'path/\\[\\*\\]/subdir'); + expect_equal(base('path/\\[foo bar\\]/subdir/foo.*'), 'path/\\[foo bar\\]/subdir'); + expect_equal(base('path/\\[bar]/'), 'path/\\[bar]/'); + expect_equal(base('path/\\[bar]'), 'path/\\[bar]'); + expect_equal(base('[bar]'), ''); + expect_equal(base('[bar]/'), ''); + expect_equal(base('./\\[bar]'), '\\[bar]'); + expect_equal(base('\\[bar]/'), '\\[bar]/'); + expect_equal(base('\\[bar\\]/'), '\\[bar\\]/'); + expect_equal(base('[bar\\]/'), '[bar\\]/'); + expect_equal(base('path/foo \\[bar]/'), 'path/foo \\[bar]/'); + expect_equal(base('\\[bar]'), '\\[bar]'); + expect_equal(base('[bar\\]'), '[bar\\]'); + }); + + test('should return full non-glob paths', () => { + expect_equal(base('path'), 'path'); + expect_equal(base('path/foo'), 'path/foo'); + expect_equal(base('path/foo/'), 'path/foo/'); + expect_equal(base('path/foo/bar.js'), 'path/foo/bar.js'); + }); + + test('should not return glob when noext is true', () => { + expect_deepEqual(scan('./foo/bar/*.js', { noext: true }), { + input: './foo/bar/*.js', + prefix: './', + start: 2, + base: 'foo/bar/*.js', + glob: '', + isBrace: false, + isBracket: false, + isGlob: false, + isGlobstar: false, + isExtglob: false, + negated: false, + negatedExtglob: false + }); + }); + + test('should respect nonegate opts', () => { + expect_deepEqual(scan('!foo/bar/*.js', { nonegate: true }), { + input: '!foo/bar/*.js', + prefix: '', + start: 0, + base: '!foo/bar', + glob: '*.js', + isBrace: false, + isBracket: false, + isGlob: true, + isGlobstar: false, + isExtglob: false, + negated: false, + negatedExtglob: false + }); + }); + + test('should return parts of the pattern', () => { + // Right now it returns [] + // assertParts('', ['']); + // assertParts('*', ['*']); + // assertParts('.*', ['.*']); + // assertParts('**', ['**']); + // assertParts('foo', ['foo']); + // assertParts('foo*', ['foo*']); + // assertParts('/', ['', '']); + // assertParts('/*', ['', '*']); + // assertParts('./', ['']); + // assertParts('{1..9}', ['{1..9}']); + // assertParts('c!(.)z', ['c!(.)z']); + // assertParts('(b|a).(a)', ['(b|a).(a)']); + // assertParts('+(a|b\\[)*', ['+(a|b\\[)*']); + // assertParts('@(a|b).md', ['@(a|b).md']); + // assertParts('(a/b)', ['(a/b)']); + // assertParts('(a\\b)', ['(a\\b)']); + // assertParts('foo\\[a\\/]', ['foo\\[a\\/]']); + // assertParts('foo[/]bar', ['foo[/]bar']); + // assertParts('/dev\\/@(tcp|udp)\\/*\\/*', ['', '/dev\\/@(tcp|udp)\\/*\\/*']); + + // Right now it returns ['*'] + // assertParts('*/', ['*', '']); + + // Right now it returns ['!(!(bar)', 'baz)'] + // assertParts('!(!(bar)/baz)', ['!(!(bar)/baz)']); + + assertParts('./foo', ['foo']); + assertParts('../foo', ['..', 'foo']); + + assertParts('foo/bar', ['foo', 'bar']); + assertParts('foo/*', ['foo', '*']); + assertParts('foo/**', ['foo', '**']); + assertParts('foo/**/*', ['foo', '**', '*']); + assertParts('フォルダ/**/*', ['フォルダ', '**', '*']); + + assertParts('foo/!(abc)', ['foo', '!(abc)']); + assertParts('c/!(z)/v', ['c', '!(z)', 'v']); + assertParts('c/@(z)/v', ['c', '@(z)', 'v']); + assertParts('foo/(bar|baz)', ['foo', '(bar|baz)']); + assertParts('foo/(bar|baz)*', ['foo', '(bar|baz)*']); + assertParts('**/*(W*, *)*', ['**', '*(W*, *)*']); + assertParts('a/**@(/x|/z)/*.md', ['a', '**@(/x|/z)', '*.md']); + assertParts('foo/(bar|baz)/*.js', ['foo', '(bar|baz)', '*.js']); + + assertParts('XXX/*/*/12/*/*/m/*/*', ['XXX', '*', '*', '12', '*', '*', 'm', '*', '*']); + assertParts('foo/\\"**\\"/bar', ['foo', '\\"**\\"', 'bar']); + + assertParts('[0-9]/[0-9]', ['[0-9]', '[0-9]']); + assertParts('foo/[0-9]/[0-9]', ['foo', '[0-9]', '[0-9]']); + assertParts('foo[0-9]/bar[0-9]', ['foo[0-9]', 'bar[0-9]']); + }); + }); + + describe('.base (glob2base test patterns)', () => { + test('should get a base name', () => { + expect_equal(base('js/*.js'), 'js'); + }); + + test('should get a base name from a nested glob', () => { + expect_equal(base('js/**/test/*.js'), 'js'); + }); + + test('should get a base name from a flat file', () => { + expect_equal(base('js/test/wow.js'), 'js/test/wow.js'); // differs + }); + + test('should get a base name from character class pattern', () => { + expect_equal(base('js/t[a-z]st}/*.js'), 'js'); + }); + + test('should get a base name from extglob', () => { + expect_equal(base('js/t+(wo|est)/*.js'), 'js'); + }); + + test('should get a base name from a path with non-exglob parens', () => { + expect_equal(base('(a|b)'), ''); + expect_equal(base('foo/(a|b)'), 'foo'); + expect_equal(base('/(a|b)'), '/'); + expect_equal(base('a/(b c)'), 'a'); + expect_equal(base('foo/(b c)/baz'), 'foo'); + expect_equal(base('a/(b c)/'), 'a'); + expect_equal(base('a/(b c)/d'), 'a'); + expect_equal(base('a/(b c)', { noparen: true }), 'a/(b c)'); + expect_equal(base('a/(b c)/', { noparen: true }), 'a/(b c)/'); + expect_equal(base('a/(b c)/d', { noparen: true }), 'a/(b c)/d'); + expect_equal(base('foo/(b c)/baz', { noparen: true }), 'foo/(b c)/baz'); + expect_equal(base('path/(foo bar)/subdir/foo.*', { noparen: true }), 'path/(foo bar)/subdir'); + expect_equal(base('a/\\(b c)'), 'a/\\(b c)', 'parens must be escaped'); + expect_equal(base('a/\\+\\(b c)/foo'), 'a/\\+\\(b c)/foo', 'parens must be escaped'); + expect_equal(base('js/t(wo|est)/*.js'), 'js'); + expect_equal(base('js/t/(wo|est)/*.js'), 'js/t'); + expect_equal(base('path/(foo bar)/subdir/foo.*'), 'path', 'parens must be escaped'); + expect_equal(base('path/(foo/bar|baz)'), 'path'); + expect_equal(base('path/(foo/bar|baz)/'), 'path'); + expect_equal(base('path/(to|from)'), 'path'); + expect_equal(base('path/\\(foo/bar|baz)/'), 'path/\\(foo/bar|baz)/'); + expect_equal(base('path/\\*(a|b)'), 'path'); + expect_equal(base('path/\\*(a|b)/subdir/foo.*'), 'path'); + expect_equal(base('path/\\*/(a|b)/subdir/foo.*'), 'path/\\*'); + expect_equal(base('path/\\*\\(a\\|b\\)/subdir/foo.*'), 'path/\\*\\(a\\|b\\)/subdir'); + }); + }); + + describe('technically invalid windows globs', () => { + test('should support simple globs with backslash path separator', () => { + expect_equal(base('C:\\path\\*.js'), 'C:\\path\\*.js'); + expect_equal(base('C:\\\\path\\\\*.js'), ''); + expect_equal(base('C:\\\\path\\*.js'), 'C:\\\\path\\*.js'); + }); + }); + + describe('glob base >', () => { + test('should parse globs', () => { + expect_deepEqual(both('!foo'), ['foo', '']); + expect_deepEqual(both('*'), ['', '*']); + expect_deepEqual(both('**'), ['', '**']); + expect_deepEqual(both('**/*.md'), ['', '**/*.md']); + expect_deepEqual(both('**/*.min.js'), ['', '**/*.min.js']); + expect_deepEqual(both('**/*foo.js'), ['', '**/*foo.js']); + expect_deepEqual(both('**/.*'), ['', '**/.*']); + expect_deepEqual(both('**/d'), ['', '**/d']); + expect_deepEqual(both('*.*'), ['', '*.*']); + expect_deepEqual(both('*.js'), ['', '*.js']); + expect_deepEqual(both('*.md'), ['', '*.md']); + expect_deepEqual(both('*.min.js'), ['', '*.min.js']); + expect_deepEqual(both('*/*'), ['', '*/*']); + expect_deepEqual(both('*/*/*/*'), ['', '*/*/*/*']); + expect_deepEqual(both('*/*/*/e'), ['', '*/*/*/e']); + expect_deepEqual(both('*/b/*/e'), ['', '*/b/*/e']); + expect_deepEqual(both('*b'), ['', '*b']); + expect_deepEqual(both('.*'), ['', '.*']); + expect_deepEqual(both('*'), ['', '*']); + expect_deepEqual(both('a/**/j/**/z/*.md'), ['a', '**/j/**/z/*.md']); + expect_deepEqual(both('a/**/z/*.md'), ['a', '**/z/*.md']); + expect_deepEqual(both('node_modules/*-glob/**/*.js'), ['node_modules', '*-glob/**/*.js']); + expect_deepEqual(both('{a/b/{c,/foo.js}/e.f.g}'), ['', '{a/b/{c,/foo.js}/e.f.g}']); + expect_deepEqual(both('.a*'), ['', '.a*']); + expect_deepEqual(both('.b*'), ['', '.b*']); + expect_deepEqual(both('/*'), ['/', '*']); + expect_deepEqual(both('a/***'), ['a', '***']); + expect_deepEqual(both('a/**/b/*.{foo,bar}'), ['a', '**/b/*.{foo,bar}']); + expect_deepEqual(both('a/**/c/*'), ['a', '**/c/*']); + expect_deepEqual(both('a/**/c/*.md'), ['a', '**/c/*.md']); + expect_deepEqual(both('a/**/e'), ['a', '**/e']); + expect_deepEqual(both('a/**/j/**/z/*.md'), ['a', '**/j/**/z/*.md']); + expect_deepEqual(both('a/**/z/*.md'), ['a', '**/z/*.md']); + expect_deepEqual(both('a/**c*'), ['a', '**c*']); + expect_deepEqual(both('a/**c/*'), ['a', '**c/*']); + expect_deepEqual(both('a/*/*/e'), ['a', '*/*/e']); + expect_deepEqual(both('a/*/c/*.md'), ['a', '*/c/*.md']); + expect_deepEqual(both('a/b/**/c{d,e}/**/xyz.md'), ['a/b', '**/c{d,e}/**/xyz.md']); + expect_deepEqual(both('a/b/**/e'), ['a/b', '**/e']); + expect_deepEqual(both('a/b/*.{foo,bar}'), ['a/b', '*.{foo,bar}']); + expect_deepEqual(both('a/b/*/e'), ['a/b', '*/e']); + expect_deepEqual(both('a/b/.git/'), ['a/b/.git/', '']); + expect_deepEqual(both('a/b/.git/**'), ['a/b/.git', '**']); + expect_deepEqual(both('a/b/.{foo,bar}'), ['a/b', '.{foo,bar}']); + expect_deepEqual(both('a/b/c/*'), ['a/b/c', '*']); + expect_deepEqual(both('a/b/c/**/*.min.js'), ['a/b/c', '**/*.min.js']); + expect_deepEqual(both('a/b/c/*.md'), ['a/b/c', '*.md']); + expect_deepEqual(both('a/b/c/.*.md'), ['a/b/c', '.*.md']); + expect_deepEqual(both('a/b/{c,.gitignore,{a,b}}/{a,b}/abc.foo.js'), ['a/b', '{c,.gitignore,{a,b}}/{a,b}/abc.foo.js']); + expect_deepEqual(both('a/b/{c,/.gitignore}'), ['a/b', '{c,/.gitignore}']); + expect_deepEqual(both('a/b/{c,d}/'), ['a/b', '{c,d}/']); + expect_deepEqual(both('a/b/{c,d}/e/f.g'), ['a/b', '{c,d}/e/f.g']); + expect_deepEqual(both('b/*/*/*'), ['b', '*/*/*']); + }); + + test('should support file extensions', () => { + expect_deepEqual(both('.md'), ['.md', '']); + }); + + test('should support negation pattern', () => { + expect_deepEqual(both('!*.min.js'), ['', '*.min.js']); + expect_deepEqual(both('!foo'), ['foo', '']); + expect_deepEqual(both('!foo/*.js'), ['foo', '*.js']); + expect_deepEqual(both('!foo/(a|b).min.js'), ['foo', '(a|b).min.js']); + expect_deepEqual(both('!foo/[a-b].min.js'), ['foo', '[a-b].min.js']); + expect_deepEqual(both('!foo/{a,b}.min.js'), ['foo', '{a,b}.min.js']); + expect_deepEqual(both('a/b/c/!foo'), ['a/b/c/!foo', '']); + }); + + test('should support extglobs', () => { + expect_deepEqual(both('/a/b/!(a|b)/e.f.g/'), ['/a/b', '!(a|b)/e.f.g/']); + expect_deepEqual(both('/a/b/@(a|b)/e.f.g/'), ['/a/b', '@(a|b)/e.f.g/']); + expect_deepEqual(both('@(a|b)/e.f.g/'), ['', '@(a|b)/e.f.g/']); + expect_equal(base('path/!(to|from)'), 'path'); + expect_equal(base('path/*(to|from)'), 'path'); + expect_equal(base('path/+(to|from)'), 'path'); + expect_equal(base('path/?(to|from)'), 'path'); + expect_equal(base('path/@(to|from)'), 'path'); + }); + + test('should support regex character classes', () => { + const opts = { unescape: true }; + expect_deepEqual(both('[a-c]b*'), ['', '[a-c]b*']); + expect_deepEqual(both('[a-j]*[^c]'), ['', '[a-j]*[^c]']); + expect_deepEqual(both('[a-j]*[^c]b/c'), ['', '[a-j]*[^c]b/c']); + expect_deepEqual(both('[a-j]*[^c]bc'), ['', '[a-j]*[^c]bc']); + expect_deepEqual(both('[ab][ab]'), ['', '[ab][ab]']); + expect_deepEqual(both('foo/[a-b].min.js'), ['foo', '[a-b].min.js']); + expect_equal(base('path/foo[a\\/]/', opts), 'path'); + expect_equal(base('path/foo\\[a\\/]/', opts), 'path/foo[a\\/]/'); + expect_equal(base('foo[a\\/]', opts), ''); + expect_equal(base('foo\\[a\\/]', opts), 'foo[a\\/]'); + }); + + test('should support qmarks', () => { + expect_deepEqual(both('?'), ['', '?']); + expect_deepEqual(both('?/?'), ['', '?/?']); + expect_deepEqual(both('??'), ['', '??']); + expect_deepEqual(both('???'), ['', '???']); + expect_deepEqual(both('?a'), ['', '?a']); + expect_deepEqual(both('?b'), ['', '?b']); + expect_deepEqual(both('a?b'), ['', 'a?b']); + expect_deepEqual(both('a/?/c.js'), ['a', '?/c.js']); + expect_deepEqual(both('a/?/c.md'), ['a', '?/c.md']); + expect_deepEqual(both('a/?/c/?/*/f.js'), ['a', '?/c/?/*/f.js']); + expect_deepEqual(both('a/?/c/?/*/f.md'), ['a', '?/c/?/*/f.md']); + expect_deepEqual(both('a/?/c/?/e.js'), ['a', '?/c/?/e.js']); + expect_deepEqual(both('a/?/c/?/e.md'), ['a', '?/c/?/e.md']); + expect_deepEqual(both('a/?/c/???/e.js'), ['a', '?/c/???/e.js']); + expect_deepEqual(both('a/?/c/???/e.md'), ['a', '?/c/???/e.md']); + expect_deepEqual(both('a/??/c.js'), ['a', '??/c.js']); + expect_deepEqual(both('a/??/c.md'), ['a', '??/c.md']); + expect_deepEqual(both('a/???/c.js'), ['a', '???/c.js']); + expect_deepEqual(both('a/???/c.md'), ['a', '???/c.md']); + expect_deepEqual(both('a/????/c.js'), ['a', '????/c.js']); + }); + + test('should support non-glob patterns', () => { + expect_deepEqual(both(''), ['', '']); + expect_deepEqual(both('.'), ['.', '']); + expect_deepEqual(both('a'), ['a', '']); + expect_deepEqual(both('.a'), ['.a', '']); + expect_deepEqual(both('/a'), ['/a', '']); + expect_deepEqual(both('a/'), ['a/', '']); + expect_deepEqual(both('/a/'), ['/a/', '']); + expect_deepEqual(both('/a/b/c'), ['/a/b/c', '']); + expect_deepEqual(both('/a/b/c/'), ['/a/b/c/', '']); + expect_deepEqual(both('a/b/c/'), ['a/b/c/', '']); + expect_deepEqual(both('a.min.js'), ['a.min.js', '']); + expect_deepEqual(both('a/.x.md'), ['a/.x.md', '']); + expect_deepEqual(both('a/b/.gitignore'), ['a/b/.gitignore', '']); + expect_deepEqual(both('a/b/c/d.md'), ['a/b/c/d.md', '']); + expect_deepEqual(both('a/b/c/d.e.f/g.min.js'), ['a/b/c/d.e.f/g.min.js', '']); + expect_deepEqual(both('a/b/.git'), ['a/b/.git', '']); + expect_deepEqual(both('a/b/.git/'), ['a/b/.git/', '']); + expect_deepEqual(both('a/b/c'), ['a/b/c', '']); + expect_deepEqual(both('a/b/c.d/e.md'), ['a/b/c.d/e.md', '']); + expect_deepEqual(both('a/b/c.md'), ['a/b/c.md', '']); + expect_deepEqual(both('a/b/c.min.js'), ['a/b/c.min.js', '']); + expect_deepEqual(both('a/b/git/'), ['a/b/git/', '']); + expect_deepEqual(both('aa'), ['aa', '']); + expect_deepEqual(both('ab'), ['ab', '']); + expect_deepEqual(both('bb'), ['bb', '']); + expect_deepEqual(both('c.md'), ['c.md', '']); + expect_deepEqual(both('foo'), ['foo', '']); + }); + }); + + describe('braces', () => { + test('should recognize brace sets', () => { + expect_equal(base('path/{to,from}'), 'path'); + expect_equal(base('path/{foo,bar}/'), 'path'); + expect_equal(base('js/{src,test}/*.js'), 'js'); + expect_equal(base('{a,b}'), ''); + expect_equal(base('/{a,b}'), '/'); + expect_equal(base('/{a,b}/'), '/'); + }); + + test('should recognize brace ranges', () => { + expect_equal(base('js/test{0..9}/*.js'), 'js'); + }); + + test('should respect brace enclosures with embedded separators', () => { + const opts = { unescape: true }; + expect_equal(base('path/{,/,bar/baz,qux}/', opts), 'path'); + expect_equal(base('path/\\{,/,bar/baz,qux}/', opts), 'path/{,/,bar/baz,qux}/'); + expect_equal(base('path/\\{,/,bar/baz,qux\\}/', opts), 'path/{,/,bar/baz,qux}/'); + expect_equal(base('/{,/,bar/baz,qux}/', opts), '/'); + expect_equal(base('/\\{,/,bar/baz,qux}/', opts), '/{,/,bar/baz,qux}/'); + expect_equal(base('{,/,bar/baz,qux}', opts), ''); + expect_equal(base('\\{,/,bar/baz,qux\\}', opts), '{,/,bar/baz,qux}'); + expect_equal(base('\\{,/,bar/baz,qux}/', opts), '{,/,bar/baz,qux}/'); + }); + + test('should handle escaped nested braces', () => { + const opts = { unescape: true }; + expect_equal(base('\\{../,./,\\{bar,/baz},qux}', opts), '{../,./,{bar,/baz},qux}'); + expect_equal(base('\\{../,./,\\{bar,/baz},qux}/', opts), '{../,./,{bar,/baz},qux}/'); + expect_equal(base('path/\\{,/,bar/{baz,qux}}/', opts), 'path/{,/,bar/{baz,qux}}/'); + expect_equal(base('path/\\{../,./,\\{bar,/baz},qux}/', opts), 'path/{../,./,{bar,/baz},qux}/'); + expect_equal(base('path/\\{../,./,\\{bar,/baz},qux}/', opts), 'path/{../,./,{bar,/baz},qux}/'); + expect_equal(base('path/\\{../,./,{bar,/baz},qux}/', opts), 'path/{../,./,{bar,/baz},qux}/'); + expect_equal(base('path/{,/,bar/\\{baz,qux}}/', opts), 'path'); + }); + + test('should recognize escaped braces', () => { + const opts = { unescape: true }; + expect_equal(base('\\{foo,bar\\}', opts), '{foo,bar}'); + expect_equal(base('\\{foo,bar\\}/', opts), '{foo,bar}/'); + expect_equal(base('\\{foo,bar}/', opts), '{foo,bar}/'); + expect_equal(base('path/\\{foo,bar}/', opts), 'path/{foo,bar}/'); + }); + + test('should get a base name from a complex brace glob', () => { + expect_equal(base('one/{foo,bar}/**/{baz,qux}/*.txt'), 'one'); + expect_equal(base('two/baz/**/{abc,xyz}/*.js'), 'two/baz'); + expect_equal(base('foo/{bar,baz}/**/aaa/{bbb,ccc}'), 'foo'); + }); + + test('should support braces: no path', () => { + expect_deepEqual(both('/a/b/{c,/foo.js}/e.f.g/'), ['/a/b', '{c,/foo.js}/e.f.g/']); + expect_deepEqual(both('{a/b/c.js,/a/b/{c,/foo.js}/e.f.g/}'), ['', '{a/b/c.js,/a/b/{c,/foo.js}/e.f.g/}']); + expect_deepEqual(both('/a/b/{c,d}/'), ['/a/b', '{c,d}/']); + expect_deepEqual(both('/a/b/{c,d}/*.js'), ['/a/b', '{c,d}/*.js']); + expect_deepEqual(both('/a/b/{c,d}/*.min.js'), ['/a/b', '{c,d}/*.min.js']); + expect_deepEqual(both('/a/b/{c,d}/e.f.g/'), ['/a/b', '{c,d}/e.f.g/']); + expect_deepEqual(both('{.,*}'), ['', '{.,*}']); + }); + + test('should support braces in filename', () => { + expect_deepEqual(both('a/b/.{c,.gitignore}'), ['a/b', '.{c,.gitignore}']); + expect_deepEqual(both('a/b/.{c,/.gitignore}'), ['a/b', '.{c,/.gitignore}']); + expect_deepEqual(both('a/b/.{foo,bar}'), ['a/b', '.{foo,bar}']); + expect_deepEqual(both('a/b/{c,.gitignore}'), ['a/b', '{c,.gitignore}']); + expect_deepEqual(both('a/b/{c,/.gitignore}'), ['a/b', '{c,/.gitignore}']); + expect_deepEqual(both('a/b/{c,/gitignore}'), ['a/b', '{c,/gitignore}']); + expect_deepEqual(both('a/b/{c,d}'), ['a/b', '{c,d}']); + }); + + test('should support braces in dirname', () => { + expect_deepEqual(both('a/b/{c,./d}/e/f.g'), ['a/b', '{c,./d}/e/f.g']); + expect_deepEqual(both('a/b/{c,./d}/e/f.min.g'), ['a/b', '{c,./d}/e/f.min.g']); + expect_deepEqual(both('a/b/{c,.gitignore,{a,./b}}/{a,b}/abc.foo.js'), ['a/b', '{c,.gitignore,{a,./b}}/{a,b}/abc.foo.js']); + expect_deepEqual(both('a/b/{c,.gitignore,{a,b}}/{a,b}/*.foo.js'), ['a/b', '{c,.gitignore,{a,b}}/{a,b}/*.foo.js']); + expect_deepEqual(both('a/b/{c,.gitignore,{a,b}}/{a,b}/abc.foo.js'), ['a/b', '{c,.gitignore,{a,b}}/{a,b}/abc.foo.js']); + expect_deepEqual(both('a/b/{c,/d}/e/f.g'), ['a/b', '{c,/d}/e/f.g']); + expect_deepEqual(both('a/b/{c,/d}/e/f.min.g'), ['a/b', '{c,/d}/e/f.min.g']); + expect_deepEqual(both('a/b/{c,d}/'), ['a/b', '{c,d}/']); + expect_deepEqual(both('a/b/{c,d}/*.js'), ['a/b', '{c,d}/*.js']); + expect_deepEqual(both('a/b/{c,d}/*.min.js'), ['a/b', '{c,d}/*.min.js']); + expect_deepEqual(both('a/b/{c,d}/e.f.g/'), ['a/b', '{c,d}/e.f.g/']); + expect_deepEqual(both('a/b/{c,d}/e/f.g'), ['a/b', '{c,d}/e/f.g']); + expect_deepEqual(both('a/b/{c,d}/e/f.min.g'), ['a/b', '{c,d}/e/f.min.g']); + expect_deepEqual(both('foo/{a,b}.min.js'), ['foo', '{a,b}.min.js']); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/bash.spec.test.ts b/packages/node-utils/test/picomatch/bash.spec.test.ts new file mode 100644 index 0000000..653d215 --- /dev/null +++ b/packages/node-utils/test/picomatch/bash.spec.test.ts @@ -0,0 +1,485 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('bash.spec', () => { + describe('dotglob', () => { + test('"a/b/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x', '**/.x/**', { bash: true })); + }); + + test('".x" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x', '**/.x/**', { bash: true })); + }); + + test('".x/" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/', '**/.x/**', { bash: true })); + }); + + test('".x/a" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/a', '**/.x/**', { bash: true })); + }); + + test('".x/a/b" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/a/b', '**/.x/**', { bash: true })); + }); + + test('".x/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/.x', '**/.x/**', { bash: true })); + }); + + test('"a/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/.x', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c/d" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c/d', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c/d/e" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c/d/e', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/', '**/.x/**', { bash: true })); + }); + + test('"a/.x/b" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/.x/b', '**/.x/**', { bash: true })); + }); + + test('"a/.x/b/.x/c" should not match "**/.x/**"', () => { + expect_truthy(!isMatch('a/.x/b/.x/c', '**/.x/**', { bash: true })); + }); + + test('".bashrc" should not match "?bashrc"', () => { + expect_truthy(!isMatch('.bashrc', '?bashrc', { bash: true })); + }); + + test('should match trailing slashes with stars', () => { + expect_truthy(isMatch('.bar.baz/', '.*.*', { bash: true })); + }); + + test('".bar.baz/" should match ".*.*/"', () => { + expect_truthy(isMatch('.bar.baz/', '.*.*/', { bash: true })); + }); + + test('".bar.baz" should match ".*.*"', () => { + expect_truthy(isMatch('.bar.baz', '.*.*', { bash: true })); + }); + }); + + describe('glob', () => { + test('"a/b/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x', '**/.x/**', { bash: true })); + }); + + test('".x" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x', '**/.x/**', { bash: true })); + }); + + test('".x/" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/', '**/.x/**', { bash: true })); + }); + + test('".x/a" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/a', '**/.x/**', { bash: true })); + }); + + test('".x/a/b" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/a/b', '**/.x/**', { bash: true })); + }); + + test('".x/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('.x/.x', '**/.x/**', { bash: true })); + }); + + test('"a/.x" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/.x', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c/d" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c/d', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/c/d/e" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/c/d/e', '**/.x/**', { bash: true })); + }); + + test('"a/b/.x/" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/b/.x/', '**/.x/**', { bash: true })); + }); + + test('"a/.x/b" should match "**/.x/**"', () => { + expect_truthy(isMatch('a/.x/b', '**/.x/**', { bash: true })); + }); + + test('"a/.x/b/.x/c" should not match "**/.x/**"', () => { + expect_truthy(!isMatch('a/.x/b/.x/c', '**/.x/**', { bash: true })); + }); + + test('"a/c/b" should match "a/*/b"', () => { + expect_truthy(isMatch('a/c/b', 'a/*/b', { bash: true })); + }); + + test('"a/.d/b" should not match "a/*/b"', () => { + expect_truthy(!isMatch('a/.d/b', 'a/*/b', { bash: true })); + }); + + test('"a/./b" should not match "a/*/b"', () => { + expect_truthy(!isMatch('a/./b', 'a/*/b', { bash: true })); + }); + + test('"a/../b" should not match "a/*/b"', () => { + expect_truthy(!isMatch('a/../b', 'a/*/b', { bash: true })); + }); + + test('"ab" should match "ab**"', () => { + expect_truthy(isMatch('ab', 'ab**', { bash: true })); + }); + + test('"abcdef" should match "ab**"', () => { + expect_truthy(isMatch('abcdef', 'ab**', { bash: true })); + }); + + test('"abef" should match "ab**"', () => { + expect_truthy(isMatch('abef', 'ab**', { bash: true })); + }); + + test('"abcfef" should match "ab**"', () => { + expect_truthy(isMatch('abcfef', 'ab**', { bash: true })); + }); + + test('"ab" should not match "ab***ef"', () => { + expect_truthy(!isMatch('ab', 'ab***ef', { bash: true })); + }); + + test('"abcdef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcdef', 'ab***ef', { bash: true })); + }); + + test('"abef" should match "ab***ef"', () => { + expect_truthy(isMatch('abef', 'ab***ef', { bash: true })); + }); + + test('"abcfef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcfef', 'ab***ef', { bash: true })); + }); + + test('".bashrc" should not match "?bashrc"', () => { + expect_truthy(!isMatch('.bashrc', '?bashrc', { bash: true })); + }); + + test('"abbc" should not match "ab?bc"', () => { + expect_truthy(!isMatch('abbc', 'ab?bc', { bash: true })); + }); + + test('"abc" should not match "ab?bc"', () => { + expect_truthy(!isMatch('abc', 'ab?bc', { bash: true })); + }); + + test('"a.a" should match "[a-d]*.[a-b]"', () => { + expect_truthy(isMatch('a.a', '[a-d]*.[a-b]', { bash: true })); + }); + + test('"a.b" should match "[a-d]*.[a-b]"', () => { + expect_truthy(isMatch('a.b', '[a-d]*.[a-b]', { bash: true })); + }); + + test('"c.a" should match "[a-d]*.[a-b]"', () => { + expect_truthy(isMatch('c.a', '[a-d]*.[a-b]', { bash: true })); + }); + + test('"a.a.a" should match "[a-d]*.[a-b]"', () => { + expect_truthy(isMatch('a.a.a', '[a-d]*.[a-b]', { bash: true })); + }); + + test('"a.a.a" should match "[a-d]*.[a-b]*.[a-b]"', () => { + expect_truthy(isMatch('a.a.a', '[a-d]*.[a-b]*.[a-b]', { bash: true })); + }); + + test('"a.a" should match "*.[a-b]"', () => { + expect_truthy(isMatch('a.a', '*.[a-b]', { bash: true })); + }); + + test('"a.b" should match "*.[a-b]"', () => { + expect_truthy(isMatch('a.b', '*.[a-b]', { bash: true })); + }); + + test('"a.a.a" should match "*.[a-b]"', () => { + expect_truthy(isMatch('a.a.a', '*.[a-b]', { bash: true })); + }); + + test('"c.a" should match "*.[a-b]"', () => { + expect_truthy(isMatch('c.a', '*.[a-b]', { bash: true })); + }); + + test('"d.a.d" should not match "*.[a-b]"', () => { + expect_truthy(!isMatch('d.a.d', '*.[a-b]', { bash: true })); + }); + + test('"a.bb" should not match "*.[a-b]"', () => { + expect_truthy(!isMatch('a.bb', '*.[a-b]', { bash: true })); + }); + + test('"a.ccc" should not match "*.[a-b]"', () => { + expect_truthy(!isMatch('a.ccc', '*.[a-b]', { bash: true })); + }); + + test('"c.ccc" should not match "*.[a-b]"', () => { + expect_truthy(!isMatch('c.ccc', '*.[a-b]', { bash: true })); + }); + + test('"a.a" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('a.a', '*.[a-b]*', { bash: true })); + }); + + test('"a.b" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('a.b', '*.[a-b]*', { bash: true })); + }); + + test('"a.a.a" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('a.a.a', '*.[a-b]*', { bash: true })); + }); + + test('"c.a" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('c.a', '*.[a-b]*', { bash: true })); + }); + + test('"d.a.d" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('d.a.d', '*.[a-b]*', { bash: true })); + }); + + test('"d.a.d" should not match "*.[a-b]*.[a-b]*"', () => { + expect_truthy(!isMatch('d.a.d', '*.[a-b]*.[a-b]*', { bash: true })); + }); + + test('"d.a.d" should match "*.[a-d]*.[a-d]*"', () => { + expect_truthy(isMatch('d.a.d', '*.[a-d]*.[a-d]*', { bash: true })); + }); + + test('"a.bb" should match "*.[a-b]*"', () => { + expect_truthy(isMatch('a.bb', '*.[a-b]*', { bash: true })); + }); + + test('"a.ccc" should not match "*.[a-b]*"', () => { + expect_truthy(!isMatch('a.ccc', '*.[a-b]*', { bash: true })); + }); + + test('"c.ccc" should not match "*.[a-b]*"', () => { + expect_truthy(!isMatch('c.ccc', '*.[a-b]*', { bash: true })); + }); + + test('"a.a" should match "*[a-b].[a-b]*"', () => { + expect_truthy(isMatch('a.a', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"a.b" should match "*[a-b].[a-b]*"', () => { + expect_truthy(isMatch('a.b', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"a.a.a" should match "*[a-b].[a-b]*"', () => { + expect_truthy(isMatch('a.a.a', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"c.a" should not match "*[a-b].[a-b]*"', () => { + expect_truthy(!isMatch('c.a', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"d.a.d" should not match "*[a-b].[a-b]*"', () => { + expect_truthy(!isMatch('d.a.d', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"a.bb" should match "*[a-b].[a-b]*"', () => { + expect_truthy(isMatch('a.bb', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"a.ccc" should not match "*[a-b].[a-b]*"', () => { + expect_truthy(!isMatch('a.ccc', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"c.ccc" should not match "*[a-b].[a-b]*"', () => { + expect_truthy(!isMatch('c.ccc', '*[a-b].[a-b]*', { bash: true })); + }); + + test('"abd" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('abd', '[a-y]*[^c]', { bash: true })); + }); + + test('"abe" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('abe', '[a-y]*[^c]', { bash: true })); + }); + + test('"bb" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('bb', '[a-y]*[^c]', { bash: true })); + }); + + test('"bcd" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('bcd', '[a-y]*[^c]', { bash: true })); + }); + + test('"ca" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('ca', '[a-y]*[^c]', { bash: true })); + }); + + test('"cb" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('cb', '[a-y]*[^c]', { bash: true })); + }); + + test('"dd" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('dd', '[a-y]*[^c]', { bash: true })); + }); + + test('"de" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('de', '[a-y]*[^c]', { bash: true })); + }); + + test('"bdir/" should match "[a-y]*[^c]"', () => { + expect_truthy(isMatch('bdir/', '[a-y]*[^c]', { bash: true })); + }); + + test('"abd" should match "**/*"', () => { + expect_truthy(isMatch('abd', '**/*', { bash: true })); + }); + }); + + describe('globstar', () => { + test('"a.js" should match "**/*.js"', () => { + expect_truthy(isMatch('a.js', '**/*.js', { bash: true })); + }); + + test('"a/a.js" should match "**/*.js"', () => { + expect_truthy(isMatch('a/a.js', '**/*.js', { bash: true })); + }); + + test('"a/a/b.js" should match "**/*.js"', () => { + expect_truthy(isMatch('a/a/b.js', '**/*.js', { bash: true })); + }); + + test('"a/b/z.js" should match "a/b/**/*.js"', () => { + expect_truthy(isMatch('a/b/z.js', 'a/b/**/*.js', { bash: true })); + }); + + test('"a/b/c/z.js" should match "a/b/**/*.js"', () => { + expect_truthy(isMatch('a/b/c/z.js', 'a/b/**/*.js', { bash: true })); + }); + + test('"foo.md" should match "**/*.md"', () => { + expect_truthy(isMatch('foo.md', '**/*.md', { bash: true })); + }); + + test('"foo/bar.md" should match "**/*.md"', () => { + expect_truthy(isMatch('foo/bar.md', '**/*.md', { bash: true })); + }); + + test('"foo/bar" should match "foo/**/bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo/**/bar', { bash: true })); + }); + + test('"foo/bar" should match "foo/**bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo/**bar', { bash: true })); + }); + + test('"ab/a/d" should match "**/*"', () => { + expect_truthy(isMatch('ab/a/d', '**/*', { bash: true })); + }); + + test('"ab/b" should match "**/*"', () => { + expect_truthy(isMatch('ab/b', '**/*', { bash: true })); + }); + + test('"a/b/c/d/a.js" should match "**/*"', () => { + expect_truthy(isMatch('a/b/c/d/a.js', '**/*', { bash: true })); + }); + + test('"a/b/c.js" should match "**/*"', () => { + expect_truthy(isMatch('a/b/c.js', '**/*', { bash: true })); + }); + + test('"a/b/c.txt" should match "**/*"', () => { + expect_truthy(isMatch('a/b/c.txt', '**/*', { bash: true })); + }); + + test('"a/b/.js/c.txt" should match "**/*"', () => { + expect_truthy(isMatch('a/b/.js/c.txt', '**/*', { bash: true })); + }); + + test('"a.js" should match "**/*"', () => { + expect_truthy(isMatch('a.js', '**/*', { bash: true })); + }); + + test('"za.js" should match "**/*"', () => { + expect_truthy(isMatch('za.js', '**/*', { bash: true })); + }); + + test('"ab" should match "**/*"', () => { + expect_truthy(isMatch('ab', '**/*', { bash: true })); + }); + + test('"a.b" should match "**/*"', () => { + expect_truthy(isMatch('a.b', '**/*', { bash: true })); + }); + + test('"foo/" should match "foo/**/"', () => { + expect_truthy(isMatch('foo/', 'foo/**/', { bash: true })); + }); + + test('"foo/bar" should not match "foo/**/"', () => { + expect_truthy(!isMatch('foo/bar', 'foo/**/', { bash: true })); + }); + + test('"foo/bazbar" should not match "foo/**/"', () => { + expect_truthy(!isMatch('foo/bazbar', 'foo/**/', { bash: true })); + }); + + test('"foo/barbar" should not match "foo/**/"', () => { + expect_truthy(!isMatch('foo/barbar', 'foo/**/', { bash: true })); + }); + + test('"foo/bar/baz/qux" should not match "foo/**/"', () => { + expect_truthy(!isMatch('foo/bar/baz/qux', 'foo/**/', { bash: true })); + }); + + test('"foo/bar/baz/qux/" should match "foo/**/"', () => { + expect_truthy(isMatch('foo/bar/baz/qux/', 'foo/**/', { bash: true })); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/bash.test.ts b/packages/node-utils/test/picomatch/bash.test.ts new file mode 100644 index 0000000..23dfadb --- /dev/null +++ b/packages/node-utils/test/picomatch/bash.test.ts @@ -0,0 +1,755 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +// $echo a/{1..3}/b +describe('from the Bash 4.3 spec/unit tests', () => { + test('should handle "regular globbing"', () => { + expect_truthy(!isMatch('*', 'a*')); + expect_truthy(!isMatch('**', 'a*')); + expect_truthy(!isMatch('\\*', 'a*')); + expect_truthy(!isMatch('a/*', 'a*')); + expect_truthy(!isMatch('b', 'a*')); + expect_truthy(!isMatch('bc', 'a*')); + expect_truthy(!isMatch('bcd', 'a*')); + expect_truthy(!isMatch('bdir/', 'a*')); + expect_truthy(!isMatch('Beware', 'a*')); + expect_truthy(isMatch('a', 'a*')); + expect_truthy(isMatch('ab', 'a*')); + expect_truthy(isMatch('abc', 'a*')); + + expect_truthy(!isMatch('*', '\\a*')); + expect_truthy(!isMatch('**', '\\a*')); + expect_truthy(!isMatch('\\*', '\\a*')); + + expect_truthy(isMatch('a', '\\a*')); + expect_truthy(!isMatch('a/*', '\\a*')); + expect_truthy(isMatch('abc', '\\a*')); + expect_truthy(isMatch('abd', '\\a*')); + expect_truthy(isMatch('abe', '\\a*')); + expect_truthy(!isMatch('b', '\\a*')); + expect_truthy(!isMatch('bb', '\\a*')); + expect_truthy(!isMatch('bcd', '\\a*')); + expect_truthy(!isMatch('bdir/', '\\a*')); + expect_truthy(!isMatch('Beware', '\\a*')); + expect_truthy(!isMatch('c', '\\a*')); + expect_truthy(!isMatch('ca', '\\a*')); + expect_truthy(!isMatch('cb', '\\a*')); + expect_truthy(!isMatch('d', '\\a*')); + expect_truthy(!isMatch('dd', '\\a*')); + expect_truthy(!isMatch('de', '\\a*')); + }); + + test('should match directories', () => { + expect_truthy(!isMatch('*', 'b*/')); + expect_truthy(!isMatch('**', 'b*/')); + expect_truthy(!isMatch('\\*', 'b*/')); + expect_truthy(!isMatch('a', 'b*/')); + expect_truthy(!isMatch('a/*', 'b*/')); + expect_truthy(!isMatch('abc', 'b*/')); + expect_truthy(!isMatch('abd', 'b*/')); + expect_truthy(!isMatch('abe', 'b*/')); + expect_truthy(!isMatch('b', 'b*/')); + expect_truthy(!isMatch('bb', 'b*/')); + expect_truthy(!isMatch('bcd', 'b*/')); + expect_truthy(isMatch('bdir/', 'b*/')); + expect_truthy(!isMatch('Beware', 'b*/')); + expect_truthy(!isMatch('c', 'b*/')); + expect_truthy(!isMatch('ca', 'b*/')); + expect_truthy(!isMatch('cb', 'b*/')); + expect_truthy(!isMatch('d', 'b*/')); + expect_truthy(!isMatch('dd', 'b*/')); + expect_truthy(!isMatch('de', 'b*/')); + }); + + test('should use escaped characters as literals', () => { + expect_truthy(!isMatch('*', '\\^')); + expect_truthy(!isMatch('**', '\\^')); + expect_truthy(!isMatch('\\*', '\\^')); + expect_truthy(!isMatch('a', '\\^')); + expect_truthy(!isMatch('a/*', '\\^')); + expect_truthy(!isMatch('abc', '\\^')); + expect_truthy(!isMatch('abd', '\\^')); + expect_truthy(!isMatch('abe', '\\^')); + expect_truthy(!isMatch('b', '\\^')); + expect_truthy(!isMatch('bb', '\\^')); + expect_truthy(!isMatch('bcd', '\\^')); + expect_truthy(!isMatch('bdir/', '\\^')); + expect_truthy(!isMatch('Beware', '\\^')); + expect_truthy(!isMatch('c', '\\^')); + expect_truthy(!isMatch('ca', '\\^')); + expect_truthy(!isMatch('cb', '\\^')); + expect_truthy(!isMatch('d', '\\^')); + expect_truthy(!isMatch('dd', '\\^')); + expect_truthy(!isMatch('de', '\\^')); + + expect_truthy(isMatch('*', '\\*')); + expect_truthy(isMatch('\\*', '\\*')); + expect_truthy(!isMatch('**', '\\*')); + expect_truthy(!isMatch('a', '\\*')); + expect_truthy(!isMatch('a/*', '\\*')); + expect_truthy(!isMatch('abc', '\\*')); + expect_truthy(!isMatch('abd', '\\*')); + expect_truthy(!isMatch('abe', '\\*')); + expect_truthy(!isMatch('b', '\\*')); + expect_truthy(!isMatch('bb', '\\*')); + expect_truthy(!isMatch('bcd', '\\*')); + expect_truthy(!isMatch('bdir/', '\\*')); + expect_truthy(!isMatch('Beware', '\\*')); + expect_truthy(!isMatch('c', '\\*')); + expect_truthy(!isMatch('ca', '\\*')); + expect_truthy(!isMatch('cb', '\\*')); + expect_truthy(!isMatch('d', '\\*')); + expect_truthy(!isMatch('dd', '\\*')); + expect_truthy(!isMatch('de', '\\*')); + + expect_truthy(!isMatch('*', 'a\\*')); + expect_truthy(!isMatch('**', 'a\\*')); + expect_truthy(!isMatch('\\*', 'a\\*')); + expect_truthy(!isMatch('a', 'a\\*')); + expect_truthy(!isMatch('a/*', 'a\\*')); + expect_truthy(!isMatch('abc', 'a\\*')); + expect_truthy(!isMatch('abd', 'a\\*')); + expect_truthy(!isMatch('abe', 'a\\*')); + expect_truthy(!isMatch('b', 'a\\*')); + expect_truthy(!isMatch('bb', 'a\\*')); + expect_truthy(!isMatch('bcd', 'a\\*')); + expect_truthy(!isMatch('bdir/', 'a\\*')); + expect_truthy(!isMatch('Beware', 'a\\*')); + expect_truthy(!isMatch('c', 'a\\*')); + expect_truthy(!isMatch('ca', 'a\\*')); + expect_truthy(!isMatch('cb', 'a\\*')); + expect_truthy(!isMatch('d', 'a\\*')); + expect_truthy(!isMatch('dd', 'a\\*')); + expect_truthy(!isMatch('de', 'a\\*')); + + expect_truthy(isMatch('aqa', '*q*')); + expect_truthy(isMatch('aaqaa', '*q*')); + expect_truthy(!isMatch('*', '*q*')); + expect_truthy(!isMatch('**', '*q*')); + expect_truthy(!isMatch('\\*', '*q*')); + expect_truthy(!isMatch('a', '*q*')); + expect_truthy(!isMatch('a/*', '*q*')); + expect_truthy(!isMatch('abc', '*q*')); + expect_truthy(!isMatch('abd', '*q*')); + expect_truthy(!isMatch('abe', '*q*')); + expect_truthy(!isMatch('b', '*q*')); + expect_truthy(!isMatch('bb', '*q*')); + expect_truthy(!isMatch('bcd', '*q*')); + expect_truthy(!isMatch('bdir/', '*q*')); + expect_truthy(!isMatch('Beware', '*q*')); + expect_truthy(!isMatch('c', '*q*')); + expect_truthy(!isMatch('ca', '*q*')); + expect_truthy(!isMatch('cb', '*q*')); + expect_truthy(!isMatch('d', '*q*')); + expect_truthy(!isMatch('dd', '*q*')); + expect_truthy(!isMatch('de', '*q*')); + + expect_truthy(isMatch('*', '\\**')); + expect_truthy(isMatch('**', '\\**')); + expect_truthy(!isMatch('\\*', '\\**')); + expect_truthy(!isMatch('a', '\\**')); + expect_truthy(!isMatch('a/*', '\\**')); + expect_truthy(!isMatch('abc', '\\**')); + expect_truthy(!isMatch('abd', '\\**')); + expect_truthy(!isMatch('abe', '\\**')); + expect_truthy(!isMatch('b', '\\**')); + expect_truthy(!isMatch('bb', '\\**')); + expect_truthy(!isMatch('bcd', '\\**')); + expect_truthy(!isMatch('bdir/', '\\**')); + expect_truthy(!isMatch('Beware', '\\**')); + expect_truthy(!isMatch('c', '\\**')); + expect_truthy(!isMatch('ca', '\\**')); + expect_truthy(!isMatch('cb', '\\**')); + expect_truthy(!isMatch('d', '\\**')); + expect_truthy(!isMatch('dd', '\\**')); + expect_truthy(!isMatch('de', '\\**')); + }); + + test('should work for quoted characters', () => { + expect_truthy(!isMatch('*', '"***"')); + expect_truthy(!isMatch('**', '"***"')); + expect_truthy(!isMatch('\\*', '"***"')); + expect_truthy(!isMatch('a', '"***"')); + expect_truthy(!isMatch('a/*', '"***"')); + expect_truthy(!isMatch('abc', '"***"')); + expect_truthy(!isMatch('abd', '"***"')); + expect_truthy(!isMatch('abe', '"***"')); + expect_truthy(!isMatch('b', '"***"')); + expect_truthy(!isMatch('bb', '"***"')); + expect_truthy(!isMatch('bcd', '"***"')); + expect_truthy(!isMatch('bdir/', '"***"')); + expect_truthy(!isMatch('Beware', '"***"')); + expect_truthy(!isMatch('c', '"***"')); + expect_truthy(!isMatch('ca', '"***"')); + expect_truthy(!isMatch('cb', '"***"')); + expect_truthy(!isMatch('d', '"***"')); + expect_truthy(!isMatch('dd', '"***"')); + expect_truthy(!isMatch('de', '"***"')); + expect_truthy(isMatch('***', '"***"')); + + expect_truthy(!isMatch('*', "'***'")); + expect_truthy(!isMatch('**', "'***'")); + expect_truthy(!isMatch('\\*', "'***'")); + expect_truthy(!isMatch('a', "'***'")); + expect_truthy(!isMatch('a/*', "'***'")); + expect_truthy(!isMatch('abc', "'***'")); + expect_truthy(!isMatch('abd', "'***'")); + expect_truthy(!isMatch('abe', "'***'")); + expect_truthy(!isMatch('b', "'***'")); + expect_truthy(!isMatch('bb', "'***'")); + expect_truthy(!isMatch('bcd', "'***'")); + expect_truthy(!isMatch('bdir/', "'***'")); + expect_truthy(!isMatch('Beware', "'***'")); + expect_truthy(!isMatch('c', "'***'")); + expect_truthy(!isMatch('ca', "'***'")); + expect_truthy(!isMatch('cb', "'***'")); + expect_truthy(!isMatch('d', "'***'")); + expect_truthy(!isMatch('dd', "'***'")); + expect_truthy(!isMatch('de', "'***'")); + expect_truthy(isMatch('\'***\'', "'***'")); + + expect_truthy(!isMatch('*', '"***"')); + expect_truthy(!isMatch('**', '"***"')); + expect_truthy(!isMatch('\\*', '"***"')); + expect_truthy(!isMatch('a', '"***"')); + expect_truthy(!isMatch('a/*', '"***"')); + expect_truthy(!isMatch('abc', '"***"')); + expect_truthy(!isMatch('abd', '"***"')); + expect_truthy(!isMatch('abe', '"***"')); + expect_truthy(!isMatch('b', '"***"')); + expect_truthy(!isMatch('bb', '"***"')); + expect_truthy(!isMatch('bcd', '"***"')); + expect_truthy(!isMatch('bdir/', '"***"')); + expect_truthy(!isMatch('Beware', '"***"')); + expect_truthy(!isMatch('c', '"***"')); + expect_truthy(!isMatch('ca', '"***"')); + expect_truthy(!isMatch('cb', '"***"')); + expect_truthy(!isMatch('d', '"***"')); + expect_truthy(!isMatch('dd', '"***"')); + expect_truthy(!isMatch('de', '"***"')); + + expect_truthy(isMatch('*', '"*"*')); + expect_truthy(isMatch('**', '"*"*')); + expect_truthy(!isMatch('\\*', '"*"*')); + expect_truthy(!isMatch('a', '"*"*')); + expect_truthy(!isMatch('a/*', '"*"*')); + expect_truthy(!isMatch('abc', '"*"*')); + expect_truthy(!isMatch('abd', '"*"*')); + expect_truthy(!isMatch('abe', '"*"*')); + expect_truthy(!isMatch('b', '"*"*')); + expect_truthy(!isMatch('bb', '"*"*')); + expect_truthy(!isMatch('bcd', '"*"*')); + expect_truthy(!isMatch('bdir/', '"*"*')); + expect_truthy(!isMatch('Beware', '"*"*')); + expect_truthy(!isMatch('c', '"*"*')); + expect_truthy(!isMatch('ca', '"*"*')); + expect_truthy(!isMatch('cb', '"*"*')); + expect_truthy(!isMatch('d', '"*"*')); + expect_truthy(!isMatch('dd', '"*"*')); + expect_truthy(!isMatch('de', '"*"*')); + }); + + test('should match escaped quotes', () => { + expect_truthy(!isMatch('*', '\\"**\\"')); + expect_truthy(!isMatch('**', '\\"**\\"')); + expect_truthy(!isMatch('\\*', '\\"**\\"')); + expect_truthy(!isMatch('a', '\\"**\\"')); + expect_truthy(!isMatch('a/*', '\\"**\\"')); + expect_truthy(!isMatch('abc', '\\"**\\"')); + expect_truthy(!isMatch('abd', '\\"**\\"')); + expect_truthy(!isMatch('abe', '\\"**\\"')); + expect_truthy(!isMatch('b', '\\"**\\"')); + expect_truthy(!isMatch('bb', '\\"**\\"')); + expect_truthy(!isMatch('bcd', '\\"**\\"')); + expect_truthy(!isMatch('bdir/', '\\"**\\"')); + expect_truthy(!isMatch('Beware', '\\"**\\"')); + expect_truthy(!isMatch('c', '\\"**\\"')); + expect_truthy(!isMatch('ca', '\\"**\\"')); + expect_truthy(!isMatch('cb', '\\"**\\"')); + expect_truthy(!isMatch('d', '\\"**\\"')); + expect_truthy(!isMatch('dd', '\\"**\\"')); + expect_truthy(!isMatch('de', '\\"**\\"')); + expect_truthy(isMatch('"**"', '\\"**\\"')); + + expect_truthy(!isMatch('*', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('**', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('\\*', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('a', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('a/*', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('abc', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('abd', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('abe', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('b', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('bb', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('bcd', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('bdir/', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('Beware', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('c', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('ca', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('cb', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('d', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('dd', 'foo/\\"**\\"/bar')); + expect_truthy(!isMatch('de', 'foo/\\"**\\"/bar')); + expect_truthy(isMatch('foo/"**"/bar', 'foo/\\"**\\"/bar')); + + expect_truthy(!isMatch('*', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('**', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('\\*', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('a', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('a/*', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('abc', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('abd', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('abe', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('b', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('bb', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('bcd', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('bdir/', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('Beware', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('c', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('ca', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('cb', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('d', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('dd', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch('de', 'foo/\\"*\\"/bar')); + expect_truthy(isMatch('foo/"*"/bar', 'foo/\\"*\\"/bar')); + expect_truthy(isMatch('foo/"a"/bar', 'foo/\\"*\\"/bar')); + expect_truthy(isMatch('foo/"b"/bar', 'foo/\\"*\\"/bar')); + expect_truthy(isMatch('foo/"c"/bar', 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch("foo/'*'/bar", 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch("foo/'a'/bar", 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch("foo/'b'/bar", 'foo/\\"*\\"/bar')); + expect_truthy(!isMatch("foo/'c'/bar", 'foo/\\"*\\"/bar')); + + expect_truthy(!isMatch('*', 'foo/"*"/bar')); + expect_truthy(!isMatch('**', 'foo/"*"/bar')); + expect_truthy(!isMatch('\\*', 'foo/"*"/bar')); + expect_truthy(!isMatch('a', 'foo/"*"/bar')); + expect_truthy(!isMatch('a/*', 'foo/"*"/bar')); + expect_truthy(!isMatch('abc', 'foo/"*"/bar')); + expect_truthy(!isMatch('abd', 'foo/"*"/bar')); + expect_truthy(!isMatch('abe', 'foo/"*"/bar')); + expect_truthy(!isMatch('b', 'foo/"*"/bar')); + expect_truthy(!isMatch('bb', 'foo/"*"/bar')); + expect_truthy(!isMatch('bcd', 'foo/"*"/bar')); + expect_truthy(!isMatch('bdir/', 'foo/"*"/bar')); + expect_truthy(!isMatch('Beware', 'foo/"*"/bar')); + expect_truthy(!isMatch('c', 'foo/"*"/bar')); + expect_truthy(!isMatch('ca', 'foo/"*"/bar')); + expect_truthy(!isMatch('cb', 'foo/"*"/bar')); + expect_truthy(!isMatch('d', 'foo/"*"/bar')); + expect_truthy(!isMatch('dd', 'foo/"*"/bar')); + expect_truthy(!isMatch('de', 'foo/"*"/bar')); + expect_truthy(isMatch('foo/*/bar', 'foo/"*"/bar')); + expect_truthy(isMatch('foo/"*"/bar', 'foo/"*"/bar')); + expect_truthy(!isMatch('foo/"a"/bar', 'foo/"*"/bar')); + expect_truthy(!isMatch('foo/"b"/bar', 'foo/"*"/bar')); + expect_truthy(!isMatch('foo/"c"/bar', 'foo/"*"/bar')); + expect_truthy(!isMatch("foo/'*'/bar", 'foo/"*"/bar')); + expect_truthy(!isMatch("foo/'a'/bar", 'foo/"*"/bar')); + expect_truthy(!isMatch("foo/'b'/bar", 'foo/"*"/bar')); + expect_truthy(!isMatch("foo/'c'/bar", 'foo/"*"/bar')); + + expect_truthy(!isMatch('*', "\\'**\\'")); + expect_truthy(!isMatch('**', "\\'**\\'")); + expect_truthy(!isMatch('\\*', "\\'**\\'")); + expect_truthy(!isMatch('a', "\\'**\\'")); + expect_truthy(!isMatch('a/*', "\\'**\\'")); + expect_truthy(!isMatch('abc', "\\'**\\'")); + expect_truthy(!isMatch('abd', "\\'**\\'")); + expect_truthy(!isMatch('abe', "\\'**\\'")); + expect_truthy(!isMatch('b', "\\'**\\'")); + expect_truthy(!isMatch('bb', "\\'**\\'")); + expect_truthy(!isMatch('bcd', "\\'**\\'")); + expect_truthy(!isMatch('bdir/', "\\'**\\'")); + expect_truthy(!isMatch('Beware', "\\'**\\'")); + expect_truthy(!isMatch('c', "\\'**\\'")); + expect_truthy(!isMatch('ca', "\\'**\\'")); + expect_truthy(!isMatch('cb', "\\'**\\'")); + expect_truthy(!isMatch('d', "\\'**\\'")); + expect_truthy(!isMatch('dd', "\\'**\\'")); + expect_truthy(!isMatch('de', "\\'**\\'")); + expect_truthy(isMatch("'**'", "\\'**\\'")); + }); + + test("Pattern from Larry Wall's Configure that caused bash to blow up:", () => { + expect_truthy(!isMatch('*', '[a-c]b*')); + expect_truthy(!isMatch('**', '[a-c]b*')); + expect_truthy(!isMatch('\\*', '[a-c]b*')); + expect_truthy(!isMatch('a', '[a-c]b*')); + expect_truthy(!isMatch('a/*', '[a-c]b*')); + expect_truthy(isMatch('abc', '[a-c]b*')); + expect_truthy(isMatch('abd', '[a-c]b*')); + expect_truthy(isMatch('abe', '[a-c]b*')); + expect_truthy(!isMatch('b', '[a-c]b*')); + expect_truthy(isMatch('bb', '[a-c]b*')); + expect_truthy(!isMatch('bcd', '[a-c]b*')); + expect_truthy(!isMatch('bdir/', '[a-c]b*')); + expect_truthy(!isMatch('Beware', '[a-c]b*')); + expect_truthy(!isMatch('c', '[a-c]b*')); + expect_truthy(!isMatch('ca', '[a-c]b*')); + expect_truthy(isMatch('cb', '[a-c]b*')); + expect_truthy(!isMatch('d', '[a-c]b*')); + expect_truthy(!isMatch('dd', '[a-c]b*')); + expect_truthy(!isMatch('de', '[a-c]b*')); + }); + + test('should support character classes', () => { + expect_truthy(!isMatch('*', 'a*[^c]')); + expect_truthy(!isMatch('**', 'a*[^c]')); + expect_truthy(!isMatch('\\*', 'a*[^c]')); + expect_truthy(!isMatch('a', 'a*[^c]')); + expect_truthy(!isMatch('a/*', 'a*[^c]')); + expect_truthy(!isMatch('abc', 'a*[^c]')); + expect_truthy(isMatch('abd', 'a*[^c]')); + expect_truthy(isMatch('abe', 'a*[^c]')); + expect_truthy(!isMatch('b', 'a*[^c]')); + expect_truthy(!isMatch('bb', 'a*[^c]')); + expect_truthy(!isMatch('bcd', 'a*[^c]')); + expect_truthy(!isMatch('bdir/', 'a*[^c]')); + expect_truthy(!isMatch('Beware', 'a*[^c]')); + expect_truthy(!isMatch('c', 'a*[^c]')); + expect_truthy(!isMatch('ca', 'a*[^c]')); + expect_truthy(!isMatch('cb', 'a*[^c]')); + expect_truthy(!isMatch('d', 'a*[^c]')); + expect_truthy(!isMatch('dd', 'a*[^c]')); + expect_truthy(!isMatch('de', 'a*[^c]')); + expect_truthy(!isMatch('baz', 'a*[^c]')); + expect_truthy(!isMatch('bzz', 'a*[^c]')); + expect_truthy(!isMatch('BZZ', 'a*[^c]')); + expect_truthy(!isMatch('beware', 'a*[^c]')); + expect_truthy(!isMatch('BewAre', 'a*[^c]')); + + expect_truthy(isMatch('a-b', 'a[X-]b')); + expect_truthy(isMatch('aXb', 'a[X-]b')); + + expect_truthy(!isMatch('*', '[a-y]*[^c]')); + expect_truthy(isMatch('a*', '[a-y]*[^c]', { bash: true })); + expect_truthy(!isMatch('**', '[a-y]*[^c]')); + expect_truthy(!isMatch('\\*', '[a-y]*[^c]')); + expect_truthy(!isMatch('a', '[a-y]*[^c]')); + expect_truthy(isMatch('a123b', '[a-y]*[^c]', { bash: true })); + expect_truthy(!isMatch('a123c', '[a-y]*[^c]', { bash: true })); + expect_truthy(isMatch('ab', '[a-y]*[^c]', { bash: true })); + expect_truthy(!isMatch('a/*', '[a-y]*[^c]')); + expect_truthy(!isMatch('abc', '[a-y]*[^c]')); + expect_truthy(isMatch('abd', '[a-y]*[^c]')); + expect_truthy(isMatch('abe', '[a-y]*[^c]')); + expect_truthy(!isMatch('b', '[a-y]*[^c]')); + expect_truthy(isMatch('bd', '[a-y]*[^c]', { bash: true })); + expect_truthy(isMatch('bb', '[a-y]*[^c]')); + expect_truthy(isMatch('bcd', '[a-y]*[^c]')); + expect_truthy(isMatch('bdir/', '[a-y]*[^c]')); + expect_truthy(!isMatch('Beware', '[a-y]*[^c]')); + expect_truthy(!isMatch('c', '[a-y]*[^c]')); + expect_truthy(isMatch('ca', '[a-y]*[^c]')); + expect_truthy(isMatch('cb', '[a-y]*[^c]')); + expect_truthy(!isMatch('d', '[a-y]*[^c]')); + expect_truthy(isMatch('dd', '[a-y]*[^c]')); + expect_truthy(isMatch('dd', '[a-y]*[^c]', { regex: true })); + expect_truthy(isMatch('dd', '[a-y]*[^c]')); + expect_truthy(isMatch('de', '[a-y]*[^c]')); + expect_truthy(isMatch('baz', '[a-y]*[^c]')); + expect_truthy(isMatch('bzz', '[a-y]*[^c]')); + expect_truthy(isMatch('bzz', '[a-y]*[^c]')); + expect_truthy(!isMatch('bzz', '[a-y]*[^c]', { regex: true })); + expect_truthy(!isMatch('BZZ', '[a-y]*[^c]')); + expect_truthy(isMatch('beware', '[a-y]*[^c]')); + expect_truthy(!isMatch('BewAre', '[a-y]*[^c]')); + + expect_truthy(isMatch('a*b/ooo', 'a\\*b/*')); + expect_truthy(isMatch('a*b/ooo', 'a\\*?/*')); + + expect_truthy(!isMatch('*', 'a[b]c')); + expect_truthy(!isMatch('**', 'a[b]c')); + expect_truthy(!isMatch('\\*', 'a[b]c')); + expect_truthy(!isMatch('a', 'a[b]c')); + expect_truthy(!isMatch('a/*', 'a[b]c')); + expect_truthy(isMatch('abc', 'a[b]c')); + expect_truthy(!isMatch('abd', 'a[b]c')); + expect_truthy(!isMatch('abe', 'a[b]c')); + expect_truthy(!isMatch('b', 'a[b]c')); + expect_truthy(!isMatch('bb', 'a[b]c')); + expect_truthy(!isMatch('bcd', 'a[b]c')); + expect_truthy(!isMatch('bdir/', 'a[b]c')); + expect_truthy(!isMatch('Beware', 'a[b]c')); + expect_truthy(!isMatch('c', 'a[b]c')); + expect_truthy(!isMatch('ca', 'a[b]c')); + expect_truthy(!isMatch('cb', 'a[b]c')); + expect_truthy(!isMatch('d', 'a[b]c')); + expect_truthy(!isMatch('dd', 'a[b]c')); + expect_truthy(!isMatch('de', 'a[b]c')); + expect_truthy(!isMatch('baz', 'a[b]c')); + expect_truthy(!isMatch('bzz', 'a[b]c')); + expect_truthy(!isMatch('BZZ', 'a[b]c')); + expect_truthy(!isMatch('beware', 'a[b]c')); + expect_truthy(!isMatch('BewAre', 'a[b]c')); + + expect_truthy(!isMatch('*', 'a["b"]c')); + expect_truthy(!isMatch('**', 'a["b"]c')); + expect_truthy(!isMatch('\\*', 'a["b"]c')); + expect_truthy(!isMatch('a', 'a["b"]c')); + expect_truthy(!isMatch('a/*', 'a["b"]c')); + expect_truthy(isMatch('abc', 'a["b"]c')); + expect_truthy(!isMatch('abd', 'a["b"]c')); + expect_truthy(!isMatch('abe', 'a["b"]c')); + expect_truthy(!isMatch('b', 'a["b"]c')); + expect_truthy(!isMatch('bb', 'a["b"]c')); + expect_truthy(!isMatch('bcd', 'a["b"]c')); + expect_truthy(!isMatch('bdir/', 'a["b"]c')); + expect_truthy(!isMatch('Beware', 'a["b"]c')); + expect_truthy(!isMatch('c', 'a["b"]c')); + expect_truthy(!isMatch('ca', 'a["b"]c')); + expect_truthy(!isMatch('cb', 'a["b"]c')); + expect_truthy(!isMatch('d', 'a["b"]c')); + expect_truthy(!isMatch('dd', 'a["b"]c')); + expect_truthy(!isMatch('de', 'a["b"]c')); + expect_truthy(!isMatch('baz', 'a["b"]c')); + expect_truthy(!isMatch('bzz', 'a["b"]c')); + expect_truthy(!isMatch('BZZ', 'a["b"]c')); + expect_truthy(!isMatch('beware', 'a["b"]c')); + expect_truthy(!isMatch('BewAre', 'a["b"]c')); + + expect_truthy(!isMatch('*', 'a[\\\\b]c')); + expect_truthy(!isMatch('**', 'a[\\\\b]c')); + expect_truthy(!isMatch('\\*', 'a[\\\\b]c')); + expect_truthy(!isMatch('a', 'a[\\\\b]c')); + expect_truthy(!isMatch('a/*', 'a[\\\\b]c')); + expect_truthy(isMatch('abc', 'a[\\\\b]c')); + expect_truthy(!isMatch('abd', 'a[\\\\b]c')); + expect_truthy(!isMatch('abe', 'a[\\\\b]c')); + expect_truthy(!isMatch('b', 'a[\\\\b]c')); + expect_truthy(!isMatch('bb', 'a[\\\\b]c')); + expect_truthy(!isMatch('bcd', 'a[\\\\b]c')); + expect_truthy(!isMatch('bdir/', 'a[\\\\b]c')); + expect_truthy(!isMatch('Beware', 'a[\\\\b]c')); + expect_truthy(!isMatch('c', 'a[\\\\b]c')); + expect_truthy(!isMatch('ca', 'a[\\\\b]c')); + expect_truthy(!isMatch('cb', 'a[\\\\b]c')); + expect_truthy(!isMatch('d', 'a[\\\\b]c')); + expect_truthy(!isMatch('dd', 'a[\\\\b]c')); + expect_truthy(!isMatch('de', 'a[\\\\b]c')); + expect_truthy(!isMatch('baz', 'a[\\\\b]c')); + expect_truthy(!isMatch('bzz', 'a[\\\\b]c')); + expect_truthy(!isMatch('BZZ', 'a[\\\\b]c')); + expect_truthy(!isMatch('beware', 'a[\\\\b]c')); + expect_truthy(!isMatch('BewAre', 'a[\\\\b]c')); + + expect_truthy(!isMatch('*', 'a[\\b]c')); + expect_truthy(!isMatch('**', 'a[\\b]c')); + expect_truthy(!isMatch('\\*', 'a[\\b]c')); + expect_truthy(!isMatch('a', 'a[\\b]c')); + expect_truthy(!isMatch('a/*', 'a[\\b]c')); + expect_truthy(!isMatch('abc', 'a[\\b]c')); + expect_truthy(!isMatch('abd', 'a[\\b]c')); + expect_truthy(!isMatch('abe', 'a[\\b]c')); + expect_truthy(!isMatch('b', 'a[\\b]c')); + expect_truthy(!isMatch('bb', 'a[\\b]c')); + expect_truthy(!isMatch('bcd', 'a[\\b]c')); + expect_truthy(!isMatch('bdir/', 'a[\\b]c')); + expect_truthy(!isMatch('Beware', 'a[\\b]c')); + expect_truthy(!isMatch('c', 'a[\\b]c')); + expect_truthy(!isMatch('ca', 'a[\\b]c')); + expect_truthy(!isMatch('cb', 'a[\\b]c')); + expect_truthy(!isMatch('d', 'a[\\b]c')); + expect_truthy(!isMatch('dd', 'a[\\b]c')); + expect_truthy(!isMatch('de', 'a[\\b]c')); + expect_truthy(!isMatch('baz', 'a[\\b]c')); + expect_truthy(!isMatch('bzz', 'a[\\b]c')); + expect_truthy(!isMatch('BZZ', 'a[\\b]c')); + expect_truthy(!isMatch('beware', 'a[\\b]c')); + expect_truthy(!isMatch('BewAre', 'a[\\b]c')); + + expect_truthy(!isMatch('*', 'a[b-d]c')); + expect_truthy(!isMatch('**', 'a[b-d]c')); + expect_truthy(!isMatch('\\*', 'a[b-d]c')); + expect_truthy(!isMatch('a', 'a[b-d]c')); + expect_truthy(!isMatch('a/*', 'a[b-d]c')); + expect_truthy(isMatch('abc', 'a[b-d]c')); + expect_truthy(!isMatch('abd', 'a[b-d]c')); + expect_truthy(!isMatch('abe', 'a[b-d]c')); + expect_truthy(!isMatch('b', 'a[b-d]c')); + expect_truthy(!isMatch('bb', 'a[b-d]c')); + expect_truthy(!isMatch('bcd', 'a[b-d]c')); + expect_truthy(!isMatch('bdir/', 'a[b-d]c')); + expect_truthy(!isMatch('Beware', 'a[b-d]c')); + expect_truthy(!isMatch('c', 'a[b-d]c')); + expect_truthy(!isMatch('ca', 'a[b-d]c')); + expect_truthy(!isMatch('cb', 'a[b-d]c')); + expect_truthy(!isMatch('d', 'a[b-d]c')); + expect_truthy(!isMatch('dd', 'a[b-d]c')); + expect_truthy(!isMatch('de', 'a[b-d]c')); + expect_truthy(!isMatch('baz', 'a[b-d]c')); + expect_truthy(!isMatch('bzz', 'a[b-d]c')); + expect_truthy(!isMatch('BZZ', 'a[b-d]c')); + expect_truthy(!isMatch('beware', 'a[b-d]c')); + expect_truthy(!isMatch('BewAre', 'a[b-d]c')); + + expect_truthy(!isMatch('*', 'a?c')); + expect_truthy(!isMatch('**', 'a?c')); + expect_truthy(!isMatch('\\*', 'a?c')); + expect_truthy(!isMatch('a', 'a?c')); + expect_truthy(!isMatch('a/*', 'a?c')); + expect_truthy(isMatch('abc', 'a?c')); + expect_truthy(!isMatch('abd', 'a?c')); + expect_truthy(!isMatch('abe', 'a?c')); + expect_truthy(!isMatch('b', 'a?c')); + expect_truthy(!isMatch('bb', 'a?c')); + expect_truthy(!isMatch('bcd', 'a?c')); + expect_truthy(!isMatch('bdir/', 'a?c')); + expect_truthy(!isMatch('Beware', 'a?c')); + expect_truthy(!isMatch('c', 'a?c')); + expect_truthy(!isMatch('ca', 'a?c')); + expect_truthy(!isMatch('cb', 'a?c')); + expect_truthy(!isMatch('d', 'a?c')); + expect_truthy(!isMatch('dd', 'a?c')); + expect_truthy(!isMatch('de', 'a?c')); + expect_truthy(!isMatch('baz', 'a?c')); + expect_truthy(!isMatch('bzz', 'a?c')); + expect_truthy(!isMatch('BZZ', 'a?c')); + expect_truthy(!isMatch('beware', 'a?c')); + expect_truthy(!isMatch('BewAre', 'a?c')); + + expect_truthy(isMatch('man/man1/bash.1', '*/man*/bash.*')); + + expect_truthy(isMatch('*', '[^a-c]*')); + expect_truthy(isMatch('**', '[^a-c]*')); + expect_truthy(!isMatch('a', '[^a-c]*')); + expect_truthy(!isMatch('a/*', '[^a-c]*')); + expect_truthy(!isMatch('abc', '[^a-c]*')); + expect_truthy(!isMatch('abd', '[^a-c]*')); + expect_truthy(!isMatch('abe', '[^a-c]*')); + expect_truthy(!isMatch('b', '[^a-c]*')); + expect_truthy(!isMatch('bb', '[^a-c]*')); + expect_truthy(!isMatch('bcd', '[^a-c]*')); + expect_truthy(!isMatch('bdir/', '[^a-c]*')); + expect_truthy(isMatch('Beware', '[^a-c]*')); + expect_truthy(isMatch('Beware', '[^a-c]*', { bash: true })); + expect_truthy(!isMatch('c', '[^a-c]*')); + expect_truthy(!isMatch('ca', '[^a-c]*')); + expect_truthy(!isMatch('cb', '[^a-c]*')); + expect_truthy(isMatch('d', '[^a-c]*')); + expect_truthy(isMatch('dd', '[^a-c]*')); + expect_truthy(isMatch('de', '[^a-c]*')); + expect_truthy(!isMatch('baz', '[^a-c]*')); + expect_truthy(!isMatch('bzz', '[^a-c]*')); + expect_truthy(isMatch('BZZ', '[^a-c]*')); + expect_truthy(!isMatch('beware', '[^a-c]*')); + expect_truthy(isMatch('BewAre', '[^a-c]*')); + }); + + test('should support basic wildmatch (brackets) features', () => { + expect_truthy(!isMatch('aab', 'a[]-]b')); + expect_truthy(!isMatch('ten', '[ten]')); + expect_truthy(isMatch(']', ']')); + expect_truthy(isMatch('a-b', 'a[]-]b')); + expect_truthy(isMatch('a]b', 'a[]-]b')); + expect_truthy(isMatch('a]b', 'a[]]b')); + expect_truthy(isMatch('aab', 'a[\\]a\\-]b')); + expect_truthy(isMatch('ten', 't[a-g]n')); + expect_truthy(isMatch('ton', 't[^a-g]n')); + }); + + test('should support extended slash-matching features', () => { + expect_truthy(!isMatch('foo/bar', 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r')); + expect_truthy(isMatch('foo/bar', 'foo[/]bar')); + expect_truthy(isMatch('foo-bar', 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r')); + }); + + test('should match escaped characters', () => { + if (process.platform !== 'win32') { + expect_truthy(isMatch('\\*', '\\*')); + expect_truthy(isMatch('XXX/\\', '[A-Z]+/\\\\')); + } + + expect_truthy(isMatch('[ab]', '\\[ab]')); + expect_truthy(isMatch('[ab]', '[\\[:]ab]')); + }); + + test('should consolidate extra stars', () => { + expect_truthy(!isMatch('bbc', 'a**c')); + expect_truthy(isMatch('abc', 'a**c')); + expect_truthy(!isMatch('bbd', 'a**c')); + + expect_truthy(!isMatch('bbc', 'a***c')); + expect_truthy(isMatch('abc', 'a***c')); + expect_truthy(!isMatch('bbd', 'a***c')); + + expect_truthy(!isMatch('bbc', 'a*****?c')); + expect_truthy(isMatch('abc', 'a*****?c')); + expect_truthy(!isMatch('bbc', 'a*****?c')); + + expect_truthy(isMatch('bbc', '?*****??')); + expect_truthy(isMatch('abc', '?*****??')); + + expect_truthy(isMatch('bbc', '*****??')); + expect_truthy(isMatch('abc', '*****??')); + + expect_truthy(isMatch('bbc', '?*****?c')); + expect_truthy(isMatch('abc', '?*****?c')); + + expect_truthy(isMatch('bbc', '?***?****c')); + expect_truthy(isMatch('abc', '?***?****c')); + expect_truthy(!isMatch('bbd', '?***?****c')); + + expect_truthy(isMatch('bbc', '?***?****?')); + expect_truthy(isMatch('abc', '?***?****?')); + + expect_truthy(isMatch('bbc', '?***?****')); + expect_truthy(isMatch('abc', '?***?****')); + + expect_truthy(isMatch('bbc', '*******c')); + expect_truthy(isMatch('abc', '*******c')); + + expect_truthy(isMatch('bbc', '*******?')); + expect_truthy(isMatch('abc', '*******?')); + + expect_truthy(isMatch('abcdecdhjk', 'a*cd**?**??k')); + expect_truthy(isMatch('abcdecdhjk', 'a**?**cd**?**??k')); + expect_truthy(isMatch('abcdecdhjk', 'a**?**cd**?**??k***')); + expect_truthy(isMatch('abcdecdhjk', 'a**?**cd**?**??***k')); + expect_truthy(isMatch('abcdecdhjk', 'a**?**cd**?**??***k**')); + expect_truthy(isMatch('abcdecdhjk', 'a****c**?**??*****')); + }); + + test('none of these should output anything', () => { + expect_truthy(!isMatch('abc', '??**********?****?')); + expect_truthy(!isMatch('abc', '??**********?****c')); + expect_truthy(!isMatch('abc', '?************c****?****')); + expect_truthy(!isMatch('abc', '*c*?**')); + expect_truthy(!isMatch('abc', 'a*****c*?**')); + expect_truthy(!isMatch('abc', 'a********???*******')); + expect_truthy(!isMatch('a', '[]')); + expect_truthy(!isMatch('[', '[abc')); + }); +}); diff --git a/packages/node-utils/test/picomatch/braces.test.ts b/packages/node-utils/test/picomatch/braces.test.ts new file mode 100644 index 0000000..efbe2c1 --- /dev/null +++ b/packages/node-utils/test/picomatch/braces.test.ts @@ -0,0 +1,241 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +import { fill } from "../../src/fill-range.ts"; +const { isMatch } = picomatch; + +describe('braces', () => { + test('should not match with brace patterns when disabled', () => { + expect_deepEqual(match(['a', 'b', 'c'], '{a,b,c,d}'), ['a', 'b', 'c']); + expect_deepEqual(match(['a', 'b', 'c'], '{a,b,c,d}', { nobrace: true }), []); + expect_deepEqual(match(['1', '2', '3'], '{1..2}', { nobrace: true }), []); + expect_truthy(!isMatch('a/a', 'a/{a,b}', { nobrace: true })); + expect_truthy(!isMatch('a/b', 'a/{a,b}', { nobrace: true })); + expect_truthy(!isMatch('a/c', 'a/{a,b}', { nobrace: true })); + expect_truthy(!isMatch('b/b', 'a/{a,b}', { nobrace: true })); + expect_truthy(!isMatch('b/b', 'a/{a,b,c}', { nobrace: true })); + expect_truthy(!isMatch('a/c', 'a/{a,b,c}', { nobrace: true })); + expect_truthy(!isMatch('a/a', 'a/{a..c}', { nobrace: true })); + expect_truthy(!isMatch('a/b', 'a/{a..c}', { nobrace: true })); + expect_truthy(!isMatch('a/c', 'a/{a..c}', { nobrace: true })); + }); + + test('should treat single-set braces as literals', () => { + expect_truthy(isMatch('a {abc} b', 'a {abc} b')); + expect_truthy(isMatch('a {a-b-c} b', 'a {a-b-c} b')); + expect_truthy(isMatch('a {a.c} b', 'a {a.c} b')); + }); + + test('should match literal braces when escaped', () => { + expect_truthy(isMatch('a {1,2}', 'a \\{1,2\\}')); + expect_truthy(isMatch('a {a..b}', 'a \\{a..b\\}')); + }); + + test('should match using brace patterns', () => { + expect_truthy(!isMatch('a/c', 'a/{a,b}')); + expect_truthy(!isMatch('b/b', 'a/{a,b,c}')); + expect_truthy(!isMatch('b/b', 'a/{a,b}')); + expect_truthy(isMatch('a/a', 'a/{a,b}')); + expect_truthy(isMatch('a/b', 'a/{a,b}')); + expect_truthy(isMatch('a/c', 'a/{a,b,c}')); + }); + + test('should support brace ranges', () => { + expect_truthy(isMatch('a/a', 'a/{a..c}')); + expect_truthy(isMatch('a/b', 'a/{a..c}')); + expect_truthy(isMatch('a/c', 'a/{a..c}')); + }); + + test('should support Kleene stars', () => { + expect_truthy(isMatch('ab', '{ab,c}*')); + expect_truthy(isMatch('abab', '{ab,c}*')); + expect_truthy(isMatch('abc', '{ab,c}*')); + expect_truthy(isMatch('c', '{ab,c}*')); + expect_truthy(isMatch('cab', '{ab,c}*')); + expect_truthy(isMatch('cc', '{ab,c}*')); + expect_truthy(isMatch('ababab', '{ab,c}*')); + expect_truthy(isMatch('ababc', '{ab,c}*')); + expect_truthy(isMatch('abcab', '{ab,c}*')); + expect_truthy(isMatch('abcc', '{ab,c}*')); + expect_truthy(isMatch('cabab', '{ab,c}*')); + expect_truthy(isMatch('cabc', '{ab,c}*')); + expect_truthy(isMatch('ccab', '{ab,c}*')); + expect_truthy(isMatch('ccc', '{ab,c}*')); + }); + + test('should not convert braces inside brackets', () => { + expect_truthy(isMatch('foo{}baz', 'foo[{a,b}]+baz')); + expect_truthy(isMatch('{a}{b}{c}', '[abc{}]+')); + }); + + test('should support braces containing slashes', () => { + expect_truthy(isMatch('a', '{/,}a/**')); + expect_truthy(isMatch('aa.txt', 'a{a,b/}*.txt')); + expect_truthy(isMatch('ab/.txt', 'a{a,b/}*.txt')); + expect_truthy(isMatch('ab/a.txt', 'a{a,b/}*.txt')); + expect_truthy(isMatch('a/', 'a/**{/,}')); + expect_truthy(isMatch('a/a', 'a/**{/,}')); + expect_truthy(isMatch('a/a/', 'a/**{/,}')); + }); + + test('should support braces with empty elements', () => { + expect_truthy(!isMatch('abc.txt', 'a{,b}.txt')); + expect_truthy(!isMatch('abc.txt', 'a{a,b,}.txt')); + expect_truthy(!isMatch('abc.txt', 'a{b,}.txt')); + expect_truthy(isMatch('a.txt', 'a{,b}.txt')); + expect_truthy(isMatch('a.txt', 'a{b,}.txt')); + expect_truthy(isMatch('aa.txt', 'a{a,b,}.txt')); + expect_truthy(isMatch('aa.txt', 'a{a,b,}.txt')); + expect_truthy(isMatch('ab.txt', 'a{,b}.txt')); + expect_truthy(isMatch('ab.txt', 'a{b,}.txt')); + }); + + test('should support braces with slashes and empty elements', () => { + expect_truthy(isMatch('a.txt', 'a{,/}*.txt')); + expect_truthy(isMatch('ab.txt', 'a{,/}*.txt')); + expect_truthy(isMatch('a/b.txt', 'a{,/}*.txt')); + expect_truthy(isMatch('a/ab.txt', 'a{,/}*.txt')); + }); + + test('should support braces with stars', () => { + expect_truthy(isMatch('a.txt', 'a{,.*{foo,db},\\(bar\\)}.txt')); + expect_truthy(!isMatch('adb.txt', 'a{,.*{foo,db},\\(bar\\)}.txt')); + expect_truthy(isMatch('a.db.txt', 'a{,.*{foo,db},\\(bar\\)}.txt')); + + expect_truthy(isMatch('a.txt', 'a{,*.{foo,db},\\(bar\\)}.txt')); + expect_truthy(!isMatch('adb.txt', 'a{,*.{foo,db},\\(bar\\)}.txt')); + expect_truthy(isMatch('a.db.txt', 'a{,*.{foo,db},\\(bar\\)}.txt')); + + expect_truthy(isMatch('a', 'a{,.*{foo,db},\\(bar\\)}')); + expect_truthy(!isMatch('adb', 'a{,.*{foo,db},\\(bar\\)}')); + expect_truthy(isMatch('a.db', 'a{,.*{foo,db},\\(bar\\)}')); + + expect_truthy(isMatch('a', 'a{,*.{foo,db},\\(bar\\)}')); + expect_truthy(!isMatch('adb', 'a{,*.{foo,db},\\(bar\\)}')); + expect_truthy(isMatch('a.db', 'a{,*.{foo,db},\\(bar\\)}')); + + expect_truthy(!isMatch('a', '{,.*{foo,db},\\(bar\\)}')); + expect_truthy(!isMatch('adb', '{,.*{foo,db},\\(bar\\)}')); + expect_truthy(!isMatch('a.db', '{,.*{foo,db},\\(bar\\)}')); + expect_truthy(isMatch('.db', '{,.*{foo,db},\\(bar\\)}')); + + expect_truthy(!isMatch('a', '{,*.{foo,db},\\(bar\\)}')); + expect_truthy(isMatch('a', '{*,*.{foo,db},\\(bar\\)}')); + expect_truthy(!isMatch('adb', '{,*.{foo,db},\\(bar\\)}')); + expect_truthy(isMatch('a.db', '{,*.{foo,db},\\(bar\\)}')); + }); + + test('should support braces in patterns with globstars', () => { + expect_truthy(!isMatch('a/b/c/xyz.md', 'a/b/**/c{d,e}/**/xyz.md')); + expect_truthy(!isMatch('a/b/d/xyz.md', 'a/b/**/c{d,e}/**/xyz.md')); + expect_truthy(isMatch('a/b/cd/xyz.md', 'a/b/**/c{d,e}/**/xyz.md')); + expect_truthy(isMatch('a/b/c/xyz.md', 'a/b/**/{c,d,e}/**/xyz.md')); + expect_truthy(isMatch('a/b/d/xyz.md', 'a/b/**/{c,d,e}/**/xyz.md')); + }); + + test('should support braces with globstars, slashes and empty elements', () => { + expect_truthy(isMatch('a.txt', 'a{,/**/}*.txt')); + expect_truthy(isMatch('a/b.txt', 'a{,/**/,/}*.txt')); + expect_truthy(isMatch('a/x/y.txt', 'a{,/**/}*.txt')); + expect_truthy(!isMatch('a/x/y/z', 'a{,/**/}*.txt')); + }); + + test('should support braces with globstars and empty elements', () => { + expect_truthy(isMatch('a/b/foo/bar/baz.qux', 'a/b{,/**}/bar{,/**}/*.*')); + expect_truthy(isMatch('a/b/bar/baz.qux', 'a/b{,/**}/bar{,/**}/*.*')); + }); + + test('should support Kleene plus', () => { + expect_truthy(isMatch('ab', '{ab,c}+')); + expect_truthy(isMatch('abab', '{ab,c}+')); + expect_truthy(isMatch('abc', '{ab,c}+')); + expect_truthy(isMatch('c', '{ab,c}+')); + expect_truthy(isMatch('cab', '{ab,c}+')); + expect_truthy(isMatch('cc', '{ab,c}+')); + expect_truthy(isMatch('ababab', '{ab,c}+')); + expect_truthy(isMatch('ababc', '{ab,c}+')); + expect_truthy(isMatch('abcab', '{ab,c}+')); + expect_truthy(isMatch('abcc', '{ab,c}+')); + expect_truthy(isMatch('cabab', '{ab,c}+')); + expect_truthy(isMatch('cabc', '{ab,c}+')); + expect_truthy(isMatch('ccab', '{ab,c}+')); + expect_truthy(isMatch('ccc', '{ab,c}+')); + expect_truthy(isMatch('ccc', '{a,b,c}+')); + + expect_truthy(isMatch('a', '{a,b,c}+')); + expect_truthy(isMatch('b', '{a,b,c}+')); + expect_truthy(isMatch('c', '{a,b,c}+')); + expect_truthy(isMatch('aa', '{a,b,c}+')); + expect_truthy(isMatch('ab', '{a,b,c}+')); + expect_truthy(isMatch('ac', '{a,b,c}+')); + expect_truthy(isMatch('ba', '{a,b,c}+')); + expect_truthy(isMatch('bb', '{a,b,c}+')); + expect_truthy(isMatch('bc', '{a,b,c}+')); + expect_truthy(isMatch('ca', '{a,b,c}+')); + expect_truthy(isMatch('cb', '{a,b,c}+')); + expect_truthy(isMatch('cc', '{a,b,c}+')); + expect_truthy(isMatch('aaa', '{a,b,c}+')); + expect_truthy(isMatch('aab', '{a,b,c}+')); + expect_truthy(isMatch('abc', '{a,b,c}+')); + }); + + test('should support braces', () => { + expect_truthy(isMatch('a', '{a,b,c}')); + expect_truthy(isMatch('b', '{a,b,c}')); + expect_truthy(isMatch('c', '{a,b,c}')); + expect_truthy(!isMatch('aa', '{a,b,c}')); + expect_truthy(!isMatch('bb', '{a,b,c}')); + expect_truthy(!isMatch('cc', '{a,b,c}')); + }); + + test('should match special chars and expand ranges in parentheses', () => { + const expandRange = (a, b) => `(${fill(a, b, { toRegex: true })})`; + + expect_truthy(!isMatch('foo/bar - 1', '*/* {4..10}', { expandRange })); + expect_truthy(!isMatch('foo/bar - copy (1)', '*/* - * \\({4..10}\\)', { expandRange })); + expect_truthy(!isMatch('foo/bar (1)', '*/* \\({4..10}\\)', { expandRange })); + expect_truthy(isMatch('foo/bar (4)', '*/* \\({4..10}\\)', { expandRange })); + expect_truthy(isMatch('foo/bar (7)', '*/* \\({4..10}\\)', { expandRange })); + expect_truthy(!isMatch('foo/bar (42)', '*/* \\({4..10}\\)', { expandRange })); + expect_truthy(isMatch('foo/bar (42)', '*/* \\({4..43}\\)', { expandRange })); + expect_truthy(isMatch('foo/bar - copy [1]', '*/* \\[{0..5}\\]', { expandRange })); + expect_truthy(isMatch('foo/bar - foo + bar - copy [1]', '*/* \\[{0..5}\\]', { expandRange })); + expect_truthy(!isMatch('foo/bar - 1', '*/* \\({4..10}\\)', { expandRange })); + expect_truthy(!isMatch('foo/bar - copy (1)', '*/* \\({4..10}\\)', { expandRange })); + expect_truthy(!isMatch('foo/bar (1)', '*/* \\({4..10}\\)', { expandRange })); + expect_truthy(isMatch('foo/bar (4)', '*/* \\({4..10}\\)', { expandRange })); + expect_truthy(isMatch('foo/bar (7)', '*/* \\({4..10}\\)', { expandRange })); + expect_truthy(!isMatch('foo/bar (42)', '*/* \\({4..10}\\)', { expandRange })); + expect_truthy(!isMatch('foo/bar - copy [1]', '*/* \\({4..10}\\)', { expandRange })); + expect_truthy(!isMatch('foo/bar - foo + bar - copy [1]', '*/* \\({4..10}\\)', { expandRange })); + }); +}); diff --git a/packages/node-utils/test/picomatch/brackets.test.ts b/packages/node-utils/test/picomatch/brackets.test.ts new file mode 100644 index 0000000..0707a2b --- /dev/null +++ b/packages/node-utils/test/picomatch/brackets.test.ts @@ -0,0 +1,55 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('brackets', () => { + describe('trailing stars', () => { + test('should support stars following brackets', () => { + expect_truthy(isMatch('a', '[a]*')); + expect_truthy(isMatch('aa', '[a]*')); + expect_truthy(isMatch('aaa', '[a]*')); + expect_truthy(isMatch('az', '[a-z]*')); + expect_truthy(isMatch('zzz', '[a-z]*')); + }); + + test('should match slashes defined in brackets', () => { + expect_truthy(isMatch('foo/bar', 'foo[/]bar')); + expect_truthy(isMatch('foo/bar/', 'foo[/]bar[/]')); + expect_truthy(isMatch('foo/bar/baz', 'foo[/]bar[/]baz')); + }); + + test('should not match slashes following brackets', () => { + expect_truthy(!isMatch('a/b', '[a]*')); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/dotfiles.test.ts b/packages/node-utils/test/picomatch/dotfiles.test.ts new file mode 100644 index 0000000..74fff5f --- /dev/null +++ b/packages/node-utils/test/picomatch/dotfiles.test.ts @@ -0,0 +1,382 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('dotfiles', () => { + describe('normal', () => { + test('should not match dotfiles by default:', () => { + expect_deepEqual(match(['.dotfile'], '*'), []); + expect_deepEqual(match(['.dotfile'], '**'), []); + expect_deepEqual(match(['a/b/c/.dotfile.md'], '*.md'), []); + expect_deepEqual(match(['a/b', 'a/.b', '.a/b', '.a/.b'], '**'), ['a/b']); + expect_deepEqual(match(['a/b/c/.dotfile'], '*.*'), []); + }); + }); + + describe('leading dot', () => { + test('should match dotfiles when a leading dot is defined in the path:', () => { + expect_deepEqual(match(['a/b/c/.dotfile.md'], '**/.*'), ['a/b/c/.dotfile.md']); + expect_deepEqual(match(['a/b/c/.dotfile.md'], '**/.*.md'), ['a/b/c/.dotfile.md']); + }); + + test('should use negation patterns on dotfiles:', () => { + expect_deepEqual(match(['.a', '.b', 'c', 'c.md'], '!.*'), ['c', 'c.md']); + expect_deepEqual(match(['.a', '.b', 'c', 'c.md'], '!.b'), ['.a', 'c', 'c.md']); + }); + + test('should match dotfiles when there is a leading dot:', () => { + const opts = { dot: true }; + expect_deepEqual(match(['.dotfile'], '*', opts), ['.dotfile']); + expect_deepEqual(match(['.dotfile'], '**', opts), ['.dotfile']); + expect_deepEqual(match(['a/b', 'a/.b', '.a/b', '.a/.b'], '**', opts), ['a/b', 'a/.b', '.a/b', '.a/.b']); + expect_deepEqual(match(['a/b', 'a/.b', 'a/.b', '.a/.b'], 'a/{.*,**}', opts), ['a/b', 'a/.b']); + expect_deepEqual(match(['a/b', 'a/.b', 'a/.b', '.a/.b'], '{.*,**}', {}), ['a/b']); + expect_deepEqual(match(['a/b', 'a/.b', 'a/.b', '.a/.b'], '{.*,**}', opts), ['a/b', 'a/.b', '.a/.b']); + expect_deepEqual(match(['.dotfile'], '.dotfile', opts), ['.dotfile']); + expect_deepEqual(match(['.dotfile.md'], '.*.md', opts), ['.dotfile.md']); + }); + + test('should match dotfiles when there is not a leading dot:', () => { + const opts = { dot: true }; + expect_deepEqual(match(['.dotfile'], '*.*', opts), ['.dotfile']); + expect_deepEqual(match(['.a', '.b', 'c', 'c.md'], '*.*', opts), ['.a', '.b', 'c.md']); + expect_deepEqual(match(['.dotfile'], '*.md', opts), []); + expect_deepEqual(match(['.verb.txt'], '*.md', opts), []); + expect_deepEqual(match(['a/b/c/.dotfile'], '*.md', opts), []); + expect_deepEqual(match(['a/b/c/.dotfile.md'], '*.md', opts), []); + expect_deepEqual(match(['a/b/c/.verb.md'], '**/*.md', opts), ['a/b/c/.verb.md']); + expect_deepEqual(match(['foo.md'], '*.md', opts), ['foo.md']); + }); + + test('should use negation patterns on dotfiles:', () => { + expect_deepEqual(match(['.a', '.b', 'c', 'c.md'], '!.*'), ['c', 'c.md']); + expect_deepEqual(match(['.a', '.b', 'c', 'c.md'], '!(.*)'), ['c', 'c.md']); + expect_deepEqual(match(['.a', '.b', 'c', 'c.md'], '!(.*)*'), ['c', 'c.md']); + expect_deepEqual(match(['.a', '.b', 'c', 'c.md'], '!*.*'), ['.a', '.b', 'c']); + }); + }); + + describe('options.dot', () => { + test('should match dotfiles when `options.dot` is true:', () => { + const fixtures = ['a/./b', 'a/../b', 'a/c/b', 'a/.d/b']; + expect_deepEqual(match(['.dotfile'], '*.*', { dot: true }), ['.dotfile']); + expect_deepEqual(match(['.dotfile'], '*.md', { dot: true }), []); + expect_deepEqual(match(['.dotfile'], '.dotfile', { dot: true }), ['.dotfile']); + expect_deepEqual(match(['.dotfile.md'], '.*.md', { dot: true }), ['.dotfile.md']); + expect_deepEqual(match(['.verb.txt'], '*.md', { dot: true }), []); + expect_deepEqual(match(['.verb.txt'], '*.md', { dot: true }), []); + expect_deepEqual(match(['a/b/c/.dotfile'], '*.md', { dot: true }), []); + expect_deepEqual(match(['a/b/c/.dotfile.md'], '**/*.md', { dot: true }), ['a/b/c/.dotfile.md']); + expect_deepEqual(match(['a/b/c/.dotfile.md'], '**/.*', { dot: false }), ['a/b/c/.dotfile.md']); + expect_deepEqual(match(['a/b/c/.dotfile.md'], '**/.*.md', { dot: false }), ['a/b/c/.dotfile.md']); + expect_deepEqual(match(['a/b/c/.dotfile.md'], '*.md', { dot: false }), []); + expect_deepEqual(match(['a/b/c/.dotfile.md'], '*.md', { dot: true }), []); + expect_deepEqual(match(['a/b/c/.verb.md'], '**/*.md', { dot: true }), ['a/b/c/.verb.md']); + expect_deepEqual(match(['d.md'], '*.md', { dot: true }), ['d.md']); + expect_deepEqual(match(fixtures, 'a/*/b', { dot: true }), ['a/c/b', 'a/.d/b']); + expect_deepEqual(match(fixtures, 'a/.*/b'), ['a/.d/b']); + expect_deepEqual(match(fixtures, 'a/.*/b', { dot: true }), ['a/.d/b']); + }); + + test('should match dotfiles when `options.dot` is true', () => { + expect_truthy(isMatch('.dot', '**/*dot', { dot: true })); + expect_truthy(isMatch('.dot', '*dot', { dot: true })); + expect_truthy(isMatch('.dot', '?dot', { dot: true })); + expect_truthy(isMatch('.dotfile.js', '.*.js', { dot: true })); + expect_truthy(isMatch('/a/b/.dot', '/**/*dot', { dot: true })); + expect_truthy(isMatch('/a/b/.dot', '**/*dot', { dot: true })); + expect_truthy(isMatch('/a/b/.dot', '**/.[d]ot', { dot: true })); + expect_truthy(isMatch('/a/b/.dot', '**/?dot', { dot: true })); + expect_truthy(isMatch('/a/b/.dot', '/**/.[d]ot', { dot: true })); + expect_truthy(isMatch('/a/b/.dot', '/**/?dot', { dot: true })); + expect_truthy(isMatch('a/b/.dot', '**/*dot', { dot: true })); + expect_truthy(isMatch('a/b/.dot', '**/.[d]ot', { dot: true })); + expect_truthy(isMatch('a/b/.dot', '**/?dot', { dot: true })); + }); + + test('should not match dotfiles when `options.dot` is false', () => { + expect_truthy(!isMatch('a/b/.dot', '**/*dot', { dot: false })); + expect_truthy(!isMatch('a/b/.dot', '**/?dot', { dot: false })); + }); + + test('should not match dotfiles when `.dot` is not defined and a dot is not in the glob pattern', () => { + expect_truthy(!isMatch('a/b/.dot', '**/*dot')); + expect_truthy(!isMatch('a/b/.dot', '**/?dot')); + }); + }); + + describe('valid dotfiles', () => { + test('micromatch issue#63 (dots)', () => { + expect_truthy(!isMatch('/aaa/.git/foo', '/aaa/**/*')); + expect_truthy(!isMatch('/aaa/bbb/.git', '/aaa/bbb/*')); + expect_truthy(!isMatch('/aaa/bbb/.git', '/aaa/bbb/**')); + expect_truthy(!isMatch('/aaa/bbb/ccc/.git', '/aaa/bbb/**')); + expect_truthy(!isMatch('aaa/bbb/.git', 'aaa/bbb/**')); + expect_truthy(isMatch('/aaa/bbb/', '/aaa/bbb/**')); + expect_truthy(isMatch('/aaa/bbb/foo', '/aaa/bbb/**')); + + expect_truthy(isMatch('/aaa/.git/foo', '/aaa/**/*', { dot: true })); + expect_truthy(isMatch('/aaa/bbb/.git', '/aaa/bbb/*', { dot: true })); + expect_truthy(isMatch('/aaa/bbb/.git', '/aaa/bbb/**', { dot: true })); + expect_truthy(isMatch('/aaa/bbb/ccc/.git', '/aaa/bbb/**', { dot: true })); + expect_truthy(isMatch('aaa/bbb/.git', 'aaa/bbb/**', { dot: true })); + }); + + test('should not match dotfiles with single stars by default', () => { + expect_truthy(isMatch('foo', '*')); + expect_truthy(isMatch('foo/bar', '*/*')); + expect_truthy(!isMatch('.foo', '*')); + expect_truthy(!isMatch('.foo/bar', '*/*')); + expect_truthy(!isMatch('.foo/.bar', '*/*')); + expect_truthy(!isMatch('foo/.bar', '*/*')); + expect_truthy(!isMatch('foo/.bar/baz', '*/*/*')); + }); + + test('should work with dots in the path', () => { + expect_truthy(isMatch('../test.js', '../*.js')); + expect_truthy(isMatch('../.test.js', '../*.js', { dot: true })); + expect_truthy(!isMatch('../.test.js', '../*.js')); + }); + + test('should not match dotfiles with globstar by default', () => { + expect_truthy(!isMatch('.foo', '**/**')); + expect_truthy(!isMatch('.foo', '**')); + expect_truthy(!isMatch('.foo', '**/*')); + expect_truthy(!isMatch('bar/.foo', '**/*')); + expect_truthy(!isMatch('.bar', '**/*')); + expect_truthy(!isMatch('foo/.bar', '**/*')); + expect_truthy(!isMatch('foo/.bar', '**/*a*')); + }); + + test('should match dotfiles when a leading dot is in the pattern', () => { + expect_truthy(!isMatch('foo', '**/.*a*')); + expect_truthy(isMatch('.bar', '**/.*a*')); + expect_truthy(isMatch('foo/.bar', '**/.*a*')); + expect_truthy(isMatch('.foo', '**/.*')); + + expect_truthy(!isMatch('foo', '.*a*')); + expect_truthy(isMatch('.bar', '.*a*')); + expect_truthy(!isMatch('bar', '.*a*')); + + expect_truthy(!isMatch('foo', '.b*')); + expect_truthy(isMatch('.bar', '.b*')); + expect_truthy(!isMatch('bar', '.b*')); + + expect_truthy(!isMatch('foo', '.*r')); + expect_truthy(isMatch('.bar', '.*r')); + expect_truthy(!isMatch('bar', '.*r')); + }); + + test('should not match a dot when the dot is not explicitly defined', () => { + expect_truthy(!isMatch('.dot', '**/*dot')); + expect_truthy(!isMatch('.dot', '**/?dot')); + expect_truthy(!isMatch('.dot', '*/*dot')); + expect_truthy(!isMatch('.dot', '*/?dot')); + expect_truthy(!isMatch('.dot', '*dot')); + expect_truthy(!isMatch('.dot', '/*dot')); + expect_truthy(!isMatch('.dot', '/?dot')); + expect_truthy(!isMatch('/.dot', '**/*dot')); + expect_truthy(!isMatch('/.dot', '**/?dot')); + expect_truthy(!isMatch('/.dot', '*/*dot')); + expect_truthy(!isMatch('/.dot', '*/?dot')); + expect_truthy(!isMatch('/.dot', '/*dot')); + expect_truthy(!isMatch('/.dot', '/?dot')); + expect_truthy(!isMatch('abc/.dot', '*/*dot')); + expect_truthy(!isMatch('abc/.dot', '*/?dot')); + expect_truthy(!isMatch('abc/.dot', 'abc/*dot')); + expect_truthy(!isMatch('abc/abc/.dot', '**/*dot')); + expect_truthy(!isMatch('abc/abc/.dot', '**/?dot')); + }); + + test('should not match leading dots with question marks', () => { + expect_truthy(!isMatch('.dot', '?dot')); + expect_truthy(!isMatch('/.dot', '/?dot')); + expect_truthy(!isMatch('abc/.dot', 'abc/?dot')); + }); + + test('should match double dots when defined in pattern', () => { + expect_truthy(!isMatch('../../b', '**/../*')); + expect_truthy(!isMatch('../../b', '*/../*')); + expect_truthy(!isMatch('../../b', '../*')); + expect_truthy(!isMatch('../abc', '*/../*')); + expect_truthy(!isMatch('../abc', '*/../*')); + expect_truthy(!isMatch('../c/d', '**/../*')); + expect_truthy(!isMatch('../c/d', '*/../*')); + expect_truthy(!isMatch('../c/d', '../*')); + expect_truthy(!isMatch('abc', '**/../*')); + expect_truthy(!isMatch('abc', '*/../*')); + expect_truthy(!isMatch('abc', '../*')); + expect_truthy(!isMatch('abc/../abc', '../*')); + expect_truthy(!isMatch('abc/../abc', '../*')); + expect_truthy(!isMatch('abc/../', '**/../*')); + + expect_truthy(isMatch('..', '..')); + expect_truthy(isMatch('../b', '../*')); + expect_truthy(isMatch('../../b', '../../*')); + expect_truthy(isMatch('../../..', '../../..')); + expect_truthy(isMatch('../abc', '**/../*')); + expect_truthy(isMatch('../abc', '../*')); + expect_truthy(isMatch('abc/../abc', '**/../*')); + expect_truthy(isMatch('abc/../abc', '*/../*')); + expect_truthy(isMatch('abc/../abc', '**/../*')); + expect_truthy(isMatch('abc/../abc', '*/../*')); + }); + + test('should not match double dots when not defined in pattern', async () => { + expect_truthy(!isMatch('../abc', '**/*')); + expect_truthy(!isMatch('../abc', '**/**/**')); + expect_truthy(!isMatch('../abc', '**/**/abc')); + expect_truthy(!isMatch('../abc', '**/**/abc/**')); + expect_truthy(!isMatch('../abc', '**/*/*')); + expect_truthy(!isMatch('../abc', '**/abc/**')); + expect_truthy(!isMatch('../abc', '*/*')); + expect_truthy(!isMatch('../abc', '*/abc/**')); + expect_truthy(!isMatch('abc/../abc', '**/*')); + expect_truthy(!isMatch('abc/../abc', '**/*/*')); + expect_truthy(!isMatch('abc/../abc', '**/*/abc')); + expect_truthy(!isMatch('abc/../abc', '*/**/*')); + expect_truthy(!isMatch('abc/../abc', '*/*/*')); + expect_truthy(!isMatch('abc/../abc', 'abc/**/*')); + expect_truthy(!isMatch('abc/../abc', '**/**/*')); + expect_truthy(!isMatch('abc/../abc', '**/*/*')); + expect_truthy(!isMatch('abc/../abc', '*/**/*')); + expect_truthy(!isMatch('abc/../abc', '*/*/*')); + + expect_truthy(!isMatch('../abc', '**/**/**', { dot: true })); + expect_truthy(!isMatch('../abc', '**/**/abc', { dot: true })); + expect_truthy(!isMatch('../abc', '**/**/abc/**', { dot: true })); + expect_truthy(!isMatch('../abc', '**/abc/**', { dot: true })); + expect_truthy(!isMatch('../abc', '*/abc/**', { dot: true })); + + expect_truthy(!isMatch('../abc', '**/*/*', { dot: true })); + expect_truthy(!isMatch('../abc', '*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '**/*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '*/*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '**/*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '*/*/*', { dot: true })); + expect_truthy(!isMatch('abc/..', '**/*', { dot: true })); + expect_truthy(!isMatch('abc/..', '*/*', { dot: true })); + expect_truthy(!isMatch('abc/abc/..', '*/**/*', { dot: true })); + + expect_truthy(!isMatch('abc/../abc', 'abc/**/*')); + expect_truthy(!isMatch('abc/../abc', 'abc/**/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', 'abc/**/*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', 'abc/*/*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', 'abc/**/*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', 'abc/*/*/*', { dot: true })); + expect_truthy(!isMatch('abc/..', 'abc/**/*', { dot: true })); + expect_truthy(!isMatch('abc/..', 'abc/*/*', { dot: true })); + expect_truthy(!isMatch('abc/abc/..', 'abc/*/**/*', { dot: true })); + + expect_truthy(!isMatch('../abc', '**/*/*', { dot: true })); + expect_truthy(!isMatch('../abc', '*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '**/*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '*/*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '**/*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '*/*/*', { dot: true })); + expect_truthy(!isMatch('abc/..', '**/*', { dot: true })); + expect_truthy(!isMatch('abc/..', '*/*', { dot: true })); + expect_truthy(!isMatch('abc/abc/..', '*/**/*', { dot: true })); + + expect_truthy(!isMatch('abc/../abc', 'abc/**/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', 'abc/**/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', 'abc/**/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', 'abc/*/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', 'abc/**/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', 'abc/*/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', 'abc/**/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', 'abc/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/abc/..', 'abc/*/**/*', { strictSlashes: true })); + }); + + test('should not match single exclusive dots when not defined in pattern', async () => { + expect_truthy(!isMatch('.', '**')); + expect_truthy(!isMatch('abc/./abc', '**')); + expect_truthy(!isMatch('abc/abc/.', '**')); + expect_truthy(!isMatch('abc/abc/./abc', '**')); + + expect_truthy(!isMatch('.', '**', { dot: true })); + expect_truthy(!isMatch('..', '**', { dot: true })); + expect_truthy(!isMatch('../', '**', { dot: true })); + expect_truthy(!isMatch('/../', '**', { dot: true })); + expect_truthy(!isMatch('/..', '**', { dot: true })); + expect_truthy(!isMatch('abc/./abc', '**', { dot: true })); + expect_truthy(!isMatch('abc/abc/.', '**', { dot: true })); + expect_truthy(!isMatch('abc/abc/./abc', '**', { dot: true })); + }); + + test('should match leading dots in root path when glob is prefixed with **/', () => { + expect_truthy(!isMatch('.abc/.abc', '**/.abc/**')); + expect_truthy(isMatch('.abc', '**/.abc/**')); + expect_truthy(isMatch('.abc/', '**/.abc/**')); + expect_truthy(isMatch('.abc/abc', '**/.abc/**')); + expect_truthy(isMatch('.abc/abc/b', '**/.abc/**')); + expect_truthy(isMatch('abc/.abc/b', '**/.abc/**')); + expect_truthy(isMatch('abc/abc/.abc', '**/.abc')); + expect_truthy(isMatch('abc/abc/.abc', '**/.abc/**')); + expect_truthy(isMatch('abc/abc/.abc/', '**/.abc/**')); + expect_truthy(isMatch('abc/abc/.abc/abc', '**/.abc/**')); + expect_truthy(isMatch('abc/abc/.abc/c/d', '**/.abc/**')); + expect_truthy(isMatch('abc/abc/.abc/c/d/e', '**/.abc/**')); + }); + + test('should match a dot when the dot is explicitly defined', () => { + expect_truthy(isMatch('/.dot', '**/.dot*')); + expect_truthy(isMatch('aaa/bbb/.dot', '**/.dot*')); + expect_truthy(isMatch('aaa/.dot', '*/.dot*')); + expect_truthy(isMatch('.aaa.bbb', '.*.*')); + expect_truthy(isMatch('.aaa.bbb', '.*.*')); + expect_truthy(!isMatch('.aaa.bbb/', '.*.*', { strictSlashes: true })); + expect_truthy(!isMatch('.aaa.bbb', '.*.*/')); + expect_truthy(isMatch('.aaa.bbb/', '.*.*/')); + expect_truthy(isMatch('.aaa.bbb/', '.*.*{,/}')); + expect_truthy(isMatch('.aaa.bbb', '.*.bbb')); + expect_truthy(isMatch('.dotfile.js', '.*.js')); + expect_truthy(isMatch('.dot', '.*ot')); + expect_truthy(isMatch('.dot.bbb.ccc', '.*ot.*.*')); + expect_truthy(isMatch('.dot', '.d?t')); + expect_truthy(isMatch('.dot', '.dot*')); + expect_truthy(isMatch('/.dot', '/.dot*')); + }); + + test('should match dots defined in brackets', () => { + expect_truthy(isMatch('/.dot', '**/.[d]ot')); + expect_truthy(isMatch('aaa/.dot', '**/.[d]ot')); + expect_truthy(isMatch('aaa/bbb/.dot', '**/.[d]ot')); + expect_truthy(isMatch('aaa/.dot', '*/.[d]ot')); + expect_truthy(isMatch('.dot', '.[d]ot')); + expect_truthy(isMatch('.dot', '.[d]ot')); + expect_truthy(isMatch('/.dot', '/.[d]ot')); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/dots-invalid.test.ts b/packages/node-utils/test/picomatch/dots-invalid.test.ts new file mode 100644 index 0000000..a817b22 --- /dev/null +++ b/packages/node-utils/test/picomatch/dots-invalid.test.ts @@ -0,0 +1,1800 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('invalid (exclusive) dots', () => { + describe('double dots', () => { + describe('no options', () => { + describe('should not match leading double-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('../abc', '*/*')); + expect_truthy(!isMatch('../abc', '*/abc')); + expect_truthy(!isMatch('../abc', '*/abc/*')); + }); + + test('with dot + single star', () => { + expect_truthy(!isMatch('../abc', '.*/*')); + expect_truthy(!isMatch('../abc', '.*/abc')); + + expect_truthy(!isMatch('../abc', '*./*')); + expect_truthy(!isMatch('../abc', '*./abc')); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('../abc', '**')); + expect_truthy(!isMatch('../abc', '**/**')); + expect_truthy(!isMatch('../abc', '**/**/**')); + + expect_truthy(!isMatch('../abc', '**/abc')); + expect_truthy(!isMatch('../abc', '**/abc/**')); + + expect_truthy(!isMatch('../abc', 'abc/**')); + expect_truthy(!isMatch('../abc', 'abc/**/**')); + expect_truthy(!isMatch('../abc', 'abc/**/**/**')); + + expect_truthy(!isMatch('../abc', '**/abc')); + expect_truthy(!isMatch('../abc', '**/abc/**')); + expect_truthy(!isMatch('../abc', '**/abc/**/**')); + + expect_truthy(!isMatch('../abc', '**/**/abc/**')); + expect_truthy(!isMatch('../abc', '**/**/abc/**/**')); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('../abc', '.**')); + expect_truthy(!isMatch('../abc', '.**/**')); + expect_truthy(!isMatch('../abc', '.**/abc')); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('../abc', '*.*/**')); + expect_truthy(!isMatch('../abc', '*.*/abc')); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('../abc', '**./**')); + expect_truthy(!isMatch('../abc', '**./abc')); + }); + }); + + describe('should not match nested double-dots', () => { + test('with star', () => { + expect_truthy(!isMatch('/../abc', '*/*')); + expect_truthy(!isMatch('/../abc', '/*/*')); + expect_truthy(!isMatch('/../abc', '*/*/*')); + + expect_truthy(!isMatch('abc/../abc', '*/*/*')); + expect_truthy(!isMatch('abc/../abc/abc', '*/*/*/*')); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('/../abc', '*/.*/*')); + expect_truthy(!isMatch('/../abc', '/.*/*')); + + expect_truthy(!isMatch('/../abc', '*/*.*/*')); + expect_truthy(!isMatch('/../abc', '/*.*/*')); + + expect_truthy(!isMatch('/../abc', '*/*./*')); + expect_truthy(!isMatch('/../abc', '/*./*')); + + expect_truthy(!isMatch('abc/../abc', '*/.*/*')); + expect_truthy(!isMatch('abc/../abc', '*/*.*/*')); + expect_truthy(!isMatch('abc/../abc', '*/*./*')); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('/../abc', '**')); + expect_truthy(!isMatch('/../abc', '**/**')); + expect_truthy(!isMatch('/../abc', '/**/**')); + expect_truthy(!isMatch('/../abc', '**/**/**')); + + expect_truthy(!isMatch('abc/../abc', '**/**/**')); + expect_truthy(!isMatch('abc/../abc/abc', '**/**/**/**')); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('/../abc', '**/.**/**')); + expect_truthy(!isMatch('/../abc', '/.**/**')); + + expect_truthy(!isMatch('abc/../abc', '**/.**/**')); + expect_truthy(!isMatch('abc/../abc', '/.**/**')); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('/../abc', '**/**./**')); + expect_truthy(!isMatch('/../abc', '/**./**')); + + expect_truthy(!isMatch('abc/../abc', '**/**./**')); + expect_truthy(!isMatch('abc/../abc', '/**./**')); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('/../abc', '**/**.**/**')); + expect_truthy(!isMatch('/../abc', '**/*.*/**')); + + expect_truthy(!isMatch('/../abc', '/**.**/**')); + expect_truthy(!isMatch('/../abc', '/*.*/**')); + + expect_truthy(!isMatch('abc/../abc', '**/**.**/**')); + expect_truthy(!isMatch('abc/../abc', '**/*.*/**')); + + expect_truthy(!isMatch('abc/../abc', '/**.**/**')); + expect_truthy(!isMatch('abc/../abc', '/*.*/**')); + }); + }); + + describe('should not match trailing double-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('abc/..', '*/*')); + expect_truthy(!isMatch('abc/..', '*/*/')); + expect_truthy(!isMatch('abc/..', '*/*/*')); + + expect_truthy(!isMatch('abc/../', '*/*')); + expect_truthy(!isMatch('abc/../', '*/*/')); + expect_truthy(!isMatch('abc/../', '*/*/*')); + + expect_truthy(!isMatch('abc/../abc/../', '*/*/*/*')); + expect_truthy(!isMatch('abc/../abc/../', '*/*/*/*/')); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/*/*/*/*')); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('abc/..', '*/.*')); + expect_truthy(!isMatch('abc/..', '*/.*/')); + expect_truthy(!isMatch('abc/..', '*/.*/*')); + + expect_truthy(!isMatch('abc/../', '*/.*')); + expect_truthy(!isMatch('abc/../', '*/.*/')); + expect_truthy(!isMatch('abc/../', '*/.*/*')); + + expect_truthy(!isMatch('abc/../abc/../', '*/.*/*/.*')); + expect_truthy(!isMatch('abc/../abc/../', '*/.*/*/.*/')); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/.*/*/.*/*')); + }); + + test('with star + dot', () => { + expect_truthy(!isMatch('abc/..', '*/*.')); + expect_truthy(!isMatch('abc/..', '*/*./')); + expect_truthy(!isMatch('abc/..', '*/*./*')); + + expect_truthy(!isMatch('abc/../', '*/*.')); + expect_truthy(!isMatch('abc/../', '*/*./')); + expect_truthy(!isMatch('abc/../', '*/*./*')); + + expect_truthy(!isMatch('abc/../abc/../', '*/*./*/*.')); + expect_truthy(!isMatch('abc/../abc/../', '*/*./*/*./')); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/*./*/*./*')); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('abc/..', '**/**')); + expect_truthy(!isMatch('abc/..', '**/**/')); + expect_truthy(!isMatch('abc/..', '**/**/**')); + + expect_truthy(!isMatch('abc/../', '**/**')); + expect_truthy(!isMatch('abc/../', '**/**/')); + expect_truthy(!isMatch('abc/../', '**/**/**')); + + expect_truthy(!isMatch('abc/../abc/../', '**/**/**/**')); + expect_truthy(!isMatch('abc/../abc/../', '**/**/**/**/')); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**/**/**/**')); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('abc/..', '**/.**')); + expect_truthy(!isMatch('abc/..', '**/.**/')); + expect_truthy(!isMatch('abc/..', '**/.**/**')); + + expect_truthy(!isMatch('abc/../', '**/.**')); + expect_truthy(!isMatch('abc/../', '**/.**/')); + expect_truthy(!isMatch('abc/../', '**/.**/**')); + + expect_truthy(!isMatch('abc/../abc/../', '**/.**/**/.**')); + expect_truthy(!isMatch('abc/../abc/../', '**/.**/**/.**/')); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/.**/**/.**/**')); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('abc/..', '**/**.**')); + expect_truthy(!isMatch('abc/..', '**/**.**/')); + expect_truthy(!isMatch('abc/..', '**/**.**/**')); + + expect_truthy(!isMatch('abc/../', '**/**.**')); + expect_truthy(!isMatch('abc/../', '**/**.**/')); + expect_truthy(!isMatch('abc/../', '**/**.**/**')); + + expect_truthy(!isMatch('abc/../abc/../', '**/**.**/**/**.**')); + expect_truthy(!isMatch('abc/../abc/../', '**/**.**/**/**.**/')); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**.**/**/.**/**')); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('abc/..', '**/**.')); + expect_truthy(!isMatch('abc/..', '**/**./')); + expect_truthy(!isMatch('abc/..', '**/**./**')); + + expect_truthy(!isMatch('abc/../', '**/**.')); + expect_truthy(!isMatch('abc/../', '**/**./')); + expect_truthy(!isMatch('abc/../', '**/**./**')); + + expect_truthy(!isMatch('abc/../abc/../', '**/**./**/**.')); + expect_truthy(!isMatch('abc/../abc/../', '**/**./**/**./')); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**./**/**./**')); + }); + }); + }); + + describe('options = { dot: true }', () => { + describe('should not match leading double-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('../abc', '*/*', { dot: true })); + expect_truthy(!isMatch('../abc', '*/abc', { dot: true })); + expect_truthy(!isMatch('../abc', '*/abc/*', { dot: true })); + }); + + test('with dot + single star', () => { + expect_truthy(!isMatch('../abc', '.*/*', { dot: true })); + expect_truthy(!isMatch('../abc', '.*/abc', { dot: true })); + + expect_truthy(!isMatch('../abc', '*./*', { dot: true })); + expect_truthy(!isMatch('../abc', '*./abc', { dot: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('../abc', '**', { dot: true })); + expect_truthy(!isMatch('../abc', '**/**', { dot: true })); + expect_truthy(!isMatch('../abc', '**/**/**', { dot: true })); + + expect_truthy(!isMatch('../abc', '**/abc', { dot: true })); + expect_truthy(!isMatch('../abc', '**/abc/**', { dot: true })); + + expect_truthy(!isMatch('../abc', 'abc/**', { dot: true })); + expect_truthy(!isMatch('../abc', 'abc/**/**', { dot: true })); + expect_truthy(!isMatch('../abc', 'abc/**/**/**', { dot: true })); + + expect_truthy(!isMatch('../abc', '**/abc', { dot: true })); + expect_truthy(!isMatch('../abc', '**/abc/**', { dot: true })); + expect_truthy(!isMatch('../abc', '**/abc/**/**', { dot: true })); + + expect_truthy(!isMatch('../abc', '**/**/abc/**', { dot: true })); + expect_truthy(!isMatch('../abc', '**/**/abc/**/**', { dot: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('../abc', '.**', { dot: true })); + expect_truthy(!isMatch('../abc', '.**/**', { dot: true })); + expect_truthy(!isMatch('../abc', '.**/abc', { dot: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('../abc', '*.*/**', { dot: true })); + expect_truthy(!isMatch('../abc', '*.*/abc', { dot: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('../abc', '**./**', { dot: true })); + expect_truthy(!isMatch('../abc', '**./abc', { dot: true })); + }); + }); + + describe('should not match nested double-dots', () => { + test('with star', () => { + expect_truthy(!isMatch('/../abc', '*/*', { dot: true })); + expect_truthy(!isMatch('/../abc', '/*/*', { dot: true })); + expect_truthy(!isMatch('/../abc', '*/*/*', { dot: true })); + + expect_truthy(!isMatch('abc/../abc', '*/*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc/abc', '*/*/*/*', { dot: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('/../abc', '*/.*/*', { dot: true })); + expect_truthy(!isMatch('/../abc', '/.*/*', { dot: true })); + + expect_truthy(!isMatch('/../abc', '*/*.*/*', { dot: true })); + expect_truthy(!isMatch('/../abc', '/*.*/*', { dot: true })); + + expect_truthy(!isMatch('/../abc', '*/*./*', { dot: true })); + expect_truthy(!isMatch('/../abc', '/*./*', { dot: true })); + + expect_truthy(!isMatch('abc/../abc', '*/.*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '*/*.*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '*/*./*', { dot: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('/../abc', '**', { dot: true })); + expect_truthy(!isMatch('/../abc', '**/**', { dot: true })); + expect_truthy(!isMatch('/../abc', '/**/**', { dot: true })); + expect_truthy(!isMatch('/../abc', '**/**/**', { dot: true })); + + expect_truthy(!isMatch('abc/../abc', '**/**/**', { dot: true })); + expect_truthy(!isMatch('abc/../abc/abc', '**/**/**/**', { dot: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('/../abc', '**/.**/**', { dot: true })); + expect_truthy(!isMatch('/../abc', '/.**/**', { dot: true })); + + expect_truthy(!isMatch('abc/../abc', '**/.**/**', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '/.**/**', { dot: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('/../abc', '**/**./**', { dot: true })); + expect_truthy(!isMatch('/../abc', '/**./**', { dot: true })); + + expect_truthy(!isMatch('abc/../abc', '**/**./**', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '/**./**', { dot: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('/../abc', '**/**.**/**', { dot: true })); + expect_truthy(!isMatch('/../abc', '**/*.*/**', { dot: true })); + + expect_truthy(!isMatch('/../abc', '/**.**/**', { dot: true })); + expect_truthy(!isMatch('/../abc', '/*.*/**', { dot: true })); + + expect_truthy(!isMatch('abc/../abc', '**/**.**/**', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '**/*.*/**', { dot: true })); + + expect_truthy(!isMatch('abc/../abc', '/**.**/**', { dot: true })); + expect_truthy(!isMatch('abc/../abc', '/*.*/**', { dot: true })); + }); + }); + + describe('should not match trailing double-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('abc/..', '*/*', { dot: true })); + expect_truthy(!isMatch('abc/..', '*/*/', { dot: true })); + expect_truthy(!isMatch('abc/..', '*/*/*', { dot: true })); + + expect_truthy(!isMatch('abc/../', '*/*', { dot: true })); + expect_truthy(!isMatch('abc/../', '*/*/', { dot: true })); + expect_truthy(!isMatch('abc/../', '*/*/*', { dot: true })); + + expect_truthy(!isMatch('abc/../abc/../', '*/*/*/*', { dot: true })); + expect_truthy(!isMatch('abc/../abc/../', '*/*/*/*/', { dot: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/*/*/*/*', { dot: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('abc/..', '*/.*', { dot: true })); + expect_truthy(!isMatch('abc/..', '*/.*/', { dot: true })); + expect_truthy(!isMatch('abc/..', '*/.*/*', { dot: true })); + + expect_truthy(!isMatch('abc/../', '*/.*', { dot: true })); + expect_truthy(!isMatch('abc/../', '*/.*/', { dot: true })); + expect_truthy(!isMatch('abc/../', '*/.*/*', { dot: true })); + + expect_truthy(!isMatch('abc/../abc/../', '*/.*/*/.*', { dot: true })); + expect_truthy(!isMatch('abc/../abc/../', '*/.*/*/.*/', { dot: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/.*/*/.*/*', { dot: true })); + }); + + test('with star + dot', () => { + expect_truthy(!isMatch('abc/..', '*/*.', { dot: true })); + expect_truthy(!isMatch('abc/..', '*/*./', { dot: true })); + expect_truthy(!isMatch('abc/..', '*/*./*', { dot: true })); + + expect_truthy(!isMatch('abc/../', '*/*.', { dot: true })); + expect_truthy(!isMatch('abc/../', '*/*./', { dot: true })); + expect_truthy(!isMatch('abc/../', '*/*./*', { dot: true })); + + expect_truthy(!isMatch('abc/../abc/../', '*/*./*/*.', { dot: true })); + expect_truthy(!isMatch('abc/../abc/../', '*/*./*/*./', { dot: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/*./*/*./*', { dot: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('abc/..', '**/**', { dot: true })); + expect_truthy(!isMatch('abc/..', '**/**/', { dot: true })); + expect_truthy(!isMatch('abc/..', '**/**/**', { dot: true })); + + expect_truthy(!isMatch('abc/../', '**/**', { dot: true })); + expect_truthy(!isMatch('abc/../', '**/**/', { dot: true })); + expect_truthy(!isMatch('abc/../', '**/**/**', { dot: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/**/**/**', { dot: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/**/**/**/', { dot: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**/**/**/**', { dot: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('abc/..', '**/.**', { dot: true })); + expect_truthy(!isMatch('abc/..', '**/.**/', { dot: true })); + expect_truthy(!isMatch('abc/..', '**/.**/**', { dot: true })); + + expect_truthy(!isMatch('abc/../', '**/.**', { dot: true })); + expect_truthy(!isMatch('abc/../', '**/.**/', { dot: true })); + expect_truthy(!isMatch('abc/../', '**/.**/**', { dot: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/.**/**/.**', { dot: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/.**/**/.**/', { dot: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/.**/**/.**/**', { dot: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('abc/..', '**/**.**', { dot: true })); + expect_truthy(!isMatch('abc/..', '**/**.**/', { dot: true })); + expect_truthy(!isMatch('abc/..', '**/**.**/**', { dot: true })); + + expect_truthy(!isMatch('abc/../', '**/**.**', { dot: true })); + expect_truthy(!isMatch('abc/../', '**/**.**/', { dot: true })); + expect_truthy(!isMatch('abc/../', '**/**.**/**', { dot: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/**.**/**/**.**', { dot: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/**.**/**/**.**/', { dot: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**.**/**/.**/**', { dot: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('abc/..', '**/**.', { dot: true })); + expect_truthy(!isMatch('abc/..', '**/**./', { dot: true })); + expect_truthy(!isMatch('abc/..', '**/**./**', { dot: true })); + + expect_truthy(!isMatch('abc/../', '**/**.', { dot: true })); + expect_truthy(!isMatch('abc/../', '**/**./', { dot: true })); + expect_truthy(!isMatch('abc/../', '**/**./**', { dot: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/**./**/**.', { dot: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/**./**/**./', { dot: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**./**/**./**', { dot: true })); + }); + }); + }); + + describe('options = { strictSlashes: true }', () => { + describe('should not match leading double-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('../abc', '*/*', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '*/abc', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '*/abc/*', { strictSlashes: true })); + }); + + test('with dot + single star', () => { + expect_truthy(!isMatch('../abc', '.*/*', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '.*/abc', { strictSlashes: true })); + + expect_truthy(!isMatch('../abc', '*./*', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '*./abc', { strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('../abc', '**', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/**', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('../abc', '**/abc', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/abc/**', { strictSlashes: true })); + + expect_truthy(!isMatch('../abc', 'abc/**', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', 'abc/**/**', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', 'abc/**/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('../abc', '**/abc', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/abc/**', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/abc/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('../abc', '**/**/abc/**', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/**/abc/**/**', { strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('../abc', '.**', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '.**/abc', { strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('../abc', '*.*/**', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '*.*/abc', { strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('../abc', '**./**', { strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**./abc', { strictSlashes: true })); + }); + }); + + describe('should not match nested double-dots', () => { + test('with star', () => { + expect_truthy(!isMatch('/../abc', '*/*', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '*/*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '*/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc', '*/*/*/*', { strictSlashes: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('/../abc', '*/.*/*', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/.*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('/../abc', '*/*.*/*', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/*.*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('/../abc', '*/*./*', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/*./*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '*/.*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '*/*.*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '*/*./*', { strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('/../abc', '**', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '**/**', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/**/**', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '**/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '**/**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc', '**/**/**/**', { strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('/../abc', '**/.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/.**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '**/.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '/.**/**', { strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('/../abc', '**/**./**', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/**./**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '**/**./**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '/**./**', { strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('/../abc', '**/**.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '**/*.*/**', { strictSlashes: true })); + + expect_truthy(!isMatch('/../abc', '/**.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/*.*/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '**/**.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '**/*.*/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '/**.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '/*.*/**', { strictSlashes: true })); + }); + }); + + describe('should not match trailing double-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('abc/..', '*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '*/*/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '*/*/*/*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/*/*/*/*', { strictSlashes: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('abc/..', '*/.*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/.*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/.*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '*/.*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/.*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/.*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '*/.*/*/.*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '*/.*/*/.*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/.*/*/.*/*', { strictSlashes: true })); + }); + + test('with star + dot', () => { + expect_truthy(!isMatch('abc/..', '*/*.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/*./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/*./*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '*/*.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/*./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/*./*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '*/*./*/*.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '*/*./*/*./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/*./*/*./*', { strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('abc/..', '**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/**/**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/**/**/**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**/**/**/**', { strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('abc/..', '**/.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/.**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '**/.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/.**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/.**/**/.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/.**/**/.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/.**/**/.**/**', { strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('abc/..', '**/**.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**.**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '**/**.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**.**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/**.**/**/**.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/**.**/**/**.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**.**/**/.**/**', { strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('abc/..', '**/**.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**./**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '**/**.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**./**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/**./**/**.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/**./**/**./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**./**/**./**', { strictSlashes: true })); + }); + }); + }); + + describe('options = { dot: true, strictSlashes: true }', () => { + describe('should not match leading double-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('../abc', '*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '*/abc', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '*/abc/*', { dot: true, strictSlashes: true })); + }); + + test('with dot + single star', () => { + expect_truthy(!isMatch('../abc', '.*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '.*/abc', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('../abc', '*./*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '*./abc', { dot: true, strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('../abc', '**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('../abc', '**/abc', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/abc/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('../abc', 'abc/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', 'abc/**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', 'abc/**/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('../abc', '**/abc', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/abc/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/abc/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('../abc', '**/**/abc/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**/**/abc/**/**', { dot: true, strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('../abc', '.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '.**/abc', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('../abc', '*.*/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '*.*/abc', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('../abc', '**./**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('../abc', '**./abc', { dot: true, strictSlashes: true })); + }); + }); + + describe('should not match nested double-dots', () => { + test('with star', () => { + expect_truthy(!isMatch('/../abc', '*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '*/*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '*/*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc', '*/*/*/*', { dot: true, strictSlashes: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('/../abc', '*/.*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/.*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('/../abc', '*/*.*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/*.*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('/../abc', '*/*./*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/*./*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '*/.*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '*/*.*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '*/*./*', { dot: true, strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('/../abc', '**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '**/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '**/**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc', '**/**/**/**', { dot: true, strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('/../abc', '**/.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/.**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '**/.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '/.**/**', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('/../abc', '**/**./**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/**./**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '**/**./**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '/**./**', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('/../abc', '**/**.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '**/*.*/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('/../abc', '/**.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/../abc', '/*.*/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '**/**.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '**/*.*/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc', '/**.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc', '/*.*/**', { dot: true, strictSlashes: true })); + }); + }); + + describe('should not match trailing double-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('abc/..', '*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '*/*/*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '*/*/*/*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/*/*/*/*', { dot: true, strictSlashes: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('abc/..', '*/.*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/.*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/.*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '*/.*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/.*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/.*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '*/.*/*/.*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '*/.*/*/.*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/.*/*/.*/*', { dot: true, strictSlashes: true })); + }); + + test('with star + dot', () => { + expect_truthy(!isMatch('abc/..', '*/*.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/*./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '*/*./*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '*/*.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/*./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '*/*./*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '*/*./*/*.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '*/*./*/*./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '*/*./*/*./*', { dot: true, strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('abc/..', '**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/**/**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/**/**/**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**/**/**/**', { dot: true, strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('abc/..', '**/.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/.**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '**/.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/.**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/.**/**/.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/.**/**/.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/.**/**/.**/**', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('abc/..', '**/**.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**.**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '**/**.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**.**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/**.**/**/**.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/**.**/**/**.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**.**/**/.**/**', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('abc/..', '**/**.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/..', '**/**./**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../', '**/**.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../', '**/**./**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/../abc/../', '**/**./**/**.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/../', '**/**./**/**./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/../abc/abc/../', '**/**./**/**./**', { dot: true, strictSlashes: true })); + }); + }); + }); + }); + + describe('single dots', () => { + describe('no options', () => { + describe('should not match leading single-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('./abc', '*')); + expect_truthy(!isMatch('./abc', '*/*')); + expect_truthy(!isMatch('./abc', '*/abc')); + expect_truthy(!isMatch('./abc', '*/abc/*')); + }); + + test('with dot + single star', () => { + expect_truthy(!isMatch('./abc', '.*/*')); + expect_truthy(!isMatch('./abc', '.*/abc')); + + expect_truthy(!isMatch('./abc', '*./*')); + expect_truthy(!isMatch('./abc', '*./abc')); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('./abc', '**')); + expect_truthy(!isMatch('./abc', '**/**')); + expect_truthy(!isMatch('./abc', '**/**/**')); + + expect_truthy(!isMatch('./abc', '**/abc')); + expect_truthy(!isMatch('./abc', '**/abc/**')); + + expect_truthy(!isMatch('./abc', 'abc/**')); + expect_truthy(!isMatch('./abc', 'abc/**/**')); + expect_truthy(!isMatch('./abc', 'abc/**/**/**')); + + expect_truthy(!isMatch('./abc', '**/abc')); + expect_truthy(!isMatch('./abc', '**/abc/**')); + expect_truthy(!isMatch('./abc', '**/abc/**/**')); + + expect_truthy(!isMatch('./abc', '**/**/abc/**')); + expect_truthy(!isMatch('./abc', '**/**/abc/**/**')); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('./abc', '.**')); + expect_truthy(!isMatch('./abc', '.**/**')); + expect_truthy(!isMatch('./abc', '.**/abc')); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('./abc', '*.*/**')); + expect_truthy(!isMatch('./abc', '*.*/abc')); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('./abc', '**./**')); + expect_truthy(!isMatch('./abc', '**./abc')); + }); + }); + + describe('should not match nested single-dots', () => { + test('with star', () => { + expect_truthy(!isMatch('/./abc', '*/*')); + expect_truthy(!isMatch('/./abc', '/*/*')); + expect_truthy(!isMatch('/./abc', '*/*/*')); + + expect_truthy(!isMatch('abc/./abc', '*/*/*')); + expect_truthy(!isMatch('abc/./abc/abc', '*/*/*/*')); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('/./abc', '*/.*/*')); + expect_truthy(!isMatch('/./abc', '/.*/*')); + + expect_truthy(!isMatch('/./abc', '*/*.*/*')); + expect_truthy(!isMatch('/./abc', '/*.*/*')); + + expect_truthy(!isMatch('/./abc', '*/*./*')); + expect_truthy(!isMatch('/./abc', '/*./*')); + + expect_truthy(!isMatch('abc/./abc', '*/.*/*')); + expect_truthy(!isMatch('abc/./abc', '*/*.*/*')); + expect_truthy(!isMatch('abc/./abc', '*/*./*')); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('/./abc', '**')); + expect_truthy(!isMatch('/./abc', '**/**')); + expect_truthy(!isMatch('/./abc', '/**/**')); + expect_truthy(!isMatch('/./abc', '**/**/**')); + + expect_truthy(!isMatch('abc/./abc', '**/**/**')); + expect_truthy(!isMatch('abc/./abc/abc', '**/**/**/**')); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('/./abc', '**/.**/**')); + expect_truthy(!isMatch('/./abc', '/.**/**')); + + expect_truthy(!isMatch('abc/./abc', '**/.**/**')); + expect_truthy(!isMatch('abc/./abc', '/.**/**')); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('/./abc', '**/**./**')); + expect_truthy(!isMatch('/./abc', '/**./**')); + + expect_truthy(!isMatch('abc/./abc', '**/**./**')); + expect_truthy(!isMatch('abc/./abc', '/**./**')); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('/./abc', '**/**.**/**')); + expect_truthy(!isMatch('/./abc', '**/*.*/**')); + + expect_truthy(!isMatch('/./abc', '/**.**/**')); + expect_truthy(!isMatch('/./abc', '/*.*/**')); + + expect_truthy(!isMatch('abc/./abc', '**/**.**/**')); + expect_truthy(!isMatch('abc/./abc', '**/*.*/**')); + + expect_truthy(!isMatch('abc/./abc', '/**.**/**')); + expect_truthy(!isMatch('abc/./abc', '/*.*/**')); + }); + }); + + describe('should not match trailing single-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('abc/.', '*/*')); + expect_truthy(!isMatch('abc/.', '*/*/')); + expect_truthy(!isMatch('abc/.', '*/*/*')); + + expect_truthy(!isMatch('abc/./', '*/*')); + expect_truthy(!isMatch('abc/./', '*/*/')); + expect_truthy(!isMatch('abc/./', '*/*/*')); + + expect_truthy(!isMatch('abc/./abc/./', '*/*/*/*')); + expect_truthy(!isMatch('abc/./abc/./', '*/*/*/*/')); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/*/*/*/*')); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('abc/.', '*/.*')); + expect_truthy(!isMatch('abc/.', '*/.*/')); + expect_truthy(!isMatch('abc/.', '*/.*/*')); + + expect_truthy(!isMatch('abc/./', '*/.*')); + expect_truthy(!isMatch('abc/./', '*/.*/')); + expect_truthy(!isMatch('abc/./', '*/.*/*')); + + expect_truthy(!isMatch('abc/./abc/./', '*/.*/*/.*')); + expect_truthy(!isMatch('abc/./abc/./', '*/.*/*/.*/')); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/.*/*/.*/*')); + }); + + test('with star + dot', () => { + expect_truthy(!isMatch('abc/.', '*/*.')); + expect_truthy(!isMatch('abc/.', '*/*./')); + expect_truthy(!isMatch('abc/.', '*/*./*')); + + expect_truthy(!isMatch('abc/./', '*/*.')); + expect_truthy(!isMatch('abc/./', '*/*./')); + expect_truthy(!isMatch('abc/./', '*/*./*')); + + expect_truthy(!isMatch('abc/./abc/./', '*/*./*/*.')); + expect_truthy(!isMatch('abc/./abc/./', '*/*./*/*./')); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/*./*/*./*')); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('abc/.', '**/**')); + expect_truthy(!isMatch('abc/.', '**/**/')); + expect_truthy(!isMatch('abc/.', '**/**/**')); + + expect_truthy(!isMatch('abc/./', '**/**')); + expect_truthy(!isMatch('abc/./', '**/**/')); + expect_truthy(!isMatch('abc/./', '**/**/**')); + + expect_truthy(!isMatch('abc/./abc/./', '**/**/**/**')); + expect_truthy(!isMatch('abc/./abc/./', '**/**/**/**/')); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**/**/**/**')); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('abc/.', '**/.**')); + expect_truthy(!isMatch('abc/.', '**/.**/')); + expect_truthy(!isMatch('abc/.', '**/.**/**')); + + expect_truthy(!isMatch('abc/./', '**/.**')); + expect_truthy(!isMatch('abc/./', '**/.**/')); + expect_truthy(!isMatch('abc/./', '**/.**/**')); + + expect_truthy(!isMatch('abc/./abc/./', '**/.**/**/.**')); + expect_truthy(!isMatch('abc/./abc/./', '**/.**/**/.**/')); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/.**/**/.**/**')); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('abc/.', '**/**.**')); + expect_truthy(!isMatch('abc/.', '**/**.**/')); + expect_truthy(!isMatch('abc/.', '**/**.**/**')); + + expect_truthy(!isMatch('abc/./', '**/**.**')); + expect_truthy(!isMatch('abc/./', '**/**.**/')); + expect_truthy(!isMatch('abc/./', '**/**.**/**')); + + expect_truthy(!isMatch('abc/./abc/./', '**/**.**/**/**.**')); + expect_truthy(!isMatch('abc/./abc/./', '**/**.**/**/**.**/')); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**.**/**/.**/**')); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('abc/.', '**/**.')); + expect_truthy(!isMatch('abc/.', '**/**./')); + expect_truthy(!isMatch('abc/.', '**/**./**')); + + expect_truthy(!isMatch('abc/./', '**/**.')); + expect_truthy(!isMatch('abc/./', '**/**./')); + expect_truthy(!isMatch('abc/./', '**/**./**')); + + expect_truthy(!isMatch('abc/./abc/./', '**/**./**/**.')); + expect_truthy(!isMatch('abc/./abc/./', '**/**./**/**./')); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**./**/**./**')); + }); + }); + }); + + describe('options = { dot: true }', () => { + describe('should not match leading single-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('./abc', '*/*', { dot: true })); + expect_truthy(!isMatch('./abc', '*/abc', { dot: true })); + expect_truthy(!isMatch('./abc', '*/abc/*', { dot: true })); + }); + + test('with dot + single star', () => { + expect_truthy(!isMatch('./abc', '.*/*', { dot: true })); + expect_truthy(!isMatch('./abc', '.*/abc', { dot: true })); + + expect_truthy(!isMatch('./abc', '*./*', { dot: true })); + expect_truthy(!isMatch('./abc', '*./abc', { dot: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('./abc', '**', { dot: true })); + expect_truthy(!isMatch('./abc', '**/**', { dot: true })); + expect_truthy(!isMatch('./abc', '**/**/**', { dot: true })); + + expect_truthy(!isMatch('./abc', '**/abc', { dot: true })); + expect_truthy(!isMatch('./abc', '**/abc/**', { dot: true })); + + expect_truthy(!isMatch('./abc', 'abc/**', { dot: true })); + expect_truthy(!isMatch('./abc', 'abc/**/**', { dot: true })); + expect_truthy(!isMatch('./abc', 'abc/**/**/**', { dot: true })); + + expect_truthy(!isMatch('./abc', '**/abc', { dot: true })); + expect_truthy(!isMatch('./abc', '**/abc/**', { dot: true })); + expect_truthy(!isMatch('./abc', '**/abc/**/**', { dot: true })); + + expect_truthy(!isMatch('./abc', '**/**/abc/**', { dot: true })); + expect_truthy(!isMatch('./abc', '**/**/abc/**/**', { dot: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('./abc', '.**', { dot: true })); + expect_truthy(!isMatch('./abc', '.**/**', { dot: true })); + expect_truthy(!isMatch('./abc', '.**/abc', { dot: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('./abc', '*.*/**', { dot: true })); + expect_truthy(!isMatch('./abc', '*.*/abc', { dot: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('./abc', '**./**', { dot: true })); + expect_truthy(!isMatch('./abc', '**./abc', { dot: true })); + }); + }); + + describe('should not match nested single-dots', () => { + test('with star', () => { + expect_truthy(!isMatch('/./abc', '*/*', { dot: true })); + expect_truthy(!isMatch('/./abc', '/*/*', { dot: true })); + expect_truthy(!isMatch('/./abc', '*/*/*', { dot: true })); + + expect_truthy(!isMatch('abc/./abc', '*/*/*', { dot: true })); + expect_truthy(!isMatch('abc/./abc/abc', '*/*/*/*', { dot: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('/./abc', '*/.*/*', { dot: true })); + expect_truthy(!isMatch('/./abc', '/.*/*', { dot: true })); + + expect_truthy(!isMatch('/./abc', '*/*.*/*', { dot: true })); + expect_truthy(!isMatch('/./abc', '/*.*/*', { dot: true })); + + expect_truthy(!isMatch('/./abc', '*/*./*', { dot: true })); + expect_truthy(!isMatch('/./abc', '/*./*', { dot: true })); + + expect_truthy(!isMatch('abc/./abc', '*/.*/*', { dot: true })); + expect_truthy(!isMatch('abc/./abc', '*/*.*/*', { dot: true })); + expect_truthy(!isMatch('abc/./abc', '*/*./*', { dot: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('/./abc', '**', { dot: true })); + expect_truthy(!isMatch('/./abc', '**/**', { dot: true })); + expect_truthy(!isMatch('/./abc', '/**/**', { dot: true })); + expect_truthy(!isMatch('/./abc', '**/**/**', { dot: true })); + + expect_truthy(!isMatch('abc/./abc', '**/**/**', { dot: true })); + expect_truthy(!isMatch('abc/./abc/abc', '**/**/**/**', { dot: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('/./abc', '**/.**/**', { dot: true })); + expect_truthy(!isMatch('/./abc', '/.**/**', { dot: true })); + + expect_truthy(!isMatch('abc/./abc', '**/.**/**', { dot: true })); + expect_truthy(!isMatch('abc/./abc', '/.**/**', { dot: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('/./abc', '**/**./**', { dot: true })); + expect_truthy(!isMatch('/./abc', '/**./**', { dot: true })); + + expect_truthy(!isMatch('abc/./abc', '**/**./**', { dot: true })); + expect_truthy(!isMatch('abc/./abc', '/**./**', { dot: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('/./abc', '**/**.**/**', { dot: true })); + expect_truthy(!isMatch('/./abc', '**/*.*/**', { dot: true })); + + expect_truthy(!isMatch('/./abc', '/**.**/**', { dot: true })); + expect_truthy(!isMatch('/./abc', '/*.*/**', { dot: true })); + + expect_truthy(!isMatch('abc/./abc', '**/**.**/**', { dot: true })); + expect_truthy(!isMatch('abc/./abc', '**/*.*/**', { dot: true })); + + expect_truthy(!isMatch('abc/./abc', '/**.**/**', { dot: true })); + expect_truthy(!isMatch('abc/./abc', '/*.*/**', { dot: true })); + }); + }); + + describe('should not match trailing single-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('abc/.', '*/*', { dot: true })); + expect_truthy(!isMatch('abc/.', '*/*/', { dot: true })); + expect_truthy(!isMatch('abc/.', '*/*/*', { dot: true })); + + expect_truthy(!isMatch('abc/./', '*/*', { dot: true })); + expect_truthy(!isMatch('abc/./', '*/*/', { dot: true })); + expect_truthy(!isMatch('abc/./', '*/*/*', { dot: true })); + + expect_truthy(!isMatch('abc/./abc/./', '*/*/*/*', { dot: true })); + expect_truthy(!isMatch('abc/./abc/./', '*/*/*/*/', { dot: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/*/*/*/*', { dot: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('abc/.', '*/.*', { dot: true })); + expect_truthy(!isMatch('abc/.', '*/.*/', { dot: true })); + expect_truthy(!isMatch('abc/.', '*/.*/*', { dot: true })); + + expect_truthy(!isMatch('abc/./', '*/.*', { dot: true })); + expect_truthy(!isMatch('abc/./', '*/.*/', { dot: true })); + expect_truthy(!isMatch('abc/./', '*/.*/*', { dot: true })); + + expect_truthy(!isMatch('abc/./abc/./', '*/.*/*/.*', { dot: true })); + expect_truthy(!isMatch('abc/./abc/./', '*/.*/*/.*/', { dot: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/.*/*/.*/*', { dot: true })); + }); + + test('with star + dot', () => { + expect_truthy(!isMatch('abc/.', '*/*.', { dot: true })); + expect_truthy(!isMatch('abc/.', '*/*./', { dot: true })); + expect_truthy(!isMatch('abc/.', '*/*./*', { dot: true })); + + expect_truthy(!isMatch('abc/./', '*/*.', { dot: true })); + expect_truthy(!isMatch('abc/./', '*/*./', { dot: true })); + expect_truthy(!isMatch('abc/./', '*/*./*', { dot: true })); + + expect_truthy(!isMatch('abc/./abc/./', '*/*./*/*.', { dot: true })); + expect_truthy(!isMatch('abc/./abc/./', '*/*./*/*./', { dot: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/*./*/*./*', { dot: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('abc/.', '**/**', { dot: true })); + expect_truthy(!isMatch('abc/.', '**/**/', { dot: true })); + expect_truthy(!isMatch('abc/.', '**/**/**', { dot: true })); + + expect_truthy(!isMatch('abc/./', '**/**', { dot: true })); + expect_truthy(!isMatch('abc/./', '**/**/', { dot: true })); + expect_truthy(!isMatch('abc/./', '**/**/**', { dot: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/**/**/**', { dot: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/**/**/**/', { dot: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**/**/**/**', { dot: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('abc/.', '**/.**', { dot: true })); + expect_truthy(!isMatch('abc/.', '**/.**/', { dot: true })); + expect_truthy(!isMatch('abc/.', '**/.**/**', { dot: true })); + + expect_truthy(!isMatch('abc/./', '**/.**', { dot: true })); + expect_truthy(!isMatch('abc/./', '**/.**/', { dot: true })); + expect_truthy(!isMatch('abc/./', '**/.**/**', { dot: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/.**/**/.**', { dot: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/.**/**/.**/', { dot: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/.**/**/.**/**', { dot: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('abc/.', '**/**.**', { dot: true })); + expect_truthy(!isMatch('abc/.', '**/**.**/', { dot: true })); + expect_truthy(!isMatch('abc/.', '**/**.**/**', { dot: true })); + + expect_truthy(!isMatch('abc/./', '**/**.**', { dot: true })); + expect_truthy(!isMatch('abc/./', '**/**.**/', { dot: true })); + expect_truthy(!isMatch('abc/./', '**/**.**/**', { dot: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/**.**/**/**.**', { dot: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/**.**/**/**.**/', { dot: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**.**/**/.**/**', { dot: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('abc/.', '**/**.', { dot: true })); + expect_truthy(!isMatch('abc/.', '**/**./', { dot: true })); + expect_truthy(!isMatch('abc/.', '**/**./**', { dot: true })); + + expect_truthy(!isMatch('abc/./', '**/**.', { dot: true })); + expect_truthy(!isMatch('abc/./', '**/**./', { dot: true })); + expect_truthy(!isMatch('abc/./', '**/**./**', { dot: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/**./**/**.', { dot: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/**./**/**./', { dot: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**./**/**./**', { dot: true })); + }); + }); + }); + + describe('options = { strictSlashes: true }', () => { + describe('should not match leading single-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('./abc', '*/*', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '*/abc', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '*/abc/*', { strictSlashes: true })); + }); + + test('with dot + single star', () => { + expect_truthy(!isMatch('./abc', '.*/*', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '.*/abc', { strictSlashes: true })); + + expect_truthy(!isMatch('./abc', '*./*', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '*./abc', { strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('./abc', '**', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/**', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('./abc', '**/abc', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/abc/**', { strictSlashes: true })); + + expect_truthy(!isMatch('./abc', 'abc/**', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', 'abc/**/**', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', 'abc/**/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('./abc', '**/abc', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/abc/**', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/abc/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('./abc', '**/**/abc/**', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/**/abc/**/**', { strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('./abc', '.**', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '.**/abc', { strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('./abc', '*.*/**', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '*.*/abc', { strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('./abc', '**./**', { strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**./abc', { strictSlashes: true })); + }); + }); + + describe('should not match nested single-dots', () => { + test('with star', () => { + expect_truthy(!isMatch('/./abc', '*/*', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '*/*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '*/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc', '*/*/*/*', { strictSlashes: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('/./abc', '*/.*/*', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/.*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('/./abc', '*/*.*/*', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/*.*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('/./abc', '*/*./*', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/*./*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '*/.*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '*/*.*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '*/*./*', { strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('/./abc', '**', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '**/**', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/**/**', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '**/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '**/**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc', '**/**/**/**', { strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('/./abc', '**/.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/.**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '**/.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '/.**/**', { strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('/./abc', '**/**./**', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/**./**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '**/**./**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '/**./**', { strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('/./abc', '**/**.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '**/*.*/**', { strictSlashes: true })); + + expect_truthy(!isMatch('/./abc', '/**.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/*.*/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '**/**.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '**/*.*/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '/**.**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '/*.*/**', { strictSlashes: true })); + }); + }); + + describe('should not match trailing single-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('abc/.', '*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '*/*/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '*/*/*/*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/*/*/*/*', { strictSlashes: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('abc/.', '*/.*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/.*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/.*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '*/.*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/.*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/.*/*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '*/.*/*/.*', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '*/.*/*/.*/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/.*/*/.*/*', { strictSlashes: true })); + }); + + test('with star + dot', () => { + expect_truthy(!isMatch('abc/.', '*/*.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/*./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/*./*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '*/*.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/*./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/*./*', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '*/*./*/*.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '*/*./*/*./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/*./*/*./*', { strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('abc/.', '**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/**/**/**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/**/**/**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**/**/**/**', { strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('abc/.', '**/.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/.**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '**/.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/.**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/.**/**/.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/.**/**/.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/.**/**/.**/**', { strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('abc/.', '**/**.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**.**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '**/**.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**.**/**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/**.**/**/**.**', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/**.**/**/**.**/', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**.**/**/.**/**', { strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('abc/.', '**/**.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**./**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '**/**.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**./**', { strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/**./**/**.', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/**./**/**./', { strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**./**/**./**', { strictSlashes: true })); + }); + }); + }); + + describe('options = { dot: true, strictSlashes: true }', () => { + describe('should not match leading single-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('./abc', '*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '*/abc', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '*/abc/*', { dot: true, strictSlashes: true })); + }); + + test('with dot + single star', () => { + expect_truthy(!isMatch('./abc', '.*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '.*/abc', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('./abc', '*./*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '*./abc', { dot: true, strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('./abc', '**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('./abc', '**/abc', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/abc/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('./abc', 'abc/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', 'abc/**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', 'abc/**/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('./abc', '**/abc', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/abc/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/abc/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('./abc', '**/**/abc/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**/**/abc/**/**', { dot: true, strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('./abc', '.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '.**/abc', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('./abc', '*.*/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '*.*/abc', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('./abc', '**./**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('./abc', '**./abc', { dot: true, strictSlashes: true })); + }); + }); + + describe('should not match nested single-dots', () => { + test('with star', () => { + expect_truthy(!isMatch('/./abc', '*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '*/*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '*/*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc', '*/*/*/*', { dot: true, strictSlashes: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('/./abc', '*/.*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/.*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('/./abc', '*/*.*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/*.*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('/./abc', '*/*./*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/*./*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '*/.*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '*/*.*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '*/*./*', { dot: true, strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('/./abc', '**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '**/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '**/**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc', '**/**/**/**', { dot: true, strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('/./abc', '**/.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/.**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '**/.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '/.**/**', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('/./abc', '**/**./**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/**./**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '**/**./**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '/**./**', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('/./abc', '**/**.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '**/*.*/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('/./abc', '/**.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('/./abc', '/*.*/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '**/**.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '**/*.*/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc', '/**.**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc', '/*.*/**', { dot: true, strictSlashes: true })); + }); + }); + + describe('should not match trailing single-dots', () => { + test('with single star', () => { + expect_truthy(!isMatch('abc/.', '*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '*/*/*/*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '*/*/*/*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/*/*/*/*', { dot: true, strictSlashes: true })); + }); + + test('with dot + star', () => { + expect_truthy(!isMatch('abc/.', '*/.*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/.*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/.*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '*/.*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/.*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/.*/*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '*/.*/*/.*', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '*/.*/*/.*/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/.*/*/.*/*', { dot: true, strictSlashes: true })); + }); + + test('with star + dot', () => { + expect_truthy(!isMatch('abc/.', '*/*.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/*./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '*/*./*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '*/*.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/*./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '*/*./*', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '*/*./*/*.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '*/*./*/*./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '*/*./*/*./*', { dot: true, strictSlashes: true })); + }); + + test('with globstar', () => { + expect_truthy(!isMatch('abc/.', '**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/**/**/**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/**/**/**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**/**/**/**', { dot: true, strictSlashes: true })); + }); + + test('with dot + globstar', () => { + expect_truthy(!isMatch('abc/.', '**/.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/.**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '**/.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/.**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/.**/**/.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/.**/**/.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/.**/**/.**/**', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot + globstar', () => { + expect_truthy(!isMatch('abc/.', '**/**.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**.**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '**/**.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**.**/**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/**.**/**/**.**', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/**.**/**/**.**/', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**.**/**/.**/**', { dot: true, strictSlashes: true })); + }); + + test('with globstar + dot', () => { + expect_truthy(!isMatch('abc/.', '**/**.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/.', '**/**./**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./', '**/**.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./', '**/**./**', { dot: true, strictSlashes: true })); + + expect_truthy(!isMatch('abc/./abc/./', '**/**./**/**.', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/./', '**/**./**/**./', { dot: true, strictSlashes: true })); + expect_truthy(!isMatch('abc/./abc/abc/./', '**/**./**/**./**', { dot: true, strictSlashes: true })); + }); + }); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/extglobs-bash.test.ts b/packages/node-utils/test/picomatch/extglobs-bash.test.ts new file mode 100644 index 0000000..e8f6f61 --- /dev/null +++ b/packages/node-utils/test/picomatch/extglobs-bash.test.ts @@ -0,0 +1,2633 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +/** + * Some of tests were converted from bash 4.3, 4.4, and minimatch unit tests. + */ + +describe('extglobs (bash)', () => { + test('should not match empty string with "*(0|1|3|5|7|9)"', () => { + expect_truthy(!isMatch('', '*(0|1|3|5|7|9)', { bash: true, windows: true })); + }); + + test('"*(a|b[)" should not match "*(a|b\\[)"', () => { + expect_truthy(!isMatch('*(a|b[)', '*(a|b\\[)', { bash: true, windows: true })); + }); + + test('"*(a|b[)" should not match "\\*\\(a|b\\[\\)"', () => { + expect_truthy(!isMatch('*(a|b[)', '\\*\\(a|b\\[\\)', { bash: true, windows: true })); + }); + + test('"***" should match "\\*\\*\\*"', () => { + expect_truthy(isMatch('***', '\\*\\*\\*', { bash: true, windows: true })); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1" should not match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(!isMatch('-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*', { bash: true, windows: true })); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1" should match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(isMatch('-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*', { bash: true, windows: true })); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1" should not match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(!isMatch('-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*', { bash: true, windows: true })); + }); + + test('"/dev/udp/129.22.8.102/45" should match "/dev\\/@(tcp|udp)\\/*\\/*"', () => { + expect_truthy(isMatch('/dev/udp/129.22.8.102/45', '/dev\\/@(tcp|udp)\\/*\\/*', { bash: true, windows: true })); + }); + + test('"/x/y/z" should match "/x/y/z"', () => { + expect_truthy(isMatch('/x/y/z', '/x/y/z', { bash: true, windows: true })); + }); + + test('"0377" should match "+([0-7])"', () => { + expect_truthy(isMatch('0377', '+([0-7])', { bash: true, windows: true })); + }); + + test('"07" should match "+([0-7])"', () => { + expect_truthy(isMatch('07', '+([0-7])', { bash: true, windows: true })); + }); + + test('"09" should not match "+([0-7])"', () => { + expect_truthy(!isMatch('09', '+([0-7])', { bash: true, windows: true })); + }); + + test('"1" should match "0|[1-9]*([0-9])"', () => { + expect_truthy(isMatch('1', '0|[1-9]*([0-9])', { bash: true, windows: true })); + }); + + test('"12" should match "0|[1-9]*([0-9])"', () => { + expect_truthy(isMatch('12', '0|[1-9]*([0-9])', { bash: true, windows: true })); + }); + + test('"123abc" should not match "(a+|b)*"', () => { + expect_truthy(!isMatch('123abc', '(a+|b)*', { bash: true, windows: true })); + }); + + test('"123abc" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('123abc', '(a+|b)+', { bash: true, windows: true })); + }); + + test('"123abc" should match "*?(a)bc"', () => { + expect_truthy(isMatch('123abc', '*?(a)bc', { bash: true, windows: true })); + }); + + test('"123abc" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('123abc', 'a(b*(foo|bar))d', { bash: true, windows: true })); + }); + + test('"123abc" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*(e|f)', { bash: true, windows: true })); + }); + + test('"123abc" should not match "ab**"', () => { + expect_truthy(!isMatch('123abc', 'ab**', { bash: true, windows: true })); + }); + + test('"123abc" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab**(e|f)', { bash: true, windows: true })); + }); + + test('"123abc" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('123abc', 'ab**(e|f)g', { bash: true, windows: true })); + }); + + test('"123abc" should not match "ab***ef"', () => { + expect_truthy(!isMatch('123abc', 'ab***ef', { bash: true, windows: true })); + }); + + test('"123abc" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*+(e|f)', { bash: true, windows: true })); + }); + + test('"123abc" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*d+(e|f)', { bash: true, windows: true })); + }); + + test('"123abc" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab?*(e|f)', { bash: true, windows: true })); + }); + + test('"12abc" should not match "0|[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('12abc', '0|[1-9]*([0-9])', { bash: true, windows: true })); + }); + + test('"137577991" should match "*(0|1|3|5|7|9)"', () => { + expect_truthy(isMatch('137577991', '*(0|1|3|5|7|9)', { bash: true, windows: true })); + }); + + test('"2468" should not match "*(0|1|3|5|7|9)"', () => { + expect_truthy(!isMatch('2468', '*(0|1|3|5|7|9)', { bash: true, windows: true })); + }); + + test('"?a?b" should match "\\??\\?b"', () => { + expect_truthy(isMatch('?a?b', '\\??\\?b', { bash: true, windows: true })); + }); + + test('"\\a\\b\\c" should not match "abc"', () => { + expect_truthy(!isMatch('\\a\\b\\c', 'abc', { bash: true, windows: true })); + }); + + test('"a" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"a" should not match "!(a)"', () => { + expect_truthy(!isMatch('a', '!(a)', { bash: true, windows: true })); + }); + + test('"a" should not match "!(a)*"', () => { + expect_truthy(!isMatch('a', '!(a)*', { bash: true, windows: true })); + }); + + test('"a" should match "(a)"', () => { + expect_truthy(isMatch('a', '(a)', { bash: true, windows: true })); + }); + + test('"a" should not match "(b)"', () => { + expect_truthy(!isMatch('a', '(b)', { bash: true, windows: true })); + }); + + test('"a" should match "*(a)"', () => { + expect_truthy(isMatch('a', '*(a)', { bash: true, windows: true })); + }); + + test('"a" should match "+(a)"', () => { + expect_truthy(isMatch('a', '+(a)', { bash: true, windows: true })); + }); + + test('"a" should match "?"', () => { + expect_truthy(isMatch('a', '?', { bash: true, windows: true })); + }); + + test('"a" should match "?(a|b)"', () => { + expect_truthy(isMatch('a', '?(a|b)', { bash: true, windows: true })); + }); + + test('"a" should not match "??"', () => { + expect_truthy(!isMatch('a', '??', { bash: true, windows: true })); + }); + + test('"a" should match "a!(b)*"', () => { + expect_truthy(isMatch('a', 'a!(b)*', { bash: true, windows: true })); + }); + + test('"a" should match "a?(a|b)"', () => { + expect_truthy(isMatch('a', 'a?(a|b)', { bash: true, windows: true })); + }); + + test('"a" should match "a?(x)"', () => { + expect_truthy(isMatch('a', 'a?(x)', { bash: true, windows: true })); + }); + + test('"a" should not match "a??b"', () => { + expect_truthy(!isMatch('a', 'a??b', { bash: true, windows: true })); + }); + + test('"a" should not match "b?(a|b)"', () => { + expect_truthy(!isMatch('a', 'b?(a|b)', { bash: true, windows: true })); + }); + + test('"a((((b" should match "a(*b"', () => { + expect_truthy(isMatch('a((((b', 'a(*b', { bash: true, windows: true })); + }); + + test('"a((((b" should not match "a(b"', () => { + expect_truthy(!isMatch('a((((b', 'a(b', { bash: true, windows: true })); + }); + + test('"a((((b" should not match "a\\(b"', () => { + expect_truthy(!isMatch('a((((b', 'a\\(b', { bash: true, windows: true })); + }); + + test('"a((b" should match "a(*b"', () => { + expect_truthy(isMatch('a((b', 'a(*b', { bash: true, windows: true })); + }); + + test('"a((b" should not match "a(b"', () => { + expect_truthy(!isMatch('a((b', 'a(b', { bash: true, windows: true })); + }); + + test('"a((b" should not match "a\\(b"', () => { + expect_truthy(!isMatch('a((b', 'a\\(b', { bash: true, windows: true })); + }); + + test('"a(b" should match "a(*b"', () => { + expect_truthy(isMatch('a(b', 'a(*b', { bash: true, windows: true })); + }); + + test('"a(b" should match "a(b"', () => { + expect_truthy(isMatch('a(b', 'a(b', { bash: true, windows: true })); + }); + + test('"a\\(b" should match "a\\(b"', () => { + expect_truthy(isMatch('a\\(b', 'a\\(b', { bash: true, windows: true })); + }); + + test('"a(b" should match "a\\(b"', () => { + expect_truthy(isMatch('a(b', 'a\\(b', { bash: true, windows: true })); + }); + + test('"a." should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"a." should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.', '*!(.a|.b|.c)', { bash: true, windows: true })); + }); + + test('"a." should match "*.!(a)"', () => { + expect_truthy(isMatch('a.', '*.!(a)', { bash: true, windows: true })); + }); + + test('"a." should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.', '*.!(a|b|c)', { bash: true, windows: true })); + }); + + test('"a." should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true, windows: true })); + }); + + test('"a." should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.', '*.+(b|d)', { bash: true, windows: true })); + }); + + test('"a.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.a', '!(*.[a-b]*)', { bash: true, windows: true })); + }); + + test('"a.a" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.a', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"a.a" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.a', '!(*[a-b].[a-b]*)', { bash: true, windows: true })); + }); + + test('"a.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.a', '!*.(a|b)', { bash: true, windows: true })); + }); + + test('"a.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.a', '!*.(a|b)*', { bash: true, windows: true })); + }); + + test('"a.a" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.a', '(a|d).(a|b)*', { bash: true, windows: true })); + }); + + test('"a.a" should match "(b|a).(a)"', () => { + expect_truthy(isMatch('a.a', '(b|a).(a)', { bash: true, windows: true })); + }); + + test('"a.a" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.a', '*!(.a|.b|.c)', { bash: true, windows: true })); + }); + + test('"a.a" should not match "*.!(a)"', () => { + expect_truthy(!isMatch('a.a', '*.!(a)', { bash: true, windows: true })); + }); + + test('"a.a" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.a', '*.!(a|b|c)', { bash: true, windows: true })); + }); + + test('"a.a" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.a', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true, windows: true })); + }); + + test('"a.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.a', '*.+(b|d)', { bash: true, windows: true })); + }); + + test('"a.a" should match "@(b|a).@(a)"', () => { + expect_truthy(isMatch('a.a', '@(b|a).@(a)', { bash: true, windows: true })); + }); + + test('"a.a.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.a.a', '!(*.[a-b]*)', { bash: true, windows: true })); + }); + + test('"a.a.a" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.a.a', '!(*[a-b].[a-b]*)', { bash: true, windows: true })); + }); + + test('"a.a.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.a.a', '!*.(a|b)', { bash: true, windows: true })); + }); + + test('"a.a.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.a.a', '!*.(a|b)*', { bash: true, windows: true })); + }); + + test('"a.a.a" should match "*.!(a)"', () => { + expect_truthy(isMatch('a.a.a', '*.!(a)', { bash: true, windows: true })); + }); + + test('"a.a.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.a.a', '*.+(b|d)', { bash: true, windows: true })); + }); + + test('"a.aa.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('a.aa.a', '(b|a).(a)', { bash: true, windows: true })); + }); + + test('"a.aa.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('a.aa.a', '@(b|a).@(a)', { bash: true, windows: true })); + }); + + test('"a.abcd" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.abcd', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"a.abcd" should not match "!(*.a|*.b|*.c)*"', () => { + expect_truthy(!isMatch('a.abcd', '!(*.a|*.b|*.c)*', { bash: true, windows: true })); + }); + + test('"a.abcd" should match "*!(*.a|*.b|*.c)*"', () => { + expect_truthy(isMatch('a.abcd', '*!(*.a|*.b|*.c)*', { bash: true, windows: true })); + }); + + test('"a.abcd" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.abcd', '*!(.a|.b|.c)', { bash: true, windows: true })); + }); + + test('"a.abcd" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.abcd', '*.!(a|b|c)', { bash: true, windows: true })); + }); + + test('"a.abcd" should not match "*.!(a|b|c)*"', () => { + expect_truthy(!isMatch('a.abcd', '*.!(a|b|c)*', { bash: true, windows: true })); + }); + + test('"a.abcd" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.abcd', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true, windows: true })); + }); + + test('"a.b" should not match "!(*.*)"', () => { + expect_truthy(!isMatch('a.b', '!(*.*)', { bash: true, windows: true })); + }); + + test('"a.b" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.b', '!(*.[a-b]*)', { bash: true, windows: true })); + }); + + test('"a.b" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.b', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"a.b" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.b', '!(*[a-b].[a-b]*)', { bash: true, windows: true })); + }); + + test('"a.b" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.b', '!*.(a|b)', { bash: true, windows: true })); + }); + + test('"a.b" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.b', '!*.(a|b)*', { bash: true, windows: true })); + }); + + test('"a.b" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.b', '(a|d).(a|b)*', { bash: true, windows: true })); + }); + + test('"a.b" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.b', '*!(.a|.b|.c)', { bash: true, windows: true })); + }); + + test('"a.b" should match "*.!(a)"', () => { + expect_truthy(isMatch('a.b', '*.!(a)', { bash: true, windows: true })); + }); + + test('"a.b" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.b', '*.!(a|b|c)', { bash: true, windows: true })); + }); + + test('"a.b" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.b', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true, windows: true })); + }); + + test('"a.b" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('a.b', '*.+(b|d)', { bash: true, windows: true })); + }); + + test('"a.bb" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.bb', '!(*.[a-b]*)', { bash: true, windows: true })); + }); + + test('"a.bb" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.bb', '!(*[a-b].[a-b]*)', { bash: true, windows: true })); + }); + + test('"a.bb" should match "!*.(a|b)"', () => { + expect_truthy(isMatch('a.bb', '!*.(a|b)', { bash: true, windows: true })); + }); + + test('"a.bb" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.bb', '!*.(a|b)*', { bash: true, windows: true })); + }); + + test('"a.bb" should not match "!*.*(a|b)"', () => { + expect_truthy(!isMatch('a.bb', '!*.*(a|b)', { bash: true, windows: true })); + }); + + test('"a.bb" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.bb', '(a|d).(a|b)*', { bash: true, windows: true })); + }); + + test('"a.bb" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('a.bb', '(b|a).(a)', { bash: true, windows: true })); + }); + + test('"a.bb" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('a.bb', '*.+(b|d)', { bash: true, windows: true })); + }); + + test('"a.bb" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('a.bb', '@(b|a).@(a)', { bash: true, windows: true })); + }); + + test('"a.c" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.c', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"a.c" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.c', '*!(.a|.b|.c)', { bash: true, windows: true })); + }); + + test('"a.c" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.c', '*.!(a|b|c)', { bash: true, windows: true })); + }); + + test('"a.c" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.c', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true, windows: true })); + }); + + test('"a.c.d" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.c.d', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"a.c.d" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.c.d', '*!(.a|.b|.c)', { bash: true, windows: true })); + }); + + test('"a.c.d" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.c.d', '*.!(a|b|c)', { bash: true, windows: true })); + }); + + test('"a.c.d" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.c.d', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true, windows: true })); + }); + + test('"a.ccc" should match "!(*.[a-b]*)"', () => { + expect_truthy(isMatch('a.ccc', '!(*.[a-b]*)', { bash: true, windows: true })); + }); + + test('"a.ccc" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('a.ccc', '!(*[a-b].[a-b]*)', { bash: true, windows: true })); + }); + + test('"a.ccc" should match "!*.(a|b)"', () => { + expect_truthy(isMatch('a.ccc', '!*.(a|b)', { bash: true, windows: true })); + }); + + test('"a.ccc" should match "!*.(a|b)*"', () => { + expect_truthy(isMatch('a.ccc', '!*.(a|b)*', { bash: true, windows: true })); + }); + + test('"a.ccc" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.ccc', '*.+(b|d)', { bash: true, windows: true })); + }); + + test('"a.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('a.js', '!(*.js)', { bash: true, windows: true })); + }); + + test('"a.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.js', '*!(.js)', { bash: true, windows: true })); + }); + + test('"a.js" should not match "*.!(js)"', () => { + expect_truthy(!isMatch('a.js', '*.!(js)', { bash: true, windows: true })); + }); + + test('"a.js" should not match "a.!(js)"', () => { + expect_truthy(!isMatch('a.js', 'a.!(js)', { bash: true, windows: true })); + }); + + test('"a.js" should not match "a.!(js)*"', () => { + expect_truthy(!isMatch('a.js', 'a.!(js)*', { bash: true, windows: true })); + }); + + test('"a.js.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('a.js.js', '!(*.js)', { bash: true, windows: true })); + }); + + test('"a.js.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.js.js', '*!(.js)', { bash: true, windows: true })); + }); + + test('"a.js.js" should match "*.!(js)"', () => { + expect_truthy(isMatch('a.js.js', '*.!(js)', { bash: true, windows: true })); + }); + + test('"a.js.js" should match "*.*(js).js"', () => { + expect_truthy(isMatch('a.js.js', '*.*(js).js', { bash: true, windows: true })); + }); + + test('"a.md" should match "!(*.js)"', () => { + expect_truthy(isMatch('a.md', '!(*.js)', { bash: true, windows: true })); + }); + + test('"a.md" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.md', '*!(.js)', { bash: true, windows: true })); + }); + + test('"a.md" should match "*.!(js)"', () => { + expect_truthy(isMatch('a.md', '*.!(js)', { bash: true, windows: true })); + }); + + test('"a.md" should match "a.!(js)"', () => { + expect_truthy(isMatch('a.md', 'a.!(js)', { bash: true, windows: true })); + }); + + test('"a.md" should match "a.!(js)*"', () => { + expect_truthy(isMatch('a.md', 'a.!(js)*', { bash: true, windows: true })); + }); + + test('"a.md.js" should not match "*.*(js).js"', () => { + expect_truthy(!isMatch('a.md.js', '*.*(js).js', { bash: true, windows: true })); + }); + + test('"a.txt" should match "a.!(js)"', () => { + expect_truthy(isMatch('a.txt', 'a.!(js)', { bash: true, windows: true })); + }); + + test('"a.txt" should match "a.!(js)*"', () => { + expect_truthy(isMatch('a.txt', 'a.!(js)*', { bash: true, windows: true })); + }); + + test('"a/!(z)" should match "a/!(z)"', () => { + expect_truthy(isMatch('a/!(z)', 'a/!(z)', { bash: true, windows: true })); + }); + + test('"a/b" should match "a/!(z)"', () => { + expect_truthy(isMatch('a/b', 'a/!(z)', { bash: true, windows: true })); + }); + + test('"a/b/c.txt" should not match "*/b/!(*).txt"', () => { + expect_truthy(!isMatch('a/b/c.txt', '*/b/!(*).txt', { bash: true, windows: true })); + }); + + test('"a/b/c.txt" should not match "*/b/!(c).txt"', () => { + expect_truthy(!isMatch('a/b/c.txt', '*/b/!(c).txt', { bash: true, windows: true })); + }); + + test('"a/b/c.txt" should match "*/b/!(cc).txt"', () => { + expect_truthy(isMatch('a/b/c.txt', '*/b/!(cc).txt', { bash: true, windows: true })); + }); + + test('"a/b/cc.txt" should not match "*/b/!(*).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(*).txt', { bash: true, windows: true })); + }); + + test('"a/b/cc.txt" should not match "*/b/!(c).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(c).txt', { bash: true, windows: true })); + }); + + test('"a/b/cc.txt" should not match "*/b/!(cc).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(cc).txt', { bash: true, windows: true })); + }); + + test('"a/dir/foo.txt" should match "*/dir/**/!(bar).txt"', () => { + expect_truthy(isMatch('a/dir/foo.txt', '*/dir/**/!(bar).txt', { bash: true, windows: true })); + }); + + test('"a/z" should not match "a/!(z)"', () => { + expect_truthy(!isMatch('a/z', 'a/!(z)', { bash: true, windows: true })); + }); + + test('"a\\(b" should not match "a(*b"', () => { + expect_truthy(!isMatch('a\\(b', 'a(*b', { bash: true, windows: true })); + }); + + test('"a\\(b" should not match "a(b"', () => { + expect_truthy(!isMatch('a\\(b', 'a(b', { bash: true, windows: true })); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\\\z', 'a\\\\z', { bash: true, windows: false })); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\\\z', 'a\\\\z', { bash: true, windows: true })); + }); + + test('"a\\b" should match "a/b"', () => { + expect_truthy(isMatch('a\\b', 'a/b', { windows: true })); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\\\z', 'a\\\\z', { bash: true, windows: true })); + expect_truthy(isMatch('a\\z', 'a\\z', { bash: true, windows: true })); + }); + + test('"a\\z" should not match "a\\z"', () => { + expect_truthy(isMatch('a\\z', 'a\\z', { bash: true, windows: true })); + }); + + test('"aa" should not match "!(a!(b))"', () => { + expect_truthy(!isMatch('aa', '!(a!(b))', { bash: true, windows: true })); + }); + + test('"aa" should match "!(a)"', () => { + expect_truthy(isMatch('aa', '!(a)', { bash: true, windows: true })); + }); + + test('"aa" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aa', '!(a)*', { bash: true, windows: true })); + }); + + test('"aa" should not match "?"', () => { + expect_truthy(!isMatch('aa', '?', { bash: true, windows: true })); + }); + + test('"aa" should not match "@(a)b"', () => { + expect_truthy(!isMatch('aa', '@(a)b', { bash: true, windows: true })); + }); + + test('"aa" should match "a!(b)*"', () => { + expect_truthy(isMatch('aa', 'a!(b)*', { bash: true, windows: true })); + }); + + test('"aa" should not match "a??b"', () => { + expect_truthy(!isMatch('aa', 'a??b', { bash: true, windows: true })); + }); + + test('"aa.aa" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('aa.aa', '(b|a).(a)', { bash: true, windows: true })); + }); + + test('"aa.aa" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('aa.aa', '@(b|a).@(a)', { bash: true, windows: true })); + }); + + test('"aaa" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aaa', '!(a)*', { bash: true, windows: true })); + }); + + test('"aaa" should match "a!(b)*"', () => { + expect_truthy(isMatch('aaa', 'a!(b)*', { bash: true, windows: true })); + }); + + test('"aaaaaaabababab" should match "*ab"', () => { + expect_truthy(isMatch('aaaaaaabababab', '*ab', { bash: true, windows: true })); + }); + + test('"aaac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('aaac', '*(@(a))a@(c)', { bash: true, windows: true })); + }); + + test('"aaaz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('aaaz', '[a*(]*z', { bash: true, windows: true })); + }); + + test('"aab" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aab', '!(a)*', { bash: true, windows: true })); + }); + + test('"aab" should not match "?"', () => { + expect_truthy(!isMatch('aab', '?', { bash: true, windows: true })); + }); + + test('"aab" should not match "??"', () => { + expect_truthy(!isMatch('aab', '??', { bash: true, windows: true })); + }); + + test('"aab" should not match "@(c)b"', () => { + expect_truthy(!isMatch('aab', '@(c)b', { bash: true, windows: true })); + }); + + test('"aab" should match "a!(b)*"', () => { + expect_truthy(isMatch('aab', 'a!(b)*', { bash: true, windows: true })); + }); + + test('"aab" should not match "a??b"', () => { + expect_truthy(!isMatch('aab', 'a??b', { bash: true, windows: true })); + }); + + test('"aac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('aac', '*(@(a))a@(c)', { bash: true, windows: true })); + }); + + test('"aac" should not match "*(@(a))b@(c)"', () => { + expect_truthy(!isMatch('aac', '*(@(a))b@(c)', { bash: true, windows: true })); + }); + + test('"aax" should not match "a!(a*|b)"', () => { + expect_truthy(!isMatch('aax', 'a!(a*|b)', { bash: true, windows: true })); + }); + + test('"aax" should match "a!(x*|b)"', () => { + expect_truthy(isMatch('aax', 'a!(x*|b)', { bash: true, windows: true })); + }); + + test('"aax" should match "a?(a*|b)"', () => { + expect_truthy(isMatch('aax', 'a?(a*|b)', { bash: true, windows: true })); + }); + + test('"aaz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('aaz', '[a*(]*z', { bash: true, windows: true })); + }); + + test('"ab" should match "!(*.*)"', () => { + expect_truthy(isMatch('ab', '!(*.*)', { bash: true, windows: true })); + }); + + test('"ab" should match "!(a!(b))"', () => { + expect_truthy(isMatch('ab', '!(a!(b))', { bash: true, windows: true })); + }); + + test('"ab" should not match "!(a)*"', () => { + expect_truthy(!isMatch('ab', '!(a)*', { bash: true, windows: true })); + }); + + test('"ab" should match "@(a+|b)*"', () => { + expect_truthy(isMatch('ab', '@(a+|b)*', { bash: true, windows: true })); + }); + + test('"ab" should match "(a+|b)+"', () => { + expect_truthy(isMatch('ab', '(a+|b)+', { bash: true, windows: true })); + }); + + test('"ab" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('ab', '*?(a)bc', { bash: true, windows: true })); + }); + + test('"ab" should not match "a!(*(b|B))"', () => { + expect_truthy(!isMatch('ab', 'a!(*(b|B))', { bash: true, windows: true })); + }); + + test('"ab" should not match "a!(@(b|B))"', () => { + expect_truthy(!isMatch('ab', 'a!(@(b|B))', { bash: true, windows: true })); + }); + + test('"aB" should not match "a!(@(b|B))"', () => { + expect_truthy(!isMatch('aB', 'a!(@(b|B))', { bash: true, windows: true })); + }); + + test('"ab" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('ab', 'a!(b)*', { bash: true, windows: true })); + }); + + test('"ab" should not match "a(*b"', () => { + expect_truthy(!isMatch('ab', 'a(*b', { bash: true, windows: true })); + }); + + test('"ab" should not match "a(b"', () => { + expect_truthy(!isMatch('ab', 'a(b', { bash: true, windows: true })); + }); + + test('"ab" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('ab', 'a(b*(foo|bar))d', { bash: true, windows: true })); + }); + + test('"ab" should not match "a/b"', () => { + expect_truthy(!isMatch('ab', 'a/b', { windows: true })); + }); + + test('"ab" should not match "a\\(b"', () => { + expect_truthy(!isMatch('ab', 'a\\(b', { bash: true, windows: true })); + }); + + test('"ab" should match "ab*(e|f)"', () => { + expect_truthy(isMatch('ab', 'ab*(e|f)', { bash: true, windows: true })); + }); + + test('"ab" should match "ab**"', () => { + expect_truthy(isMatch('ab', 'ab**', { bash: true, windows: true })); + }); + + test('"ab" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('ab', 'ab**(e|f)', { bash: true, windows: true })); + }); + + test('"ab" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('ab', 'ab**(e|f)g', { bash: true, windows: true })); + }); + + test('"ab" should not match "ab***ef"', () => { + expect_truthy(!isMatch('ab', 'ab***ef', { bash: true, windows: true })); + }); + + test('"ab" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab*+(e|f)', { bash: true, windows: true })); + }); + + test('"ab" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab*d+(e|f)', { bash: true, windows: true })); + }); + + test('"ab" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab?*(e|f)', { bash: true, windows: true })); + }); + + test('"ab/cXd/efXg/hi" should match "**/*X*/**/*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '**/*X*/**/*i', { bash: true, windows: true })); + }); + + test('"ab/cXd/efXg/hi" should match "*/*X*/*/*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '*/*X*/*/*i', { bash: true, windows: true })); + }); + + test('"ab/cXd/efXg/hi" should match "*X*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '*X*i', { bash: true, windows: true })); + }); + + test('"ab/cXd/efXg/hi" should match "*Xg*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '*Xg*i', { bash: true, windows: true })); + }); + + test('"ab]" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('ab]', 'a!(@(b|B))', { bash: true, windows: true })); + }); + + test('"abab" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abab', '(a+|b)*', { bash: true, windows: true })); + }); + + test('"abab" should match "(a+|b)+"', () => { + expect_truthy(isMatch('abab', '(a+|b)+', { bash: true, windows: true })); + }); + + test('"abab" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abab', '*?(a)bc', { bash: true, windows: true })); + }); + + test('"abab" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abab', 'a(b*(foo|bar))d', { bash: true, windows: true })); + }); + + test('"abab" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*(e|f)', { bash: true, windows: true })); + }); + + test('"abab" should match "ab**"', () => { + expect_truthy(isMatch('abab', 'ab**', { bash: true, windows: true })); + }); + + test('"abab" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abab', 'ab**(e|f)', { bash: true, windows: true })); + }); + + test('"abab" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abab', 'ab**(e|f)g', { bash: true, windows: true })); + }); + + test('"abab" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abab', 'ab***ef', { bash: true, windows: true })); + }); + + test('"abab" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*+(e|f)', { bash: true, windows: true })); + }); + + test('"abab" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*d+(e|f)', { bash: true, windows: true })); + }); + + test('"abab" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab?*(e|f)', { bash: true, windows: true })); + }); + + test('"abb" should match "!(*.*)"', () => { + expect_truthy(isMatch('abb', '!(*.*)', { bash: true, windows: true })); + }); + + test('"abb" should not match "!(a)*"', () => { + expect_truthy(!isMatch('abb', '!(a)*', { bash: true, windows: true })); + }); + + test('"abb" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('abb', 'a!(b)*', { bash: true, windows: true })); + }); + + test('"abbcd" should match "@(ab|a*(b))*(c)d"', () => { + expect_truthy(isMatch('abbcd', '@(ab|a*(b))*(c)d', { bash: true, windows: true })); + }); + + test('"abc" should not match "\\a\\b\\c"', () => { + expect_truthy(!isMatch('abc', '\\a\\b\\c', { bash: true, windows: true })); + }); + + test('"aBc" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('aBc', 'a!(@(b|B))', { bash: true, windows: true })); + }); + + test('"abcd" should match "?@(a|b)*@(c)d"', () => { + expect_truthy(isMatch('abcd', '?@(a|b)*@(c)d', { bash: true, windows: true })); + }); + + test('"abcd" should match "@(ab|a*@(b))*(c)d"', () => { + expect_truthy(isMatch('abcd', '@(ab|a*@(b))*(c)d', { bash: true, windows: true })); + }); + + test('"abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt" should match "**/*a*b*g*n*t"', () => { + expect_truthy(isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt', '**/*a*b*g*n*t', { bash: true, windows: true })); + }); + + test('"abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz" should not match "**/*a*b*g*n*t"', () => { + expect_truthy(!isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz', '**/*a*b*g*n*t', { bash: true, windows: true })); + }); + + test('"abcdef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcdef', '(a+|b)*', { bash: true, windows: true })); + }); + + test('"abcdef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcdef', '(a+|b)+', { bash: true, windows: true })); + }); + + test('"abcdef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcdef', '*?(a)bc', { bash: true, windows: true })); + }); + + test('"abcdef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcdef', 'a(b*(foo|bar))d', { bash: true, windows: true })); + }); + + test('"abcdef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcdef', 'ab*(e|f)', { bash: true, windows: true })); + }); + + test('"abcdef" should match "ab**"', () => { + expect_truthy(isMatch('abcdef', 'ab**', { bash: true, windows: true })); + }); + + test('"abcdef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab**(e|f)', { bash: true, windows: true })); + }); + + test('"abcdef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abcdef', 'ab**(e|f)g', { bash: true, windows: true })); + }); + + test('"abcdef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcdef', 'ab***ef', { bash: true, windows: true })); + }); + + test('"abcdef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab*+(e|f)', { bash: true, windows: true })); + }); + + test('"abcdef" should match "ab*d+(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab*d+(e|f)', { bash: true, windows: true })); + }); + + test('"abcdef" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abcdef', 'ab?*(e|f)', { bash: true, windows: true })); + }); + + test('"abcfef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcfef', '(a+|b)*', { bash: true, windows: true })); + }); + + test('"abcfef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcfef', '(a+|b)+', { bash: true, windows: true })); + }); + + test('"abcfef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcfef', '*?(a)bc', { bash: true, windows: true })); + }); + + test('"abcfef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcfef', 'a(b*(foo|bar))d', { bash: true, windows: true })); + }); + + test('"abcfef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcfef', 'ab*(e|f)', { bash: true, windows: true })); + }); + + test('"abcfef" should match "ab**"', () => { + expect_truthy(isMatch('abcfef', 'ab**', { bash: true, windows: true })); + }); + + test('"abcfef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab**(e|f)', { bash: true, windows: true })); + }); + + test('"abcfef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abcfef', 'ab**(e|f)g', { bash: true, windows: true })); + }); + + test('"abcfef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcfef', 'ab***ef', { bash: true, windows: true })); + }); + + test('"abcfef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab*+(e|f)', { bash: true, windows: true })); + }); + + test('"abcfef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abcfef', 'ab*d+(e|f)', { bash: true, windows: true })); + }); + + test('"abcfef" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab?*(e|f)', { bash: true, windows: true })); + }); + + test('"abcfefg" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcfefg', '(a+|b)*', { bash: true, windows: true })); + }); + + test('"abcfefg" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcfefg', '(a+|b)+', { bash: true, windows: true })); + }); + + test('"abcfefg" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcfefg', '*?(a)bc', { bash: true, windows: true })); + }); + + test('"abcfefg" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcfefg', 'a(b*(foo|bar))d', { bash: true, windows: true })); + }); + + test('"abcfefg" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*(e|f)', { bash: true, windows: true })); + }); + + test('"abcfefg" should match "ab**"', () => { + expect_truthy(isMatch('abcfefg', 'ab**', { bash: true, windows: true })); + }); + + test('"abcfefg" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcfefg', 'ab**(e|f)', { bash: true, windows: true })); + }); + + test('"abcfefg" should match "ab**(e|f)g"', () => { + expect_truthy(isMatch('abcfefg', 'ab**(e|f)g', { bash: true, windows: true })); + }); + + test('"abcfefg" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abcfefg', 'ab***ef', { bash: true, windows: true })); + }); + + test('"abcfefg" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*+(e|f)', { bash: true, windows: true })); + }); + + test('"abcfefg" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*d+(e|f)', { bash: true, windows: true })); + }); + + test('"abcfefg" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab?*(e|f)', { bash: true, windows: true })); + }); + + test('"abcx" should match "!([[*])*"', () => { + expect_truthy(isMatch('abcx', '!([[*])*', { bash: true, windows: true })); + }); + + test('"abcx" should match "+(a|b\\[)*"', () => { + expect_truthy(isMatch('abcx', '+(a|b\\[)*', { bash: true, windows: true })); + }); + + test('"abcx" should not match "[a*(]*z"', () => { + expect_truthy(!isMatch('abcx', '[a*(]*z', { bash: true, windows: true })); + }); + + test('"abcXdefXghi" should match "*X*i"', () => { + expect_truthy(isMatch('abcXdefXghi', '*X*i', { bash: true, windows: true })); + }); + + test('"abcz" should match "!([[*])*"', () => { + expect_truthy(isMatch('abcz', '!([[*])*', { bash: true, windows: true })); + }); + + test('"abcz" should match "+(a|b\\[)*"', () => { + expect_truthy(isMatch('abcz', '+(a|b\\[)*', { bash: true, windows: true })); + }); + + test('"abcz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('abcz', '[a*(]*z', { bash: true, windows: true })); + }); + + test('"abd" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abd', '(a+|b)*', { bash: true, windows: true })); + }); + + test('"abd" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abd', '(a+|b)+', { bash: true, windows: true })); + }); + + test('"abd" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abd', '*?(a)bc', { bash: true, windows: true })); + }); + + test('"abd" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('abd', 'a!(*(b|B))', { bash: true, windows: true })); + }); + + test('"abd" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('abd', 'a!(@(b|B))', { bash: true, windows: true })); + }); + + test('"abd" should not match "a!(@(b|B))d"', () => { + expect_truthy(!isMatch('abd', 'a!(@(b|B))d', { bash: true, windows: true })); + }); + + test('"abd" should match "a(b*(foo|bar))d"', () => { + expect_truthy(isMatch('abd', 'a(b*(foo|bar))d', { bash: true, windows: true })); + }); + + test('"abd" should match "a+(b|c)d"', () => { + expect_truthy(isMatch('abd', 'a+(b|c)d', { bash: true, windows: true })); + }); + + test('"abd" should match "a[b*(foo|bar)]d"', () => { + expect_truthy(isMatch('abd', 'a[b*(foo|bar)]d', { bash: true, windows: true })); + }); + + test('"abd" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*(e|f)', { bash: true, windows: true })); + }); + + test('"abd" should match "ab**"', () => { + expect_truthy(isMatch('abd', 'ab**', { bash: true, windows: true })); + }); + + test('"abd" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abd', 'ab**(e|f)', { bash: true, windows: true })); + }); + + test('"abd" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abd', 'ab**(e|f)g', { bash: true, windows: true })); + }); + + test('"abd" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abd', 'ab***ef', { bash: true, windows: true })); + }); + + test('"abd" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*+(e|f)', { bash: true, windows: true })); + }); + + test('"abd" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*d+(e|f)', { bash: true, windows: true })); + }); + + test('"abd" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abd', 'ab?*(e|f)', { bash: true, windows: true })); + }); + + test('"abef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abef', '(a+|b)*', { bash: true, windows: true })); + }); + + test('"abef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abef', '(a+|b)+', { bash: true, windows: true })); + }); + + test('"abef" should not match "*(a+|b)"', () => { + expect_truthy(!isMatch('abef', '*(a+|b)', { bash: true, windows: true })); + }); + + test('"abef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abef', '*?(a)bc', { bash: true, windows: true })); + }); + + test('"abef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abef', 'a(b*(foo|bar))d', { bash: true, windows: true })); + }); + + test('"abef" should match "ab*(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab*(e|f)', { bash: true, windows: true })); + }); + + test('"abef" should match "ab**"', () => { + expect_truthy(isMatch('abef', 'ab**', { bash: true, windows: true })); + }); + + test('"abef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab**(e|f)', { bash: true, windows: true })); + }); + + test('"abef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abef', 'ab**(e|f)g', { bash: true, windows: true })); + }); + + test('"abef" should match "ab***ef"', () => { + expect_truthy(isMatch('abef', 'ab***ef', { bash: true, windows: true })); + }); + + test('"abef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab*+(e|f)', { bash: true, windows: true })); + }); + + test('"abef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abef', 'ab*d+(e|f)', { bash: true, windows: true })); + }); + + test('"abef" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab?*(e|f)', { bash: true, windows: true })); + }); + + test('"abz" should not match "a!(*)"', () => { + expect_truthy(!isMatch('abz', 'a!(*)', { bash: true, windows: true })); + }); + + test('"abz" should match "a!(z)"', () => { + expect_truthy(isMatch('abz', 'a!(z)', { bash: true, windows: true })); + }); + + test('"abz" should match "a*!(z)"', () => { + expect_truthy(isMatch('abz', 'a*!(z)', { bash: true, windows: true })); + }); + + test('"abz" should not match "a*(z)"', () => { + expect_truthy(!isMatch('abz', 'a*(z)', { bash: true, windows: true })); + }); + + test('"abz" should match "a**(z)"', () => { + expect_truthy(isMatch('abz', 'a**(z)', { bash: true, windows: true })); + }); + + test('"abz" should match "a*@(z)"', () => { + expect_truthy(isMatch('abz', 'a*@(z)', { bash: true, windows: true })); + }); + + test('"abz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('abz', 'a+(z)', { bash: true, windows: true })); + }); + + test('"abz" should not match "a?(z)"', () => { + expect_truthy(!isMatch('abz', 'a?(z)', { bash: true, windows: true })); + }); + + test('"abz" should not match "a@(z)"', () => { + expect_truthy(!isMatch('abz', 'a@(z)', { bash: true, windows: true })); + }); + + test('"ac" should not match "!(a)*"', () => { + expect_truthy(!isMatch('ac', '!(a)*', { bash: true, windows: true })); + }); + + test('"ac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('ac', '*(@(a))a@(c)', { bash: true, windows: true })); + }); + + test('"ac" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('ac', 'a!(*(b|B))', { bash: true, windows: true })); + }); + + test('"ac" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('ac', 'a!(@(b|B))', { bash: true, windows: true })); + }); + + test('"ac" should match "a!(b)*"', () => { + expect_truthy(isMatch('ac', 'a!(b)*', { bash: true, windows: true })); + }); + + test('"accdef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('accdef', '(a+|b)*', { bash: true, windows: true })); + }); + + test('"accdef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('accdef', '(a+|b)+', { bash: true, windows: true })); + }); + + test('"accdef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('accdef', '*?(a)bc', { bash: true, windows: true })); + }); + + test('"accdef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('accdef', 'a(b*(foo|bar))d', { bash: true, windows: true })); + }); + + test('"accdef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*(e|f)', { bash: true, windows: true })); + }); + + test('"accdef" should not match "ab**"', () => { + expect_truthy(!isMatch('accdef', 'ab**', { bash: true, windows: true })); + }); + + test('"accdef" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab**(e|f)', { bash: true, windows: true })); + }); + + test('"accdef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('accdef', 'ab**(e|f)g', { bash: true, windows: true })); + }); + + test('"accdef" should not match "ab***ef"', () => { + expect_truthy(!isMatch('accdef', 'ab***ef', { bash: true, windows: true })); + }); + + test('"accdef" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*+(e|f)', { bash: true, windows: true })); + }); + + test('"accdef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*d+(e|f)', { bash: true, windows: true })); + }); + + test('"accdef" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab?*(e|f)', { bash: true, windows: true })); + }); + + test('"acd" should match "(a+|b)*"', () => { + expect_truthy(isMatch('acd', '(a+|b)*', { bash: true, windows: true })); + }); + + test('"acd" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('acd', '(a+|b)+', { bash: true, windows: true })); + }); + + test('"acd" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('acd', '*?(a)bc', { bash: true, windows: true })); + }); + + test('"acd" should match "@(ab|a*(b))*(c)d"', () => { + expect_truthy(isMatch('acd', '@(ab|a*(b))*(c)d', { bash: true, windows: true })); + }); + + test('"acd" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('acd', 'a!(*(b|B))', { bash: true, windows: true })); + }); + + test('"acd" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('acd', 'a!(@(b|B))', { bash: true, windows: true })); + }); + + test('"acd" should match "a!(@(b|B))d"', () => { + expect_truthy(isMatch('acd', 'a!(@(b|B))d', { bash: true, windows: true })); + }); + + test('"acd" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('acd', 'a(b*(foo|bar))d', { bash: true, windows: true })); + }); + + test('"acd" should match "a+(b|c)d"', () => { + expect_truthy(isMatch('acd', 'a+(b|c)d', { bash: true, windows: true })); + }); + + test('"acd" should not match "a[b*(foo|bar)]d"', () => { + expect_truthy(!isMatch('acd', 'a[b*(foo|bar)]d', { bash: true, windows: true })); + }); + + test('"acd" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*(e|f)', { bash: true, windows: true })); + }); + + test('"acd" should not match "ab**"', () => { + expect_truthy(!isMatch('acd', 'ab**', { bash: true, windows: true })); + }); + + test('"acd" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab**(e|f)', { bash: true, windows: true })); + }); + + test('"acd" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('acd', 'ab**(e|f)g', { bash: true, windows: true })); + }); + + test('"acd" should not match "ab***ef"', () => { + expect_truthy(!isMatch('acd', 'ab***ef', { bash: true, windows: true })); + }); + + test('"acd" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*+(e|f)', { bash: true, windows: true })); + }); + + test('"acd" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*d+(e|f)', { bash: true, windows: true })); + }); + + test('"acd" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab?*(e|f)', { bash: true, windows: true })); + }); + + test('"ax" should match "?(a*|b)"', () => { + expect_truthy(isMatch('ax', '?(a*|b)', { bash: true, windows: true })); + }); + + test('"ax" should not match "a?(b*)"', () => { + expect_truthy(!isMatch('ax', 'a?(b*)', { bash: true, windows: true })); + }); + + test('"axz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('axz', 'a+(z)', { bash: true, windows: true })); + }); + + test('"az" should not match "a!(*)"', () => { + expect_truthy(!isMatch('az', 'a!(*)', { bash: true, windows: true })); + }); + + test('"az" should not match "a!(z)"', () => { + expect_truthy(!isMatch('az', 'a!(z)', { bash: true, windows: true })); + }); + + test('"az" should match "a*!(z)"', () => { + expect_truthy(isMatch('az', 'a*!(z)', { bash: true, windows: true })); + }); + + test('"az" should match "a*(z)"', () => { + expect_truthy(isMatch('az', 'a*(z)', { bash: true, windows: true })); + }); + + test('"az" should match "a**(z)"', () => { + expect_truthy(isMatch('az', 'a**(z)', { bash: true, windows: true })); + }); + + test('"az" should match "a*@(z)"', () => { + expect_truthy(isMatch('az', 'a*@(z)', { bash: true, windows: true })); + }); + + test('"az" should match "a+(z)"', () => { + expect_truthy(isMatch('az', 'a+(z)', { bash: true, windows: true })); + }); + + test('"az" should match "a?(z)"', () => { + expect_truthy(isMatch('az', 'a?(z)', { bash: true, windows: true })); + }); + + test('"az" should match "a@(z)"', () => { + expect_truthy(isMatch('az', 'a@(z)', { bash: true, windows: true })); + }); + + test('"az" should not match "a\\z"', () => { + expect_truthy(!isMatch('az', 'a\\\\z', { bash: true, windows: false })); + }); + + test('"az" should not match "a\\z"', () => { + expect_truthy(!isMatch('az', 'a\\\\z', { bash: true, windows: true })); + }); + + test('"b" should match "!(a)*"', () => { + expect_truthy(isMatch('b', '!(a)*', { bash: true, windows: true })); + }); + + test('"b" should match "(a+|b)*"', () => { + expect_truthy(isMatch('b', '(a+|b)*', { bash: true, windows: true })); + }); + + test('"b" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('b', 'a!(b)*', { bash: true, windows: true })); + }); + + test('"b.a" should match "(b|a).(a)"', () => { + expect_truthy(isMatch('b.a', '(b|a).(a)', { bash: true, windows: true })); + }); + + test('"b.a" should match "@(b|a).@(a)"', () => { + expect_truthy(isMatch('b.a', '@(b|a).@(a)', { bash: true, windows: true })); + }); + + test('"b/a" should not match "!(b/a)"', () => { + expect_truthy(!isMatch('b/a', '!(b/a)', { bash: true, windows: true })); + }); + + test('"b/b" should match "!(b/a)"', () => { + expect_truthy(isMatch('b/b', '!(b/a)', { bash: true, windows: true })); + }); + + test('"b/c" should match "!(b/a)"', () => { + expect_truthy(isMatch('b/c', '!(b/a)', { bash: true, windows: true })); + }); + + test('"b/c" should not match "b/!(c)"', () => { + expect_truthy(!isMatch('b/c', 'b/!(c)', { bash: true, windows: true })); + }); + + test('"b/c" should match "b/!(cc)"', () => { + expect_truthy(isMatch('b/c', 'b/!(cc)', { bash: true, windows: true })); + }); + + test('"b/c.txt" should not match "b/!(c).txt"', () => { + expect_truthy(!isMatch('b/c.txt', 'b/!(c).txt', { bash: true, windows: true })); + }); + + test('"b/c.txt" should match "b/!(cc).txt"', () => { + expect_truthy(isMatch('b/c.txt', 'b/!(cc).txt', { bash: true, windows: true })); + }); + + test('"b/cc" should match "b/!(c)"', () => { + expect_truthy(isMatch('b/cc', 'b/!(c)', { bash: true, windows: true })); + }); + + test('"b/cc" should not match "b/!(cc)"', () => { + expect_truthy(!isMatch('b/cc', 'b/!(cc)', { bash: true, windows: true })); + }); + + test('"b/cc.txt" should not match "b/!(c).txt"', () => { + expect_truthy(!isMatch('b/cc.txt', 'b/!(c).txt', { bash: true, windows: true })); + }); + + test('"b/cc.txt" should not match "b/!(cc).txt"', () => { + expect_truthy(!isMatch('b/cc.txt', 'b/!(cc).txt', { bash: true, windows: true })); + }); + + test('"b/ccc" should match "b/!(c)"', () => { + expect_truthy(isMatch('b/ccc', 'b/!(c)', { bash: true, windows: true })); + }); + + test('"ba" should match "!(a!(b))"', () => { + expect_truthy(isMatch('ba', '!(a!(b))', { bash: true, windows: true })); + }); + + test('"ba" should match "b?(a|b)"', () => { + expect_truthy(isMatch('ba', 'b?(a|b)', { bash: true, windows: true })); + }); + + test('"baaac" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('baaac', '*(@(a))a@(c)', { bash: true, windows: true })); + }); + + test('"bar" should match "!(foo)"', () => { + expect_truthy(isMatch('bar', '!(foo)', { bash: true, windows: true })); + }); + + test('"bar" should match "!(foo)*"', () => { + expect_truthy(isMatch('bar', '!(foo)*', { bash: true, windows: true })); + }); + + test('"bar" should match "!(foo)b*"', () => { + expect_truthy(isMatch('bar', '!(foo)b*', { bash: true, windows: true })); + }); + + test('"bar" should match "*(!(foo))"', () => { + expect_truthy(isMatch('bar', '*(!(foo))', { bash: true, windows: true })); + }); + + test('"baz" should match "!(foo)*"', () => { + expect_truthy(isMatch('baz', '!(foo)*', { bash: true, windows: true })); + }); + + test('"baz" should match "!(foo)b*"', () => { + expect_truthy(isMatch('baz', '!(foo)b*', { bash: true, windows: true })); + }); + + test('"baz" should match "*(!(foo))"', () => { + expect_truthy(isMatch('baz', '*(!(foo))', { bash: true, windows: true })); + }); + + test('"bb" should match "!(a!(b))"', () => { + expect_truthy(isMatch('bb', '!(a!(b))', { bash: true, windows: true })); + }); + + test('"bb" should match "!(a)*"', () => { + expect_truthy(isMatch('bb', '!(a)*', { bash: true, windows: true })); + }); + + test('"bb" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('bb', 'a!(b)*', { bash: true, windows: true })); + }); + + test('"bb" should not match "a?(a|b)"', () => { + expect_truthy(!isMatch('bb', 'a?(a|b)', { bash: true, windows: true })); + }); + + test('"bbc" should match "!([[*])*"', () => { + expect_truthy(isMatch('bbc', '!([[*])*', { bash: true, windows: true })); + }); + + test('"bbc" should not match "+(a|b\\[)*"', () => { + expect_truthy(!isMatch('bbc', '+(a|b\\[)*', { bash: true, windows: true })); + }); + + test('"bbc" should not match "[a*(]*z"', () => { + expect_truthy(!isMatch('bbc', '[a*(]*z', { bash: true, windows: true })); + }); + + test('"bz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('bz', 'a+(z)', { bash: true, windows: true })); + }); + + test('"c" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('c', '*(@(a))a@(c)', { bash: true, windows: true })); + }); + + test('"c.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('c.a', '!(*.[a-b]*)', { bash: true, windows: true })); + }); + + test('"c.a" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('c.a', '!(*[a-b].[a-b]*)', { bash: true, windows: true })); + }); + + test('"c.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('c.a', '!*.(a|b)', { bash: true, windows: true })); + }); + + test('"c.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('c.a', '!*.(a|b)*', { bash: true, windows: true })); + }); + + test('"c.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('c.a', '(b|a).(a)', { bash: true, windows: true })); + }); + + test('"c.a" should not match "*.!(a)"', () => { + expect_truthy(!isMatch('c.a', '*.!(a)', { bash: true, windows: true })); + }); + + test('"c.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('c.a', '*.+(b|d)', { bash: true, windows: true })); + }); + + test('"c.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('c.a', '@(b|a).@(a)', { bash: true, windows: true })); + }); + + test('"c.c" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('c.c', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"c.c" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('c.c', '*!(.a|.b|.c)', { bash: true, windows: true })); + }); + + test('"c.c" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('c.c', '*.!(a|b|c)', { bash: true, windows: true })); + }); + + test('"c.c" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('c.c', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true, windows: true })); + }); + + test('"c.ccc" should match "!(*.[a-b]*)"', () => { + expect_truthy(isMatch('c.ccc', '!(*.[a-b]*)', { bash: true, windows: true })); + }); + + test('"c.ccc" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('c.ccc', '!(*[a-b].[a-b]*)', { bash: true, windows: true })); + }); + + test('"c.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('c.js', '!(*.js)', { bash: true, windows: true })); + }); + + test('"c.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('c.js', '*!(.js)', { bash: true, windows: true })); + }); + + test('"c.js" should not match "*.!(js)"', () => { + expect_truthy(!isMatch('c.js', '*.!(js)', { bash: true, windows: true })); + }); + + test('"c/a/v" should match "c/!(z)/v"', () => { + expect_truthy(isMatch('c/a/v', 'c/!(z)/v', { bash: true, windows: true })); + }); + + test('"c/a/v" should not match "c/*(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/*(z)/v', { bash: true, windows: true })); + }); + + test('"c/a/v" should not match "c/+(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/+(z)/v', { bash: true, windows: true })); + }); + + test('"c/a/v" should not match "c/@(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/@(z)/v', { bash: true, windows: true })); + }); + + test('"c/z/v" should not match "*(z)"', () => { + expect_truthy(!isMatch('c/z/v', '*(z)', { bash: true, windows: true })); + }); + + test('"c/z/v" should not match "+(z)"', () => { + expect_truthy(!isMatch('c/z/v', '+(z)', { bash: true, windows: true })); + }); + + test('"c/z/v" should not match "?(z)"', () => { + expect_truthy(!isMatch('c/z/v', '?(z)', { bash: true, windows: true })); + }); + + test('"c/z/v" should not match "c/!(z)/v"', () => { + expect_truthy(!isMatch('c/z/v', 'c/!(z)/v', { bash: true, windows: true })); + }); + + test('"c/z/v" should match "c/*(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/*(z)/v', { bash: true, windows: true })); + }); + + test('"c/z/v" should match "c/+(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/+(z)/v', { bash: true, windows: true })); + }); + + test('"c/z/v" should match "c/@(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/@(z)/v', { bash: true, windows: true })); + }); + + test('"c/z/v" should match "c/z/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/z/v', { bash: true, windows: true })); + }); + + test('"cc.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('cc.a', '(b|a).(a)', { bash: true, windows: true })); + }); + + test('"cc.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('cc.a', '@(b|a).@(a)', { bash: true, windows: true })); + }); + + test('"ccc" should match "!(a)*"', () => { + expect_truthy(isMatch('ccc', '!(a)*', { bash: true, windows: true })); + }); + + test('"ccc" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('ccc', 'a!(b)*', { bash: true, windows: true })); + }); + + test('"cow" should match "!(*.*)"', () => { + expect_truthy(isMatch('cow', '!(*.*)', { bash: true, windows: true })); + }); + + test('"cow" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('cow', '!(*.*).', { bash: true, windows: true })); + }); + + test('"cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('cow', '.!(*.*)', { bash: true, windows: true })); + }); + + test('"cz" should not match "a!(*)"', () => { + expect_truthy(!isMatch('cz', 'a!(*)', { bash: true, windows: true })); + }); + + test('"cz" should not match "a!(z)"', () => { + expect_truthy(!isMatch('cz', 'a!(z)', { bash: true, windows: true })); + }); + + test('"cz" should not match "a*!(z)"', () => { + expect_truthy(!isMatch('cz', 'a*!(z)', { bash: true, windows: true })); + }); + + test('"cz" should not match "a*(z)"', () => { + expect_truthy(!isMatch('cz', 'a*(z)', { bash: true, windows: true })); + }); + + test('"cz" should not match "a**(z)"', () => { + expect_truthy(!isMatch('cz', 'a**(z)', { bash: true, windows: true })); + }); + + test('"cz" should not match "a*@(z)"', () => { + expect_truthy(!isMatch('cz', 'a*@(z)', { bash: true, windows: true })); + }); + + test('"cz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('cz', 'a+(z)', { bash: true, windows: true })); + }); + + test('"cz" should not match "a?(z)"', () => { + expect_truthy(!isMatch('cz', 'a?(z)', { bash: true, windows: true })); + }); + + test('"cz" should not match "a@(z)"', () => { + expect_truthy(!isMatch('cz', 'a@(z)', { bash: true, windows: true })); + }); + + test('"d.a.d" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('d.a.d', '!(*.[a-b]*)', { bash: true, windows: true })); + }); + + test('"d.a.d" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('d.a.d', '!(*[a-b].[a-b]*)', { bash: true, windows: true })); + }); + + test('"d.a.d" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('d.a.d', '!*.(a|b)*', { bash: true, windows: true })); + }); + + test('"d.a.d" should match "!*.*(a|b)"', () => { + expect_truthy(isMatch('d.a.d', '!*.*(a|b)', { bash: true, windows: true })); + }); + + test('"d.a.d" should not match "!*.{a,b}*"', () => { + expect_truthy(!isMatch('d.a.d', '!*.{a,b}*', { bash: true, windows: true })); + }); + + test('"d.a.d" should match "*.!(a)"', () => { + expect_truthy(isMatch('d.a.d', '*.!(a)', { bash: true, windows: true })); + }); + + test('"d.a.d" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('d.a.d', '*.+(b|d)', { bash: true, windows: true })); + }); + + test('"d.d" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('d.d', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"d.d" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('d.d', '*!(.a|.b|.c)', { bash: true, windows: true })); + }); + + test('"d.d" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('d.d', '*.!(a|b|c)', { bash: true, windows: true })); + }); + + test('"d.d" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('d.d', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true, windows: true })); + }); + + test('"d.js.d" should match "!(*.js)"', () => { + expect_truthy(isMatch('d.js.d', '!(*.js)', { bash: true, windows: true })); + }); + + test('"d.js.d" should match "*!(.js)"', () => { + expect_truthy(isMatch('d.js.d', '*!(.js)', { bash: true, windows: true })); + }); + + test('"d.js.d" should match "*.!(js)"', () => { + expect_truthy(isMatch('d.js.d', '*.!(js)', { bash: true, windows: true })); + }); + + test('"dd.aa.d" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('dd.aa.d', '(b|a).(a)', { bash: true, windows: true })); + }); + + test('"dd.aa.d" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('dd.aa.d', '@(b|a).@(a)', { bash: true, windows: true })); + }); + + test('"def" should not match "()ef"', () => { + expect_truthy(!isMatch('def', '()ef', { bash: true, windows: true })); + }); + + test('"e.e" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('e.e', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"e.e" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('e.e', '*!(.a|.b|.c)', { bash: true, windows: true })); + }); + + test('"e.e" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('e.e', '*.!(a|b|c)', { bash: true, windows: true })); + }); + + test('"e.e" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('e.e', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true, windows: true })); + }); + + test('"ef" should match "()ef"', () => { + expect_truthy(isMatch('ef', '()ef', { bash: true, windows: true })); + }); + + test('"effgz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('effgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { bash: true, windows: true })); + }); + + test('"efgz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('efgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { bash: true, windows: true })); + }); + + test('"egz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('egz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { bash: true, windows: true })); + }); + + test('"egz" should not match "@(b+(c)d|e+(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(!isMatch('egz', '@(b+(c)d|e+(f)g?|?(h)i@(j|k))', { bash: true, windows: true })); + }); + + test('"egzefffgzbcdij" should match "*(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('egzefffgzbcdij', '*(b+(c)d|e*(f)g?|?(h)i@(j|k))', { bash: true, windows: true })); + }); + + test('"f" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('f', '!(f!(o))', { bash: true, windows: true })); + }); + + test('"f" should match "!(f(o))"', () => { + expect_truthy(isMatch('f', '!(f(o))', { bash: true, windows: true })); + }); + + test('"f" should not match "!(f)"', () => { + expect_truthy(!isMatch('f', '!(f)', { bash: true, windows: true })); + }); + + test('"f" should not match "*(!(f))"', () => { + expect_truthy(!isMatch('f', '*(!(f))', { bash: true, windows: true })); + }); + + test('"f" should not match "+(!(f))"', () => { + expect_truthy(!isMatch('f', '+(!(f))', { bash: true, windows: true })); + }); + + test('"f.a" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('f.a', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"f.a" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('f.a', '*!(.a|.b|.c)', { bash: true, windows: true })); + }); + + test('"f.a" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('f.a', '*.!(a|b|c)', { bash: true, windows: true })); + }); + + test('"f.f" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('f.f', '!(*.a|*.b|*.c)', { bash: true, windows: true })); + }); + + test('"f.f" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('f.f', '*!(.a|.b|.c)', { bash: true, windows: true })); + }); + + test('"f.f" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('f.f', '*.!(a|b|c)', { bash: true, windows: true })); + }); + + test('"f.f" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('f.f', '*.(a|b|@(ab|a*@(b))*(c)d)', { bash: true, windows: true })); + }); + + test('"fa" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('fa', '!(f!(o))', { bash: true, windows: true })); + }); + + test('"fa" should match "!(f(o))"', () => { + expect_truthy(isMatch('fa', '!(f(o))', { bash: true, windows: true })); + }); + + test('"fb" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('fb', '!(f!(o))', { bash: true, windows: true })); + }); + + test('"fb" should match "!(f(o))"', () => { + expect_truthy(isMatch('fb', '!(f(o))', { bash: true, windows: true })); + }); + + test('"fff" should match "!(f)"', () => { + expect_truthy(isMatch('fff', '!(f)', { bash: true, windows: true })); + }); + + test('"fff" should match "*(!(f))"', () => { + expect_truthy(isMatch('fff', '*(!(f))', { bash: true, windows: true })); + }); + + test('"fff" should match "+(!(f))"', () => { + expect_truthy(isMatch('fff', '+(!(f))', { bash: true, windows: true })); + }); + + test('"fffooofoooooffoofffooofff" should match "*(*(f)*(o))"', () => { + expect_truthy(isMatch('fffooofoooooffoofffooofff', '*(*(f)*(o))', { bash: true, windows: true })); + }); + + test('"ffo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('ffo', '*(f*(o))', { bash: true, windows: true })); + }); + + test('"file.C" should not match "*.c?(c)"', () => { + expect_truthy(!isMatch('file.C', '*.c?(c)', { bash: true, windows: true })); + }); + + test('"file.c" should match "*.c?(c)"', () => { + expect_truthy(isMatch('file.c', '*.c?(c)', { bash: true, windows: true })); + }); + + test('"file.cc" should match "*.c?(c)"', () => { + expect_truthy(isMatch('file.cc', '*.c?(c)', { bash: true, windows: true })); + }); + + test('"file.ccc" should not match "*.c?(c)"', () => { + expect_truthy(!isMatch('file.ccc', '*.c?(c)', { bash: true, windows: true })); + }); + + test('"fo" should match "!(f!(o))"', () => { + expect_truthy(isMatch('fo', '!(f!(o))', { bash: true, windows: true })); + }); + + test('"fo" should not match "!(f(o))"', () => { + expect_truthy(!isMatch('fo', '!(f(o))', { bash: true, windows: true })); + }); + + test('"fofo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('fofo', '*(f*(o))', { bash: true, windows: true })); + }); + + test('"fofoofoofofoo" should match "*(fo|foo)"', () => { + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)', { bash: true, windows: true })); + }); + + test('"fofoofoofofoo" should match "*(fo|foo)"', () => { + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)', { bash: true, windows: true })); + }); + + test('"foo" should match "!(!(foo))"', () => { + expect_truthy(isMatch('foo', '!(!(foo))', { bash: true, windows: true })); + }); + + test('"foo" should match "!(f)"', () => { + expect_truthy(isMatch('foo', '!(f)', { bash: true, windows: true })); + }); + + test('"foo" should not match "!(foo)"', () => { + expect_truthy(!isMatch('foo', '!(foo)', { bash: true, windows: true })); + }); + + test('"foo" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foo', '!(foo)*', { bash: true, windows: true })); + }); + + test('"foo" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foo', '!(foo)*', { bash: true, windows: true })); + }); + + test('"foo" should not match "!(foo)+"', () => { + expect_truthy(!isMatch('foo', '!(foo)+', { bash: true, windows: true })); + }); + + test('"foo" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foo', '!(foo)b*', { bash: true, windows: true })); + }); + + test('"foo" should match "!(x)"', () => { + expect_truthy(isMatch('foo', '!(x)', { bash: true, windows: true })); + }); + + test('"foo" should match "!(x)*"', () => { + expect_truthy(isMatch('foo', '!(x)*', { bash: true, windows: true })); + }); + + test('"foo" should match "*"', () => { + expect_truthy(isMatch('foo', '*', { bash: true, windows: true })); + }); + + test('"foo" should match "*(!(f))"', () => { + expect_truthy(isMatch('foo', '*(!(f))', { bash: true, windows: true })); + }); + + test('"foo" should not match "*(!(foo))"', () => { + expect_truthy(!isMatch('foo', '*(!(foo))', { bash: true, windows: true })); + }); + + test('"foo" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('foo', '*(@(a))a@(c)', { bash: true, windows: true })); + }); + + test('"foo" should match "*(@(foo))"', () => { + expect_truthy(isMatch('foo', '*(@(foo))', { bash: true, windows: true })); + }); + + test('"foo" should not match "*(a|b\\[)"', () => { + expect_truthy(!isMatch('foo', '*(a|b\\[)', { bash: true, windows: true })); + }); + + test('"foo" should match "*(a|b\\[)|f*"', () => { + expect_truthy(isMatch('foo', '*(a|b\\[)|f*', { bash: true, windows: true })); + }); + + test('"foo" should match "@(*(a|b\\[)|f*)"', () => { + expect_truthy(isMatch('foo', '@(*(a|b\\[)|f*)', { bash: true, windows: true })); + }); + + test('"foo" should not match "*/*/*"', () => { + expect_truthy(!isMatch('foo', '*/*/*', { bash: true, windows: true })); + }); + + test('"foo" should not match "*f"', () => { + expect_truthy(!isMatch('foo', '*f', { bash: true, windows: true })); + }); + + test('"foo" should match "*foo*"', () => { + expect_truthy(isMatch('foo', '*foo*', { bash: true, windows: true })); + }); + + test('"foo" should match "+(!(f))"', () => { + expect_truthy(isMatch('foo', '+(!(f))', { bash: true, windows: true })); + }); + + test('"foo" should not match "??"', () => { + expect_truthy(!isMatch('foo', '??', { bash: true, windows: true })); + }); + + test('"foo" should match "???"', () => { + expect_truthy(isMatch('foo', '???', { bash: true, windows: true })); + }); + + test('"foo" should not match "bar"', () => { + expect_truthy(!isMatch('foo', 'bar', { bash: true, windows: true })); + }); + + test('"foo" should match "f*"', () => { + expect_truthy(isMatch('foo', 'f*', { bash: true, windows: true })); + }); + + test('"foo" should not match "fo"', () => { + expect_truthy(!isMatch('foo', 'fo', { bash: true, windows: true })); + }); + + test('"foo" should match "foo"', () => { + expect_truthy(isMatch('foo', 'foo', { bash: true, windows: true })); + }); + + test('"foo" should match "{*(a|b\\[),f*}"', () => { + expect_truthy(isMatch('foo', '{*(a|b\\[),f*}', { bash: true, windows: true })); + }); + + test('"foo*" should match "foo\\*"', () => { + expect_truthy(isMatch('foo*', 'foo\\*', { bash: true, windows: false })); + }); + + test('"foo*bar" should match "foo\\*bar"', () => { + expect_truthy(isMatch('foo*bar', 'foo\\*bar', { bash: true, windows: true })); + }); + + test('"foo.js" should not match "!(foo).js"', () => { + expect_truthy(!isMatch('foo.js', '!(foo).js', { bash: true, windows: true })); + }); + + test('"foo.js.js" should match "*.!(js)"', () => { + expect_truthy(isMatch('foo.js.js', '*.!(js)', { bash: true, windows: true })); + }); + + test('"foo.js.js" should not match "*.!(js)*"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)*', { bash: true, windows: true })); + }); + + test('"foo.js.js" should not match "*.!(js)*.!(js)"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)*.!(js)', { bash: true, windows: true })); + }); + + test('"foo.js.js" should not match "*.!(js)+"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)+', { bash: true, windows: true })); + }); + + test('"foo.txt" should match "**/!(bar).txt"', () => { + expect_truthy(isMatch('foo.txt', '**/!(bar).txt', { bash: true, windows: true })); + }); + + test('"foo/bar" should not match "*/*/*"', () => { + expect_truthy(!isMatch('foo/bar', '*/*/*', { bash: true, windows: true })); + }); + + test('"foo/bar" should match "foo/!(foo)"', () => { + expect_truthy(isMatch('foo/bar', 'foo/!(foo)', { bash: true, windows: true })); + }); + + test('"foo/bar" should match "foo/*"', () => { + expect_truthy(isMatch('foo/bar', 'foo/*', { bash: true, windows: true })); + }); + + test('"foo/bar" should match "foo/bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo/bar', { bash: true, windows: true })); + }); + + test('"foo/bar" should not match "foo?bar"', () => { + expect_truthy(!isMatch('foo/bar', 'foo?bar', { bash: true, windows: true })); + }); + + test('"foo/bar" should match "foo[/]bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo[/]bar', { bash: true, windows: true })); + }); + + test('"foo/bar/baz.jsx" should match "foo/bar/**/*.+(js|jsx)"', () => { + expect_truthy(isMatch('foo/bar/baz.jsx', 'foo/bar/**/*.+(js|jsx)', { bash: true, windows: true })); + }); + + test('"foo/bar/baz.jsx" should match "foo/bar/*.+(js|jsx)"', () => { + expect_truthy(isMatch('foo/bar/baz.jsx', 'foo/bar/*.+(js|jsx)', { bash: true, windows: true })); + }); + + test('"foo/bb/aa/rr" should match "**/**/**"', () => { + expect_truthy(isMatch('foo/bb/aa/rr', '**/**/**', { bash: true, windows: true })); + }); + + test('"foo/bb/aa/rr" should match "*/*/*"', () => { + expect_truthy(isMatch('foo/bb/aa/rr', '*/*/*', { bash: true, windows: true })); + }); + + test('"foo/bba/arr" should match "*/*/*"', () => { + expect_truthy(isMatch('foo/bba/arr', '*/*/*', { bash: true, windows: true })); + }); + + test('"foo/bba/arr" should match "foo*"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo*', { bash: true, windows: true })); + }); + + test('"foo/bba/arr" should match "foo**"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo**', { bash: true, windows: true })); + }); + + test('"foo/bba/arr" should match "foo/*"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo/*', { bash: true, windows: true })); + }); + + test('"foo/bba/arr" should match "foo/**"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo/**', { bash: true, windows: true })); + }); + + test('"foo/bba/arr" should match "foo/**arr"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo/**arr', { bash: true, windows: true })); + }); + + test('"foo/bba/arr" should not match "foo/**z"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/**z', { bash: true, windows: true })); + }); + + test('"foo/bba/arr" should match "foo/*arr"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo/*arr', { bash: true, windows: true })); + }); + + test('"foo/bba/arr" should not match "foo/*z"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/*z', { bash: true, windows: true })); + }); + + test('"foob" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foob', '!(foo)b*', { bash: true, windows: true })); + }); + + test('"foob" should not match "(foo)bb"', () => { + expect_truthy(!isMatch('foob', '(foo)bb', { bash: true, windows: true })); + }); + + test('"foobar" should match "!(foo)"', () => { + expect_truthy(isMatch('foobar', '!(foo)', { bash: true, windows: true })); + }); + + test('"foobar" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foobar', '!(foo)*', { bash: true, windows: true })); + }); + + test('"foobar" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foobar', '!(foo)*', { bash: true, windows: true })); + }); + + test('"foobar" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foobar', '!(foo)b*', { bash: true, windows: true })); + }); + + test('"foobar" should match "*(!(foo))"', () => { + expect_truthy(isMatch('foobar', '*(!(foo))', { bash: true, windows: true })); + }); + + test('"foobar" should match "*ob*a*r*"', () => { + expect_truthy(isMatch('foobar', '*ob*a*r*', { bash: true, windows: true })); + }); + + test('"foobar" should match "foo\\*bar"', () => { + expect_truthy(isMatch('foobar', 'foo*bar', { bash: true, windows: true })); + }); + + test('"foobb" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foobb', '!(foo)b*', { bash: true, windows: true })); + }); + + test('"foobb" should match "(foo)bb"', () => { + expect_truthy(isMatch('foobb', '(foo)bb', { bash: true, windows: true })); + }); + + test('"(foo)bb" should match "\\(foo\\)bb"', () => { + expect_truthy(isMatch('(foo)bb', '\\(foo\\)bb', { bash: true, windows: true })); + }); + + test('"foofoofo" should match "@(foo|f|fo)*(f|of+(o))"', () => { + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))', { bash: true, windows: true })); + }); + + test('"foofoofo" should match "@(foo|f|fo)*(f|of+(o))"', () => { + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))', { bash: true, windows: true })); + }); + + test('"fooofoofofooo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('fooofoofofooo', '*(f*(o))', { bash: true, windows: true })); + }); + + test('"foooofo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('foooofo', '*(f*(o))', { bash: true, windows: true })); + }); + + test('"foooofof" should match "*(f*(o))"', () => { + expect_truthy(isMatch('foooofof', '*(f*(o))', { bash: true, windows: true })); + }); + + test('"foooofof" should not match "*(f+(o))"', () => { + expect_truthy(!isMatch('foooofof', '*(f+(o))', { bash: true, windows: true })); + }); + + test('"foooofofx" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('foooofofx', '*(f*(o))', { bash: true, windows: true })); + }); + + test('"foooxfooxfoxfooox" should match "*(f*(o)x)"', () => { + expect_truthy(isMatch('foooxfooxfoxfooox', '*(f*(o)x)', { bash: true, windows: true })); + }); + + test('"foooxfooxfxfooox" should match "*(f*(o)x)"', () => { + expect_truthy(isMatch('foooxfooxfxfooox', '*(f*(o)x)', { bash: true, windows: true })); + }); + + test('"foooxfooxofoxfooox" should not match "*(f*(o)x)"', () => { + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f*(o)x)', { bash: true, windows: true })); + }); + + test('"foot" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('foot', '@(!(z*)|*x)', { bash: true, windows: true })); + }); + + test('"foox" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('foox', '@(!(z*)|*x)', { bash: true, windows: true })); + }); + + test('"fz" should not match "*(z)"', () => { + expect_truthy(!isMatch('fz', '*(z)', { bash: true, windows: true })); + }); + + test('"fz" should not match "+(z)"', () => { + expect_truthy(!isMatch('fz', '+(z)', { bash: true, windows: true })); + }); + + test('"fz" should not match "?(z)"', () => { + expect_truthy(!isMatch('fz', '?(z)', { bash: true, windows: true })); + }); + + test('"moo.cow" should not match "!(moo).!(cow)"', () => { + expect_truthy(!isMatch('moo.cow', '!(moo).!(cow)', { bash: true, windows: true })); + }); + + test('"moo.cow" should not match "!(*).!(*)"', () => { + expect_truthy(!isMatch('moo.cow', '!(*).!(*)', { bash: true, windows: true })); + }); + + test('"moo.cow" should not match "!(*.*).!(*.*)"', () => { + expect_truthy(!isMatch('moo.cow', '!(*.*).!(*.*)', { bash: true, windows: true })); + }); + + test('"mad.moo.cow" should not match "!(*.*).!(*.*)"', () => { + expect_truthy(!isMatch('mad.moo.cow', '!(*.*).!(*.*)', { bash: true, windows: true })); + }); + + test('"mad.moo.cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('mad.moo.cow', '.!(*.*)', { bash: true, windows: true })); + }); + + test('"Makefile" should match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(isMatch('Makefile', '!(*.c|*.h|Makefile.in|config*|README)', { bash: true, windows: true })); + }); + + test('"Makefile.in" should not match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(!isMatch('Makefile.in', '!(*.c|*.h|Makefile.in|config*|README)', { bash: true, windows: true })); + }); + + test('"moo" should match "!(*.*)"', () => { + expect_truthy(isMatch('moo', '!(*.*)', { bash: true, windows: true })); + }); + + test('"moo" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('moo', '!(*.*).', { bash: true, windows: true })); + }); + + test('"moo" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('moo', '.!(*.*)', { bash: true, windows: true })); + }); + + test('"moo.cow" should not match "!(*.*)"', () => { + expect_truthy(!isMatch('moo.cow', '!(*.*)', { bash: true, windows: true })); + }); + + test('"moo.cow" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('moo.cow', '!(*.*).', { bash: true, windows: true })); + }); + + test('"moo.cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('moo.cow', '.!(*.*)', { bash: true, windows: true })); + }); + + test('"mucca.pazza" should not match "mu!(*(c))?.pa!(*(z))?"', () => { + expect_truthy(!isMatch('mucca.pazza', 'mu!(*(c))?.pa!(*(z))?', { bash: true, windows: true })); + }); + + test('"ofoofo" should match "*(of+(o))"', () => { + expect_truthy(isMatch('ofoofo', '*(of+(o))', { bash: true, windows: true })); + }); + + test('"ofoofo" should match "*(of+(o)|f)"', () => { + expect_truthy(isMatch('ofoofo', '*(of+(o)|f)', { bash: true, windows: true })); + }); + + test('"ofooofoofofooo" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('ofooofoofofooo', '*(f*(o))', { bash: true, windows: true })); + }); + + test('"ofoooxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxo', '*(*(of*(o)x)o)', { bash: true, windows: true })); + }); + + test('"ofoooxoofxoofoooxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxo', '*(*(of*(o)x)o)', { bash: true, windows: true })); + }); + + test('"ofoooxoofxoofoooxoofxofo" should not match "*(*(of*(o)x)o)"', () => { + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(*(of*(o)x)o)', { bash: true, windows: true })); + }); + + test('"ofoooxoofxoofoooxoofxoo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxoo', '*(*(of*(o)x)o)', { bash: true, windows: true })); + }); + + test('"ofoooxoofxoofoooxoofxooofxofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(*(of*(o)x)o)', { bash: true, windows: true })); + }); + + test('"ofxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofxoofxo', '*(*(of*(o)x)o)', { bash: true, windows: true })); + }); + + test('"oofooofo" should match "*(of|oof+(o))"', () => { + expect_truthy(isMatch('oofooofo', '*(of|oof+(o))', { bash: true, windows: true })); + }); + + test('"ooo" should match "!(f)"', () => { + expect_truthy(isMatch('ooo', '!(f)', { bash: true, windows: true })); + }); + + test('"ooo" should match "*(!(f))"', () => { + expect_truthy(isMatch('ooo', '*(!(f))', { bash: true, windows: true })); + }); + + test('"ooo" should match "+(!(f))"', () => { + expect_truthy(isMatch('ooo', '+(!(f))', { bash: true, windows: true })); + }); + + test('"oxfoxfox" should not match "*(oxf+(ox))"', () => { + expect_truthy(!isMatch('oxfoxfox', '*(oxf+(ox))', { bash: true, windows: true })); + }); + + test('"oxfoxoxfox" should match "*(oxf+(ox))"', () => { + expect_truthy(isMatch('oxfoxoxfox', '*(oxf+(ox))', { bash: true, windows: true })); + }); + + test('"para" should match "para*([0-9])"', () => { + expect_truthy(isMatch('para', 'para*([0-9])', { bash: true, windows: true })); + }); + + test('"para" should not match "para+([0-9])"', () => { + expect_truthy(!isMatch('para', 'para+([0-9])', { bash: true, windows: true })); + }); + + test('"para.38" should match "para!(*.[00-09])"', () => { + expect_truthy(isMatch('para.38', 'para!(*.[00-09])', { bash: true, windows: true })); + }); + + test('"para.graph" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('para.graph', 'para!(*.[0-9])', { bash: true, windows: true })); + }); + + test('"para13829383746592" should match "para*([0-9])"', () => { + expect_truthy(isMatch('para13829383746592', 'para*([0-9])', { bash: true, windows: true })); + }); + + test('"para381" should not match "para?([345]|99)1"', () => { + expect_truthy(!isMatch('para381', 'para?([345]|99)1', { bash: true, windows: true })); + }); + + test('"para39" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('para39', 'para!(*.[0-9])', { bash: true, windows: true })); + }); + + test('"para987346523" should match "para+([0-9])"', () => { + expect_truthy(isMatch('para987346523', 'para+([0-9])', { bash: true, windows: true })); + }); + + test('"para991" should match "para?([345]|99)1"', () => { + expect_truthy(isMatch('para991', 'para?([345]|99)1', { bash: true, windows: true })); + }); + + test('"paragraph" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('paragraph', 'para!(*.[0-9])', { bash: true, windows: true })); + }); + + test('"paragraph" should not match "para*([0-9])"', () => { + expect_truthy(!isMatch('paragraph', 'para*([0-9])', { bash: true, windows: true })); + }); + + test('"paragraph" should match "para@(chute|graph)"', () => { + expect_truthy(isMatch('paragraph', 'para@(chute|graph)', { bash: true, windows: true })); + }); + + test('"paramour" should not match "para@(chute|graph)"', () => { + expect_truthy(!isMatch('paramour', 'para@(chute|graph)', { bash: true, windows: true })); + }); + + test('"parse.y" should match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(isMatch('parse.y', '!(*.c|*.h|Makefile.in|config*|README)', { bash: true, windows: true })); + }); + + test('"shell.c" should not match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(!isMatch('shell.c', '!(*.c|*.h|Makefile.in|config*|README)', { bash: true, windows: true })); + }); + + test('"VMS.FILE;" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;', '*\\;[1-9]*([0-9])', { bash: true, windows: true })); + }); + + test('"VMS.FILE;0" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;0', '*\\;[1-9]*([0-9])', { bash: true, windows: true })); + }); + + test('"VMS.FILE;9" should match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;9', '*\\;[1-9]*([0-9])', { bash: true, windows: true })); + }); + + test('"VMS.FILE;1" should match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;1', '*\\;[1-9]*([0-9])', { bash: true, windows: true })); + }); + + test('"VMS.FILE;1" should match "*;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;1', '*;[1-9]*([0-9])', { bash: true, windows: true })); + }); + + test('"VMS.FILE;139" should match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;139', '*\\;[1-9]*([0-9])', { bash: true, windows: true })); + }); + + test('"VMS.FILE;1N" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;1N', '*\\;[1-9]*([0-9])', { bash: true, windows: true })); + }); + + test('"xfoooofof" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('xfoooofof', '*(f*(o))', { bash: true, windows: true })); + }); + + test('"XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1" should match "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*"', () => { + expect_truthy(isMatch('XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1', 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*', { bash: true, windows: false })); + }); + + test('"XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1" should not match "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*"', () => { + expect_truthy(!isMatch('XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1', 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*', { bash: true, windows: true })); + }); + + test('"z" should match "*(z)"', () => { + expect_truthy(isMatch('z', '*(z)', { bash: true, windows: true })); + }); + + test('"z" should match "+(z)"', () => { + expect_truthy(isMatch('z', '+(z)', { bash: true, windows: true })); + }); + + test('"z" should match "?(z)"', () => { + expect_truthy(isMatch('z', '?(z)', { bash: true, windows: true })); + }); + + test('"zf" should not match "*(z)"', () => { + expect_truthy(!isMatch('zf', '*(z)', { bash: true, windows: true })); + }); + + test('"zf" should not match "+(z)"', () => { + expect_truthy(!isMatch('zf', '+(z)', { bash: true, windows: true })); + }); + + test('"zf" should not match "?(z)"', () => { + expect_truthy(!isMatch('zf', '?(z)', { bash: true, windows: true })); + }); + + test('"zoot" should not match "@(!(z*)|*x)"', () => { + expect_truthy(!isMatch('zoot', '@(!(z*)|*x)', { bash: true, windows: true })); + }); + + test('"zoox" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('zoox', '@(!(z*)|*x)', { bash: true, windows: true })); + }); + + test('"zz" should not match "(a+|b)*"', () => { + expect_truthy(!isMatch('zz', '(a+|b)*', { bash: true, windows: true })); + }); +}); + diff --git a/packages/node-utils/test/picomatch/extglobs-minimatch.test.ts b/packages/node-utils/test/picomatch/extglobs-minimatch.test.ts new file mode 100644 index 0000000..a95e174 --- /dev/null +++ b/packages/node-utils/test/picomatch/extglobs-minimatch.test.ts @@ -0,0 +1,2607 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +/** + * Some of tests were converted from bash 4.3, 4.4, and minimatch unit tests. + */ + +describe('extglobs (minimatch)', () => { + test('should not match empty string with "*(0|1|3|5|7|9)"', () => { + expect_truthy(!isMatch('', '*(0|1|3|5|7|9)', { windows: true })); + }); + + test('"*(a|b[)" should not match "*(a|b\\[)"', () => { + expect_truthy(!isMatch('*(a|b[)', '*(a|b\\[)', { windows: true })); + }); + + test('"*(a|b[)" should match "\\*\\(a\\|b\\[\\)"', () => { + expect_truthy(isMatch('*(a|b[)', '\\*\\(a\\|b\\[\\)', { windows: true })); + }); + + test('"***" should match "\\*\\*\\*"', () => { + expect_truthy(isMatch('***', '\\*\\*\\*', { windows: true })); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1" should not match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(!isMatch('-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*', { windows: true })); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1" should match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(isMatch('-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*', { windows: true })); + }); + + test('"-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1" should not match "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"', () => { + expect_truthy(!isMatch('-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*', { windows: true })); + }); + + test('"/dev/udp/129.22.8.102/45" should match "/dev\\/@(tcp|udp)\\/*\\/*"', () => { + expect_truthy(isMatch('/dev/udp/129.22.8.102/45', '/dev\\/@(tcp|udp)\\/*\\/*', { windows: true })); + }); + + test('"/x/y/z" should match "/x/y/z"', () => { + expect_truthy(isMatch('/x/y/z', '/x/y/z', { windows: true })); + }); + + test('"0377" should match "+([0-7])"', () => { + expect_truthy(isMatch('0377', '+([0-7])', { windows: true })); + }); + + test('"07" should match "+([0-7])"', () => { + expect_truthy(isMatch('07', '+([0-7])', { windows: true })); + }); + + test('"09" should not match "+([0-7])"', () => { + expect_truthy(!isMatch('09', '+([0-7])', { windows: true })); + }); + + test('"1" should match "0|[1-9]*([0-9])"', () => { + expect_truthy(isMatch('1', '0|[1-9]*([0-9])', { windows: true })); + }); + + test('"12" should match "0|[1-9]*([0-9])"', () => { + expect_truthy(isMatch('12', '0|[1-9]*([0-9])', { windows: true })); + }); + + test('"123abc" should not match "(a+|b)*"', () => { + expect_truthy(!isMatch('123abc', '(a+|b)*', { windows: true })); + }); + + test('"123abc" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('123abc', '(a+|b)+', { windows: true })); + }); + + test('"123abc" should match "*?(a)bc"', () => { + expect_truthy(isMatch('123abc', '*?(a)bc', { windows: true })); + }); + + test('"123abc" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('123abc', 'a(b*(foo|bar))d', { windows: true })); + }); + + test('"123abc" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*(e|f)', { windows: true })); + }); + + test('"123abc" should not match "ab**"', () => { + expect_truthy(!isMatch('123abc', 'ab**', { windows: true })); + }); + + test('"123abc" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab**(e|f)', { windows: true })); + }); + + test('"123abc" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('123abc', 'ab**(e|f)g', { windows: true })); + }); + + test('"123abc" should not match "ab***ef"', () => { + expect_truthy(!isMatch('123abc', 'ab***ef', { windows: true })); + }); + + test('"123abc" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*+(e|f)', { windows: true })); + }); + + test('"123abc" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab*d+(e|f)', { windows: true })); + }); + + test('"123abc" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('123abc', 'ab?*(e|f)', { windows: true })); + }); + + test('"12abc" should not match "0|[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('12abc', '0|[1-9]*([0-9])', { windows: true })); + }); + + test('"137577991" should match "*(0|1|3|5|7|9)"', () => { + expect_truthy(isMatch('137577991', '*(0|1|3|5|7|9)', { windows: true })); + }); + + test('"2468" should not match "*(0|1|3|5|7|9)"', () => { + expect_truthy(!isMatch('2468', '*(0|1|3|5|7|9)', { windows: true })); + }); + + test('"?a?b" should match "\\??\\?b"', () => { + expect_truthy(isMatch('?a?b', '\\??\\?b', { windows: true })); + }); + + test('"\\a\\b\\c" should not match "abc"', () => { + expect_truthy(!isMatch('\\a\\b\\c', 'abc', { windows: true })); + }); + + test('"a" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"a" should not match "!(a)"', () => { + expect_truthy(!isMatch('a', '!(a)', { windows: true })); + }); + + test('"a" should not match "!(a)*"', () => { + expect_truthy(!isMatch('a', '!(a)*', { windows: true })); + }); + + test('"a" should match "(a)"', () => { + expect_truthy(isMatch('a', '(a)', { windows: true })); + }); + + test('"a" should not match "(b)"', () => { + expect_truthy(!isMatch('a', '(b)', { windows: true })); + }); + + test('"a" should match "*(a)"', () => { + expect_truthy(isMatch('a', '*(a)', { windows: true })); + }); + + test('"a" should match "+(a)"', () => { + expect_truthy(isMatch('a', '+(a)', { windows: true })); + }); + + test('"a" should match "?"', () => { + expect_truthy(isMatch('a', '?', { windows: true })); + }); + + test('"a" should match "?(a|b)"', () => { + expect_truthy(isMatch('a', '?(a|b)', { windows: true })); + }); + + test('"a" should not match "??"', () => { + expect_truthy(!isMatch('a', '??', { windows: true })); + }); + + test('"a" should match "a!(b)*"', () => { + expect_truthy(isMatch('a', 'a!(b)*', { windows: true })); + }); + + test('"a" should match "a?(a|b)"', () => { + expect_truthy(isMatch('a', 'a?(a|b)', { windows: true })); + }); + + test('"a" should match "a?(x)"', () => { + expect_truthy(isMatch('a', 'a?(x)', { windows: true })); + }); + + test('"a" should not match "a??b"', () => { + expect_truthy(!isMatch('a', 'a??b', { windows: true })); + }); + + test('"a" should not match "b?(a|b)"', () => { + expect_truthy(!isMatch('a', 'b?(a|b)', { windows: true })); + }); + + test('"a((((b" should match "a(*b"', () => { + expect_truthy(isMatch('a((((b', 'a(*b', { windows: true })); + }); + + test('"a((((b" should not match "a(b"', () => { + expect_truthy(!isMatch('a((((b', 'a(b', { windows: true })); + }); + + test('"a((((b" should not match "a\\(b"', () => { + expect_truthy(!isMatch('a((((b', 'a\\(b', { windows: true })); + }); + + test('"a((b" should match "a(*b"', () => { + expect_truthy(isMatch('a((b', 'a(*b', { windows: true })); + }); + + test('"a((b" should not match "a(b"', () => { + expect_truthy(!isMatch('a((b', 'a(b', { windows: true })); + }); + + test('"a((b" should not match "a\\(b"', () => { + expect_truthy(!isMatch('a((b', 'a\\(b', { windows: true })); + }); + + test('"a(b" should match "a(*b"', () => { + expect_truthy(isMatch('a(b', 'a(*b', { windows: true })); + }); + + test('"a(b" should match "a(b"', () => { + expect_truthy(isMatch('a(b', 'a(b', { windows: true })); + }); + + test('"a(b" should match "a\\(b"', () => { + expect_truthy(isMatch('a(b', 'a\\(b', { windows: true })); + }); + + test('"a." should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"a." should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.', '*!(.a|.b|.c)', { windows: true })); + }); + + test('"a." should match "*.!(a)"', () => { + expect_truthy(isMatch('a.', '*.!(a)', { windows: true })); + }); + + test('"a." should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.', '*.!(a|b|c)', { windows: true })); + }); + + test('"a." should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.', '*.(a|b|@(ab|a*@(b))*(c)d)', { windows: true })); + }); + + test('"a." should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.', '*.+(b|d)', { windows: true })); + }); + + test('"a.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.a', '!(*.[a-b]*)', { windows: true })); + }); + + test('"a.a" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.a', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"a.a" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.a', '!(*[a-b].[a-b]*)', { windows: true })); + }); + + test('"a.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.a', '!*.(a|b)', { windows: true })); + }); + + test('"a.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.a', '!*.(a|b)*', { windows: true })); + }); + + test('"a.a" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.a', '(a|d).(a|b)*', { windows: true })); + }); + + test('"a.a" should match "(b|a).(a)"', () => { + expect_truthy(isMatch('a.a', '(b|a).(a)', { windows: true })); + }); + + test('"a.a" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.a', '*!(.a|.b|.c)', { windows: true })); + }); + + test('"a.a" should not match "*.!(a)"', () => { + expect_truthy(!isMatch('a.a', '*.!(a)', { windows: true })); + }); + + test('"a.a" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.a', '*.!(a|b|c)', { windows: true })); + }); + + test('"a.a" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.a', '*.(a|b|@(ab|a*@(b))*(c)d)', { windows: true })); + }); + + test('"a.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.a', '*.+(b|d)', { windows: true })); + }); + + test('"a.a" should match "@(b|a).@(a)"', () => { + expect_truthy(isMatch('a.a', '@(b|a).@(a)', { windows: true })); + }); + + test('"a.a.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.a.a', '!(*.[a-b]*)', { windows: true })); + }); + + test('"a.a.a" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.a.a', '!(*[a-b].[a-b]*)', { windows: true })); + }); + + test('"a.a.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.a.a', '!*.(a|b)', { windows: true })); + }); + + test('"a.a.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.a.a', '!*.(a|b)*', { windows: true })); + }); + + test('"a.a.a" should match "*.!(a)"', () => { + expect_truthy(isMatch('a.a.a', '*.!(a)', { windows: true })); + }); + + test('"a.a.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.a.a', '*.+(b|d)', { windows: true })); + }); + + test('"a.aa.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('a.aa.a', '(b|a).(a)', { windows: true })); + }); + + test('"a.aa.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('a.aa.a', '@(b|a).@(a)', { windows: true })); + }); + + test('"a.abcd" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.abcd', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"a.abcd" should not match "!(*.a|*.b|*.c)*"', () => { + expect_truthy(!isMatch('a.abcd', '!(*.a|*.b|*.c)*', { windows: true })); + }); + + test('"a.abcd" should match "*!(*.a|*.b|*.c)*"', () => { + expect_truthy(isMatch('a.abcd', '*!(*.a|*.b|*.c)*', { windows: true })); + }); + + test('"a.abcd" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.abcd', '*!(.a|.b|.c)', { windows: true })); + }); + + test('"a.abcd" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.abcd', '*.!(a|b|c)', { windows: true })); + }); + + test('"a.abcd" should not match "*.!(a|b|c)*"', () => { + expect_truthy(!isMatch('a.abcd', '*.!(a|b|c)*', { windows: true })); + }); + + test('"a.abcd" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.abcd', '*.(a|b|@(ab|a*@(b))*(c)d)', { windows: true })); + }); + + test('"a.b" should not match "!(*.*)"', () => { + expect_truthy(!isMatch('a.b', '!(*.*)', { windows: true })); + }); + + test('"a.b" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.b', '!(*.[a-b]*)', { windows: true })); + }); + + test('"a.b" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.b', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"a.b" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.b', '!(*[a-b].[a-b]*)', { windows: true })); + }); + + test('"a.b" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('a.b', '!*.(a|b)', { windows: true })); + }); + + test('"a.b" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.b', '!*.(a|b)*', { windows: true })); + }); + + test('"a.b" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.b', '(a|d).(a|b)*', { windows: true })); + }); + + test('"a.b" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.b', '*!(.a|.b|.c)', { windows: true })); + }); + + test('"a.b" should match "*.!(a)"', () => { + expect_truthy(isMatch('a.b', '*.!(a)', { windows: true })); + }); + + test('"a.b" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.b', '*.!(a|b|c)', { windows: true })); + }); + + test('"a.b" should match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(isMatch('a.b', '*.(a|b|@(ab|a*@(b))*(c)d)', { windows: true })); + }); + + test('"a.b" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('a.b', '*.+(b|d)', { windows: true })); + }); + + test('"a.bb" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('a.bb', '!(*.[a-b]*)', { windows: true })); + }); + + test('"a.bb" should not match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(!isMatch('a.bb', '!(*[a-b].[a-b]*)', { windows: true })); + }); + + test('"a.bb" should match "!*.(a|b)"', () => { + expect_truthy(isMatch('a.bb', '!*.(a|b)', { windows: true })); + }); + + test('"a.bb" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('a.bb', '!*.(a|b)*', { windows: true })); + }); + + test('"a.bb" should not match "!*.*(a|b)"', () => { + expect_truthy(!isMatch('a.bb', '!*.*(a|b)', { windows: true })); + }); + + test('"a.bb" should match "(a|d).(a|b)*"', () => { + expect_truthy(isMatch('a.bb', '(a|d).(a|b)*', { windows: true })); + }); + + test('"a.bb" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('a.bb', '(b|a).(a)', { windows: true })); + }); + + test('"a.bb" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('a.bb', '*.+(b|d)', { windows: true })); + }); + + test('"a.bb" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('a.bb', '@(b|a).@(a)', { windows: true })); + }); + + test('"a.c" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('a.c', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"a.c" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.c', '*!(.a|.b|.c)', { windows: true })); + }); + + test('"a.c" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('a.c', '*.!(a|b|c)', { windows: true })); + }); + + test('"a.c" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.c', '*.(a|b|@(ab|a*@(b))*(c)d)', { windows: true })); + }); + + test('"a.c.d" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('a.c.d', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"a.c.d" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('a.c.d', '*!(.a|.b|.c)', { windows: true })); + }); + + test('"a.c.d" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('a.c.d', '*.!(a|b|c)', { windows: true })); + }); + + test('"a.c.d" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('a.c.d', '*.(a|b|@(ab|a*@(b))*(c)d)', { windows: true })); + }); + + test('"a.ccc" should match "!(*.[a-b]*)"', () => { + expect_truthy(isMatch('a.ccc', '!(*.[a-b]*)', { windows: true })); + }); + + test('"a.ccc" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('a.ccc', '!(*[a-b].[a-b]*)', { windows: true })); + }); + + test('"a.ccc" should match "!*.(a|b)"', () => { + expect_truthy(isMatch('a.ccc', '!*.(a|b)', { windows: true })); + }); + + test('"a.ccc" should match "!*.(a|b)*"', () => { + expect_truthy(isMatch('a.ccc', '!*.(a|b)*', { windows: true })); + }); + + test('"a.ccc" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('a.ccc', '*.+(b|d)', { windows: true })); + }); + + test('"a.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('a.js', '!(*.js)', { windows: true })); + }); + + test('"a.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.js', '*!(.js)', { windows: true })); + }); + + test('"a.js" should not match "*.!(js)"', () => { + expect_truthy(!isMatch('a.js', '*.!(js)', { windows: true })); + }); + + test('"a.js" should not match "a.!(js)"', () => { + expect_truthy(!isMatch('a.js', 'a.!(js)', { windows: true })); + }); + + test('"a.js" should not match "a.!(js)*"', () => { + expect_truthy(!isMatch('a.js', 'a.!(js)*', { windows: true })); + }); + + test('"a.js.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('a.js.js', '!(*.js)', { windows: true })); + }); + + test('"a.js.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.js.js', '*!(.js)', { windows: true })); + }); + + test('"a.js.js" should match "*.!(js)"', () => { + expect_truthy(isMatch('a.js.js', '*.!(js)', { windows: true })); + }); + + test('"a.js.js" should match "*.*(js).js"', () => { + expect_truthy(isMatch('a.js.js', '*.*(js).js', { windows: true })); + }); + + test('"a.md" should match "!(*.js)"', () => { + expect_truthy(isMatch('a.md', '!(*.js)', { windows: true })); + }); + + test('"a.md" should match "*!(.js)"', () => { + expect_truthy(isMatch('a.md', '*!(.js)', { windows: true })); + }); + + test('"a.md" should match "*.!(js)"', () => { + expect_truthy(isMatch('a.md', '*.!(js)', { windows: true })); + }); + + test('"a.md" should match "a.!(js)"', () => { + expect_truthy(isMatch('a.md', 'a.!(js)', { windows: true })); + }); + + test('"a.md" should match "a.!(js)*"', () => { + expect_truthy(isMatch('a.md', 'a.!(js)*', { windows: true })); + }); + + test('"a.md.js" should not match "*.*(js).js"', () => { + expect_truthy(!isMatch('a.md.js', '*.*(js).js', { windows: true })); + }); + + test('"a.txt" should match "a.!(js)"', () => { + expect_truthy(isMatch('a.txt', 'a.!(js)', { windows: true })); + }); + + test('"a.txt" should match "a.!(js)*"', () => { + expect_truthy(isMatch('a.txt', 'a.!(js)*', { windows: true })); + }); + + test('"a/!(z)" should match "a/!(z)"', () => { + expect_truthy(isMatch('a/!(z)', 'a/!(z)', { windows: true })); + }); + + test('"a/b" should match "a/!(z)"', () => { + expect_truthy(isMatch('a/b', 'a/!(z)', { windows: true })); + }); + + test('"a/b/c.txt" should not match "*/b/!(*).txt"', () => { + expect_truthy(!isMatch('a/b/c.txt', '*/b/!(*).txt', { windows: true })); + }); + + test('"a/b/c.txt" should not match "*/b/!(c).txt"', () => { + expect_truthy(!isMatch('a/b/c.txt', '*/b/!(c).txt', { windows: true })); + }); + + test('"a/b/c.txt" should match "*/b/!(cc).txt"', () => { + expect_truthy(isMatch('a/b/c.txt', '*/b/!(cc).txt', { windows: true })); + }); + + test('"a/b/cc.txt" should not match "*/b/!(*).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(*).txt', { windows: true })); + }); + + test('"a/b/cc.txt" should not match "*/b/!(c).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(c).txt', { windows: true })); + }); + + test('"a/b/cc.txt" should not match "*/b/!(cc).txt"', () => { + expect_truthy(!isMatch('a/b/cc.txt', '*/b/!(cc).txt', { windows: true })); + }); + + test('"a/dir/foo.txt" should match "*/dir/**/!(bar).txt"', () => { + expect_truthy(isMatch('a/dir/foo.txt', '*/dir/**/!(bar).txt', { windows: true })); + }); + + test('"a/z" should not match "a/!(z)"', () => { + expect_truthy(!isMatch('a/z', 'a/!(z)', { windows: true })); + }); + + test('"a\\(b" should not match "a(*b"', () => { + expect_truthy(!isMatch('a\\(b', 'a(*b', { windows: true })); + }); + + test('"a\\(b" should not match "a(b"', () => { + expect_truthy(!isMatch('a\\(b', 'a(b', { windows: true })); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\\\z', 'a\\\\z', { windows: false })); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\\\z', 'a\\\\z', { windows: true })); + }); + + test('"a\\b" should match "a/b"', () => { + expect_truthy(isMatch('a\\b', 'a/b', { windows: true })); + }); + + test('"a\\z" should match "a\\z"', () => { + expect_truthy(isMatch('a\\z', 'a\\\\z', { windows: false })); + }); + + test('"a\\z" should not match "a\\z"', () => { + expect_truthy(isMatch('a\\z', 'a\\z', { windows: true })); + }); + + test('"aa" should not match "!(a!(b))"', () => { + expect_truthy(!isMatch('aa', '!(a!(b))', { windows: true })); + }); + + test('"aa" should match "!(a)"', () => { + expect_truthy(isMatch('aa', '!(a)', { windows: true })); + }); + + test('"aa" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aa', '!(a)*', { windows: true })); + }); + + test('"aa" should not match "?"', () => { + expect_truthy(!isMatch('aa', '?', { windows: true })); + }); + + test('"aa" should not match "@(a)b"', () => { + expect_truthy(!isMatch('aa', '@(a)b', { windows: true })); + }); + + test('"aa" should match "a!(b)*"', () => { + expect_truthy(isMatch('aa', 'a!(b)*', { windows: true })); + }); + + test('"aa" should not match "a??b"', () => { + expect_truthy(!isMatch('aa', 'a??b', { windows: true })); + }); + + test('"aa.aa" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('aa.aa', '(b|a).(a)', { windows: true })); + }); + + test('"aa.aa" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('aa.aa', '@(b|a).@(a)', { windows: true })); + }); + + test('"aaa" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aaa', '!(a)*', { windows: true })); + }); + + test('"aaa" should match "a!(b)*"', () => { + expect_truthy(isMatch('aaa', 'a!(b)*', { windows: true })); + }); + + test('"aaaaaaabababab" should match "*ab"', () => { + expect_truthy(isMatch('aaaaaaabababab', '*ab', { windows: true })); + }); + + test('"aaac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('aaac', '*(@(a))a@(c)', { windows: true })); + }); + + test('"aaaz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('aaaz', '[a*(]*z', { windows: true })); + }); + + test('"aab" should not match "!(a)*"', () => { + expect_truthy(!isMatch('aab', '!(a)*', { windows: true })); + }); + + test('"aab" should not match "?"', () => { + expect_truthy(!isMatch('aab', '?', { windows: true })); + }); + + test('"aab" should not match "??"', () => { + expect_truthy(!isMatch('aab', '??', { windows: true })); + }); + + test('"aab" should not match "@(c)b"', () => { + expect_truthy(!isMatch('aab', '@(c)b', { windows: true })); + }); + + test('"aab" should match "a!(b)*"', () => { + expect_truthy(isMatch('aab', 'a!(b)*', { windows: true })); + }); + + test('"aab" should not match "a??b"', () => { + expect_truthy(!isMatch('aab', 'a??b', { windows: true })); + }); + + test('"aac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('aac', '*(@(a))a@(c)', { windows: true })); + }); + + test('"aac" should not match "*(@(a))b@(c)"', () => { + expect_truthy(!isMatch('aac', '*(@(a))b@(c)', { windows: true })); + }); + + test('"aax" should not match "a!(a*|b)"', () => { + expect_truthy(!isMatch('aax', 'a!(a*|b)', { windows: true })); + }); + + test('"aax" should match "a!(x*|b)"', () => { + expect_truthy(isMatch('aax', 'a!(x*|b)', { windows: true })); + }); + + test('"aax" should match "a?(a*|b)"', () => { + expect_truthy(isMatch('aax', 'a?(a*|b)', { windows: true })); + }); + + test('"aaz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('aaz', '[a*(]*z', { windows: true })); + }); + + test('"ab" should match "!(*.*)"', () => { + expect_truthy(isMatch('ab', '!(*.*)', { windows: true })); + }); + + test('"ab" should match "!(a!(b))"', () => { + expect_truthy(isMatch('ab', '!(a!(b))', { windows: true })); + }); + + test('"ab" should not match "!(a)*"', () => { + expect_truthy(!isMatch('ab', '!(a)*', { windows: true })); + }); + + test('"ab" should match "(a+|b)*"', () => { + expect_truthy(isMatch('ab', '(a+|b)*', { windows: true })); + }); + + test('"ab" should match "(a+|b)+"', () => { + expect_truthy(isMatch('ab', '(a+|b)+', { windows: true })); + }); + + test('"ab" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('ab', '*?(a)bc', { windows: true })); + }); + + test('"ab" should not match "a!(*(b|B))"', () => { + expect_truthy(!isMatch('ab', 'a!(*(b|B))', { windows: true })); + }); + + test('"ab" should not match "a!(@(b|B))"', () => { + expect_truthy(!isMatch('ab', 'a!(@(b|B))', { windows: true })); + }); + + test('"aB" should not match "a!(@(b|B))"', () => { + expect_truthy(!isMatch('aB', 'a!(@(b|B))', { windows: true })); + }); + + test('"ab" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('ab', 'a!(b)*', { windows: true })); + }); + + test('"ab" should not match "a(*b"', () => { + expect_truthy(!isMatch('ab', 'a(*b', { windows: true })); + }); + + test('"ab" should not match "a(b"', () => { + expect_truthy(!isMatch('ab', 'a(b', { windows: true })); + }); + + test('"ab" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('ab', 'a(b*(foo|bar))d', { windows: true })); + }); + + test('"ab" should not match "a/b"', () => { + expect_truthy(!isMatch('ab', 'a/b', { windows: true })); + }); + + test('"ab" should not match "a\\(b"', () => { + expect_truthy(!isMatch('ab', 'a\\(b', { windows: true })); + }); + + test('"ab" should match "ab*(e|f)"', () => { + expect_truthy(isMatch('ab', 'ab*(e|f)', { windows: true })); + }); + + test('"ab" should match "ab**"', () => { + expect_truthy(isMatch('ab', 'ab**', { windows: true })); + }); + + test('"ab" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('ab', 'ab**(e|f)', { windows: true })); + }); + + test('"ab" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('ab', 'ab**(e|f)g', { windows: true })); + }); + + test('"ab" should not match "ab***ef"', () => { + expect_truthy(!isMatch('ab', 'ab***ef', { windows: true })); + }); + + test('"ab" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab*+(e|f)', { windows: true })); + }); + + test('"ab" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab*d+(e|f)', { windows: true })); + }); + + test('"ab" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('ab', 'ab?*(e|f)', { windows: true })); + }); + + test('"ab/cXd/efXg/hi" should match "**/*X*/**/*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '**/*X*/**/*i', { windows: true })); + }); + + test('"ab/cXd/efXg/hi" should match "*/*X*/*/*i"', () => { + expect_truthy(isMatch('ab/cXd/efXg/hi', '*/*X*/*/*i', { windows: true })); + }); + + test('"ab/cXd/efXg/hi" should not match "*X*i"', () => { + expect_truthy(!isMatch('ab/cXd/efXg/hi', '*X*i', { windows: true })); + }); + + test('"ab/cXd/efXg/hi" should not match "*Xg*i"', () => { + expect_truthy(!isMatch('ab/cXd/efXg/hi', '*Xg*i', { windows: true })); + }); + + test('"ab]" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('ab]', 'a!(@(b|B))', { windows: true })); + }); + + test('"abab" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abab', '(a+|b)*', { windows: true })); + }); + + test('"abab" should match "(a+|b)+"', () => { + expect_truthy(isMatch('abab', '(a+|b)+', { windows: true })); + }); + + test('"abab" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abab', '*?(a)bc', { windows: true })); + }); + + test('"abab" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abab', 'a(b*(foo|bar))d', { windows: true })); + }); + + test('"abab" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*(e|f)', { windows: true })); + }); + + test('"abab" should match "ab**"', () => { + expect_truthy(isMatch('abab', 'ab**', { windows: true })); + }); + + test('"abab" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abab', 'ab**(e|f)', { windows: true })); + }); + + test('"abab" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abab', 'ab**(e|f)g', { windows: true })); + }); + + test('"abab" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abab', 'ab***ef', { windows: true })); + }); + + test('"abab" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*+(e|f)', { windows: true })); + }); + + test('"abab" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab*d+(e|f)', { windows: true })); + }); + + test('"abab" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abab', 'ab?*(e|f)', { windows: true })); + }); + + test('"abb" should match "!(*.*)"', () => { + expect_truthy(isMatch('abb', '!(*.*)', { windows: true })); + }); + + test('"abb" should not match "!(a)*"', () => { + expect_truthy(!isMatch('abb', '!(a)*', { windows: true })); + }); + + test('"abb" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('abb', 'a!(b)*', { windows: true })); + }); + + test('"abbcd" should match "@(ab|a*(b))*(c)d"', () => { + expect_truthy(isMatch('abbcd', '@(ab|a*(b))*(c)d', { windows: true })); + }); + + test('"abc" should not match "\\a\\b\\c"', () => { + expect_truthy(!isMatch('abc', '\\a\\b\\c', { windows: true })); + }); + + test('"aBc" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('aBc', 'a!(@(b|B))', { windows: true })); + }); + + test('"abcd" should match "?@(a|b)*@(c)d"', () => { + expect_truthy(isMatch('abcd', '?@(a|b)*@(c)d', { windows: true })); + }); + + test('"abcd" should match "@(ab|a*@(b))*(c)d"', () => { + expect_truthy(isMatch('abcd', '@(ab|a*@(b))*(c)d', { windows: true })); + }); + + test('"abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt" should match "**/*a*b*g*n*t"', () => { + expect_truthy(isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt', '**/*a*b*g*n*t', { windows: true })); + }); + + test('"abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz" should not match "**/*a*b*g*n*t"', () => { + expect_truthy(!isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz', '**/*a*b*g*n*t', { windows: true })); + }); + + test('"abcdef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcdef', '(a+|b)*', { windows: true })); + }); + + test('"abcdef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcdef', '(a+|b)+', { windows: true })); + }); + + test('"abcdef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcdef', '*?(a)bc', { windows: true })); + }); + + test('"abcdef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcdef', 'a(b*(foo|bar))d', { windows: true })); + }); + + test('"abcdef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcdef', 'ab*(e|f)', { windows: true })); + }); + + test('"abcdef" should match "ab**"', () => { + expect_truthy(isMatch('abcdef', 'ab**', { windows: true })); + }); + + test('"abcdef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab**(e|f)', { windows: true })); + }); + + test('"abcdef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abcdef', 'ab**(e|f)g', { windows: true })); + }); + + test('"abcdef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcdef', 'ab***ef', { windows: true })); + }); + + test('"abcdef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab*+(e|f)', { windows: true })); + }); + + test('"abcdef" should match "ab*d+(e|f)"', () => { + expect_truthy(isMatch('abcdef', 'ab*d+(e|f)', { windows: true })); + }); + + test('"abcdef" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abcdef', 'ab?*(e|f)', { windows: true })); + }); + + test('"abcfef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcfef', '(a+|b)*', { windows: true })); + }); + + test('"abcfef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcfef', '(a+|b)+', { windows: true })); + }); + + test('"abcfef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcfef', '*?(a)bc', { windows: true })); + }); + + test('"abcfef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcfef', 'a(b*(foo|bar))d', { windows: true })); + }); + + test('"abcfef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcfef', 'ab*(e|f)', { windows: true })); + }); + + test('"abcfef" should match "ab**"', () => { + expect_truthy(isMatch('abcfef', 'ab**', { windows: true })); + }); + + test('"abcfef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab**(e|f)', { windows: true })); + }); + + test('"abcfef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abcfef', 'ab**(e|f)g', { windows: true })); + }); + + test('"abcfef" should match "ab***ef"', () => { + expect_truthy(isMatch('abcfef', 'ab***ef', { windows: true })); + }); + + test('"abcfef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab*+(e|f)', { windows: true })); + }); + + test('"abcfef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abcfef', 'ab*d+(e|f)', { windows: true })); + }); + + test('"abcfef" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abcfef', 'ab?*(e|f)', { windows: true })); + }); + + test('"abcfefg" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abcfefg', '(a+|b)*', { windows: true })); + }); + + test('"abcfefg" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abcfefg', '(a+|b)+', { windows: true })); + }); + + test('"abcfefg" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abcfefg', '*?(a)bc', { windows: true })); + }); + + test('"abcfefg" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abcfefg', 'a(b*(foo|bar))d', { windows: true })); + }); + + test('"abcfefg" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*(e|f)', { windows: true })); + }); + + test('"abcfefg" should match "ab**"', () => { + expect_truthy(isMatch('abcfefg', 'ab**', { windows: true })); + }); + + test('"abcfefg" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abcfefg', 'ab**(e|f)', { windows: true })); + }); + + test('"abcfefg" should match "ab**(e|f)g"', () => { + expect_truthy(isMatch('abcfefg', 'ab**(e|f)g', { windows: true })); + }); + + test('"abcfefg" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abcfefg', 'ab***ef', { windows: true })); + }); + + test('"abcfefg" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*+(e|f)', { windows: true })); + }); + + test('"abcfefg" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab*d+(e|f)', { windows: true })); + }); + + test('"abcfefg" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('abcfefg', 'ab?*(e|f)', { windows: true })); + }); + + test('"abcx" should match "!([[*])*"', () => { + expect_truthy(isMatch('abcx', '!([[*])*', { windows: true })); + }); + + test('"abcx" should match "+(a|b\\[)*"', () => { + expect_truthy(isMatch('abcx', '+(a|b\\[)*', { windows: true })); + }); + + test('"abcx" should not match "[a*(]*z"', () => { + expect_truthy(!isMatch('abcx', '[a*(]*z', { windows: true })); + }); + + test('"abcXdefXghi" should match "*X*i"', () => { + expect_truthy(isMatch('abcXdefXghi', '*X*i', { windows: true })); + }); + + test('"abcz" should match "!([[*])*"', () => { + expect_truthy(isMatch('abcz', '!([[*])*', { windows: true })); + }); + + test('"abcz" should match "+(a|b\\[)*"', () => { + expect_truthy(isMatch('abcz', '+(a|b\\[)*', { windows: true })); + }); + + test('"abcz" should match "[a*(]*z"', () => { + expect_truthy(isMatch('abcz', '[a*(]*z', { windows: true })); + }); + + test('"abd" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abd', '(a+|b)*', { windows: true })); + }); + + test('"abd" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abd', '(a+|b)+', { windows: true })); + }); + + test('"abd" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abd', '*?(a)bc', { windows: true })); + }); + + test('"abd" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('abd', 'a!(*(b|B))', { windows: true })); + }); + + test('"abd" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('abd', 'a!(@(b|B))', { windows: true })); + }); + + test('"abd" should not match "a!(@(b|B))d"', () => { + expect_truthy(!isMatch('abd', 'a!(@(b|B))d', { windows: true })); + }); + + test('"abd" should match "a(b*(foo|bar))d"', () => { + expect_truthy(isMatch('abd', 'a(b*(foo|bar))d', { windows: true })); + }); + + test('"abd" should match "a+(b|c)d"', () => { + expect_truthy(isMatch('abd', 'a+(b|c)d', { windows: true })); + }); + + test('"abd" should match "a[b*(foo|bar)]d"', () => { + expect_truthy(isMatch('abd', 'a[b*(foo|bar)]d', { windows: true })); + }); + + test('"abd" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*(e|f)', { windows: true })); + }); + + test('"abd" should match "ab**"', () => { + expect_truthy(isMatch('abd', 'ab**', { windows: true })); + }); + + test('"abd" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abd', 'ab**(e|f)', { windows: true })); + }); + + test('"abd" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abd', 'ab**(e|f)g', { windows: true })); + }); + + test('"abd" should not match "ab***ef"', () => { + expect_truthy(!isMatch('abd', 'ab***ef', { windows: true })); + }); + + test('"abd" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*+(e|f)', { windows: true })); + }); + + test('"abd" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abd', 'ab*d+(e|f)', { windows: true })); + }); + + test('"abd" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abd', 'ab?*(e|f)', { windows: true })); + }); + + test('"abef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('abef', '(a+|b)*', { windows: true })); + }); + + test('"abef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('abef', '(a+|b)+', { windows: true })); + }); + + test('"abef" should not match "*(a+|b)"', () => { + expect_truthy(!isMatch('abef', '*(a+|b)', { windows: true })); + }); + + test('"abef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('abef', '*?(a)bc', { windows: true })); + }); + + test('"abef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('abef', 'a(b*(foo|bar))d', { windows: true })); + }); + + test('"abef" should match "ab*(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab*(e|f)', { windows: true })); + }); + + test('"abef" should match "ab**"', () => { + expect_truthy(isMatch('abef', 'ab**', { windows: true })); + }); + + test('"abef" should match "ab**(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab**(e|f)', { windows: true })); + }); + + test('"abef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('abef', 'ab**(e|f)g', { windows: true })); + }); + + test('"abef" should match "ab***ef"', () => { + expect_truthy(isMatch('abef', 'ab***ef', { windows: true })); + }); + + test('"abef" should match "ab*+(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab*+(e|f)', { windows: true })); + }); + + test('"abef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('abef', 'ab*d+(e|f)', { windows: true })); + }); + + test('"abef" should match "ab?*(e|f)"', () => { + expect_truthy(isMatch('abef', 'ab?*(e|f)', { windows: true })); + }); + + test('"abz" should not match "a!(*)"', () => { + expect_truthy(!isMatch('abz', 'a!(*)', { windows: true })); + }); + + test('"abz" should match "a!(z)"', () => { + expect_truthy(isMatch('abz', 'a!(z)', { windows: true })); + }); + + test('"abz" should match "a*!(z)"', () => { + expect_truthy(isMatch('abz', 'a*!(z)', { windows: true })); + }); + + test('"abz" should not match "a*(z)"', () => { + expect_truthy(!isMatch('abz', 'a*(z)', { windows: true })); + }); + + test('"abz" should match "a**(z)"', () => { + expect_truthy(isMatch('abz', 'a**(z)', { windows: true })); + }); + + test('"abz" should match "a*@(z)"', () => { + expect_truthy(isMatch('abz', 'a*@(z)', { windows: true })); + }); + + test('"abz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('abz', 'a+(z)', { windows: true })); + }); + + test('"abz" should not match "a?(z)"', () => { + expect_truthy(!isMatch('abz', 'a?(z)', { windows: true })); + }); + + test('"abz" should not match "a@(z)"', () => { + expect_truthy(!isMatch('abz', 'a@(z)', { windows: true })); + }); + + test('"ac" should not match "!(a)*"', () => { + expect_truthy(!isMatch('ac', '!(a)*', { windows: true })); + }); + + test('"ac" should match "*(@(a))a@(c)"', () => { + expect_truthy(isMatch('ac', '*(@(a))a@(c)', { windows: true })); + }); + + test('"ac" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('ac', 'a!(*(b|B))', { windows: true })); + }); + + test('"ac" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('ac', 'a!(@(b|B))', { windows: true })); + }); + + test('"ac" should match "a!(b)*"', () => { + expect_truthy(isMatch('ac', 'a!(b)*', { windows: true })); + }); + + test('"accdef" should match "(a+|b)*"', () => { + expect_truthy(isMatch('accdef', '(a+|b)*', { windows: true })); + }); + + test('"accdef" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('accdef', '(a+|b)+', { windows: true })); + }); + + test('"accdef" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('accdef', '*?(a)bc', { windows: true })); + }); + + test('"accdef" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('accdef', 'a(b*(foo|bar))d', { windows: true })); + }); + + test('"accdef" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*(e|f)', { windows: true })); + }); + + test('"accdef" should not match "ab**"', () => { + expect_truthy(!isMatch('accdef', 'ab**', { windows: true })); + }); + + test('"accdef" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab**(e|f)', { windows: true })); + }); + + test('"accdef" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('accdef', 'ab**(e|f)g', { windows: true })); + }); + + test('"accdef" should not match "ab***ef"', () => { + expect_truthy(!isMatch('accdef', 'ab***ef', { windows: true })); + }); + + test('"accdef" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*+(e|f)', { windows: true })); + }); + + test('"accdef" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab*d+(e|f)', { windows: true })); + }); + + test('"accdef" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('accdef', 'ab?*(e|f)', { windows: true })); + }); + + test('"acd" should match "(a+|b)*"', () => { + expect_truthy(isMatch('acd', '(a+|b)*', { windows: true })); + }); + + test('"acd" should not match "(a+|b)+"', () => { + expect_truthy(!isMatch('acd', '(a+|b)+', { windows: true })); + }); + + test('"acd" should not match "*?(a)bc"', () => { + expect_truthy(!isMatch('acd', '*?(a)bc', { windows: true })); + }); + + test('"acd" should match "@(ab|a*(b))*(c)d"', () => { + expect_truthy(isMatch('acd', '@(ab|a*(b))*(c)d', { windows: true })); + }); + + test('"acd" should match "a!(*(b|B))"', () => { + expect_truthy(isMatch('acd', 'a!(*(b|B))', { windows: true })); + }); + + test('"acd" should match "a!(@(b|B))"', () => { + expect_truthy(isMatch('acd', 'a!(@(b|B))', { windows: true })); + }); + + test('"acd" should match "a!(@(b|B))d"', () => { + expect_truthy(isMatch('acd', 'a!(@(b|B))d', { windows: true })); + }); + + test('"acd" should not match "a(b*(foo|bar))d"', () => { + expect_truthy(!isMatch('acd', 'a(b*(foo|bar))d', { windows: true })); + }); + + test('"acd" should match "a+(b|c)d"', () => { + expect_truthy(isMatch('acd', 'a+(b|c)d', { windows: true })); + }); + + test('"acd" should not match "a[b*(foo|bar)]d"', () => { + expect_truthy(!isMatch('acd', 'a[b*(foo|bar)]d', { windows: true })); + }); + + test('"acd" should not match "ab*(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*(e|f)', { windows: true })); + }); + + test('"acd" should not match "ab**"', () => { + expect_truthy(!isMatch('acd', 'ab**', { windows: true })); + }); + + test('"acd" should not match "ab**(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab**(e|f)', { windows: true })); + }); + + test('"acd" should not match "ab**(e|f)g"', () => { + expect_truthy(!isMatch('acd', 'ab**(e|f)g', { windows: true })); + }); + + test('"acd" should not match "ab***ef"', () => { + expect_truthy(!isMatch('acd', 'ab***ef', { windows: true })); + }); + + test('"acd" should not match "ab*+(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*+(e|f)', { windows: true })); + }); + + test('"acd" should not match "ab*d+(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab*d+(e|f)', { windows: true })); + }); + + test('"acd" should not match "ab?*(e|f)"', () => { + expect_truthy(!isMatch('acd', 'ab?*(e|f)', { windows: true })); + }); + + test('"axz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('axz', 'a+(z)', { windows: true })); + }); + + test('"az" should not match "a!(*)"', () => { + expect_truthy(!isMatch('az', 'a!(*)', { windows: true })); + }); + + test('"az" should not match "a!(z)"', () => { + expect_truthy(!isMatch('az', 'a!(z)', { windows: true })); + }); + + test('"az" should match "a*!(z)"', () => { + expect_truthy(isMatch('az', 'a*!(z)', { windows: true })); + }); + + test('"az" should match "a*(z)"', () => { + expect_truthy(isMatch('az', 'a*(z)', { windows: true })); + }); + + test('"az" should match "a**(z)"', () => { + expect_truthy(isMatch('az', 'a**(z)', { windows: true })); + }); + + test('"az" should match "a*@(z)"', () => { + expect_truthy(isMatch('az', 'a*@(z)', { windows: true })); + }); + + test('"az" should match "a+(z)"', () => { + expect_truthy(isMatch('az', 'a+(z)', { windows: true })); + }); + + test('"az" should match "a?(z)"', () => { + expect_truthy(isMatch('az', 'a?(z)', { windows: true })); + }); + + test('"az" should match "a@(z)"', () => { + expect_truthy(isMatch('az', 'a@(z)', { windows: true })); + }); + + test('"az" should not match "a\\z"', () => { + expect_truthy(!isMatch('az', 'a\\\\z', { windows: false })); + }); + + test('"az" should not match "a\\z"', () => { + expect_truthy(!isMatch('az', 'a\\\\z', { windows: true })); + }); + + test('"b" should match "!(a)*"', () => { + expect_truthy(isMatch('b', '!(a)*', { windows: true })); + }); + + test('"b" should match "(a+|b)*"', () => { + expect_truthy(isMatch('b', '(a+|b)*', { windows: true })); + }); + + test('"b" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('b', 'a!(b)*', { windows: true })); + }); + + test('"b.a" should match "(b|a).(a)"', () => { + expect_truthy(isMatch('b.a', '(b|a).(a)', { windows: true })); + }); + + test('"b.a" should match "@(b|a).@(a)"', () => { + expect_truthy(isMatch('b.a', '@(b|a).@(a)', { windows: true })); + }); + + test('"b/a" should not match "!(b/a)"', () => { + expect_truthy(!isMatch('b/a', '!(b/a)', { windows: true })); + }); + + test('"b/b" should match "!(b/a)"', () => { + expect_truthy(isMatch('b/b', '!(b/a)', { windows: true })); + }); + + test('"b/c" should match "!(b/a)"', () => { + expect_truthy(isMatch('b/c', '!(b/a)', { windows: true })); + }); + + test('"b/c" should not match "b/!(c)"', () => { + expect_truthy(!isMatch('b/c', 'b/!(c)', { windows: true })); + }); + + test('"b/c" should match "b/!(cc)"', () => { + expect_truthy(isMatch('b/c', 'b/!(cc)', { windows: true })); + }); + + test('"b/c.txt" should not match "b/!(c).txt"', () => { + expect_truthy(!isMatch('b/c.txt', 'b/!(c).txt', { windows: true })); + }); + + test('"b/c.txt" should match "b/!(cc).txt"', () => { + expect_truthy(isMatch('b/c.txt', 'b/!(cc).txt', { windows: true })); + }); + + test('"b/cc" should match "b/!(c)"', () => { + expect_truthy(isMatch('b/cc', 'b/!(c)', { windows: true })); + }); + + test('"b/cc" should not match "b/!(cc)"', () => { + expect_truthy(!isMatch('b/cc', 'b/!(cc)', { windows: true })); + }); + + test('"b/cc.txt" should not match "b/!(c).txt"', () => { + expect_truthy(!isMatch('b/cc.txt', 'b/!(c).txt', { windows: true })); + }); + + test('"b/cc.txt" should not match "b/!(cc).txt"', () => { + expect_truthy(!isMatch('b/cc.txt', 'b/!(cc).txt', { windows: true })); + }); + + test('"b/ccc" should match "b/!(c)"', () => { + expect_truthy(isMatch('b/ccc', 'b/!(c)', { windows: true })); + }); + + test('"ba" should match "!(a!(b))"', () => { + expect_truthy(isMatch('ba', '!(a!(b))', { windows: true })); + }); + + test('"ba" should match "b?(a|b)"', () => { + expect_truthy(isMatch('ba', 'b?(a|b)', { windows: true })); + }); + + test('"baaac" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('baaac', '*(@(a))a@(c)', { windows: true })); + }); + + test('"bar" should match "!(foo)"', () => { + expect_truthy(isMatch('bar', '!(foo)', { windows: true })); + }); + + test('"bar" should match "!(foo)*"', () => { + expect_truthy(isMatch('bar', '!(foo)*', { windows: true })); + }); + + test('"bar" should match "!(foo)b*"', () => { + expect_truthy(isMatch('bar', '!(foo)b*', { windows: true })); + }); + + test('"bar" should match "*(!(foo))"', () => { + expect_truthy(isMatch('bar', '*(!(foo))', { windows: true })); + }); + + test('"baz" should match "!(foo)*"', () => { + expect_truthy(isMatch('baz', '!(foo)*', { windows: true })); + }); + + test('"baz" should match "!(foo)b*"', () => { + expect_truthy(isMatch('baz', '!(foo)b*', { windows: true })); + }); + + test('"baz" should match "*(!(foo))"', () => { + expect_truthy(isMatch('baz', '*(!(foo))', { windows: true })); + }); + + test('"bb" should match "!(a!(b))"', () => { + expect_truthy(isMatch('bb', '!(a!(b))', { windows: true })); + }); + + test('"bb" should match "!(a)*"', () => { + expect_truthy(isMatch('bb', '!(a)*', { windows: true })); + }); + + test('"bb" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('bb', 'a!(b)*', { windows: true })); + }); + + test('"bb" should not match "a?(a|b)"', () => { + expect_truthy(!isMatch('bb', 'a?(a|b)', { windows: true })); + }); + + test('"bbc" should match "!([[*])*"', () => { + expect_truthy(isMatch('bbc', '!([[*])*', { windows: true })); + }); + + test('"bbc" should not match "+(a|b\\[)*"', () => { + expect_truthy(!isMatch('bbc', '+(a|b\\[)*', { windows: true })); + }); + + test('"bbc" should not match "[a*(]*z"', () => { + expect_truthy(!isMatch('bbc', '[a*(]*z', { windows: true })); + }); + + test('"bz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('bz', 'a+(z)', { windows: true })); + }); + + test('"c" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('c', '*(@(a))a@(c)', { windows: true })); + }); + + test('"c.a" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('c.a', '!(*.[a-b]*)', { windows: true })); + }); + + test('"c.a" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('c.a', '!(*[a-b].[a-b]*)', { windows: true })); + }); + + test('"c.a" should not match "!*.(a|b)"', () => { + expect_truthy(!isMatch('c.a', '!*.(a|b)', { windows: true })); + }); + + test('"c.a" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('c.a', '!*.(a|b)*', { windows: true })); + }); + + test('"c.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('c.a', '(b|a).(a)', { windows: true })); + }); + + test('"c.a" should not match "*.!(a)"', () => { + expect_truthy(!isMatch('c.a', '*.!(a)', { windows: true })); + }); + + test('"c.a" should not match "*.+(b|d)"', () => { + expect_truthy(!isMatch('c.a', '*.+(b|d)', { windows: true })); + }); + + test('"c.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('c.a', '@(b|a).@(a)', { windows: true })); + }); + + test('"c.c" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('c.c', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"c.c" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('c.c', '*!(.a|.b|.c)', { windows: true })); + }); + + test('"c.c" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('c.c', '*.!(a|b|c)', { windows: true })); + }); + + test('"c.c" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('c.c', '*.(a|b|@(ab|a*@(b))*(c)d)', { windows: true })); + }); + + test('"c.ccc" should match "!(*.[a-b]*)"', () => { + expect_truthy(isMatch('c.ccc', '!(*.[a-b]*)', { windows: true })); + }); + + test('"c.ccc" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('c.ccc', '!(*[a-b].[a-b]*)', { windows: true })); + }); + + test('"c.js" should not match "!(*.js)"', () => { + expect_truthy(!isMatch('c.js', '!(*.js)', { windows: true })); + }); + + test('"c.js" should match "*!(.js)"', () => { + expect_truthy(isMatch('c.js', '*!(.js)', { windows: true })); + }); + + test('"c.js" should not match "*.!(js)"', () => { + expect_truthy(!isMatch('c.js', '*.!(js)', { windows: true })); + }); + + test('"c/a/v" should match "c/!(z)/v"', () => { + expect_truthy(isMatch('c/a/v', 'c/!(z)/v', { windows: true })); + }); + + test('"c/a/v" should not match "c/*(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/*(z)/v', { windows: true })); + }); + + test('"c/a/v" should not match "c/+(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/+(z)/v', { windows: true })); + }); + + test('"c/a/v" should not match "c/@(z)/v"', () => { + expect_truthy(!isMatch('c/a/v', 'c/@(z)/v', { windows: true })); + }); + + test('"c/z/v" should not match "*(z)"', () => { + expect_truthy(!isMatch('c/z/v', '*(z)', { windows: true })); + }); + + test('"c/z/v" should not match "+(z)"', () => { + expect_truthy(!isMatch('c/z/v', '+(z)', { windows: true })); + }); + + test('"c/z/v" should not match "?(z)"', () => { + expect_truthy(!isMatch('c/z/v', '?(z)', { windows: true })); + }); + + test('"c/z/v" should not match "c/!(z)/v"', () => { + expect_truthy(!isMatch('c/z/v', 'c/!(z)/v', { windows: true })); + }); + + test('"c/z/v" should match "c/*(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/*(z)/v', { windows: true })); + }); + + test('"c/z/v" should match "c/+(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/+(z)/v', { windows: true })); + }); + + test('"c/z/v" should match "c/@(z)/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/@(z)/v', { windows: true })); + }); + + test('"c/z/v" should match "c/z/v"', () => { + expect_truthy(isMatch('c/z/v', 'c/z/v', { windows: true })); + }); + + test('"cc.a" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('cc.a', '(b|a).(a)', { windows: true })); + }); + + test('"cc.a" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('cc.a', '@(b|a).@(a)', { windows: true })); + }); + + test('"ccc" should match "!(a)*"', () => { + expect_truthy(isMatch('ccc', '!(a)*', { windows: true })); + }); + + test('"ccc" should not match "a!(b)*"', () => { + expect_truthy(!isMatch('ccc', 'a!(b)*', { windows: true })); + }); + + test('"cow" should match "!(*.*)"', () => { + expect_truthy(isMatch('cow', '!(*.*)', { windows: true })); + }); + + test('"cow" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('cow', '!(*.*).', { windows: true })); + }); + + test('"cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('cow', '.!(*.*)', { windows: true })); + }); + + test('"cz" should not match "a!(*)"', () => { + expect_truthy(!isMatch('cz', 'a!(*)', { windows: true })); + }); + + test('"cz" should not match "a!(z)"', () => { + expect_truthy(!isMatch('cz', 'a!(z)', { windows: true })); + }); + + test('"cz" should not match "a*!(z)"', () => { + expect_truthy(!isMatch('cz', 'a*!(z)', { windows: true })); + }); + + test('"cz" should not match "a*(z)"', () => { + expect_truthy(!isMatch('cz', 'a*(z)', { windows: true })); + }); + + test('"cz" should not match "a**(z)"', () => { + expect_truthy(!isMatch('cz', 'a**(z)', { windows: true })); + }); + + test('"cz" should not match "a*@(z)"', () => { + expect_truthy(!isMatch('cz', 'a*@(z)', { windows: true })); + }); + + test('"cz" should not match "a+(z)"', () => { + expect_truthy(!isMatch('cz', 'a+(z)', { windows: true })); + }); + + test('"cz" should not match "a?(z)"', () => { + expect_truthy(!isMatch('cz', 'a?(z)', { windows: true })); + }); + + test('"cz" should not match "a@(z)"', () => { + expect_truthy(!isMatch('cz', 'a@(z)', { windows: true })); + }); + + test('"d.a.d" should not match "!(*.[a-b]*)"', () => { + expect_truthy(!isMatch('d.a.d', '!(*.[a-b]*)', { windows: true })); + }); + + test('"d.a.d" should match "!(*[a-b].[a-b]*)"', () => { + expect_truthy(isMatch('d.a.d', '!(*[a-b].[a-b]*)', { windows: true })); + }); + + test('"d.a.d" should not match "!*.(a|b)*"', () => { + expect_truthy(!isMatch('d.a.d', '!*.(a|b)*', { windows: true })); + }); + + test('"d.a.d" should match "!*.*(a|b)"', () => { + expect_truthy(isMatch('d.a.d', '!*.*(a|b)', { windows: true })); + }); + + test('"d.a.d" should not match "!*.{a,b}*"', () => { + expect_truthy(!isMatch('d.a.d', '!*.{a,b}*', { windows: true })); + }); + + test('"d.a.d" should match "*.!(a)"', () => { + expect_truthy(isMatch('d.a.d', '*.!(a)', { windows: true })); + }); + + test('"d.a.d" should match "*.+(b|d)"', () => { + expect_truthy(isMatch('d.a.d', '*.+(b|d)', { windows: true })); + }); + + test('"d.d" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('d.d', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"d.d" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('d.d', '*!(.a|.b|.c)', { windows: true })); + }); + + test('"d.d" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('d.d', '*.!(a|b|c)', { windows: true })); + }); + + test('"d.d" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('d.d', '*.(a|b|@(ab|a*@(b))*(c)d)', { windows: true })); + }); + + test('"d.js.d" should match "!(*.js)"', () => { + expect_truthy(isMatch('d.js.d', '!(*.js)', { windows: true })); + }); + + test('"d.js.d" should match "*!(.js)"', () => { + expect_truthy(isMatch('d.js.d', '*!(.js)', { windows: true })); + }); + + test('"d.js.d" should match "*.!(js)"', () => { + expect_truthy(isMatch('d.js.d', '*.!(js)', { windows: true })); + }); + + test('"dd.aa.d" should not match "(b|a).(a)"', () => { + expect_truthy(!isMatch('dd.aa.d', '(b|a).(a)', { windows: true })); + }); + + test('"dd.aa.d" should not match "@(b|a).@(a)"', () => { + expect_truthy(!isMatch('dd.aa.d', '@(b|a).@(a)', { windows: true })); + }); + + test('"def" should not match "()ef"', () => { + expect_truthy(!isMatch('def', '()ef', { windows: true })); + }); + + test('"e.e" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('e.e', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"e.e" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('e.e', '*!(.a|.b|.c)', { windows: true })); + }); + + test('"e.e" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('e.e', '*.!(a|b|c)', { windows: true })); + }); + + test('"e.e" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('e.e', '*.(a|b|@(ab|a*@(b))*(c)d)', { windows: true })); + }); + + test('"ef" should match "()ef"', () => { + expect_truthy(isMatch('ef', '()ef', { windows: true })); + }); + + test('"effgz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('effgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + }); + + test('"efgz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('efgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + }); + + test('"egz" should match "@(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('egz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + }); + + test('"egz" should not match "@(b+(c)d|e+(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(!isMatch('egz', '@(b+(c)d|e+(f)g?|?(h)i@(j|k))', { windows: true })); + }); + + test('"egzefffgzbcdij" should match "*(b+(c)d|e*(f)g?|?(h)i@(j|k))"', () => { + expect_truthy(isMatch('egzefffgzbcdij', '*(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + }); + + test('"f" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('f', '!(f!(o))', { windows: true })); + }); + + test('"f" should match "!(f(o))"', () => { + expect_truthy(isMatch('f', '!(f(o))', { windows: true })); + }); + + test('"f" should not match "!(f)"', () => { + expect_truthy(!isMatch('f', '!(f)', { windows: true })); + }); + + test('"f" should not match "*(!(f))"', () => { + expect_truthy(!isMatch('f', '*(!(f))', { windows: true })); + }); + + test('"f" should not match "+(!(f))"', () => { + expect_truthy(!isMatch('f', '+(!(f))', { windows: true })); + }); + + test('"f.a" should not match "!(*.a|*.b|*.c)"', () => { + expect_truthy(!isMatch('f.a', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"f.a" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('f.a', '*!(.a|.b|.c)', { windows: true })); + }); + + test('"f.a" should not match "*.!(a|b|c)"', () => { + expect_truthy(!isMatch('f.a', '*.!(a|b|c)', { windows: true })); + }); + + test('"f.f" should match "!(*.a|*.b|*.c)"', () => { + expect_truthy(isMatch('f.f', '!(*.a|*.b|*.c)', { windows: true })); + }); + + test('"f.f" should match "*!(.a|.b|.c)"', () => { + expect_truthy(isMatch('f.f', '*!(.a|.b|.c)', { windows: true })); + }); + + test('"f.f" should match "*.!(a|b|c)"', () => { + expect_truthy(isMatch('f.f', '*.!(a|b|c)', { windows: true })); + }); + + test('"f.f" should not match "*.(a|b|@(ab|a*@(b))*(c)d)"', () => { + expect_truthy(!isMatch('f.f', '*.(a|b|@(ab|a*@(b))*(c)d)', { windows: true })); + }); + + test('"fa" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('fa', '!(f!(o))', { windows: true })); + }); + + test('"fa" should match "!(f(o))"', () => { + expect_truthy(isMatch('fa', '!(f(o))', { windows: true })); + }); + + test('"fb" should not match "!(f!(o))"', () => { + expect_truthy(!isMatch('fb', '!(f!(o))', { windows: true })); + }); + + test('"fb" should match "!(f(o))"', () => { + expect_truthy(isMatch('fb', '!(f(o))', { windows: true })); + }); + + test('"fff" should match "!(f)"', () => { + expect_truthy(isMatch('fff', '!(f)', { windows: true })); + }); + + test('"fff" should match "*(!(f))"', () => { + expect_truthy(isMatch('fff', '*(!(f))', { windows: true })); + }); + + test('"fff" should match "+(!(f))"', () => { + expect_truthy(isMatch('fff', '+(!(f))', { windows: true })); + }); + + test('"fffooofoooooffoofffooofff" should match "*(*(f)*(o))"', () => { + expect_truthy(isMatch('fffooofoooooffoofffooofff', '*(*(f)*(o))', { windows: true })); + }); + + test('"ffo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('ffo', '*(f*(o))', { windows: true })); + }); + + test('"file.C" should not match "*.c?(c)"', () => { + expect_truthy(!isMatch('file.C', '*.c?(c)', { windows: true })); + }); + + test('"file.c" should match "*.c?(c)"', () => { + expect_truthy(isMatch('file.c', '*.c?(c)', { windows: true })); + }); + + test('"file.cc" should match "*.c?(c)"', () => { + expect_truthy(isMatch('file.cc', '*.c?(c)', { windows: true })); + }); + + test('"file.ccc" should not match "*.c?(c)"', () => { + expect_truthy(!isMatch('file.ccc', '*.c?(c)', { windows: true })); + }); + + test('"fo" should match "!(f!(o))"', () => { + expect_truthy(isMatch('fo', '!(f!(o))', { windows: true })); + }); + + test('"fo" should not match "!(f(o))"', () => { + expect_truthy(!isMatch('fo', '!(f(o))', { windows: true })); + }); + + test('"fofo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('fofo', '*(f*(o))', { windows: true })); + }); + + test('"fofoofoofofoo" should match "*(fo|foo)"', () => { + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)', { windows: true })); + }); + + test('"fofoofoofofoo" should match "*(fo|foo)"', () => { + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)', { windows: true })); + }); + + test('"foo" should match "!(!(foo))"', () => { + expect_truthy(isMatch('foo', '!(!(foo))', { windows: true })); + }); + + test('"foo" should match "!(f)"', () => { + expect_truthy(isMatch('foo', '!(f)', { windows: true })); + }); + + test('"foo" should not match "!(foo)"', () => { + expect_truthy(!isMatch('foo', '!(foo)', { windows: true })); + }); + + test('"foo" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foo', '!(foo)*', { windows: true })); + }); + + test('"foo" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foo', '!(foo)*', { windows: true })); + }); + + test('"foo" should not match "!(foo)+"', () => { + expect_truthy(!isMatch('foo', '!(foo)+', { windows: true })); + }); + + test('"foo" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foo', '!(foo)b*', { windows: true })); + }); + + test('"foo" should match "!(x)"', () => { + expect_truthy(isMatch('foo', '!(x)', { windows: true })); + }); + + test('"foo" should match "!(x)*"', () => { + expect_truthy(isMatch('foo', '!(x)*', { windows: true })); + }); + + test('"foo" should match "*"', () => { + expect_truthy(isMatch('foo', '*', { windows: true })); + }); + + test('"foo" should match "*(!(f))"', () => { + expect_truthy(isMatch('foo', '*(!(f))', { windows: true })); + }); + + test('"foo" should not match "*(!(foo))"', () => { + expect_truthy(!isMatch('foo', '*(!(foo))', { windows: true })); + }); + + test('"foo" should not match "*(@(a))a@(c)"', () => { + expect_truthy(!isMatch('foo', '*(@(a))a@(c)', { windows: true })); + }); + + test('"foo" should match "*(@(foo))"', () => { + expect_truthy(isMatch('foo', '*(@(foo))', { windows: true })); + }); + + test('"foo" should not match "*(a|b\\[)"', () => { + expect_truthy(!isMatch('foo', '*(a|b\\[)', { windows: true })); + }); + + test('"foo" should match "*(a|b\\[)|f*"', () => { + expect_truthy(isMatch('foo', '*(a|b\\[)|f*', { windows: true })); + }); + + test('"foo" should match "@(*(a|b\\[)|f*)"', () => { + expect_truthy(isMatch('foo', '@(*(a|b\\[)|f*)', { windows: true })); + }); + + test('"foo" should not match "*/*/*"', () => { + expect_truthy(!isMatch('foo', '*/*/*', { windows: true })); + }); + + test('"foo" should not match "*f"', () => { + expect_truthy(!isMatch('foo', '*f', { windows: true })); + }); + + test('"foo" should match "*foo*"', () => { + expect_truthy(isMatch('foo', '*foo*', { windows: true })); + }); + + test('"foo" should match "+(!(f))"', () => { + expect_truthy(isMatch('foo', '+(!(f))', { windows: true })); + }); + + test('"foo" should not match "??"', () => { + expect_truthy(!isMatch('foo', '??', { windows: true })); + }); + + test('"foo" should match "???"', () => { + expect_truthy(isMatch('foo', '???', { windows: true })); + }); + + test('"foo" should not match "bar"', () => { + expect_truthy(!isMatch('foo', 'bar', { windows: true })); + }); + + test('"foo" should match "f*"', () => { + expect_truthy(isMatch('foo', 'f*', { windows: true })); + }); + + test('"foo" should not match "fo"', () => { + expect_truthy(!isMatch('foo', 'fo', { windows: true })); + }); + + test('"foo" should match "foo"', () => { + expect_truthy(isMatch('foo', 'foo', { windows: true })); + }); + + test('"foo" should match "{*(a|b\\[),f*}"', () => { + expect_truthy(isMatch('foo', '{*(a|b\\[),f*}', { windows: true })); + }); + + test('"foo*" should match "foo\\*"', () => { + expect_truthy(isMatch('foo*', 'foo\\*', { windows: false })); + }); + + test('"foo*bar" should match "foo\\*bar"', () => { + expect_truthy(isMatch('foo*bar', 'foo\\*bar', { windows: true })); + }); + + test('"foo.js" should not match "!(foo).js"', () => { + expect_truthy(!isMatch('foo.js', '!(foo).js', { windows: true })); + }); + + test('"foo.js.js" should match "*.!(js)"', () => { + expect_truthy(isMatch('foo.js.js', '*.!(js)', { windows: true })); + }); + + test('"foo.js.js" should not match "*.!(js)*"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)*', { windows: true })); + }); + + test('"foo.js.js" should not match "*.!(js)*.!(js)"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)*.!(js)', { windows: true })); + }); + + test('"foo.js.js" should not match "*.!(js)+"', () => { + expect_truthy(!isMatch('foo.js.js', '*.!(js)+', { windows: true })); + }); + + test('"foo.txt" should match "**/!(bar).txt"', () => { + expect_truthy(isMatch('foo.txt', '**/!(bar).txt', { windows: true })); + }); + + test('"foo/bar" should not match "*/*/*"', () => { + expect_truthy(!isMatch('foo/bar', '*/*/*', { windows: true })); + }); + + test('"foo/bar" should match "foo/!(foo)"', () => { + expect_truthy(isMatch('foo/bar', 'foo/!(foo)', { windows: true })); + }); + + test('"foo/bar" should match "foo/*"', () => { + expect_truthy(isMatch('foo/bar', 'foo/*', { windows: true })); + }); + + test('"foo/bar" should match "foo/bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo/bar', { windows: true })); + }); + + test('"foo/bar" should not match "foo?bar"', () => { + expect_truthy(!isMatch('foo/bar', 'foo?bar', { windows: true })); + }); + + test('"foo/bar" should match "foo[/]bar"', () => { + expect_truthy(isMatch('foo/bar', 'foo[/]bar', { windows: true })); + }); + + test('"foo/bar/baz.jsx" should match "foo/bar/**/*.+(js|jsx)"', () => { + expect_truthy(isMatch('foo/bar/baz.jsx', 'foo/bar/**/*.+(js|jsx)', { windows: true })); + }); + + test('"foo/bar/baz.jsx" should match "foo/bar/*.+(js|jsx)"', () => { + expect_truthy(isMatch('foo/bar/baz.jsx', 'foo/bar/*.+(js|jsx)', { windows: true })); + }); + + test('"foo/bb/aa/rr" should match "**/**/**"', () => { + expect_truthy(isMatch('foo/bb/aa/rr', '**/**/**', { windows: true })); + }); + + test('"foo/bb/aa/rr" should not match "*/*/*"', () => { + expect_truthy(!isMatch('foo/bb/aa/rr', '*/*/*', { windows: true })); + }); + + test('"foo/bba/arr" should match "*/*/*"', () => { + expect_truthy(isMatch('foo/bba/arr', '*/*/*', { windows: true })); + }); + + test('"foo/bba/arr" should not match "foo*"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo*', { windows: true })); + }); + + test('"foo/bba/arr" should not match "foo**"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo**', { windows: true })); + }); + + test('"foo/bba/arr" should not match "foo/*"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/*', { windows: true })); + }); + + test('"foo/bba/arr" should match "foo/**"', () => { + expect_truthy(isMatch('foo/bba/arr', 'foo/**', { windows: true })); + }); + + test('"foo/bba/arr" should not match "foo/**arr"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/**arr', { windows: true })); + }); + + test('"foo/bba/arr" should not match "foo/**z"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/**z', { windows: true })); + }); + + test('"foo/bba/arr" should not match "foo/*arr"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/*arr', { windows: true })); + }); + + test('"foo/bba/arr" should not match "foo/*z"', () => { + expect_truthy(!isMatch('foo/bba/arr', 'foo/*z', { windows: true })); + }); + + test('"foob" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foob', '!(foo)b*', { windows: true })); + }); + + test('"foob" should not match "(foo)bb"', () => { + expect_truthy(!isMatch('foob', '(foo)bb', { windows: true })); + }); + + test('"foobar" should match "!(foo)"', () => { + expect_truthy(isMatch('foobar', '!(foo)', { windows: true })); + }); + + test('"foobar" should not match "!(foo)*"', () => { + expect_truthy(!isMatch('foobar', '!(foo)*', { windows: true })); + }); + + test('"foobar" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foobar', '!(foo)b*', { windows: true })); + }); + + test('"foobar" should match "*(!(foo))"', () => { + expect_truthy(isMatch('foobar', '*(!(foo))', { windows: true })); + }); + + test('"foobar" should match "*ob*a*r*"', () => { + expect_truthy(isMatch('foobar', '*ob*a*r*', { windows: true })); + }); + + test('"foobar" should not match "foo\\*bar"', () => { + expect_truthy(!isMatch('foobar', 'foo\\*bar', { windows: true })); + }); + + test('"foobb" should not match "!(foo)b*"', () => { + expect_truthy(!isMatch('foobb', '!(foo)b*', { windows: true })); + }); + + test('"foobb" should match "(foo)bb"', () => { + expect_truthy(isMatch('foobb', '(foo)bb', { windows: true })); + }); + + test('"(foo)bb" should match "\\(foo\\)bb"', () => { + expect_truthy(isMatch('(foo)bb', '\\(foo\\)bb', { windows: true })); + }); + + test('"foofoofo" should match "@(foo|f|fo)*(f|of+(o))"', () => { + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + }); + + test('"foofoofo" should match "@(foo|f|fo)*(f|of+(o))"', () => { + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + }); + + test('"fooofoofofooo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('fooofoofofooo', '*(f*(o))', { windows: true })); + }); + + test('"foooofo" should match "*(f*(o))"', () => { + expect_truthy(isMatch('foooofo', '*(f*(o))', { windows: true })); + }); + + test('"foooofof" should match "*(f*(o))"', () => { + expect_truthy(isMatch('foooofof', '*(f*(o))', { windows: true })); + }); + + test('"foooofof" should not match "*(f+(o))"', () => { + expect_truthy(!isMatch('foooofof', '*(f+(o))', { windows: true })); + }); + + test('"foooofofx" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('foooofofx', '*(f*(o))', { windows: true })); + }); + + test('"foooxfooxfoxfooox" should match "*(f*(o)x)"', () => { + expect_truthy(isMatch('foooxfooxfoxfooox', '*(f*(o)x)', { windows: true })); + }); + + test('"foooxfooxfxfooox" should match "*(f*(o)x)"', () => { + expect_truthy(isMatch('foooxfooxfxfooox', '*(f*(o)x)', { windows: true })); + }); + + test('"foooxfooxofoxfooox" should not match "*(f*(o)x)"', () => { + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f*(o)x)', { windows: true })); + }); + + test('"foot" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('foot', '@(!(z*)|*x)', { windows: true })); + }); + + test('"foox" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('foox', '@(!(z*)|*x)', { windows: true })); + }); + + test('"fz" should not match "*(z)"', () => { + expect_truthy(!isMatch('fz', '*(z)', { windows: true })); + }); + + test('"fz" should not match "+(z)"', () => { + expect_truthy(!isMatch('fz', '+(z)', { windows: true })); + }); + + test('"fz" should not match "?(z)"', () => { + expect_truthy(!isMatch('fz', '?(z)', { windows: true })); + }); + + test('"moo.cow" should not match "!(moo).!(cow)"', () => { + expect_truthy(!isMatch('moo.cow', '!(moo).!(cow)', { windows: true })); + }); + + test('"moo.cow" should not match "!(*).!(*)"', () => { + expect_truthy(!isMatch('moo.cow', '!(*).!(*)', { windows: true })); + }); + + test('"mad.moo.cow" should not match "!(*.*).!(*.*)"', () => { + expect_truthy(!isMatch('mad.moo.cow', '!(*.*).!(*.*)', { windows: true })); + }); + + test('"mad.moo.cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('mad.moo.cow', '.!(*.*)', { windows: true })); + }); + + test('"Makefile" should match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(isMatch('Makefile', '!(*.c|*.h|Makefile.in|config*|README)', { windows: true })); + }); + + test('"Makefile.in" should not match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(!isMatch('Makefile.in', '!(*.c|*.h|Makefile.in|config*|README)', { windows: true })); + }); + + test('"moo" should match "!(*.*)"', () => { + expect_truthy(isMatch('moo', '!(*.*)', { windows: true })); + }); + + test('"moo" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('moo', '!(*.*).', { windows: true })); + }); + + test('"moo" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('moo', '.!(*.*)', { windows: true })); + }); + + test('"moo.cow" should not match "!(*.*)"', () => { + expect_truthy(!isMatch('moo.cow', '!(*.*)', { windows: true })); + }); + + test('"moo.cow" should not match "!(*.*)."', () => { + expect_truthy(!isMatch('moo.cow', '!(*.*).', { windows: true })); + }); + + test('"moo.cow" should not match ".!(*.*)"', () => { + expect_truthy(!isMatch('moo.cow', '.!(*.*)', { windows: true })); + }); + + test('"mucca.pazza" should not match "mu!(*(c))?.pa!(*(z))?"', () => { + expect_truthy(!isMatch('mucca.pazza', 'mu!(*(c))?.pa!(*(z))?', { windows: true })); + }); + + test('"ofoofo" should match "*(of+(o))"', () => { + expect_truthy(isMatch('ofoofo', '*(of+(o))', { windows: true })); + }); + + test('"ofoofo" should match "*(of+(o)|f)"', () => { + expect_truthy(isMatch('ofoofo', '*(of+(o)|f)', { windows: true })); + }); + + test('"ofooofoofofooo" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('ofooofoofofooo', '*(f*(o))', { windows: true })); + }); + + test('"ofoooxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxo', '*(*(of*(o)x)o)', { windows: true })); + }); + + test('"ofoooxoofxoofoooxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxo', '*(*(of*(o)x)o)', { windows: true })); + }); + + test('"ofoooxoofxoofoooxoofxofo" should not match "*(*(of*(o)x)o)"', () => { + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(*(of*(o)x)o)', { windows: true })); + }); + + test('"ofoooxoofxoofoooxoofxoo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxoo', '*(*(of*(o)x)o)', { windows: true })); + }); + + test('"ofoooxoofxoofoooxoofxooofxofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(*(of*(o)x)o)', { windows: true })); + }); + + test('"ofxoofxo" should match "*(*(of*(o)x)o)"', () => { + expect_truthy(isMatch('ofxoofxo', '*(*(of*(o)x)o)', { windows: true })); + }); + + test('"oofooofo" should match "*(of|oof+(o))"', () => { + expect_truthy(isMatch('oofooofo', '*(of|oof+(o))', { windows: true })); + }); + + test('"ooo" should match "!(f)"', () => { + expect_truthy(isMatch('ooo', '!(f)', { windows: true })); + }); + + test('"ooo" should match "*(!(f))"', () => { + expect_truthy(isMatch('ooo', '*(!(f))', { windows: true })); + }); + + test('"ooo" should match "+(!(f))"', () => { + expect_truthy(isMatch('ooo', '+(!(f))', { windows: true })); + }); + + test('"oxfoxfox" should not match "*(oxf+(ox))"', () => { + expect_truthy(!isMatch('oxfoxfox', '*(oxf+(ox))', { windows: true })); + }); + + test('"oxfoxoxfox" should match "*(oxf+(ox))"', () => { + expect_truthy(isMatch('oxfoxoxfox', '*(oxf+(ox))', { windows: true })); + }); + + test('"para" should match "para*([0-9])"', () => { + expect_truthy(isMatch('para', 'para*([0-9])', { windows: true })); + }); + + test('"para" should not match "para+([0-9])"', () => { + expect_truthy(!isMatch('para', 'para+([0-9])', { windows: true })); + }); + + test('"para.38" should match "para!(*.[00-09])"', () => { + expect_truthy(isMatch('para.38', 'para!(*.[00-09])', { windows: true })); + }); + + test('"para.graph" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('para.graph', 'para!(*.[0-9])', { windows: true })); + }); + + test('"para13829383746592" should match "para*([0-9])"', () => { + expect_truthy(isMatch('para13829383746592', 'para*([0-9])', { windows: true })); + }); + + test('"para381" should not match "para?([345]|99)1"', () => { + expect_truthy(!isMatch('para381', 'para?([345]|99)1', { windows: true })); + }); + + test('"para39" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('para39', 'para!(*.[0-9])', { windows: true })); + }); + + test('"para987346523" should match "para+([0-9])"', () => { + expect_truthy(isMatch('para987346523', 'para+([0-9])', { windows: true })); + }); + + test('"para991" should match "para?([345]|99)1"', () => { + expect_truthy(isMatch('para991', 'para?([345]|99)1', { windows: true })); + }); + + test('"paragraph" should match "para!(*.[0-9])"', () => { + expect_truthy(isMatch('paragraph', 'para!(*.[0-9])', { windows: true })); + }); + + test('"paragraph" should not match "para*([0-9])"', () => { + expect_truthy(!isMatch('paragraph', 'para*([0-9])', { windows: true })); + }); + + test('"paragraph" should match "para@(chute|graph)"', () => { + expect_truthy(isMatch('paragraph', 'para@(chute|graph)', { windows: true })); + }); + + test('"paramour" should not match "para@(chute|graph)"', () => { + expect_truthy(!isMatch('paramour', 'para@(chute|graph)', { windows: true })); + }); + + test('"parse.y" should match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(isMatch('parse.y', '!(*.c|*.h|Makefile.in|config*|README)', { windows: true })); + }); + + test('"shell.c" should not match "!(*.c|*.h|Makefile.in|config*|README)"', () => { + expect_truthy(!isMatch('shell.c', '!(*.c|*.h|Makefile.in|config*|README)', { windows: true })); + }); + + test('"VMS.FILE;" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;', '*\\;[1-9]*([0-9])', { windows: true })); + }); + + test('"VMS.FILE;0" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;0', '*\\;[1-9]*([0-9])', { windows: true })); + }); + + test('"VMS.FILE;1" should match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;1', '*\\;[1-9]*([0-9])', { windows: true })); + }); + + test('"VMS.FILE;1" should match "*;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;1', '*;[1-9]*([0-9])', { windows: true })); + }); + + test('"VMS.FILE;139" should match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(isMatch('VMS.FILE;139', '*\\;[1-9]*([0-9])', { windows: true })); + }); + + test('"VMS.FILE;1N" should not match "*\\;[1-9]*([0-9])"', () => { + expect_truthy(!isMatch('VMS.FILE;1N', '*\\;[1-9]*([0-9])', { windows: true })); + }); + + test('"xfoooofof" should not match "*(f*(o))"', () => { + expect_truthy(!isMatch('xfoooofof', '*(f*(o))', { windows: true })); + }); + + test('"XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1" should match "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*"', () => { + expect_truthy(isMatch('XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1', 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*', { windows: false })); + }); + + test('"XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1" should not match "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*"', () => { + expect_truthy(!isMatch('XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1', 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*', { windows: true })); + }); + + test('"z" should match "*(z)"', () => { + expect_truthy(isMatch('z', '*(z)', { windows: true })); + }); + + test('"z" should match "+(z)"', () => { + expect_truthy(isMatch('z', '+(z)', { windows: true })); + }); + + test('"z" should match "?(z)"', () => { + expect_truthy(isMatch('z', '?(z)', { windows: true })); + }); + + test('"zf" should not match "*(z)"', () => { + expect_truthy(!isMatch('zf', '*(z)', { windows: true })); + }); + + test('"zf" should not match "+(z)"', () => { + expect_truthy(!isMatch('zf', '+(z)', { windows: true })); + }); + + test('"zf" should not match "?(z)"', () => { + expect_truthy(!isMatch('zf', '?(z)', { windows: true })); + }); + + test('"zoot" should not match "@(!(z*)|*x)"', () => { + expect_truthy(!isMatch('zoot', '@(!(z*)|*x)', { windows: true })); + }); + + test('"zoox" should match "@(!(z*)|*x)"', () => { + expect_truthy(isMatch('zoox', '@(!(z*)|*x)', { windows: true })); + }); + + test('"zz" should not match "(a+|b)*"', () => { + expect_truthy(!isMatch('zz', '(a+|b)*', { windows: true })); + }); +}); diff --git a/packages/node-utils/test/picomatch/extglobs-temp.test.ts b/packages/node-utils/test/picomatch/extglobs-temp.test.ts new file mode 100644 index 0000000..4d69fcd --- /dev/null +++ b/packages/node-utils/test/picomatch/extglobs-temp.test.ts @@ -0,0 +1,1244 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +/** + * Some of tests were converted from bash 4.3, 4.4, and minimatch unit tests. + * This is called "temp" as a reminder to reorganize these test and remove duplicates. + */ + +describe('extglobs', () => { + describe('bash', () => { + test('should match extended globs from the bash spec:', () => { + expect_truthy(isMatch('bar', '!(foo)', { windows: true })); + expect_truthy(isMatch('f', '!(foo)', { windows: true })); + expect_truthy(isMatch('fa', '!(foo)', { windows: true })); + expect_truthy(isMatch('fb', '!(foo)', { windows: true })); + expect_truthy(isMatch('ff', '!(foo)', { windows: true })); + expect_truthy(isMatch('fff', '!(foo)', { windows: true })); + expect_truthy(isMatch('fo', '!(foo)', { windows: true })); + expect_truthy(!isMatch('foo', '!(foo)', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(foo)', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(foo)/*', { windows: true })); + expect_truthy(isMatch('foobar', '!(foo)', { windows: true })); + expect_truthy(isMatch('foot', '!(foo)', { windows: true })); + expect_truthy(isMatch('foox', '!(foo)', { windows: true })); + expect_truthy(isMatch('o', '!(foo)', { windows: true })); + expect_truthy(isMatch('of', '!(foo)', { windows: true })); + expect_truthy(isMatch('ooo', '!(foo)', { windows: true })); + expect_truthy(isMatch('ox', '!(foo)', { windows: true })); + expect_truthy(isMatch('x', '!(foo)', { windows: true })); + expect_truthy(isMatch('xx', '!(foo)', { windows: true })); + + expect_truthy(!isMatch('bar', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('f', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('fa', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('fb', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('ff', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('fff', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('fo', '!(!(foo))', { windows: true })); + expect_truthy(isMatch('foo', '!(!(foo))', { windows: true })); + expect_truthy(isMatch('foo/bar', '!(!(bar)/baz)', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('foobar', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('foot', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('foox', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('o', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('of', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('ooo', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('ox', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('x', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('xx', '!(!(foo))', { windows: true })); + + expect_truthy(isMatch('bar', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('f', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('fa', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('fb', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('ff', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('fff', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('fo', '!(!(!(foo)))', { windows: true })); + expect_truthy(!isMatch('foo', '!(!(!(foo)))', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('foobar', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('foot', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('foox', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('o', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('of', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('ooo', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('ox', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('x', '!(!(!(foo)))', { windows: true })); + expect_truthy(isMatch('xx', '!(!(!(foo)))', { windows: true })); + + expect_truthy(!isMatch('bar', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('f', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('fa', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('fb', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('ff', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('fff', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('fo', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(isMatch('foo', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('foot', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('o', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('of', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('ooo', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('ox', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('x', '!(!(!(!(foo))))', { windows: true })); + expect_truthy(!isMatch('xx', '!(!(!(!(foo))))', { windows: true })); + + expect_truthy(!isMatch('bar', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('f', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('fa', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('fb', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('ff', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('fff', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('fo', '!(!(foo))*', { windows: true })); + expect_truthy(isMatch('foo', '!(!(foo))*', { windows: true })); + expect_truthy(isMatch('foobar', '!(!(foo))*', { windows: true })); + expect_truthy(isMatch('foot', '!(!(foo))*', { windows: true })); + expect_truthy(isMatch('foox', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('o', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('of', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('ooo', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('ox', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('x', '!(!(foo))*', { windows: true })); + expect_truthy(!isMatch('xx', '!(!(foo))*', { windows: true })); + + expect_truthy(isMatch('bar', '!(f!(o))', { windows: true })); + expect_truthy(!isMatch('f', '!(f!(o))', { windows: true })); + expect_truthy(!isMatch('fa', '!(f!(o))', { windows: true })); + expect_truthy(!isMatch('fb', '!(f!(o))', { windows: true })); + expect_truthy(!isMatch('ff', '!(f!(o))', { windows: true })); + expect_truthy(!isMatch('fff', '!(f!(o))', { windows: true })); + expect_truthy(isMatch('fo', '!(f!(o))', { windows: true })); + expect_truthy(isMatch('foo', '!(!(foo))', { windows: true })); + expect_truthy(!isMatch('foo', '!(f)!(o)!(o)', { windows: true })); + expect_truthy(isMatch('foo', '!(fo)', { windows: true })); + expect_truthy(isMatch('foo', '!(f!(o)*)', { windows: true })); + expect_truthy(!isMatch('foo', '!(f!(o))', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(f!(o))', { windows: true })); + expect_truthy(!isMatch('foobar', '!(f!(o))', { windows: true })); + expect_truthy(isMatch('o', '!(f!(o))', { windows: true })); + expect_truthy(isMatch('of', '!(f!(o))', { windows: true })); + expect_truthy(isMatch('ooo', '!(f!(o))', { windows: true })); + expect_truthy(isMatch('ox', '!(f!(o))', { windows: true })); + expect_truthy(isMatch('x', '!(f!(o))', { windows: true })); + expect_truthy(isMatch('xx', '!(f!(o))', { windows: true })); + + expect_truthy(isMatch('bar', '!(f(o))', { windows: true })); + expect_truthy(isMatch('f', '!(f(o))', { windows: true })); + expect_truthy(isMatch('fa', '!(f(o))', { windows: true })); + expect_truthy(isMatch('fb', '!(f(o))', { windows: true })); + expect_truthy(isMatch('ff', '!(f(o))', { windows: true })); + expect_truthy(isMatch('fff', '!(f(o))', { windows: true })); + expect_truthy(!isMatch('fo', '!(f(o))', { windows: true })); + expect_truthy(isMatch('foo', '!(f(o))', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(f(o))', { windows: true })); + expect_truthy(isMatch('foobar', '!(f(o))', { windows: true })); + expect_truthy(isMatch('foot', '!(f(o))', { windows: true })); + expect_truthy(isMatch('foox', '!(f(o))', { windows: true })); + expect_truthy(isMatch('o', '!(f(o))', { windows: true })); + expect_truthy(isMatch('of', '!(f(o))', { windows: true })); + expect_truthy(isMatch('ooo', '!(f(o))', { windows: true })); + expect_truthy(isMatch('ox', '!(f(o))', { windows: true })); + expect_truthy(isMatch('x', '!(f(o))', { windows: true })); + expect_truthy(isMatch('xx', '!(f(o))', { windows: true })); + + expect_truthy(isMatch('bar', '!(f)', { windows: true })); + expect_truthy(!isMatch('f', '!(f)', { windows: true })); + expect_truthy(isMatch('fa', '!(f)', { windows: true })); + expect_truthy(isMatch('fb', '!(f)', { windows: true })); + expect_truthy(isMatch('ff', '!(f)', { windows: true })); + expect_truthy(isMatch('fff', '!(f)', { windows: true })); + expect_truthy(isMatch('fo', '!(f)', { windows: true })); + expect_truthy(isMatch('foo', '!(f)', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(f)', { windows: true })); + expect_truthy(isMatch('foobar', '!(f)', { windows: true })); + expect_truthy(isMatch('foot', '!(f)', { windows: true })); + expect_truthy(isMatch('foox', '!(f)', { windows: true })); + expect_truthy(isMatch('o', '!(f)', { windows: true })); + expect_truthy(isMatch('of', '!(f)', { windows: true })); + expect_truthy(isMatch('ooo', '!(f)', { windows: true })); + expect_truthy(isMatch('ox', '!(f)', { windows: true })); + expect_truthy(isMatch('x', '!(f)', { windows: true })); + expect_truthy(isMatch('xx', '!(f)', { windows: true })); + + expect_truthy(isMatch('bar', '!(f)', { windows: true })); + expect_truthy(!isMatch('f', '!(f)', { windows: true })); + expect_truthy(isMatch('fa', '!(f)', { windows: true })); + expect_truthy(isMatch('fb', '!(f)', { windows: true })); + expect_truthy(isMatch('ff', '!(f)', { windows: true })); + expect_truthy(isMatch('fff', '!(f)', { windows: true })); + expect_truthy(isMatch('fo', '!(f)', { windows: true })); + expect_truthy(isMatch('foo', '!(f)', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(f)', { windows: true })); + expect_truthy(isMatch('foobar', '!(f)', { windows: true })); + expect_truthy(isMatch('foot', '!(f)', { windows: true })); + expect_truthy(isMatch('foox', '!(f)', { windows: true })); + expect_truthy(isMatch('o', '!(f)', { windows: true })); + expect_truthy(isMatch('of', '!(f)', { windows: true })); + expect_truthy(isMatch('ooo', '!(f)', { windows: true })); + expect_truthy(isMatch('ox', '!(f)', { windows: true })); + expect_truthy(isMatch('x', '!(f)', { windows: true })); + expect_truthy(isMatch('xx', '!(f)', { windows: true })); + + expect_truthy(isMatch('bar', '!(foo)', { windows: true })); + expect_truthy(isMatch('f', '!(foo)', { windows: true })); + expect_truthy(isMatch('fa', '!(foo)', { windows: true })); + expect_truthy(isMatch('fb', '!(foo)', { windows: true })); + expect_truthy(isMatch('ff', '!(foo)', { windows: true })); + expect_truthy(isMatch('fff', '!(foo)', { windows: true })); + expect_truthy(isMatch('fo', '!(foo)', { windows: true })); + expect_truthy(!isMatch('foo', '!(foo)', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(foo)', { windows: true })); + expect_truthy(isMatch('foobar', '!(foo)', { windows: true })); + expect_truthy(isMatch('foot', '!(foo)', { windows: true })); + expect_truthy(isMatch('foox', '!(foo)', { windows: true })); + expect_truthy(isMatch('o', '!(foo)', { windows: true })); + expect_truthy(isMatch('of', '!(foo)', { windows: true })); + expect_truthy(isMatch('ooo', '!(foo)', { windows: true })); + expect_truthy(isMatch('ox', '!(foo)', { windows: true })); + expect_truthy(isMatch('x', '!(foo)', { windows: true })); + expect_truthy(isMatch('xx', '!(foo)', { windows: true })); + + expect_truthy(isMatch('bar', '!(foo)*', { windows: true })); + expect_truthy(isMatch('f', '!(foo)*', { windows: true })); + expect_truthy(isMatch('fa', '!(foo)*', { windows: true })); + expect_truthy(isMatch('fb', '!(foo)*', { windows: true })); + expect_truthy(isMatch('ff', '!(foo)*', { windows: true })); + expect_truthy(isMatch('fff', '!(foo)*', { windows: true })); + expect_truthy(isMatch('fo', '!(foo)*', { windows: true })); + expect_truthy(!isMatch('foo', '!(foo)*', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(foo)*', { windows: true })); + expect_truthy(!isMatch('foobar', '!(foo)*', { windows: true })); + expect_truthy(!isMatch('foot', '!(foo)*', { windows: true })); + expect_truthy(!isMatch('foox', '!(foo)*', { windows: true })); + expect_truthy(isMatch('o', '!(foo)*', { windows: true })); + expect_truthy(isMatch('of', '!(foo)*', { windows: true })); + expect_truthy(isMatch('ooo', '!(foo)*', { windows: true })); + expect_truthy(isMatch('ox', '!(foo)*', { windows: true })); + expect_truthy(isMatch('x', '!(foo)*', { windows: true })); + expect_truthy(isMatch('xx', '!(foo)*', { windows: true })); + + expect_truthy(isMatch('bar', '!(x)', { windows: true })); + expect_truthy(isMatch('f', '!(x)', { windows: true })); + expect_truthy(isMatch('fa', '!(x)', { windows: true })); + expect_truthy(isMatch('fb', '!(x)', { windows: true })); + expect_truthy(isMatch('ff', '!(x)', { windows: true })); + expect_truthy(isMatch('fff', '!(x)', { windows: true })); + expect_truthy(isMatch('fo', '!(x)', { windows: true })); + expect_truthy(isMatch('foo', '!(x)', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(x)', { windows: true })); + expect_truthy(isMatch('foobar', '!(x)', { windows: true })); + expect_truthy(isMatch('foot', '!(x)', { windows: true })); + expect_truthy(isMatch('foox', '!(x)', { windows: true })); + expect_truthy(isMatch('o', '!(x)', { windows: true })); + expect_truthy(isMatch('of', '!(x)', { windows: true })); + expect_truthy(isMatch('ooo', '!(x)', { windows: true })); + expect_truthy(isMatch('ox', '!(x)', { windows: true })); + expect_truthy(!isMatch('x', '!(x)', { windows: true })); + expect_truthy(isMatch('xx', '!(x)', { windows: true })); + + expect_truthy(isMatch('bar', '!(x)*', { windows: true })); + expect_truthy(isMatch('f', '!(x)*', { windows: true })); + expect_truthy(isMatch('fa', '!(x)*', { windows: true })); + expect_truthy(isMatch('fb', '!(x)*', { windows: true })); + expect_truthy(isMatch('ff', '!(x)*', { windows: true })); + expect_truthy(isMatch('fff', '!(x)*', { windows: true })); + expect_truthy(isMatch('fo', '!(x)*', { windows: true })); + expect_truthy(isMatch('foo', '!(x)*', { windows: true })); + expect_truthy(!isMatch('foo/bar', '!(x)*', { windows: true })); + expect_truthy(isMatch('foobar', '!(x)*', { windows: true })); + expect_truthy(isMatch('foot', '!(x)*', { windows: true })); + expect_truthy(isMatch('foox', '!(x)*', { windows: true })); + expect_truthy(isMatch('o', '!(x)*', { windows: true })); + expect_truthy(isMatch('of', '!(x)*', { windows: true })); + expect_truthy(isMatch('ooo', '!(x)*', { windows: true })); + expect_truthy(isMatch('ox', '!(x)*', { windows: true })); + expect_truthy(!isMatch('x', '!(x)*', { windows: true })); + expect_truthy(!isMatch('xx', '!(x)*', { windows: true })); + + expect_truthy(isMatch('bar', '*(!(f))', { windows: true })); + expect_truthy(!isMatch('f', '*(!(f))', { windows: true })); + expect_truthy(isMatch('fa', '*(!(f))', { windows: true })); + expect_truthy(isMatch('fb', '*(!(f))', { windows: true })); + expect_truthy(isMatch('ff', '*(!(f))', { windows: true })); + expect_truthy(isMatch('fff', '*(!(f))', { windows: true })); + expect_truthy(isMatch('fo', '*(!(f))', { windows: true })); + expect_truthy(isMatch('foo', '*(!(f))', { windows: true })); + expect_truthy(!isMatch('foo/bar', '*(!(f))', { windows: true })); + expect_truthy(isMatch('foobar', '*(!(f))', { windows: true })); + expect_truthy(isMatch('foot', '*(!(f))', { windows: true })); + expect_truthy(isMatch('foox', '*(!(f))', { windows: true })); + expect_truthy(isMatch('o', '*(!(f))', { windows: true })); + expect_truthy(isMatch('of', '*(!(f))', { windows: true })); + expect_truthy(isMatch('ooo', '*(!(f))', { windows: true })); + expect_truthy(isMatch('ox', '*(!(f))', { windows: true })); + expect_truthy(isMatch('x', '*(!(f))', { windows: true })); + expect_truthy(isMatch('xx', '*(!(f))', { windows: true })); + + expect_truthy(!isMatch('bar', '*((foo))', { windows: true })); + expect_truthy(!isMatch('f', '*((foo))', { windows: true })); + expect_truthy(!isMatch('fa', '*((foo))', { windows: true })); + expect_truthy(!isMatch('fb', '*((foo))', { windows: true })); + expect_truthy(!isMatch('ff', '*((foo))', { windows: true })); + expect_truthy(!isMatch('fff', '*((foo))', { windows: true })); + expect_truthy(!isMatch('fo', '*((foo))', { windows: true })); + expect_truthy(isMatch('foo', '*((foo))', { windows: true })); + expect_truthy(!isMatch('foo/bar', '*((foo))', { windows: true })); + expect_truthy(!isMatch('foobar', '*((foo))', { windows: true })); + expect_truthy(!isMatch('foot', '*((foo))', { windows: true })); + expect_truthy(!isMatch('foox', '*((foo))', { windows: true })); + expect_truthy(!isMatch('o', '*((foo))', { windows: true })); + expect_truthy(!isMatch('of', '*((foo))', { windows: true })); + expect_truthy(!isMatch('ooo', '*((foo))', { windows: true })); + expect_truthy(!isMatch('ox', '*((foo))', { windows: true })); + expect_truthy(!isMatch('x', '*((foo))', { windows: true })); + expect_truthy(!isMatch('xx', '*((foo))', { windows: true })); + + expect_truthy(isMatch('bar', '+(!(f))', { windows: true })); + expect_truthy(!isMatch('f', '+(!(f))', { windows: true })); + expect_truthy(isMatch('fa', '+(!(f))', { windows: true })); + expect_truthy(isMatch('fb', '+(!(f))', { windows: true })); + expect_truthy(isMatch('ff', '+(!(f))', { windows: true })); + expect_truthy(isMatch('fff', '+(!(f))', { windows: true })); + expect_truthy(isMatch('fo', '+(!(f))', { windows: true })); + expect_truthy(isMatch('foo', '+(!(f))', { windows: true })); + expect_truthy(!isMatch('foo/bar', '+(!(f))', { windows: true })); + expect_truthy(isMatch('foobar', '+(!(f))', { windows: true })); + expect_truthy(isMatch('foot', '+(!(f))', { windows: true })); + expect_truthy(isMatch('foox', '+(!(f))', { windows: true })); + expect_truthy(isMatch('o', '+(!(f))', { windows: true })); + expect_truthy(isMatch('of', '+(!(f))', { windows: true })); + expect_truthy(isMatch('ooo', '+(!(f))', { windows: true })); + expect_truthy(isMatch('ox', '+(!(f))', { windows: true })); + expect_truthy(isMatch('x', '+(!(f))', { windows: true })); + expect_truthy(isMatch('xx', '+(!(f))', { windows: true })); + + expect_truthy(isMatch('bar', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('f', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('fa', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('fb', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ff', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('fff', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('fo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foo/bar', '@(!(z*/*)|*x)', { windows: true })); + expect_truthy(!isMatch('foo/bar', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foobar', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foot', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foox', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('o', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('of', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ooo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ox', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('x', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('xx', '@(!(z*)|*x)', { windows: true })); + + expect_truthy(!isMatch('bar', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('f', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('fa', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('fb', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('ff', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('fff', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('fo', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('foo', 'foo/!(foo)', { windows: true })); + expect_truthy(isMatch('foo/bar', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('foobar', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('foot', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('foox', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('o', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('of', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('ooo', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('ox', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('x', 'foo/!(foo)', { windows: true })); + expect_truthy(!isMatch('xx', 'foo/!(foo)', { windows: true })); + + expect_truthy(!isMatch('ffffffo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('ffo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('fofo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('fofoofoofofoo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('foo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('foob', '(foo)bb', { windows: true })); + expect_truthy(isMatch('foobb', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('foofoofo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('fooofoofofooo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('foooofo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('foooofof', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('foooofofx', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('foooxfooxfoxfooox', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('foooxfooxfxfooox', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('foot', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('foox', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('ofoofo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('ofooofoofofooo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('ofxoofxo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('oofooofo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('ooo', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('oxfoxoxfox', '(foo)bb', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '(foo)bb', { windows: true })); + + expect_truthy(isMatch('ffffffo', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('fffooofoooooffoofffooofff', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('ffo', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('fofo', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('fofoofoofofoo', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('foo', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('foob', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('foobb', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('foofoofo', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('fooofoofofooo', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('foooofo', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('foooofof', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('foooofofx', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('foot', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('foox', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('ofoofo', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('ofooofoofofooo', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxo', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('ofxoofxo', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('oofooofo', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('ooo', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxoxfox', '*(*(f)*(o))', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '*(*(f)*(o))', { windows: true })); + + expect_truthy(!isMatch('ffffffo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('ffo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('fofo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('fofoofoofofoo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foob', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foobb', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foofoofo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('fooofoofofooo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foooofo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foooofof', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foooofofx', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foot', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('foox', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('ofoofo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('ofooofoofofooo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxoo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(isMatch('ofxoofxo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('oofooofo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(isMatch('ooo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('oxfoxoxfox', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '*(*(of*(o)x)o)', { windows: true })); + + expect_truthy(isMatch('ffffffo', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('fffooofoooooffoofffooofff', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('ffo', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('fofo', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('fofoofoofofoo', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('foo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('foob', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('foobb', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('foofoofo', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('fooofoofofooo', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('foooofo', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('foooofof', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('foooofofx', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('foot', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('foox', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('ofoofo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('ofooofoofofooo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('ofxoofxo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('oofooofo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('ooo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxoxfox', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '*(f*(o))', { windows: true })); + + expect_truthy(!isMatch('ffffffo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('ffo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('fofo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('fofoofoofofoo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('foo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('foob', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('foobb', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('foofoofo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('fooofoofofooo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('foooofo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('foooofof', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('foooofofx', '*(f*(o)x)', { windows: true })); + expect_truthy(isMatch('foooxfooxfoxfooox', '*(f*(o)x)', { windows: true })); + expect_truthy(isMatch('foooxfooxfxfooox', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('foot', '*(f*(o)x)', { windows: true })); + expect_truthy(isMatch('foox', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('ofoofo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('ofooofoofofooo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('ofxoofxo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('oofooofo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('ooo', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('oxfoxoxfox', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '*(f*(o)x)', { windows: true })); + + expect_truthy(!isMatch('ffffffo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('ffo', '*(f+(o))', { windows: true })); + expect_truthy(isMatch('fofo', '*(f+(o))', { windows: true })); + expect_truthy(isMatch('fofoofoofofoo', '*(f+(o))', { windows: true })); + expect_truthy(isMatch('foo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('foob', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('foobb', '*(f+(o))', { windows: true })); + expect_truthy(isMatch('foofoofo', '*(f+(o))', { windows: true })); + expect_truthy(isMatch('fooofoofofooo', '*(f+(o))', { windows: true })); + expect_truthy(isMatch('foooofo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('foooofof', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('foooofofx', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('foot', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('foox', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('ofoofo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('ofooofoofofooo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('ofxoofxo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('oofooofo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('ooo', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxoxfox', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '*(f+(o))', { windows: true })); + + expect_truthy(!isMatch('ffffffo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('ffo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('fofo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('fofoofoofofoo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foob', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foobb', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foofoofo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('fooofoofofooo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foooofo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foooofof', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foooofofx', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foot', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('foox', '*(of+(o))', { windows: true })); + expect_truthy(isMatch('ofoofo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('ofooofoofofooo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('ofxoofxo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('oofooofo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('ooo', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxoxfox', '*(of+(o))', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '*(of+(o))', { windows: true })); + + expect_truthy(!isMatch('ffffffo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('ffo', '*(of+(o)|f)', { windows: true })); + expect_truthy(isMatch('fofo', '*(of+(o)|f)', { windows: true })); + expect_truthy(isMatch('fofoofoofofoo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foob', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foobb', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foofoofo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('fooofoofofooo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foooofo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foooofof', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foooofofx', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foot', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('foox', '*(of+(o)|f)', { windows: true })); + expect_truthy(isMatch('ofoofo', '*(of+(o)|f)', { windows: true })); + expect_truthy(isMatch('ofooofoofofooo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('ofxoofxo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('oofooofo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('ooo', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('oxfoxoxfox', '*(of+(o)|f)', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '*(of+(o)|f)', { windows: true })); + + expect_truthy(!isMatch('ffffffo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('ffo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('fofo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('fofoofoofofoo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foob', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foobb', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foofoofo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('fooofoofofooo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foooofo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foooofof', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foooofofx', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foot', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('foox', '*(of|oof+(o))', { windows: true })); + expect_truthy(isMatch('ofoofo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('ofooofoofofooo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('ofxoofxo', '*(of|oof+(o))', { windows: true })); + expect_truthy(isMatch('oofooofo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('ooo', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxoxfox', '*(of|oof+(o))', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '*(of|oof+(o))', { windows: true })); + + expect_truthy(!isMatch('ffffffo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('ffo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('fofo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('fofoofoofofoo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foob', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foobb', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foofoofo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('fooofoofofooo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foooofo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foooofof', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foooofofx', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foot', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('foox', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('ofoofo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('ofooofoofofooo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('ofxoofxo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('oofooofo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('ooo', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '*(oxf+(ox))', { windows: true })); + expect_truthy(isMatch('oxfoxoxfox', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '*(oxf+(ox))', { windows: true })); + + expect_truthy(isMatch('ffffffo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('fffooofoooooffoofffooofff', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ffo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('fofo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('fofoofoofofoo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foob', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foobb', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foofoofo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('fooofoofofooo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foooofo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foooofof', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foooofofx', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foooxfooxfoxfooox', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foooxfooxfxfooox', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foooxfooxofoxfooox', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foot', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foox', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ofoofo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ofooofoofofooo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxofo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxoo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxooofxofxo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ofxoofxo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('oofooofo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ooo', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('oxfoxfox', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('oxfoxoxfox', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('xfoooofof', '@(!(z*)|*x)', { windows: true })); + + expect_truthy(!isMatch('ffffffo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('ffo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(isMatch('fofo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(isMatch('fofoofoofofoo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(isMatch('foo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('foob', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('foobb', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(isMatch('fooofoofofooo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('foooofo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('foooofof', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('foooofofx', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfoxfooox', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxfxfooox', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('foot', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('foox', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('ofoofo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('ofooofoofofooo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('ofxoofxo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('oofooofo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('ooo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('oxfoxoxfox', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + + expect_truthy(isMatch('aaac', '*(@(a))a@(c)', { windows: true })); + expect_truthy(isMatch('aac', '*(@(a))a@(c)', { windows: true })); + expect_truthy(isMatch('ac', '*(@(a))a@(c)', { windows: true })); + expect_truthy(!isMatch('abbcd', '*(@(a))a@(c)', { windows: true })); + expect_truthy(!isMatch('abcd', '*(@(a))a@(c)', { windows: true })); + expect_truthy(!isMatch('acd', '*(@(a))a@(c)', { windows: true })); + expect_truthy(!isMatch('baaac', '*(@(a))a@(c)', { windows: true })); + expect_truthy(!isMatch('c', '*(@(a))a@(c)', { windows: true })); + expect_truthy(!isMatch('foo', '*(@(a))a@(c)', { windows: true })); + + expect_truthy(!isMatch('aaac', '@(ab|a*(b))*(c)d', { windows: true })); + expect_truthy(!isMatch('aac', '@(ab|a*(b))*(c)d', { windows: true })); + expect_truthy(!isMatch('ac', '@(ab|a*(b))*(c)d', { windows: true })); + expect_truthy(isMatch('abbcd', '@(ab|a*(b))*(c)d', { windows: true })); + expect_truthy(isMatch('abcd', '@(ab|a*(b))*(c)d', { windows: true })); + expect_truthy(isMatch('acd', '@(ab|a*(b))*(c)d', { windows: true })); + expect_truthy(!isMatch('baaac', '@(ab|a*(b))*(c)d', { windows: true })); + expect_truthy(!isMatch('c', '@(ab|a*(b))*(c)d', { windows: true })); + expect_truthy(!isMatch('foo', '@(ab|a*(b))*(c)d', { windows: true })); + + expect_truthy(!isMatch('aaac', '?@(a|b)*@(c)d', { windows: true })); + expect_truthy(!isMatch('aac', '?@(a|b)*@(c)d', { windows: true })); + expect_truthy(!isMatch('ac', '?@(a|b)*@(c)d', { windows: true })); + expect_truthy(isMatch('abbcd', '?@(a|b)*@(c)d', { windows: true })); + expect_truthy(isMatch('abcd', '?@(a|b)*@(c)d', { windows: true })); + expect_truthy(!isMatch('acd', '?@(a|b)*@(c)d', { windows: true })); + expect_truthy(!isMatch('baaac', '?@(a|b)*@(c)d', { windows: true })); + expect_truthy(!isMatch('c', '?@(a|b)*@(c)d', { windows: true })); + expect_truthy(!isMatch('foo', '?@(a|b)*@(c)d', { windows: true })); + + expect_truthy(!isMatch('aaac', '@(ab|a*@(b))*(c)d', { windows: true })); + expect_truthy(!isMatch('aac', '@(ab|a*@(b))*(c)d', { windows: true })); + expect_truthy(!isMatch('ac', '@(ab|a*@(b))*(c)d', { windows: true })); + expect_truthy(isMatch('abbcd', '@(ab|a*@(b))*(c)d', { windows: true })); + expect_truthy(isMatch('abcd', '@(ab|a*@(b))*(c)d', { windows: true })); + expect_truthy(!isMatch('acd', '@(ab|a*@(b))*(c)d', { windows: true })); + expect_truthy(!isMatch('baaac', '@(ab|a*@(b))*(c)d', { windows: true })); + expect_truthy(!isMatch('c', '@(ab|a*@(b))*(c)d', { windows: true })); + expect_truthy(!isMatch('foo', '@(ab|a*@(b))*(c)d', { windows: true })); + + expect_truthy(!isMatch('aac', '*(@(a))b@(c)', { windows: true })); + }); + + }); + + describe('other', () => { + test('should support backtracking in alternation matches', () => { + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('ffffffo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('fffooofoooooffoofffooofff', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('ffo', '*(fo|foo)', { windows: true })); + expect_truthy(isMatch('fofo', '*(fo|foo)', { windows: true })); + expect_truthy(isMatch('fofoofoofofoo', '*(fo|foo)', { windows: true })); + expect_truthy(isMatch('foo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('foob', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('foobb', '*(fo|foo)', { windows: true })); + expect_truthy(isMatch('foofoofo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('fooofoofofooo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('foooofo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('foooofof', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('foooofofx', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('foooxfooxfoxfooox', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('foooxfooxfxfooox', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('foot', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('foox', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('ofoofo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('ofooofoofofooo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxoo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('ofxoofxo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('oofooofo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('ooo', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('oxfoxoxfox', '*(fo|foo)', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '*(fo|foo)', { windows: true })); + }); + + test('should support exclusions', () => { + expect_truthy(!isMatch('foob', '!(foo)b*', { windows: true })); + expect_truthy(!isMatch('foobb', '!(foo)b*', { windows: true })); + expect_truthy(!isMatch('foo', '!(foo)b*', { windows: true })); + expect_truthy(isMatch('bar', '!(foo)b*', { windows: true })); + expect_truthy(isMatch('baz', '!(foo)b*', { windows: true })); + expect_truthy(!isMatch('foobar', '!(foo)b*', { windows: true })); + + expect_truthy(!isMatch('foo', '*(!(foo))', { windows: true })); + expect_truthy(isMatch('bar', '*(!(foo))', { windows: true })); + expect_truthy(isMatch('baz', '*(!(foo))', { windows: true })); + expect_truthy(isMatch('foobar', '*(!(foo))', { windows: true })); + + // Bash 4.3 says this should match `foo` and `foobar`, which makes no sense + expect_truthy(!isMatch('foo', '!(foo)*', { windows: true })); + expect_truthy(!isMatch('foobar', '!(foo)*', { windows: true })); + expect_truthy(isMatch('bar', '!(foo)*', { windows: true })); + expect_truthy(isMatch('baz', '!(foo)*', { windows: true })); + + expect_truthy(!isMatch('moo.cow', '!(*.*)', { windows: true })); + expect_truthy(isMatch('moo', '!(*.*)', { windows: true })); + expect_truthy(isMatch('cow', '!(*.*)', { windows: true })); + + expect_truthy(isMatch('moo.cow', '!(a*).!(b*)', { windows: true })); + expect_truthy(!isMatch('moo.cow', '!(*).!(*)', { windows: true })); + expect_truthy(!isMatch('moo.cow.moo.cow', '!(*.*).!(*.*)', { windows: true })); + expect_truthy(!isMatch('mad.moo.cow', '!(*.*).!(*.*)', { windows: true })); + + expect_truthy(!isMatch('moo.cow', '!(*.*).', { windows: true })); + expect_truthy(!isMatch('moo', '!(*.*).', { windows: true })); + expect_truthy(!isMatch('cow', '!(*.*).', { windows: true })); + + expect_truthy(!isMatch('moo.cow', '.!(*.*)', { windows: true })); + expect_truthy(!isMatch('moo', '.!(*.*)', { windows: true })); + expect_truthy(!isMatch('cow', '.!(*.*)', { windows: true })); + + expect_truthy(!isMatch('mucca.pazza', 'mu!(*(c))?.pa!(*(z))?', { windows: true })); + + expect_truthy(isMatch('effgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + expect_truthy(isMatch('efgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + expect_truthy(isMatch('egz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + expect_truthy(!isMatch('egz', '@(b+(c)d|e+(f)g?|?(h)i@(j|k))', { windows: true })); + expect_truthy(isMatch('egzefffgzbcdij', '*(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + }); + + test('valid numbers', () => { + expect_truthy(isMatch('/dev/udp/129.22.8.102/45', '/dev/@(tcp|udp)/*/*', { windows: true })); + + expect_truthy(!isMatch('0', '[1-6]([0-9])', { windows: true })); + expect_truthy(isMatch('12', '[1-6]([0-9])', { windows: true })); + expect_truthy(!isMatch('1', '[1-6]([0-9])', { windows: true })); + expect_truthy(!isMatch('12abc', '[1-6]([0-9])', { windows: true })); + expect_truthy(!isMatch('555', '[1-6]([0-9])', { windows: true })); + + expect_truthy(!isMatch('0', '[1-6]*([0-9])', { windows: true })); + expect_truthy(isMatch('12', '[1-6]*([0-9])', { windows: true })); + expect_truthy(isMatch('1', '[1-6]*([0-9])', { windows: true })); + expect_truthy(!isMatch('12abc', '[1-6]*([0-9])', { windows: true })); + expect_truthy(isMatch('555', '[1-6]*([0-9])', { windows: true })); + + expect_truthy(!isMatch('0', '[1-5]*([6-9])', { windows: true })); + expect_truthy(!isMatch('12', '[1-5]*([6-9])', { windows: true })); + expect_truthy(isMatch('1', '[1-5]*([6-9])', { windows: true })); + expect_truthy(!isMatch('12abc', '[1-5]*([6-9])', { windows: true })); + expect_truthy(!isMatch('555', '[1-5]*([6-9])', { windows: true })); + + expect_truthy(isMatch('0', '0|[1-6]*([0-9])', { windows: true })); + expect_truthy(isMatch('12', '0|[1-6]*([0-9])', { windows: true })); + expect_truthy(isMatch('1', '0|[1-6]*([0-9])', { windows: true })); + expect_truthy(!isMatch('12abc', '0|[1-6]*([0-9])', { windows: true })); + expect_truthy(isMatch('555', '0|[1-6]*([0-9])', { windows: true })); + + expect_truthy(isMatch('07', '+([0-7])', { windows: true })); + expect_truthy(isMatch('0377', '+([0-7])', { windows: true })); + expect_truthy(!isMatch('09', '+([0-7])', { windows: true })); + }); + + test('check extended globbing in pattern removal', () => { + expect_truthy(isMatch('a', '+(a|abc)', { windows: true })); + expect_truthy(isMatch('abc', '+(a|abc)', { windows: true })); + + expect_truthy(!isMatch('abcd', '+(a|abc)', { windows: true })); + expect_truthy(!isMatch('abcde', '+(a|abc)', { windows: true })); + expect_truthy(!isMatch('abcedf', '+(a|abc)', { windows: true })); + + expect_truthy(isMatch('f', '+(def|f)', { windows: true })); + expect_truthy(isMatch('def', '+(f|def)', { windows: true })); + + expect_truthy(!isMatch('cdef', '+(f|def)', { windows: true })); + expect_truthy(!isMatch('bcdef', '+(f|def)', { windows: true })); + expect_truthy(!isMatch('abcedf', '+(f|def)', { windows: true })); + + expect_truthy(isMatch('abcd', '*(a|b)cd', { windows: true })); + + expect_truthy(!isMatch('a', '*(a|b)cd', { windows: true })); + expect_truthy(!isMatch('ab', '*(a|b)cd', { windows: true })); + expect_truthy(!isMatch('abc', '*(a|b)cd', { windows: true })); + + expect_truthy(!isMatch('a', '"*(a|b)cd"', { windows: true })); + expect_truthy(!isMatch('ab', '"*(a|b)cd"', { windows: true })); + expect_truthy(!isMatch('abc', '"*(a|b)cd"', { windows: true })); + expect_truthy(!isMatch('abcde', '"*(a|b)cd"', { windows: true })); + expect_truthy(!isMatch('abcdef', '"*(a|b)cd"', { windows: true })); + }); + + test('More tests derived from a bug report (in bash) concerning extended glob patterns following a *', () => { + expect_truthy(isMatch('/dev/udp/129.22.8.102/45', '/dev\\/@(tcp|udp)\\/*\\/*', { windows: true })); + expect_truthy(!isMatch('123abc', '(a+|b)*', { windows: true })); + expect_truthy(isMatch('ab', '(a+|b)*', { windows: true })); + expect_truthy(isMatch('abab', '(a+|b)*', { windows: true })); + expect_truthy(isMatch('abcdef', '(a+|b)*', { windows: true })); + expect_truthy(isMatch('accdef', '(a+|b)*', { windows: true })); + expect_truthy(isMatch('abcfefg', '(a+|b)*', { windows: true })); + expect_truthy(isMatch('abef', '(a+|b)*', { windows: true })); + expect_truthy(isMatch('abcfef', '(a+|b)*', { windows: true })); + expect_truthy(isMatch('abd', '(a+|b)*', { windows: true })); + expect_truthy(isMatch('acd', '(a+|b)*', { windows: true })); + + expect_truthy(!isMatch('123abc', '(a+|b)+', { windows: true })); + expect_truthy(isMatch('ab', '(a+|b)+', { windows: true })); + expect_truthy(isMatch('abab', '(a+|b)+', { windows: true })); + expect_truthy(!isMatch('abcdef', '(a+|b)+', { windows: true })); + expect_truthy(!isMatch('accdef', '(a+|b)+', { windows: true })); + expect_truthy(!isMatch('abcfefg', '(a+|b)+', { windows: true })); + expect_truthy(!isMatch('abef', '(a+|b)+', { windows: true })); + expect_truthy(!isMatch('abcfef', '(a+|b)+', { windows: true })); + expect_truthy(!isMatch('abd', '(a+|b)+', { windows: true })); + expect_truthy(!isMatch('acd', '(a+|b)+', { windows: true })); + + expect_truthy(!isMatch('123abc', 'a(b*(foo|bar))d', { windows: true })); + expect_truthy(!isMatch('ab', 'a(b*(foo|bar))d', { windows: true })); + expect_truthy(!isMatch('abab', 'a(b*(foo|bar))d', { windows: true })); + expect_truthy(!isMatch('abcdef', 'a(b*(foo|bar))d', { windows: true })); + expect_truthy(!isMatch('accdef', 'a(b*(foo|bar))d', { windows: true })); + expect_truthy(!isMatch('abcfefg', 'a(b*(foo|bar))d', { windows: true })); + expect_truthy(!isMatch('abef', 'a(b*(foo|bar))d', { windows: true })); + expect_truthy(!isMatch('abcfef', 'a(b*(foo|bar))d', { windows: true })); + expect_truthy(isMatch('abd', 'a(b*(foo|bar))d', { windows: true })); + expect_truthy(!isMatch('acd', 'a(b*(foo|bar))d', { windows: true })); + + expect_truthy(!isMatch('123abc', 'ab*(e|f)', { windows: true })); + expect_truthy(isMatch('ab', 'ab*(e|f)', { windows: true })); + expect_truthy(!isMatch('abab', 'ab*(e|f)', { windows: true })); + expect_truthy(!isMatch('abcdef', 'ab*(e|f)', { windows: true })); + expect_truthy(!isMatch('accdef', 'ab*(e|f)', { windows: true })); + expect_truthy(!isMatch('abcfefg', 'ab*(e|f)', { windows: true })); + expect_truthy(isMatch('abef', 'ab*(e|f)', { windows: true })); + expect_truthy(!isMatch('abcfef', 'ab*(e|f)', { windows: true })); + expect_truthy(!isMatch('abd', 'ab*(e|f)', { windows: true })); + expect_truthy(!isMatch('acd', 'ab*(e|f)', { windows: true })); + + expect_truthy(!isMatch('123abc', 'ab**(e|f)', { windows: true })); + expect_truthy(isMatch('ab', 'ab**(e|f)', { windows: true })); + expect_truthy(isMatch('abab', 'ab**(e|f)', { windows: true })); + expect_truthy(isMatch('abcdef', 'ab**(e|f)', { windows: true })); + expect_truthy(!isMatch('accdef', 'ab**(e|f)', { windows: true })); + expect_truthy(isMatch('abcfefg', 'ab**(e|f)', { windows: true })); + expect_truthy(isMatch('abef', 'ab**(e|f)', { windows: true })); + expect_truthy(isMatch('abcfef', 'ab**(e|f)', { windows: true })); + expect_truthy(isMatch('abd', 'ab**(e|f)', { windows: true })); + expect_truthy(!isMatch('acd', 'ab**(e|f)', { windows: true })); + + expect_truthy(!isMatch('123abc', 'ab**(e|f)g', { windows: true })); + expect_truthy(!isMatch('ab', 'ab**(e|f)g', { windows: true })); + expect_truthy(!isMatch('abab', 'ab**(e|f)g', { windows: true })); + expect_truthy(!isMatch('abcdef', 'ab**(e|f)g', { windows: true })); + expect_truthy(!isMatch('accdef', 'ab**(e|f)g', { windows: true })); + expect_truthy(isMatch('abcfefg', 'ab**(e|f)g', { windows: true })); + expect_truthy(!isMatch('abef', 'ab**(e|f)g', { windows: true })); + expect_truthy(!isMatch('abcfef', 'ab**(e|f)g', { windows: true })); + expect_truthy(!isMatch('abd', 'ab**(e|f)g', { windows: true })); + expect_truthy(!isMatch('acd', 'ab**(e|f)g', { windows: true })); + + expect_truthy(!isMatch('123abc', 'ab***ef', { windows: true })); + expect_truthy(!isMatch('ab', 'ab***ef', { windows: true })); + expect_truthy(!isMatch('abab', 'ab***ef', { windows: true })); + expect_truthy(isMatch('abcdef', 'ab***ef', { windows: true })); + expect_truthy(!isMatch('accdef', 'ab***ef', { windows: true })); + expect_truthy(!isMatch('abcfefg', 'ab***ef', { windows: true })); + expect_truthy(isMatch('abef', 'ab***ef', { windows: true })); + expect_truthy(isMatch('abcfef', 'ab***ef', { windows: true })); + expect_truthy(!isMatch('abd', 'ab***ef', { windows: true })); + expect_truthy(!isMatch('acd', 'ab***ef', { windows: true })); + + expect_truthy(!isMatch('123abc', 'ab*+(e|f)', { windows: true })); + expect_truthy(!isMatch('ab', 'ab*+(e|f)', { windows: true })); + expect_truthy(!isMatch('abab', 'ab*+(e|f)', { windows: true })); + expect_truthy(isMatch('abcdef', 'ab*+(e|f)', { windows: true })); + expect_truthy(!isMatch('accdef', 'ab*+(e|f)', { windows: true })); + expect_truthy(!isMatch('abcfefg', 'ab*+(e|f)', { windows: true })); + expect_truthy(isMatch('abef', 'ab*+(e|f)', { windows: true })); + expect_truthy(isMatch('abcfef', 'ab*+(e|f)', { windows: true })); + expect_truthy(!isMatch('abd', 'ab*+(e|f)', { windows: true })); + expect_truthy(!isMatch('acd', 'ab*+(e|f)', { windows: true })); + + expect_truthy(!isMatch('123abc', 'ab*d*(e|f)', { windows: true })); + expect_truthy(!isMatch('ab', 'ab*d*(e|f)', { windows: true })); + expect_truthy(!isMatch('abab', 'ab*d*(e|f)', { windows: true })); + expect_truthy(isMatch('abcdef', 'ab*d*(e|f)', { windows: true })); + expect_truthy(!isMatch('accdef', 'ab*d*(e|f)', { windows: true })); + expect_truthy(!isMatch('abcfefg', 'ab*d*(e|f)', { windows: true })); + expect_truthy(!isMatch('abef', 'ab*d*(e|f)', { windows: true })); + expect_truthy(!isMatch('abcfef', 'ab*d*(e|f)', { windows: true })); + expect_truthy(isMatch('abd', 'ab*d*(e|f)', { windows: true })); + expect_truthy(!isMatch('acd', 'ab*d*(e|f)', { windows: true })); + + expect_truthy(!isMatch('123abc', 'ab*d+(e|f)', { windows: true })); + expect_truthy(!isMatch('ab', 'ab*d+(e|f)', { windows: true })); + expect_truthy(!isMatch('abab', 'ab*d+(e|f)', { windows: true })); + expect_truthy(isMatch('abcdef', 'ab*d+(e|f)', { windows: true })); + expect_truthy(!isMatch('accdef', 'ab*d+(e|f)', { windows: true })); + expect_truthy(!isMatch('abcfefg', 'ab*d+(e|f)', { windows: true })); + expect_truthy(!isMatch('abef', 'ab*d+(e|f)', { windows: true })); + expect_truthy(!isMatch('abcfef', 'ab*d+(e|f)', { windows: true })); + expect_truthy(!isMatch('abd', 'ab*d+(e|f)', { windows: true })); + expect_truthy(!isMatch('acd', 'ab*d+(e|f)', { windows: true })); + + expect_truthy(!isMatch('123abc', 'ab?*(e|f)', { windows: true })); + expect_truthy(!isMatch('ab', 'ab?*(e|f)', { windows: true })); + expect_truthy(!isMatch('abab', 'ab?*(e|f)', { windows: true })); + expect_truthy(!isMatch('abcdef', 'ab?*(e|f)', { windows: true })); + expect_truthy(!isMatch('accdef', 'ab?*(e|f)', { windows: true })); + expect_truthy(!isMatch('abcfefg', 'ab?*(e|f)', { windows: true })); + expect_truthy(isMatch('abef', 'ab?*(e|f)', { windows: true })); + expect_truthy(isMatch('abcfef', 'ab?*(e|f)', { windows: true })); + expect_truthy(isMatch('abd', 'ab?*(e|f)', { windows: true })); + expect_truthy(!isMatch('acd', 'ab?*(e|f)', { windows: true })); + }); + + test('bug in all versions up to and including bash-2.05b', () => { + expect_truthy(isMatch('123abc', '*?(a)bc', { windows: true })); + }); + + test('should work with character classes', () => { + const opts = { posix: true }; + expect_truthy(isMatch('a.b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a,b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a:b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a-b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a;b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a b', 'a[^[:alnum:]]b', opts)); + expect_truthy(isMatch('a_b', 'a[^[:alnum:]]b', opts)); + + expect_truthy(isMatch('a.b', 'a[-.,:\\;\\ _]b', { windows: true })); + expect_truthy(isMatch('a,b', 'a[-.,:\\;\\ _]b', { windows: true })); + expect_truthy(isMatch('a:b', 'a[-.,:\\;\\ _]b', { windows: true })); + expect_truthy(isMatch('a-b', 'a[-.,:\\;\\ _]b', { windows: true })); + expect_truthy(isMatch('a;b', 'a[-.,:\\;\\ _]b', { windows: true })); + expect_truthy(isMatch('a b', 'a[-.,:\\;\\ _]b', { windows: true })); + expect_truthy(isMatch('a_b', 'a[-.,:\\;\\ _]b', { windows: true })); + + expect_truthy(isMatch('a.b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a,b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a:b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a-b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a;b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a b', 'a@([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a_b', 'a@([^[:alnum:]])b', opts)); + + expect_truthy(isMatch('a.b', 'a@([-.,:; _])b', { windows: true })); + expect_truthy(isMatch('a,b', 'a@([-.,:; _])b', { windows: true })); + expect_truthy(isMatch('a:b', 'a@([-.,:; _])b', { windows: true })); + expect_truthy(isMatch('a-b', 'a@([-.,:; _])b', { windows: true })); + expect_truthy(isMatch('a;b', 'a@([-.,:; _])b', { windows: true })); + expect_truthy(isMatch('a b', 'a@([-.,:; _])b', { windows: true })); + expect_truthy(isMatch('a_b', 'a@([-.,:; _])b', { windows: true })); + + expect_truthy(isMatch('a.b', 'a@([.])b', { windows: true })); + expect_truthy(!isMatch('a,b', 'a@([.])b', { windows: true })); + expect_truthy(!isMatch('a:b', 'a@([.])b', { windows: true })); + expect_truthy(!isMatch('a-b', 'a@([.])b', { windows: true })); + expect_truthy(!isMatch('a;b', 'a@([.])b', { windows: true })); + expect_truthy(!isMatch('a b', 'a@([.])b', { windows: true })); + expect_truthy(!isMatch('a_b', 'a@([.])b', { windows: true })); + + expect_truthy(!isMatch('a.b', 'a@([^.])b', { windows: true })); + expect_truthy(isMatch('a,b', 'a@([^.])b', { windows: true })); + expect_truthy(isMatch('a:b', 'a@([^.])b', { windows: true })); + expect_truthy(isMatch('a-b', 'a@([^.])b', { windows: true })); + expect_truthy(isMatch('a;b', 'a@([^.])b', { windows: true })); + expect_truthy(isMatch('a b', 'a@([^.])b', { windows: true })); + expect_truthy(isMatch('a_b', 'a@([^.])b', { windows: true })); + + expect_truthy(isMatch('a.b', 'a@([^x])b', { windows: true })); + expect_truthy(isMatch('a,b', 'a@([^x])b', { windows: true })); + expect_truthy(isMatch('a:b', 'a@([^x])b', { windows: true })); + expect_truthy(isMatch('a-b', 'a@([^x])b', { windows: true })); + expect_truthy(isMatch('a;b', 'a@([^x])b', { windows: true })); + expect_truthy(isMatch('a b', 'a@([^x])b', { windows: true })); + expect_truthy(isMatch('a_b', 'a@([^x])b', { windows: true })); + + expect_truthy(isMatch('a.b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a,b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a:b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a-b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a;b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a b', 'a+([^[:alnum:]])b', opts)); + expect_truthy(isMatch('a_b', 'a+([^[:alnum:]])b', opts)); + + expect_truthy(isMatch('a.b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a,b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a:b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a-b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a;b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a b', 'a@(.|[^[:alnum:]])b', opts)); + expect_truthy(isMatch('a_b', 'a@(.|[^[:alnum:]])b', opts)); + }); + + test('should support POSIX character classes in extglobs', () => { + const opts = { posix: true }; + expect_truthy(isMatch('a.c', '+([[:alpha:].])', opts)); + expect_truthy(isMatch('a.c', '+([[:alpha:].])+([[:alpha:].])', opts)); + expect_truthy(isMatch('a.c', '*([[:alpha:].])', opts)); + expect_truthy(isMatch('a.c', '*([[:alpha:].])*([[:alpha:].])', opts)); + expect_truthy(isMatch('a.c', '?([[:alpha:].])?([[:alpha:].])?([[:alpha:].])', opts)); + expect_truthy(isMatch('a.c', '@([[:alpha:].])@([[:alpha:].])@([[:alpha:].])', opts)); + expect_truthy(!isMatch('.', '!(\\.)', opts)); + expect_truthy(!isMatch('.', '!([[:alpha:].])', opts)); + expect_truthy(isMatch('.', '?([[:alpha:].])', opts)); + expect_truthy(isMatch('.', '@([[:alpha:].])', opts)); + }); + + // ported from http://www.bashcookbook.com/bashinfo/source/bash-4.3/tests/extglob2.tests + test('should pass extglob2 tests', () => { + expect_truthy(!isMatch('baaac', '*(@(a))a@(c)', { windows: true })); + expect_truthy(!isMatch('c', '*(@(a))a@(c)', { windows: true })); + expect_truthy(!isMatch('egz', '@(b+(c)d|e+(f)g?|?(h)i@(j|k))', { windows: true })); + expect_truthy(!isMatch('foooofof', '*(f+(o))', { windows: true })); + expect_truthy(!isMatch('foooofofx', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('foooxfooxofoxfooox', '*(f*(o)x)', { windows: true })); + expect_truthy(!isMatch('ofooofoofofooo', '*(f*(o))', { windows: true })); + expect_truthy(!isMatch('ofoooxoofxoofoooxoofxofo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(!isMatch('oxfoxfox', '*(oxf+(ox))', { windows: true })); + expect_truthy(!isMatch('xfoooofof', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('aaac', '*(@(a))a@(c)', { windows: true })); + expect_truthy(isMatch('aac', '*(@(a))a@(c)', { windows: true })); + expect_truthy(isMatch('abbcd', '@(ab|a*(b))*(c)d', { windows: true })); + expect_truthy(isMatch('abcd', '?@(a|b)*@(c)d', { windows: true })); + expect_truthy(isMatch('abcd', '@(ab|a*@(b))*(c)d', { windows: true })); + expect_truthy(isMatch('ac', '*(@(a))a@(c)', { windows: true })); + expect_truthy(isMatch('acd', '@(ab|a*(b))*(c)d', { windows: true })); + expect_truthy(isMatch('effgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + expect_truthy(isMatch('efgz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + expect_truthy(isMatch('egz', '@(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + expect_truthy(isMatch('egzefffgzbcdij', '*(b+(c)d|e*(f)g?|?(h)i@(j|k))', { windows: true })); + expect_truthy(isMatch('fffooofoooooffoofffooofff', '*(*(f)*(o))', { windows: true })); + expect_truthy(isMatch('ffo', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('fofo', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('foofoofo', '@(foo|f|fo)*(f|of+(o))', { windows: true })); + expect_truthy(isMatch('fooofoofofooo', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('foooofo', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('foooofof', '*(f*(o))', { windows: true })); + expect_truthy(isMatch('foooxfooxfoxfooox', '*(f*(o)x)', { windows: true })); + expect_truthy(isMatch('foooxfooxfxfooox', '*(f*(o)x)', { windows: true })); + expect_truthy(isMatch('ofoofo', '*(of+(o))', { windows: true })); + expect_truthy(isMatch('ofoofo', '*(of+(o)|f)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxoo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(isMatch('ofoooxoofxoofoooxoofxooofxofxo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(isMatch('ofxoofxo', '*(*(of*(o)x)o)', { windows: true })); + expect_truthy(isMatch('oofooofo', '*(of|oof+(o))', { windows: true })); + expect_truthy(isMatch('oxfoxoxfox', '*(oxf+(ox))', { windows: true })); + }); + + test('should support exclusions', () => { + expect_truthy(!isMatch('f', '!(f)', { windows: true })); + expect_truthy(!isMatch('f', '*(!(f))', { windows: true })); + expect_truthy(!isMatch('f', '+(!(f))', { windows: true })); + expect_truthy(!isMatch('foo', '!(foo)', { windows: true })); + expect_truthy(!isMatch('foob', '!(foo)b*', { windows: true })); + expect_truthy(!isMatch('mad.moo.cow', '!(*.*).!(*.*)', { windows: true })); + expect_truthy(!isMatch('mucca.pazza', 'mu!(*(c))?.pa!(*(z))?', { windows: true })); + expect_truthy(!isMatch('zoot', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('fff', '!(f)', { windows: true })); + expect_truthy(isMatch('fff', '*(!(f))', { windows: true })); + expect_truthy(isMatch('fff', '+(!(f))', { windows: true })); + expect_truthy(isMatch('foo', '!(f)', { windows: true })); + expect_truthy(isMatch('foo', '!(x)', { windows: true })); + expect_truthy(isMatch('foo', '!(x)*', { windows: true })); + expect_truthy(isMatch('foo', '*(!(f))', { windows: true })); + expect_truthy(isMatch('foo', '+(!(f))', { windows: true })); + expect_truthy(isMatch('foobar', '!(foo)', { windows: true })); + expect_truthy(isMatch('foot', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('foox', '@(!(z*)|*x)', { windows: true })); + expect_truthy(isMatch('ooo', '!(f)', { windows: true })); + expect_truthy(isMatch('ooo', '*(!(f))', { windows: true })); + expect_truthy(isMatch('ooo', '+(!(f))', { windows: true })); + expect_truthy(isMatch('zoox', '@(!(z*)|*x)', { windows: true })); + }); + }); +}); + diff --git a/packages/node-utils/test/picomatch/extglobs.test.ts b/packages/node-utils/test/picomatch/extglobs.test.ts new file mode 100644 index 0000000..032f026 --- /dev/null +++ b/packages/node-utils/test/picomatch/extglobs.test.ts @@ -0,0 +1,801 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch, makeRe } = picomatch; + +/** + * Ported from Bash 4.3 and 4.4 unit tests + */ + +describe('extglobs', () => { + test('should throw on imbalanced sets when `optionsBrackets` is true', () => { + const opts = { strictBrackets: true }; + expect_throws(() => makeRe('a(b', opts), /Missing closing: "\)"/i); + expect_throws(() => makeRe('a)b', opts), /Missing opening: "\("/i); + }); + + test('should escape special characters immediately following opening parens', () => { + expect_truthy(isMatch('cbz', 'c!(.)z')); + expect_truthy(!isMatch('cbz', 'c!(*)z')); + expect_truthy(isMatch('cccz', 'c!(b*)z')); + expect_truthy(isMatch('cbz', 'c!(+)z')); + expect_truthy(isMatch('cbz', 'c!(?)z')); + expect_truthy(isMatch('cbz', 'c!(@)z')); + }); + + test('should not convert capture groups to extglobs', () => { + expect_equal(makeRe('c!(?:foo)?z').source, '^(?:c!(?:foo)?z)$'); + expect_truthy(!isMatch('c/z', 'c!(?:foo)?z')); + expect_truthy(isMatch('c!fooz', 'c!(?:foo)?z')); + expect_truthy(isMatch('c!z', 'c!(?:foo)?z')); + }); + + describe('negation', () => { + test('should support negation extglobs as the entire pattern', () => { + expect_truthy(!isMatch('abc', '!(abc)')); + expect_truthy(!isMatch('a', '!(a)')); + expect_truthy(isMatch('aa', '!(a)')); + expect_truthy(isMatch('b', '!(a)')); + }); + + test('should support negation extglobs as part of a pattern', () => { + expect_truthy(isMatch('aac', 'a!(b)c')); + expect_truthy(!isMatch('abc', 'a!(b)c')); + expect_truthy(isMatch('acc', 'a!(b)c')); + expect_truthy(isMatch('abz', 'a!(z)')); + expect_truthy(!isMatch('az', 'a!(z)')); + }); + + test('should support excluding dots with negation extglobs', () => { + expect_truthy(!isMatch('a.', 'a!(.)')); + expect_truthy(!isMatch('.a', '!(.)a')); + expect_truthy(!isMatch('a.c', 'a!(.)c')); + expect_truthy(isMatch('abc', 'a!(.)c')); + }); + + // See https://github.com/micromatch/picomatch/issues/83 + test('should support stars in negation extglobs', () => { + expect_truthy(!isMatch('/file.d.ts', '/!(*.d).ts')); + expect_truthy(isMatch('/file.ts', '/!(*.d).ts')); + expect_truthy(isMatch('/file.something.ts', '/!(*.d).ts')); + expect_truthy(isMatch('/file.d.something.ts', '/!(*.d).ts')); + expect_truthy(isMatch('/file.dhello.ts', '/!(*.d).ts')); + + expect_truthy(!isMatch('/file.d.ts', '**/!(*.d).ts')); + expect_truthy(isMatch('/file.ts', '**/!(*.d).ts')); + expect_truthy(isMatch('/file.something.ts', '**/!(*.d).ts')); + expect_truthy(isMatch('/file.d.something.ts', '**/!(*.d).ts')); + expect_truthy(isMatch('/file.dhello.ts', '**/!(*.d).ts')); + }); + + // See https://github.com/micromatch/picomatch/issues/93 + test('should support stars in negation extglobs with expression after closing parenthesis', () => { + // Nested expression after closing parenthesis + expect_truthy(!isMatch('/file.d.ts', '/!(*.d).{ts,tsx}')); + expect_truthy(isMatch('/file.ts', '/!(*.d).{ts,tsx}')); + expect_truthy(isMatch('/file.something.ts', '/!(*.d).{ts,tsx}')); + expect_truthy(isMatch('/file.d.something.ts', '/!(*.d).{ts,tsx}')); + expect_truthy(isMatch('/file.dhello.ts', '/!(*.d).{ts,tsx}')); + + // Extglob after closing parenthesis + expect_truthy(!isMatch('/file.d.ts', '/!(*.d).@(ts)')); + expect_truthy(isMatch('/file.ts', '/!(*.d).@(ts)')); + expect_truthy(isMatch('/file.something.ts', '/!(*.d).@(ts)')); + expect_truthy(isMatch('/file.d.something.ts', '/!(*.d).@(ts)')); + expect_truthy(isMatch('/file.dhello.ts', '/!(*.d).@(ts)')); + }); + + test('should support negation extglobs in patterns with slashes', () => { + expect_truthy(!isMatch('foo/abc', 'foo/!(abc)')); + expect_truthy(isMatch('foo/bar', 'foo/!(abc)')); + + expect_truthy(!isMatch('a/z', 'a/!(z)')); + expect_truthy(isMatch('a/b', 'a/!(z)')); + + expect_truthy(!isMatch('c/z/v', 'c/!(z)/v')); + expect_truthy(isMatch('c/a/v', 'c/!(z)/v')); + + expect_truthy(isMatch('a/a', '!(b/a)')); + expect_truthy(!isMatch('b/a', '!(b/a)')); + + expect_truthy(!isMatch('foo/bar', '!(!(foo))*')); + expect_truthy(isMatch('a/a', '!(b/a)')); + expect_truthy(!isMatch('b/a', '!(b/a)')); + + expect_truthy(isMatch('a/a', '(!(b/a))')); + expect_truthy(isMatch('a/a', '!((b/a))')); + expect_truthy(!isMatch('b/a', '!((b/a))')); + + expect_truthy(!isMatch('a/a', '(!(?:b/a))')); + expect_truthy(!isMatch('b/a', '!((?:b/a))')); + + expect_truthy(isMatch('a/a', '!(b/(a))')); + expect_truthy(!isMatch('b/a', '!(b/(a))')); + + expect_truthy(isMatch('a/a', '!(b/a)')); + expect_truthy(!isMatch('b/a', '!(b/a)')); + }); + + test('should not match slashes with extglobs that do not have slashes', () => { + expect_truthy(!isMatch('c/z', 'c!(z)')); + expect_truthy(!isMatch('c/z', 'c!(z)z')); + expect_truthy(!isMatch('c/z', 'c!(.)z')); + expect_truthy(!isMatch('c/z', 'c!(*)z')); + expect_truthy(!isMatch('c/z', 'c!(+)z')); + expect_truthy(!isMatch('c/z', 'c!(?)z')); + expect_truthy(!isMatch('c/z', 'c!(@)z')); + }); + + test('should support matching slashes with extglobs that have slashes', () => { + expect_truthy(!isMatch('c/z', 'a!(z)')); + expect_truthy(!isMatch('c/z', 'c!(.)z')); + expect_truthy(!isMatch('c/z', 'c!(/)z')); + expect_truthy(!isMatch('c/z', 'c!(/z)z')); + expect_truthy(!isMatch('c/b', 'c!(/z)z')); + expect_truthy(isMatch('c/b/z', 'c!(/z)z')); + }); + + test('should support negation extglobs following !', () => { + expect_truthy(isMatch('abc', '!!(abc)')); + expect_truthy(!isMatch('abc', '!!!(abc)')); + expect_truthy(isMatch('abc', '!!!!(abc)')); + expect_truthy(!isMatch('abc', '!!!!!(abc)')); + expect_truthy(isMatch('abc', '!!!!!!(abc)')); + expect_truthy(!isMatch('abc', '!!!!!!!(abc)')); + expect_truthy(isMatch('abc', '!!!!!!!!(abc)')); + }); + + test('should support nested negation extglobs', () => { + expect_truthy(isMatch('abc', '!(!(abc))')); + expect_truthy(!isMatch('abc', '!(!(!(abc)))')); + expect_truthy(isMatch('abc', '!(!(!(!(abc))))')); + expect_truthy(!isMatch('abc', '!(!(!(!(!(abc)))))')); + expect_truthy(isMatch('abc', '!(!(!(!(!(!(abc))))))')); + expect_truthy(!isMatch('abc', '!(!(!(!(!(!(!(abc)))))))')); + expect_truthy(isMatch('abc', '!(!(!(!(!(!(!(!(abc))))))))')); + + expect_truthy(isMatch('foo/abc', 'foo/!(!(abc))')); + expect_truthy(!isMatch('foo/abc', 'foo/!(!(!(abc)))')); + expect_truthy(isMatch('foo/abc', 'foo/!(!(!(!(abc))))')); + expect_truthy(!isMatch('foo/abc', 'foo/!(!(!(!(!(abc)))))')); + expect_truthy(isMatch('foo/abc', 'foo/!(!(!(!(!(!(abc))))))')); + expect_truthy(!isMatch('foo/abc', 'foo/!(!(!(!(!(!(!(abc)))))))')); + expect_truthy(isMatch('foo/abc', 'foo/!(!(!(!(!(!(!(!(abc))))))))')); + }); + + test('should support multiple !(...) extglobs in a pattern', () => { + expect_truthy(!isMatch('moo.cow', '!(moo).!(cow)')); + expect_truthy(!isMatch('foo.cow', '!(moo).!(cow)')); + expect_truthy(!isMatch('moo.bar', '!(moo).!(cow)')); + expect_truthy(isMatch('foo.bar', '!(moo).!(cow)')); + + expect_truthy(!isMatch('a ', '@(!(a) )*')); + expect_truthy(!isMatch('a b', '@(!(a) )*')); + expect_truthy(!isMatch('a b', '@(!(a) )*')); + expect_truthy(!isMatch('a ', '@(!(a) )*')); + expect_truthy(!isMatch('a ', '@(!(a) )*')); + expect_truthy(!isMatch('a', '@(!(a) )*')); + expect_truthy(!isMatch('aa', '@(!(a) )*')); + expect_truthy(!isMatch('b', '@(!(a) )*')); + expect_truthy(!isMatch('bb', '@(!(a) )*')); + expect_truthy(isMatch(' a ', '@(!(a) )*')); + expect_truthy(isMatch('b ', '@(!(a) )*')); + expect_truthy(isMatch('b ', '@(!(a) )*')); + + expect_truthy(!isMatch('c/z', 'a*!(z)')); + expect_truthy(isMatch('abz', 'a*!(z)')); + expect_truthy(isMatch('az', 'a*!(z)')); + + expect_truthy(!isMatch('a', '!(a*)')); + expect_truthy(!isMatch('aa', '!(a*)')); + expect_truthy(!isMatch('ab', '!(a*)')); + expect_truthy(isMatch('b', '!(a*)')); + + expect_truthy(!isMatch('a', '!(*a*)')); + expect_truthy(!isMatch('aa', '!(*a*)')); + expect_truthy(!isMatch('ab', '!(*a*)')); + expect_truthy(!isMatch('ac', '!(*a*)')); + expect_truthy(isMatch('b', '!(*a*)')); + + expect_truthy(!isMatch('a', '!(*a)')); + expect_truthy(!isMatch('aa', '!(*a)')); + expect_truthy(!isMatch('bba', '!(*a)')); + expect_truthy(isMatch('ab', '!(*a)')); + expect_truthy(isMatch('ac', '!(*a)')); + expect_truthy(isMatch('b', '!(*a)')); + + expect_truthy(!isMatch('a', '!(*a)*')); + expect_truthy(!isMatch('aa', '!(*a)*')); + expect_truthy(!isMatch('bba', '!(*a)*')); + expect_truthy(!isMatch('ab', '!(*a)*')); + expect_truthy(!isMatch('ac', '!(*a)*')); + expect_truthy(isMatch('b', '!(*a)*')); + + expect_truthy(!isMatch('a', '!(a)*')); + expect_truthy(!isMatch('abb', '!(a)*')); + expect_truthy(isMatch('ba', '!(a)*')); + + expect_truthy(isMatch('aa', 'a!(b)*')); + expect_truthy(!isMatch('ab', 'a!(b)*')); + expect_truthy(!isMatch('aba', 'a!(b)*')); + expect_truthy(isMatch('ac', 'a!(b)*')); + }); + + test('should multiple nested negation extglobs', () => { + expect_truthy(isMatch('moo.cow', '!(!(moo)).!(!(cow))')); + }); + + test('should support logical-or inside negation !(...) extglobs', () => { + expect_truthy(!isMatch('ac', '!(a|b)c')); + expect_truthy(!isMatch('bc', '!(a|b)c')); + expect_truthy(isMatch('cc', '!(a|b)c')); + }); + + test('should support multiple logical-ors negation extglobs', () => { + expect_truthy(!isMatch('ac.d', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('bc.d', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('cc.d', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('ac.e', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('bc.e', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('cc.e', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('ac.f', '!(a|b)c.!(d|e)')); + expect_truthy(!isMatch('bc.f', '!(a|b)c.!(d|e)')); + expect_truthy(isMatch('cc.f', '!(a|b)c.!(d|e)')); + expect_truthy(isMatch('dc.g', '!(a|b)c.!(d|e)')); + }); + + test('should support nested logical-ors inside negation extglobs', () => { + expect_truthy(isMatch('ac.d', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('bc.d', '!(!(a|b)c.!(d|e))')); + expect_truthy(!isMatch('cc.d', '!(a|b)c.!(d|e)')); + expect_truthy(isMatch('cc.d', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('cc.d', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('ac.e', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('bc.e', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('cc.e', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('ac.f', '!(!(a|b)c.!(d|e))')); + expect_truthy(isMatch('bc.f', '!(!(a|b)c.!(d|e))')); + expect_truthy(!isMatch('cc.f', '!(!(a|b)c.!(d|e))')); + expect_truthy(!isMatch('dc.g', '!(!(a|b)c.!(d|e))')); + }); + }); + + describe('file extensions', () => { + test('should support matching file extensions with @(...)', () => { + expect_truthy(!isMatch('.md', '@(a|b).md')); + expect_truthy(!isMatch('a.js', '@(a|b).md')); + expect_truthy(!isMatch('c.md', '@(a|b).md')); + expect_truthy(isMatch('a.md', '@(a|b).md')); + expect_truthy(isMatch('b.md', '@(a|b).md')); + }); + + test('should support matching file extensions with +(...)', () => { + expect_truthy(!isMatch('.md', '+(a|b).md')); + expect_truthy(!isMatch('a.js', '+(a|b).md')); + expect_truthy(!isMatch('c.md', '+(a|b).md')); + expect_truthy(isMatch('a.md', '+(a|b).md')); + expect_truthy(isMatch('aa.md', '+(a|b).md')); + expect_truthy(isMatch('ab.md', '+(a|b).md')); + expect_truthy(isMatch('b.md', '+(a|b).md')); + expect_truthy(isMatch('bb.md', '+(a|b).md')); + }); + + test('should support matching file extensions with *(...)', () => { + expect_truthy(!isMatch('a.js', '*(a|b).md')); + expect_truthy(!isMatch('c.md', '*(a|b).md')); + expect_truthy(isMatch('.md', '*(a|b).md')); + expect_truthy(isMatch('a.md', '*(a|b).md')); + expect_truthy(isMatch('aa.md', '*(a|b).md')); + expect_truthy(isMatch('ab.md', '*(a|b).md')); + expect_truthy(isMatch('b.md', '*(a|b).md')); + expect_truthy(isMatch('bb.md', '*(a|b).md')); + }); + + test('should support matching file extensions with ?(...)', () => { + expect_truthy(!isMatch('a.js', '?(a|b).md')); + expect_truthy(!isMatch('bb.md', '?(a|b).md')); + expect_truthy(!isMatch('c.md', '?(a|b).md')); + expect_truthy(isMatch('.md', '?(a|b).md')); + expect_truthy(isMatch('a.md', '?(a|ab|b).md')); + expect_truthy(isMatch('a.md', '?(a|b).md')); + expect_truthy(isMatch('aa.md', '?(a|aa|b).md')); + expect_truthy(isMatch('ab.md', '?(a|ab|b).md')); + expect_truthy(isMatch('b.md', '?(a|ab|b).md')); + + // See https://github.com/micromatch/micromatch/issues/186 + expect_truthy(isMatch('ab', '+(a)?(b)')); + expect_truthy(isMatch('aab', '+(a)?(b)')); + expect_truthy(isMatch('aa', '+(a)?(b)')); + expect_truthy(isMatch('a', '+(a)?(b)')); + }); + }); + + describe('statechar', () => { + test('should support ?(...) extglobs ending with statechar', () => { + expect_truthy(!isMatch('ax', 'a?(b*)')); + expect_truthy(isMatch('ax', '?(a*|b)')); + }); + + test('should support *(...) extglobs ending with statechar', () => { + expect_truthy(!isMatch('ax', 'a*(b*)')); + expect_truthy(isMatch('ax', '*(a*|b)')); + }); + + test('should support @(...) extglobs ending with statechar', () => { + expect_truthy(!isMatch('ax', 'a@(b*)')); + expect_truthy(isMatch('ax', '@(a*|b)')); + }); + + test('should support ?(...) extglobs ending with statechar', () => { + expect_truthy(!isMatch('ax', 'a?(b*)')); + expect_truthy(isMatch('ax', '?(a*|b)')); + }); + + test('should support !(...) extglobs ending with statechar', () => { + expect_truthy(isMatch('ax', 'a!(b*)')); + expect_truthy(!isMatch('ax', '!(a*|b)')); + }); + }); + + test('should match nested directories with negation extglobs', () => { + expect_truthy(isMatch('a', '!(a/**)')); + expect_truthy(!isMatch('a/', '!(a/**)')); + expect_truthy(!isMatch('a/b', '!(a/**)')); + expect_truthy(!isMatch('a/b/c', '!(a/**)')); + expect_truthy(isMatch('b', '!(a/**)')); + expect_truthy(isMatch('b/c', '!(a/**)')); + + expect_truthy(isMatch('a/a', 'a/!(b*)')); + expect_truthy(!isMatch('a/b', 'a/!(b*)')); + expect_truthy(!isMatch('a/b/c', 'a/!(b/*)')); + expect_truthy(!isMatch('a/b/c', 'a/!(b*)')); + expect_truthy(isMatch('a/c', 'a/!(b*)')); + + expect_truthy(isMatch('a/a/', 'a/!(b*)/**')); + expect_truthy(isMatch('a/a', 'a/!(b*)')); + expect_truthy(isMatch('a/a', 'a/!(b*)/**')); + expect_truthy(!isMatch('a/b', 'a/!(b*)/**')); + expect_truthy(!isMatch('a/b/c', 'a/!(b*)/**')); + expect_truthy(isMatch('a/c', 'a/!(b*)/**')); + expect_truthy(isMatch('a/c', 'a/!(b*)')); + expect_truthy(isMatch('a/c/', 'a/!(b*)/**')); + }); + + test('should support *(...)', () => { + expect_truthy(isMatch('a', 'a*(z)')); + expect_truthy(isMatch('az', 'a*(z)')); + expect_truthy(isMatch('azz', 'a*(z)')); + expect_truthy(isMatch('azzz', 'a*(z)')); + expect_truthy(!isMatch('abz', 'a*(z)')); + expect_truthy(!isMatch('cz', 'a*(z)')); + + expect_truthy(!isMatch('a/a', '*(b/a)')); + expect_truthy(!isMatch('a/b', '*(b/a)')); + expect_truthy(!isMatch('a/c', '*(b/a)')); + expect_truthy(isMatch('b/a', '*(b/a)')); + expect_truthy(!isMatch('b/b', '*(b/a)')); + expect_truthy(!isMatch('b/c', '*(b/a)')); + + expect_truthy(!isMatch('cz', 'a**(z)')); + expect_truthy(isMatch('abz', 'a**(z)')); + expect_truthy(isMatch('az', 'a**(z)')); + + expect_truthy(!isMatch('c/z/v', '*(z)')); + expect_truthy(isMatch('z', '*(z)')); + expect_truthy(!isMatch('zf', '*(z)')); + expect_truthy(!isMatch('fz', '*(z)')); + + expect_truthy(!isMatch('c/a/v', 'c/*(z)/v')); + expect_truthy(isMatch('c/z/v', 'c/*(z)/v')); + + expect_truthy(!isMatch('a.md.js', '*.*(js).js')); + expect_truthy(isMatch('a.js.js', '*.*(js).js')); + }); + + test('should support +(...) extglobs', () => { + expect_truthy(!isMatch('a', 'a+(z)')); + expect_truthy(isMatch('az', 'a+(z)')); + expect_truthy(!isMatch('cz', 'a+(z)')); + expect_truthy(!isMatch('abz', 'a+(z)')); + expect_truthy(!isMatch('a+z', 'a+(z)')); + expect_truthy(isMatch('a+z', 'a++(z)')); + expect_truthy(!isMatch('c+z', 'a+(z)')); + expect_truthy(!isMatch('a+bz', 'a+(z)')); + expect_truthy(!isMatch('az', '+(z)')); + expect_truthy(!isMatch('cz', '+(z)')); + expect_truthy(!isMatch('abz', '+(z)')); + expect_truthy(!isMatch('fz', '+(z)')); + expect_truthy(isMatch('z', '+(z)')); + expect_truthy(isMatch('zz', '+(z)')); + expect_truthy(isMatch('c/z/v', 'c/+(z)/v')); + expect_truthy(isMatch('c/zz/v', 'c/+(z)/v')); + expect_truthy(!isMatch('c/a/v', 'c/+(z)/v')); + }); + + test('should support ?(...) extglobs', () => { + expect_truthy(isMatch('a?z', 'a??(z)')); + expect_truthy(isMatch('a.z', 'a??(z)')); + expect_truthy(!isMatch('a/z', 'a??(z)')); + expect_truthy(isMatch('a?', 'a??(z)')); + expect_truthy(isMatch('ab', 'a??(z)')); + expect_truthy(!isMatch('a/', 'a??(z)')); + + expect_truthy(!isMatch('a?z', 'a?(z)')); + expect_truthy(!isMatch('abz', 'a?(z)')); + expect_truthy(!isMatch('z', 'a?(z)')); + expect_truthy(isMatch('a', 'a?(z)')); + expect_truthy(isMatch('az', 'a?(z)')); + + expect_truthy(!isMatch('abz', '?(z)')); + expect_truthy(!isMatch('az', '?(z)')); + expect_truthy(!isMatch('cz', '?(z)')); + expect_truthy(!isMatch('fz', '?(z)')); + expect_truthy(!isMatch('zz', '?(z)')); + expect_truthy(isMatch('z', '?(z)')); + + expect_truthy(!isMatch('c/a/v', 'c/?(z)/v')); + expect_truthy(!isMatch('c/zz/v', 'c/?(z)/v')); + expect_truthy(isMatch('c/z/v', 'c/?(z)/v')); + }); + + test('should support @(...) extglobs', () => { + expect_truthy(isMatch('c/z/v', 'c/@(z)/v')); + expect_truthy(!isMatch('c/a/v', 'c/@(z)/v')); + expect_truthy(isMatch('moo.cow', '@(*.*)')); + + expect_truthy(!isMatch('cz', 'a*@(z)')); + expect_truthy(isMatch('abz', 'a*@(z)')); + expect_truthy(isMatch('az', 'a*@(z)')); + + expect_truthy(!isMatch('cz', 'a@(z)')); + expect_truthy(!isMatch('abz', 'a@(z)')); + expect_truthy(isMatch('az', 'a@(z)')); + }); + + test('should match exactly one of the given pattern:', () => { + expect_truthy(!isMatch('aa.aa', '(b|a).(a)')); + expect_truthy(!isMatch('a.bb', '(b|a).(a)')); + expect_truthy(!isMatch('a.aa.a', '(b|a).(a)')); + expect_truthy(!isMatch('cc.a', '(b|a).(a)')); + expect_truthy(isMatch('a.a', '(b|a).(a)')); + expect_truthy(!isMatch('c.a', '(b|a).(a)')); + expect_truthy(!isMatch('dd.aa.d', '(b|a).(a)')); + expect_truthy(isMatch('b.a', '(b|a).(a)')); + + expect_truthy(!isMatch('aa.aa', '@(b|a).@(a)')); + expect_truthy(!isMatch('a.bb', '@(b|a).@(a)')); + expect_truthy(!isMatch('a.aa.a', '@(b|a).@(a)')); + expect_truthy(!isMatch('cc.a', '@(b|a).@(a)')); + expect_truthy(isMatch('a.a', '@(b|a).@(a)')); + expect_truthy(!isMatch('c.a', '@(b|a).@(a)')); + expect_truthy(!isMatch('dd.aa.d', '@(b|a).@(a)')); + expect_truthy(isMatch('b.a', '@(b|a).@(a)')); + }); + + test('should pass tests from rosenblatt\'s korn shell book', () => { + // This one is the only difference, since picomatch does not match empty strings. + expect_truthy(!isMatch('', '*(0|1|3|5|7|9)')); + + expect_truthy(isMatch('137577991', '*(0|1|3|5|7|9)')); + expect_truthy(!isMatch('2468', '*(0|1|3|5|7|9)')); + + expect_truthy(isMatch('file.c', '*.c?(c)')); + expect_truthy(!isMatch('file.C', '*.c?(c)')); + expect_truthy(isMatch('file.cc', '*.c?(c)')); + expect_truthy(!isMatch('file.ccc', '*.c?(c)')); + + expect_truthy(isMatch('parse.y', '!(*.c|*.h|Makefile.in|config*|README)')); + expect_truthy(!isMatch('shell.c', '!(*.c|*.h|Makefile.in|config*|README)')); + expect_truthy(isMatch('Makefile', '!(*.c|*.h|Makefile.in|config*|README)')); + expect_truthy(!isMatch('Makefile.in', '!(*.c|*.h|Makefile.in|config*|README)')); + + expect_truthy(!isMatch('VMS.FILE;', '*\\;[1-9]*([0-9])')); + expect_truthy(!isMatch('VMS.FILE;0', '*\\;[1-9]*([0-9])')); + expect_truthy(isMatch('VMS.FILE;1', '*\\;[1-9]*([0-9])')); + expect_truthy(isMatch('VMS.FILE;139', '*\\;[1-9]*([0-9])')); + expect_truthy(!isMatch('VMS.FILE;1N', '*\\;[1-9]*([0-9])')); + }); + + test('tests derived from the pd-ksh test suite', () => { + expect_truthy(isMatch('abcx', '!([*)*')); + expect_truthy(isMatch('abcz', '!([*)*')); + expect_truthy(isMatch('bbc', '!([*)*')); + + expect_truthy(isMatch('abcx', '!([[*])*')); + expect_truthy(isMatch('abcz', '!([[*])*')); + expect_truthy(isMatch('bbc', '!([[*])*')); + + expect_truthy(isMatch('abcx', '+(a|b\\[)*')); + expect_truthy(isMatch('abcz', '+(a|b\\[)*')); + expect_truthy(!isMatch('bbc', '+(a|b\\[)*')); + + expect_truthy(isMatch('abcx', '+(a|b[)*')); + expect_truthy(isMatch('abcz', '+(a|b[)*')); + expect_truthy(!isMatch('bbc', '+(a|b[)*')); + + expect_truthy(!isMatch('abcx', '[a*(]*z')); + expect_truthy(isMatch('abcz', '[a*(]*z')); + expect_truthy(!isMatch('bbc', '[a*(]*z')); + expect_truthy(isMatch('aaz', '[a*(]*z')); + expect_truthy(isMatch('aaaz', '[a*(]*z')); + + expect_truthy(!isMatch('abcx', '[a*(]*)z')); + expect_truthy(!isMatch('abcz', '[a*(]*)z')); + expect_truthy(!isMatch('bbc', '[a*(]*)z')); + + expect_truthy(!isMatch('abc', '+()c')); + expect_truthy(!isMatch('abc', '+()x')); + expect_truthy(isMatch('abc', '+(*)c')); + expect_truthy(!isMatch('abc', '+(*)x')); + expect_truthy(!isMatch('abc', 'no-file+(a|b)stuff')); + expect_truthy(!isMatch('abc', 'no-file+(a*(c)|b)stuff')); + + expect_truthy(isMatch('abd', 'a+(b|c)d')); + expect_truthy(isMatch('acd', 'a+(b|c)d')); + + expect_truthy(!isMatch('abc', 'a+(b|c)d')); + + expect_truthy(isMatch('abd', 'a!(b|B)')); + expect_truthy(isMatch('acd', 'a!(@(b|B))')); + expect_truthy(isMatch('ac', 'a!(@(b|B))')); + expect_truthy(!isMatch('ab', 'a!(@(b|B))')); + + expect_truthy(!isMatch('abc', 'a!(@(b|B))d')); + expect_truthy(!isMatch('abd', 'a!(@(b|B))d')); + expect_truthy(isMatch('acd', 'a!(@(b|B))d')); + + expect_truthy(isMatch('abd', 'a[b*(foo|bar)]d')); + expect_truthy(!isMatch('abc', 'a[b*(foo|bar)]d')); + expect_truthy(!isMatch('acd', 'a[b*(foo|bar)]d')); + }); + + test('stuff from korn\'s book', () => { + expect_truthy(!isMatch('para', 'para+([0-9])')); + expect_truthy(!isMatch('para381', 'para?([345]|99)1')); + expect_truthy(!isMatch('paragraph', 'para*([0-9])')); + expect_truthy(!isMatch('paramour', 'para@(chute|graph)')); + expect_truthy(isMatch('para', 'para*([0-9])')); + expect_truthy(isMatch('para.38', 'para!(*.[0-9])')); + expect_truthy(isMatch('para.38', 'para!(*.[00-09])')); + expect_truthy(isMatch('para.graph', 'para!(*.[0-9])')); + expect_truthy(isMatch('para13829383746592', 'para*([0-9])')); + expect_truthy(isMatch('para39', 'para!(*.[0-9])')); + expect_truthy(isMatch('para987346523', 'para+([0-9])')); + expect_truthy(isMatch('para991', 'para?([345]|99)1')); + expect_truthy(isMatch('paragraph', 'para!(*.[0-9])')); + expect_truthy(isMatch('paragraph', 'para@(chute|graph)')); + }); + + test('simple kleene star tests', () => { + expect_truthy(!isMatch('foo', '*(a|b[)')); + expect_truthy(!isMatch('(', '*(a|b[)')); + expect_truthy(!isMatch(')', '*(a|b[)')); + expect_truthy(!isMatch('|', '*(a|b[)')); + expect_truthy(isMatch('a', '*(a|b)')); + expect_truthy(isMatch('b', '*(a|b)')); + expect_truthy(isMatch('b[', '*(a|b\\[)')); + expect_truthy(isMatch('ab[', '+(a|b\\[)')); + expect_truthy(!isMatch('ab[cde', '+(a|b\\[)')); + expect_truthy(isMatch('ab[cde', '+(a|b\\[)*')); + + expect_truthy(isMatch('foo', '*(a|b|f)*')); + expect_truthy(isMatch('foo', '*(a|b|o)*')); + expect_truthy(isMatch('foo', '*(a|b|f|o)')); + expect_truthy(isMatch('*(a|b[)', '\\*\\(a\\|b\\[\\)')); + expect_truthy(!isMatch('foo', '*(a|b)')); + expect_truthy(!isMatch('foo', '*(a|b\\[)')); + expect_truthy(isMatch('foo', '*(a|b\\[)|f*')); + }); + + test('should support multiple extglobs:', () => { + expect_truthy(isMatch('moo.cow', '@(*).@(*)')); + expect_truthy(isMatch('a.a', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(isMatch('a.b', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('a.c', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('a.c.d', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('c.c', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('a.', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('d.d', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('e.e', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(!isMatch('f.f', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + expect_truthy(isMatch('a.abcd', '*.@(a|b|@(ab|a*@(b))*@(c)d)')); + + expect_truthy(!isMatch('a.a', '!(*.a|*.b|*.c)')); + expect_truthy(!isMatch('a.b', '!(*.a|*.b|*.c)')); + expect_truthy(!isMatch('a.c', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('a.c.d', '!(*.a|*.b|*.c)')); + expect_truthy(!isMatch('c.c', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('a.', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('d.d', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('e.e', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('f.f', '!(*.a|*.b|*.c)')); + expect_truthy(isMatch('a.abcd', '!(*.a|*.b|*.c)')); + + expect_truthy(isMatch('a.a', '!(*.[^a-c])')); + expect_truthy(isMatch('a.b', '!(*.[^a-c])')); + expect_truthy(isMatch('a.c', '!(*.[^a-c])')); + expect_truthy(!isMatch('a.c.d', '!(*.[^a-c])')); + expect_truthy(isMatch('c.c', '!(*.[^a-c])')); + expect_truthy(isMatch('a.', '!(*.[^a-c])')); + expect_truthy(!isMatch('d.d', '!(*.[^a-c])')); + expect_truthy(!isMatch('e.e', '!(*.[^a-c])')); + expect_truthy(!isMatch('f.f', '!(*.[^a-c])')); + expect_truthy(isMatch('a.abcd', '!(*.[^a-c])')); + + expect_truthy(!isMatch('a.a', '!(*.[a-c])')); + expect_truthy(!isMatch('a.b', '!(*.[a-c])')); + expect_truthy(!isMatch('a.c', '!(*.[a-c])')); + expect_truthy(isMatch('a.c.d', '!(*.[a-c])')); + expect_truthy(!isMatch('c.c', '!(*.[a-c])')); + expect_truthy(isMatch('a.', '!(*.[a-c])')); + expect_truthy(isMatch('d.d', '!(*.[a-c])')); + expect_truthy(isMatch('e.e', '!(*.[a-c])')); + expect_truthy(isMatch('f.f', '!(*.[a-c])')); + expect_truthy(isMatch('a.abcd', '!(*.[a-c])')); + + expect_truthy(!isMatch('a.a', '!(*.[a-c]*)')); + expect_truthy(!isMatch('a.b', '!(*.[a-c]*)')); + expect_truthy(!isMatch('a.c', '!(*.[a-c]*)')); + expect_truthy(!isMatch('a.c.d', '!(*.[a-c]*)')); + expect_truthy(!isMatch('c.c', '!(*.[a-c]*)')); + expect_truthy(isMatch('a.', '!(*.[a-c]*)')); + expect_truthy(isMatch('d.d', '!(*.[a-c]*)')); + expect_truthy(isMatch('e.e', '!(*.[a-c]*)')); + expect_truthy(isMatch('f.f', '!(*.[a-c]*)')); + expect_truthy(!isMatch('a.abcd', '!(*.[a-c]*)')); + + expect_truthy(!isMatch('a.a', '*.!(a|b|c)')); + expect_truthy(!isMatch('a.b', '*.!(a|b|c)')); + expect_truthy(!isMatch('a.c', '*.!(a|b|c)')); + expect_truthy(isMatch('a.c.d', '*.!(a|b|c)')); + expect_truthy(!isMatch('c.c', '*.!(a|b|c)')); + expect_truthy(isMatch('a.', '*.!(a|b|c)')); + expect_truthy(isMatch('d.d', '*.!(a|b|c)')); + expect_truthy(isMatch('e.e', '*.!(a|b|c)')); + expect_truthy(isMatch('f.f', '*.!(a|b|c)')); + expect_truthy(isMatch('a.abcd', '*.!(a|b|c)')); + + expect_truthy(isMatch('a.a', '*!(.a|.b|.c)')); + expect_truthy(isMatch('a.b', '*!(.a|.b|.c)')); + expect_truthy(isMatch('a.c', '*!(.a|.b|.c)')); + expect_truthy(isMatch('a.c.d', '*!(.a|.b|.c)')); + expect_truthy(isMatch('c.c', '*!(.a|.b|.c)')); + expect_truthy(isMatch('a.', '*!(.a|.b|.c)')); + expect_truthy(isMatch('d.d', '*!(.a|.b|.c)')); + expect_truthy(isMatch('e.e', '*!(.a|.b|.c)')); + expect_truthy(isMatch('f.f', '*!(.a|.b|.c)')); + expect_truthy(isMatch('a.abcd', '*!(.a|.b|.c)')); + + expect_truthy(!isMatch('a.a', '!(*.[a-c])*')); + expect_truthy(!isMatch('a.b', '!(*.[a-c])*')); + expect_truthy(!isMatch('a.c', '!(*.[a-c])*')); + expect_truthy(!isMatch('a.c.d', '!(*.[a-c])*')); + expect_truthy(!isMatch('c.c', '!(*.[a-c])*')); + expect_truthy(isMatch('a.', '!(*.[a-c])*')); + expect_truthy(isMatch('d.d', '!(*.[a-c])*')); + expect_truthy(isMatch('e.e', '!(*.[a-c])*')); + expect_truthy(isMatch('f.f', '!(*.[a-c])*')); + expect_truthy(!isMatch('a.abcd', '!(*.[a-c])*')); + + expect_truthy(isMatch('a.a', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('a.b', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('a.c', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('a.c.d', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('c.c', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('a.', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('d.d', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('e.e', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('f.f', '*!(.a|.b|.c)*')); + expect_truthy(isMatch('a.abcd', '*!(.a|.b|.c)*')); + + expect_truthy(!isMatch('a.a', '*.!(a|b|c)*')); + expect_truthy(!isMatch('a.b', '*.!(a|b|c)*')); + expect_truthy(!isMatch('a.c', '*.!(a|b|c)*')); + expect_truthy(isMatch('a.c.d', '*.!(a|b|c)*')); + expect_truthy(!isMatch('c.c', '*.!(a|b|c)*')); + expect_truthy(isMatch('a.', '*.!(a|b|c)*')); + expect_truthy(isMatch('d.d', '*.!(a|b|c)*')); + expect_truthy(isMatch('e.e', '*.!(a|b|c)*')); + expect_truthy(isMatch('f.f', '*.!(a|b|c)*')); + expect_truthy(!isMatch('a.abcd', '*.!(a|b|c)*')); + }); + + test('should correctly match empty parens', () => { + expect_truthy(!isMatch('def', '@()ef')); + expect_truthy(isMatch('ef', '@()ef')); + + expect_truthy(!isMatch('def', '()ef')); + expect_truthy(isMatch('ef', '()ef')); + }); + + test('should match escaped parens', () => { + if (process.platform !== 'win32') { + expect_truthy(isMatch('a\\(b', 'a\\\\\\(b')); + } + expect_truthy(isMatch('a(b', 'a(b')); + expect_truthy(isMatch('a(b', 'a\\(b')); + expect_truthy(!isMatch('a((b', 'a(b')); + expect_truthy(!isMatch('a((((b', 'a(b')); + expect_truthy(!isMatch('ab', 'a(b')); + + expect_truthy(isMatch('a(b', 'a\\(b')); + expect_truthy(!isMatch('a((b', 'a\\(b')); + expect_truthy(!isMatch('a((((b', 'a\\(b')); + expect_truthy(!isMatch('ab', 'a\\(b')); + + expect_truthy(isMatch('a(b', 'a(*b')); + expect_truthy(isMatch('a(ab', 'a\\(*b')); + expect_truthy(isMatch('a((b', 'a(*b')); + expect_truthy(isMatch('a((((b', 'a(*b')); + expect_truthy(!isMatch('ab', 'a(*b')); + }); + + test('should match escaped backslashes', () => { + expect_truthy(isMatch('a(b', 'a\\(b')); + expect_truthy(isMatch('a((b', 'a\\(\\(b')); + expect_truthy(isMatch('a((((b', 'a\\(\\(\\(\\(b')); + + expect_truthy(!isMatch('a(b', 'a\\\\(b')); + expect_truthy(!isMatch('a((b', 'a\\\\(b')); + expect_truthy(!isMatch('a((((b', 'a\\\\(b')); + expect_truthy(!isMatch('ab', 'a\\\\(b')); + + expect_truthy(!isMatch('a/b', 'a\\\\b')); + expect_truthy(!isMatch('ab', 'a\\\\b')); + }); + + // these are not extglobs, and do not need to pass, but they are included + // to test integration with other features + test('should support regex characters', () => { + const fixtures = ['a c', 'a.c', 'a.xy.zc', 'a.zc', 'a123c', 'a1c', 'abbbbc', 'abbbc', 'abbc', 'abc', 'abq', 'axy zc', 'axy', 'axy.zc', 'axyzc']; + + if (process.platform !== 'win32') { + expect_deepEqual(match(['a\\b', 'a/b', 'ab'], 'a/b'), ['a/b']); + } + + expect_deepEqual(match(['a/b', 'ab'], 'a/b'), ['a/b']); + expect_deepEqual(match(fixtures, 'ab?bc'), ['abbbc']); + expect_deepEqual(match(fixtures, 'ab*c'), ['abbbbc', 'abbbc', 'abbc', 'abc']); + expect_deepEqual(match(fixtures, 'a+(b)bc'), ['abbbbc', 'abbbc', 'abbc']); + expect_deepEqual(match(fixtures, '^abc$'), []); + expect_deepEqual(match(fixtures, 'a.c'), ['a.c']); + expect_deepEqual(match(fixtures, 'a.*c'), ['a.c', 'a.xy.zc', 'a.zc']); + expect_deepEqual(match(fixtures, 'a*c'), ['a c', 'a.c', 'a.xy.zc', 'a.zc', 'a123c', 'a1c', 'abbbbc', 'abbbc', 'abbc', 'abc', 'axy zc', 'axy.zc', 'axyzc']); + expect_deepEqual(match(fixtures, 'a[\\w]+c'), ['a123c', 'a1c', 'abbbbc', 'abbbc', 'abbc', 'abc', 'axyzc'], 'Should match word characters'); + expect_deepEqual(match(fixtures, 'a[\\W]+c'), ['a c', 'a.c'], 'Should match non-word characters'); + expect_deepEqual(match(fixtures, 'a[\\d]+c'), ['a123c', 'a1c'], 'Should match numbers'); + expect_deepEqual(match(['foo@#$%123ASD #$$%^&', 'foo!@#$asdfl;', '123'], '[\\d]+'), ['123']); + expect_deepEqual(match(['a123c', 'abbbc'], 'a[\\D]+c'), ['abbbc'], 'Should match non-numbers'); + expect_deepEqual(match(['foo', ' foo '], '(f|o)+\\b'), ['foo'], 'Should match word boundaries'); + }); +}); + diff --git a/packages/node-utils/test/picomatch/globstars.test.ts b/packages/node-utils/test/picomatch/globstars.test.ts new file mode 100644 index 0000000..ae26956 --- /dev/null +++ b/packages/node-utils/test/picomatch/globstars.test.ts @@ -0,0 +1,465 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('stars', () => { + describe('issue related', () => { + test('should match paths with no slashes (micromatch/#15)', () => { + expect_truthy(isMatch('a.js', '**/*.js')); + expect_truthy(isMatch('a.js', '**/a*')); + expect_truthy(isMatch('a.js', '**/a*.js')); + expect_truthy(isMatch('abc', '**/abc')); + }); + + test('should regard non-exclusive double-stars as single stars', () => { + const fixtures = ['a', 'a/', 'a/a', 'a/a/', 'a/a/a', 'a/a/a/', 'a/a/a/a', 'a/a/a/a/', 'a/a/a/a/a', 'a/a/a/a/a/', 'a/a/b', 'a/a/b/', 'a/b', 'a/b/', 'a/b/c/.d/e/', 'a/c', 'a/c/', 'a/b', 'a/x/', 'b', 'b/', 'x/y', 'x/y/', 'z/z', 'z/z/']; + + expect_deepEqual(match(fixtures, '**a/a/*/'), ['a/a/a/', 'a/a/b/']); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/**ccc')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/**z')); + expect_truthy(isMatch('aaa/bba/ccc', 'aaa/**b**/ccc')); + expect_truthy(!isMatch('a/b/c', '**c')); + expect_truthy(!isMatch('a/b/c', 'a/**c')); + expect_truthy(!isMatch('a/b/c', 'a/**z')); + expect_truthy(!isMatch('a/b/c/b/c', 'a/**b**/c')); + expect_truthy(!isMatch('a/b/c/d/e.js', 'a/b/c**/*.js')); + expect_truthy(isMatch('a/b/c/b/c', 'a/**/b/**/c')); + expect_truthy(isMatch('a/aba/c', 'a/**b**/c')); + expect_truthy(isMatch('a/b/c', 'a/**b**/c')); + expect_truthy(isMatch('a/b/c/d.js', 'a/b/c**/*.js')); + }); + + test('should support globstars followed by braces', () => { + expect_truthy(isMatch('a/b/c/d/e/z/foo.md', 'a/**/c/**{,(/z|/x)}/*.md')); + expect_truthy(isMatch('a/b/c/d/e/z/foo.md', 'a/**{,(/x|/z)}/*.md')); + }); + + test('should support globstars followed by braces with nested extglobs', () => { + expect_truthy(isMatch('/x/foo.md', '@(/x|/z)/*.md')); + expect_truthy(isMatch('/z/foo.md', '@(/x|/z)/*.md')); + expect_truthy(isMatch('a/b/c/d/e/z/foo.md', 'a/**/c/**@(/z|/x)/*.md')); + expect_truthy(isMatch('a/b/c/d/e/z/foo.md', 'a/**@(/x|/z)/*.md')); + }); + + test('should support multiple globstars in one pattern', () => { + expect_truthy(!isMatch('a/b/c/d/e/z/foo.md', 'a/**/j/**/z/*.md')); + expect_truthy(!isMatch('a/b/c/j/e/z/foo.txt', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('a/b/c/d/e/j/n/p/o/z/foo.md', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('a/b/c/d/e/z/foo.md', 'a/**/z/*.md')); + expect_truthy(isMatch('a/b/c/j/e/z/foo.md', 'a/**/j/**/z/*.md')); + }); + + test('should match file extensions:', () => { + expect_deepEqual(match(['.md', 'a.md', 'a/b/c.md', '.txt'], '**/*.md'), ['a.md', 'a/b/c.md']); + expect_deepEqual(match(['.md/.md', '.md', 'a/.md', 'a/b/.md'], '**/.md'), ['.md', 'a/.md', 'a/b/.md']); + expect_deepEqual(match(['.md/.md', '.md/foo/.md', '.md', 'a/.md', 'a/b/.md'], '.md/**/.md'), ['.md/.md', '.md/foo/.md']); + }); + + test('should respect trailing slashes on patterns', () => { + const fixtures = ['a', 'a/', 'a/a', 'a/a/', 'a/a/a', 'a/a/a/', 'a/a/a/a', 'a/a/a/a/', 'a/a/a/a/a', 'a/a/a/a/a/', 'a/a/b', 'a/a/b/', 'a/b', 'a/b/', 'a/b/c/.d/e/', 'a/c', 'a/c/', 'a/b', 'a/x/', 'b', 'b/', 'x/y', 'x/y/', 'z/z', 'z/z/']; + + expect_deepEqual(match(fixtures, '**/*/a/'), ['a/a/', 'a/a/a/', 'a/a/a/a/', 'a/a/a/a/a/']); + expect_deepEqual(match(fixtures, '**/*/a/*/'), ['a/a/a/', 'a/a/a/a/', 'a/a/a/a/a/', 'a/a/b/']); + expect_deepEqual(match(fixtures, '**/*/x/'), ['a/x/']); + expect_deepEqual(match(fixtures, '**/*/*/*/*/'), ['a/a/a/a/', 'a/a/a/a/a/']); + expect_deepEqual(match(fixtures, '**/*/*/*/*/*/'), ['a/a/a/a/a/']); + expect_deepEqual(match(fixtures, '*a/a/*/'), ['a/a/a/', 'a/a/b/']); + expect_deepEqual(match(fixtures, '**a/a/*/'), ['a/a/a/', 'a/a/b/']); + expect_deepEqual(match(fixtures, '**/a/*/*/'), ['a/a/a/', 'a/a/a/a/', 'a/a/a/a/a/', 'a/a/b/']); + expect_deepEqual(match(fixtures, '**/a/*/*/*/'), ['a/a/a/a/', 'a/a/a/a/a/']); + expect_deepEqual(match(fixtures, '**/a/*/*/*/*/'), ['a/a/a/a/a/']); + expect_deepEqual(match(fixtures, '**/a/*/a/'), ['a/a/a/', 'a/a/a/a/', 'a/a/a/a/a/']); + expect_deepEqual(match(fixtures, '**/a/*/b/'), ['a/a/b/']); + }); + + test('should match literal globstars when stars are escaped', () => { + const fixtures = ['.md', '**a.md', '**.md', '.md', '**']; + expect_deepEqual(match(fixtures, '\\*\\**.md'), ['**a.md', '**.md']); + expect_deepEqual(match(fixtures, '\\*\\*.md'), ['**.md']); + }); + + test('single dots', () => { + expect_truthy(!isMatch('.a/a', '**')); + expect_truthy(!isMatch('a/.a', '**')); + expect_truthy(!isMatch('.a/a', '**/')); + expect_truthy(!isMatch('a/.a', '**/')); + expect_truthy(!isMatch('.a/a', '**/**')); + expect_truthy(!isMatch('a/.a', '**/**')); + expect_truthy(!isMatch('.a/a', '**/**/*')); + expect_truthy(!isMatch('a/.a', '**/**/*')); + expect_truthy(!isMatch('.a/a', '**/**/x')); + expect_truthy(!isMatch('a/.a', '**/**/x')); + expect_truthy(!isMatch('.a/a', '**/x')); + expect_truthy(!isMatch('a/.a', '**/x')); + expect_truthy(!isMatch('.a/a', '**/x/*')); + expect_truthy(!isMatch('a/.a', '**/x/*')); + expect_truthy(!isMatch('.a/a', '**/x/**')); + expect_truthy(!isMatch('a/.a', '**/x/**')); + expect_truthy(!isMatch('.a/a', '**/x/*/*')); + expect_truthy(!isMatch('a/.a', '**/x/*/*')); + expect_truthy(!isMatch('.a/a', '*/x/**')); + expect_truthy(!isMatch('a/.a', '*/x/**')); + expect_truthy(!isMatch('.a/a', 'a/**')); + expect_truthy(!isMatch('a/.a', 'a/**')); + expect_truthy(!isMatch('.a/a', 'a/**/*')); + expect_truthy(!isMatch('a/.a', 'a/**/*')); + expect_truthy(!isMatch('.a/a', 'a/**/**/*')); + expect_truthy(!isMatch('a/.a', 'a/**/**/*')); + expect_truthy(!isMatch('.a/a', 'b/**')); + expect_truthy(!isMatch('a/.a', 'b/**')); + }); + + test('double dots', () => { + expect_truthy(!isMatch('a/../a', '**')); + expect_truthy(!isMatch('ab/../ac', '**')); + expect_truthy(!isMatch('../a', '**')); + expect_truthy(!isMatch('../../b', '**')); + expect_truthy(!isMatch('../c', '**')); + expect_truthy(!isMatch('../c/d', '**')); + expect_truthy(!isMatch('a/../a', '**/')); + expect_truthy(!isMatch('ab/../ac', '**/')); + expect_truthy(!isMatch('../a', '**/')); + expect_truthy(!isMatch('../../b', '**/')); + expect_truthy(!isMatch('../c', '**/')); + expect_truthy(!isMatch('../c/d', '**/')); + expect_truthy(!isMatch('a/../a', '**/**')); + expect_truthy(!isMatch('ab/../ac', '**/**')); + expect_truthy(!isMatch('../a', '**/**')); + expect_truthy(!isMatch('../../b', '**/**')); + expect_truthy(!isMatch('../c', '**/**')); + expect_truthy(!isMatch('../c/d', '**/**')); + expect_truthy(!isMatch('a/../a', '**/**/*')); + expect_truthy(!isMatch('ab/../ac', '**/**/*')); + expect_truthy(!isMatch('../a', '**/**/*')); + expect_truthy(!isMatch('../../b', '**/**/*')); + expect_truthy(!isMatch('../c', '**/**/*')); + expect_truthy(!isMatch('../c/d', '**/**/*')); + expect_truthy(!isMatch('a/../a', '**/**/x')); + expect_truthy(!isMatch('ab/../ac', '**/**/x')); + expect_truthy(!isMatch('../a', '**/**/x')); + expect_truthy(!isMatch('../../b', '**/**/x')); + expect_truthy(!isMatch('../c', '**/**/x')); + expect_truthy(!isMatch('../c/d', '**/**/x')); + expect_truthy(!isMatch('a/../a', '**/x')); + expect_truthy(!isMatch('ab/../ac', '**/x')); + expect_truthy(!isMatch('../a', '**/x')); + expect_truthy(!isMatch('../../b', '**/x')); + expect_truthy(!isMatch('../c', '**/x')); + expect_truthy(!isMatch('../c/d', '**/x')); + expect_truthy(!isMatch('a/../a', '**/x/*')); + expect_truthy(!isMatch('ab/../ac', '**/x/*')); + expect_truthy(!isMatch('../a', '**/x/*')); + expect_truthy(!isMatch('../../b', '**/x/*')); + expect_truthy(!isMatch('../c', '**/x/*')); + expect_truthy(!isMatch('../c/d', '**/x/*')); + expect_truthy(!isMatch('a/../a', '**/x/**')); + expect_truthy(!isMatch('ab/../ac', '**/x/**')); + expect_truthy(!isMatch('../a', '**/x/**')); + expect_truthy(!isMatch('../../b', '**/x/**')); + expect_truthy(!isMatch('../c', '**/x/**')); + expect_truthy(!isMatch('../c/d', '**/x/**')); + expect_truthy(!isMatch('a/../a', '**/x/*/*')); + expect_truthy(!isMatch('ab/../ac', '**/x/*/*')); + expect_truthy(!isMatch('../a', '**/x/*/*')); + expect_truthy(!isMatch('../../b', '**/x/*/*')); + expect_truthy(!isMatch('../c', '**/x/*/*')); + expect_truthy(!isMatch('../c/d', '**/x/*/*')); + expect_truthy(!isMatch('a/../a', '*/x/**')); + expect_truthy(!isMatch('ab/../ac', '*/x/**')); + expect_truthy(!isMatch('../a', '*/x/**')); + expect_truthy(!isMatch('../../b', '*/x/**')); + expect_truthy(!isMatch('../c', '*/x/**')); + expect_truthy(!isMatch('../c/d', '*/x/**')); + expect_truthy(!isMatch('a/../a', 'a/**')); + expect_truthy(!isMatch('ab/../ac', 'a/**')); + expect_truthy(!isMatch('../a', 'a/**')); + expect_truthy(!isMatch('../../b', 'a/**')); + expect_truthy(!isMatch('../c', 'a/**')); + expect_truthy(!isMatch('../c/d', 'a/**')); + expect_truthy(!isMatch('a/../a', 'a/**/*')); + expect_truthy(!isMatch('ab/../ac', 'a/**/*')); + expect_truthy(!isMatch('../a', 'a/**/*')); + expect_truthy(!isMatch('../../b', 'a/**/*')); + expect_truthy(!isMatch('../c', 'a/**/*')); + expect_truthy(!isMatch('../c/d', 'a/**/*')); + expect_truthy(!isMatch('a/../a', 'a/**/**/*')); + expect_truthy(!isMatch('ab/../ac', 'a/**/**/*')); + expect_truthy(!isMatch('../a', 'a/**/**/*')); + expect_truthy(!isMatch('../../b', 'a/**/**/*')); + expect_truthy(!isMatch('../c', 'a/**/**/*')); + expect_truthy(!isMatch('../c/d', 'a/**/**/*')); + expect_truthy(!isMatch('a/../a', 'b/**')); + expect_truthy(!isMatch('ab/../ac', 'b/**')); + expect_truthy(!isMatch('../a', 'b/**')); + expect_truthy(!isMatch('../../b', 'b/**')); + expect_truthy(!isMatch('../c', 'b/**')); + expect_truthy(!isMatch('../c/d', 'b/**')); + }); + + test('should match', () => { + expect_truthy(!isMatch('a', '**/')); + expect_truthy(!isMatch('a', '**/a/*')); + expect_truthy(!isMatch('a', '**/a/*/*')); + expect_truthy(!isMatch('a', '*/a/**')); + expect_truthy(!isMatch('a', 'a/**/*')); + expect_truthy(!isMatch('a', 'a/**/**/*')); + expect_truthy(!isMatch('a/b', '**/')); + expect_truthy(!isMatch('a/b', '**/b/*')); + expect_truthy(!isMatch('a/b', '**/b/*/*')); + expect_truthy(!isMatch('a/b', 'b/**')); + expect_truthy(!isMatch('a/b/c', '**/')); + expect_truthy(!isMatch('a/b/c', '**/**/b')); + expect_truthy(!isMatch('a/b/c', '**/b')); + expect_truthy(!isMatch('a/b/c', '**/b/*/*')); + expect_truthy(!isMatch('a/b/c', 'b/**')); + expect_truthy(!isMatch('a/b/c/d', '**/')); + expect_truthy(!isMatch('a/b/c/d', '**/d/*')); + expect_truthy(!isMatch('a/b/c/d', 'b/**')); + expect_truthy(isMatch('a', '**')); + expect_truthy(isMatch('a', '**/**')); + expect_truthy(isMatch('a', '**/**/*')); + expect_truthy(isMatch('a', '**/**/a')); + expect_truthy(isMatch('a', '**/a')); + expect_truthy(isMatch('a', '**/a/**')); + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(isMatch('a/b', '**')); + expect_truthy(isMatch('a/b', '**/**')); + expect_truthy(isMatch('a/b', '**/**/*')); + expect_truthy(isMatch('a/b', '**/**/b')); + expect_truthy(isMatch('a/b', '**/b')); + expect_truthy(isMatch('a/b', '**/b/**')); + expect_truthy(isMatch('a/b', '*/b/**')); + expect_truthy(isMatch('a/b', 'a/**')); + expect_truthy(isMatch('a/b', 'a/**/*')); + expect_truthy(isMatch('a/b', 'a/**/**/*')); + expect_truthy(isMatch('a/b/c', '**')); + expect_truthy(isMatch('a/b/c', '**/**')); + expect_truthy(isMatch('a/b/c', '**/**/*')); + expect_truthy(isMatch('a/b/c', '**/b/*')); + expect_truthy(isMatch('a/b/c', '**/b/**')); + expect_truthy(isMatch('a/b/c', '*/b/**')); + expect_truthy(isMatch('a/b/c', 'a/**')); + expect_truthy(isMatch('a/b/c', 'a/**/*')); + expect_truthy(isMatch('a/b/c', 'a/**/**/*')); + expect_truthy(isMatch('a/b/c/d', '**')); + expect_truthy(isMatch('a/b/c/d', '**/**')); + expect_truthy(isMatch('a/b/c/d', '**/**/*')); + expect_truthy(isMatch('a/b/c/d', '**/**/d')); + expect_truthy(isMatch('a/b/c/d', '**/b/**')); + expect_truthy(isMatch('a/b/c/d', '**/b/*/*')); + expect_truthy(isMatch('a/b/c/d', '**/d')); + expect_truthy(isMatch('a/b/c/d', '*/b/**')); + expect_truthy(isMatch('a/b/c/d', 'a/**')); + expect_truthy(isMatch('a/b/c/d', 'a/**/*')); + expect_truthy(isMatch('a/b/c/d', 'a/**/**/*')); + }); + + test('should match nested directories', () => { + expect_truthy(isMatch('a/b', '*/*')); + expect_truthy(isMatch('a/b/c/xyz.md', 'a/b/c/*.md')); + expect_truthy(isMatch('a/bb.bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bbbb/c/xyz.md', 'a/*/c/*.md')); + + expect_truthy(isMatch('a/b/c', '**/*')); + expect_truthy(isMatch('a/b/c', '**/**')); + expect_truthy(isMatch('a/b/c', '*/**')); + expect_truthy(isMatch('a/b/c/d/e/j/n/p/o/z/c.md', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('a/b/c/d/e/z/c.md', 'a/**/z/*.md')); + expect_truthy(isMatch('a/bb.bb/aa/b.b/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(isMatch('a/bb.bb/aa/bb/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(!isMatch('a/b/c/j/e/z/c.txt', 'a/**/j/**/z/*.md')); + expect_truthy(!isMatch('a/b/c/xyz.md', 'a/b/**/c{d,e}/**/xyz.md')); + expect_truthy(!isMatch('a/b/d/xyz.md', 'a/b/**/c{d,e}/**/xyz.md')); + expect_truthy(!isMatch('a/b', 'a/**/')); + expect_truthy(!isMatch('a/b/.js/c.txt', '**/*')); + expect_truthy(!isMatch('a/b/c/d', 'a/**/')); + expect_truthy(!isMatch('a/bb', 'a/**/')); + expect_truthy(!isMatch('a/cb', 'a/**/')); + expect_truthy(isMatch('/a/b', '/**')); + expect_truthy(isMatch('a.b', '**/*')); + expect_truthy(isMatch('a.js', '**/*')); + expect_truthy(isMatch('a.js', '**/*.js')); + expect_truthy(isMatch('a/', 'a/**/')); + expect_truthy(isMatch('a/a.js', '**/*.js')); + expect_truthy(isMatch('a/a/b.js', '**/*.js')); + expect_truthy(isMatch('a/b', 'a/**/b')); + expect_truthy(isMatch('a/b', 'a/**b')); + expect_truthy(isMatch('a/b.md', '**/*.md')); + expect_truthy(isMatch('a/b/c.js', '**/*')); + expect_truthy(isMatch('a/b/c.txt', '**/*')); + expect_truthy(isMatch('a/b/c/d/', 'a/**/')); + expect_truthy(isMatch('a/b/c/d/a.js', '**/*')); + expect_truthy(isMatch('a/b/c/z.js', 'a/b/**/*.js')); + expect_truthy(isMatch('a/b/z.js', 'a/b/**/*.js')); + expect_truthy(isMatch('ab', '**/*')); + expect_truthy(isMatch('ab/c', '**/*')); + expect_truthy(isMatch('ab/c/d', '**/*')); + expect_truthy(isMatch('abc.js', '**/*')); + }); + + test('should not match dotfiles by default', () => { + expect_truthy(!isMatch('a/.b', 'a/**/z/*.md')); + expect_truthy(!isMatch('a/b/z/.a', 'a/**/z/*.a')); + expect_truthy(!isMatch('a/b/z/.a', 'a/*/z/*.a')); + expect_truthy(!isMatch('a/b/z/.a', 'b/a')); + expect_truthy(!isMatch('a/foo/z/.b', 'a/**/z/*.md')); + }); + + test('should match leading dots when defined in pattern', () => { + const fixtures = ['.gitignore', 'a/b/z/.dotfile', 'a/b/z/.dotfile.md', 'a/b/z/.dotfile.md', 'a/b/z/.dotfile.md']; + expect_truthy(!isMatch('.gitignore', 'a/**/z/*.md')); + expect_truthy(!isMatch('a/b/z/.dotfile', 'a/**/z/*.md')); + expect_truthy(!isMatch('a/b/z/.dotfile.md', '**/c/.*.md')); + expect_truthy(isMatch('a/.b', 'a/.*')); + expect_truthy(isMatch('a/b/z/.a', 'a/*/z/.a')); + expect_truthy(isMatch('a/b/z/.dotfile.md', '**/.*.md')); + expect_truthy(isMatch('a/b/z/.dotfile.md', 'a/**/z/.*.md')); + expect_deepEqual(match(['.md', 'a.md', 'a/b/c.md', '.txt'], '**/*.md'), ['a.md', 'a/b/c.md']); + expect_deepEqual(match(['.md/.md', '.md', 'a/.md', 'a/b/.md'], '**/.md'), ['.md', 'a/.md', 'a/b/.md']); + expect_deepEqual(match(['.md/.md', '.md/foo/.md', '.md', 'a/.md', 'a/b/.md'], '.md/**/.md'), ['.md/.md', '.md/foo/.md']); + expect_deepEqual(match(fixtures, 'a/**/z/.*.md'), ['a/b/z/.dotfile.md']); + }); + + test('todo... (micromatch/#24)', () => { + expect_truthy(isMatch('foo/bar/baz/one/image.png', 'foo/bar/**/one/**/*.*')); + expect_truthy(isMatch('foo/bar/baz/one/two/image.png', 'foo/bar/**/one/**/*.*')); + expect_truthy(isMatch('foo/bar/baz/one/two/three/image.png', 'foo/bar/**/one/**/*.*')); + expect_truthy(!isMatch('a/b/c/d/', 'a/b/**/f')); + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(isMatch('a', '**')); + expect_truthy(isMatch('a', 'a{,/**}')); + expect_truthy(isMatch('a/', '**')); + expect_truthy(isMatch('a/', 'a/**')); + expect_truthy(isMatch('a/b/c/d', '**')); + expect_truthy(isMatch('a/b/c/d/', '**')); + expect_truthy(isMatch('a/b/c/d/', '**/**')); + expect_truthy(isMatch('a/b/c/d/', '**/b/**')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**/')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**/c/**/')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**/c/**/d/')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/**/*.*')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/*.*')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/c/**/d/*.*')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/d/**/*.*')); + expect_truthy(isMatch('a/b/c/d/g/e.f', 'a/b/**/d/**/*.*')); + expect_truthy(isMatch('a/b/c/d/g/g/e.f', 'a/b/**/d/**/*.*')); + + expect_truthy(isMatch('a/b-c/z.js', 'a/b-*/**/z.js')); + expect_truthy(isMatch('a/b-c/d/e/z.js', 'a/b-*/**/z.js')); + }); + }); + + describe('globstars', () => { + test('should match globstars', () => { + expect_truthy(isMatch('a/b/c/d.js', '**/*.js')); + expect_truthy(isMatch('a/b/c.js', '**/*.js')); + expect_truthy(isMatch('a/b.js', '**/*.js')); + expect_truthy(isMatch('a/b/c/d/e/f.js', 'a/b/**/*.js')); + expect_truthy(isMatch('a/b/c/d/e.js', 'a/b/**/*.js')); + expect_truthy(isMatch('a/b/c/d.js', 'a/b/c/**/*.js')); + expect_truthy(isMatch('a/b/c/d.js', 'a/b/**/*.js')); + expect_truthy(isMatch('a/b/d.js', 'a/b/**/*.js')); + + expect_truthy(!isMatch('a/d.js', 'a/b/**/*.js')); + expect_truthy(!isMatch('d.js', 'a/b/**/*.js')); + }); + + test('should regard non-exclusive double-stars as single stars', () => { + expect_truthy(!isMatch('a/b/c', '**c')); + expect_truthy(!isMatch('a/b/c', 'a/**c')); + expect_truthy(!isMatch('a/b/c', 'a/**z')); + expect_truthy(!isMatch('a/b/c/b/c', 'a/**b**/c')); + expect_truthy(!isMatch('a/b/c/d/e.js', 'a/b/c**/*.js')); + expect_truthy(isMatch('a/b/c/b/c', 'a/**/b/**/c')); + expect_truthy(isMatch('a/aba/c', 'a/**b**/c')); + expect_truthy(isMatch('a/b/c', 'a/**b**/c')); + expect_truthy(isMatch('a/b/c/d.js', 'a/b/c**/*.js')); + }); + + test('should support globstars (**)', () => { + expect_truthy(!isMatch('a', 'a/**/*')); + expect_truthy(!isMatch('a', 'a/**/**/*')); + expect_truthy(!isMatch('a', 'a/**/**/**/*')); + expect_truthy(!isMatch('a/', '**/a')); + expect_truthy(!isMatch('a/', 'a/**/*')); + expect_truthy(!isMatch('a/', 'a/**/**/*')); + expect_truthy(!isMatch('a/', 'a/**/**/**/*')); + expect_truthy(!isMatch('a/b', '**/a')); + expect_truthy(!isMatch('a/b/c/j/e/z/c.txt', 'a/**/j/**/z/*.md')); + expect_truthy(!isMatch('a/bb', 'a/**/b')); + expect_truthy(!isMatch('a/c', '**/a')); + expect_truthy(!isMatch('a/b', '**/a')); + expect_truthy(!isMatch('a/x/y', '**/a')); + expect_truthy(!isMatch('a/b/c/d', '**/a')); + expect_truthy(isMatch('a', '**')); + expect_truthy(isMatch('a', '**/a')); + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(isMatch('a/', '**')); + expect_truthy(isMatch('a/', '**/a/**')); + expect_truthy(isMatch('a/', 'a/**')); + expect_truthy(isMatch('a/', 'a/**/**')); + expect_truthy(isMatch('a/a', '**/a')); + expect_truthy(isMatch('a/b', '**')); + expect_truthy(isMatch('a/b', '*/*')); + expect_truthy(isMatch('a/b', 'a/**')); + expect_truthy(isMatch('a/b', 'a/**/*')); + expect_truthy(isMatch('a/b', 'a/**/**/*')); + expect_truthy(isMatch('a/b', 'a/**/**/**/*')); + expect_truthy(isMatch('a/b', 'a/**/b')); + expect_truthy(isMatch('a/b/c', '**')); + expect_truthy(isMatch('a/b/c', '**/*')); + expect_truthy(isMatch('a/b/c', '**/**')); + expect_truthy(isMatch('a/b/c', '*/**')); + expect_truthy(isMatch('a/b/c', 'a/**')); + expect_truthy(isMatch('a/b/c', 'a/**/*')); + expect_truthy(isMatch('a/b/c', 'a/**/**/*')); + expect_truthy(isMatch('a/b/c', 'a/**/**/**/*')); + expect_truthy(isMatch('a/b/c/d', '**')); + expect_truthy(isMatch('a/b/c/d', 'a/**')); + expect_truthy(isMatch('a/b/c/d', 'a/**/*')); + expect_truthy(isMatch('a/b/c/d', 'a/**/**/*')); + expect_truthy(isMatch('a/b/c/d', 'a/**/**/**/*')); + expect_truthy(isMatch('a/b/c/d.e', 'a/b/**/c/**/*.*')); + expect_truthy(isMatch('a/b/c/d/e/f/g.md', 'a/**/f/*.md')); + expect_truthy(isMatch('a/b/c/d/e/f/g/h/i/j/k/l.md', 'a/**/f/**/k/*.md')); + expect_truthy(isMatch('a/b/c/def.md', 'a/b/c/*.md')); + expect_truthy(isMatch('a/bb.bb/c/ddd.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bb.bb/cc/d.d/ee/f/ggg.md', 'a/**/f/*.md')); + expect_truthy(isMatch('a/bb.bb/cc/dd/ee/f/ggg.md', 'a/**/f/*.md')); + expect_truthy(isMatch('a/bb/c/ddd.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bbbb/c/ddd.md', 'a/*/c/*.md')); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/issue-related.test.ts b/packages/node-utils/test/picomatch/issue-related.test.ts new file mode 100644 index 0000000..eabf6c2 --- /dev/null +++ b/packages/node-utils/test/picomatch/issue-related.test.ts @@ -0,0 +1,93 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('issue-related tests', () => { + test('should match with braces (see picomatch/issues#8)', () => { + expect_truthy(isMatch('directory/.test.txt', '{file.txt,directory/**/*}', { dot: true })); + expect_truthy(isMatch('directory/test.txt', '{file.txt,directory/**/*}', { dot: true })); + expect_truthy(!isMatch('directory/.test.txt', '{file.txt,directory/**/*}')); + expect_truthy(isMatch('directory/test.txt', '{file.txt,directory/**/*}')); + }); + + test('should match Japanese characters (see micromatch/issues#127)', () => { + expect_truthy(isMatch('フォルダ/aaa.js', 'フ*/**/*')); + expect_truthy(isMatch('フォルダ/aaa.js', 'フォ*/**/*')); + expect_truthy(isMatch('フォルダ/aaa.js', 'フォル*/**/*')); + expect_truthy(isMatch('フォルダ/aaa.js', 'フ*ル*/**/*')); + expect_truthy(isMatch('フォルダ/aaa.js', 'フォルダ/**/*')); + }); + + test('micromatch issue#15', () => { + expect_truthy(isMatch('a/b-c/d/e/z.js', 'a/b-*/**/z.js')); + expect_truthy(isMatch('z.js', 'z*')); + expect_truthy(isMatch('z.js', '**/z*')); + expect_truthy(isMatch('z.js', '**/z*.js')); + expect_truthy(isMatch('z.js', '**/*.js')); + expect_truthy(isMatch('foo', '**/foo')); + }); + + test('micromatch issue#23', () => { + expect_truthy(!isMatch('zzjs', 'z*.js')); + expect_truthy(!isMatch('zzjs', '*z.js')); + }); + + test('micromatch issue#24', () => { + expect_truthy(!isMatch('a/b/c/d/', 'a/b/**/f')); + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(isMatch('a', '**')); + expect_truthy(isMatch('a/', '**')); + expect_truthy(isMatch('a/b/c/d', '**')); + expect_truthy(isMatch('a/b/c/d/', '**')); + expect_truthy(isMatch('a/b/c/d/', '**/**')); + expect_truthy(isMatch('a/b/c/d/', '**/b/**')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**')); + expect_truthy(isMatch('a/b/c/d/', 'a/b/**/')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/**/*.*')); + expect_truthy(isMatch('a/b/c/d/e.f', 'a/b/**/*.*')); + expect_truthy(isMatch('a/b/c/d/g/e.f', 'a/b/**/d/**/*.*')); + expect_truthy(isMatch('a/b/c/d/g/g/e.f', 'a/b/**/d/**/*.*')); + }); + + test('micromatch issue#58 - only match nested dirs when `**` is the only thing in a segment', () => { + expect_truthy(!isMatch('a/b/c', 'a/b**')); + expect_truthy(!isMatch('a/c/b', 'a/**b')); + }); + + test('micromatch issue#79', () => { + expect_truthy(isMatch('a/foo.js', '**/foo.js')); + expect_truthy(isMatch('foo.js', '**/foo.js')); + expect_truthy(isMatch('a/foo.js', '**/foo.js', { dot: true })); + expect_truthy(isMatch('foo.js', '**/foo.js', { dot: true })); + }); +}); diff --git a/packages/node-utils/test/picomatch/malicious.test.ts b/packages/node-utils/test/picomatch/malicious.test.ts new file mode 100644 index 0000000..4189e92 --- /dev/null +++ b/packages/node-utils/test/picomatch/malicious.test.ts @@ -0,0 +1,74 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch, makeRe } = picomatch; +const repeat = n => '\\'.repeat(n); + +/** + * These tests are based on minimatch unit tests + */ + +describe('handling of potential regex exploits', () => { + test('should support long escape sequences', () => { + if (process.platform !== 'win32') { + expect_truthy(isMatch('\\A', `${repeat(65500)}A`), 'within the limits, and valid match'); + } + expect_truthy(isMatch('A', `!${repeat(65500)}A`), 'within the limits, and valid match'); + expect_truthy(isMatch('A', `!(${repeat(65500)}A)`), 'within the limits, and valid match'); + expect_truthy(!isMatch('A', `[!(${repeat(65500)}A`), 'within the limits, but invalid regex'); + }); + + test('should throw an error when the pattern is too long', () => { + expect_throws(() => isMatch('foo', '*'.repeat(65537)), /exceeds maximum allowed/); + expect_throws(() => { + expect_truthy(!isMatch('A', `!(${repeat(65536)}A)`)); + }, /Input length: 65540, exceeds maximum allowed length: 65536/); + }); + + test('should allow max bytes to be customized', () => { + expect_throws(() => { + expect_truthy(!isMatch('A', `!(${repeat(500)}A)`, { maxLength: 499 })); + }, /Input length: 504, exceeds maximum allowed length: 499/); + }); + + test('should be able to accept Object instance properties', () => { + expect_truthy(isMatch('constructor', 'constructor'), 'valid match'); + expect_truthy(isMatch('__proto__', '__proto__'), 'valid match'); + expect_truthy(isMatch('toString', 'toString'), 'valid match'); + }); + + test('should not expose internal prototype properties', () => { + expect_equal(makeRe('[[:constructor:]]').toString(), '/^(?:[[:constructor:]\\])$/'); + expect_truthy(!isMatch('f }]', '[[:constructor:]]'), 'not valid match'); + expect_truthy(!isMatch('a }]', '[[:constructor:]]'), 'not valid match'); + }); +}); diff --git a/packages/node-utils/test/picomatch/minimatch.test.ts b/packages/node-utils/test/picomatch/minimatch.test.ts new file mode 100644 index 0000000..d5bd5fd --- /dev/null +++ b/packages/node-utils/test/picomatch/minimatch.test.ts @@ -0,0 +1,99 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const format = str => str.replace(/^\.\//, ''); +const { isMatch, makeRe } = picomatch; + +describe('minimatch parity:', () => { + describe('minimatch issues (as of 12/7/2016)', () => { + test('https://github.com/isaacs/minimatch/issues/29', () => { + expect_truthy(isMatch('foo/bar.txt', 'foo/**/*.txt')); + expect_truthy(makeRe('foo/**/*.txt').test('foo/bar.txt')); + expect_truthy(!isMatch('n/!(axios)/**', 'n/axios/a.js')); + expect_truthy(!makeRe('n/!(axios)/**').test('n/axios/a.js')); + }); + + test('https://github.com/isaacs/minimatch/issues/30', () => { + expect_truthy(isMatch('foo/bar.js', '**/foo/**', { format })); + expect_truthy(isMatch('./foo/bar.js', './**/foo/**', { format })); + expect_truthy(isMatch('./foo/bar.js', '**/foo/**', { format })); + expect_truthy(isMatch('./foo/bar.txt', 'foo/**/*.txt', { format })); + expect_truthy(makeRe('./foo/**/*.txt').test('foo/bar.txt')); + expect_truthy(!isMatch('./foo/!(bar)/**', 'foo/bar/a.js', { format })); + expect_truthy(!makeRe('./foo/!(bar)/**').test('foo/bar/a.js')); + }); + + test('https://github.com/isaacs/minimatch/issues/50', () => { + expect_truthy(isMatch('foo/bar-[ABC].txt', 'foo/**/*-\\[ABC\\].txt')); + expect_truthy(!isMatch('foo/bar-[ABC].txt', 'foo/**/*-\\[abc\\].txt')); + expect_truthy(isMatch('foo/bar-[ABC].txt', 'foo/**/*-\\[abc\\].txt', { nocase: true })); + }); + + test('https://github.com/isaacs/minimatch/issues/67 (should work consistently with `makeRe` and matcher functions)', () => { + const re = makeRe('node_modules/foobar/**/*.bar'); + expect_truthy(re.test('node_modules/foobar/foo.bar')); + expect_truthy(isMatch('node_modules/foobar/foo.bar', 'node_modules/foobar/**/*.bar')); + }); + + test('https://github.com/isaacs/minimatch/issues/75', () => { + expect_truthy(isMatch('foo/baz.qux.js', 'foo/@(baz.qux).js')); + expect_truthy(isMatch('foo/baz.qux.js', 'foo/+(baz.qux).js')); + expect_truthy(isMatch('foo/baz.qux.js', 'foo/*(baz.qux).js')); + expect_truthy(!isMatch('foo/baz.qux.js', 'foo/!(baz.qux).js')); + expect_truthy(!isMatch('foo/bar/baz.qux.js', 'foo/*/!(baz.qux).js')); + expect_truthy(!isMatch('foo/bar/bazqux.js', '**/!(bazqux).js')); + expect_truthy(!isMatch('foo/bar/bazqux.js', '**/bar/!(bazqux).js')); + expect_truthy(!isMatch('foo/bar/bazqux.js', 'foo/**/!(bazqux).js')); + expect_truthy(!isMatch('foo/bar/bazqux.js', 'foo/**/!(bazqux)*.js')); + expect_truthy(!isMatch('foo/bar/baz.qux.js', 'foo/**/!(baz.qux)*.js')); + expect_truthy(!isMatch('foo/bar/baz.qux.js', 'foo/**/!(baz.qux).js')); + expect_truthy(!isMatch('foobar.js', '!(foo)*.js')); + expect_truthy(!isMatch('foo.js', '!(foo).js')); + expect_truthy(!isMatch('foo.js', '!(foo)*.js')); + }); + + test('https://github.com/isaacs/minimatch/issues/78', () => { + expect_truthy(isMatch('a\\b\\c.txt', 'a/**/*.txt', { windows: true })); + expect_truthy(isMatch('a/b/c.txt', 'a/**/*.txt', { windows: true })); + }); + + test('https://github.com/isaacs/minimatch/issues/82', () => { + expect_truthy(isMatch('./src/test/a.js', '**/test/**', { format })); + expect_truthy(isMatch('src/test/a.js', '**/test/**')); + }); + + test('https://github.com/isaacs/minimatch/issues/83', () => { + expect_truthy(!makeRe('foo/!(bar)/**').test('foo/bar/a.js')); + expect_truthy(!isMatch('foo/!(bar)/**', 'foo/bar/a.js')); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/negation.test.ts b/packages/node-utils/test/picomatch/negation.test.ts new file mode 100644 index 0000000..8e356ad --- /dev/null +++ b/packages/node-utils/test/picomatch/negation.test.ts @@ -0,0 +1,305 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('negation patterns - "!"', () => { + test('should patterns with a leading "!" as negated/inverted globs', () => { + expect_truthy(!isMatch('abc', '!*')); + expect_truthy(!isMatch('abc', '!abc')); + expect_truthy(!isMatch('bar.md', '*!.md')); + expect_truthy(!isMatch('bar.md', 'foo!.md')); + expect_truthy(!isMatch('foo!.md', '\\!*!*.md')); + expect_truthy(!isMatch('foo!bar.md', '\\!*!*.md')); + expect_truthy(isMatch('!foo!.md', '*!*.md')); + expect_truthy(isMatch('!foo!.md', '\\!*!*.md')); + expect_truthy(isMatch('abc', '!*foo')); + expect_truthy(isMatch('abc', '!foo*')); + expect_truthy(isMatch('abc', '!xyz')); + expect_truthy(isMatch('ba!r.js', '*!*.*')); + expect_truthy(isMatch('bar.md', '*.md')); + expect_truthy(isMatch('foo!.md', '*!*.*')); + expect_truthy(isMatch('foo!.md', '*!*.md')); + expect_truthy(isMatch('foo!.md', '*!.md')); + expect_truthy(isMatch('foo!.md', '*.md')); + expect_truthy(isMatch('foo!.md', 'foo!.md')); + expect_truthy(isMatch('foo!bar.md', '*!*.md')); + expect_truthy(isMatch('foobar.md', '*b*.md')); + }); + + test('should treat non-leading "!" as literal characters', () => { + expect_truthy(!isMatch('a', 'a!!b')); + expect_truthy(!isMatch('aa', 'a!!b')); + expect_truthy(!isMatch('a/b', 'a!!b')); + expect_truthy(!isMatch('a!b', 'a!!b')); + expect_truthy(isMatch('a!!b', 'a!!b')); + expect_truthy(!isMatch('a/!!/b', 'a!!b')); + }); + + test('should support negation in globs that have no other special characters', () => { + expect_truthy(!isMatch('a/b', '!a/b')); + expect_truthy(isMatch('a', '!a/b')); + expect_truthy(isMatch('a.b', '!a/b')); + expect_truthy(isMatch('a/a', '!a/b')); + expect_truthy(isMatch('a/c', '!a/b')); + expect_truthy(isMatch('b/a', '!a/b')); + expect_truthy(isMatch('b/b', '!a/b')); + expect_truthy(isMatch('b/c', '!a/b')); + }); + + test('should support multiple leading ! to toggle negation', () => { + expect_truthy(!isMatch('abc', '!abc')); + expect_truthy(isMatch('abc', '!!abc')); + expect_truthy(!isMatch('abc', '!!!abc')); + expect_truthy(isMatch('abc', '!!!!abc')); + expect_truthy(!isMatch('abc', '!!!!!abc')); + expect_truthy(isMatch('abc', '!!!!!!abc')); + expect_truthy(!isMatch('abc', '!!!!!!!abc')); + expect_truthy(isMatch('abc', '!!!!!!!!abc')); + }); + + test('should support negation extglobs after leading !', () => { + expect_truthy(!isMatch('abc', '!(abc)')); + expect_truthy(isMatch('abc', '!!(abc)')); + expect_truthy(!isMatch('abc', '!!!(abc)')); + expect_truthy(isMatch('abc', '!!!!(abc)')); + expect_truthy(!isMatch('abc', '!!!!!(abc)')); + expect_truthy(isMatch('abc', '!!!!!!(abc)')); + expect_truthy(!isMatch('abc', '!!!!!!!(abc)')); + expect_truthy(isMatch('abc', '!!!!!!!!(abc)')); + }); + + test('should support negation with globs', () => { + expect_truthy(!isMatch('a/a', '!(*/*)')); + expect_truthy(!isMatch('a/b', '!(*/*)')); + expect_truthy(!isMatch('a/c', '!(*/*)')); + expect_truthy(!isMatch('b/a', '!(*/*)')); + expect_truthy(!isMatch('b/b', '!(*/*)')); + expect_truthy(!isMatch('b/c', '!(*/*)')); + expect_truthy(!isMatch('a/b', '!(*/b)')); + expect_truthy(!isMatch('b/b', '!(*/b)')); + expect_truthy(!isMatch('a/b', '!(a/b)')); + expect_truthy(!isMatch('a', '!*')); + expect_truthy(!isMatch('a.b', '!*')); + expect_truthy(!isMatch('a/a', '!*/*')); + expect_truthy(!isMatch('a/b', '!*/*')); + expect_truthy(!isMatch('a/c', '!*/*')); + expect_truthy(!isMatch('b/a', '!*/*')); + expect_truthy(!isMatch('b/b', '!*/*')); + expect_truthy(!isMatch('b/c', '!*/*')); + expect_truthy(!isMatch('a/b', '!*/b')); + expect_truthy(!isMatch('b/b', '!*/b')); + expect_truthy(!isMatch('a/c', '!*/c')); + expect_truthy(!isMatch('a/c', '!*/c')); + expect_truthy(!isMatch('b/c', '!*/c')); + expect_truthy(!isMatch('b/c', '!*/c')); + expect_truthy(!isMatch('bar', '!*a*')); + expect_truthy(!isMatch('fab', '!*a*')); + expect_truthy(!isMatch('a/a', '!a/(*)')); + expect_truthy(!isMatch('a/b', '!a/(*)')); + expect_truthy(!isMatch('a/c', '!a/(*)')); + expect_truthy(!isMatch('a/b', '!a/(b)')); + expect_truthy(!isMatch('a/a', '!a/*')); + expect_truthy(!isMatch('a/b', '!a/*')); + expect_truthy(!isMatch('a/c', '!a/*')); + expect_truthy(!isMatch('fab', '!f*b')); + expect_truthy(isMatch('a', '!(*/*)')); + expect_truthy(isMatch('a.b', '!(*/*)')); + expect_truthy(isMatch('a', '!(*/b)')); + expect_truthy(isMatch('a.b', '!(*/b)')); + expect_truthy(isMatch('a/a', '!(*/b)')); + expect_truthy(isMatch('a/c', '!(*/b)')); + expect_truthy(isMatch('b/a', '!(*/b)')); + expect_truthy(isMatch('b/c', '!(*/b)')); + expect_truthy(isMatch('a', '!(a/b)')); + expect_truthy(isMatch('a.b', '!(a/b)')); + expect_truthy(isMatch('a/a', '!(a/b)')); + expect_truthy(isMatch('a/c', '!(a/b)')); + expect_truthy(isMatch('b/a', '!(a/b)')); + expect_truthy(isMatch('b/b', '!(a/b)')); + expect_truthy(isMatch('b/c', '!(a/b)')); + expect_truthy(isMatch('a/a', '!*')); + expect_truthy(isMatch('a/b', '!*')); + expect_truthy(isMatch('a/c', '!*')); + expect_truthy(isMatch('b/a', '!*')); + expect_truthy(isMatch('b/b', '!*')); + expect_truthy(isMatch('b/c', '!*')); + expect_truthy(isMatch('a', '!*/*')); + expect_truthy(isMatch('a.b', '!*/*')); + expect_truthy(isMatch('a', '!*/b')); + expect_truthy(isMatch('a.b', '!*/b')); + expect_truthy(isMatch('a/a', '!*/b')); + expect_truthy(isMatch('a/c', '!*/b')); + expect_truthy(isMatch('b/a', '!*/b')); + expect_truthy(isMatch('b/c', '!*/b')); + expect_truthy(isMatch('a', '!*/c')); + expect_truthy(isMatch('a.b', '!*/c')); + expect_truthy(isMatch('a/a', '!*/c')); + expect_truthy(isMatch('a/b', '!*/c')); + expect_truthy(isMatch('b/a', '!*/c')); + expect_truthy(isMatch('b/b', '!*/c')); + expect_truthy(isMatch('foo', '!*a*')); + expect_truthy(isMatch('a', '!a/(*)')); + expect_truthy(isMatch('a.b', '!a/(*)')); + expect_truthy(isMatch('b/a', '!a/(*)')); + expect_truthy(isMatch('b/b', '!a/(*)')); + expect_truthy(isMatch('b/c', '!a/(*)')); + expect_truthy(isMatch('a', '!a/(b)')); + expect_truthy(isMatch('a.b', '!a/(b)')); + expect_truthy(isMatch('a/a', '!a/(b)')); + expect_truthy(isMatch('a/c', '!a/(b)')); + expect_truthy(isMatch('b/a', '!a/(b)')); + expect_truthy(isMatch('b/b', '!a/(b)')); + expect_truthy(isMatch('b/c', '!a/(b)')); + expect_truthy(isMatch('a', '!a/*')); + expect_truthy(isMatch('a.b', '!a/*')); + expect_truthy(isMatch('b/a', '!a/*')); + expect_truthy(isMatch('b/b', '!a/*')); + expect_truthy(isMatch('b/c', '!a/*')); + expect_truthy(isMatch('bar', '!f*b')); + expect_truthy(isMatch('foo', '!f*b')); + }); + + test('should negate files with extensions', () => { + expect_truthy(!isMatch('.md', '!.md')); + expect_truthy(isMatch('a.js', '!**/*.md')); + expect_truthy(!isMatch('b.md', '!**/*.md')); + expect_truthy(isMatch('c.txt', '!**/*.md')); + expect_truthy(isMatch('a.js', '!*.md')); + expect_truthy(!isMatch('b.md', '!*.md')); + expect_truthy(isMatch('c.txt', '!*.md')); + expect_truthy(!isMatch('abc.md', '!*.md')); + expect_truthy(isMatch('abc.txt', '!*.md')); + expect_truthy(!isMatch('foo.md', '!*.md')); + expect_truthy(isMatch('foo.md', '!.md')); + }); + + test('should support negated single stars', () => { + expect_truthy(isMatch('a.js', '!*.md')); + expect_truthy(isMatch('b.txt', '!*.md')); + expect_truthy(!isMatch('c.md', '!*.md')); + expect_truthy(!isMatch('a/a/a.js', '!a/*/a.js')); + expect_truthy(!isMatch('a/b/a.js', '!a/*/a.js')); + expect_truthy(!isMatch('a/c/a.js', '!a/*/a.js')); + expect_truthy(!isMatch('a/a/a/a.js', '!a/*/*/a.js')); + expect_truthy(isMatch('b/a/b/a.js', '!a/*/*/a.js')); + expect_truthy(isMatch('c/a/c/a.js', '!a/*/*/a.js')); + expect_truthy(!isMatch('a/a.txt', '!a/a*.txt')); + expect_truthy(isMatch('a/b.txt', '!a/a*.txt')); + expect_truthy(isMatch('a/c.txt', '!a/a*.txt')); + expect_truthy(!isMatch('a.a.txt', '!a.a*.txt')); + expect_truthy(isMatch('a.b.txt', '!a.a*.txt')); + expect_truthy(isMatch('a.c.txt', '!a.a*.txt')); + expect_truthy(!isMatch('a/a.txt', '!a/*.txt')); + expect_truthy(!isMatch('a/b.txt', '!a/*.txt')); + expect_truthy(!isMatch('a/c.txt', '!a/*.txt')); + }); + + test('should support negated globstars (multiple stars)', () => { + expect_truthy(isMatch('a.js', '!*.md')); + expect_truthy(isMatch('b.txt', '!*.md')); + expect_truthy(!isMatch('c.md', '!*.md')); + expect_truthy(!isMatch('a/a/a.js', '!**/a.js')); + expect_truthy(!isMatch('a/b/a.js', '!**/a.js')); + expect_truthy(!isMatch('a/c/a.js', '!**/a.js')); + expect_truthy(isMatch('a/a/b.js', '!**/a.js')); + expect_truthy(!isMatch('a/a/a/a.js', '!a/**/a.js')); + expect_truthy(isMatch('b/a/b/a.js', '!a/**/a.js')); + expect_truthy(isMatch('c/a/c/a.js', '!a/**/a.js')); + expect_truthy(isMatch('a/b.js', '!**/*.md')); + expect_truthy(isMatch('a.js', '!**/*.md')); + expect_truthy(!isMatch('a/b.md', '!**/*.md')); + expect_truthy(!isMatch('a.md', '!**/*.md')); + expect_truthy(!isMatch('a/b.js', '**/*.md')); + expect_truthy(!isMatch('a.js', '**/*.md')); + expect_truthy(isMatch('a/b.md', '**/*.md')); + expect_truthy(isMatch('a.md', '**/*.md')); + expect_truthy(isMatch('a/b.js', '!**/*.md')); + expect_truthy(isMatch('a.js', '!**/*.md')); + expect_truthy(!isMatch('a/b.md', '!**/*.md')); + expect_truthy(!isMatch('a.md', '!**/*.md')); + expect_truthy(isMatch('a/b.js', '!*.md')); + expect_truthy(isMatch('a.js', '!*.md')); + expect_truthy(isMatch('a/b.md', '!*.md')); + expect_truthy(!isMatch('a.md', '!*.md')); + expect_truthy(isMatch('a.js', '!**/*.md')); + expect_truthy(!isMatch('b.md', '!**/*.md')); + expect_truthy(isMatch('c.txt', '!**/*.md')); + }); + + test('should not negate when inside quoted strings', () => { + expect_truthy(!isMatch('foo.md', '"!*".md')); + expect_truthy(isMatch('"!*".md', '"!*".md')); + expect_truthy(isMatch('!*.md', '"!*".md')); + + expect_truthy(!isMatch('foo.md', '"!*".md', { keepQuotes: true })); + expect_truthy(isMatch('"!*".md', '"!*".md', { keepQuotes: true })); + expect_truthy(!isMatch('!*.md', '"!*".md', { keepQuotes: true })); + + expect_truthy(!isMatch('foo.md', '"**".md')); + expect_truthy(isMatch('"**".md', '"**".md')); + expect_truthy(isMatch('**.md', '"**".md')); + + expect_truthy(!isMatch('foo.md', '"**".md', { keepQuotes: true })); + expect_truthy(isMatch('"**".md', '"**".md', { keepQuotes: true })); + expect_truthy(!isMatch('**.md', '"**".md', { keepQuotes: true })); + }); + + test('should negate dotfiles', () => { + expect_truthy(!isMatch('.dotfile.md', '!.*.md')); + expect_truthy(isMatch('.dotfile.md', '!*.md')); + expect_truthy(isMatch('.dotfile.txt', '!*.md')); + expect_truthy(isMatch('.dotfile.txt', '!*.md')); + expect_truthy(isMatch('a/b/.dotfile', '!*.md')); + expect_truthy(!isMatch('.gitignore', '!.gitignore')); + expect_truthy(isMatch('a', '!.gitignore')); + expect_truthy(isMatch('b', '!.gitignore')); + }); + + test('should not match slashes with a single star', () => { + expect_truthy(isMatch('foo/bar.md', '!*.md')); + expect_truthy(!isMatch('foo.md', '!*.md')); + }); + + test('should match nested directories with globstars', () => { + expect_truthy(!isMatch('a', '!a/**')); + expect_truthy(!isMatch('a/', '!a/**')); + expect_truthy(!isMatch('a/b', '!a/**')); + expect_truthy(!isMatch('a/b/c', '!a/**')); + expect_truthy(isMatch('b', '!a/**')); + expect_truthy(isMatch('b/c', '!a/**')); + + expect_truthy(isMatch('foo', '!f*b')); + expect_truthy(isMatch('bar', '!f*b')); + expect_truthy(!isMatch('fab', '!f*b')); + }); +}); diff --git a/packages/node-utils/test/picomatch/non-globs.test.ts b/packages/node-utils/test/picomatch/non-globs.test.ts new file mode 100644 index 0000000..cc4a807 --- /dev/null +++ b/packages/node-utils/test/picomatch/non-globs.test.ts @@ -0,0 +1,83 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('non-globs', () => { + test('should match non-globs', () => { + expect_truthy(!isMatch('/ab', '/a')); + expect_truthy(!isMatch('a/a', 'a/b')); + expect_truthy(!isMatch('a/a', 'a/c')); + expect_truthy(!isMatch('a/b', 'a/c')); + expect_truthy(!isMatch('a/c', 'a/b')); + expect_truthy(!isMatch('aaa', 'aa')); + expect_truthy(!isMatch('ab', '/a')); + expect_truthy(!isMatch('ab', 'a')); + + expect_truthy(isMatch('/a', '/a')); + expect_truthy(isMatch('/a/', '/a/')); + expect_truthy(isMatch('/a/a', '/a/a')); + expect_truthy(isMatch('/a/a/', '/a/a/')); + expect_truthy(isMatch('/a/a/a', '/a/a/a')); + expect_truthy(isMatch('/a/a/a/', '/a/a/a/')); + expect_truthy(isMatch('/a/a/a/a', '/a/a/a/a')); + expect_truthy(isMatch('/a/a/a/a/a', '/a/a/a/a/a')); + + expect_truthy(isMatch('a', 'a')); + expect_truthy(isMatch('a/', 'a/')); + expect_truthy(isMatch('a/a', 'a/a')); + expect_truthy(isMatch('a/a/', 'a/a/')); + expect_truthy(isMatch('a/a/a', 'a/a/a')); + expect_truthy(isMatch('a/a/a/', 'a/a/a/')); + expect_truthy(isMatch('a/a/a/a', 'a/a/a/a')); + expect_truthy(isMatch('a/a/a/a/a', 'a/a/a/a/a')); + }); + + test('should match literal dots', () => { + expect_truthy(isMatch('.', '.')); + expect_truthy(isMatch('..', '..')); + expect_truthy(!isMatch('...', '..')); + expect_truthy(isMatch('...', '...')); + expect_truthy(isMatch('....', '....')); + expect_truthy(!isMatch('....', '...')); + }); + + test('should handle escaped characters as literals', () => { + expect_truthy(!isMatch('abc', 'abc\\*')); + expect_truthy(isMatch('abc*', 'abc\\*')); + }); + + test('should match windows paths', () => { + expect_truthy(isMatch('aaa\\bbb', 'aaa/bbb', { windows: true })); + expect_truthy(isMatch('aaa/bbb', 'aaa/bbb', { windows: true })); + }); +}); diff --git a/packages/node-utils/test/picomatch/options.expandRange.test.ts b/packages/node-utils/test/picomatch/options.expandRange.test.ts new file mode 100644 index 0000000..55fa59e --- /dev/null +++ b/packages/node-utils/test/picomatch/options.expandRange.test.ts @@ -0,0 +1,46 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +import { fill } from "../../src/fill-range.ts"; +const { isMatch } = picomatch; + +describe('options.expandRange', () => { + test('should support a custom function for expanding ranges in brace patterns', () => { + expect_truthy(isMatch('a/c', 'a/{a..c}', { expandRange: (a, b) => `([${a}-${b}])` })); + expect_truthy(!isMatch('a/z', 'a/{a..c}', { expandRange: (a, b) => `([${a}-${b}])` })); + expect_truthy(isMatch('a/99', 'a/{1..100}', { + expandRange(a, b) { + return `(${fill(a, b, { toRegex: true })})`; + } + })); + }); +}); diff --git a/packages/node-utils/test/picomatch/options.format.test.ts b/packages/node-utils/test/picomatch/options.format.test.ts new file mode 100644 index 0000000..ecf5ff4 --- /dev/null +++ b/packages/node-utils/test/picomatch/options.format.test.ts @@ -0,0 +1,88 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +const equal = (actual, expected, msg) => { + expect_deepEqual([].concat(actual).sort(), [].concat(expected).sort(), msg); +}; + +describe('options.format', () => { + + // see https://github.com/isaacs/minimatch/issues/30 + test('should match the string returned by options.format', () => { + const opts = { format: str => str.replace(/\\/g, '/').replace(/^\.\//, ''), strictSlashes: true }; + const fixtures = ['a', './a', 'b', 'a/a', './a/b', 'a/c', './a/x', './a/a/a', 'a/a/b', './a/a/a/a', './a/a/a/a/a', 'x/y', './z/z']; + + expect_truthy(!isMatch('./.a', '*.a', opts)); + expect_truthy(!isMatch('./.a', './*.a', opts)); + expect_truthy(!isMatch('./.a', 'a/**/z/*.md', opts)); + expect_truthy(!isMatch('./a/b/c/d/e/z/c.md', './a/**/j/**/z/*.md', opts)); + expect_truthy(!isMatch('./a/b/c/j/e/z/c.txt', './a/**/j/**/z/*.md', opts)); + expect_truthy(!isMatch('a/b/c/d/e/z/c.md', './a/**/j/**/z/*.md', opts)); + expect_truthy(isMatch('./.a', './.a', opts)); + expect_truthy(isMatch('./a/b/c.md', 'a/**/*.md', opts)); + expect_truthy(isMatch('./a/b/c/d/e/j/n/p/o/z/c.md', './a/**/j/**/z/*.md', opts)); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', '**/*.md', opts)); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', './a/**/z/*.md', opts)); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', 'a/**/z/*.md', opts)); + expect_truthy(isMatch('./a/b/c/j/e/z/c.md', './a/**/j/**/z/*.md', opts)); + expect_truthy(isMatch('./a/b/c/j/e/z/c.md', 'a/**/j/**/z/*.md', opts)); + expect_truthy(isMatch('./a/b/z/.a', './a/**/z/.a', opts)); + expect_truthy(isMatch('./a/b/z/.a', 'a/**/z/.a', opts)); + expect_truthy(isMatch('.a', './.a', opts)); + expect_truthy(isMatch('a/b/c.md', './a/**/*.md', opts)); + expect_truthy(isMatch('a/b/c.md', 'a/**/*.md', opts)); + expect_truthy(isMatch('a/b/c/d/e/z/c.md', 'a/**/z/*.md', opts)); + expect_truthy(isMatch('a/b/c/j/e/z/c.md', 'a/**/j/**/z/*.md', opts)); + expect_truthy(isMatch('./a', '*', opts)); + + expect_truthy(isMatch('./foo/bar.js', '**/foo/**', opts)); + expect_truthy(isMatch('./foo/bar.js', './**/foo/**', opts)); + expect_truthy(isMatch('.\\foo\\bar.js', '**/foo/**', { ...opts, windows: false })); + expect_truthy(isMatch('.\\foo\\bar.js', './**/foo/**', opts)); + equal(match(fixtures, '*', opts), ['a', 'b']); + equal(match(fixtures, '**/a/**', opts), ['a/a', 'a/c', 'a/b', 'a/x', 'a/a/a', 'a/a/b', 'a/a/a/a', 'a/a/a/a/a']); + equal(match(fixtures, '*/*', opts), ['a/a', 'a/b', 'a/c', 'a/x', 'x/y', 'z/z']); + equal(match(fixtures, '*/*/*', opts), ['a/a/a', 'a/a/b']); + equal(match(fixtures, '*/*/*/*', opts), ['a/a/a/a']); + equal(match(fixtures, '*/*/*/*/*', opts), ['a/a/a/a/a']); + equal(match(fixtures, '*', opts), ['a', 'b']); + equal(match(fixtures, '**/a/**', opts), ['a/a', 'a/c', 'a/b', 'a/x', 'a/a/a', 'a/a/b', 'a/a/a/a', 'a/a/a/a/a']); + equal(match(fixtures, 'a/*/a', opts), ['a/a/a']); + equal(match(fixtures, 'a/*', opts), ['a/a', 'a/b', 'a/c', 'a/x']); + equal(match(fixtures, 'a/*/*', opts), ['a/a/a', 'a/a/b']); + equal(match(fixtures, 'a/*/*/*', opts), ['a/a/a/a']); + equal(match(fixtures, 'a/*/*/*/*', opts), ['a/a/a/a/a']); + equal(match(fixtures, 'a/*/a', opts), ['a/a/a']); + }); +}); diff --git a/packages/node-utils/test/picomatch/options.ignore.test.ts b/packages/node-utils/test/picomatch/options.ignore.test.ts new file mode 100644 index 0000000..34aae90 --- /dev/null +++ b/packages/node-utils/test/picomatch/options.ignore.test.ts @@ -0,0 +1,83 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('options.ignore', () => { + test('should not match ignored patterns', () => { + expect_truthy(isMatch('a+b/src/glimini.js', 'a+b/src/*.js', { ignore: ['**/f*'] })); + expect_truthy(!isMatch('a+b/src/glimini.js', 'a+b/src/*.js', { ignore: ['**/g*'] })); + expect_truthy(isMatch('+b/src/glimini.md', '+b/src/*', { ignore: ['**/*.js'] })); + expect_truthy(!isMatch('+b/src/glimini.js', '+b/src/*', { ignore: ['**/*.js'] })); + }); + + const negations = ['a/a', 'a/b', 'a/c', 'a/d', 'a/e', 'b/a', 'b/b', 'b/c']; + const globs = ['.a', '.a/a', '.a/a/a', '.a/a/a/a', 'a', 'a/.a', 'a/a', 'a/a/.a', 'a/a/a', 'a/a/a/a', 'a/a/a/a/a', 'a/a/b', 'a/b', 'a/b/c', 'a/c', 'a/x', 'b', 'b/b/b', 'b/b/c', 'c/c/c', 'e/f/g', 'h/i/a', 'x/x/x', 'x/y', 'z/z', 'z/z/z'].sort(); + + test('should filter out ignored patterns', () => { + const opts = { ignore: ['a/**'], strictSlashes: true }; + const dotOpts = { ...opts, dot: true }; + + expect_deepEqual(match(globs, '*', opts), ['a', 'b']); + expect_deepEqual(match(globs, '*', { ...opts, strictSlashes: false }), ['b']); + expect_deepEqual(match(globs, '*', { ignore: '**/a' }), ['b']); + expect_deepEqual(match(globs, '*/*', opts), ['x/y', 'z/z']); + expect_deepEqual(match(globs, '*/*/*', opts), ['b/b/b', 'b/b/c', 'c/c/c', 'e/f/g', 'h/i/a', 'x/x/x', 'z/z/z']); + expect_deepEqual(match(globs, '*/*/*/*', opts), []); + expect_deepEqual(match(globs, '*/*/*/*/*', opts), []); + expect_deepEqual(match(globs, 'a/*', opts), []); + expect_deepEqual(match(globs, '**/*/x', opts), ['x/x/x']); + expect_deepEqual(match(globs, '**/*/[b-z]', opts), ['b/b/b', 'b/b/c', 'c/c/c', 'e/f/g', 'x/x/x', 'x/y', 'z/z', 'z/z/z']); + + expect_deepEqual(match(globs, '*', { ignore: '**/a', dot: true }), ['.a', 'b']); + expect_deepEqual(match(globs, '*', dotOpts), ['.a', 'a', 'b']); + expect_deepEqual(match(globs, '*/*', dotOpts), ['.a/a', 'x/y', 'z/z'].sort()); + expect_deepEqual(match(globs, '*/*/*', dotOpts), ['.a/a/a', 'b/b/b', 'b/b/c', 'c/c/c', 'e/f/g', 'h/i/a', 'x/x/x', 'z/z/z'].sort()); + expect_deepEqual(match(globs, '*/*/*/*', dotOpts), ['.a/a/a/a']); + expect_deepEqual(match(globs, '*/*/*/*/*', dotOpts), []); + expect_deepEqual(match(globs, 'a/*', dotOpts), []); + expect_deepEqual(match(globs, '**/*/x', dotOpts), ['x/x/x']); + + // see https://github.com/jonschlinkert/micromatch/issues/79 + expect_deepEqual(match(['foo.js', 'a/foo.js'], '**/foo.js'), ['foo.js', 'a/foo.js']); + expect_deepEqual(match(['foo.js', 'a/foo.js'], '**/foo.js', { dot: true }), ['foo.js', 'a/foo.js']); + + expect_deepEqual(match(negations, '!b/a', opts), ['b/b', 'b/c']); + expect_deepEqual(match(negations, '!b/(a)', opts), ['b/b', 'b/c']); + expect_deepEqual(match(negations, '!(b/(a))', opts), ['b/b', 'b/c']); + expect_deepEqual(match(negations, '!(b/a)', opts), ['b/b', 'b/c']); + + expect_deepEqual(match(negations, '**'), negations, 'nothing is ignored'); + expect_deepEqual(match(negations, '**', { ignore: ['*/b', '*/a'] }), ['a/c', 'a/d', 'a/e', 'b/c']); + expect_deepEqual(match(negations, '**', { ignore: ['**'] }), []); + }); +}); diff --git a/packages/node-utils/test/picomatch/options.maxExtglobRecursion.test.ts b/packages/node-utils/test/picomatch/options.maxExtglobRecursion.test.ts new file mode 100644 index 0000000..b0cc514 --- /dev/null +++ b/packages/node-utils/test/picomatch/options.maxExtglobRecursion.test.ts @@ -0,0 +1,162 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch, makeRe } = picomatch; + +describe('options.maxExtglobRecursion', () => { + test('should literalize risky repeated extglobs by default', () => { + expect_equal( + makeRe('+(a|aa)').source, + '^(?:\\+\\(a\\|aa\\))$' + ); + expect_equal( + makeRe('+(*|?)').source, + '^(?:\\+\\(\\*\\|\\?\\))$' + ); + expect_equal( + makeRe('+(+(a))').source, + '^(?:\\+\\(\\+\\(a\\)\\))$' + ); + expect_equal( + makeRe('*(+(a))').source, + '^(?:\\*\\(\\+\\(a\\)\\))$' + ); + + expect_truthy(!isMatch('a'.repeat(20) + 'b', '+(a|aa)')); + expect_truthy(!isMatch('a'.repeat(12) + '!', '+(+(a))')); + }); + + test('should preserve non-risky extglobs by default', () => { + expect_truthy(isMatch('abcabc', '+(abc)')); + expect_truthy(isMatch('foobar', '*(foo|bar)')); + expect_truthy(isMatch('a', '(a|@(b|c)|d)')); + expect_truthy(isMatch('fffooo', '*(*(f)*(o))')); + expect_truthy(isMatch('abc', '+(*)c')); + }); + + test('should allow limited nested repeated extglobs when configured', () => { + expect_equal( + makeRe('+(+(a))', { maxExtglobRecursion: 1 }).source, + '^(?:(?=.)(?:(?:a)+)+)$' + ); + expect_equal( + makeRe('*(+(a))', { maxExtglobRecursion: 1 }).source, + '^(?:(?=.)(?:(?:a)+)*)$' + ); + + expect_truthy(isMatch('aaa', '+(+(a))', { maxExtglobRecursion: 1 })); + expect_truthy(isMatch('aaa', '*(+(a))', { maxExtglobRecursion: 1 })); + }); + + test('should still block ambiguous repeated alternation when recursion is allowed', () => { + expect_equal( + makeRe('+(a|aa)', { maxExtglobRecursion: 1 }).source, + '^(?:\\+\\(a\\|aa\\))$' + ); + expect_equal( + makeRe('+(*|?)', { maxExtglobRecursion: 1 }).source, + '^(?:\\+\\(\\*\\|\\?\\))$' + ); + }); + + test('should rewrite risky repeated extglobs embedded in larger patterns', () => { + expect_equal( + makeRe('foo/+(a|aa)/bar').source, + '^(?:foo\\/\\+\\(a\\|aa\\)\\/bar)$' + ); + expect_equal( + makeRe('x+(a|aa)y').source, + '^(?:x\\+\\(a\\|aa\\)y)$' + ); + + expect_truthy(isMatch('foo/+(a|aa)/bar', 'foo/+(a|aa)/bar')); + expect_truthy(!isMatch('foo/aa/bar', 'foo/+(a|aa)/bar')); + expect_truthy(isMatch('x+(a|aa)y', 'x+(a|aa)y')); + expect_truthy(!isMatch('xaay', 'x+(a|aa)y')); + }); + + test('should rewrite star-only repeated extglobs embedded in larger patterns', () => { + expect_equal( + makeRe('pre*(*(f)*(o))post').source, + '^(?:pre[fo]*post)$' + ); + + expect_truthy(isMatch('prefoopost', 'pre*(*(f)*(o))post')); + }); + + test('should rewrite star-only repeated extglobs', () => { + expect_equal( + makeRe('*(*(f))').source, + '^(?:(?=.)f*)$' + ); + + expect_truthy(isMatch('fff', '*(*(f))')); + }); + + test('should preserve capture behavior for rewritten repeated extglobs', () => { + const embedded = makeRe('foo/+(a|aa)/bar', { capture: true }); + expect_equal(embedded.source, '^(?:foo\\/\\+\\(a\\|aa\\)\\/bar)$'); + expect_deepEqual( + Array.from(embedded.exec('foo/+(a|aa)/bar')), + ['foo/+(a|aa)/bar'] + ); + + const simplified = makeRe('*(*(f)*(o))', { capture: true }); + expect_equal(simplified.source, '^(?:(?=.)([fo]*))$'); + expect_deepEqual( + Array.from(simplified.exec('fffooo')), + ['fffooo', 'fffooo'] + ); + }); + + test('should only rewrite the risky repeated extglob when adjacent extglobs are present', () => { + expect_equal( + makeRe('+(a|aa)@(x)').source, + '^(?:\\+\\(a\\|aa\\)(x))$' + ); + + expect_truthy(isMatch('+(a|aa)x', '+(a|aa)@(x)')); + expect_truthy(!isMatch('aaax', '+(a|aa)@(x)')); + }); + test('should disable the safeguard when maxExtglobRecursion is false', () => { + expect_truthy( + /\(\?:a\|aa\)\+/.test( + makeRe('+(a|aa)', { maxExtglobRecursion: false }).source + ) + ); + expect_truthy( + /\(\?:\(\?:a\)\+\)\+/.test( + makeRe('+(+(a))', { maxExtglobRecursion: false }).source + ) + ); + }); +}); diff --git a/packages/node-utils/test/picomatch/options.noextglob.test.ts b/packages/node-utils/test/picomatch/options.noextglob.test.ts new file mode 100644 index 0000000..45553fb --- /dev/null +++ b/packages/node-utils/test/picomatch/options.noextglob.test.ts @@ -0,0 +1,49 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('options.noextglob', () => { + test('should disable extglob support when options.noextglob is true', () => { + expect_truthy(isMatch('a+z', 'a+(z)', { noextglob: true })); + expect_truthy(!isMatch('az', 'a+(z)', { noextglob: true })); + expect_truthy(!isMatch('azz', 'a+(z)', { noextglob: true })); + expect_truthy(!isMatch('azzz', 'a+(z)', { noextglob: true })); + }); + + test('should work with noext alias to support minimatch', () => { + expect_truthy(isMatch('a+z', 'a+(z)', { noext: true })); + expect_truthy(!isMatch('az', 'a+(z)', { noext: true })); + expect_truthy(!isMatch('azz', 'a+(z)', { noext: true })); + expect_truthy(!isMatch('azzz', 'a+(z)', { noext: true })); + }); +}); diff --git a/packages/node-utils/test/picomatch/options.noglobstar.test.ts b/packages/node-utils/test/picomatch/options.noglobstar.test.ts new file mode 100644 index 0000000..60af0d2 --- /dev/null +++ b/packages/node-utils/test/picomatch/options.noglobstar.test.ts @@ -0,0 +1,42 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('options.noglobstar', () => { + test('should disable extglob support when options.noglobstar is true', () => { + expect_truthy(isMatch('a/b/c', '**', { noglobstar: false })); + expect_truthy(!isMatch('a/b/c', '**', { noglobstar: true })); + expect_truthy(isMatch('a/b/c', 'a/**', { noglobstar: false })); + expect_truthy(!isMatch('a/b/c', 'a/**', { noglobstar: true })); + }); +}); diff --git a/packages/node-utils/test/picomatch/options.onMatch.test.ts b/packages/node-utils/test/picomatch/options.onMatch.test.ts new file mode 100644 index 0000000..3eda74d --- /dev/null +++ b/packages/node-utils/test/picomatch/options.onMatch.test.ts @@ -0,0 +1,95 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +const equal = (actual, expected, msg) => { + expect_deepEqual([].concat(actual).sort(), [].concat(expected).sort(), msg); +}; + +const format = str => str.replace(/^\.\//, ''); +const options = () => { + return { + format, + onMatch({ pattern, regex, input, output }, matches) { + if (output.length > 2 && (output.startsWith('./') || output.startsWith('.\\'))) { + output = output.slice(2); + } + if (matches) { + matches.add(output); + } + } + }; +}; + +describe('options.onMatch', () => { + test('should call options.onMatch on each matching string', () => { + const fixtures = ['a', './a', 'b', 'a/a', './a/b', 'a/c', './a/x', './a/a/a', 'a/a/b', './a/a/a/a', './a/a/a/a/a', 'x/y', './z/z']; + + expect_truthy(!isMatch('./.a', '*.a', { format })); + expect_truthy(!isMatch('./.a', './*.a', { format })); + expect_truthy(!isMatch('./.a', 'a/**/z/*.md', { format })); + expect_truthy(!isMatch('./a/b/c/d/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(!isMatch('./a/b/c/j/e/z/c.txt', './a/**/j/**/z/*.md', { format })); + expect_truthy(!isMatch('a/b/c/d/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./.a', './.a', { format })); + expect_truthy(isMatch('./a/b/c.md', 'a/**/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/j/n/p/o/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', '**/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', './a/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', 'a/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/j/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/j/e/z/c.md', 'a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/z/.a', './a/**/z/.a', { format })); + expect_truthy(isMatch('./a/b/z/.a', 'a/**/z/.a', { format })); + expect_truthy(isMatch('.a', './.a', { format })); + expect_truthy(isMatch('a/b/c.md', './a/**/*.md', { format })); + expect_truthy(isMatch('a/b/c.md', 'a/**/*.md', { format })); + expect_truthy(isMatch('a/b/c/d/e/z/c.md', 'a/**/z/*.md', { format })); + expect_truthy(isMatch('a/b/c/j/e/z/c.md', 'a/**/j/**/z/*.md', { format })); + + equal(match(fixtures, '*', options()), ['a', 'b']); + equal(match(fixtures, '**/a/**', options()), ['a', 'a/a', 'a/c', 'a/b', 'a/x', 'a/a/a', 'a/a/b', 'a/a/a/a', 'a/a/a/a/a']); + equal(match(fixtures, '*/*', options()), ['a/a', 'a/b', 'a/c', 'a/x', 'x/y', 'z/z']); + equal(match(fixtures, '*/*/*', options()), ['a/a/a', 'a/a/b']); + equal(match(fixtures, '*/*/*/*', options()), ['a/a/a/a']); + equal(match(fixtures, '*/*/*/*/*', options()), ['a/a/a/a/a']); + equal(match(fixtures, './*', options()), ['a', 'b']); + equal(match(fixtures, './**/a/**', options()), ['a', 'a/a', 'a/b', 'a/c', 'a/x', 'a/a/a', 'a/a/b', 'a/a/a/a', 'a/a/a/a/a']); + equal(match(fixtures, './a/*/a', options()), ['a/a/a']); + equal(match(fixtures, 'a/*', options()), ['a/a', 'a/b', 'a/c', 'a/x']); + equal(match(fixtures, 'a/*/*', options()), ['a/a/a', 'a/a/b']); + equal(match(fixtures, 'a/*/*/*', options()), ['a/a/a/a']); + equal(match(fixtures, 'a/*/*/*/*', options()), ['a/a/a/a/a']); + equal(match(fixtures, 'a/*/a', options()), ['a/a/a']); + }); +}); diff --git a/packages/node-utils/test/picomatch/options.test.ts b/packages/node-utils/test/picomatch/options.test.ts new file mode 100644 index 0000000..7687c3e --- /dev/null +++ b/packages/node-utils/test/picomatch/options.test.ts @@ -0,0 +1,189 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('options', () => { + describe('options.matchBase', () => { + test('should match the basename of file paths when `options.matchBase` is true', () => { + expect_deepEqual(match(['a/b/c/d.md'], '*.md', { windows: true }), [], 'should not match multiple levels'); + expect_deepEqual(match(['a/b/c/foo.md'], '*.md', { windows: true }), [], 'should not match multiple levels'); + expect_deepEqual(match(['ab', 'acb', 'acb/', 'acb/d/e', 'x/y/acb', 'x/y/acb/d'], 'a?b', { windows: true }), ['acb'], 'should not match multiple levels'); + expect_deepEqual(match(['a/b/c/d.md'], '*.md', { matchBase: true, windows: true }), ['a/b/c/d.md']); + expect_deepEqual(match(['a/b/c/foo.md'], '*.md', { matchBase: true, windows: true }), ['a/b/c/foo.md']); + expect_deepEqual(match(['x/y/acb', 'acb/', 'acb/d/e', 'x/y/acb/d'], 'a?b', { matchBase: true, windows: true }), ['x/y/acb', 'acb/']); + }); + + test('should work with negation patterns', () => { + expect_truthy(isMatch('./x/y.js', '*.js', { matchBase: true, windows: true })); + expect_truthy(!isMatch('./x/y.js', '!*.js', { matchBase: true, windows: true })); + expect_truthy(isMatch('./x/y.js', '**/*.js', { matchBase: true, windows: true })); + expect_truthy(!isMatch('./x/y.js', '!**/*.js', { matchBase: true, windows: true })); + }); + }); + + describe('options.flags', () => { + test('should be case-sensitive by default', () => { + expect_deepEqual(match(['a/b/d/e.md'], 'a/b/D/*.md', { windows: true }), [], 'should not match a dirname'); + expect_deepEqual(match(['a/b/c/e.md'], 'A/b/*/E.md', { windows: true }), [], 'should not match a basename'); + expect_deepEqual(match(['a/b/c/e.md'], 'A/b/C/*.MD', { windows: true }), [], 'should not match a file extension'); + }); + + test('should not be case-sensitive when `i` is set on `options.flags`', () => { + expect_deepEqual(match(['a/b/d/e.md'], 'a/b/D/*.md', { flags: 'i', windows: true }), ['a/b/d/e.md']); + expect_deepEqual(match(['a/b/c/e.md'], 'A/b/*/E.md', { flags: 'i', windows: true }), ['a/b/c/e.md']); + expect_deepEqual(match(['a/b/c/e.md'], 'A/b/C/*.MD', { flags: 'i', windows: true }), ['a/b/c/e.md']); + }); + }); + + describe('options.nocase', () => { + test('should not be case-sensitive when `options.nocase` is true', () => { + expect_deepEqual(match(['a/b/c/e.md'], 'A/b/*/E.md', { nocase: true, windows: true }), ['a/b/c/e.md']); + expect_deepEqual(match(['a/b/c/e.md'], 'A/b/C/*.MD', { nocase: true, windows: true }), ['a/b/c/e.md']); + expect_deepEqual(match(['a/b/c/e.md'], 'A/b/C/*.md', { nocase: true, windows: true }), ['a/b/c/e.md']); + expect_deepEqual(match(['a/b/d/e.md'], 'a/b/D/*.md', { nocase: true, windows: true }), ['a/b/d/e.md']); + }); + + test('should not double-set `i` when both `nocase` and the `i` flag are set', () => { + const opts = { nocase: true, flags: 'i', windows: true }; + expect_deepEqual(match(['a/b/d/e.md'], 'a/b/D/*.md', opts), ['a/b/d/e.md']); + expect_deepEqual(match(['a/b/c/e.md'], 'A/b/*/E.md', opts), ['a/b/c/e.md']); + expect_deepEqual(match(['a/b/c/e.md'], 'A/b/C/*.MD', opts), ['a/b/c/e.md']); + }); + }); + + describe('options.noextglob', () => { + test('should match literal parens when noextglob is true (issue #116)', () => { + expect_truthy(isMatch('a/(dir)', 'a/(dir)', { noextglob: true, windows: true })); + }); + + test('should not match extglobs when noextglob is true', () => { + expect_truthy(!isMatch('ax', '?(a*|b)', { noextglob: true, windows: true })); + expect_deepEqual(match(['a.j.js', 'a.md.js'], '*.*(j).js', { noextglob: true, windows: true }), ['a.j.js']); + expect_deepEqual(match(['a/z', 'a/b', 'a/!(z)'], 'a/!(z)', { noextglob: true, windows: true }), ['a/!(z)']); + expect_deepEqual(match(['a/z', 'a/b'], 'a/!(z)', { noextglob: true, windows: true }), []); + expect_deepEqual(match(['c/a/v'], 'c/!(z)/v', { noextglob: true, windows: true }), []); + expect_deepEqual(match(['c/z/v', 'c/a/v'], 'c/!(z)/v', { noextglob: true, windows: true }), []); + expect_deepEqual(match(['c/z/v', 'c/a/v'], 'c/@(z)/v', { noextglob: true, windows: true }), []); + expect_deepEqual(match(['c/z/v', 'c/a/v'], 'c/+(z)/v', { noextglob: true, windows: true }), []); + expect_deepEqual(match(['c/z/v', 'c/a/v'], 'c/*(z)/v', { noextglob: true, windows: true }), ['c/z/v']); + expect_deepEqual(match(['c/z/v', 'z', 'zf', 'fz'], '?(z)', { noextglob: true, windows: true }), ['fz']); + expect_deepEqual(match(['c/z/v', 'z', 'zf', 'fz'], '+(z)', { noextglob: true, windows: true }), []); + expect_deepEqual(match(['c/z/v', 'z', 'zf', 'fz'], '*(z)', { noextglob: true, windows: true }), ['z', 'fz']); + expect_deepEqual(match(['cz', 'abz', 'az'], 'a@(z)', { noextglob: true, windows: true }), []); + expect_deepEqual(match(['cz', 'abz', 'az'], 'a*@(z)', { noextglob: true, windows: true }), []); + expect_deepEqual(match(['cz', 'abz', 'az'], 'a!(z)', { noextglob: true, windows: true }), []); + expect_deepEqual(match(['cz', 'abz', 'az', 'azz'], 'a?(z)', { noextglob: true, windows: true }), ['abz', 'azz']); + expect_deepEqual(match(['cz', 'abz', 'az', 'azz', 'a+z'], 'a+(z)', { noextglob: true, windows: true }), ['a+z']); + expect_deepEqual(match(['cz', 'abz', 'az'], 'a*(z)', { noextglob: true, windows: true }), ['abz', 'az']); + expect_deepEqual(match(['cz', 'abz', 'az'], 'a**(z)', { noextglob: true, windows: true }), ['abz', 'az']); + expect_deepEqual(match(['cz', 'abz', 'az'], 'a*!(z)', { noextglob: true, windows: true }), []); + }); + }); + + describe('options.unescape', () => { + test('should remove backslashes in glob patterns:', () => { + const fixtures = ['abc', '/a/b/c', '\\a\\b\\c']; + expect_deepEqual(match(fixtures, '\\a\\b\\c', { windows: true }), ['/a/b/c']); + expect_deepEqual(match(fixtures, '\\a\\b\\c', { unescape: true, windows: true }), ['abc', '/a/b/c']); + expect_deepEqual(match(fixtures, '\\a\\b\\c', { unescape: false, windows: true }), ['/a/b/c']); + }); + }); + + describe('options.nonegate', () => { + test('should support the `nonegate` option:', () => { + expect_deepEqual(match(['a/a/a', 'a/b/a', 'b/b/a', 'c/c/a', 'c/c/b'], '!**/a', { windows: true }), ['c/c/b']); + expect_deepEqual(match(['a.md', '!a.md', 'a.txt'], '!*.md', { nonegate: true, windows: true }), ['!a.md']); + expect_deepEqual(match(['!a/a/a', '!a/a', 'a/b/a', 'b/b/a', '!c/c/a', '!c/a'], '!**/a', { nonegate: true, windows: true }), ['!a/a', '!c/a']); + expect_deepEqual(match(['!*.md', '.dotfile.txt', 'a/b/.dotfile'], '!*.md', { nonegate: true, windows: true }), ['!*.md']); + }); + }); + + describe('options.windows', () => { + test('should windows file paths by default', () => { + expect_deepEqual(match(['a\\b\\c.md'], '**/*.md', { windows: true }), ['a/b/c.md']); + expect_deepEqual(match(['a\\b\\c.md'], '**/*.md', { windows: false }), ['a\\b\\c.md']); + }); + + test('should windows absolute paths', () => { + expect_deepEqual(match(['E:\\a\\b\\c.md'], 'E:/**/*.md', { windows: true }), ['E:/a/b/c.md']); + expect_deepEqual(match(['E:\\a\\b\\c.md'], 'E:/**/*.md', { windows: false }), []); + }); + + test('should strip leading `./`', () => { + const fixtures = ['./a', './a/a/a', './a/a/a/a', './a/a/a/a/a', './a/b', './a/x', './z/z', 'a', 'a/a', 'a/a/b', 'a/c', 'b', 'x/y'].sort(); + const format = str => str.replace(/^\.\//, ''); + const opts = { format, windows: true }; + expect_deepEqual(match(fixtures, '*', opts), ['a', 'b']); + expect_deepEqual(match(fixtures, '**/a/**', opts), ['a', 'a/a/a', 'a/a/a/a', 'a/a/a/a/a', 'a/b', 'a/x', 'a/a', 'a/a/b', 'a/c']); + expect_deepEqual(match(fixtures, '*/*', opts), ['a/b', 'a/x', 'z/z', 'a/a', 'a/c', 'x/y']); + expect_deepEqual(match(fixtures, '*/*/*', opts), ['a/a/a', 'a/a/b']); + expect_deepEqual(match(fixtures, '*/*/*/*', opts), ['a/a/a/a']); + expect_deepEqual(match(fixtures, '*/*/*/*/*', opts), ['a/a/a/a/a']); + expect_deepEqual(match(fixtures, './*', opts), ['a', 'b']); + expect_deepEqual(match(fixtures, './**/a/**', opts), ['a', 'a/a/a', 'a/a/a/a', 'a/a/a/a/a', 'a/b', 'a/x', 'a/a', 'a/a/b', 'a/c']); + expect_deepEqual(match(fixtures, './a/*/a', opts), ['a/a/a']); + expect_deepEqual(match(fixtures, 'a/*', opts), ['a/b', 'a/x', 'a/a', 'a/c']); + expect_deepEqual(match(fixtures, 'a/*/*', opts), ['a/a/a', 'a/a/b']); + expect_deepEqual(match(fixtures, 'a/*/*/*', opts), ['a/a/a/a']); + expect_deepEqual(match(fixtures, 'a/*/*/*/*', opts), ['a/a/a/a/a']); + expect_deepEqual(match(fixtures, 'a/*/a', opts), ['a/a/a']); + + expect_deepEqual(match(fixtures, '*', { ...opts, windows: false }), ['a', 'b']); + expect_deepEqual(match(fixtures, '**/a/**', { ...opts, windows: false }), ['a', 'a/a/a', 'a/a/a/a', 'a/a/a/a/a', 'a/b', 'a/x', 'a/a', 'a/a/b', 'a/c']); + expect_deepEqual(match(fixtures, '*/*', { ...opts, windows: false }), ['a/b', 'a/x', 'z/z', 'a/a', 'a/c', 'x/y']); + expect_deepEqual(match(fixtures, '*/*/*', { ...opts, windows: false }), ['a/a/a', 'a/a/b']); + expect_deepEqual(match(fixtures, '*/*/*/*', { ...opts, windows: false }), ['a/a/a/a']); + expect_deepEqual(match(fixtures, '*/*/*/*/*', { ...opts, windows: false }), ['a/a/a/a/a']); + expect_deepEqual(match(fixtures, './*', { ...opts, windows: false }), ['a', 'b']); + expect_deepEqual(match(fixtures, './**/a/**', { ...opts, windows: false }), ['a', 'a/a/a', 'a/a/a/a', 'a/a/a/a/a', 'a/b', 'a/x', 'a/a', 'a/a/b', 'a/c']); + expect_deepEqual(match(fixtures, './a/*/a', { ...opts, windows: false }), ['a/a/a']); + expect_deepEqual(match(fixtures, 'a/*', { ...opts, windows: false }), ['a/b', 'a/x', 'a/a', 'a/c']); + expect_deepEqual(match(fixtures, 'a/*/*', { ...opts, windows: false }), ['a/a/a', 'a/a/b']); + expect_deepEqual(match(fixtures, 'a/*/*/*', { ...opts, windows: false }), ['a/a/a/a']); + expect_deepEqual(match(fixtures, 'a/*/*/*/*', { ...opts, windows: false }), ['a/a/a/a/a']); + expect_deepEqual(match(fixtures, 'a/*/a', { ...opts, windows: false }), ['a/a/a']); + }); + }); + + describe('windows', () => { + test('should convert file paths to posix slashes', () => { + expect_deepEqual(match(['a\\b\\c.md'], '**/*.md', { windows: true }), ['a/b/c.md']); + expect_deepEqual(match(['a\\b\\c.md'], '**/*.md', { windows: false }), ['a\\b\\c.md']); + }); + + test('should convert absolute paths to posix slashes', () => { + expect_deepEqual(match(['E:\\a\\b\\c.md'], 'E:/**/*.md', { windows: true }), ['E:/a/b/c.md']); + expect_deepEqual(match(['E:\\a\\b\\c.md'], 'E:/**/*.md', { windows: false }), []); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/parens.test.ts b/packages/node-utils/test/picomatch/parens.test.ts new file mode 100644 index 0000000..a2925e4 --- /dev/null +++ b/packages/node-utils/test/picomatch/parens.test.ts @@ -0,0 +1,51 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('parens (non-extglobs)', () => { + test('should support stars following parens', () => { + expect_truthy(isMatch('a', '(a)*')); + expect_truthy(isMatch('az', '(a)*')); + expect_truthy(!isMatch('zz', '(a)*')); + expect_truthy(isMatch('ab', '(a|b)*')); + expect_truthy(isMatch('abc', '(a|b)*')); + expect_truthy(isMatch('aa', '(a)*')); + expect_truthy(isMatch('aaab', '(a|b)*')); + expect_truthy(isMatch('aaabbb', '(a|b)*')); + }); + + test('should not match slashes with single stars', () => { + expect_truthy(!isMatch('a/b', '(a)*')); + expect_truthy(!isMatch('a/b', '(a|b)*')); + }); +}); diff --git a/packages/node-utils/test/picomatch/posix-classes.test.ts b/packages/node-utils/test/picomatch/posix-classes.test.ts new file mode 100644 index 0000000..52b6a08 --- /dev/null +++ b/packages/node-utils/test/picomatch/posix-classes.test.ts @@ -0,0 +1,382 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const pm = picomatch; +const { makeRe, parse } = pm; + +const opts = { strictSlashes: true, posix: true, regex: true }; +const isMatch = (...args) => pm.isMatch(...args, opts); +const convert = (...args) => { + const state = parse(...args, opts); + return state.output; +}; + +describe('posix classes', () => { + describe('posix bracket type conversion', () => { + test('should create regex character classes from POSIX bracket expressions:', () => { + expect_equal(convert('foo[[:lower:]]bar'), 'foo[a-z]bar'); + expect_equal(convert('foo[[:lower:][:upper:]]bar'), 'foo[a-zA-Z]bar'); + expect_equal(convert('[[:alpha:]123]'), '(?=.)[a-zA-Z123]'); + expect_equal(convert('[[:lower:]]'), '(?=.)[a-z]'); + expect_equal(convert('[![:lower:]]'), '(?=.)[^a-z]'); + expect_equal(convert('[[:digit:][:upper:][:space:]]'), '(?=.)[0-9A-Z \\t\\r\\n\\v\\f]'); + expect_equal(convert('[[:xdigit:]]'), '(?=.)[A-Fa-f0-9]'); + expect_equal(convert('[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]'), '(?=.)[a-zA-Z0-9a-zA-Z \\t\\x00-\\x1F\\x7F0-9\\x21-\\x7Ea-z\\x20-\\x7E \\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~ \\t\\r\\n\\v\\fA-ZA-Fa-f0-9]'); + expect_equal(convert('[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]'), '(?=.)[^a-zA-Z0-9a-zA-Z \\t\\x00-\\x1F\\x7F0-9a-z \\t\\r\\n\\v\\fA-ZA-Fa-f0-9]'); + expect_equal(convert('[a-c[:digit:]x-z]'), '(?=.)[a-c0-9x-z]'); + expect_equal(convert('[_[:alpha:]][_[:alnum:]][_[:alnum:]]*'), '(?=.)[_a-zA-Z][_a-zA-Z0-9][_a-zA-Z0-9]*', []); + }); + }); + + describe('.isMatch', () => { + test('should support POSIX.2 character classes', () => { + expect_truthy(isMatch('e', '[[:xdigit:]]')); + + expect_truthy(isMatch('a', '[[:alpha:]123]')); + expect_truthy(isMatch('1', '[[:alpha:]123]')); + expect_truthy(!isMatch('5', '[[:alpha:]123]')); + expect_truthy(isMatch('A', '[[:alpha:]123]')); + + expect_truthy(isMatch('A', '[[:alpha:]]')); + expect_truthy(!isMatch('9', '[[:alpha:]]')); + expect_truthy(isMatch('b', '[[:alpha:]]')); + + expect_truthy(!isMatch('A', '[![:alpha:]]')); + expect_truthy(isMatch('9', '[![:alpha:]]')); + expect_truthy(!isMatch('b', '[![:alpha:]]')); + + expect_truthy(!isMatch('A', '[^[:alpha:]]')); + expect_truthy(isMatch('9', '[^[:alpha:]]')); + expect_truthy(!isMatch('b', '[^[:alpha:]]')); + + expect_truthy(!isMatch('A', '[[:digit:]]')); + expect_truthy(isMatch('9', '[[:digit:]]')); + expect_truthy(!isMatch('b', '[[:digit:]]')); + + expect_truthy(isMatch('A', '[^[:digit:]]')); + expect_truthy(!isMatch('9', '[^[:digit:]]')); + expect_truthy(isMatch('b', '[^[:digit:]]')); + + expect_truthy(isMatch('A', '[![:digit:]]')); + expect_truthy(!isMatch('9', '[![:digit:]]')); + expect_truthy(isMatch('b', '[![:digit:]]')); + + expect_truthy(isMatch('a', '[[:lower:]]')); + expect_truthy(!isMatch('A', '[[:lower:]]')); + expect_truthy(!isMatch('9', '[[:lower:]]')); + + expect_truthy(isMatch('a', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + expect_truthy(isMatch('l', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + expect_truthy(isMatch('p', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + expect_truthy(isMatch('h', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + expect_truthy(isMatch(':', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + expect_truthy(!isMatch('b', '[:alpha:]'), 'invalid posix bracket, but valid char class'); + }); + + test('should support multiple posix brackets in one character class', () => { + expect_truthy(isMatch('9', '[[:lower:][:digit:]]')); + expect_truthy(isMatch('a', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('A', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('aa', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('99', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('a9', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('9a', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('aA', '[[:lower:][:digit:]]')); + expect_truthy(!isMatch('9A', '[[:lower:][:digit:]]')); + expect_truthy(isMatch('aa', '[[:lower:][:digit:]]+')); + expect_truthy(isMatch('99', '[[:lower:][:digit:]]+')); + expect_truthy(isMatch('a9', '[[:lower:][:digit:]]+')); + expect_truthy(isMatch('9a', '[[:lower:][:digit:]]+')); + expect_truthy(!isMatch('aA', '[[:lower:][:digit:]]+')); + expect_truthy(!isMatch('9A', '[[:lower:][:digit:]]+')); + expect_truthy(isMatch('a', '[[:lower:][:digit:]]*')); + expect_truthy(!isMatch('A', '[[:lower:][:digit:]]*')); + expect_truthy(!isMatch('AA', '[[:lower:][:digit:]]*')); + expect_truthy(isMatch('aa', '[[:lower:][:digit:]]*')); + expect_truthy(isMatch('aaa', '[[:lower:][:digit:]]*')); + expect_truthy(isMatch('999', '[[:lower:][:digit:]]*')); + }); + + test('should match word characters', () => { + expect_truthy(!isMatch('a c', 'a[[:word:]]+c')); + expect_truthy(!isMatch('a.c', 'a[[:word:]]+c')); + expect_truthy(!isMatch('a.xy.zc', 'a[[:word:]]+c')); + expect_truthy(!isMatch('a.zc', 'a[[:word:]]+c')); + expect_truthy(!isMatch('abq', 'a[[:word:]]+c')); + expect_truthy(!isMatch('axy zc', 'a[[:word:]]+c')); + expect_truthy(!isMatch('axy', 'a[[:word:]]+c')); + expect_truthy(!isMatch('axy.zc', 'a[[:word:]]+c')); + expect_truthy(isMatch('a123c', 'a[[:word:]]+c')); + expect_truthy(isMatch('a1c', 'a[[:word:]]+c')); + expect_truthy(isMatch('abbbbc', 'a[[:word:]]+c')); + expect_truthy(isMatch('abbbc', 'a[[:word:]]+c')); + expect_truthy(isMatch('abbc', 'a[[:word:]]+c')); + expect_truthy(isMatch('abc', 'a[[:word:]]+c')); + + expect_truthy(!isMatch('a c', 'a[[:word:]]+')); + expect_truthy(!isMatch('a.c', 'a[[:word:]]+')); + expect_truthy(!isMatch('a.xy.zc', 'a[[:word:]]+')); + expect_truthy(!isMatch('a.zc', 'a[[:word:]]+')); + expect_truthy(!isMatch('axy zc', 'a[[:word:]]+')); + expect_truthy(!isMatch('axy.zc', 'a[[:word:]]+')); + expect_truthy(isMatch('a123c', 'a[[:word:]]+')); + expect_truthy(isMatch('a1c', 'a[[:word:]]+')); + expect_truthy(isMatch('abbbbc', 'a[[:word:]]+')); + expect_truthy(isMatch('abbbc', 'a[[:word:]]+')); + expect_truthy(isMatch('abbc', 'a[[:word:]]+')); + expect_truthy(isMatch('abc', 'a[[:word:]]+')); + expect_truthy(isMatch('abq', 'a[[:word:]]+')); + expect_truthy(isMatch('axy', 'a[[:word:]]+')); + expect_truthy(isMatch('axyzc', 'a[[:word:]]+')); + expect_truthy(isMatch('axyzc', 'a[[:word:]]+')); + }); + + test('should not create an invalid posix character class:', () => { + expect_equal(convert('[:al:]'), '(?:\\[:al:\\]|[:al:])'); + expect_equal(convert('[abc[:punct:][0-9]'), '(?=.)[abc\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~\\[0-9]'); + }); + + test('should return `true` when the pattern matches:', () => { + expect_truthy(isMatch('a', '[[:lower:]]')); + expect_truthy(isMatch('A', '[[:upper:]]')); + expect_truthy(isMatch('A', '[[:digit:][:upper:][:space:]]')); + expect_truthy(isMatch('1', '[[:digit:][:upper:][:space:]]')); + expect_truthy(isMatch(' ', '[[:digit:][:upper:][:space:]]')); + expect_truthy(isMatch('5', '[[:xdigit:]]')); + expect_truthy(isMatch('f', '[[:xdigit:]]')); + expect_truthy(isMatch('D', '[[:xdigit:]]')); + expect_truthy(isMatch('_', '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]')); + expect_truthy(isMatch('_', '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]')); + expect_truthy(isMatch('.', '[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]')); + expect_truthy(isMatch('5', '[a-c[:digit:]x-z]')); + expect_truthy(isMatch('b', '[a-c[:digit:]x-z]')); + expect_truthy(isMatch('y', '[a-c[:digit:]x-z]')); + }); + + test('should return `false` when the pattern does not match:', () => { + expect_truthy(!isMatch('A', '[[:lower:]]')); + expect_truthy(isMatch('A', '[![:lower:]]')); + expect_truthy(!isMatch('a', '[[:upper:]]')); + expect_truthy(!isMatch('a', '[[:digit:][:upper:][:space:]]')); + expect_truthy(!isMatch('.', '[[:digit:][:upper:][:space:]]')); + expect_truthy(!isMatch('.', '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]')); + expect_truthy(!isMatch('q', '[a-c[:digit:]x-z]')); + }); + }); + + describe('literals', () => { + test('should match literal brackets when escaped', () => { + expect_truthy(isMatch('a [b]', 'a [b]')); + expect_truthy(isMatch('a b', 'a [b]')); + + expect_truthy(isMatch('a [b] c', 'a [b] c')); + expect_truthy(isMatch('a b c', 'a [b] c')); + + expect_truthy(isMatch('a [b]', 'a \\[b\\]')); + expect_truthy(!isMatch('a b', 'a \\[b\\]')); + + expect_truthy(isMatch('a [b]', 'a ([b])')); + expect_truthy(isMatch('a b', 'a ([b])')); + + expect_truthy(isMatch('a b', 'a (\\[b\\]|[b])')); + expect_truthy(isMatch('a [b]', 'a (\\[b\\]|[b])')); + }); + }); + + describe('.makeRe()', () => { + test('should make a regular expression for the given pattern:', () => { + expect_deepEqual(makeRe('[[:alpha:]123]', opts), /^(?:(?=.)[a-zA-Z123])$/); + expect_deepEqual(makeRe('[![:lower:]]', opts), /^(?:(?=.)[^a-z])$/); + }); + }); + + describe('POSIX: From the test suite for the POSIX.2 (BRE) pattern matching code:', () => { + test('First, test POSIX.2 character classes', () => { + expect_truthy(isMatch('e', '[[:xdigit:]]')); + expect_truthy(isMatch('1', '[[:xdigit:]]')); + expect_truthy(isMatch('a', '[[:alpha:]123]')); + expect_truthy(isMatch('1', '[[:alpha:]123]')); + }); + + test('should match using POSIX.2 negation patterns', () => { + expect_truthy(isMatch('9', '[![:alpha:]]')); + expect_truthy(isMatch('9', '[^[:alpha:]]')); + }); + + test('should match word characters', () => { + expect_truthy(isMatch('A', '[[:word:]]')); + expect_truthy(isMatch('B', '[[:word:]]')); + expect_truthy(isMatch('a', '[[:word:]]')); + expect_truthy(isMatch('b', '[[:word:]]')); + }); + + test('should match digits with word class', () => { + expect_truthy(isMatch('1', '[[:word:]]')); + expect_truthy(isMatch('2', '[[:word:]]')); + }); + + test('should not digits', () => { + expect_truthy(isMatch('1', '[[:digit:]]')); + expect_truthy(isMatch('2', '[[:digit:]]')); + }); + + test('should not match word characters with digit class', () => { + expect_truthy(!isMatch('a', '[[:digit:]]')); + expect_truthy(!isMatch('A', '[[:digit:]]')); + }); + + test('should match uppercase alpha characters', () => { + expect_truthy(isMatch('A', '[[:upper:]]')); + expect_truthy(isMatch('B', '[[:upper:]]')); + }); + + test('should not match lowercase alpha characters', () => { + expect_truthy(!isMatch('a', '[[:upper:]]')); + expect_truthy(!isMatch('b', '[[:upper:]]')); + }); + + test('should not match digits with upper class', () => { + expect_truthy(!isMatch('1', '[[:upper:]]')); + expect_truthy(!isMatch('2', '[[:upper:]]')); + }); + + test('should match lowercase alpha characters', () => { + expect_truthy(isMatch('a', '[[:lower:]]')); + expect_truthy(isMatch('b', '[[:lower:]]')); + }); + + test('should not match uppercase alpha characters', () => { + expect_truthy(!isMatch('A', '[[:lower:]]')); + expect_truthy(!isMatch('B', '[[:lower:]]')); + }); + + test('should match one lower and one upper character', () => { + expect_truthy(isMatch('aA', '[[:lower:]][[:upper:]]')); + expect_truthy(!isMatch('AA', '[[:lower:]][[:upper:]]')); + expect_truthy(!isMatch('Aa', '[[:lower:]][[:upper:]]')); + }); + + test('should match hexadecimal digits', () => { + expect_truthy(isMatch('ababab', '[[:xdigit:]]*')); + expect_truthy(isMatch('020202', '[[:xdigit:]]*')); + expect_truthy(isMatch('900', '[[:xdigit:]]*')); + }); + + test('should match punctuation characters (\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~)', () => { + expect_truthy(isMatch('!', '[[:punct:]]')); + expect_truthy(isMatch('?', '[[:punct:]]')); + expect_truthy(isMatch('#', '[[:punct:]]')); + expect_truthy(isMatch('&', '[[:punct:]]')); + expect_truthy(isMatch('@', '[[:punct:]]')); + expect_truthy(isMatch('+', '[[:punct:]]')); + expect_truthy(isMatch('*', '[[:punct:]]')); + expect_truthy(isMatch(':', '[[:punct:]]')); + expect_truthy(isMatch('=', '[[:punct:]]')); + expect_truthy(isMatch('|', '[[:punct:]]')); + expect_truthy(isMatch('|++', '[[:punct:]]*')); + }); + + test('should only match one character', () => { + expect_truthy(!isMatch('?*+', '[[:punct:]]')); + }); + + test('should only match zero or more punctuation characters', () => { + expect_truthy(isMatch('?*+', '[[:punct:]]*')); + expect_truthy(isMatch('foo', 'foo[[:punct:]]*')); + expect_truthy(isMatch('foo?*+', 'foo[[:punct:]]*')); + }); + + test('invalid character class expressions are just characters to be matched', () => { + expect_truthy(isMatch('a', '[:al:]')); + expect_truthy(isMatch('a', '[[:al:]')); + expect_truthy(isMatch('!', '[abc[:punct:][0-9]')); + }); + + test('should match the start of a valid sh identifier', () => { + expect_truthy(isMatch('PATH', '[_[:alpha:]]*')); + }); + + test('should match the first two characters of a valid sh identifier', () => { + expect_truthy(isMatch('PATH', '[_[:alpha:]][_[:alnum:]]*')); + }); + + test('should match multiple posix classses', () => { + expect_truthy(isMatch('a1B', '[[:alpha:]][[:digit:]][[:upper:]]')); + expect_truthy(!isMatch('a1b', '[[:alpha:]][[:digit:]][[:upper:]]')); + expect_truthy(isMatch('.', '[[:digit:][:punct:][:space:]]')); + expect_truthy(!isMatch('a', '[[:digit:][:punct:][:space:]]')); + expect_truthy(isMatch('!', '[[:digit:][:punct:][:space:]]')); + expect_truthy(!isMatch('!', '[[:digit:]][[:punct:]][[:space:]]')); + expect_truthy(isMatch('1! ', '[[:digit:]][[:punct:]][[:space:]]')); + expect_truthy(!isMatch('1! ', '[[:digit:]][[:punct:]][[:space:]]')); + }); + + /** + * Some of these tests (and their descriptions) were ported directly + * from the Bash 4.3 unit tests. + */ + + test('how about A?', () => { + expect_truthy(isMatch('9', '[[:digit:]]')); + expect_truthy(!isMatch('X', '[[:digit:]]')); + expect_truthy(isMatch('aB', '[[:lower:]][[:upper:]]')); + expect_truthy(isMatch('a', '[[:alpha:][:digit:]]')); + expect_truthy(isMatch('3', '[[:alpha:][:digit:]]')); + expect_truthy(!isMatch('aa', '[[:alpha:][:digit:]]')); + expect_truthy(!isMatch('a3', '[[:alpha:][:digit:]]')); + expect_truthy(!isMatch('a', '[[:alpha:]\\]')); + expect_truthy(!isMatch('b', '[[:alpha:]\\]')); + }); + + test('OK, what\'s a tab? is it a blank? a space?', () => { + expect_truthy(isMatch('\t', '[[:blank:]]')); + expect_truthy(isMatch('\t', '[[:space:]]')); + expect_truthy(isMatch(' ', '[[:space:]]')); + }); + + test('let\'s check out characters in the ASCII range', () => { + expect_truthy(!isMatch('\\377', '[[:ascii:]]')); + expect_truthy(!isMatch('9', '[1[:alpha:]123]')); + }); + + test('punctuation', () => { + expect_truthy(!isMatch(' ', '[[:punct:]]')); + }); + + test('graph', () => { + expect_truthy(isMatch('A', '[[:graph:]]')); + expect_truthy(!isMatch('\\b', '[[:graph:]]')); + expect_truthy(!isMatch('\\n', '[[:graph:]]')); + expect_truthy(!isMatch('\\s', '[[:graph:]]')); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/qmarks.test.ts b/packages/node-utils/test/picomatch/qmarks.test.ts new file mode 100644 index 0000000..0f33301 --- /dev/null +++ b/packages/node-utils/test/picomatch/qmarks.test.ts @@ -0,0 +1,157 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('qmarks and stars', () => { + test('should match question marks with question marks', () => { + expect_deepEqual(match(['?', '??', '???'], '?'), ['?']); + expect_deepEqual(match(['?', '??', '???'], '??'), ['??']); + expect_deepEqual(match(['?', '??', '???'], '???'), ['???']); + }); + + test('should match question marks and stars with question marks and stars', () => { + expect_deepEqual(match(['?', '??', '???'], '?*'), ['?', '??', '???']); + expect_deepEqual(match(['?', '??', '???'], '*?'), ['?', '??', '???']); + expect_deepEqual(match(['?', '??', '???'], '?*?'), ['??', '???']); + expect_deepEqual(match(['?*', '?*?', '?*?*?'], '?*'), ['?*', '?*?', '?*?*?']); + expect_deepEqual(match(['?*', '?*?', '?*?*?'], '*?'), ['?*', '?*?', '?*?*?']); + expect_deepEqual(match(['?*', '?*?', '?*?*?'], '?*?'), ['?*', '?*?', '?*?*?']); + }); + + test('should support consecutive stars and question marks', () => { + expect_deepEqual(match(['aaa', 'aac', 'abc'], 'a*?c'), ['aac', 'abc']); + expect_deepEqual(match(['abc', 'abb', 'acc'], 'a**?c'), ['abc', 'acc']); + expect_deepEqual(match(['abc', 'aaaabbbbbbccccc'], 'a*****?c'), ['abc', 'aaaabbbbbbccccc']); + expect_deepEqual(match(['a', 'ab', 'abc', 'abcd'], '*****?'), ['a', 'ab', 'abc', 'abcd']); + expect_deepEqual(match(['a', 'ab', 'abc', 'abcd'], '*****??'), ['ab', 'abc', 'abcd']); + expect_deepEqual(match(['a', 'ab', 'abc', 'abcd'], '?*****??'), ['abc', 'abcd']); + expect_deepEqual(match(['abc', 'abb', 'zzz'], '?*****?c'), ['abc']); + expect_deepEqual(match(['abc', 'bbb', 'zzz'], '?***?****?'), ['abc', 'bbb', 'zzz']); + expect_deepEqual(match(['abc', 'bbb', 'zzz'], '?***?****c'), ['abc']); + expect_deepEqual(match(['abc'], '*******?'), ['abc']); + expect_deepEqual(match(['abc'], '*******c'), ['abc']); + expect_deepEqual(match(['abc'], '?***?****'), ['abc']); + expect_deepEqual(match(['abcdecdhjk'], 'a****c**?**??*****'), ['abcdecdhjk']); + expect_deepEqual(match(['abcdecdhjk'], 'a**?**cd**?**??***k'), ['abcdecdhjk']); + expect_deepEqual(match(['abcdecdhjk'], 'a**?**cd**?**??***k**'), ['abcdecdhjk']); + expect_deepEqual(match(['abcdecdhjk'], 'a**?**cd**?**??k'), ['abcdecdhjk']); + expect_deepEqual(match(['abcdecdhjk'], 'a**?**cd**?**??k***'), ['abcdecdhjk']); + expect_deepEqual(match(['abcdecdhjk'], 'a*cd**?**??k'), ['abcdecdhjk']); + }); + + test('should match backslashes with question marks when not on windows', () => { + if (process.platform !== 'win32') { + expect_truthy(!isMatch('aaa\\\\bbb', 'aaa?bbb')); + expect_truthy(isMatch('aaa\\\\bbb', 'aaa??bbb')); + expect_truthy(isMatch('aaa\\bbb', 'aaa?bbb')); + } + }); + + test('should match one character per question mark', () => { + const fixtures = ['a', 'aa', 'ab', 'aaa', 'abcdefg']; + expect_deepEqual(match(fixtures, '?'), ['a']); + expect_deepEqual(match(fixtures, '??'), ['aa', 'ab']); + expect_deepEqual(match(fixtures, '???'), ['aaa']); + expect_deepEqual(match(['a/', '/a/', '/a/b/', '/a/b/c/', '/a/b/c/d/'], '??'), []); + expect_deepEqual(match(['a/b/c.md'], 'a/?/c.md'), ['a/b/c.md']); + expect_deepEqual(match(['a/bb/c.md'], 'a/?/c.md'), []); + expect_deepEqual(match(['a/bb/c.md'], 'a/??/c.md'), ['a/bb/c.md']); + expect_deepEqual(match(['a/bbb/c.md'], 'a/??/c.md'), []); + expect_deepEqual(match(['a/bbb/c.md'], 'a/???/c.md'), ['a/bbb/c.md']); + expect_deepEqual(match(['a/bbbb/c.md'], 'a/????/c.md'), ['a/bbbb/c.md']); + }); + + test('should not match slashes question marks', () => { + const fixtures = ['//', 'a/', '/a', '/a/', 'aa', '/aa', 'a/a', 'aaa', '/aaa']; + expect_deepEqual(match(fixtures, '/?'), ['/a']); + expect_deepEqual(match(fixtures, '/??'), ['/aa']); + expect_deepEqual(match(fixtures, '/???'), ['/aaa']); + expect_deepEqual(match(fixtures, '/?/'), ['/a/']); + expect_deepEqual(match(fixtures, '??'), ['aa']); + expect_deepEqual(match(fixtures, '?/?'), ['a/a']); + expect_deepEqual(match(fixtures, '???'), ['aaa']); + expect_deepEqual(match(fixtures, 'a?a'), ['aaa']); + expect_deepEqual(match(fixtures, 'aa?'), ['aaa']); + expect_deepEqual(match(fixtures, '?aa'), ['aaa']); + }); + + test('should support question marks and stars between slashes', () => { + expect_deepEqual(match(['a/b.bb/c/d/efgh.ijk/e'], 'a/*/?/**/e'), ['a/b.bb/c/d/efgh.ijk/e']); + expect_deepEqual(match(['a/b/c/d/e'], 'a/?/c/?/*/e'), []); + expect_deepEqual(match(['a/b/c/d/e/e'], 'a/?/c/?/*/e'), ['a/b/c/d/e/e']); + expect_deepEqual(match(['a/b/c/d/efgh.ijk/e'], 'a/*/?/**/e'), ['a/b/c/d/efgh.ijk/e']); + expect_deepEqual(match(['a/b/c/d/efghijk/e'], 'a/*/?/**/e'), ['a/b/c/d/efghijk/e']); + expect_deepEqual(match(['a/b/c/d/efghijk/e'], 'a/?/**/e'), ['a/b/c/d/efghijk/e']); + expect_deepEqual(match(['a/b/c/d/efghijk/e'], 'a/?/c/?/*/e'), ['a/b/c/d/efghijk/e']); + expect_deepEqual(match(['a/bb/e'], 'a/?/**/e'), []); + expect_deepEqual(match(['a/bb/e'], 'a/?/e'), []); + expect_deepEqual(match(['a/bbb/c/d/efgh.ijk/e'], 'a/*/?/**/e'), ['a/bbb/c/d/efgh.ijk/e']); + }); + + test('should match no more than one character between slashes', () => { + const fixtures = ['a/a', 'a/a/a', 'a/aa/a', 'a/aaa/a', 'a/aaaa/a', 'a/aaaaa/a']; + expect_deepEqual(match(fixtures, '?/?'), ['a/a']); + expect_deepEqual(match(fixtures, '?/???/?'), ['a/aaa/a']); + expect_deepEqual(match(fixtures, '?/????/?'), ['a/aaaa/a']); + expect_deepEqual(match(fixtures, '?/?????/?'), ['a/aaaaa/a']); + expect_deepEqual(match(fixtures, 'a/?'), ['a/a']); + expect_deepEqual(match(fixtures, 'a/?/a'), ['a/a/a']); + expect_deepEqual(match(fixtures, 'a/??/a'), ['a/aa/a']); + expect_deepEqual(match(fixtures, 'a/???/a'), ['a/aaa/a']); + expect_deepEqual(match(fixtures, 'a/????/a'), ['a/aaaa/a']); + expect_deepEqual(match(fixtures, 'a/????a/a'), ['a/aaaaa/a']); + }); + + test('should not match non-leading dots with question marks', () => { + const fixtures = ['.', '.a', 'a', 'aa', 'a.a', 'aa.a', 'aaa', 'aaa.a', 'aaaa.a', 'aaaaa']; + expect_deepEqual(match(fixtures, '?'), ['a']); + expect_deepEqual(match(fixtures, '.?'), ['.a']); + expect_deepEqual(match(fixtures, '?a'), ['aa']); + expect_deepEqual(match(fixtures, '??'), ['aa']); + expect_deepEqual(match(fixtures, '?a?'), ['aaa']); + expect_deepEqual(match(fixtures, 'aaa?a'), ['aaa.a', 'aaaaa']); + expect_deepEqual(match(fixtures, 'a?a?a'), ['aaa.a', 'aaaaa']); + expect_deepEqual(match(fixtures, 'a???a'), ['aaa.a', 'aaaaa']); + expect_deepEqual(match(fixtures, 'a?????'), ['aaaa.a']); + }); + + test('should match non-leading dots with question marks when options.dot is true', () => { + const fixtures = ['.', '.a', 'a', 'aa', 'a.a', 'aa.a', '.aa', 'aaa.a', 'aaaa.a', 'aaaaa']; + const opts = { dot: true }; + expect_deepEqual(match(fixtures, '?', opts), ['.', 'a']); + expect_deepEqual(match(fixtures, '.?', opts), ['.a']); + expect_deepEqual(match(fixtures, '?a', opts), ['.a', 'aa']); + expect_deepEqual(match(fixtures, '??', opts), ['.a', 'aa']); + expect_deepEqual(match(fixtures, '?a?', opts), ['.aa']); + }); +}); diff --git a/packages/node-utils/test/picomatch/regex-features.test.ts b/packages/node-utils/test/picomatch/regex-features.test.ts new file mode 100644 index 0000000..a8205d3 --- /dev/null +++ b/packages/node-utils/test/picomatch/regex-features.test.ts @@ -0,0 +1,344 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +import * as utils from "../../src/picomatch/utils.ts"; +const { isMatch } = picomatch; + +describe('regex features', () => { + describe('word boundaries', () => { + test('should support word boundaries', () => { + expect_truthy(isMatch('a', 'a\\b')); + }); + + test('should support word boundaries in parens', () => { + expect_truthy(isMatch('a', '(a\\b)')); + }); + }); + + describe('regex lookarounds', () => { + test('should support regex lookbehinds', () => { + expect_truthy(isMatch('foo/cbaz', 'foo/*(? { + test('should support regex backreferences', () => { + expect_truthy(!isMatch('1/2', '(*)/\\1')); + expect_truthy(isMatch('1/1', '(*)/\\1')); + expect_truthy(isMatch('1/1/1/1', '(*)/\\1/\\1/\\1')); + expect_truthy(!isMatch('1/11/111/1111', '(*)/\\1/\\1/\\1')); + expect_truthy(isMatch('1/11/111/1111', '(*)/(\\1)+/(\\1)+/(\\1)+')); + expect_truthy(!isMatch('1/2/1/1', '(*)/\\1/\\1/\\1')); + expect_truthy(!isMatch('1/1/2/1', '(*)/\\1/\\1/\\1')); + expect_truthy(!isMatch('1/1/1/2', '(*)/\\1/\\1/\\1')); + expect_truthy(isMatch('1/1/1/1', '(*)/\\1/(*)/\\2')); + expect_truthy(!isMatch('1/1/2/1', '(*)/\\1/(*)/\\2')); + expect_truthy(!isMatch('1/1/2/1', '(*)/\\1/(*)/\\2')); + expect_truthy(isMatch('1/1/2/2', '(*)/\\1/(*)/\\2')); + }); + }); + + describe('regex character classes', () => { + test('should not match with character classes when disabled', () => { + expect_truthy(!isMatch('a/a', 'a/[a-z]', { nobracket: true })); + expect_truthy(!isMatch('a/b', 'a/[a-z]', { nobracket: true })); + expect_truthy(!isMatch('a/c', 'a/[a-z]', { nobracket: true })); + }); + + test('should match with character classes by default', () => { + expect_truthy(isMatch('a/a', 'a/[a-z]')); + expect_truthy(isMatch('a/b', 'a/[a-z]')); + expect_truthy(isMatch('a/c', 'a/[a-z]')); + + expect_truthy(!isMatch('foo/bar', '**/[jkl]*')); + expect_truthy(isMatch('foo/jar', '**/[jkl]*')); + + expect_truthy(isMatch('foo/bar', '**/[^jkl]*')); + expect_truthy(!isMatch('foo/jar', '**/[^jkl]*')); + + expect_truthy(isMatch('foo/bar', '**/[abc]*')); + expect_truthy(!isMatch('foo/jar', '**/[abc]*')); + + expect_truthy(!isMatch('foo/bar', '**/[^abc]*')); + expect_truthy(isMatch('foo/jar', '**/[^abc]*')); + + expect_truthy(isMatch('foo/bar', '**/[abc]ar')); + expect_truthy(!isMatch('foo/jar', '**/[abc]ar')); + }); + + test('should match character classes', () => { + expect_truthy(!isMatch('abc', 'a[bc]d')); + expect_truthy(isMatch('abd', 'a[bc]d')); + }); + + test('should match character class alphabetical ranges', () => { + expect_truthy(!isMatch('abc', 'a[b-d]e')); + expect_truthy(!isMatch('abd', 'a[b-d]e')); + expect_truthy(isMatch('abe', 'a[b-d]e')); + expect_truthy(!isMatch('ac', 'a[b-d]e')); + expect_truthy(!isMatch('a-', 'a[b-d]e')); + + expect_truthy(!isMatch('abc', 'a[b-d]')); + expect_truthy(!isMatch('abd', 'a[b-d]')); + expect_truthy(isMatch('abd', 'a[b-d]+')); + expect_truthy(!isMatch('abe', 'a[b-d]')); + expect_truthy(isMatch('ac', 'a[b-d]')); + expect_truthy(!isMatch('a-', 'a[b-d]')); + }); + + test('should match character classes with leading dashes', () => { + expect_truthy(!isMatch('abc', 'a[-c]')); + expect_truthy(isMatch('ac', 'a[-c]')); + expect_truthy(isMatch('a-', 'a[-c]')); + }); + + test('should match character classes with trailing dashes', () => { + expect_truthy(!isMatch('abc', 'a[c-]')); + expect_truthy(isMatch('ac', 'a[c-]')); + expect_truthy(isMatch('a-', 'a[c-]')); + }); + + test('should match bracket literals', () => { + expect_truthy(isMatch('a]c', 'a[]]c')); + expect_truthy(isMatch('a]c', 'a]c')); + expect_truthy(isMatch('a]', 'a]')); + + expect_truthy(isMatch('a[c', 'a[\\[]c')); + expect_truthy(isMatch('a[c', 'a[c')); + expect_truthy(isMatch('a[', 'a[')); + }); + + test('should support negated character classes', () => { + expect_truthy(!isMatch('a]', 'a[^bc]d')); + expect_truthy(!isMatch('acd', 'a[^bc]d')); + expect_truthy(isMatch('aed', 'a[^bc]d')); + expect_truthy(isMatch('azd', 'a[^bc]d')); + expect_truthy(!isMatch('ac', 'a[^bc]d')); + expect_truthy(!isMatch('a-', 'a[^bc]d')); + }); + + test('should match negated dashes', () => { + expect_truthy(!isMatch('abc', 'a[^-b]c')); + expect_truthy(isMatch('adc', 'a[^-b]c')); + expect_truthy(!isMatch('a-c', 'a[^-b]c')); + }); + + test('should match negated pm', () => { + expect_truthy(isMatch('a-c', 'a[^\\]b]c')); + expect_truthy(!isMatch('abc', 'a[^\\]b]c')); + expect_truthy(!isMatch('a]c', 'a[^\\]b]c')); + expect_truthy(isMatch('adc', 'a[^\\]b]c')); + }); + + test('should match alpha-numeric characters', () => { + expect_truthy(!isMatch('0123e45g78', '[\\de]+')); + expect_truthy(isMatch('0123e456', '[\\de]+')); + expect_truthy(isMatch('01234', '[\\de]+')); + }); + + test('should support valid regex ranges', () => { + expect_truthy(!isMatch('a/a', 'a/[b-c]')); + expect_truthy(!isMatch('a/z', 'a/[b-c]')); + expect_truthy(isMatch('a/b', 'a/[b-c]')); + expect_truthy(isMatch('a/c', 'a/[b-c]')); + expect_truthy(isMatch('a/b', '[a-z]/[a-z]')); + expect_truthy(isMatch('a/z', '[a-z]/[a-z]')); + expect_truthy(isMatch('z/z', '[a-z]/[a-z]')); + expect_truthy(!isMatch('a/x/y', 'a/[a-z]')); + + expect_truthy(isMatch('a.a', '[a-b].[a-b]')); + expect_truthy(isMatch('a.b', '[a-b].[a-b]')); + expect_truthy(!isMatch('a.a.a', '[a-b].[a-b]')); + expect_truthy(!isMatch('c.a', '[a-b].[a-b]')); + expect_truthy(!isMatch('d.a.d', '[a-b].[a-b]')); + expect_truthy(!isMatch('a.bb', '[a-b].[a-b]')); + expect_truthy(!isMatch('a.ccc', '[a-b].[a-b]')); + + expect_truthy(isMatch('a.a', '[a-d].[a-b]')); + expect_truthy(isMatch('a.b', '[a-d].[a-b]')); + expect_truthy(!isMatch('a.a.a', '[a-d].[a-b]')); + expect_truthy(isMatch('c.a', '[a-d].[a-b]')); + expect_truthy(!isMatch('d.a.d', '[a-d].[a-b]')); + expect_truthy(!isMatch('a.bb', '[a-d].[a-b]')); + expect_truthy(!isMatch('a.ccc', '[a-d].[a-b]')); + + expect_truthy(isMatch('a.a', '[a-d]*.[a-b]')); + expect_truthy(isMatch('a.b', '[a-d]*.[a-b]')); + expect_truthy(isMatch('a.a.a', '[a-d]*.[a-b]')); + expect_truthy(isMatch('c.a', '[a-d]*.[a-b]')); + expect_truthy(!isMatch('d.a.d', '[a-d]*.[a-b]')); + expect_truthy(!isMatch('a.bb', '[a-d]*.[a-b]')); + expect_truthy(!isMatch('a.ccc', '[a-d]*.[a-b]')); + }); + + test('should support valid regex ranges with glob negation patterns', () => { + expect_truthy(!isMatch('a.a', '!*.[a-b]')); + expect_truthy(!isMatch('a.b', '!*.[a-b]')); + expect_truthy(!isMatch('a.a.a', '!*.[a-b]')); + expect_truthy(!isMatch('c.a', '!*.[a-b]')); + expect_truthy(isMatch('d.a.d', '!*.[a-b]')); + expect_truthy(isMatch('a.bb', '!*.[a-b]')); + expect_truthy(isMatch('a.ccc', '!*.[a-b]')); + + expect_truthy(!isMatch('a.a', '!*.[a-b]*')); + expect_truthy(!isMatch('a.b', '!*.[a-b]*')); + expect_truthy(!isMatch('a.a.a', '!*.[a-b]*')); + expect_truthy(!isMatch('c.a', '!*.[a-b]*')); + expect_truthy(!isMatch('d.a.d', '!*.[a-b]*')); + expect_truthy(!isMatch('a.bb', '!*.[a-b]*')); + expect_truthy(isMatch('a.ccc', '!*.[a-b]*')); + + expect_truthy(!isMatch('a.a', '![a-b].[a-b]')); + expect_truthy(!isMatch('a.b', '![a-b].[a-b]')); + expect_truthy(isMatch('a.a.a', '![a-b].[a-b]')); + expect_truthy(isMatch('c.a', '![a-b].[a-b]')); + expect_truthy(isMatch('d.a.d', '![a-b].[a-b]')); + expect_truthy(isMatch('a.bb', '![a-b].[a-b]')); + expect_truthy(isMatch('a.ccc', '![a-b].[a-b]')); + + expect_truthy(!isMatch('a.a', '![a-b]+.[a-b]+')); + expect_truthy(!isMatch('a.b', '![a-b]+.[a-b]+')); + expect_truthy(isMatch('a.a.a', '![a-b]+.[a-b]+')); + expect_truthy(isMatch('c.a', '![a-b]+.[a-b]+')); + expect_truthy(isMatch('d.a.d', '![a-b]+.[a-b]+')); + expect_truthy(!isMatch('a.bb', '![a-b]+.[a-b]+')); + expect_truthy(isMatch('a.ccc', '![a-b]+.[a-b]+')); + }); + + test('should support valid regex ranges in negated character classes', () => { + expect_truthy(!isMatch('a.a', '*.[^a-b]')); + expect_truthy(!isMatch('a.b', '*.[^a-b]')); + expect_truthy(!isMatch('a.a.a', '*.[^a-b]')); + expect_truthy(!isMatch('c.a', '*.[^a-b]')); + expect_truthy(isMatch('d.a.d', '*.[^a-b]')); + expect_truthy(!isMatch('a.bb', '*.[^a-b]')); + expect_truthy(!isMatch('a.ccc', '*.[^a-b]')); + + expect_truthy(!isMatch('a.a', 'a.[^a-b]*')); + expect_truthy(!isMatch('a.b', 'a.[^a-b]*')); + expect_truthy(!isMatch('a.a.a', 'a.[^a-b]*')); + expect_truthy(!isMatch('c.a', 'a.[^a-b]*')); + expect_truthy(!isMatch('d.a.d', 'a.[^a-b]*')); + expect_truthy(!isMatch('a.bb', 'a.[^a-b]*')); + expect_truthy(isMatch('a.ccc', 'a.[^a-b]*')); + }); + }); + + describe('regex capture groups', () => { + test('should support regex logical "or"', () => { + expect_truthy(isMatch('a/a', 'a/(a|c)')); + expect_truthy(!isMatch('a/b', 'a/(a|c)')); + expect_truthy(isMatch('a/c', 'a/(a|c)')); + + expect_truthy(isMatch('a/a', 'a/(a|b|c)')); + expect_truthy(isMatch('a/b', 'a/(a|b|c)')); + expect_truthy(isMatch('a/c', 'a/(a|b|c)')); + }); + + test('should support regex character classes inside extglobs', () => { + expect_truthy(!isMatch('foo/bar', '**/!([a-k])*')); + expect_truthy(!isMatch('foo/jar', '**/!([a-k])*')); + + expect_truthy(!isMatch('foo/bar', '**/!([a-i])*')); + expect_truthy(isMatch('foo/bar', '**/!([c-i])*')); + expect_truthy(isMatch('foo/jar', '**/!([a-i])*')); + }); + + test('should support regex capture groups', () => { + expect_truthy(isMatch('a/bb/c/dd/e.md', 'a/??/?/(dd)/e.md')); + expect_truthy(isMatch('a/b/c/d/e.md', 'a/?/c/?/(e|f).md')); + expect_truthy(isMatch('a/b/c/d/f.md', 'a/?/c/?/(e|f).md')); + }); + + test('should support regex capture groups with slashes', () => { + expect_truthy(!isMatch('a/a', '(a/b)')); + expect_truthy(isMatch('a/b', '(a/b)')); + expect_truthy(!isMatch('a/c', '(a/b)')); + expect_truthy(!isMatch('b/a', '(a/b)')); + expect_truthy(!isMatch('b/b', '(a/b)')); + expect_truthy(!isMatch('b/c', '(a/b)')); + }); + + test('should support regex non-capture groups', () => { + expect_truthy(isMatch('a/bb/c/dd/e.md', 'a/**/(?:dd)/e.md')); + expect_truthy(isMatch('a/b/c/d/e.md', 'a/?/c/?/(?:e|f).md')); + expect_truthy(isMatch('a/b/c/d/f.md', 'a/?/c/?/(?:e|f).md')); + }); + }); + + describe('quantifiers', () => { + test('should support regex quantifiers by escaping braces', () => { + expect_truthy(isMatch('a ', 'a \\{1,5\\}', { unescape: true })); + expect_truthy(!isMatch('a ', 'a \\{1,2\\}', { unescape: true })); + expect_truthy(!isMatch('a ', 'a \\{1,2\\}')); + }); + + test('should support extglobs with regex quantifiers', () => { + expect_truthy(!isMatch('a ', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('a ', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('a', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('aa', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('aaa', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('b', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('bb', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(!isMatch('bbb', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(isMatch(' a ', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(isMatch('b ', '@(!(a) \\{1,2\\})*', { unescape: true })); + expect_truthy(isMatch('b ', '@(!(a) \\{1,2\\})*', { unescape: true })); + + expect_truthy(isMatch('a ', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('a b', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('a b', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('a ', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('a ', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('a', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('aa', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('b', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('bb', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch(' a ', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('b ', '@(!(a \\{1,2\\}))*')); + expect_truthy(isMatch('b ', '@(!(a \\{1,2\\}))*')); + }); + + test('should basename paths', () => { + expect_equal(utils.basename('/a/b/c'), 'c'); + expect_equal(utils.basename('/a/b/c/'), 'c'); + expect_equal(utils.basename('/a\\b/c', { windows: true }), 'c'); + expect_equal(utils.basename('/a\\b/c\\', { windows: true }), 'c'); + expect_equal(utils.basename('\\a/b\\c', { windows: true }), 'c'); + expect_equal(utils.basename('\\a/b\\c/', { windows: true }), 'c'); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/slashes-posix.test.ts b/packages/node-utils/test/picomatch/slashes-posix.test.ts new file mode 100644 index 0000000..ab38bc3 --- /dev/null +++ b/packages/node-utils/test/picomatch/slashes-posix.test.ts @@ -0,0 +1,1240 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('slash handling - posix', () => { + test('should match a literal string', () => { + expect_truthy(!isMatch('a/a', '(a/b)')); + expect_truthy(isMatch('a/b', '(a/b)')); + expect_truthy(!isMatch('a/c', '(a/b)')); + expect_truthy(!isMatch('b/a', '(a/b)')); + expect_truthy(!isMatch('b/b', '(a/b)')); + expect_truthy(!isMatch('b/c', '(a/b)')); + + expect_truthy(!isMatch('a/a', 'a/b')); + expect_truthy(isMatch('a/b', 'a/b')); + expect_truthy(!isMatch('a/c', 'a/b')); + expect_truthy(!isMatch('b/a', 'a/b')); + expect_truthy(!isMatch('b/b', 'a/b')); + expect_truthy(!isMatch('b/c', 'a/b')); + }); + + test('should match an array of literal strings', () => { + expect_truthy(!isMatch('a/a', 'a/b')); + expect_truthy(isMatch('a/b', 'a/b')); + expect_truthy(!isMatch('a/c', 'a/b')); + expect_truthy(!isMatch('b/a', 'a/b')); + expect_truthy(!isMatch('b/b', 'a/b')); + expect_truthy(isMatch('b/b', 'b/b')); + expect_truthy(!isMatch('b/c', 'a/b')); + }); + + test('should support regex logical or', () => { + expect_truthy(isMatch('a/a', 'a/(a|c)')); + expect_truthy(!isMatch('a/b', 'a/(a|c)')); + expect_truthy(isMatch('a/c', 'a/(a|c)')); + + expect_truthy(isMatch('a/a', 'a/(a|b|c)')); + expect_truthy(isMatch('a/b', 'a/(a|b|c)')); + expect_truthy(isMatch('a/c', 'a/(a|b|c)')); + }); + + test('should support regex ranges', () => { + expect_truthy(!isMatch('a/a', 'a/[b-c]')); + expect_truthy(isMatch('a/b', 'a/[b-c]')); + expect_truthy(isMatch('a/c', 'a/[b-c]')); + + expect_truthy(isMatch('a/a', 'a/[a-z]')); + expect_truthy(isMatch('a/b', 'a/[a-z]')); + expect_truthy(isMatch('a/c', 'a/[a-z]')); + expect_truthy(!isMatch('a/x/y', 'a/[a-z]')); + expect_truthy(isMatch('a/x', 'a/[a-z]')); + }); + + test('should support single globs (*)', () => { + expect_truthy(isMatch('a', '*')); + expect_truthy(isMatch('b', '*')); + expect_truthy(!isMatch('a/a', '*')); + expect_truthy(!isMatch('a/b', '*')); + expect_truthy(!isMatch('a/c', '*')); + expect_truthy(!isMatch('a/x', '*')); + expect_truthy(!isMatch('a/a/a', '*')); + expect_truthy(!isMatch('a/a/b', '*')); + expect_truthy(!isMatch('a/a/a/a', '*')); + expect_truthy(!isMatch('a/a/a/a/a', '*')); + expect_truthy(!isMatch('x/y', '*')); + expect_truthy(!isMatch('z/z', '*')); + + expect_truthy(!isMatch('a', '*/*')); + expect_truthy(!isMatch('b', '*/*')); + expect_truthy(isMatch('a/a', '*/*')); + expect_truthy(isMatch('a/b', '*/*')); + expect_truthy(isMatch('a/c', '*/*')); + expect_truthy(isMatch('a/x', '*/*')); + expect_truthy(!isMatch('a/a/a', '*/*')); + expect_truthy(!isMatch('a/a/b', '*/*')); + expect_truthy(!isMatch('a/a/a/a', '*/*')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*')); + expect_truthy(isMatch('x/y', '*/*')); + expect_truthy(isMatch('z/z', '*/*')); + + expect_truthy(!isMatch('a', '*/*/*')); + expect_truthy(!isMatch('b', '*/*/*')); + expect_truthy(!isMatch('a/a', '*/*/*')); + expect_truthy(!isMatch('a/b', '*/*/*')); + expect_truthy(!isMatch('a/c', '*/*/*')); + expect_truthy(!isMatch('a/x', '*/*/*')); + expect_truthy(isMatch('a/a/a', '*/*/*')); + expect_truthy(isMatch('a/a/b', '*/*/*')); + expect_truthy(!isMatch('a/a/a/a', '*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*/*')); + expect_truthy(!isMatch('x/y', '*/*/*')); + expect_truthy(!isMatch('z/z', '*/*/*')); + + expect_truthy(!isMatch('a', '*/*/*/*')); + expect_truthy(!isMatch('b', '*/*/*/*')); + expect_truthy(!isMatch('a/a', '*/*/*/*')); + expect_truthy(!isMatch('a/b', '*/*/*/*')); + expect_truthy(!isMatch('a/c', '*/*/*/*')); + expect_truthy(!isMatch('a/x', '*/*/*/*')); + expect_truthy(!isMatch('a/a/a', '*/*/*/*')); + expect_truthy(!isMatch('a/a/b', '*/*/*/*')); + expect_truthy(isMatch('a/a/a/a', '*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*/*/*')); + expect_truthy(!isMatch('x/y', '*/*/*/*')); + expect_truthy(!isMatch('z/z', '*/*/*/*')); + + expect_truthy(!isMatch('a', '*/*/*/*/*')); + expect_truthy(!isMatch('b', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/b', '*/*/*/*/*')); + expect_truthy(!isMatch('a/c', '*/*/*/*/*')); + expect_truthy(!isMatch('a/x', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/b', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a', '*/*/*/*/*')); + expect_truthy(isMatch('a/a/a/a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('x/y', '*/*/*/*/*')); + expect_truthy(!isMatch('z/z', '*/*/*/*/*')); + + expect_truthy(!isMatch('a', 'a/*')); + expect_truthy(!isMatch('b', 'a/*')); + expect_truthy(isMatch('a/a', 'a/*')); + expect_truthy(isMatch('a/b', 'a/*')); + expect_truthy(isMatch('a/c', 'a/*')); + expect_truthy(isMatch('a/x', 'a/*')); + expect_truthy(!isMatch('a/a/a', 'a/*')); + expect_truthy(!isMatch('a/a/b', 'a/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*')); + expect_truthy(!isMatch('x/y', 'a/*')); + expect_truthy(!isMatch('z/z', 'a/*')); + + expect_truthy(!isMatch('a', 'a/*/*')); + expect_truthy(!isMatch('b', 'a/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/*')); + expect_truthy(!isMatch('a/b', 'a/*/*')); + expect_truthy(!isMatch('a/c', 'a/*/*')); + expect_truthy(!isMatch('a/x', 'a/*/*')); + expect_truthy(isMatch('a/a/a', 'a/*/*')); + expect_truthy(isMatch('a/a/b', 'a/*/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/*')); + expect_truthy(!isMatch('x/y', 'a/*/*')); + expect_truthy(!isMatch('z/z', 'a/*/*')); + + expect_truthy(!isMatch('a', 'a/*/*/*')); + expect_truthy(!isMatch('b', 'a/*/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/*/*')); + expect_truthy(!isMatch('a/b', 'a/*/*/*')); + expect_truthy(!isMatch('a/c', 'a/*/*/*')); + expect_truthy(!isMatch('a/x', 'a/*/*/*')); + expect_truthy(!isMatch('a/a/a', 'a/*/*/*')); + expect_truthy(!isMatch('a/a/b', 'a/*/*/*')); + expect_truthy(isMatch('a/a/a/a', 'a/*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/*/*')); + expect_truthy(!isMatch('x/y', 'a/*/*/*')); + expect_truthy(!isMatch('z/z', 'a/*/*/*')); + + expect_truthy(!isMatch('a', 'a/*/*/*/*')); + expect_truthy(!isMatch('b', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/b', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/c', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/x', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/b', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/*/*/*')); + expect_truthy(isMatch('a/a/a/a/a', 'a/*/*/*/*')); + expect_truthy(!isMatch('x/y', 'a/*/*/*/*')); + expect_truthy(!isMatch('z/z', 'a/*/*/*/*')); + + expect_truthy(!isMatch('a', 'a/*/a')); + expect_truthy(!isMatch('b', 'a/*/a')); + expect_truthy(!isMatch('a/a', 'a/*/a')); + expect_truthy(!isMatch('a/b', 'a/*/a')); + expect_truthy(!isMatch('a/c', 'a/*/a')); + expect_truthy(!isMatch('a/x', 'a/*/a')); + expect_truthy(isMatch('a/a/a', 'a/*/a')); + expect_truthy(!isMatch('a/a/b', 'a/*/a')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/a')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/a')); + expect_truthy(!isMatch('x/y', 'a/*/a')); + expect_truthy(!isMatch('z/z', 'a/*/a')); + + expect_truthy(!isMatch('a', 'a/*/b')); + expect_truthy(!isMatch('b', 'a/*/b')); + expect_truthy(!isMatch('a/a', 'a/*/b')); + expect_truthy(!isMatch('a/b', 'a/*/b')); + expect_truthy(!isMatch('a/c', 'a/*/b')); + expect_truthy(!isMatch('a/x', 'a/*/b')); + expect_truthy(!isMatch('a/a/a', 'a/*/b')); + expect_truthy(isMatch('a/a/b', 'a/*/b')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/b')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/b')); + expect_truthy(!isMatch('x/y', 'a/*/b')); + expect_truthy(!isMatch('z/z', 'a/*/b')); + }); + + test('should support globstars (**)', () => { + expect_truthy(isMatch('a', 'a')); + expect_truthy(!isMatch('a/', 'a')); + expect_truthy(!isMatch('a/a', 'a')); + expect_truthy(!isMatch('a/b', 'a')); + expect_truthy(!isMatch('a/c', 'a')); + expect_truthy(!isMatch('a/x', 'a')); + expect_truthy(!isMatch('a/x/y', 'a')); + expect_truthy(!isMatch('a/x/y/z', 'a')); + + expect_truthy(isMatch('a', '*')); + expect_truthy(isMatch('a/', '*', { relaxSlashes: true })); + expect_truthy(isMatch('a/', '*{,/}')); + expect_truthy(!isMatch('a/a', '*')); + expect_truthy(!isMatch('a/b', '*')); + expect_truthy(!isMatch('a/c', '*')); + expect_truthy(!isMatch('a/x', '*')); + expect_truthy(!isMatch('a/x/y', '*')); + expect_truthy(!isMatch('a/x/y/z', '*')); + + expect_truthy(!isMatch('a', '*/')); + expect_truthy(isMatch('a/', '*/')); + expect_truthy(!isMatch('a/a', '*/')); + expect_truthy(!isMatch('a/b', '*/')); + expect_truthy(!isMatch('a/c', '*/')); + expect_truthy(!isMatch('a/x', '*/')); + expect_truthy(!isMatch('a/x/y', '*/')); + expect_truthy(!isMatch('a/x/y/z', '*/')); + + expect_truthy(!isMatch('a', '*/*')); + expect_truthy(!isMatch('a/', '*/*')); + expect_truthy(isMatch('a/a', '*/*')); + expect_truthy(isMatch('a/b', '*/*')); + expect_truthy(isMatch('a/c', '*/*')); + expect_truthy(isMatch('a/x', '*/*')); + expect_truthy(!isMatch('a/x/y', '*/*')); + expect_truthy(!isMatch('a/x/y/z', '*/*')); + + expect_truthy(isMatch('a', '**')); + expect_truthy(isMatch('a/', '**')); + expect_truthy(isMatch('a/a', '**')); + expect_truthy(isMatch('a/b', '**')); + expect_truthy(isMatch('a/c', '**')); + expect_truthy(isMatch('a/x', '**')); + expect_truthy(isMatch('a/x/y', '**')); + expect_truthy(isMatch('a/x/y/z', '**')); + + expect_truthy(!isMatch('a/', '**/a')); + expect_truthy(!isMatch('a/b', '**/a')); + expect_truthy(!isMatch('a/c', '**/a')); + expect_truthy(!isMatch('a/x', '**/a')); + expect_truthy(!isMatch('a/x/y/z', '**/a')); + expect_truthy(isMatch('a/x/y/z/a', '**/a')); + expect_truthy(isMatch('a', '**/a')); + expect_truthy(isMatch('a/a', '**/a')); + + expect_truthy(!isMatch('a', 'a/*')); + expect_truthy(!isMatch('a/', 'a/*')); + expect_truthy(isMatch('a/a', 'a/*')); + expect_truthy(isMatch('a/b', 'a/*')); + expect_truthy(isMatch('a/c', 'a/*')); + expect_truthy(isMatch('a/x', 'a/*')); + expect_truthy(!isMatch('a/x/y', 'a/*')); + expect_truthy(!isMatch('a/x/y/z', 'a/*')); + + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(isMatch('a/', 'a/**')); + expect_truthy(isMatch('a/a', 'a/**')); + expect_truthy(isMatch('a/b', 'a/**')); + expect_truthy(isMatch('a/c', 'a/**')); + expect_truthy(isMatch('a/x', 'a/**')); + expect_truthy(isMatch('a/x/y', 'a/**')); + expect_truthy(isMatch('a/x/y/z', 'a/**')); + + expect_truthy(!isMatch('a', 'a/**/*')); + expect_truthy(!isMatch('a/', 'a/**/*')); + expect_truthy(isMatch('a/a', 'a/**/*')); + expect_truthy(isMatch('a/b', 'a/**/*')); + expect_truthy(isMatch('a/c', 'a/**/*')); + expect_truthy(isMatch('a/x', 'a/**/*')); + expect_truthy(isMatch('a/x/y', 'a/**/*')); + expect_truthy(isMatch('a/x/y/z', 'a/**/*')); + + expect_truthy(!isMatch('a', 'a/**/**/*')); + expect_truthy(!isMatch('a/', 'a/**/**/*')); + expect_truthy(isMatch('a/a', 'a/**/**/*')); + expect_truthy(isMatch('a/b', 'a/**/**/*')); + expect_truthy(isMatch('a/c', 'a/**/**/*')); + expect_truthy(isMatch('a/x', 'a/**/**/*')); + expect_truthy(isMatch('a/x/y', 'a/**/**/*')); + expect_truthy(isMatch('a/x/y/z', 'a/**/**/*')); + + expect_truthy(isMatch('a/b/foo/bar/baz.qux', 'a/b/**/bar/**/*.*')); + expect_truthy(isMatch('a/b/bar/baz.qux', 'a/b/**/bar/**/*.*')); + }); + + test('should support negation patterns', () => { + expect_truthy(isMatch('a/a', '!a/b')); + expect_truthy(!isMatch('a/b', '!a/b')); + expect_truthy(isMatch('a/c', '!a/b')); + expect_truthy(isMatch('b/a', '!a/b')); + expect_truthy(isMatch('b/b', '!a/b')); + expect_truthy(isMatch('b/c', '!a/b')); + + expect_truthy(isMatch('a/a', ['*/*', '!a/b', '!*/c'])); + expect_truthy(isMatch('a/b', ['*/*', '!a/b', '!*/c'])); + expect_truthy(isMatch('a/c', ['*/*', '!a/b', '!*/c'])); + expect_truthy(isMatch('b/a', ['*/*', '!a/b', '!*/c'])); + expect_truthy(isMatch('b/b', ['*/*', '!a/b', '!*/c'])); + expect_truthy(isMatch('b/c', ['*/*', '!a/b', '!*/c'])); + + expect_truthy(isMatch('a/a', ['!a/b', '!*/c'])); + expect_truthy(isMatch('a/b', ['!a/b', '!*/c'])); + expect_truthy(isMatch('a/c', ['!a/b', '!*/c'])); + expect_truthy(isMatch('b/a', ['!a/b', '!*/c'])); + expect_truthy(isMatch('b/b', ['!a/b', '!*/c'])); + expect_truthy(isMatch('b/c', ['!a/b', '!*/c'])); + + expect_truthy(isMatch('a/a', ['!a/b', '!a/c'])); + expect_truthy(isMatch('a/b', ['!a/b', '!a/c'])); + expect_truthy(isMatch('a/c', ['!a/b', '!a/c'])); + expect_truthy(isMatch('b/a', ['!a/b', '!a/c'])); + expect_truthy(isMatch('b/b', ['!a/b', '!a/c'])); + expect_truthy(isMatch('b/c', ['!a/b', '!a/c'])); + + expect_truthy(isMatch('a/a', '!a/(b)')); + expect_truthy(!isMatch('a/b', '!a/(b)')); + expect_truthy(isMatch('a/c', '!a/(b)')); + expect_truthy(isMatch('b/a', '!a/(b)')); + expect_truthy(isMatch('b/b', '!a/(b)')); + expect_truthy(isMatch('b/c', '!a/(b)')); + + expect_truthy(isMatch('a/a', '!(a/b)')); + expect_truthy(!isMatch('a/b', '!(a/b)')); + expect_truthy(isMatch('a/c', '!(a/b)')); + expect_truthy(isMatch('b/a', '!(a/b)')); + expect_truthy(isMatch('b/b', '!(a/b)')); + expect_truthy(isMatch('b/c', '!(a/b)')); + }); + + test('should work with file extensions', () => { + expect_truthy(!isMatch('a.txt', 'a/**/*.txt')); + expect_truthy(isMatch('a/b.txt', 'a/**/*.txt')); + expect_truthy(isMatch('a/x/y.txt', 'a/**/*.txt')); + expect_truthy(!isMatch('a/x/y/z', ['a/**/*.txt'])); + + expect_truthy(!isMatch('a.txt', 'a/*.txt')); + expect_truthy(isMatch('a/b.txt', 'a/*.txt')); + expect_truthy(!isMatch('a/x/y.txt', 'a/*.txt')); + expect_truthy(!isMatch('a/x/y/z', 'a/*.txt')); + + expect_truthy(isMatch('a.txt', 'a*.txt')); + expect_truthy(!isMatch('a/b.txt', 'a*.txt')); + expect_truthy(!isMatch('a/x/y.txt', 'a*.txt')); + expect_truthy(!isMatch('a/x/y/z', 'a*.txt')); + + expect_truthy(isMatch('a.txt', '*.txt')); + expect_truthy(!isMatch('a/b.txt', '*.txt')); + expect_truthy(!isMatch('a/x/y.txt', '*.txt')); + expect_truthy(!isMatch('a/x/y/z', '*.txt')); + }); + + test('should match one directory level with a single star (*)', () => { + expect_truthy(!isMatch('/a', '*/')); + expect_truthy(!isMatch('/a', '*/*/*')); + expect_truthy(!isMatch('/a', '*/*/*/*')); + expect_truthy(!isMatch('/a', '*/*/*/*/*')); + expect_truthy(!isMatch('/a', '/*/')); + expect_truthy(!isMatch('/a', 'a/*')); + expect_truthy(!isMatch('/a', 'a/*/*')); + expect_truthy(!isMatch('/a', 'a/*/*/*')); + expect_truthy(!isMatch('/a', 'a/*/*/*/*')); + expect_truthy(!isMatch('/a', 'a/*/a')); + expect_truthy(!isMatch('/a', 'a/*/b')); + expect_truthy(!isMatch('/a/', '*')); + expect_truthy(!isMatch('/a/', '**/*', { strictSlashes: true })); + expect_truthy(!isMatch('/a/', '*/')); + expect_truthy(!isMatch('/a/', '*/*', { strictSlashes: true })); + expect_truthy(!isMatch('/a/', '*/*/*')); + expect_truthy(!isMatch('/a/', '*/*/*/*')); + expect_truthy(!isMatch('/a/', '*/*/*/*/*')); + expect_truthy(!isMatch('/a/', '/*', { strictSlashes: true })); + expect_truthy(!isMatch('/a/', 'a/*')); + expect_truthy(!isMatch('/a/', 'a/*/*')); + expect_truthy(!isMatch('/a/', 'a/*/*/*')); + expect_truthy(!isMatch('/a/', 'a/*/*/*/*')); + expect_truthy(!isMatch('/a/', 'a/*/a')); + expect_truthy(!isMatch('/a/', 'a/*/b')); + expect_truthy(!isMatch('/ab', '*')); + expect_truthy(!isMatch('/abc', '*')); + expect_truthy(!isMatch('/b', '*')); + expect_truthy(!isMatch('/b', '*/')); + expect_truthy(!isMatch('/b', '*/*/*')); + expect_truthy(!isMatch('/b', '*/*/*/*')); + expect_truthy(!isMatch('/b', '*/*/*/*/*')); + expect_truthy(!isMatch('/b', '/*/')); + expect_truthy(!isMatch('/b', 'a/*')); + expect_truthy(!isMatch('/b', 'a/*/*')); + expect_truthy(!isMatch('/b', 'a/*/*/*')); + expect_truthy(!isMatch('/b', 'a/*/*/*/*')); + expect_truthy(!isMatch('/b', 'a/*/a')); + expect_truthy(!isMatch('/b', 'a/*/b')); + expect_truthy(!isMatch('a', '*/')); + expect_truthy(!isMatch('a', '*/*')); + expect_truthy(!isMatch('a', '*/*/*')); + expect_truthy(!isMatch('a', '*/*/*/*')); + expect_truthy(!isMatch('a', '*/*/*/*/*')); + expect_truthy(!isMatch('a', '/*')); + expect_truthy(!isMatch('a', '/*/')); + expect_truthy(!isMatch('a', 'a/*')); + expect_truthy(!isMatch('a', 'a/*/*')); + expect_truthy(!isMatch('a', 'a/*/*/*')); + expect_truthy(!isMatch('a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a', 'a/*/a')); + expect_truthy(!isMatch('a', 'a/*/b')); + expect_truthy(!isMatch('a/', '*', { strictSlashes: true })); + expect_truthy(!isMatch('a/', '**/*', { strictSlashes: true })); + expect_truthy(!isMatch('a/', '*/*', { strictSlashes: true })); + expect_truthy(!isMatch('a/', '*/*/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('a/', '*/*/*/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('a/', '/*', { strictSlashes: true })); + expect_truthy(!isMatch('a/', '/*/', { strictSlashes: true })); + expect_truthy(!isMatch('a/', 'a/*', { strictSlashes: true })); + expect_truthy(!isMatch('a/', 'a/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('a/', 'a/*/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('a/', 'a/*/*/*/*', { strictSlashes: true })); + expect_truthy(!isMatch('a/', 'a/*/a', { strictSlashes: true })); + expect_truthy(!isMatch('a/', 'a/*/b', { strictSlashes: true })); + expect_truthy(!isMatch('a/a', '*')); + expect_truthy(!isMatch('a/a', '*/')); + expect_truthy(!isMatch('a/a', '*/*/*')); + expect_truthy(!isMatch('a/a', '*/*/*/*')); + expect_truthy(!isMatch('a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a', '/*')); + expect_truthy(!isMatch('a/a', '/*/')); + expect_truthy(!isMatch('a/a', 'a/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/a')); + expect_truthy(!isMatch('a/a', 'a/*/b')); + expect_truthy(!isMatch('a/a/a', '*')); + expect_truthy(!isMatch('a/a/a', '*/')); + expect_truthy(!isMatch('a/a/a', '*/*')); + expect_truthy(!isMatch('a/a/a', '*/*/*/*')); + expect_truthy(!isMatch('a/a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/a', '/*')); + expect_truthy(!isMatch('a/a/a', '/*/')); + expect_truthy(!isMatch('a/a/a', 'a/*')); + expect_truthy(!isMatch('a/a/a', 'a/*/*/*')); + expect_truthy(!isMatch('a/a/a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/a', 'a/*/b')); + expect_truthy(!isMatch('a/a/a/a', '*')); + expect_truthy(!isMatch('a/a/a/a', '*/')); + expect_truthy(!isMatch('a/a/a/a', '*/*')); + expect_truthy(!isMatch('a/a/a/a', '*/*/*')); + expect_truthy(!isMatch('a/a/a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a', '/*')); + expect_truthy(!isMatch('a/a/a/a', '/*/')); + expect_truthy(!isMatch('a/a/a/a', 'a/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/a')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/b')); + expect_truthy(!isMatch('a/a/a/a/a', '*')); + expect_truthy(!isMatch('a/a/a/a/a', '*/')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', '/*')); + expect_truthy(!isMatch('a/a/a/a/a', '/*/')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/a')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/b')); + expect_truthy(!isMatch('a/a/b', '*')); + expect_truthy(!isMatch('a/a/b', '*/')); + expect_truthy(!isMatch('a/a/b', '*/*')); + expect_truthy(!isMatch('a/a/b', '*/*/*/*')); + expect_truthy(!isMatch('a/a/b', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/b', '/*')); + expect_truthy(!isMatch('a/a/b', '/*/')); + expect_truthy(!isMatch('a/a/b', 'a/*')); + expect_truthy(!isMatch('a/a/b', 'a/*/*/*')); + expect_truthy(!isMatch('a/a/b', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/b', 'a/*/a')); + expect_truthy(!isMatch('a/b', '*')); + expect_truthy(!isMatch('a/b', '*/')); + expect_truthy(!isMatch('a/b', '*/*/*/*')); + expect_truthy(!isMatch('a/b', '*/*/*/*/*')); + expect_truthy(!isMatch('a/b', '/*')); + expect_truthy(!isMatch('a/b', '/*/')); + expect_truthy(!isMatch('a/b', 'a/*/*')); + expect_truthy(!isMatch('a/b', 'a/*/*/*')); + expect_truthy(!isMatch('a/b', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/b', 'a/*/a')); + expect_truthy(!isMatch('a/b', 'a/*/b')); + expect_truthy(!isMatch('a/c', '*')); + expect_truthy(!isMatch('a/c', '*/')); + expect_truthy(!isMatch('a/c', '*/*/*/*')); + expect_truthy(!isMatch('a/c', '*/*/*/*/*')); + expect_truthy(!isMatch('a/c', '/*')); + expect_truthy(!isMatch('a/c', '/*/')); + expect_truthy(!isMatch('a/c', 'a/*/*')); + expect_truthy(!isMatch('a/c', 'a/*/*/*')); + expect_truthy(!isMatch('a/c', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/c', 'a/*/a')); + expect_truthy(!isMatch('a/c', 'a/*/b')); + expect_truthy(!isMatch('a/x', '*')); + expect_truthy(!isMatch('a/x', '*/')); + expect_truthy(!isMatch('a/x', '*/*/*/*')); + expect_truthy(!isMatch('a/x', '*/*/*/*/*')); + expect_truthy(!isMatch('a/x', '/*')); + expect_truthy(!isMatch('a/x', '/*/')); + expect_truthy(!isMatch('a/x', 'a/*/*')); + expect_truthy(!isMatch('a/x', 'a/*/*/*')); + expect_truthy(!isMatch('a/x', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/x', 'a/*/a')); + expect_truthy(!isMatch('a/x', 'a/*/b')); + expect_truthy(!isMatch('b', '*/')); + expect_truthy(!isMatch('b', '*/*')); + expect_truthy(!isMatch('b', '*/*/*/*')); + expect_truthy(!isMatch('b', '*/*/*/*/*')); + expect_truthy(!isMatch('b', '/*')); + expect_truthy(!isMatch('b', '/*/')); + expect_truthy(!isMatch('b', 'a/*')); + expect_truthy(!isMatch('b', 'a/*/*')); + expect_truthy(!isMatch('b', 'a/*/*/*')); + expect_truthy(!isMatch('b', 'a/*/*/*/*')); + expect_truthy(!isMatch('b', 'a/*/a')); + expect_truthy(!isMatch('b', 'a/*/b')); + expect_truthy(!isMatch('x/y', '*')); + expect_truthy(!isMatch('x/y', '*/')); + expect_truthy(!isMatch('x/y', '*/*/*')); + expect_truthy(!isMatch('x/y', '*/*/*/*')); + expect_truthy(!isMatch('x/y', '*/*/*/*/*')); + expect_truthy(!isMatch('x/y', '/*')); + expect_truthy(!isMatch('x/y', '/*/')); + expect_truthy(!isMatch('x/y', 'a/*')); + expect_truthy(!isMatch('x/y', 'a/*/*')); + expect_truthy(!isMatch('x/y', 'a/*/*/*')); + expect_truthy(!isMatch('x/y', 'a/*/*/*/*')); + expect_truthy(!isMatch('x/y', 'a/*/a')); + expect_truthy(!isMatch('x/y', 'a/*/b')); + expect_truthy(!isMatch('z/z', '*')); + expect_truthy(!isMatch('z/z', '*/')); + expect_truthy(!isMatch('z/z', '*/*/*/*')); + expect_truthy(!isMatch('z/z', '*/*/*/*/*')); + expect_truthy(!isMatch('z/z', '/*')); + expect_truthy(!isMatch('z/z', '/*/')); + expect_truthy(!isMatch('z/z', 'a/*')); + expect_truthy(!isMatch('z/z', 'a/*/*')); + expect_truthy(!isMatch('z/z', 'a/*/*/*')); + expect_truthy(!isMatch('z/z', 'a/*/*/*/*')); + expect_truthy(!isMatch('z/z', 'a/*/a')); + expect_truthy(!isMatch('z/z', 'a/*/b')); + expect_truthy(isMatch('/a', '**/*')); + expect_truthy(isMatch('/a', '*/*')); + expect_truthy(isMatch('/a', '/*')); + expect_truthy(isMatch('/a/', '**/*{,/}')); + expect_truthy(isMatch('/a/', '*/*')); + expect_truthy(isMatch('/a/', '*/*{,/}')); + expect_truthy(isMatch('/a/', '/*')); + expect_truthy(isMatch('/a/', '/*/')); + expect_truthy(isMatch('/a/', '/*{,/}')); + expect_truthy(isMatch('/b', '**/*')); + expect_truthy(isMatch('/b', '*/*')); + expect_truthy(isMatch('/b', '/*')); + expect_truthy(isMatch('a', '*')); + expect_truthy(isMatch('a', '**/*')); + expect_truthy(isMatch('a/', '**/*{,/}')); + expect_truthy(isMatch('a/', '*/')); + expect_truthy(isMatch('a/', '*{,/}')); + expect_truthy(isMatch('a/a', '**/*')); + expect_truthy(isMatch('a/a', '*/*')); + expect_truthy(isMatch('a/a', 'a/*')); + expect_truthy(isMatch('a/a/a', '**/*')); + expect_truthy(isMatch('a/a/a', '*/*/*')); + expect_truthy(isMatch('a/a/a', 'a/*/*')); + expect_truthy(isMatch('a/a/a', 'a/*/a')); + expect_truthy(isMatch('a/a/a/a', '**/*')); + expect_truthy(isMatch('a/a/a/a', '*/*/*/*')); + expect_truthy(isMatch('a/a/a/a', 'a/*/*/*')); + expect_truthy(isMatch('a/a/a/a/a', '**/*')); + expect_truthy(isMatch('a/a/a/a/a', '*/*/*/*/*')); + expect_truthy(isMatch('a/a/a/a/a', 'a/*/*/*/*')); + expect_truthy(isMatch('a/a/b', '**/*')); + expect_truthy(isMatch('a/a/b', 'a/*/*')); + expect_truthy(isMatch('a/a/b', 'a/*/b')); + expect_truthy(isMatch('a/b', '**/*')); + expect_truthy(isMatch('a/b', '*/*')); + expect_truthy(isMatch('a/b', 'a/*')); + expect_truthy(isMatch('a/c', '**/*')); + expect_truthy(isMatch('a/c', '*/*')); + expect_truthy(isMatch('a/c', 'a/*')); + expect_truthy(isMatch('a/x', '**/*')); + expect_truthy(isMatch('a/x', '*/*')); + expect_truthy(isMatch('a/x', 'a/*')); + expect_truthy(isMatch('b', '*')); + expect_truthy(isMatch('b', '**/*')); + expect_truthy(isMatch('x/y', '**/*')); + expect_truthy(isMatch('x/y', '*/*')); + expect_truthy(isMatch('z/z', '**/*')); + expect_truthy(isMatch('z/z', '*/*')); + }); + + test('should match one or more directories with a globstar', () => { + expect_truthy(!isMatch('a/', '**/a')); + expect_truthy(!isMatch('/a/', '**/a')); + expect_truthy(!isMatch('a/a/', '**/a')); + expect_truthy(!isMatch('/a/a/', '**/a')); + expect_truthy(!isMatch('a/a/a/', '**/a')); + + expect_truthy(isMatch('a', '**/a')); + expect_truthy(isMatch('a/a', '**/a')); + expect_truthy(isMatch('a/a/a', '**/a')); + expect_truthy(isMatch('/a', '**/a')); + expect_truthy(isMatch('a/a/', '**/a/*{,/}')); + expect_truthy(!isMatch('a/a/', '**/a/*', { strictSlashes: true })); + expect_truthy(isMatch('/a/a', '**/a')); + + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(!isMatch('/a', 'a/**')); + expect_truthy(!isMatch('/a/', 'a/**')); + expect_truthy(!isMatch('/a/a', 'a/**')); + expect_truthy(!isMatch('/a/a/', 'a/**')); + expect_truthy(isMatch('/a', '/a/**')); + expect_truthy(isMatch('/a/', '/a/**')); + expect_truthy(isMatch('/a/a', '/a/**')); + expect_truthy(isMatch('/a/a/', '/a/**')); + expect_truthy(isMatch('a/', 'a/**')); + expect_truthy(isMatch('a/a', 'a/**')); + expect_truthy(isMatch('a/a/', 'a/**')); + expect_truthy(isMatch('a/a/a', 'a/**')); + expect_truthy(isMatch('a/a/a/', 'a/**')); + + expect_truthy(isMatch('a', '**/a/**')); + expect_truthy(isMatch('/a', '**/a/**')); + expect_truthy(isMatch('/a/', '**/a/**')); + expect_truthy(isMatch('/a/a', '**/a/**')); + expect_truthy(isMatch('/a/a/', '**/a/**')); + expect_truthy(isMatch('a/', '**/a/**')); + expect_truthy(isMatch('a/a', '**/a/**')); + expect_truthy(isMatch('a/a/', '**/a/**')); + expect_truthy(isMatch('a/a/a', '**/a/**')); + expect_truthy(isMatch('a/a/a/', '**/a/**')); + }); + + test('should match one or more characters', () => { + expect_truthy(isMatch('a', '*')); + expect_truthy(isMatch('aa', '*')); + expect_truthy(isMatch('aaa', '*')); + expect_truthy(isMatch('aaaa', '*')); + expect_truthy(isMatch('ab', '*')); + expect_truthy(isMatch('b', '*')); + expect_truthy(isMatch('bb', '*')); + expect_truthy(isMatch('c', '*')); + expect_truthy(isMatch('cc', '*')); + expect_truthy(isMatch('cac', '*')); + expect_truthy(!isMatch('a/a', '*')); + expect_truthy(!isMatch('a/b', '*')); + expect_truthy(!isMatch('a/c', '*')); + expect_truthy(!isMatch('a/x', '*')); + expect_truthy(!isMatch('a/a/a', '*')); + expect_truthy(!isMatch('a/a/b', '*')); + expect_truthy(!isMatch('a/a/a/a', '*')); + expect_truthy(!isMatch('a/a/a/a/a', '*')); + expect_truthy(!isMatch('x/y', '*')); + expect_truthy(!isMatch('z/z', '*')); + + expect_truthy(isMatch('a', 'a*')); + expect_truthy(isMatch('aa', 'a*')); + expect_truthy(isMatch('aaa', 'a*')); + expect_truthy(isMatch('aaaa', 'a*')); + expect_truthy(isMatch('ab', 'a*')); + expect_truthy(!isMatch('b', 'a*')); + expect_truthy(!isMatch('bb', 'a*')); + expect_truthy(!isMatch('c', 'a*')); + expect_truthy(!isMatch('cc', 'a*')); + expect_truthy(!isMatch('cac', 'a*')); + expect_truthy(!isMatch('a/a', 'a*')); + expect_truthy(!isMatch('a/b', 'a*')); + expect_truthy(!isMatch('a/c', 'a*')); + expect_truthy(!isMatch('a/x', 'a*')); + expect_truthy(!isMatch('a/a/a', 'a*')); + expect_truthy(!isMatch('a/a/b', 'a*')); + expect_truthy(!isMatch('a/a/a/a', 'a*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a*')); + expect_truthy(!isMatch('x/y', 'a*')); + expect_truthy(!isMatch('z/z', 'a*')); + + expect_truthy(!isMatch('a', '*b')); + expect_truthy(!isMatch('aa', '*b')); + expect_truthy(!isMatch('aaa', '*b')); + expect_truthy(!isMatch('aaaa', '*b')); + expect_truthy(isMatch('ab', '*b')); + expect_truthy(isMatch('b', '*b')); + expect_truthy(isMatch('bb', '*b')); + expect_truthy(!isMatch('c', '*b')); + expect_truthy(!isMatch('cc', '*b')); + expect_truthy(!isMatch('cac', '*b')); + expect_truthy(!isMatch('a/a', '*b')); + expect_truthy(!isMatch('a/b', '*b')); + expect_truthy(!isMatch('a/c', '*b')); + expect_truthy(!isMatch('a/x', '*b')); + expect_truthy(!isMatch('a/a/a', '*b')); + expect_truthy(!isMatch('a/a/b', '*b')); + expect_truthy(!isMatch('a/a/a/a', '*b')); + expect_truthy(!isMatch('a/a/a/a/a', '*b')); + expect_truthy(!isMatch('x/y', '*b')); + expect_truthy(!isMatch('z/z', '*b')); + }); + + test('should match one or zero characters', () => { + expect_truthy(isMatch('a', '*')); + expect_truthy(isMatch('aa', '*')); + expect_truthy(isMatch('aaa', '*')); + expect_truthy(isMatch('aaaa', '*')); + expect_truthy(isMatch('ab', '*')); + expect_truthy(isMatch('b', '*')); + expect_truthy(isMatch('bb', '*')); + expect_truthy(isMatch('c', '*')); + expect_truthy(isMatch('cc', '*')); + expect_truthy(isMatch('cac', '*')); + expect_truthy(!isMatch('a/a', '*')); + expect_truthy(!isMatch('a/b', '*')); + expect_truthy(!isMatch('a/c', '*')); + expect_truthy(!isMatch('a/x', '*')); + expect_truthy(!isMatch('a/a/a', '*')); + expect_truthy(!isMatch('a/a/b', '*')); + expect_truthy(!isMatch('a/a/a/a', '*')); + expect_truthy(!isMatch('a/a/a/a/a', '*')); + expect_truthy(!isMatch('x/y', '*')); + expect_truthy(!isMatch('z/z', '*')); + + expect_truthy(isMatch('a', '*a*')); + expect_truthy(isMatch('aa', '*a*')); + expect_truthy(isMatch('aaa', '*a*')); + expect_truthy(isMatch('aaaa', '*a*')); + expect_truthy(isMatch('ab', '*a*')); + expect_truthy(!isMatch('b', '*a*')); + expect_truthy(!isMatch('bb', '*a*')); + expect_truthy(!isMatch('c', '*a*')); + expect_truthy(!isMatch('cc', '*a*')); + expect_truthy(isMatch('cac', '*a*')); + expect_truthy(!isMatch('a/a', '*a*')); + expect_truthy(!isMatch('a/b', '*a*')); + expect_truthy(!isMatch('a/c', '*a*')); + expect_truthy(!isMatch('a/x', '*a*')); + expect_truthy(!isMatch('a/a/a', '*a*')); + expect_truthy(!isMatch('a/a/b', '*a*')); + expect_truthy(!isMatch('a/a/a/a', '*a*')); + expect_truthy(!isMatch('a/a/a/a/a', '*a*')); + expect_truthy(!isMatch('x/y', '*a*')); + expect_truthy(!isMatch('z/z', '*a*')); + + expect_truthy(!isMatch('a', '*b*')); + expect_truthy(!isMatch('aa', '*b*')); + expect_truthy(!isMatch('aaa', '*b*')); + expect_truthy(!isMatch('aaaa', '*b*')); + expect_truthy(isMatch('ab', '*b*')); + expect_truthy(isMatch('b', '*b*')); + expect_truthy(isMatch('bb', '*b*')); + expect_truthy(!isMatch('c', '*b*')); + expect_truthy(!isMatch('cc', '*b*')); + expect_truthy(!isMatch('cac', '*b*')); + expect_truthy(!isMatch('a/a', '*b*')); + expect_truthy(!isMatch('a/b', '*b*')); + expect_truthy(!isMatch('a/c', '*b*')); + expect_truthy(!isMatch('a/x', '*b*')); + expect_truthy(!isMatch('a/a/a', '*b*')); + expect_truthy(!isMatch('a/a/b', '*b*')); + expect_truthy(!isMatch('a/a/a/a', '*b*')); + expect_truthy(!isMatch('a/a/a/a/a', '*b*')); + expect_truthy(!isMatch('x/y', '*b*')); + expect_truthy(!isMatch('z/z', '*b*')); + + expect_truthy(!isMatch('a', '*c*')); + expect_truthy(!isMatch('aa', '*c*')); + expect_truthy(!isMatch('aaa', '*c*')); + expect_truthy(!isMatch('aaaa', '*c*')); + expect_truthy(!isMatch('ab', '*c*')); + expect_truthy(!isMatch('b', '*c*')); + expect_truthy(!isMatch('bb', '*c*')); + expect_truthy(isMatch('c', '*c*')); + expect_truthy(isMatch('cc', '*c*')); + expect_truthy(isMatch('cac', '*c*')); + expect_truthy(!isMatch('a/a', '*c*')); + expect_truthy(!isMatch('a/b', '*c*')); + expect_truthy(!isMatch('a/c', '*c*')); + expect_truthy(!isMatch('a/x', '*c*')); + expect_truthy(!isMatch('a/a/a', '*c*')); + expect_truthy(!isMatch('a/a/b', '*c*')); + expect_truthy(!isMatch('a/a/a/a', '*c*')); + expect_truthy(!isMatch('a/a/a/a/a', '*c*')); + expect_truthy(!isMatch('x/y', '*c*')); + expect_truthy(!isMatch('z/z', '*c*')); + }); + + test('should respect trailing slashes on paterns', () => { + expect_truthy(!isMatch('a', '*/')); + expect_truthy(isMatch('a/', '*/')); + expect_truthy(!isMatch('b', '*/')); + expect_truthy(isMatch('b/', '*/')); + expect_truthy(!isMatch('a/a', '*/')); + expect_truthy(!isMatch('a/a/', '*/')); + expect_truthy(!isMatch('a/b', '*/')); + expect_truthy(!isMatch('a/b/', '*/')); + expect_truthy(!isMatch('a/c', '*/')); + expect_truthy(!isMatch('a/c/', '*/')); + expect_truthy(!isMatch('a/x', '*/')); + expect_truthy(!isMatch('a/x/', '*/')); + expect_truthy(!isMatch('a/a/a', '*/')); + expect_truthy(!isMatch('a/a/b', '*/')); + expect_truthy(!isMatch('a/a/b/', '*/')); + expect_truthy(!isMatch('a/a/a/', '*/')); + expect_truthy(!isMatch('a/a/a/a', '*/')); + expect_truthy(!isMatch('a/a/a/a/', '*/')); + expect_truthy(!isMatch('a/a/a/a/a', '*/')); + expect_truthy(!isMatch('a/a/a/a/a/', '*/')); + expect_truthy(!isMatch('x/y', '*/')); + expect_truthy(!isMatch('z/z', '*/')); + expect_truthy(!isMatch('x/y/', '*/')); + expect_truthy(!isMatch('z/z/', '*/')); + expect_truthy(!isMatch('a/b/c/.d/e/', '*/')); + + expect_truthy(!isMatch('a', '*/*/')); + expect_truthy(!isMatch('a/', '*/*/')); + expect_truthy(!isMatch('b', '*/*/')); + expect_truthy(!isMatch('b/', '*/*/')); + expect_truthy(!isMatch('a/a', '*/*/')); + expect_truthy(isMatch('a/a/', '*/*/')); + expect_truthy(!isMatch('a/b', '*/*/')); + expect_truthy(isMatch('a/b/', '*/*/')); + expect_truthy(!isMatch('a/c', '*/*/')); + expect_truthy(isMatch('a/c/', '*/*/')); + expect_truthy(!isMatch('a/x', '*/*/')); + expect_truthy(isMatch('a/x/', '*/*/')); + expect_truthy(!isMatch('a/a/a', '*/*/')); + expect_truthy(!isMatch('a/a/b', '*/*/')); + expect_truthy(!isMatch('a/a/b/', '*/*/')); + expect_truthy(!isMatch('a/a/a/', '*/*/')); + expect_truthy(!isMatch('a/a/a/a', '*/*/')); + expect_truthy(!isMatch('a/a/a/a/', '*/*/')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*/')); + expect_truthy(!isMatch('a/a/a/a/a/', '*/*/')); + expect_truthy(!isMatch('x/y', '*/*/')); + expect_truthy(!isMatch('z/z', '*/*/')); + expect_truthy(isMatch('x/y/', '*/*/')); + expect_truthy(isMatch('z/z/', '*/*/')); + expect_truthy(!isMatch('a/b/c/.d/e/', '*/*/')); + + expect_truthy(!isMatch('a', '*/*/*/')); + expect_truthy(!isMatch('a/', '*/*/*/')); + expect_truthy(!isMatch('b', '*/*/*/')); + expect_truthy(!isMatch('b/', '*/*/*/')); + expect_truthy(!isMatch('a/a', '*/*/*/')); + expect_truthy(!isMatch('a/a/', '*/*/*/')); + expect_truthy(!isMatch('a/b', '*/*/*/')); + expect_truthy(!isMatch('a/b/', '*/*/*/')); + expect_truthy(!isMatch('a/c', '*/*/*/')); + expect_truthy(!isMatch('a/c/', '*/*/*/')); + expect_truthy(!isMatch('a/x', '*/*/*/')); + expect_truthy(!isMatch('a/x/', '*/*/*/')); + expect_truthy(!isMatch('a/a/a', '*/*/*/')); + expect_truthy(!isMatch('a/a/b', '*/*/*/')); + expect_truthy(isMatch('a/a/b/', '*/*/*/')); + expect_truthy(isMatch('a/a/a/', '*/*/*/')); + expect_truthy(!isMatch('a/a/a/a', '*/*/*/')); + expect_truthy(!isMatch('a/a/a/a/', '*/*/*/')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*/*/')); + expect_truthy(!isMatch('a/a/a/a/a/', '*/*/*/')); + expect_truthy(!isMatch('x/y', '*/*/*/')); + expect_truthy(!isMatch('z/z', '*/*/*/')); + expect_truthy(!isMatch('x/y/', '*/*/*/')); + expect_truthy(!isMatch('z/z/', '*/*/*/')); + expect_truthy(!isMatch('a/b/c/.d/e/', '*/*/*/')); + + expect_truthy(!isMatch('a', '*/*/*/*/')); + expect_truthy(!isMatch('a/', '*/*/*/*/')); + expect_truthy(!isMatch('b', '*/*/*/*/')); + expect_truthy(!isMatch('b/', '*/*/*/*/')); + expect_truthy(!isMatch('a/a', '*/*/*/*/')); + expect_truthy(!isMatch('a/a/', '*/*/*/*/')); + expect_truthy(!isMatch('a/b', '*/*/*/*/')); + expect_truthy(!isMatch('a/b/', '*/*/*/*/')); + expect_truthy(!isMatch('a/c', '*/*/*/*/')); + expect_truthy(!isMatch('a/c/', '*/*/*/*/')); + expect_truthy(!isMatch('a/x', '*/*/*/*/')); + expect_truthy(!isMatch('a/x/', '*/*/*/*/')); + expect_truthy(!isMatch('a/a/a', '*/*/*/*/')); + expect_truthy(!isMatch('a/a/b', '*/*/*/*/')); + expect_truthy(!isMatch('a/a/b/', '*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/', '*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a', '*/*/*/*/')); + expect_truthy(isMatch('a/a/a/a/', '*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a/a/', '*/*/*/*/')); + expect_truthy(!isMatch('x/y', '*/*/*/*/')); + expect_truthy(!isMatch('z/z', '*/*/*/*/')); + expect_truthy(!isMatch('x/y/', '*/*/*/*/')); + expect_truthy(!isMatch('z/z/', '*/*/*/*/')); + expect_truthy(!isMatch('a/b/c/.d/e/', '*/*/*/*/')); + + expect_truthy(!isMatch('a', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/', '*/*/*/*/*/')); + expect_truthy(!isMatch('b', '*/*/*/*/*/')); + expect_truthy(!isMatch('b/', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/a', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/a/', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/b', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/b/', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/c', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/c/', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/x', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/x/', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/a/a', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/a/b', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/a/b/', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a/', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*/*/*/*/')); + expect_truthy(isMatch('a/a/a/a/a/', '*/*/*/*/*/')); + expect_truthy(!isMatch('x/y', '*/*/*/*/*/')); + expect_truthy(!isMatch('z/z', '*/*/*/*/*/')); + expect_truthy(!isMatch('x/y/', '*/*/*/*/*/')); + expect_truthy(!isMatch('z/z/', '*/*/*/*/*/')); + expect_truthy(!isMatch('a/b/c/.d/e/', '*/*/*/*/*/')); + + expect_truthy(!isMatch('a', 'a/*/')); + expect_truthy(!isMatch('a/', 'a/*/')); + expect_truthy(!isMatch('b', 'a/*/')); + expect_truthy(!isMatch('b/', 'a/*/')); + expect_truthy(!isMatch('a/a', 'a/*/')); + expect_truthy(isMatch('a/a/', 'a/*/')); + expect_truthy(!isMatch('a/b', 'a/*/')); + expect_truthy(isMatch('a/b/', 'a/*/')); + expect_truthy(!isMatch('a/c', 'a/*/')); + expect_truthy(isMatch('a/c/', 'a/*/')); + expect_truthy(!isMatch('a/x', 'a/*/')); + expect_truthy(isMatch('a/x/', 'a/*/')); + expect_truthy(!isMatch('a/a/a', 'a/*/')); + expect_truthy(!isMatch('a/a/b', 'a/*/')); + expect_truthy(!isMatch('a/a/b/', 'a/*/')); + expect_truthy(!isMatch('a/a/a/', 'a/*/')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/')); + expect_truthy(!isMatch('a/a/a/a/', 'a/*/')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/')); + expect_truthy(!isMatch('a/a/a/a/a/', 'a/*/')); + expect_truthy(!isMatch('x/y', 'a/*/')); + expect_truthy(!isMatch('z/z', 'a/*/')); + expect_truthy(!isMatch('x/y/', 'a/*/')); + expect_truthy(!isMatch('z/z/', 'a/*/')); + expect_truthy(!isMatch('a/b/c/.d/e/', 'a/*/')); + + expect_truthy(!isMatch('a', 'a/*/*/')); + expect_truthy(!isMatch('a/', 'a/*/*/')); + expect_truthy(!isMatch('b', 'a/*/*/')); + expect_truthy(!isMatch('b/', 'a/*/*/')); + expect_truthy(!isMatch('a/a', 'a/*/*/')); + expect_truthy(!isMatch('a/a/', 'a/*/*/')); + expect_truthy(!isMatch('a/b', 'a/*/*/')); + expect_truthy(!isMatch('a/b/', 'a/*/*/')); + expect_truthy(!isMatch('a/c', 'a/*/*/')); + expect_truthy(!isMatch('a/c/', 'a/*/*/')); + expect_truthy(!isMatch('a/x', 'a/*/*/')); + expect_truthy(!isMatch('a/x/', 'a/*/*/')); + expect_truthy(!isMatch('a/a/a', 'a/*/*/')); + expect_truthy(!isMatch('a/a/b', 'a/*/*/')); + expect_truthy(isMatch('a/a/b/', 'a/*/*/')); + expect_truthy(isMatch('a/a/a/', 'a/*/*/')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/*/')); + expect_truthy(!isMatch('a/a/a/a/', 'a/*/*/')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/*/')); + expect_truthy(!isMatch('a/a/a/a/a/', 'a/*/*/')); + expect_truthy(!isMatch('x/y', 'a/*/*/')); + expect_truthy(!isMatch('z/z', 'a/*/*/')); + expect_truthy(!isMatch('x/y/', 'a/*/*/')); + expect_truthy(!isMatch('z/z/', 'a/*/*/')); + expect_truthy(!isMatch('a/b/c/.d/e/', 'a/*/*/')); + + expect_truthy(!isMatch('a', 'a/*/*/*/')); + expect_truthy(!isMatch('a/', 'a/*/*/*/')); + expect_truthy(!isMatch('b', 'a/*/*/*/')); + expect_truthy(!isMatch('b/', 'a/*/*/*/')); + expect_truthy(!isMatch('a/a', 'a/*/*/*/')); + expect_truthy(!isMatch('a/a/', 'a/*/*/*/')); + expect_truthy(!isMatch('a/b', 'a/*/*/*/')); + expect_truthy(!isMatch('a/b/', 'a/*/*/*/')); + expect_truthy(!isMatch('a/c', 'a/*/*/*/')); + expect_truthy(!isMatch('a/c/', 'a/*/*/*/')); + expect_truthy(!isMatch('a/x', 'a/*/*/*/')); + expect_truthy(!isMatch('a/x/', 'a/*/*/*/')); + expect_truthy(!isMatch('a/a/a', 'a/*/*/*/')); + expect_truthy(!isMatch('a/a/b', 'a/*/*/*/')); + expect_truthy(!isMatch('a/a/b/', 'a/*/*/*/')); + expect_truthy(!isMatch('a/a/a/', 'a/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/*/*/')); + expect_truthy(isMatch('a/a/a/a/', 'a/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a/a/', 'a/*/*/*/')); + expect_truthy(!isMatch('x/y', 'a/*/*/*/')); + expect_truthy(!isMatch('z/z', 'a/*/*/*/')); + expect_truthy(!isMatch('x/y/', 'a/*/*/*/')); + expect_truthy(!isMatch('z/z/', 'a/*/*/*/')); + expect_truthy(!isMatch('a/b/c/.d/e/', 'a/*/*/*/')); + + expect_truthy(!isMatch('a', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('b', 'a/*/*/*/*/')); + expect_truthy(!isMatch('b/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/a', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/a/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/b', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/b/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/c', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/c/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/x', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/x/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/a/a', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/a/b', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/a/b/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/*/*/*/')); + expect_truthy(isMatch('a/a/a/a/a/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('x/y', 'a/*/*/*/*/')); + expect_truthy(!isMatch('z/z', 'a/*/*/*/*/')); + expect_truthy(!isMatch('x/y/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('z/z/', 'a/*/*/*/*/')); + expect_truthy(!isMatch('a/b/c/.d/e/', 'a/*/*/*/*/')); + + expect_truthy(!isMatch('a', 'a/*/a/')); + expect_truthy(!isMatch('a/', 'a/*/a/')); + expect_truthy(!isMatch('b', 'a/*/a/')); + expect_truthy(!isMatch('b/', 'a/*/a/')); + expect_truthy(!isMatch('a/a', 'a/*/a/')); + expect_truthy(!isMatch('a/a/', 'a/*/a/')); + expect_truthy(!isMatch('a/b', 'a/*/a/')); + expect_truthy(!isMatch('a/b/', 'a/*/a/')); + expect_truthy(!isMatch('a/c', 'a/*/a/')); + expect_truthy(!isMatch('a/c/', 'a/*/a/')); + expect_truthy(!isMatch('a/x', 'a/*/a/')); + expect_truthy(!isMatch('a/x/', 'a/*/a/')); + expect_truthy(!isMatch('a/a/a', 'a/*/a/')); + expect_truthy(!isMatch('a/a/b', 'a/*/a/')); + expect_truthy(!isMatch('a/a/b/', 'a/*/a/')); + expect_truthy(isMatch('a/a/a/', 'a/*/a/')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/a/')); + expect_truthy(!isMatch('a/a/a/a/', 'a/*/a/')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/a/')); + expect_truthy(!isMatch('a/a/a/a/a/', 'a/*/a/')); + expect_truthy(!isMatch('x/y', 'a/*/a/')); + expect_truthy(!isMatch('z/z', 'a/*/a/')); + expect_truthy(!isMatch('x/y/', 'a/*/a/')); + expect_truthy(!isMatch('z/z/', 'a/*/a/')); + expect_truthy(!isMatch('a/b/c/.d/e/', 'a/*/a/')); + + expect_truthy(!isMatch('a', 'a/*/b/')); + expect_truthy(!isMatch('a/', 'a/*/b/')); + expect_truthy(!isMatch('b', 'a/*/b/')); + expect_truthy(!isMatch('b/', 'a/*/b/')); + expect_truthy(!isMatch('a/a', 'a/*/b/')); + expect_truthy(!isMatch('a/a/', 'a/*/b/')); + expect_truthy(!isMatch('a/b', 'a/*/b/')); + expect_truthy(!isMatch('a/b/', 'a/*/b/')); + expect_truthy(!isMatch('a/c', 'a/*/b/')); + expect_truthy(!isMatch('a/c/', 'a/*/b/')); + expect_truthy(!isMatch('a/x', 'a/*/b/')); + expect_truthy(!isMatch('a/x/', 'a/*/b/')); + expect_truthy(!isMatch('a/a/a', 'a/*/b/')); + expect_truthy(!isMatch('a/a/b', 'a/*/b/')); + expect_truthy(isMatch('a/a/b/', 'a/*/b/')); + expect_truthy(!isMatch('a/a/a/', 'a/*/b/')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/b/')); + expect_truthy(!isMatch('a/a/a/a/', 'a/*/b/')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/b/')); + expect_truthy(!isMatch('a/a/a/a/a/', 'a/*/b/')); + expect_truthy(!isMatch('x/y', 'a/*/b/')); + expect_truthy(!isMatch('z/z', 'a/*/b/')); + expect_truthy(!isMatch('x/y/', 'a/*/b/')); + expect_truthy(!isMatch('z/z/', 'a/*/b/')); + expect_truthy(!isMatch('a/b/c/.d/e/', 'a/*/b/')); + }); + + test('should match a literal star when escaped', () => { + expect_truthy(!isMatch('.md', '\\*')); + expect_truthy(!isMatch('a**a.md', '\\*')); + expect_truthy(!isMatch('**a.md', '\\*')); + expect_truthy(!isMatch('**/a.md', '\\*')); + expect_truthy(!isMatch('**.md', '\\*')); + expect_truthy(!isMatch('.md', '\\*')); + expect_truthy(isMatch('*', '\\*')); + expect_truthy(!isMatch('**', '\\*')); + expect_truthy(!isMatch('*.md', '\\*')); + + expect_truthy(!isMatch('.md', '\\*.md')); + expect_truthy(!isMatch('a**a.md', '\\*.md')); + expect_truthy(!isMatch('**a.md', '\\*.md')); + expect_truthy(!isMatch('**/a.md', '\\*.md')); + expect_truthy(!isMatch('**.md', '\\*.md')); + expect_truthy(!isMatch('.md', '\\*.md')); + expect_truthy(!isMatch('*', '\\*.md')); + expect_truthy(!isMatch('**', '\\*.md')); + expect_truthy(isMatch('*.md', '\\*.md')); + + expect_truthy(!isMatch('.md', '\\**.md')); + expect_truthy(!isMatch('a**a.md', '\\**.md')); + expect_truthy(isMatch('**a.md', '\\**.md')); + expect_truthy(!isMatch('**/a.md', '\\**.md')); + expect_truthy(isMatch('**.md', '\\**.md')); + expect_truthy(!isMatch('.md', '\\**.md')); + expect_truthy(!isMatch('*', '\\**.md')); + expect_truthy(!isMatch('**', '\\**.md')); + expect_truthy(isMatch('*.md', '\\**.md')); + + expect_truthy(!isMatch('.md', 'a\\**.md')); + expect_truthy(isMatch('a**a.md', 'a\\**.md')); + expect_truthy(!isMatch('**a.md', 'a\\**.md')); + expect_truthy(!isMatch('**/a.md', 'a\\**.md')); + expect_truthy(!isMatch('**.md', 'a\\**.md')); + expect_truthy(!isMatch('.md', 'a\\**.md')); + expect_truthy(!isMatch('*', 'a\\**.md')); + expect_truthy(!isMatch('**', 'a\\**.md')); + expect_truthy(!isMatch('*.md', 'a\\**.md')); + }); + + test('should match file paths', () => { + expect_truthy(!isMatch('a/.b', 'a/**/z/*.md')); + expect_truthy(!isMatch('a/b/c/j/e/z/c.txt', 'a/**/j/**/z/*.md')); + expect_truthy(!isMatch('a/b/z/.a', 'a/**/z/*.a')); + expect_truthy(!isMatch('a/b/z/.a', 'a/*/z/*.a')); + expect_truthy(!isMatch('foo.txt', '*/*.txt')); + expect_truthy(isMatch('a/.b', 'a/.*')); + expect_truthy(isMatch('a/b/c/d/e/j/n/p/o/z/c.md', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('a/b/c/d/e/z/c.md', 'a/**/z/*.md')); + expect_truthy(isMatch('a/b/c/xyz.md', 'a/b/c/*.md')); + expect_truthy(isMatch('a/b/z/.a', 'a/*/z/.a')); + expect_truthy(isMatch('a/bb.bb/aa/b.b/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(isMatch('a/bb.bb/aa/bb/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(isMatch('a/bb.bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bbbb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('foo.txt', '**/foo.txt')); + expect_truthy(isMatch('foo/bar.txt', '**/*.txt')); + expect_truthy(isMatch('foo/bar/baz.txt', '**/*.txt')); + }); + + test('should match paths with leading `./` when pattern has `./`', () => { + const format = str => str.replace(/^\.\//, ''); + expect_truthy(!isMatch('./a/b/c/d/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(!isMatch('./a/b/c/j/e/z/c.txt', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/j/n/p/o/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/d/e/z/c.md', './a/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/c/j/e/z/c.md', './a/**/j/**/z/*.md', { format })); + expect_truthy(isMatch('./a/b/z/.a', './a/**/z/.a', { format })); + }); + + test('should match leading slashes', () => { + expect_truthy(!isMatch('ef', '/*')); + expect_truthy(isMatch('/ef', '/*')); + expect_truthy(isMatch('/foo/bar.txt', '/foo/*')); + expect_truthy(isMatch('/foo/bar.txt', '/foo/**')); + expect_truthy(isMatch('/foo/bar.txt', '/foo/**/**/*.txt')); + expect_truthy(isMatch('/foo/bar.txt', '/foo/**/**/bar.txt')); + expect_truthy(isMatch('/foo/bar.txt', '/foo/**/*.txt')); + expect_truthy(isMatch('/foo/bar.txt', '/foo/**/bar.txt')); + expect_truthy(!isMatch('/foo/bar.txt', '/foo/*/bar.txt')); + expect_truthy(!isMatch('/foo/bar/baz.txt', '/foo/*')); + expect_truthy(isMatch('/foo/bar/baz.txt', '/foo/**')); + expect_truthy(isMatch('/foo/bar/baz.txt', '/foo/**')); + expect_truthy(isMatch('/foo/bar/baz.txt', '/foo/**/*.txt')); + expect_truthy(isMatch('/foo/bar/baz.txt', '/foo/**/*/*.txt')); + expect_truthy(isMatch('/foo/bar/baz.txt', '/foo/**/*/baz.txt')); + expect_truthy(!isMatch('/foo/bar/baz.txt', '/foo/*.txt')); + expect_truthy(isMatch('/foo/bar/baz.txt', '/foo/*/*.txt')); + expect_truthy(!isMatch('/foo/bar/baz.txt', '/foo/*/*/baz.txt')); + expect_truthy(!isMatch('/foo/bar/baz.txt', '/foo/bar**')); + expect_truthy(isMatch('/foo/bar/baz/qux.txt', '**/*.txt')); + expect_truthy(!isMatch('/foo/bar/baz/qux.txt', '**/.txt')); + expect_truthy(!isMatch('/foo/bar/baz/qux.txt', '*/*.txt')); + expect_truthy(!isMatch('/foo/bar/baz/qux.txt', '/foo/**.txt')); + expect_truthy(isMatch('/foo/bar/baz/qux.txt', '/foo/**/*.txt')); + expect_truthy(!isMatch('/foo/bar/baz/qux.txt', '/foo/*/*.txt')); + expect_truthy(!isMatch('/foo/bar/baz/qux.txt', '/foo/bar**/*.txt')); + expect_truthy(!isMatch('/.txt', '*.txt')); + expect_truthy(!isMatch('/.txt', '/*.txt')); + expect_truthy(!isMatch('/.txt', '*/*.txt')); + expect_truthy(!isMatch('/.txt', '**/*.txt')); + expect_truthy(!isMatch('/.txt', '*.txt', { dot: true })); + expect_truthy(isMatch('/.txt', '/*.txt', { dot: true })); + expect_truthy(isMatch('/.txt', '*/*.txt', { dot: true })); + expect_truthy(isMatch('/.txt', '**/*.txt', { dot: true })); + }); + + test('should match double slashes', () => { + expect_truthy(!isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/*')); + expect_truthy(!isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/*')); + expect_truthy(isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/**')); + expect_truthy(!isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/**', { noglobstar: true })); + expect_truthy(isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/**')); + expect_truthy(isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/**/app.min.js')); + expect_truthy(isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/*/*/app.min.js')); + expect_truthy(isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/*/*/app.min.js', { noglobstar: true })); + expect_truthy(!isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/*/app.min.js')); + expect_truthy(!isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/*/app.min.js')); + expect_truthy(isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/**/app.min.js')); + expect_truthy(!isMatch('https://foo.com/bar/baz/app.min.js', 'https://foo.com/**/app.min.js', { noglobstar: true })); + }); +}); diff --git a/packages/node-utils/test/picomatch/slashes-windows.test.ts b/packages/node-utils/test/picomatch/slashes-windows.test.ts new file mode 100644 index 0000000..a591999 --- /dev/null +++ b/packages/node-utils/test/picomatch/slashes-windows.test.ts @@ -0,0 +1,559 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch, makeRe } = picomatch; + +describe('slash handling - windows', () => { + test('should match absolute windows paths with regex from makeRe', () => { + const regex = makeRe('**/path/**', { windows: true }); + expect_truthy(regex.test('C:\\Users\\user\\Projects\\project\\path\\image.jpg', { windows: true })); + }); + + test('should match windows path separators with a string literal', () => { + expect_truthy(!isMatch('a\\a', '(a/b)', { windows: true })); + expect_truthy(isMatch('a\\b', '(a/b)', { windows: true })); + expect_truthy(!isMatch('a\\c', '(a/b)', { windows: true })); + expect_truthy(!isMatch('b\\a', '(a/b)', { windows: true })); + expect_truthy(!isMatch('b\\b', '(a/b)', { windows: true })); + expect_truthy(!isMatch('b\\c', '(a/b)', { windows: true })); + + expect_truthy(!isMatch('a\\a', 'a/b', { windows: true })); + expect_truthy(isMatch('a\\b', 'a/b', { windows: true })); + expect_truthy(!isMatch('a\\c', 'a/b', { windows: true })); + expect_truthy(!isMatch('b\\a', 'a/b', { windows: true })); + expect_truthy(!isMatch('b\\b', 'a/b', { windows: true })); + expect_truthy(!isMatch('b\\c', 'a/b', { windows: true })); + }); + + test('should not match literal backslashes with literal forward slashes when windows is disabled', () => { + expect_truthy(!isMatch('a\\a', 'a\\b', { windows: false })); + expect_truthy(isMatch('a\\b', 'a\\b', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a\\b', { windows: false })); + expect_truthy(!isMatch('b\\a', 'a\\b', { windows: false })); + expect_truthy(!isMatch('b\\b', 'a\\b', { windows: false })); + expect_truthy(!isMatch('b\\c', 'a\\b', { windows: false })); + + expect_truthy(!isMatch('a\\a', 'a/b', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/b', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/b', { windows: false })); + expect_truthy(!isMatch('b\\a', 'a/b', { windows: false })); + expect_truthy(!isMatch('b\\b', 'a/b', { windows: false })); + expect_truthy(!isMatch('b\\c', 'a/b', { windows: false })); + }); + + test('should match an array of literal strings', () => { + expect_truthy(!isMatch('a\\a', '(a/b)', { windows: true })); + expect_truthy(isMatch('a\\b', '(a/b)', { windows: true })); + expect_truthy(!isMatch('a\\c', '(a/b)', { windows: true })); + expect_truthy(!isMatch('b\\a', '(a/b)', { windows: true })); + expect_truthy(!isMatch('b\\b', '(a/b)', { windows: true })); + expect_truthy(!isMatch('b\\c', '(a/b)', { windows: true })); + }); + + test('should not match backslashes with forward slashes when windows is disabled', () => { + expect_truthy(!isMatch('a\\a', 'a/(a|c)', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/(a|c)', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/(a|c)', { windows: false })); + expect_truthy(!isMatch('a\\a', 'a/(a|b|c)', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/(a|b|c)', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/(a|b|c)', { windows: false })); + expect_truthy(!isMatch('a\\a', '(a\\b)', { windows: false })); + expect_truthy(isMatch('a\\b', '(a\\\\b)', { windows: false })); + expect_truthy(!isMatch('a\\c', '(a\\b)', { windows: false })); + expect_truthy(!isMatch('b\\a', '(a\\b)', { windows: false })); + expect_truthy(!isMatch('b\\b', '(a\\b)', { windows: false })); + expect_truthy(!isMatch('b\\c', '(a\\b)', { windows: false })); + expect_truthy(!isMatch('a\\a', '(a/b)', { windows: false })); + expect_truthy(!isMatch('a\\b', '(a/b)', { windows: false })); + expect_truthy(!isMatch('a\\c', '(a/b)', { windows: false })); + expect_truthy(!isMatch('b\\a', '(a/b)', { windows: false })); + expect_truthy(!isMatch('b\\b', '(a/b)', { windows: false })); + expect_truthy(!isMatch('b\\c', '(a/b)', { windows: false })); + + expect_truthy(!isMatch('a\\a', 'a/c', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/c', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/c', { windows: false })); + expect_truthy(!isMatch('b\\a', 'a/c', { windows: false })); + expect_truthy(!isMatch('b\\b', 'a/c', { windows: false })); + expect_truthy(!isMatch('b\\c', 'a/c', { windows: false })); + }); + + test('should match backslashes when followed by regex logical "or"', () => { + expect_truthy(isMatch('a\\a', 'a/(a|c)', { windows: true })); + expect_truthy(!isMatch('a\\b', 'a/(a|c)', { windows: true })); + expect_truthy(isMatch('a\\c', 'a/(a|c)', { windows: true })); + + expect_truthy(isMatch('a\\a', 'a/(a|b|c)', { windows: true })); + expect_truthy(isMatch('a\\b', 'a/(a|b|c)', { windows: true })); + expect_truthy(isMatch('a\\c', 'a/(a|b|c)', { windows: true })); + }); + + test('should support matching backslashes with regex ranges', () => { + expect_truthy(!isMatch('a\\a', 'a/[b-c]', { windows: true })); + expect_truthy(isMatch('a\\b', 'a/[b-c]', { windows: true })); + expect_truthy(isMatch('a\\c', 'a/[b-c]', { windows: true })); + expect_truthy(!isMatch('a\\x\\y', 'a/[b-c]', { windows: true })); + expect_truthy(!isMatch('a\\x', 'a/[b-c]', { windows: true })); + + expect_truthy(isMatch('a\\a', 'a/[a-z]', { windows: true })); + expect_truthy(isMatch('a\\b', 'a/[a-z]', { windows: true })); + expect_truthy(isMatch('a\\c', 'a/[a-z]', { windows: true })); + expect_truthy(!isMatch('a\\x\\y', 'a/[a-z]', { windows: true })); + expect_truthy(isMatch('a\\x\\y', 'a/[a-z]/y', { windows: true })); + expect_truthy(isMatch('a\\x', 'a/[a-z]', { windows: true })); + + expect_truthy(!isMatch('a\\a', 'a/[b-c]', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/[b-c]', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/[b-c]', { windows: false })); + expect_truthy(!isMatch('a\\x\\y', 'a/[b-c]', { windows: false })); + expect_truthy(!isMatch('a\\x', 'a/[b-c]', { windows: false })); + + expect_truthy(!isMatch('a\\a', 'a/[a-z]', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/[a-z]', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/[a-z]', { windows: false })); + expect_truthy(!isMatch('a\\x\\y', 'a/[a-z]', { windows: false })); + expect_truthy(!isMatch('a\\x', 'a/[a-z]', { windows: false })); + }); + + test('should not match slashes with single stars', () => { + expect_truthy(isMatch('a', '*', { windows: true })); + expect_truthy(isMatch('b', '*', { windows: true })); + expect_truthy(!isMatch('a\\a', '*', { windows: true })); + expect_truthy(!isMatch('a\\b', '*', { windows: true })); + expect_truthy(!isMatch('a\\c', '*', { windows: true })); + expect_truthy(!isMatch('a\\x', '*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a', '*', { windows: true })); + expect_truthy(!isMatch('a\\a\\b', '*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a', '*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', '*', { windows: true })); + expect_truthy(!isMatch('x\\y', '*', { windows: true })); + expect_truthy(!isMatch('z\\z', '*', { windows: true })); + + expect_truthy(!isMatch('a', '*/*', { windows: true })); + expect_truthy(!isMatch('b', '*/*', { windows: true })); + expect_truthy(isMatch('a\\a', '*/*', { windows: true })); + expect_truthy(isMatch('a\\b', '*/*', { windows: true })); + expect_truthy(isMatch('a\\c', '*/*', { windows: true })); + expect_truthy(isMatch('a\\x', '*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a', '*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\b', '*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a', '*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', '*/*', { windows: true })); + expect_truthy(isMatch('x\\y', '*/*', { windows: true })); + expect_truthy(isMatch('z\\z', '*/*', { windows: true })); + + expect_truthy(!isMatch('a', '*/*/*', { windows: true })); + expect_truthy(!isMatch('b', '*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a', '*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\b', '*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\c', '*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\x', '*/*/*', { windows: true })); + expect_truthy(isMatch('a\\a\\a', '*/*/*', { windows: true })); + expect_truthy(isMatch('a\\a\\b', '*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a', '*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', '*/*/*', { windows: true })); + expect_truthy(!isMatch('x\\y', '*/*/*', { windows: true })); + expect_truthy(!isMatch('z\\z', '*/*/*', { windows: true })); + + expect_truthy(!isMatch('a', '*/*/*/*', { windows: true })); + expect_truthy(!isMatch('b', '*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a', '*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\b', '*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\c', '*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\x', '*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a', '*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\b', '*/*/*/*', { windows: true })); + expect_truthy(isMatch('a\\a\\a\\a', '*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', '*/*/*/*', { windows: true })); + expect_truthy(!isMatch('x\\y', '*/*/*/*', { windows: true })); + expect_truthy(!isMatch('z\\z', '*/*/*/*', { windows: true })); + + expect_truthy(!isMatch('a', '*/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('b', '*/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a', '*/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\b', '*/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\c', '*/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\x', '*/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a', '*/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\b', '*/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a', '*/*/*/*/*', { windows: true })); + expect_truthy(isMatch('a\\a\\a\\a\\a', '*/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('x\\y', '*/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('z\\z', '*/*/*/*/*', { windows: true })); + + expect_truthy(!isMatch('a', 'a/*', { windows: true })); + expect_truthy(!isMatch('b', 'a/*', { windows: true })); + expect_truthy(isMatch('a\\a', 'a/*', { windows: true })); + expect_truthy(isMatch('a\\b', 'a/*', { windows: true })); + expect_truthy(isMatch('a\\c', 'a/*', { windows: true })); + expect_truthy(isMatch('a\\x', 'a/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a', 'a/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\b', 'a/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a', 'a/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', 'a/*', { windows: true })); + expect_truthy(!isMatch('x\\y', 'a/*', { windows: true })); + expect_truthy(!isMatch('z\\z', 'a/*', { windows: true })); + + expect_truthy(!isMatch('a', 'a/*/*', { windows: true })); + expect_truthy(!isMatch('b', 'a/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a', 'a/*/*', { windows: true })); + expect_truthy(!isMatch('a\\b', 'a/*/*', { windows: true })); + expect_truthy(!isMatch('a\\c', 'a/*/*', { windows: true })); + expect_truthy(!isMatch('a\\x', 'a/*/*', { windows: true })); + expect_truthy(isMatch('a\\a\\a', 'a/*/*', { windows: true })); + expect_truthy(isMatch('a\\a\\b', 'a/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a', 'a/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', 'a/*/*', { windows: true })); + expect_truthy(!isMatch('x\\y', 'a/*/*', { windows: true })); + expect_truthy(!isMatch('z\\z', 'a/*/*', { windows: true })); + + expect_truthy(!isMatch('a', 'a/*/*/*', { windows: true })); + expect_truthy(!isMatch('b', 'a/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a', 'a/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\b', 'a/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\c', 'a/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\x', 'a/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a', 'a/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\b', 'a/*/*/*', { windows: true })); + expect_truthy(isMatch('a\\a\\a\\a', 'a/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', 'a/*/*/*', { windows: true })); + expect_truthy(!isMatch('x\\y', 'a/*/*/*', { windows: true })); + expect_truthy(!isMatch('z\\z', 'a/*/*/*', { windows: true })); + + expect_truthy(!isMatch('a', 'a/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('b', 'a/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a', 'a/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\b', 'a/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\c', 'a/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\x', 'a/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a', 'a/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\b', 'a/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a', 'a/*/*/*/*', { windows: true })); + expect_truthy(isMatch('a\\a\\a\\a\\a', 'a/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('x\\y', 'a/*/*/*/*', { windows: true })); + expect_truthy(!isMatch('z\\z', 'a/*/*/*/*', { windows: true })); + + expect_truthy(!isMatch('a', 'a/*/a', { windows: true })); + expect_truthy(!isMatch('b', 'a/*/a', { windows: true })); + expect_truthy(!isMatch('a\\a', 'a/*/a', { windows: true })); + expect_truthy(!isMatch('a\\b', 'a/*/a', { windows: true })); + expect_truthy(!isMatch('a\\c', 'a/*/a', { windows: true })); + expect_truthy(!isMatch('a\\x', 'a/*/a', { windows: true })); + expect_truthy(isMatch('a\\a\\a', 'a/*/a', { windows: true })); + expect_truthy(!isMatch('a\\a\\b', 'a/*/a', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a', 'a/*/a', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', 'a/*/a', { windows: true })); + expect_truthy(!isMatch('x\\y', 'a/*/a', { windows: true })); + expect_truthy(!isMatch('z\\z', 'a/*/a', { windows: true })); + + expect_truthy(!isMatch('a', 'a/*/b', { windows: true })); + expect_truthy(!isMatch('b', 'a/*/b', { windows: true })); + expect_truthy(!isMatch('a\\a', 'a/*/b', { windows: true })); + expect_truthy(!isMatch('a\\b', 'a/*/b', { windows: true })); + expect_truthy(!isMatch('a\\c', 'a/*/b', { windows: true })); + expect_truthy(!isMatch('a\\x', 'a/*/b', { windows: true })); + expect_truthy(!isMatch('a\\a\\a', 'a/*/b', { windows: true })); + expect_truthy(isMatch('a\\a\\b', 'a/*/b', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a', 'a/*/b', { windows: true })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', 'a/*/b', { windows: true })); + expect_truthy(!isMatch('x\\y', 'a/*/b', { windows: true })); + expect_truthy(!isMatch('z\\z', 'a/*/b', { windows: true })); + + expect_truthy(!isMatch('a', '*/*', { windows: false })); + expect_truthy(!isMatch('b', '*/*', { windows: false })); + expect_truthy(!isMatch('a\\a', '*/*', { windows: false })); + expect_truthy(!isMatch('a\\b', '*/*', { windows: false })); + expect_truthy(!isMatch('a\\c', '*/*', { windows: false })); + expect_truthy(!isMatch('a\\x', '*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a', '*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\b', '*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a', '*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', '*/*', { windows: false })); + expect_truthy(!isMatch('x\\y', '*/*', { windows: false })); + expect_truthy(!isMatch('z\\z', '*/*', { windows: false })); + + expect_truthy(!isMatch('a', '*/*/*', { windows: false })); + expect_truthy(!isMatch('b', '*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a', '*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\b', '*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\c', '*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\x', '*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a', '*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\b', '*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a', '*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', '*/*/*', { windows: false })); + expect_truthy(!isMatch('x\\y', '*/*/*', { windows: false })); + expect_truthy(!isMatch('z\\z', '*/*/*', { windows: false })); + + expect_truthy(!isMatch('a', '*/*/*/*', { windows: false })); + expect_truthy(!isMatch('b', '*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a', '*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\b', '*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\c', '*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\x', '*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a', '*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\b', '*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a', '*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', '*/*/*/*', { windows: false })); + expect_truthy(!isMatch('x\\y', '*/*/*/*', { windows: false })); + expect_truthy(!isMatch('z\\z', '*/*/*/*', { windows: false })); + + expect_truthy(!isMatch('a', '*/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('b', '*/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a', '*/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\b', '*/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\c', '*/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\x', '*/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a', '*/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\b', '*/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a', '*/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', '*/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('x\\y', '*/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('z\\z', '*/*/*/*/*', { windows: false })); + + expect_truthy(!isMatch('a', 'a/*', { windows: false })); + expect_truthy(!isMatch('b', 'a/*', { windows: false })); + expect_truthy(!isMatch('a\\a', 'a/*', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/*', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/*', { windows: false })); + expect_truthy(!isMatch('a\\x', 'a/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a', 'a/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\b', 'a/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a', 'a/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', 'a/*', { windows: false })); + expect_truthy(!isMatch('x\\y', 'a/*', { windows: false })); + expect_truthy(!isMatch('z\\z', 'a/*', { windows: false })); + + expect_truthy(!isMatch('a', 'a/*/*', { windows: false })); + expect_truthy(!isMatch('b', 'a/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a', 'a/*/*', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/*/*', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/*/*', { windows: false })); + expect_truthy(!isMatch('a\\x', 'a/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a', 'a/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\b', 'a/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a', 'a/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', 'a/*/*', { windows: false })); + expect_truthy(!isMatch('x\\y', 'a/*/*', { windows: false })); + expect_truthy(!isMatch('z\\z', 'a/*/*', { windows: false })); + + expect_truthy(!isMatch('a', 'a/*/*/*', { windows: false })); + expect_truthy(!isMatch('b', 'a/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a', 'a/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\x', 'a/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a', 'a/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\b', 'a/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a', 'a/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', 'a/*/*/*', { windows: false })); + expect_truthy(!isMatch('x\\y', 'a/*/*/*', { windows: false })); + expect_truthy(!isMatch('z\\z', 'a/*/*/*', { windows: false })); + + expect_truthy(!isMatch('a', 'a/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('b', 'a/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a', 'a/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\x', 'a/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a', 'a/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\b', 'a/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a', 'a/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', 'a/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('x\\y', 'a/*/*/*/*', { windows: false })); + expect_truthy(!isMatch('z\\z', 'a/*/*/*/*', { windows: false })); + + expect_truthy(!isMatch('a', 'a/*/a', { windows: false })); + expect_truthy(!isMatch('b', 'a/*/a', { windows: false })); + expect_truthy(!isMatch('a\\a', 'a/*/a', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/*/a', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/*/a', { windows: false })); + expect_truthy(!isMatch('a\\x', 'a/*/a', { windows: false })); + expect_truthy(!isMatch('a\\a\\a', 'a/*/a', { windows: false })); + expect_truthy(!isMatch('a\\a\\b', 'a/*/a', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a', 'a/*/a', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', 'a/*/a', { windows: false })); + expect_truthy(!isMatch('x\\y', 'a/*/a', { windows: false })); + expect_truthy(!isMatch('z\\z', 'a/*/a', { windows: false })); + + expect_truthy(!isMatch('a', 'a/*/b', { windows: false })); + expect_truthy(!isMatch('b', 'a/*/b', { windows: false })); + expect_truthy(!isMatch('a\\a', 'a/*/b', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/*/b', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/*/b', { windows: false })); + expect_truthy(!isMatch('a\\x', 'a/*/b', { windows: false })); + expect_truthy(!isMatch('a\\a\\a', 'a/*/b', { windows: false })); + expect_truthy(!isMatch('a\\a\\b', 'a/*/b', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a', 'a/*/b', { windows: false })); + expect_truthy(!isMatch('a\\a\\a\\a\\a', 'a/*/b', { windows: false })); + expect_truthy(!isMatch('x\\y', 'a/*/b', { windows: false })); + expect_truthy(!isMatch('z\\z', 'a/*/b', { windows: false })); + }); + + test('should support globstars (**)', () => { + expect_truthy(isMatch('a\\a', 'a/**', { windows: true })); + expect_truthy(isMatch('a\\b', 'a/**', { windows: true })); + expect_truthy(isMatch('a\\c', 'a/**', { windows: true })); + expect_truthy(isMatch('a\\x', 'a/**', { windows: true })); + expect_truthy(isMatch('a\\x\\y', 'a/**', { windows: true })); + expect_truthy(isMatch('a\\x\\y\\z', 'a/**', { windows: true })); + + expect_truthy(isMatch('a\\a', 'a/**/*', { windows: true })); + expect_truthy(isMatch('a\\b', 'a/**/*', { windows: true })); + expect_truthy(isMatch('a\\c', 'a/**/*', { windows: true })); + expect_truthy(isMatch('a\\x', 'a/**/*', { windows: true })); + expect_truthy(isMatch('a\\x\\y', 'a/**/*', { windows: true })); + expect_truthy(isMatch('a\\x\\y\\z', 'a/**/*', { windows: true })); + + expect_truthy(isMatch('a\\a', 'a/**/**/*', { windows: true })); + expect_truthy(isMatch('a\\b', 'a/**/**/*', { windows: true })); + expect_truthy(isMatch('a\\c', 'a/**/**/*', { windows: true })); + expect_truthy(isMatch('a\\x', 'a/**/**/*', { windows: true })); + expect_truthy(isMatch('a\\x\\y', 'a/**/**/*', { windows: true })); + expect_truthy(isMatch('a\\x\\y\\z', 'a/**/**/*', { windows: true })); + }); + + test('should not match backslashes with globstars when disabled', () => { + expect_truthy(!isMatch('a\\a', 'a/**', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/**', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/**', { windows: false })); + expect_truthy(!isMatch('a\\x', 'a/**', { windows: false })); + expect_truthy(!isMatch('a\\x\\y', 'a/**', { windows: false })); + expect_truthy(!isMatch('a\\x\\y\\z', 'a/**', { windows: false })); + + expect_truthy(!isMatch('a\\a', 'a/**/*', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/**/*', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/**/*', { windows: false })); + expect_truthy(!isMatch('a\\x', 'a/**/*', { windows: false })); + expect_truthy(!isMatch('a\\x\\y', 'a/**/*', { windows: false })); + expect_truthy(!isMatch('a\\x\\y\\z', 'a/**/*', { windows: false })); + + expect_truthy(!isMatch('a\\a', 'a/**/**/*', { windows: false })); + expect_truthy(!isMatch('a\\b', 'a/**/**/*', { windows: false })); + expect_truthy(!isMatch('a\\c', 'a/**/**/*', { windows: false })); + expect_truthy(!isMatch('a\\x', 'a/**/**/*', { windows: false })); + expect_truthy(!isMatch('a\\x\\y', 'a/**/**/*', { windows: false })); + expect_truthy(!isMatch('a\\x\\y\\z', 'a/**/**/*', { windows: false })); + }); + + test('should work with file extensions', () => { + expect_truthy(isMatch('a.txt', 'a*.txt', { windows: true })); + expect_truthy(!isMatch('a\\b.txt', 'a*.txt', { windows: true })); + expect_truthy(!isMatch('a\\x\\y.txt', 'a*.txt', { windows: true })); + expect_truthy(!isMatch('a\\x\\y\\z', 'a*.txt', { windows: true })); + + expect_truthy(isMatch('a.txt', 'a.txt', { windows: true })); + expect_truthy(!isMatch('a\\b.txt', 'a.txt', { windows: true })); + expect_truthy(!isMatch('a\\x\\y.txt', 'a.txt', { windows: true })); + expect_truthy(!isMatch('a\\x\\y\\z', 'a.txt', { windows: true })); + + expect_truthy(!isMatch('a.txt', 'a/**/*.txt', { windows: true })); + expect_truthy(isMatch('a\\b.txt', 'a/**/*.txt', { windows: true })); + expect_truthy(isMatch('a\\x\\y.txt', 'a/**/*.txt', { windows: true })); + expect_truthy(!isMatch('a\\x\\y\\z', 'a/**/*.txt', { windows: true })); + + expect_truthy(!isMatch('a.txt', 'a/**/*.txt', { windows: false })); + expect_truthy(!isMatch('a\\b.txt', 'a/**/*.txt', { windows: false })); + expect_truthy(!isMatch('a\\x\\y.txt', 'a/**/*.txt', { windows: false })); + expect_truthy(!isMatch('a\\x\\y\\z', 'a/**/*.txt', { windows: false })); + + expect_truthy(!isMatch('a.txt', 'a/*.txt', { windows: true })); + expect_truthy(isMatch('a\\b.txt', 'a/*.txt', { windows: true })); + expect_truthy(!isMatch('a\\x\\y.txt', 'a/*.txt', { windows: true })); + expect_truthy(!isMatch('a\\x\\y\\z', 'a/*.txt', { windows: true })); + + expect_truthy(!isMatch('a.txt', 'a/*.txt', { windows: false })); + expect_truthy(!isMatch('a\\b.txt', 'a/*.txt', { windows: false })); + expect_truthy(!isMatch('a\\x\\y.txt', 'a/*.txt', { windows: false })); + expect_truthy(!isMatch('a\\x\\y\\z', 'a/*.txt', { windows: false })); + + expect_truthy(!isMatch('a.txt', 'a/*/*.txt', { windows: true })); + expect_truthy(!isMatch('a\\b.txt', 'a/*/*.txt', { windows: true })); + expect_truthy(isMatch('a\\x\\y.txt', 'a/*/*.txt', { windows: true })); + expect_truthy(!isMatch('a\\x\\y\\z', 'a/*/*.txt', { windows: true })); + + expect_truthy(!isMatch('a.txt', 'a/*/*.txt', { windows: false })); + expect_truthy(!isMatch('a\\b.txt', 'a/*/*.txt', { windows: false })); + expect_truthy(!isMatch('a\\x\\y.txt', 'a/*/*.txt', { windows: false })); + expect_truthy(!isMatch('a\\x\\y\\z', 'a/*/*.txt', { windows: false })); + }); + + test('should support negation patterns', () => { + expect_truthy(isMatch('a', '!a/b', { windows: true })); + expect_truthy(isMatch('a\\a', '!a/b', { windows: true })); + expect_truthy(!isMatch('a\\b', '!a/b', { windows: true })); + expect_truthy(isMatch('a\\c', '!a/b', { windows: true })); + expect_truthy(isMatch('b\\a', '!a/b', { windows: true })); + expect_truthy(isMatch('b\\b', '!a/b', { windows: true })); + expect_truthy(isMatch('b\\c', '!a/b', { windows: true })); + + expect_truthy(isMatch('a', '!*/c', { windows: true })); + expect_truthy(isMatch('a\\a', '!*/c', { windows: true })); + expect_truthy(isMatch('a\\b', '!*/c', { windows: true })); + expect_truthy(!isMatch('a\\c', '!*/c', { windows: true })); + expect_truthy(isMatch('b\\a', '!*/c', { windows: true })); + expect_truthy(isMatch('b\\b', '!*/c', { windows: true })); + expect_truthy(!isMatch('b\\c', '!*/c', { windows: true })); + + expect_truthy(isMatch('a', '!a/b', { windows: true })); + expect_truthy(isMatch('a\\a', '!a/b', { windows: true })); + expect_truthy(!isMatch('a\\b', '!a/b', { windows: true })); + expect_truthy(isMatch('a\\c', '!a/b', { windows: true })); + expect_truthy(isMatch('b\\a', '!a/b', { windows: true })); + expect_truthy(isMatch('b\\b', '!a/b', { windows: true })); + expect_truthy(isMatch('b\\c', '!a/b', { windows: true })); + + expect_truthy(isMatch('a', '!*/c', { windows: true })); + expect_truthy(isMatch('a\\a', '!*/c', { windows: true })); + expect_truthy(isMatch('a\\b', '!*/c', { windows: true })); + expect_truthy(!isMatch('a\\c', '!*/c', { windows: true })); + expect_truthy(isMatch('b\\a', '!*/c', { windows: true })); + expect_truthy(isMatch('b\\b', '!*/c', { windows: true })); + expect_truthy(!isMatch('b\\c', '!*/c', { windows: true })); + + expect_truthy(isMatch('a', '!a/(b)', { windows: true })); + expect_truthy(isMatch('a\\a', '!a/(b)', { windows: true })); + expect_truthy(!isMatch('a\\b', '!a/(b)', { windows: true })); + expect_truthy(isMatch('a\\c', '!a/(b)', { windows: true })); + expect_truthy(isMatch('b\\a', '!a/(b)', { windows: true })); + expect_truthy(isMatch('b\\b', '!a/(b)', { windows: true })); + expect_truthy(isMatch('b\\c', '!a/(b)', { windows: true })); + + expect_truthy(isMatch('a', '!(a/b)', { windows: true })); + expect_truthy(isMatch('a\\a', '!(a/b)', { windows: true })); + expect_truthy(!isMatch('a\\b', '!(a/b)', { windows: true })); + expect_truthy(isMatch('a\\c', '!(a/b)', { windows: true })); + expect_truthy(isMatch('b\\a', '!(a/b)', { windows: true })); + expect_truthy(isMatch('b\\b', '!(a/b)', { windows: true })); + expect_truthy(isMatch('b\\c', '!(a/b)', { windows: true })); + }); +}); diff --git a/packages/node-utils/test/picomatch/special-characters.test.ts b/packages/node-utils/test/picomatch/special-characters.test.ts new file mode 100644 index 0000000..72ec081 --- /dev/null +++ b/packages/node-utils/test/picomatch/special-characters.test.ts @@ -0,0 +1,693 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch, makeRe } = picomatch; + +describe('special characters', () => { + describe('numbers', () => { + test('should match numbers in the input string', () => { + expect_truthy(!isMatch('1', '*/*')); + expect_truthy(isMatch('1/1', '*/*')); + expect_truthy(isMatch('1/2', '*/*')); + expect_truthy(!isMatch('1/1/1', '*/*')); + expect_truthy(!isMatch('1/1/2', '*/*')); + + expect_truthy(!isMatch('1', '*/*/1')); + expect_truthy(!isMatch('1/1', '*/*/1')); + expect_truthy(!isMatch('1/2', '*/*/1')); + expect_truthy(isMatch('1/1/1', '*/*/1')); + expect_truthy(!isMatch('1/1/2', '*/*/1')); + + expect_truthy(!isMatch('1', '*/*/2')); + expect_truthy(!isMatch('1/1', '*/*/2')); + expect_truthy(!isMatch('1/2', '*/*/2')); + expect_truthy(!isMatch('1/1/1', '*/*/2')); + expect_truthy(isMatch('1/1/2', '*/*/2')); + }); + }); + + describe('qmarks', () => { + test('should match literal ? in the input string', () => { + expect_truthy(isMatch('?', '*')); + expect_truthy(isMatch('/?', '/*')); + expect_truthy(isMatch('?/?', '*/*')); + expect_truthy(isMatch('?/?/', '*/*/')); + expect_truthy(isMatch('/?', '/?')); + expect_truthy(isMatch('?/?', '?/?')); + expect_truthy(isMatch('foo?/bar?', '*/*')); + }); + + test('should not match slashes with qmarks', () => { + expect_truthy(!isMatch('aaa/bbb', 'aaa?bbb')); + }); + + test('should match literal ? with qmarks', () => { + expect_truthy(!isMatch('?', '??')); + expect_truthy(!isMatch('?', '???')); + expect_truthy(!isMatch('??', '?')); + expect_truthy(!isMatch('??', '???')); + expect_truthy(!isMatch('???', '?')); + expect_truthy(!isMatch('???', '??')); + expect_truthy(!isMatch('ac?', 'ab?')); + expect_truthy(isMatch('?', '?*')); + expect_truthy(isMatch('??', '?*')); + expect_truthy(isMatch('???', '?*')); + expect_truthy(isMatch('????', '?*')); + expect_truthy(isMatch('?', '?')); + expect_truthy(isMatch('??', '??')); + expect_truthy(isMatch('???', '???')); + expect_truthy(isMatch('ab?', 'ab?')); + }); + + test('should match other non-slash characters with qmarks', () => { + expect_truthy(!isMatch('/a/', '?')); + expect_truthy(!isMatch('/a/', '??')); + expect_truthy(!isMatch('/a/', '???')); + expect_truthy(!isMatch('/a/b/', '??')); + expect_truthy(!isMatch('aaa/bbb', 'aaa?bbb')); + expect_truthy(!isMatch('aaa//bbb', 'aaa?bbb')); + expect_truthy(!isMatch('aaa\\\\bbb', 'aaa?bbb')); + expect_truthy(isMatch('acb/', 'a?b/')); + expect_truthy(isMatch('acdb/', 'a??b/')); + expect_truthy(isMatch('/acb', '/a?b')); + }); + + test('should match non-slash characters when ? is escaped', () => { + expect_truthy(!isMatch('acb/', 'a\\?b/')); + expect_truthy(!isMatch('acdb/', 'a\\?\\?b/')); + expect_truthy(!isMatch('/acb', '/a\\?b')); + }); + + test('should match one character per question mark', () => { + expect_truthy(isMatch('a', '?')); + expect_truthy(!isMatch('aa', '?')); + expect_truthy(!isMatch('ab', '?')); + expect_truthy(!isMatch('aaa', '?')); + expect_truthy(!isMatch('abcdefg', '?')); + + expect_truthy(!isMatch('a', '??')); + expect_truthy(isMatch('aa', '??')); + expect_truthy(isMatch('ab', '??')); + expect_truthy(!isMatch('aaa', '??')); + expect_truthy(!isMatch('abcdefg', '??')); + + expect_truthy(!isMatch('a', '???')); + expect_truthy(!isMatch('aa', '???')); + expect_truthy(!isMatch('ab', '???')); + expect_truthy(isMatch('aaa', '???')); + expect_truthy(!isMatch('abcdefg', '???')); + + expect_truthy(!isMatch('aaa', 'a?c')); + expect_truthy(isMatch('aac', 'a?c')); + expect_truthy(isMatch('abc', 'a?c')); + expect_truthy(!isMatch('a', 'ab?')); + expect_truthy(!isMatch('aa', 'ab?')); + expect_truthy(!isMatch('ab', 'ab?')); + expect_truthy(!isMatch('ac', 'ab?')); + expect_truthy(!isMatch('abcd', 'ab?')); + expect_truthy(!isMatch('abbb', 'ab?')); + expect_truthy(isMatch('acb', 'a?b')); + + expect_truthy(!isMatch('a/bb/c/dd/e.md', 'a/?/c/?/e.md')); + expect_truthy(isMatch('a/bb/c/dd/e.md', 'a/??/c/??/e.md')); + expect_truthy(!isMatch('a/bbb/c.md', 'a/??/c.md')); + expect_truthy(isMatch('a/b/c.md', 'a/?/c.md')); + expect_truthy(isMatch('a/b/c/d/e.md', 'a/?/c/?/e.md')); + expect_truthy(!isMatch('a/b/c/d/e.md', 'a/?/c/???/e.md')); + expect_truthy(isMatch('a/b/c/zzz/e.md', 'a/?/c/???/e.md')); + expect_truthy(!isMatch('a/bb/c.md', 'a/?/c.md')); + expect_truthy(isMatch('a/bb/c.md', 'a/??/c.md')); + expect_truthy(isMatch('a/bbb/c.md', 'a/???/c.md')); + expect_truthy(isMatch('a/bbbb/c.md', 'a/????/c.md')); + }); + + test('should enforce one character per qmark even when preceded by stars', () => { + expect_truthy(!isMatch('a', '*??')); + expect_truthy(!isMatch('aa', '*???')); + expect_truthy(isMatch('aaa', '*???')); + expect_truthy(!isMatch('a', '*****??')); + expect_truthy(!isMatch('aa', '*****???')); + expect_truthy(isMatch('aaa', '*****???')); + }); + + test('should support qmarks and stars', () => { + expect_truthy(!isMatch('aaa', 'a*?c')); + expect_truthy(isMatch('aac', 'a*?c')); + expect_truthy(isMatch('abc', 'a*?c')); + + expect_truthy(isMatch('abc', 'a**?c')); + expect_truthy(!isMatch('abb', 'a**?c')); + expect_truthy(isMatch('acc', 'a**?c')); + expect_truthy(isMatch('abc', 'a*****?c')); + + expect_truthy(isMatch('a', '*****?')); + expect_truthy(isMatch('aa', '*****?')); + expect_truthy(isMatch('abc', '*****?')); + expect_truthy(isMatch('zzz', '*****?')); + expect_truthy(isMatch('bbb', '*****?')); + expect_truthy(isMatch('aaaa', '*****?')); + + expect_truthy(!isMatch('a', '*****??')); + expect_truthy(isMatch('aa', '*****??')); + expect_truthy(isMatch('abc', '*****??')); + expect_truthy(isMatch('zzz', '*****??')); + expect_truthy(isMatch('bbb', '*****??')); + expect_truthy(isMatch('aaaa', '*****??')); + + expect_truthy(!isMatch('a', '?*****??')); + expect_truthy(!isMatch('aa', '?*****??')); + expect_truthy(isMatch('abc', '?*****??')); + expect_truthy(isMatch('zzz', '?*****??')); + expect_truthy(isMatch('bbb', '?*****??')); + expect_truthy(isMatch('aaaa', '?*****??')); + + expect_truthy(isMatch('abc', '?*****?c')); + expect_truthy(!isMatch('abb', '?*****?c')); + expect_truthy(!isMatch('zzz', '?*****?c')); + + expect_truthy(isMatch('abc', '?***?****c')); + expect_truthy(!isMatch('bbb', '?***?****c')); + expect_truthy(!isMatch('zzz', '?***?****c')); + + expect_truthy(isMatch('abc', '?***?****?')); + expect_truthy(isMatch('bbb', '?***?****?')); + expect_truthy(isMatch('zzz', '?***?****?')); + + expect_truthy(isMatch('abc', '?***?****')); + expect_truthy(isMatch('abc', '*******c')); + expect_truthy(isMatch('abc', '*******?')); + expect_truthy(isMatch('abcdecdhjk', 'a*cd**?**??k')); + expect_truthy(isMatch('abcdecdhjk', 'a**?**cd**?**??k')); + expect_truthy(isMatch('abcdecdhjk', 'a**?**cd**?**??k***')); + expect_truthy(isMatch('abcdecdhjk', 'a**?**cd**?**??***k')); + expect_truthy(isMatch('abcdecdhjk', 'a**?**cd**?**??***k**')); + expect_truthy(isMatch('abcdecdhjk', 'a****c**?**??*****')); + }); + + test('should support qmarks, stars and slashes', () => { + expect_truthy(!isMatch('a/b/c/d/e.md', 'a/?/c/?/*/e.md')); + expect_truthy(isMatch('a/b/c/d/e/e.md', 'a/?/c/?/*/e.md')); + expect_truthy(isMatch('a/b/c/d/efghijk/e.md', 'a/?/c/?/*/e.md')); + expect_truthy(isMatch('a/b/c/d/efghijk/e.md', 'a/?/**/e.md')); + expect_truthy(!isMatch('a/bb/e.md', 'a/?/e.md')); + expect_truthy(isMatch('a/bb/e.md', 'a/??/e.md')); + expect_truthy(!isMatch('a/bb/e.md', 'a/?/**/e.md')); + expect_truthy(isMatch('a/b/ccc/e.md', 'a/?/**/e.md')); + expect_truthy(isMatch('a/b/c/d/efghijk/e.md', 'a/*/?/**/e.md')); + expect_truthy(isMatch('a/b/c/d/efgh.ijk/e.md', 'a/*/?/**/e.md')); + expect_truthy(isMatch('a/b.bb/c/d/efgh.ijk/e.md', 'a/*/?/**/e.md')); + expect_truthy(isMatch('a/bbb/c/d/efgh.ijk/e.md', 'a/*/?/**/e.md')); + }); + + test('should match non-leading dots', () => { + expect_truthy(isMatch('aaa.bbb', 'aaa?bbb')); + }); + + test('should not match leading dots', () => { + expect_truthy(!isMatch('.aaa/bbb', '?aaa/bbb')); + expect_truthy(!isMatch('aaa/.bbb', 'aaa/?bbb')); + }); + + test('should match characters preceding a dot', () => { + expect_truthy(isMatch('a/bbb/abcd.md', 'a/*/ab??.md')); + expect_truthy(isMatch('a/bbb/abcd.md', 'a/bbb/ab??.md')); + expect_truthy(isMatch('a/bbb/abcd.md', 'a/bbb/ab???md')); + }); + }); + + describe('parentheses ()', () => { + test('should match literal parentheses in the input string', () => { + expect_truthy(!isMatch('my/folder (Work, Accts)', '/*')); + expect_truthy(isMatch('my/folder (Work, Accts)', '*/*')); + expect_truthy(isMatch('my/folder (Work, Accts)', '*/*,*')); + expect_truthy(isMatch('my/folder (Work, Accts)', '*/*(W*, *)*')); + expect_truthy(isMatch('my/folder/(Work, Accts)', '**/*(W*, *)*')); + expect_truthy(!isMatch('my/folder/(Work, Accts)', '*/*(W*, *)*')); + expect_truthy(isMatch('foo(bar)baz', 'foo*baz')); + }); + + test('should match literal parens with brackets', async () => { + expect_truthy(isMatch('foo(bar)baz', 'foo[bar()]+baz')); + }); + + test('should throw an error on imbalanced, unescaped parens', () => { + const opts = { strictBrackets: true }; + expect_throws(() => makeRe('*)', opts), /Missing opening: "\("/); + expect_throws(() => makeRe('*(', opts), /Missing closing: "\)"/); + }); + + test('should throw an error on imbalanced, unescaped brackets', () => { + const opts = { strictBrackets: true }; + expect_throws(() => makeRe('*]', opts), /Missing opening: "\["/); + expect_throws(() => makeRe('*[', opts), /Missing closing: "\]"/); + }); + }); + + describe('path characters', () => { + test('should match windows drives with globstars', () => { + expect_truthy(isMatch('bar/', '**')); + expect_truthy(isMatch('A://', '**')); + expect_truthy(isMatch('B:foo/a/b/c/d', '**')); + expect_truthy(isMatch('C:/Users/', '**')); + expect_truthy(isMatch('c:\\', '**')); + expect_truthy(isMatch('C:\\Users\\', '**')); + expect_truthy(isMatch('C:cwd/another', '**')); + expect_truthy(isMatch('C:cwd\\another', '**')); + }); + + test('should not match multiple windows directories with a single star', () => { + expect_truthy(isMatch('c:\\', '*{,/}', { windows: true })); + expect_truthy(!isMatch('C:\\Users\\', '*', { windows: true })); + expect_truthy(!isMatch('C:cwd\\another', '*', { windows: true })); + }); + + test('should match mixed slashes on windows', () => { + expect_truthy(isMatch('//C://user\\docs\\Letter.txt', '**', { windows: true })); + expect_truthy(isMatch('//C:\\\\user/docs/Letter.txt', '**', { windows: true })); + expect_truthy(isMatch(':\\', '*{,/}', { windows: true })); + expect_truthy(isMatch(':\\', ':*{,/}', { windows: true })); + expect_truthy(isMatch('\\\\foo/bar', '**', { windows: true })); + expect_truthy(isMatch('\\\\foo/bar', '//*/*', { windows: true })); + expect_truthy(isMatch('\\\\unc\\admin$', '**', { windows: true })); + expect_truthy(isMatch('\\\\unc\\admin$', '//*/*$', { windows: true })); + expect_truthy(isMatch('\\\\unc\\admin$\\system32', '//*/*$/*32', { windows: true })); + expect_truthy(isMatch('\\\\unc\\share\\foo', '//u*/s*/f*', { windows: true })); + expect_truthy(isMatch('foo\\bar\\baz', 'f*/*/*', { windows: true })); + }); + + test('should match mixed slashes when options.windows is true', () => { + expect_truthy(isMatch('//C://user\\docs\\Letter.txt', '**', { windows: true })); + expect_truthy(isMatch('//C:\\\\user/docs/Letter.txt', '**', { windows: true })); + expect_truthy(isMatch(':\\', '*{,/}', { windows: true })); + expect_truthy(isMatch(':\\', ':*{,/}', { windows: true })); + expect_truthy(isMatch('\\\\foo/bar', '**', { windows: true })); + expect_truthy(isMatch('\\\\foo/bar', '//*/*', { windows: true })); + expect_truthy(isMatch('\\\\unc\\admin$', '//**', { windows: true })); + expect_truthy(isMatch('\\\\unc\\admin$', '//*/*$', { windows: true })); + expect_truthy(isMatch('\\\\unc\\admin$\\system32', '//*/*$/*32', { windows: true })); + expect_truthy(isMatch('\\\\unc\\share\\foo', '//u*/s*/f*', { windows: true })); + expect_truthy(isMatch('\\\\\\\\\\\\unc\\share\\foo', '/\\{1,\\}u*/s*/f*', { windows: true, unescape: true })); + expect_truthy(isMatch('foo\\bar\\baz', 'f*/*/*', { windows: true })); + expect_truthy(isMatch('//*:/**', '**')); + expect_truthy(!isMatch('//server/file', '//*')); + expect_truthy(isMatch('//server/file', '/**')); + expect_truthy(isMatch('//server/file', '//**')); + expect_truthy(isMatch('//server/file', '**')); + expect_truthy(isMatch('//UNC//Server01//user//docs//Letter.txt', '**')); + expect_truthy(isMatch('/foo', '**')); + expect_truthy(isMatch('/foo/a/b/c/d', '**')); + expect_truthy(isMatch('/foo/bar', '**')); + expect_truthy(isMatch('/home/foo', '**')); + expect_truthy(isMatch('/home/foo/..', '**/..')); + expect_truthy(isMatch('/user/docs/Letter.txt', '**')); + expect_truthy(isMatch('directory\\directory', '**')); + expect_truthy(isMatch('a/b/c.js', '**')); + expect_truthy(isMatch('directory/directory', '**')); + expect_truthy(isMatch('foo/bar', '**')); + }); + + test('should match any character zero or more times, except for /', () => { + expect_truthy(!isMatch('foo', '*a*')); + expect_truthy(!isMatch('foo', '*r')); + expect_truthy(!isMatch('foo', 'b*')); + expect_truthy(!isMatch('foo/bar', '*')); + expect_truthy(isMatch('foo/bar', '*/*')); + expect_truthy(!isMatch('foo/bar/baz', '*/*')); + expect_truthy(isMatch('bar', '*a*')); + expect_truthy(isMatch('bar', '*r')); + expect_truthy(isMatch('bar', 'b*')); + expect_truthy(isMatch('foo/bar/baz', '*/*/*')); + }); + + test('should match dashes surrounded by spaces', () => { + expect_truthy(isMatch('my/folder - 1', '*/*')); + expect_truthy(isMatch('my/folder - copy (1)', '*/*')); + expect_truthy(isMatch('my/folder - copy [1]', '*/*')); + expect_truthy(isMatch('my/folder - foo + bar - copy [1]', '*/*')); + expect_truthy(!isMatch('my/folder - foo + bar - copy [1]', '*')); + + expect_truthy(isMatch('my/folder - 1', '*/*-*')); + expect_truthy(isMatch('my/folder - copy (1)', '*/*-*')); + expect_truthy(isMatch('my/folder - copy [1]', '*/*-*')); + expect_truthy(isMatch('my/folder - foo + bar - copy [1]', '*/*-*')); + + expect_truthy(isMatch('my/folder - 1', '*/*1')); + expect_truthy(!isMatch('my/folder - copy (1)', '*/*1')); + }); + }); + + describe('brackets', () => { + test('should support square brackets in globs', () => { + expect_truthy(isMatch('foo/bar - 1', '**/*[1]')); + expect_truthy(!isMatch('foo/bar - copy (1)', '**/*[1]')); + expect_truthy(!isMatch('foo/bar (1)', '**/*[1]')); + expect_truthy(!isMatch('foo/bar (4)', '**/*[1]')); + expect_truthy(!isMatch('foo/bar (7)', '**/*[1]')); + expect_truthy(!isMatch('foo/bar (42)', '**/*[1]')); + expect_truthy(isMatch('foo/bar - copy [1]', '**/*[1]')); + expect_truthy(isMatch('foo/bar - foo + bar - copy [1]', '**/*[1]')); + }); + + test('should match (escaped) bracket literals', () => { + expect_truthy(isMatch('a [b]', 'a \\[b\\]')); + expect_truthy(isMatch('a [b] c', 'a [b] c')); + expect_truthy(isMatch('a [b]', 'a \\[b\\]*')); + expect_truthy(isMatch('a [bc]', 'a \\[bc\\]*')); + expect_truthy(!isMatch('a [b]', 'a \\[b\\].*')); + expect_truthy(isMatch('a [b].js', 'a \\[b\\].*')); + expect_truthy(!isMatch('foo/bar - 1', '**/*\\[*\\]')); + expect_truthy(!isMatch('foo/bar - copy (1)', '**/*\\[*\\]')); + expect_truthy(!isMatch('foo/bar (1)', '**/*\\[*\\]')); + expect_truthy(!isMatch('foo/bar (4)', '**/*\\[*\\]')); + expect_truthy(!isMatch('foo/bar (7)', '**/*\\[*\\]')); + expect_truthy(!isMatch('foo/bar (42)', '**/*\\[*\\]')); + expect_truthy(isMatch('foo/bar - copy [1]', '**/*\\[*\\]')); + expect_truthy(isMatch('foo/bar - foo + bar - copy [1]', '**/*\\[*\\]')); + + expect_truthy(!isMatch('foo/bar - 1', '**/*\\[1\\]')); + expect_truthy(!isMatch('foo/bar - copy (1)', '**/*\\[1\\]')); + expect_truthy(!isMatch('foo/bar (1)', '**/*\\[1\\]')); + expect_truthy(!isMatch('foo/bar (4)', '**/*\\[1\\]')); + expect_truthy(!isMatch('foo/bar (7)', '**/*\\[1\\]')); + expect_truthy(!isMatch('foo/bar (42)', '**/*\\[1\\]')); + expect_truthy(isMatch('foo/bar - copy [1]', '**/*\\[1\\]')); + expect_truthy(isMatch('foo/bar - foo + bar - copy [1]', '**/*\\[1\\]')); + + expect_truthy(!isMatch('foo/bar - 1', '*/*\\[*\\]')); + expect_truthy(!isMatch('foo/bar - copy (1)', '*/*\\[*\\]')); + expect_truthy(!isMatch('foo/bar (1)', '*/*\\[*\\]')); + expect_truthy(!isMatch('foo/bar (4)', '*/*\\[*\\]')); + expect_truthy(!isMatch('foo/bar (7)', '*/*\\[*\\]')); + expect_truthy(!isMatch('foo/bar (42)', '*/*\\[*\\]')); + expect_truthy(isMatch('foo/bar - copy [1]', '*/*\\[*\\]')); + expect_truthy(isMatch('foo/bar - foo + bar - copy [1]', '*/*\\[*\\]')); + + expect_truthy(isMatch('a [b]', 'a \\[b\\]')); + expect_truthy(isMatch('a [b] c', 'a [b] c')); + expect_truthy(isMatch('a [b]', 'a \\[b\\]*')); + expect_truthy(isMatch('a [bc]', 'a \\[bc\\]*')); + expect_truthy(!isMatch('a [b]', 'a \\[b\\].*')); + expect_truthy(isMatch('a [b].js', 'a \\[b\\].*')); + }); + }); + + describe('star - "*"', () => { + test('should match literal *', () => { + expect_truthy(isMatch('*', '*')); + expect_truthy(isMatch('*/*', '*/*')); + expect_truthy(isMatch('*/*', '?/?')); + expect_truthy(isMatch('*/*/', '*/*/')); + expect_truthy(isMatch('/*', '/*')); + expect_truthy(isMatch('/*', '/?')); + expect_truthy(isMatch('foo*/bar*', '*/*')); + }); + + test('should support stars following brackets', () => { + expect_truthy(isMatch('a', '[a]*')); + expect_truthy(isMatch('aa', '[a]*')); + expect_truthy(isMatch('aaa', '[a]*')); + expect_truthy(isMatch('az', '[a-z]*')); + expect_truthy(isMatch('zzz', '[a-z]*')); + }); + + test('should support stars following parens', () => { + expect_truthy(isMatch('a', '(a)*')); + expect_truthy(isMatch('ab', '(a|b)*')); + expect_truthy(isMatch('aa', '(a)*')); + expect_truthy(isMatch('aaab', '(a|b)*')); + expect_truthy(isMatch('aaabbb', '(a|b)*')); + }); + + test('should not match slashes with single stars', () => { + expect_truthy(!isMatch('a/b', '(a)*')); + expect_truthy(!isMatch('a/b', '[a]*')); + expect_truthy(!isMatch('a/b', 'a*')); + expect_truthy(!isMatch('a/b', '(a|b)*')); + }); + + test('should not match dots with stars by default', () => { + expect_truthy(!isMatch('.a', '(a)*')); + expect_truthy(!isMatch('.a', '*[a]*')); + expect_truthy(!isMatch('.a', '*[a]')); + expect_truthy(!isMatch('.a', '*a*')); + expect_truthy(!isMatch('.a', '*a')); + expect_truthy(!isMatch('.a', '*(a|b)')); + }); + }); + + describe('plus - "+"', () => { + test('should match literal +', () => { + expect_truthy(isMatch('+', '*')); + expect_truthy(isMatch('/+', '/*')); + expect_truthy(isMatch('+/+', '*/*')); + expect_truthy(isMatch('+/+/', '*/*/')); + expect_truthy(isMatch('/+', '/+')); + expect_truthy(isMatch('/+', '/?')); + expect_truthy(isMatch('+/+', '?/?')); + expect_truthy(isMatch('+/+', '+/+')); + expect_truthy(isMatch('foo+/bar+', '*/*')); + }); + + test('should support plus signs that follow brackets (and not escape them)', () => { + expect_truthy(isMatch('a', '[a]+')); + expect_truthy(isMatch('aa', '[a]+')); + expect_truthy(isMatch('aaa', '[a]+')); + expect_truthy(isMatch('az', '[a-z]+')); + expect_truthy(isMatch('zzz', '[a-z]+')); + }); + + test('should not escape plus signs that follow parens', () => { + expect_truthy(isMatch('a', '(a)+')); + expect_truthy(isMatch('ab', '(a|b)+')); + expect_truthy(isMatch('aa', '(a)+')); + expect_truthy(isMatch('aaab', '(a|b)+')); + expect_truthy(isMatch('aaabbb', '(a|b)+')); + }); + + test('should escape plus signs to match string literals', () => { + expect_truthy(isMatch('a+b/src/glimini.js', 'a+b/src/*.js')); + expect_truthy(isMatch('+b/src/glimini.js', '+b/src/*.js')); + expect_truthy(isMatch('coffee+/src/glimini.js', 'coffee+/src/*.js')); + expect_truthy(isMatch('coffee+/src/glimini.js', 'coffee+/src/*.js')); + expect_truthy(isMatch('coffee+/src/glimini.js', 'coffee+/src/*')); + }); + + test('should not escape + following brackets', () => { + expect_truthy(isMatch('a', '[a]+')); + expect_truthy(isMatch('aa', '[a]+')); + expect_truthy(isMatch('aaa', '[a]+')); + expect_truthy(isMatch('az', '[a-z]+')); + expect_truthy(isMatch('zzz', '[a-z]+')); + }); + + test('should not escape + following parens', () => { + expect_truthy(isMatch('a', '(a)+')); + expect_truthy(isMatch('ab', '(a|b)+')); + expect_truthy(isMatch('aa', '(a)+')); + expect_truthy(isMatch('aaab', '(a|b)+')); + expect_truthy(isMatch('aaabbb', '(a|b)+')); + }); + }); + + describe('dollar $', () => { + test('should match dollar signs', () => { + expect_truthy(!isMatch('$', '!($)')); + expect_truthy(!isMatch('$', '!$')); + expect_truthy(isMatch('$$', '!$')); + expect_truthy(isMatch('$$', '!($)')); + expect_truthy(isMatch('$$$', '!($)')); + expect_truthy(isMatch('^', '!($)')); + + expect_truthy(isMatch('$', '!($$)')); + expect_truthy(!isMatch('$$', '!($$)')); + expect_truthy(isMatch('$$$', '!($$)')); + expect_truthy(isMatch('^', '!($$)')); + + expect_truthy(!isMatch('$', '!($*)')); + expect_truthy(!isMatch('$$', '!($*)')); + expect_truthy(!isMatch('$$$', '!($*)')); + expect_truthy(isMatch('^', '!($*)')); + + expect_truthy(isMatch('$', '*')); + expect_truthy(isMatch('$$', '*')); + expect_truthy(isMatch('$$$', '*')); + expect_truthy(isMatch('^', '*')); + + expect_truthy(isMatch('$', '$*')); + expect_truthy(isMatch('$$', '$*')); + expect_truthy(isMatch('$$$', '$*')); + expect_truthy(!isMatch('^', '$*')); + + expect_truthy(isMatch('$', '*$*')); + expect_truthy(isMatch('$$', '*$*')); + expect_truthy(isMatch('$$$', '*$*')); + expect_truthy(!isMatch('^', '*$*')); + + expect_truthy(isMatch('$', '*$')); + expect_truthy(isMatch('$$', '*$')); + expect_truthy(isMatch('$$$', '*$')); + expect_truthy(!isMatch('^', '*$')); + + expect_truthy(!isMatch('$', '?$')); + expect_truthy(isMatch('$$', '?$')); + expect_truthy(!isMatch('$$$', '?$')); + expect_truthy(!isMatch('^', '?$')); + }); + }); + + describe('caret ^', () => { + test('should match carets', () => { + expect_truthy(isMatch('^', '^')); + expect_truthy(isMatch('^/foo', '^/*')); + expect_truthy(isMatch('^/foo', '^/*')); + expect_truthy(isMatch('foo^', '*^')); + expect_truthy(isMatch('^foo/foo', '^foo/*')); + expect_truthy(isMatch('foo^/foo', 'foo^/*')); + + expect_truthy(!isMatch('^', '!(^)')); + expect_truthy(isMatch('^^', '!(^)')); + expect_truthy(isMatch('^^^', '!(^)')); + expect_truthy(isMatch('&', '!(^)')); + + expect_truthy(isMatch('^', '!(^^)')); + expect_truthy(!isMatch('^^', '!(^^)')); + expect_truthy(isMatch('^^^', '!(^^)')); + expect_truthy(isMatch('&', '!(^^)')); + + expect_truthy(!isMatch('^', '!(^*)')); + expect_truthy(!isMatch('^^', '!(^*)')); + expect_truthy(!isMatch('^^^', '!(^*)')); + expect_truthy(isMatch('&', '!(^*)')); + + expect_truthy(isMatch('^', '*')); + expect_truthy(isMatch('^^', '*')); + expect_truthy(isMatch('^^^', '*')); + expect_truthy(isMatch('&', '*')); + + expect_truthy(isMatch('^', '^*')); + expect_truthy(isMatch('^^', '^*')); + expect_truthy(isMatch('^^^', '^*')); + expect_truthy(!isMatch('&', '^*')); + + expect_truthy(isMatch('^', '*^*')); + expect_truthy(isMatch('^^', '*^*')); + expect_truthy(isMatch('^^^', '*^*')); + expect_truthy(!isMatch('&', '*^*')); + + expect_truthy(isMatch('^', '*^')); + expect_truthy(isMatch('^^', '*^')); + expect_truthy(isMatch('^^^', '*^')); + expect_truthy(!isMatch('&', '*^')); + + expect_truthy(!isMatch('^', '?^')); + expect_truthy(isMatch('^^', '?^')); + expect_truthy(!isMatch('^^^', '?^')); + expect_truthy(!isMatch('&', '?^')); + }); + }); + + describe('mixed special characters', () => { + test('should match special characters in paths', () => { + expect_truthy(isMatch('my/folder +1', '*/*')); + expect_truthy(isMatch('my/folder -1', '*/*')); + expect_truthy(isMatch('my/folder *1', '*/*')); + expect_truthy(isMatch('my/folder', '*/*')); + expect_truthy(isMatch('my/folder+foo+bar&baz', '*/*')); + expect_truthy(isMatch('my/folder - $1.00', '*/*')); + expect_truthy(isMatch('my/folder - ^1.00', '*/*')); + expect_truthy(isMatch('my/folder - %1.00', '*/*')); + + expect_truthy(isMatch('my/folder +1', '*/!(*%)*')); + expect_truthy(isMatch('my/folder -1', '*/!(*%)*')); + expect_truthy(isMatch('my/folder *1', '*/!(*%)*')); + expect_truthy(isMatch('my/folder', '*/!(*%)*')); + expect_truthy(isMatch('my/folder+foo+bar&baz', '*/!(*%)*')); + expect_truthy(isMatch('my/folder - $1.00', '*/!(*%)*')); + expect_truthy(isMatch('my/folder - ^1.00', '*/!(*%)*')); + expect_truthy(!isMatch('my/folder - %1.00', '*/!(*%)*')); + + expect_truthy(!isMatch('my/folder +1', '*/*$*')); + expect_truthy(!isMatch('my/folder -1', '*/*$*')); + expect_truthy(!isMatch('my/folder *1', '*/*$*')); + expect_truthy(!isMatch('my/folder', '*/*$*')); + expect_truthy(!isMatch('my/folder+foo+bar&baz', '*/*$*')); + expect_truthy(isMatch('my/folder - $1.00', '*/*$*')); + expect_truthy(!isMatch('my/folder - ^1.00', '*/*$*')); + expect_truthy(!isMatch('my/folder - %1.00', '*/*$*')); + + expect_truthy(!isMatch('my/folder +1', '*/*^*')); + expect_truthy(!isMatch('my/folder -1', '*/*^*')); + expect_truthy(!isMatch('my/folder *1', '*/*^*')); + expect_truthy(!isMatch('my/folder', '*/*^*')); + expect_truthy(!isMatch('my/folder+foo+bar&baz', '*/*^*')); + expect_truthy(!isMatch('my/folder - $1.00', '*/*^*')); + expect_truthy(isMatch('my/folder - ^1.00', '*/*^*')); + expect_truthy(!isMatch('my/folder - %1.00', '*/*^*')); + + expect_truthy(!isMatch('my/folder +1', '*/*&*')); + expect_truthy(!isMatch('my/folder -1', '*/*&*')); + expect_truthy(!isMatch('my/folder *1', '*/*&*')); + expect_truthy(!isMatch('my/folder', '*/*&*')); + expect_truthy(isMatch('my/folder+foo+bar&baz', '*/*&*')); + expect_truthy(!isMatch('my/folder - $1.00', '*/*&*')); + expect_truthy(!isMatch('my/folder - ^1.00', '*/*&*')); + expect_truthy(!isMatch('my/folder - %1.00', '*/*&*')); + + expect_truthy(isMatch('my/folder +1', '*/*+*')); + expect_truthy(!isMatch('my/folder -1', '*/*+*')); + expect_truthy(!isMatch('my/folder *1', '*/*+*')); + expect_truthy(!isMatch('my/folder', '*/*+*')); + expect_truthy(isMatch('my/folder+foo+bar&baz', '*/*+*')); + expect_truthy(!isMatch('my/folder - $1.00', '*/*+*')); + expect_truthy(!isMatch('my/folder - ^1.00', '*/*+*')); + expect_truthy(!isMatch('my/folder - %1.00', '*/*+*')); + + expect_truthy(!isMatch('my/folder +1', '*/*-*')); + expect_truthy(isMatch('my/folder -1', '*/*-*')); + expect_truthy(!isMatch('my/folder *1', '*/*-*')); + expect_truthy(!isMatch('my/folder', '*/*-*')); + expect_truthy(!isMatch('my/folder+foo+bar&baz', '*/*-*')); + expect_truthy(isMatch('my/folder - $1.00', '*/*-*')); + expect_truthy(isMatch('my/folder - ^1.00', '*/*-*')); + expect_truthy(isMatch('my/folder - %1.00', '*/*-*')); + + expect_truthy(!isMatch('my/folder +1', '*/*\\**')); + expect_truthy(!isMatch('my/folder -1', '*/*\\**')); + expect_truthy(isMatch('my/folder *1', '*/*\\**')); + expect_truthy(!isMatch('my/folder', '*/*\\**')); + expect_truthy(!isMatch('my/folder+foo+bar&baz', '*/*\\**')); + expect_truthy(!isMatch('my/folder - $1.00', '*/*\\**')); + expect_truthy(!isMatch('my/folder - ^1.00', '*/*\\**')); + expect_truthy(!isMatch('my/folder - %1.00', '*/*\\**')); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/stars.test.ts b/packages/node-utils/test/picomatch/stars.test.ts new file mode 100644 index 0000000..f9499e8 --- /dev/null +++ b/packages/node-utils/test/picomatch/stars.test.ts @@ -0,0 +1,398 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('stars', () => { + describe('issue related', () => { + test('should respect dots defined in glob pattern (micromatch/#23)', () => { + expect_truthy(isMatch('z.js', 'z*')); + expect_truthy(!isMatch('zzjs', 'z*.js')); + expect_truthy(!isMatch('zzjs', '*z.js')); + }); + }); + + describe('single stars', () => { + test('should match anything except slashes and leading dots', () => { + expect_truthy(!isMatch('a/b/c/z.js', '*.js')); + expect_truthy(!isMatch('a/b/z.js', '*.js')); + expect_truthy(!isMatch('a/z.js', '*.js')); + expect_truthy(isMatch('z.js', '*.js')); + + expect_truthy(!isMatch('a/.ab', '*/*')); + expect_truthy(!isMatch('.ab', '*')); + + expect_truthy(isMatch('z.js', 'z*.js')); + expect_truthy(isMatch('a/z', '*/*')); + expect_truthy(isMatch('a/z.js', '*/z*.js')); + expect_truthy(isMatch('a/z.js', 'a/z*.js')); + + expect_truthy(isMatch('ab', '*')); + expect_truthy(isMatch('abc', '*')); + + expect_truthy(!isMatch('bar', 'f*')); + expect_truthy(!isMatch('foo', '*r')); + expect_truthy(!isMatch('foo', 'b*')); + expect_truthy(!isMatch('foo/bar', '*')); + expect_truthy(isMatch('abc', '*c')); + expect_truthy(isMatch('abc', 'a*')); + expect_truthy(isMatch('abc', 'a*c')); + expect_truthy(isMatch('bar', '*r')); + expect_truthy(isMatch('bar', 'b*')); + expect_truthy(isMatch('foo', 'f*')); + }); + + test('should match spaces', () => { + expect_truthy(isMatch('one abc two', '*abc*')); + expect_truthy(isMatch('a b', 'a*b')); + }); + + test('should support multiple non-consecutive stars in a path segment', () => { + expect_truthy(!isMatch('foo', '*a*')); + expect_truthy(isMatch('bar', '*a*')); + expect_truthy(isMatch('oneabctwo', '*abc*')); + expect_truthy(!isMatch('a-b.c-d', '*-bc-*')); + expect_truthy(isMatch('a-b.c-d', '*-*.*-*')); + expect_truthy(isMatch('a-b.c-d', '*-b*c-*')); + expect_truthy(isMatch('a-b.c-d', '*-b.c-*')); + expect_truthy(isMatch('a-b.c-d', '*.*')); + expect_truthy(isMatch('a-b.c-d', '*.*-*')); + expect_truthy(isMatch('a-b.c-d', '*.*-d')); + expect_truthy(isMatch('a-b.c-d', '*.c-*')); + expect_truthy(isMatch('a-b.c-d', '*b.*d')); + expect_truthy(isMatch('a-b.c-d', 'a*.c*')); + expect_truthy(isMatch('a-b.c-d', 'a-*.*-d')); + expect_truthy(isMatch('a.b', '*.*')); + expect_truthy(isMatch('a.b', '*.b')); + expect_truthy(isMatch('a.b', 'a.*')); + expect_truthy(isMatch('a.b', 'a.b')); + }); + + test('should support multiple stars in a segment', () => { + expect_truthy(!isMatch('a-b.c-d', '**-bc-**')); + expect_truthy(isMatch('a-b.c-d', '**-**.**-**')); + expect_truthy(isMatch('a-b.c-d', '**-b**c-**')); + expect_truthy(isMatch('a-b.c-d', '**-b.c-**')); + expect_truthy(isMatch('a-b.c-d', '**.**')); + expect_truthy(isMatch('a-b.c-d', '**.**-**')); + expect_truthy(isMatch('a-b.c-d', '**.**-d')); + expect_truthy(isMatch('a-b.c-d', '**.c-**')); + expect_truthy(isMatch('a-b.c-d', '**b.**d')); + expect_truthy(isMatch('a-b.c-d', 'a**.c**')); + expect_truthy(isMatch('a-b.c-d', 'a-**.**-d')); + expect_truthy(isMatch('a.b', '**.**')); + expect_truthy(isMatch('a.b', '**.b')); + expect_truthy(isMatch('a.b', 'a.**')); + expect_truthy(isMatch('a.b', 'a.b')); + }); + + test('should return true when one of the given patterns matches the string', () => { + expect_truthy(isMatch('/ab', '*/*')); + expect_truthy(isMatch('.', '.')); + expect_truthy(!isMatch('a/.b', 'a/')); + expect_truthy(isMatch('/ab', '/*')); + expect_truthy(isMatch('/ab', '/??')); + expect_truthy(isMatch('/ab', '/?b')); + expect_truthy(isMatch('/cd', '/*')); + expect_truthy(isMatch('a', 'a')); + expect_truthy(isMatch('a/.b', 'a/.*')); + expect_truthy(isMatch('a/b', '?/?')); + expect_truthy(isMatch('a/b/c/d/e/j/n/p/o/z/c.md', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('a/b/c/d/e/z/c.md', 'a/**/z/*.md')); + expect_truthy(isMatch('a/b/c/xyz.md', 'a/b/c/*.md')); + expect_truthy(isMatch('a/b/c/xyz.md', 'a/b/c/*.md')); + expect_truthy(isMatch('a/b/z/.a', 'a/*/z/.a')); + expect_truthy(!isMatch('a/b/z/.a', 'bz')); + expect_truthy(isMatch('a/bb.bb/aa/b.b/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(isMatch('a/bb.bb/aa/bb/aa/c/xyz.md', 'a/**/c/*.md')); + expect_truthy(isMatch('a/bb.bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('a/bbbb/c/xyz.md', 'a/*/c/*.md')); + expect_truthy(isMatch('aaa', '*')); + expect_truthy(isMatch('ab', '*')); + expect_truthy(isMatch('ab', 'ab')); + }); + + test('should return false when the path does not match the pattern', () => { + expect_truthy(!isMatch('/ab', ['*/'])); + expect_truthy(!isMatch('/ab', ['*/a'])); + expect_truthy(!isMatch('/ab', ['/'])); + expect_truthy(!isMatch('/ab', ['/?'])); + expect_truthy(!isMatch('/ab', ['/a'])); + expect_truthy(!isMatch('/ab', ['?/?'])); + expect_truthy(!isMatch('/ab', ['a/*'])); + expect_truthy(!isMatch('a/.b', ['a/'])); + expect_truthy(!isMatch('a/b/c', ['a/*'])); + expect_truthy(!isMatch('a/b/c', ['a/b'])); + expect_truthy(!isMatch('a/b/c/d/e/z/c.md', ['b/c/d/e'])); + expect_truthy(!isMatch('a/b/z/.a', ['b/z'])); + expect_truthy(!isMatch('ab', ['*/*'])); + expect_truthy(!isMatch('ab', ['/a'])); + expect_truthy(!isMatch('ab', ['a'])); + expect_truthy(!isMatch('ab', ['b'])); + expect_truthy(!isMatch('ab', ['c'])); + expect_truthy(!isMatch('abcd', ['ab'])); + expect_truthy(!isMatch('abcd', ['bc'])); + expect_truthy(!isMatch('abcd', ['c'])); + expect_truthy(!isMatch('abcd', ['cd'])); + expect_truthy(!isMatch('abcd', ['d'])); + expect_truthy(!isMatch('abcd', ['f'])); + expect_truthy(!isMatch('ef', ['/*'])); + }); + + test('should match a path segment for each single star', () => { + expect_truthy(!isMatch('aaa', '*/*/*')); + expect_truthy(!isMatch('aaa/bb/aa/rr', '*/*/*')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa*')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa**')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/*')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/*ccc')); + expect_truthy(!isMatch('aaa/bba/ccc', 'aaa/*z')); + expect_truthy(!isMatch('aaa/bbb', '*/*/*')); + expect_truthy(!isMatch('ab/zzz/ejkl/hi', '*/*jk*/*i')); + expect_truthy(isMatch('aaa/bba/ccc', '*/*/*')); + expect_truthy(isMatch('aaa/bba/ccc', 'aaa/**')); + expect_truthy(isMatch('aaa/bbb', 'aaa/*')); + expect_truthy(isMatch('ab/zzz/ejkl/hi', '*/*z*/*/*i')); + expect_truthy(isMatch('abzzzejklhi', '*j*i')); + }); + + test('should support single globs (*)', () => { + expect_truthy(isMatch('a', '*')); + expect_truthy(isMatch('b', '*')); + expect_truthy(!isMatch('a/a', '*')); + expect_truthy(!isMatch('a/a/a', '*')); + expect_truthy(!isMatch('a/a/b', '*')); + expect_truthy(!isMatch('a/a/a/a', '*')); + expect_truthy(!isMatch('a/a/a/a/a', '*')); + + expect_truthy(!isMatch('a', '*/*')); + expect_truthy(isMatch('a/a', '*/*')); + expect_truthy(!isMatch('a/a/a', '*/*')); + + expect_truthy(!isMatch('a', '*/*/*')); + expect_truthy(!isMatch('a/a', '*/*/*')); + expect_truthy(isMatch('a/a/a', '*/*/*')); + expect_truthy(!isMatch('a/a/a/a', '*/*/*')); + + expect_truthy(!isMatch('a', '*/*/*/*')); + expect_truthy(!isMatch('a/a', '*/*/*/*')); + expect_truthy(!isMatch('a/a/a', '*/*/*/*')); + expect_truthy(isMatch('a/a/a/a', '*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', '*/*/*/*')); + + expect_truthy(!isMatch('a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/b', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a', '*/*/*/*/*')); + expect_truthy(isMatch('a/a/a/a/a', '*/*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a/a', '*/*/*/*/*')); + + expect_truthy(!isMatch('a', 'a/*')); + expect_truthy(isMatch('a/a', 'a/*')); + expect_truthy(!isMatch('a/a/a', 'a/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*')); + + expect_truthy(!isMatch('a', 'a/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/*')); + expect_truthy(isMatch('a/a/a', 'a/*/*')); + expect_truthy(!isMatch('b/a/a', 'a/*/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/*')); + + expect_truthy(!isMatch('a', 'a/*/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/*/*')); + expect_truthy(!isMatch('a/a/a', 'a/*/*/*')); + expect_truthy(isMatch('a/a/a/a', 'a/*/*/*')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/*/*')); + + expect_truthy(!isMatch('a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/a', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/b', 'a/*/*/*/*')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/*/*/*')); + expect_truthy(isMatch('a/a/a/a/a', 'a/*/*/*/*')); + + expect_truthy(!isMatch('a', 'a/*/a')); + expect_truthy(!isMatch('a/a', 'a/*/a')); + expect_truthy(isMatch('a/a/a', 'a/*/a')); + expect_truthy(!isMatch('a/a/b', 'a/*/a')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/a')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/a')); + + expect_truthy(!isMatch('a', 'a/*/b')); + expect_truthy(!isMatch('a/a', 'a/*/b')); + expect_truthy(!isMatch('a/a/a', 'a/*/b')); + expect_truthy(isMatch('a/a/b', 'a/*/b')); + expect_truthy(!isMatch('a/a/a/a', 'a/*/b')); + expect_truthy(!isMatch('a/a/a/a/a', 'a/*/b')); + }); + + test('should only match a single folder per star when globstars are used', () => { + expect_truthy(!isMatch('a', '*/**/a')); + expect_truthy(!isMatch('a/a/b', '*/**/a')); + expect_truthy(isMatch('a/a', '*/**/a')); + expect_truthy(isMatch('a/a/a', '*/**/a')); + expect_truthy(isMatch('a/a/a/a', '*/**/a')); + expect_truthy(isMatch('a/a/a/a/a', '*/**/a')); + }); + + test('should not match a trailing slash when a star is last char', () => { + expect_truthy(!isMatch('a', '*/')); + expect_truthy(!isMatch('a', '*/*')); + expect_truthy(!isMatch('a', 'a/*')); + expect_truthy(!isMatch('a/', '*/*')); + expect_truthy(!isMatch('a/', 'a/*')); + expect_truthy(!isMatch('a/a', '*')); + expect_truthy(!isMatch('a/a', '*/')); + expect_truthy(!isMatch('a/x/y', '*/')); + expect_truthy(!isMatch('a/x/y', '*/*')); + expect_truthy(!isMatch('a/x/y', 'a/*')); + expect_truthy(!isMatch('a/', '*', { strictSlashes: true })); + expect_truthy(isMatch('a/', '*')); + expect_truthy(isMatch('a', '*')); + expect_truthy(isMatch('a/', '*/')); + expect_truthy(isMatch('a/', '*{,/}')); + expect_truthy(isMatch('a/a', '*/*')); + expect_truthy(isMatch('a/a', 'a/*')); + }); + + test('should work with file extensions', () => { + expect_truthy(!isMatch('a.txt', 'a/**/*.txt')); + expect_truthy(isMatch('a/x/y.txt', 'a/**/*.txt')); + expect_truthy(!isMatch('a/x/y/z', 'a/**/*.txt')); + + expect_truthy(!isMatch('a.txt', 'a/*.txt')); + expect_truthy(isMatch('a/b.txt', 'a/*.txt')); + expect_truthy(!isMatch('a/x/y.txt', 'a/*.txt')); + expect_truthy(!isMatch('a/x/y/z', 'a/*.txt')); + + expect_truthy(isMatch('a.txt', 'a*.txt')); + expect_truthy(!isMatch('a/b.txt', 'a*.txt')); + expect_truthy(!isMatch('a/x/y.txt', 'a*.txt')); + expect_truthy(!isMatch('a/x/y/z', 'a*.txt')); + + expect_truthy(isMatch('a.txt', '*.txt')); + expect_truthy(!isMatch('a/b.txt', '*.txt')); + expect_truthy(!isMatch('a/x/y.txt', '*.txt')); + expect_truthy(!isMatch('a/x/y/z', '*.txt')); + }); + + test('should not match slashes when globstars are not exclusive in a path segment', () => { + expect_truthy(!isMatch('foo/baz/bar', 'foo**bar')); + expect_truthy(isMatch('foobazbar', 'foo**bar')); + }); + + test('should match slashes when defined in braces', () => { + expect_truthy(isMatch('foo', 'foo{,/**}')); + }); + + test('should correctly match slashes', () => { + expect_truthy(!isMatch('a/b', 'a*')); + expect_truthy(!isMatch('a/a/bb', 'a/**/b')); + expect_truthy(!isMatch('a/bb', 'a/**/b')); + + expect_truthy(!isMatch('foo', '*/**')); + expect_truthy(!isMatch('foo/bar', '**/')); + expect_truthy(!isMatch('foo/bar', '**/*/')); + expect_truthy(!isMatch('foo/bar', '*/*/')); + expect_truthy(!isMatch('foo/bar/', '**/*', { strictSlashes: true })); + + expect_truthy(isMatch('/home/foo/..', '**/..')); + expect_truthy(isMatch('a', '**/a')); + expect_truthy(isMatch('a/a', '**')); + expect_truthy(isMatch('a/a', 'a/**')); + expect_truthy(isMatch('a/', 'a/**')); + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(!isMatch('a/a', '**/')); + expect_truthy(isMatch('a', '**/a/**')); + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(!isMatch('a/a', '**/')); + expect_truthy(isMatch('a/a', '*/**/a')); + expect_truthy(isMatch('a', 'a/**')); + expect_truthy(isMatch('foo/', '*/**')); + expect_truthy(isMatch('foo/bar', '**/*')); + expect_truthy(isMatch('foo/bar', '*/*')); + expect_truthy(isMatch('foo/bar', '*/**')); + expect_truthy(isMatch('foo/bar/', '**/')); + expect_truthy(isMatch('foo/bar/', '**/*')); + expect_truthy(isMatch('foo/bar/', '**/*/')); + expect_truthy(isMatch('foo/bar/', '*/**')); + expect_truthy(isMatch('foo/bar/', '*/*/')); + + expect_truthy(!isMatch('bar/baz/foo', '*/foo')); + expect_truthy(!isMatch('deep/foo/bar', '**/bar/*')); + expect_truthy(!isMatch('deep/foo/bar/baz/x', '*/bar/**')); + expect_truthy(!isMatch('ef', '/*')); + expect_truthy(!isMatch('foo/bar', 'foo?bar')); + expect_truthy(!isMatch('foo/bar/baz', '**/bar*')); + expect_truthy(!isMatch('foo/bar/baz', '**/bar**')); + expect_truthy(!isMatch('foo/baz/bar', 'foo**bar')); + expect_truthy(!isMatch('foo/baz/bar', 'foo*bar')); + expect_truthy(isMatch('foo', 'foo/**')); + expect_truthy(isMatch('/ab', '/*')); + expect_truthy(isMatch('/cd', '/*')); + expect_truthy(isMatch('/ef', '/*')); + expect_truthy(isMatch('a/b/j/c/z/x.md', 'a/**/j/**/z/*.md')); + expect_truthy(isMatch('a/j/z/x.md', 'a/**/j/**/z/*.md')); + + expect_truthy(isMatch('bar/baz/foo', '**/foo')); + expect_truthy(isMatch('deep/foo/bar/baz', '**/bar/*')); + expect_truthy(isMatch('deep/foo/bar/baz/', '**/bar/**')); + expect_truthy(isMatch('deep/foo/bar/baz/x', '**/bar/*/*')); + expect_truthy(isMatch('foo/b/a/z/bar', 'foo/**/**/bar')); + expect_truthy(isMatch('foo/b/a/z/bar', 'foo/**/bar')); + expect_truthy(isMatch('foo/bar', 'foo/**/**/bar')); + expect_truthy(isMatch('foo/bar', 'foo/**/bar')); + expect_truthy(isMatch('foo/bar/baz/x', '*/bar/**')); + expect_truthy(isMatch('foo/baz/bar', 'foo/**/**/bar')); + expect_truthy(isMatch('foo/baz/bar', 'foo/**/bar')); + expect_truthy(isMatch('XXX/foo', '**/foo')); + }); + + test('should ignore leading "./" when defined on pattern', () => { + expect_truthy(isMatch('ab', './*')); + expect_truthy(!isMatch('ab', './*/')); + expect_truthy(isMatch('ab/', './*/')); + }); + + test('should optionally match trailing slashes with braces', () => { + expect_truthy(isMatch('foo', '**/*')); + expect_truthy(isMatch('foo', '**/*{,/}')); + expect_truthy(isMatch('foo/', '**/*{,/}')); + expect_truthy(isMatch('foo/bar', '**/*{,/}')); + expect_truthy(isMatch('foo/bar/', '**/*{,/}')); + }); + }); +}); diff --git a/packages/node-utils/test/picomatch/wildmat.test.ts b/packages/node-utils/test/picomatch/wildmat.test.ts new file mode 100644 index 0000000..2f02e20 --- /dev/null +++ b/packages/node-utils/test/picomatch/wildmat.test.ts @@ -0,0 +1,83 @@ +// @ts-nocheck — mechanically ported from upstream JS tests; bun runs them as-is. +import { describe, expect, test } from "bun:test"; +import picomatch from "../../src/picomatch/index.ts"; + +const match = (list: string | string[], pattern: string, options: any = {}): string[] => { + const isMatch = picomatch(pattern, options, true); + const matches: Set = options.matches || new Set(); + for (const item of ([] as string[]).concat(list)) { + const m = (isMatch as any)(item, true); + if (m && m.output && m.isMatch === true) matches.add(m.output); + } + return [...matches]; +}; + +const expect_truthy = (v: unknown, _msg?: unknown) => { + expect(Boolean(v)).toBe(true); +}; +const expect_equal = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toBe(expected as any); +}; +const expect_deepEqual = (actual: unknown, expected: unknown, _msg?: unknown) => { + expect(actual).toEqual(expected as any); +}; +const expect_throws = (fn: () => unknown, matcher?: any, _msg?: unknown) => { + if (matcher) expect(fn).toThrow(matcher); + else expect(fn).toThrow(); +}; +const expect_doesNotThrow = (fn: () => unknown, _msg?: unknown) => { + expect(fn).not.toThrow(); +}; + + +const { isMatch } = picomatch; + +describe('Wildmat (git) tests', () => { + test('Basic wildmat features', () => { + expect_truthy(!isMatch('foo', '*f')); + expect_truthy(!isMatch('foo', '??')); + expect_truthy(!isMatch('foo', 'bar')); + expect_truthy(!isMatch('foobar', 'foo\\*bar')); + expect_truthy(isMatch('?a?b', '\\??\\?b')); + expect_truthy(isMatch('aaaaaaabababab', '*ab')); + expect_truthy(isMatch('foo', '*')); + expect_truthy(isMatch('foo', '*foo*')); + expect_truthy(isMatch('foo', '???')); + expect_truthy(isMatch('foo', 'f*')); + expect_truthy(isMatch('foo', 'foo')); + expect_truthy(isMatch('foobar', '*ob*a*r*')); + }); + + test('should support recursion', () => { + expect_truthy(!isMatch('-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*')); + expect_truthy(!isMatch('-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*')); + expect_truthy(!isMatch('ab/cXd/efXg/hi', '*X*i')); + expect_truthy(!isMatch('ab/cXd/efXg/hi', '*Xg*i')); + expect_truthy(!isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz', '**/*a*b*g*n*t')); + expect_truthy(!isMatch('foo', '*/*/*')); + expect_truthy(!isMatch('foo', 'fo')); + expect_truthy(!isMatch('foo/bar', '*/*/*')); + expect_truthy(!isMatch('foo/bar', 'foo?bar')); + expect_truthy(!isMatch('foo/bb/aa/rr', '*/*/*')); + expect_truthy(!isMatch('foo/bba/arr', 'foo*')); + expect_truthy(!isMatch('foo/bba/arr', 'foo**')); + expect_truthy(!isMatch('foo/bba/arr', 'foo/*')); + expect_truthy(!isMatch('foo/bba/arr', 'foo/**arr')); + expect_truthy(!isMatch('foo/bba/arr', 'foo/**z')); + expect_truthy(!isMatch('foo/bba/arr', 'foo/*arr')); + expect_truthy(!isMatch('foo/bba/arr', 'foo/*z')); + expect_truthy(!isMatch('XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1', 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*')); + expect_truthy(isMatch('-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1', '-*-*-*-*-*-*-12-*-*-*-m-*-*-*')); + expect_truthy(isMatch('ab/cXd/efXg/hi', '**/*X*/**/*i')); + expect_truthy(isMatch('ab/cXd/efXg/hi', '*/*X*/*/*i')); + expect_truthy(isMatch('abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt', '**/*a*b*g*n*t')); + expect_truthy(isMatch('abcXdefXghi', '*X*i')); + expect_truthy(isMatch('foo', 'foo')); + expect_truthy(isMatch('foo/bar', 'foo/*')); + expect_truthy(isMatch('foo/bar', 'foo/bar')); + expect_truthy(isMatch('foo/bar', 'foo[/]bar')); + expect_truthy(isMatch('foo/bb/aa/rr', '**/**/**')); + expect_truthy(isMatch('foo/bba/arr', '*/*/*')); + expect_truthy(isMatch('foo/bba/arr', 'foo/**')); + }); +}); diff --git a/packages/node-utils/tsconfig.test.json b/packages/node-utils/tsconfig.test.json index 3cea8ed..214b3ce 100644 --- a/packages/node-utils/tsconfig.test.json +++ b/packages/node-utils/tsconfig.test.json @@ -3,6 +3,12 @@ "include": ["src/**/*.ts", "test/**/*.ts"], "exclude": ["node_modules", "dist"], "compilerOptions": { - "noEmit": true + "noEmit": true, + // Many test files are 1:1 mechanical ports from upstream JS — relax the + // strict-mode rules that would force us to manually annotate every callback + // parameter or guard against runtime tricks (e.g. `path.sep = '\\'`). + "strict": false, + "noImplicitAny": false, + "noImplicitThis": false } }