char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport URL extension to typescriptbf382b3

main
8.0 KiB237 linesraw
1import {
2  Array,
3  ArrayPrototypePush,
4  ArrayPrototypeSort as sort,
5  ArrayPrototypeSplice as splice,
6  IteratorPrototype,
7  Map,
8  MapPrototypeGet,
9  MapPrototypeHas,
10  MapPrototypeSet,
11  ObjectDefineProperty,
12  ObjectGetOwnPropertyDescriptor,
13  ObjectSetPrototypeOf,
14  ReflectApply,
15  ReflectOwnKeys,
16  StringPrototypeSlice,
17  SymbolIterator,
18  SymbolToStringTag,
19  TypeError,
20  WeakMapPrototypeSet as weakSet,
21} from "ext:core/primordials";
22import { isObject, required, state, usv } from "ext:web/idl.js";
23import { markUncloneable } from "ext:web/serialization.js";
24import { op_url_search_parse, op_url_search_serialize } from "ext:core/ops";
25import { params, privateArray, update } from "./state.js";
26import type { SearchPair } from "ext:core/ops";
27import type { ParamsState } from "./state.js";
28
29type IteratorValues = { 0: SearchPair; 1: string; 2: string };
30
31const arrayPrototype = Array.prototype;
32function iterable(value: object, method: Function): Iterable<unknown> {
33  return {
34    [SymbolIterator]() {
35      // for-of validates the returned iterator and owns IteratorClose.
36      return ReflectApply(method, value, []) as Iterator<unknown>;
37    },
38  };
39}
40
41interface ParamsIterator<K extends keyof IteratorValues> extends IterableIterator<IteratorValues[K], undefined, unknown> {}
42class ParamsIterator<K extends keyof IteratorValues> {
43  #state: ParamsState;
44  #index = 0;
45  #kind: K;
46  constructor(value: ParamsState, kind: K) {
47    markUncloneable(this);
48    this.#state = value;
49    this.#kind = kind;
50  }
51  next(): IteratorResult<IteratorValues[K], undefined> {
52    const value = this.#state;
53    if (this.#index >= value.list.length) {
54      return { value: undefined, done: true };
55    }
56    const pair = value.list[this.#index++];
57    return {
58      value: (this.#kind === 0 ? [pair[0], pair[1]] : pair[this.#kind - 1]) as IteratorValues[K],
59      done: false,
60    };
61  }
62}
63delete (ParamsIterator.prototype as { constructor?: Function }).constructor;
64ObjectDefineProperty(ParamsIterator.prototype, "next", { enumerable: true });
65ObjectSetPrototypeOf(ParamsIterator.prototype, IteratorPrototype);
66ObjectDefineProperty(ParamsIterator.prototype, SymbolToStringTag, {
67  value: "URLSearchParams Iterator",
68  configurable: true,
69});
70
71interface URLSearchParams {
72  [Symbol.iterator](): ParamsIterator<0>;
73}
74class URLSearchParams {
75  constructor(init: unknown = "") {
76    const list = privateArray<string[]>();
77    if (isObject(init)) {
78      const method = init[SymbolIterator];
79      if (method !== undefined && method !== null) {
80        if (typeof method !== "function") {
81          throw new TypeError("Invalid iterator");
82        }
83        for (const pair of iterable(init, method)) {
84          if (!isObject(pair)) {
85            throw new TypeError("Query pairs must be sequences");
86          }
87          const method = pair[SymbolIterator];
88          if (typeof method !== "function") {
89            throw new TypeError("Query pairs must be sequences");
90          }
91          const values = privateArray<string>();
92          for (const item of iterable(pair, method)) {
93            ArrayPrototypePush(values, usv(item));
94          }
95          ArrayPrototypePush(list, values);
96        }
97        for (let index = 0; index < list.length; index++) {
98          if (list[index].length !== 2) {
99            throw new TypeError("Query pairs must have exactly two items");
100          }
101        }
102      } else {
103        const keys = new Map<string, number>();
104        const ownKeys = ReflectOwnKeys(init);
105        for (let index = 0; index < ownKeys.length; index++) {
106          const key = ownKeys[index];
107          if (!ObjectGetOwnPropertyDescriptor(init, key)?.enumerable) continue;
108          const name = usv(key);
109          const value = usv(init[key]);
110          if (MapPrototypeHas(keys, name)) {
111            list[MapPrototypeGet(keys, name)!][1] = value;
112          } else {
113            MapPrototypeSet(keys, name, list.length);
114            ArrayPrototypePush(list, privateArray([name, value]));
115          }
116        }
117      }
118    } else {
119      const text = usv(init);
120      const parsed = op_url_search_parse(
121        text[0] === "?" ? StringPrototypeSlice(text, 1) : text,
122      );
123      for (let index = 0; index < parsed.length; index++) {
124        ArrayPrototypePush(list, privateArray(parsed[index]));
125      }
126    }
127    // Pair lengths are checked only after the entire sequence has been converted.
128    weakSet(params, this, { list: list as SearchPair[], url: null });
129  }
130  get size(): number {
131    return state(params, this).list.length;
132  }
133  append(rawName: unknown, rawValue: unknown): void {
134    const data = state(params, this);
135    required(arguments.length, 2);
136    const name = usv(rawName);
137    const value = usv(rawValue);
138    ArrayPrototypePush(data.list, privateArray<SearchPair>([name, value]));
139    update(data);
140  }
141  delete(rawName: unknown, rawValue: unknown = undefined): void {
142    const data = state(params, this);
143    required(arguments.length, 1);
144    const name = usv(rawName);
145    const value = rawValue === undefined ? undefined : usv(rawValue);
146    for (let index = 0; index < data.list.length;) {
147      const pair = data.list[index];
148      if (pair[0] === name && (value === undefined || pair[1] === value)) {
149        splice(data.list, index, 1);
150      } else index++;
151    }
152    update(data);
153  }
154  get(rawName: unknown): string | null {
155    const data = state(params, this);
156    required(arguments.length, 1);
157    const name = usv(rawName);
158    for (let index = 0; index < data.list.length; index++) {
159      if (data.list[index][0] === name) return data.list[index][1];
160    }
161    return null;
162  }
163  getAll(rawName: unknown): string[] {
164    const data = state(params, this);
165    required(arguments.length, 1);
166    const name = usv(rawName);
167    const result = privateArray<string>();
168    for (let index = 0; index < data.list.length; index++) {
169      if (data.list[index][0] === name) {
170        ArrayPrototypePush(result, data.list[index][1]);
171      }
172    }
173    ObjectSetPrototypeOf(result, arrayPrototype);
174    return result;
175  }
176  has(rawName: unknown, rawValue: unknown = undefined): boolean {
177    const data = state(params, this);
178    required(arguments.length, 1);
179    const name = usv(rawName);
180    const value = rawValue === undefined ? undefined : usv(rawValue);
181    for (let index = 0; index < data.list.length; index++) {
182      const pair = data.list[index];
183      if (pair[0] === name && (value === undefined || pair[1] === value)) {
184        return true;
185      }
186    }
187    return false;
188  }
189  set(rawName: unknown, rawValue: unknown): void {
190    const data = state(params, this);
191    required(arguments.length, 2);
192    const name = usv(rawName);
193    const value = usv(rawValue);
194    let found = false;
195    for (let index = 0; index < data.list.length;) {
196      if (data.list[index][0] !== name) index++;
197      else if (found) splice(data.list, index, 1);
198      else {
199        data.list[index++][1] = value;
200        found = true;
201      }
202    }
203    if (!found) ArrayPrototypePush(data.list, privateArray<SearchPair>([name, value]));
204    update(data);
205  }
206  sort(): void {
207    const data = state(params, this);
208    // JS string comparison supplies the required UTF-16 ordering, not UTF-8 ordering.
209    sort(data.list, (a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
210    update(data);
211  }
212  forEach(callback: (value: string, name: string, parent: URLSearchParams) => void, thisArg: unknown = undefined): void {
213    const data = state(params, this);
214    required(arguments.length, 1);
215    if (typeof callback !== "function") {
216      throw new TypeError("Callback must be callable");
217    }
218    for (let index = 0; index < data.list.length; index++) {
219      const pair = data.list[index];
220      ReflectApply(callback, thisArg, [pair[1], pair[0], this]);
221    }
222  }
223  entries(): ParamsIterator<0> {
224    return new ParamsIterator(state(params, this), 0);
225  }
226  keys(): ParamsIterator<1> {
227    return new ParamsIterator(state(params, this), 1);
228  }
229  values(): ParamsIterator<2> {
230    return new ParamsIterator(state(params, this), 2);
231  }
232  toString(): string {
233    return op_url_search_serialize(state(params, this).list);
234  }
235}
236
237export { URLSearchParams };