Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

byPrototype #507

Open
wants to merge 26 commits into
base: v4.9.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1c446d3
prototype prop search type
yofukashino Jul 3, 2023
cd865e4
Update filters.ts
yofukashino Jul 3, 2023
02be07d
Update getExportForProps
yofukashino Jul 3, 2023
715bc70
forgot this
yofukashino Jul 3, 2023
c9ea555
done and fixed
yofukashino Jul 3, 2023
6716e87
prettier
yofukashino Jul 3, 2023
000f282
cspell
yofukashino Jul 3, 2023
1dbb68a
type error,...
yofukashino Jul 3, 2023
e79e2f3
idk english, gugu gaga
yofukashino Jul 3, 2023
d5b4bd1
Merge branch 'main' into byPrototype
yofukashino Jul 5, 2023
12dd911
Merge branch 'v4.6.0' into byPrototype
asportnoy Aug 3, 2023
414ee01
Merge branch 'v4.6.0' into byPrototype
yofukashino Aug 11, 2023
3ebd3a5
requested changes
yofukashino Aug 11, 2023
4c2612b
type errors f me hard
yofukashino Aug 11, 2023
aa734aa
Merge branch 'v4.7.0' into byPrototype
yofukashino Aug 24, 2023
fe79463
Merge branch 'v4.7.0' into byPrototype
yofukashino Sep 1, 2023
0f3b97e
undo changes
yofukashino Sep 7, 2023
cb6aa6c
Merge branch 'byPrototype' of https://github.com/Tharki-God/replugged…
yofukashino Sep 7, 2023
3fec953
Merge branch 'v4.7.0' into byPrototype
yofukashino Sep 24, 2023
9850360
Merge branch 'replugged-org:main' into byPrototype
yofukashino Sep 24, 2023
e0bd96a
Merge branch 'v4.7.0' into byPrototype
yofukashino Sep 30, 2023
7b8893c
Merge branch 'v4.8.0' into byPrototype
yofukashino Apr 5, 2024
17e0fe7
prettier
yofukashino Oct 21, 2024
b9b8b88
prettier
yofukashino Oct 21, 2024
4327edc
lint?
yofukashino Oct 21, 2024
dd1e181
Merge branch 'main' of https://github.com/replugged-org/replugged int…
yofukashino Oct 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/renderer/modules/webpack/filters.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { getExportsForProps } from "./get-modules";
import { sourceStrings } from "./patch-load";
import type { RawModule } from "../../../types";
import type { ByPropsOptions, RawModule } from "../../../types";

