char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
688e96e
main
1mod common; 2 3use common:: run; 4 5# [ test ] 6fn matches_components_and_returns_named_optional_and_numbered_groups () { 7let output =run ( 8r#" 9const p = new URLPattern({ protocol: 'https', hostname: ':sub.example.com', 10pathname: '/books/:id(\\d+){/:slug}?', search: 'q=:term', hash: ':section' }); 11const url = 'https://api.example.com/books/42?q=rust#intro'; 12const result = p.exec(url); 13console.log(p.protocol, p.username, p.password, p.hostname, p.port, p.pathname, p.search, p.hash, p.hasRegExpGroups); 14console.log(p.test(url), p.test('https://api.example.com/books/no?q=rust#intro')); 15console.log(JSON.stringify(result.inputs), result.hostname.groups.sub, result.pathname.groups.id, 16Object.hasOwn(result.pathname.groups, 'slug'), result.pathname.groups.slug, 17result.search.groups.term, result.hash.groups.section, result.port.groups[0]); 18const second = p.exec('https://www.example.com/books/7/title?q=js#end'); 19console.log(second.pathname.groups.slug, result.pathname.groups.id, second !== result, 20second.pathname !== result.pathname, second.pathname.groups !== result.pathname.groups); 21const numbered = new URLPattern({pathname: '/(foo|bar)/*'}).exec({pathname:'/foo/rest/of/path'}); 22console.log(JSON.stringify(numbered.pathname)); 23console.log(Object.keys(result).join(','), Object.keys(result.pathname).join(',')); 24console.log(p.exec('https://api.example.com/nope')); 25console.log(Object.prototype.toString.call(p), Object.getPrototypeOf(result) === Object.prototype, 26Object.getPrototypeOf(result.pathname.groups) === Object.prototype); 27"# , 28& [], 29); 30assert_eq! ( 31String :: from_utf8 ( output. stdout ). unwrap (), 32concat! ( 33"https * * :sub.example.com * /books/:id(\\d+)/:slug? q=:term :section true\n" , 34"true false\n" , 35"[\"https://api.example.com/books/42?q=rust#intro\"] api 42 true undefined rust intro \n" , 36"title 42 true true true\n" , 37"{\"groups\":{\"0\":\"foo\",\"1\":\"rest/of/path\"},\"input\":\"/foo/rest/of/path\"}\n" , 38"hash,hostname,inputs,password,pathname,port,protocol,search,username groups,input\n" , 39"null\n" , 40"[object URLPattern] true true\n" , 41) 42); 43} 44 45# [ test ] 46fn accepts_constructor_overloads_relative_inputs_and_normalized_urls () { 47let output =run ( 48r#" 49const p = new URLPattern('./books/:id', 'https://example.com/store/index'); 50console.log(p.hostname, p.pathname, p.test('./books/42', 'https://example.com/store/index')); 51console.log(JSON.stringify(p.exec('./books/42', 'https://example.com/store/index').inputs)); 52const init = { pathname: './books/:id', baseURL: 'https://example.com/store/index' }; 53console.log(new URLPattern(init).test('https://example.com/store/books/42')); 54const international = new URLPattern('https://BÜCHER.example:443/café/:id'); 55console.log(international.hostname, international.port, international.pathname, 56international.exec('https://xn--bcher-kva.example/caf%C3%A9/42').pathname.groups.id); 57const ipv6 = new URLPattern({ protocol: 'https', hostname: '[\\:\\:1]', port: '443' }); 58console.log(ipv6.test('https://[::1]/'), ipv6.port); 59console.log(new URLPattern(new URL('https://example.com/path')).test('https://example.com/path')); 60console.log(new URLPattern().test(), new URLPattern(null).test(null), new URLPattern(undefined, undefined).test(undefined)); 61console.log(JSON.stringify(new URLPattern().exec().inputs)); 62console.log(new URLPattern({pathname:'/foo'}).test('/foo'), new URLPattern().test('http://['), 63new URLPattern().exec({baseURL:'invalid'})); 64const dictionary = { pathname: 42, extra: 'ignored' }; 65const r = new URLPattern({pathname:':id'}).exec(dictionary); 66console.log(JSON.stringify(r.inputs), r.pathname.groups.id, r.inputs[0] !== dictionary); 67console.log(new URLPattern(42, 'https://example.com/').pathname); 68"# , 69& [], 70); 71assert_eq! ( 72String :: from_utf8 ( output. stdout ). unwrap (), 73concat! ( 74"example.com /store/books/:id true\n" , 75"[\"./books/42\",\"https://example.com/store/index\"]\n" , 76"true\n" , 77"xn--bcher-kva.example /caf%C3%A9/:id 42\n" , 78"true \n" , 79"true\ntrue true true\n[{}]\n" , 80"false false null\n" , 81"[{\"pathname\":\"42\"}] 42 true\n" , 82"/42\n" , 83) 84); 85} 86 87# [ test ] 88fn uses_ecmascript_regexps_unicode_sets_and_component_specific_case_rules () { 89let output =run ( 90r#" 91const p = new URLPattern({protocol:'((?=https)https)', pathname:'/((?!admin)[a-z]+)'}); 92console.log(p.test('https://example.com/public'), p.test('https://example.com/admin'), p.hasRegExpGroups); 93const sets = new URLPattern({pathname:'/([[a-z]--a])'}); 94console.log(sets.test({pathname:'/a'}), sets.exec({pathname:'/z'}).pathname.groups[0]); 95const intersection = new URLPattern({pathname:'/([\\d&&[0-1]])'}); 96console.log(intersection.test({pathname:'/0'}), intersection.test({pathname:'/3'})); 97const unicode = new URLPattern({hash:':letter(\\p{Letter}+)' }); 98console.log(unicode.exec({hash:'abc'}).hash.groups.letter); 99const opts = {ignoreCase: 'truthy'}; 100for (const component of ['protocol','username','password','hostname','pathname','search','hash']) { 101const value = component === 'protocol' ? '(HTTPS)' : 'FOO'; 102const input = component === 'protocol' ? 'https' : 'foo'; 103console.log(component, new URLPattern({[component]:value}, opts).test({[component]:input})); 104} 105console.log(new URLPattern({pathname:'/x/:id?'}).hasRegExpGroups, 106new URLPattern({pathname:'/x/:id(.*)'}).hasRegExpGroups, 107new URLPattern({pathname:'/x/:id([a-z]+)'}).hasRegExpGroups); 108"# , 109& [], 110); 111assert_eq! ( output. stdout , b"true false true\nfalse z\ntrue false\nabc\nprotocol false\nusername false\npassword false\nhostname true\npathname true\nsearch true\nhash true\nfalse false true\n" ); 112} 113 114# [ test ] 115fn validates_patterns_receivers_and_webidl_conversion_without_poisoning_later_calls () { 116let output =run ( 117r#" 118const typeError = fn => { try { fn(); } catch (e) { if (e instanceof TypeError) return; throw e; } throw new Error('Expected TypeError'); }; 119const p = new URLPattern(); 120for (const fn of [() => URLPattern(), () => new URLPattern('/relative'), 121() => new URLPattern({pathname:'()'}), () => new URLPattern({pathname:'/(a(b))'}), 122() => new URLPattern({pathname:'/(a{1,0})'}), () => new URLPattern({pathname:'/:x/:x'}), 123() => new URLPattern({pathname:'\\'}), () => new URLPattern(Symbol()), 124() => new URLPattern({}, 'https://example.com'), () => new URLPattern({}, null, {}), 125() => new URLPattern({}, 1), () => new URLPattern({}, {}, {}), 126() => p.test({}, 'https://example.com'), () => p.exec({}, ''), () => p.test(Symbol()), 127() => URLPattern.prototype.test.call({}), () => URLPattern.prototype.exec.call({}), 128() => Object.getOwnPropertyDescriptor(URLPattern.prototype,'pathname').get.call({}), 129() => Object.getOwnPropertyDescriptor(URLPattern.prototype,'hasRegExpGroups').get.call({}), 130]) typeError(fn); 131console.log(new URLPattern({pathname:'/x'}).test('https://example.com/x')); 132const order = []; 133const input = new Proxy({}, {get(_, key) {order.push(key); return undefined;}}); 134new URLPattern(input, {get ignoreCase() {order.push('ignoreCase'); return false;}}); 135console.log(order.join(',')); 136console.log(new URLPattern({pathname:'/\uD800'}).pathname); 137const r = p.exec({pathname:'\uD800'}); console.log(r.inputs[0].pathname === '\uFFFD'); 138class Child extends URLPattern {} 139console.log(new Child({pathname:'/x'}) instanceof Child, URLPattern.length, p.test.length, p.exec.length); 140const descriptor = Object.getOwnPropertyDescriptor(URLPattern.prototype,'pathname'); 141console.log(descriptor.enumerable, descriptor.configurable, descriptor.set, descriptor.get.name); 142"# , 143& [], 144); 145assert_eq! ( output. stdout , b"true\nbaseURL,hash,hostname,password,pathname,port,protocol,search,username,ignoreCase\n/%EF%BF%BD\ntrue\ntrue 0 0 0\ntrue true undefined get pathname\n" ); 146} 147 148# [ test ] 149fn captures_primordials_and_materializes_groups_without_prototype_setters () { 150let output =run ( 151r#" 152const p = new URLPattern({pathname:'/:__proto__/:constructor/:id'}); 153const re = RegExp.prototype, strings = String.prototype, weak = WeakMap.prototype; 154const poison = () => {throw new Error('Poisoned intrinsic');}; 155globalThis.RegExp = poison; globalThis.URL = poison; globalThis.String = poison; globalThis.WeakMap = poison; 156re.exec = poison; strings.toWellFormed = poison; weak.get = poison; weak.set = poison; 157Object.defineProperty(Object.prototype,'id',{set:poison,configurable:true}); 158Object.defineProperty(Array.prototype,'1',{set:poison,configurable:true}); 159Object.defineProperty(p,'pathname',{value:'overridden'}); 160const r = p.exec('https://example.com/first/second/third'); 161const fresh = new URLPattern({protocol:'((?=https)https)', pathname:'/x/:id'}); 162const matched = fresh.exec('https://example.com/x/42'); 163delete Array.prototype[1]; delete Object.prototype.id; 164console.log(r.pathname.groups.__proto__, r.pathname.groups.constructor, r.pathname.groups.id, 165Object.getPrototypeOf(r.pathname.groups) === Object.prototype, Object.hasOwn(r.pathname.groups,'__proto__')); 166console.log(fresh.test('https://example.com/x/7'), matched.pathname.groups.id, 167new URLPattern({pathname:'/x'}).test('https://example.com/x')); 168"# , 169& [], 170); 171assert_eq! ( 172 output. stdout , 173b"first second third true true\ntrue 42 true\n" 174); 175} 176 177# [ test ] 178fn native_protocol_checks_never_call_user_regexp_exec () { 179let output =run ( 180r#" 181const original = Object.getOwnPropertyDescriptor(RegExp.prototype, 'exec'); 182let calls = 0; 183const build = () => [ 184new URLPattern({protocol:'(https)', pathname:'x y'}).pathname, 185new URLPattern({protocol:'(foo)', pathname:'x y'}).pathname, 186].join('|'); 187const baseline = build(); 188for (const replacement of [() => {calls++; return {};}, () => {calls++; throw new Error('Poisoned exec');}, 189() => {calls++; new URLPattern({protocol:'(x)'}); return null;}]) { 190Object.defineProperty(RegExp.prototype, 'exec', {value:replacement, writable:true, configurable:true}); 191if (build() !== baseline) throw new Error('User exec changed parsing'); 192} 193Object.defineProperty(RegExp.prototype, 'exec', {get() {calls++; throw new Error('Poisoned exec getter');}, configurable:true}); 194if (build() !== baseline) throw new Error('User getter changed parsing'); 195Object.defineProperty(RegExp.prototype, 'exec', original); 196console.log(baseline, calls); 197"# , 198& [], 199); 200assert_eq! ( output. stdout , b"x%20y|x y 0\n" ); 201}