mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-07 08:33:50 +08:00
470 lines
7.7 KiB
JavaScript
470 lines
7.7 KiB
JavaScript
// Generates path.matchesGlob test cases with Node as the oracle.
|
|
//
|
|
// node --expose-internals gen-glob-cases.mjs > glob-cases.txt
|
|
//
|
|
// Lines are tab-separated:
|
|
// p <path> adds a path to the curated list
|
|
// c <pattern> <posix> <win32> a pattern against every curated path
|
|
// f <path> <pattern> <posix> <win32> one fuzz case
|
|
// Strings keep printable ASCII and spell everything else (and "\") as
|
|
// \uXXXX UTF-16 code units, so lone surrogates survive. Each answer is two
|
|
// characters per path, case-sensitive then case-insensitive ("1", "0", or
|
|
// "E" when Node throws): Node 24 matches case-insensitively on macOS and
|
|
// Windows hosts, so both modes are recorded through the bundled minimatch
|
|
// with matchGlobPattern's exact options. Node v24 is required: the cases pin
|
|
// its bundled minimatch.
|
|
import { posix, win32 } from "node:path";
|
|
import { stdout } from "node:process";
|
|
import { createRequire } from "node:module";
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const { minimatch } = require("internal/deps/minimatch/index");
|
|
|
|
// The pattern has no u flag, so each match is one UTF-16 code unit.
|
|
const escape = (s) =>
|
|
s.replace(/[^\x20-\x5b\x5d-\x7e]/g, (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));
|
|
const options = (windows, nocase) => ({
|
|
nocase,
|
|
windowsPathsNoEscape: true,
|
|
nonegate: true,
|
|
nocomment: true,
|
|
optimizationLevel: 2,
|
|
platform: windows ? "win32" : "posix",
|
|
nocaseMagicOnly: true,
|
|
});
|
|
const lines = [];
|
|
const answer = (path, pattern, windows, nocase) => {
|
|
try {
|
|
return minimatch(path, pattern, options(windows, nocase)) ? "1" : "0";
|
|
} catch {
|
|
return "E";
|
|
}
|
|
};
|
|
const hostNocase = process.platform === "darwin" || process.platform === "win32";
|
|
// Both case modes for one family; the public API on this host must agree
|
|
// with the recorded options.
|
|
const answers = (path, pattern, windows) => {
|
|
const expected = [false, true].map((nocase) => answer(path, pattern, windows, nocase));
|
|
let actual;
|
|
try {
|
|
actual = (windows ? win32 : posix).matchesGlob(path, pattern) ? "1" : "0";
|
|
} catch {
|
|
actual = "E";
|
|
}
|
|
if (actual !== expected[hostNocase ? 1 : 0])
|
|
throw new Error(`oracle mismatch: ${path} ${pattern}`);
|
|
return expected.join("");
|
|
};
|
|
|
|
// ── curated vocabulary ──────────────────────────────────────────────
|
|
const paths = [
|
|
"",
|
|
".",
|
|
"..",
|
|
"/",
|
|
"a",
|
|
"A",
|
|
"ab",
|
|
"a.b",
|
|
"a.js",
|
|
"A.JS",
|
|
".a",
|
|
".js",
|
|
"a/b",
|
|
"a/b/c",
|
|
"a/b/c/d",
|
|
"a/.b",
|
|
"a/.b/c",
|
|
"a/../b",
|
|
"./a",
|
|
"../a",
|
|
"a//b",
|
|
"a/b/",
|
|
"/a",
|
|
"/a/b/c.js",
|
|
"//a/b",
|
|
"x/y/.z.ts",
|
|
"src/lib/index.test.ts",
|
|
"node_modules/pkg/index.js",
|
|
"a\\b",
|
|
"C:\\a\\b",
|
|
"c:\\a",
|
|
"C:/a/b",
|
|
"\\\\server\\share\\x",
|
|
"//server/share/x",
|
|
"//?/C:/x",
|
|
"a b",
|
|
"a,b",
|
|
"a{b}",
|
|
"a(b)",
|
|
"[a]",
|
|
"a!b",
|
|
"a+b",
|
|
"a*b",
|
|
"file.tar.gz",
|
|
"file.",
|
|
"1",
|
|
"12",
|
|
"a10",
|
|
"é",
|
|
"É",
|
|
"😀",
|
|
"a😀b",
|
|
"\ud83d",
|
|
"abc/def/ghi.txt",
|
|
"+(a)",
|
|
"\t",
|
|
"a\nb",
|
|
"aaa",
|
|
"b",
|
|
"ba",
|
|
"a/../../b",
|
|
"../../a",
|
|
];
|
|
const patterns = [
|
|
"",
|
|
"*",
|
|
"**",
|
|
"***",
|
|
"?",
|
|
"??",
|
|
"???",
|
|
"a",
|
|
"A",
|
|
"a*",
|
|
"*a",
|
|
"*a*",
|
|
"a?",
|
|
"?a",
|
|
".*",
|
|
"*.*",
|
|
"*.js",
|
|
"*.JS",
|
|
"**.js",
|
|
"**/*.js",
|
|
"**/*",
|
|
"*/*",
|
|
"*/**",
|
|
"**/**",
|
|
"a/**",
|
|
"a/**/b",
|
|
"a/**/*",
|
|
"**/a",
|
|
"a/*/c",
|
|
"a/*/*",
|
|
"/a",
|
|
"/**",
|
|
"/*",
|
|
"//a",
|
|
"a/b/",
|
|
"a/b",
|
|
"./a",
|
|
"../a",
|
|
"a/../b",
|
|
"a/./b",
|
|
"a/.b",
|
|
"a/.*",
|
|
".a",
|
|
".a/*",
|
|
"**/.b",
|
|
"**/.*",
|
|
"**/../a",
|
|
"a/**/../b",
|
|
"a/**/../b/c",
|
|
"{a,b}",
|
|
"{a,b}/c",
|
|
"{,a}",
|
|
"{a,}",
|
|
"{a}",
|
|
"{}",
|
|
"{}a",
|
|
"a{}",
|
|
"{a,b{c,d}}",
|
|
"{a..c}",
|
|
"{c..a}",
|
|
"{1..3}",
|
|
"{01..3}",
|
|
"{3..1}",
|
|
"{1..10..3}",
|
|
"{a..e..2}",
|
|
"{-2..2}",
|
|
"{a,b,c}{1,2}",
|
|
"x{a,b}y",
|
|
"{a/b,c}",
|
|
"${a,b}",
|
|
"{a,b}}",
|
|
"{{a,b}",
|
|
"{a,{b,c}}",
|
|
"{a\\,b}",
|
|
"{1..1}",
|
|
"{A..c}",
|
|
"{x..z,a}",
|
|
"[abc]",
|
|
"[a-c]",
|
|
"[!a]",
|
|
"[^a]",
|
|
"[a-]",
|
|
"[-a]",
|
|
"[]]",
|
|
"[]a]",
|
|
"[!]]",
|
|
"[a-z0-9]",
|
|
"[z-a]",
|
|
"[a",
|
|
"a]",
|
|
"[[]",
|
|
"[\\]]",
|
|
"[.]*",
|
|
"[[:alpha:]]",
|
|
"[[:digit:]]*",
|
|
"[[:upper:]]",
|
|
"[[:lower:]]",
|
|
"[[:alnum:]_]",
|
|
"[[:space:]]",
|
|
"[[:punct:]]",
|
|
"[[:xdigit:]]",
|
|
"[[:word:]]",
|
|
"[[:ascii:]]",
|
|
"[![:alpha:]]",
|
|
"[[:graph:]]",
|
|
"[[:print:]]",
|
|
"[[:cntrl:]]",
|
|
"[[:blank:]]",
|
|
"[a-[:digit:]]",
|
|
"[[:bogus:]]",
|
|
"[é]",
|
|
"[😀]",
|
|
"[à-ü]",
|
|
"+(a)",
|
|
"*(a)",
|
|
"?(a)",
|
|
"@(a)",
|
|
"!(a)",
|
|
"!(a|b)",
|
|
"+(a|b)",
|
|
"*(a|b)c",
|
|
"a+(b)",
|
|
"a*(b)",
|
|
"a?(b)",
|
|
"a@(b|c)",
|
|
"a!(b)",
|
|
"!(*.js)",
|
|
"!(.*)",
|
|
"*.!(js)",
|
|
"!(a)/b",
|
|
"a/!(b)",
|
|
"+(a|*.js)",
|
|
"@(a|b)/**",
|
|
"!(!(a))",
|
|
"+(+(a))",
|
|
"*(+(a))",
|
|
"?(*(a))",
|
|
"@(+(a))",
|
|
"+(?(a))",
|
|
"!(@(a))",
|
|
"@(!(a))",
|
|
"+(*(a)|b)",
|
|
"*(a|+(b))",
|
|
"!()",
|
|
"+()",
|
|
"@()",
|
|
"?()",
|
|
"*()",
|
|
"a!()",
|
|
"!(a",
|
|
"+(a",
|
|
"a)",
|
|
"(a)",
|
|
"+(a)b)",
|
|
"!(a)*",
|
|
"*!(a)",
|
|
"+(a|)",
|
|
"@(|a)",
|
|
"!(|a)",
|
|
"x/+(a|b)/y",
|
|
"+([a-c])",
|
|
"!([a-c])",
|
|
"@(*.ts|*.js)",
|
|
"**/@(a|b).js",
|
|
"?(a|b|c)?",
|
|
"!(foo).js",
|
|
"!(foo)*.js",
|
|
"+(!(a))",
|
|
"*(!(a))",
|
|
"!(+(a))",
|
|
"!(*(a))",
|
|
"!(?(a))",
|
|
"#a",
|
|
"!a",
|
|
"!!a",
|
|
"a\\b",
|
|
"\\a",
|
|
"a\\",
|
|
"C:/a/*",
|
|
"c:/a/*",
|
|
"C:\\a\\*",
|
|
"C:/**",
|
|
"//server/share/*",
|
|
"//?/C:/*",
|
|
"//?/c:/x",
|
|
"*/share/*",
|
|
"**/c",
|
|
"**/*.ts",
|
|
"src/**/*.ts",
|
|
"src/**/!(*.test).ts",
|
|
"**/node_modules/**",
|
|
"**/*.{js,ts}",
|
|
"**/*.+(js|ts)",
|
|
"docs/*.md",
|
|
"*.min.js",
|
|
"*.js.map",
|
|
"**/index.@(ts|js)",
|
|
"x/**/z.ts",
|
|
"x/*/z.ts",
|
|
"x/**/.z.ts",
|
|
"a/b/**/c/**/d",
|
|
"**/b/**",
|
|
"a/**/b/**/c",
|
|
"?/?",
|
|
"??/*",
|
|
"*.?",
|
|
"a.?",
|
|
"a??",
|
|
"a/?/c",
|
|
"é",
|
|
"É",
|
|
"日*",
|
|
"😀",
|
|
"?😀?",
|
|
"a?b",
|
|
"a*b*c",
|
|
"a**b",
|
|
"**a",
|
|
"a**",
|
|
"*/",
|
|
"**/",
|
|
"/",
|
|
"//",
|
|
".",
|
|
"..",
|
|
"./*",
|
|
"../*",
|
|
"*.tar.*",
|
|
"file.*",
|
|
"*.",
|
|
"[.]",
|
|
"\\*",
|
|
"a{b,c",
|
|
"a{b..c}",
|
|
"{a,b,c,d,e}{f,g}",
|
|
"*{.js,.ts}",
|
|
"+(a|b)+(c|d)",
|
|
"!(a).!(b)",
|
|
"@(a|b|c|d|e|f|g)",
|
|
"*(?)",
|
|
"+(*)",
|
|
"!(*)",
|
|
"@(*)",
|
|
"?(*)",
|
|
"\t",
|
|
"a\nb",
|
|
" ",
|
|
"*\n*",
|
|
"[\n]",
|
|
"a/../../b",
|
|
"../../*",
|
|
"**/../../a",
|
|
"a/b/../../../c",
|
|
];
|
|
for (const path of paths) lines.push(`p\t${escape(path)}`);
|
|
for (const pattern of patterns) {
|
|
const family = (windows) => paths.map((path) => answers(path, pattern, windows)).join("");
|
|
lines.push(`c\t${escape(pattern)}\t${family(false)}\t${family(true)}`);
|
|
}
|
|
|
|
// ── seeded fuzz over glob syntax ────────────────────────────────────
|
|
let seed = 20261014;
|
|
const random = () => {
|
|
seed = (Math.imul(seed, 1103515245) + 12345) >>> 0;
|
|
return seed / 4294967296;
|
|
};
|
|
const pick = (list) => list[Math.floor(random() * list.length)];
|
|
const patternTokens = [
|
|
"a",
|
|
"b",
|
|
"c",
|
|
".",
|
|
"/",
|
|
"*",
|
|
"**",
|
|
"?",
|
|
"[",
|
|
"]",
|
|
"!",
|
|
"^",
|
|
"-",
|
|
"(",
|
|
")",
|
|
"|",
|
|
"+",
|
|
"@",
|
|
"{",
|
|
"}",
|
|
",",
|
|
"..",
|
|
"\\",
|
|
":",
|
|
"A",
|
|
"B",
|
|
"é",
|
|
"1",
|
|
"2",
|
|
"[:alpha:]",
|
|
"[:digit:]",
|
|
"!(",
|
|
"+(",
|
|
"*(",
|
|
"?(",
|
|
"@(",
|
|
"{a,b}",
|
|
"{1..3}",
|
|
"[a-c]",
|
|
"[!b]",
|
|
"C:",
|
|
];
|
|
const pathTokens = [
|
|
"a",
|
|
"b",
|
|
"c",
|
|
".",
|
|
"..",
|
|
"/",
|
|
"\\",
|
|
"A",
|
|
"B",
|
|
"ab",
|
|
"é",
|
|
"1",
|
|
"2",
|
|
"-",
|
|
"C:",
|
|
"+",
|
|
"(",
|
|
")",
|
|
];
|
|
const make = (tokens, max) => {
|
|
const n = 1 + Math.floor(random() * max);
|
|
let s = "";
|
|
for (let i = 0; i < n; i++) s += pick(tokens);
|
|
return s;
|
|
};
|
|
// A zero step never ends: Node's expansion fails with RangeError (slowly).
|
|
const fuzz = [["0", "{0..1..0}"]];
|
|
for (let i = 0; i < 3000; i++) fuzz.push([make(pathTokens, 6), make(patternTokens, 7)]);
|
|
for (const [path, pattern] of fuzz) {
|
|
const results = [false, true].map((windows) => answers(path, pattern, windows));
|
|
lines.push(`f\t${escape(path)}\t${escape(pattern)}\t${results.join("\t")}`);
|
|
}
|
|
|
|
stdout.write(lines.join("\n") + "\n");
|