/**
* Get a module that has all the given properties on one of its exports
* @param props List of property names
*/
export const byProps = <P extends PropertyKey = PropertyKey>(...props: P[]) => {
return (m: RawModule) => typeof getExportsForProps(m.exports, props) !== "undefined";
export const byProps = <P extends PropertyKey = PropertyKey>(
...args: [...P[], ByPropsOptions] | P[]
): ((m: RawModule) => boolean) => {
if (typeof args.at(-1) === "object" && !Array.isArray(args.at(-1)) && args.at(-1) !== null) {
const options = args.pop() as ByPropsOptions;
return (m: RawModule) =>
typeof getExportsForProps(m.exports, args as P[], options) !== "undefined";
}
return (m: RawModule) => typeof getExportsForProps(m.exports, args as P[]) !== "undefined";
};

/**
Expand Down
46 changes: 34 additions & 12 deletions src/renderer/modules/webpack/get-modules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Filter, GetModuleOptions, RawModule } from "src/types";
import type { ByPropsOptions, Filter, GetModuleOptions, RawModule } from "src/types";
import { wpRequire } from "./patch-load";
import { logError } from "./util";

Expand All @@ -22,44 +22,66 @@ export function getExports<T>(m: RawModule): T | undefined {
}

/**
* Iterates over an object and its top-level children that could have properties
* Iterates over an object and its top-level and second-level (if specified) children that could have properties
* @param m Object (module exports) to iterate over
yofukashino marked this conversation as resolved.
Show resolved Hide resolved
* @param secondLevel Boolean on whether to iterate over second level children in object
*/
function* iterateModuleExports(m: unknown): IterableIterator<Record<PropertyKey, unknown>> {
function* iterateModuleExports(
m: unknown,
secondLevel?: boolean,
): IterableIterator<Record<PropertyKey, unknown>> {
// if m is null or not an object/function, then it will obviously not have the props
// if if has no props, then it definitely has no children either
if (m && (typeof m === "object" || typeof m === "function")) {
yield m as Record<PropertyKey, unknown>;
for (const key in m) {
try {
// if it has no props, then it definitely has no children either
try {
if (m && (typeof m === "object" || typeof m === "function")) {
yield m as Record<PropertyKey, unknown>;
for (const key in m) {
// This could throw an error ("illegal invocation") if val === DOMTokenList.prototype
// and key === "length"
// There could be other cases too, hence this try-catch instead of a specific exclusion
const val = (m as Record<PropertyKey, unknown>)[key];
if (val && (typeof val === "object" || typeof val === "function")) {
yield val as Record<PropertyKey, unknown>;
if (secondLevel && typeof val === "object") {
for (const subKey in val) {
const subVal = (val as Record<PropertyKey, unknown>)[subKey];
if (subVal && (typeof val === "object" || typeof val === "function")) {
yield subVal as Record<PropertyKey, unknown>;
continue;
}
}
}
}
} catch {
// ignore this export
}
}
} catch {
// ignore this export
}
}

/**
* Find an object in a module that has all the given properties. You will usually not need this function.
* @param m Module to search
* @param props Array of prop names
* @param options object containing option on whether to look in prototype or not
* @returns Object that contains all the given properties (and any others), or undefined if not found
*/
export function getExportsForProps<T, P extends PropertyKey = keyof T>(
m: unknown,
props: P[],
options?: ByPropsOptions,
): T | undefined {
// Loop over the module and its exports at the top level
// Return the first thing that has all the indicated props
for (const exported of iterateModuleExports(m)) {
if (props.every((p) => p in (exported as Record<P, unknown>))) {
// Checks only in prototypes if specified, usually to look for functions
for (const exported of iterateModuleExports(m, options?.byPrototype)) {
if (
props.every((p) =>
options?.byPrototype
? (exported?.prototype as Record<P, unknown>)?.[p]
: p in (exported as Record<P, unknown>),
)
) {
return exported as T;
}
}
Expand Down
56 changes: 29 additions & 27 deletions src/renderer/modules/webpack/helpers.ts
yofukashino marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { GetModuleOptions, RawModule, WaitForOptions } from "src/types";
import type { ByPropsOptions, GetModuleOptions, RawModule, WaitForOptions } from "src/types";
import { getExportsForProps, getModule } from "./get-modules";
import * as filters from "./filters";
import Flux, { Store } from "../common/flux";
Expand Down Expand Up @@ -60,39 +60,39 @@ export function getBySource<T>(

export function getByProps<T, P extends PropertyKey = keyof T>(
props: P[],
options?: { all?: false; raw?: false },
options?: { all?: false; raw?: false } & ByPropsOptions,
): T | undefined;
export function getByProps<T, P extends PropertyKey = keyof T>(
props: P[],
options: { all: true; raw?: false },
options: { all: true; raw?: false } & ByPropsOptions,
): T[];
export function getByProps<T, P extends PropertyKey = keyof T>(
props: P[],
options: { all?: false; raw: true },
options: { all?: false; raw: true } & ByPropsOptions,
): RawModule<T> | undefined;
export function getByProps<T, P extends PropertyKey = keyof T>(
props: P[],
options: { all: true; raw: true },
options: { all: true; raw: true } & ByPropsOptions,
): Array<RawModule<T>>;
export function getByProps<T, P extends PropertyKey = keyof T>(
props: P[],
options?: { all: true; raw?: boolean },
options?: { all: true; raw?: boolean } & ByPropsOptions,
): T[] | Array<RawModule<T>>;
export function getByProps<T, P extends PropertyKey = keyof T>(
props: P[],
options: { all?: false; raw?: boolean },
options: { all?: false; raw?: boolean } & ByPropsOptions,
): T | RawModule<T> | undefined;
export function getByProps<T, P extends PropertyKey = keyof T>(
props: P[],
options: { all?: boolean; raw: true },
options: { all?: boolean; raw: true } & ByPropsOptions,
): RawModule<T> | Array<RawModule<T>> | undefined;
export function getByProps<T, P extends PropertyKey = keyof T>(
props: P,
options: { all?: boolean; raw?: false },
props: P[],
options: { all?: boolean; raw?: false } & ByPropsOptions,
): T | T[] | undefined;
export function getByProps<T, P extends PropertyKey = keyof T>(
props: P[],
options?: { all?: boolean; raw?: boolean },
options?: { all?: boolean; raw?: boolean } & ByPropsOptions,
): T | T[] | RawModule<T> | Array<RawModule<T>> | undefined;
export function getByProps<T, P extends PropertyKey[] = Array<keyof T>>(...props: P): T | undefined;

Expand All @@ -103,41 +103,42 @@ export function getByProps<T, P extends PropertyKey[] = Array<keyof T>>(...props
* @see {@link getModule}
*/
export function getByProps<T, P extends PropertyKey = keyof T>(
...args: [P[], GetModuleOptions] | P[]
...args: [P[], GetModuleOptions & ByPropsOptions] | P[]
): T | T[] | RawModule<T> | Array<RawModule<T>> | undefined {
const props = (typeof args[0] === "string" ? args : args[0]) as P[];
const raw = typeof args[0] === "string" ? false : (args[1] as GetModuleOptions)?.raw;

const byPrototype =
typeof args[0] === "string" ? false : (args[1] as ByPropsOptions)?.byPrototype;
const result =
typeof args.at(-1) === "object"
? getModule<T>(filters.byProps(...props), args.at(-1) as GetModuleOptions)
: getModule<T>(filters.byProps(...props));
? getModule<T>(filters.byProps(...props, { byPrototype }), args.at(-1) as GetModuleOptions)
: getModule<T>(filters.byProps(...props, { byPrototype }));

if (raw || typeof result === "undefined") {
return result as RawModule<T> | undefined;
}

if (result instanceof Array) {
// @ts-expect-error TypeScript isn't going to infer types based on the raw variable, so this is fine
return result.map((m) => getExportsForProps(m, props));
return result.map((m) => getExportsForProps(m, props, { byPrototype }));
}

return getExportsForProps<T, P>(result, props);
return getExportsForProps<T, P>(result, props, { byPrototype });
}

// Wait for props

export function waitForProps<T, P extends PropertyKey = keyof T>(
props: P[],
options: WaitForOptions & { raw?: false },
options: WaitForOptions & ByPropsOptions & { raw?: false },
): Promise<T>;
export function waitForProps<T, P extends PropertyKey = keyof T>(
props: P[],
options: WaitForOptions & { raw: true },
options: WaitForOptions & ByPropsOptions & { raw: true },
): Promise<RawModule<T>>;
export function waitForProps<T, P extends PropertyKey = keyof T>(
props: P[],
options?: WaitForOptions,
options?: WaitForOptions & ByPropsOptions,
): Promise<T | RawModule<T>>;
export function waitForProps<T, P extends PropertyKey = keyof T>(...props: P[]): Promise<T>;

Expand All @@ -148,21 +149,22 @@ export function waitForProps<T, P extends PropertyKey = keyof T>(...props: P[]):
* @see {@link waitForModule}
*/
export async function waitForProps<T, P extends PropertyKey = keyof T>(
...args: [P[], WaitForOptions] | P[]
...args: [P[], WaitForOptions & ByPropsOptions] | P[]
): Promise<T | RawModule<T>> {
const props = (typeof args[0] === "string" ? args : args[0]) as P[];
const raw = typeof args[0] === "string" ? false : (args[1] as WaitForOptions)?.raw;

const byPrototype =
typeof args[0] === "string" ? false : (args[1] as ByPropsOptions)?.byPrototype;
const result = await (typeof args.at(-1) === "object"
? waitForModule<T>(filters.byProps(...props), args.at(-1) as WaitForOptions)
: waitForModule<T>(filters.byProps(...props)));
? waitForModule<T>(filters.byProps(...props, { byPrototype }), args.at(-1) as WaitForOptions)
: waitForModule<T>(filters.byProps(...props, { byPrototype })));

if (raw) {
return result as RawModule<T>;
}

// We know this will always exist since filters.byProps will always return a module that has the props
return getExportsForProps<T, P>(result as T, props)!;
return getExportsForProps<T, P>(result as T, props, { byPrototype })!;
}

// Get by value
Expand Down Expand Up @@ -246,7 +248,7 @@ export function getByValue<T>(
return getModule<T>(filters.byValue(match), options);
}

export function getByStoreName<T extends Store>(name: string): T | undefined {
export function getByStoreName<T>(name: string): (T & Store) | undefined {
const stores = Flux.Store.getAll();
return stores.find((store) => store.getName() === name) as T | undefined;
return stores.find((store) => store.getName() === name) as (T & Store) | undefined;
}
5 changes: 5 additions & 0 deletions src/types/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,8 @@ export interface WaitForOptions {
/** If nothing is found after this delay (ms), stop and throw an error. */
timeout?: number;
}

export interface ByPropsOptions {
/** searches inside the prototype if getting by props */
byPrototype?: boolean;
}
Loading