feat(lib/modules/finders/filters)!: improve filters
- Filter generators now expose a `keyFor` method to generate filter keys.
- Dependency filtering is now more usable
- Destructure helpers from `byDependencies` instead:
- `looseDeps` -> `byDependencies.loose`
- `relativeDep` -> `byDependencies.relative`
- Nested dependencies check can now be done with relative checks via `byDependencies.relative.withDependencies`
- Dependency filtering is now more performant
- Relative dependencies are done by bit manipulation instead
- Reverted from stack system to recursion
- Skip relative dependencies resolution (there is no need for it)
- Filter keys for `byDependencies` fixed
This commit is contained in:
parent
63b7c6c648
commit
d5fa40fabd
11 changed files with 256 additions and 198 deletions
|
|
@ -3,7 +3,6 @@ import {
|
|||
byProps,
|
||||
every,
|
||||
preferExports,
|
||||
relativeDep,
|
||||
withoutProps,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import {
|
||||
|
|
@ -19,6 +18,8 @@ import { proxify } from '@revenge-mod/utils/proxy'
|
|||
import { DispatcherModuleId } from './common/flux'
|
||||
import type { DiscordModules } from './types'
|
||||
|
||||
const { relative } = byDependencies
|
||||
|
||||
export let ActionSheetActionCreators: DiscordModules.Actions.ActionSheetActionCreators =
|
||||
proxify(
|
||||
() => {
|
||||
|
|
@ -33,8 +34,8 @@ export let ActionSheetActionCreators: DiscordModules.Actions.ActionSheetActionCr
|
|||
ReactModuleId,
|
||||
ReactJsxRuntimeModuleId,
|
||||
DispatcherModuleId,
|
||||
relativeDep(1),
|
||||
relativeDep(2),
|
||||
relative(1),
|
||||
relative(2),
|
||||
2,
|
||||
]),
|
||||
),
|
||||
|
|
@ -59,7 +60,7 @@ export let AlertActionCreators: DiscordModules.Actions.AlertActionCreators =
|
|||
'openAlert',
|
||||
),
|
||||
byDependencies([
|
||||
[[], relativeDep(1)],
|
||||
[[], relative(1)],
|
||||
[ReactNativeModuleId, 2],
|
||||
2,
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import {
|
|||
byProps,
|
||||
createFilterGenerator,
|
||||
preferExports,
|
||||
relativeDep,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
|
||||
import { waitForModules } from '@revenge-mod/modules/finders/wait'
|
||||
|
|
@ -12,13 +11,15 @@ import type { Filter } from '@revenge-mod/modules/finders/filters'
|
|||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { DiscordModules } from '../types'
|
||||
|
||||
const { relative } = byDependencies
|
||||
|
||||
// ../discord_common/js/packages/flux
|
||||
|
||||
export const [Dispatcher, DispatcherModuleId] = lookupModule(
|
||||
preferExports(
|
||||
byProps<DiscordModules.Flux.Dispatcher>('_interceptors'),
|
||||
byDependencies([
|
||||
relativeDep(1),
|
||||
relative(1),
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ import {
|
|||
byDependencies,
|
||||
byName,
|
||||
byProps,
|
||||
every,
|
||||
looseDeps,
|
||||
preferExports,
|
||||
relativeDep,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
|
||||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
|
|
@ -15,6 +12,8 @@ import type { DiscordModules } from '../types'
|
|||
export { AppStartPerformance } from '../preinit'
|
||||
export * as Flux from './flux'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
|
||||
// ../discord_common/js/shared/Logger.tsx
|
||||
export const [Logger] = lookupModule(
|
||||
byName<DiscordModules.Logger>('Logger'),
|
||||
|
|
@ -33,22 +32,18 @@ export let Constants: DiscordModules.Constants = proxify(
|
|||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<DiscordModules.Constants>('ME'),
|
||||
every(
|
||||
// ID: 1236
|
||||
// Deps: 26, 1237, 1238, 1239, 1240, ...
|
||||
// Every module has only one dependency, which is the import tracker
|
||||
byDependencies(
|
||||
looseDeps([
|
||||
undefined,
|
||||
relativeDep(1),
|
||||
relativeDep(2),
|
||||
relativeDep(3),
|
||||
relativeDep(4),
|
||||
]),
|
||||
),
|
||||
byDependencies(
|
||||
looseDeps(new Array(5).fill(OnlyImportTrackerDep)),
|
||||
),
|
||||
|
||||
// ID: 1236
|
||||
// Deps: 26, 1237, 1238, 1239, 1240, ...
|
||||
// Every module has only one dependency, which is the import tracker
|
||||
byDependencies(
|
||||
loose([
|
||||
undefined,
|
||||
relative.withDependencies(OnlyImportTrackerDep, 1),
|
||||
relative.withDependencies(OnlyImportTrackerDep, 2),
|
||||
relative.withDependencies(OnlyImportTrackerDep, 3),
|
||||
relative.withDependencies(OnlyImportTrackerDep, 4),
|
||||
]),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ import {
|
|||
byDependencies,
|
||||
byProps,
|
||||
bySingleProp,
|
||||
looseDeps,
|
||||
preferExports,
|
||||
relativeDep,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
|
||||
import {
|
||||
|
|
@ -15,6 +13,8 @@ import {
|
|||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
import type { DiscordModules } from './types'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
|
||||
// design/native.tsx
|
||||
export let Design: Design = proxify(
|
||||
() => {
|
||||
|
|
@ -24,11 +24,11 @@ export let Design: Design = proxify(
|
|||
preferExports(
|
||||
byProps<Design>('TableRow', 'Button'),
|
||||
byDependencies(
|
||||
looseDeps([
|
||||
relativeDep(1),
|
||||
loose([
|
||||
relative(1),
|
||||
undefined,
|
||||
relativeDep(2),
|
||||
relativeDep(3),
|
||||
relative(2),
|
||||
relative(3),
|
||||
2,
|
||||
]),
|
||||
),
|
||||
|
|
@ -58,7 +58,7 @@ export let FormSwitch: DiscordModules.Components.FormSwitch = proxify(() => {
|
|||
'FormSwitch',
|
||||
),
|
||||
byDependencies([
|
||||
[relativeDep(1), relativeDep(2), undefined, relativeDep(3)],
|
||||
[relative(1), relative(2), undefined, relative(3)],
|
||||
ReactModuleId,
|
||||
ReactNativeModuleId,
|
||||
ReactJsxRuntimeModuleId,
|
||||
|
|
@ -69,8 +69,8 @@ export let FormSwitch: DiscordModules.Components.FormSwitch = proxify(() => {
|
|||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
relativeDep(1),
|
||||
relativeDep(2),
|
||||
relative(1),
|
||||
relative(2),
|
||||
2,
|
||||
]),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
every,
|
||||
looseDeps,
|
||||
preferExports,
|
||||
relativeDep,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
|
||||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
import type { NavigationContainerRef } from '@react-navigation/core'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
|
||||
export interface RootNavigationRef {
|
||||
getRootNavigationRef<
|
||||
T extends object = Record<string, unknown>,
|
||||
|
|
@ -21,15 +20,10 @@ export let RootNavigationRef: RootNavigationRef = proxify(
|
|||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<RootNavigationRef>('getRootNavigationRef'),
|
||||
every(
|
||||
byDependencies([
|
||||
looseDeps([
|
||||
[],
|
||||
looseDeps([relativeDep(1), relativeDep(2)]),
|
||||
]),
|
||||
2,
|
||||
]),
|
||||
),
|
||||
byDependencies([
|
||||
loose([[], loose([relative(1), relative(2)])]),
|
||||
2,
|
||||
]),
|
||||
),
|
||||
{
|
||||
includeUninitialized: true,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
looseDeps,
|
||||
preferExports,
|
||||
relativeDep,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
|
||||
import {
|
||||
|
|
@ -14,6 +12,8 @@ import {
|
|||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
import type { DiscordModules } from '../../types'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
|
||||
export type SettingListRenderer =
|
||||
DiscordModules.Modules.Settings.SettingListRenderer
|
||||
|
||||
|
|
@ -23,11 +23,11 @@ export let SettingListRenderer: SettingListRenderer = proxify(
|
|||
preferExports(
|
||||
byProps<SettingListRenderer>('SettingsList'),
|
||||
byDependencies(
|
||||
looseDeps([
|
||||
loose([
|
||||
ReactModuleId,
|
||||
ReactNativeModuleId,
|
||||
relativeDep(1),
|
||||
relativeDep(2),
|
||||
relative(1),
|
||||
relative(2),
|
||||
undefined,
|
||||
ReactJsxRuntimeModuleId,
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -2,17 +2,18 @@ import {
|
|||
byDependencies,
|
||||
byProps,
|
||||
preferExports,
|
||||
relativeDep,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { DiscordModules } from './types'
|
||||
|
||||
const { relative } = byDependencies
|
||||
|
||||
// ../discord_common/js/shared/AppStartPerformance.tsx
|
||||
export const [AppStartPerformance] = lookupModule(
|
||||
preferExports(
|
||||
byProps<DiscordModules.AppStartPerformance>('markAndLog'),
|
||||
byDependencies([relativeDep(-1), undefined, undefined, 2]),
|
||||
byDependencies([relative(-1), undefined, undefined, 2]),
|
||||
),
|
||||
{
|
||||
includeUninitialized: true,
|
||||
|
|
|
|||
6
lib/externals/src/browserify.ts
vendored
6
lib/externals/src/browserify.ts
vendored
|
|
@ -1,20 +1,20 @@
|
|||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
looseDeps,
|
||||
preferExports,
|
||||
relativeDep,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
|
||||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
|
||||
export let nodeUtil: typeof import('node:util') = proxify(
|
||||
() => {
|
||||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<typeof nodeUtil>('inspect'),
|
||||
byDependencies([
|
||||
looseDeps([relativeDep(2, true), relativeDep(4, true)]),
|
||||
loose([relative(2, true), relative(4, true)]),
|
||||
[],
|
||||
[],
|
||||
]),
|
||||
|
|
|
|||
21
lib/externals/src/react-native-clipboard.ts
vendored
21
lib/externals/src/react-native-clipboard.ts
vendored
|
|
@ -1,14 +1,14 @@
|
|||
import {
|
||||
byDependencies,
|
||||
byProps,
|
||||
every,
|
||||
preferExports,
|
||||
relativeDep,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
|
||||
import { ReactModuleId, ReactNativeModuleId } from '@revenge-mod/react'
|
||||
import { destructure, proxify } from '@revenge-mod/utils/proxy'
|
||||
|
||||
const { relative } = byDependencies
|
||||
|
||||
let ClipboardModule: typeof import('@react-native-clipboard/clipboard') =
|
||||
proxify(
|
||||
() => {
|
||||
|
|
@ -26,13 +26,16 @@ let ClipboardModule: typeof import('@react-native-clipboard/clipboard') =
|
|||
const [module] = lookupModule(
|
||||
preferExports(
|
||||
byProps<typeof ClipboardModule>('useClipboard'),
|
||||
every(
|
||||
byDependencies([relativeDep(1), relativeDep(2)]),
|
||||
byDependencies([
|
||||
[ReactModuleId, relativeDep(2, true)],
|
||||
[ReactNativeModuleId, relativeDep(3, true)],
|
||||
]),
|
||||
),
|
||||
byDependencies([
|
||||
relative.withDependencies(
|
||||
[ReactModuleId, relative(2, true)],
|
||||
1,
|
||||
),
|
||||
relative.withDependencies(
|
||||
[ReactNativeModuleId, relative(3, true)],
|
||||
2,
|
||||
),
|
||||
]),
|
||||
),
|
||||
{
|
||||
includeUninitialized: true,
|
||||
|
|
|
|||
31
lib/externals/src/react-navigation.ts
vendored
31
lib/externals/src/react-navigation.ts
vendored
|
|
@ -2,15 +2,14 @@ import {
|
|||
byDependencies,
|
||||
byName,
|
||||
byProps,
|
||||
every,
|
||||
looseDeps,
|
||||
preferExports,
|
||||
relativeDep,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
|
||||
import { ReactJsxRuntimeModuleId, ReactModuleId } from '@revenge-mod/react'
|
||||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
|
||||
const { loose, relative } = byDependencies
|
||||
|
||||
const [, _createClassModuleId] = lookupModule(byName('_createClass'))
|
||||
const [, _classCallCheckModuleId] = lookupModule(byName('_classCallCheck'))
|
||||
|
||||
|
|
@ -21,9 +20,9 @@ export let ReactNavigationNative: typeof import('@react-navigation/native') =
|
|||
preferExports(
|
||||
byProps<typeof ReactNavigationNative>('useLinkTo'),
|
||||
byDependencies(
|
||||
looseDeps([
|
||||
loose([
|
||||
[],
|
||||
looseDeps([
|
||||
loose([
|
||||
[_createClassModuleId, _classCallCheckModuleId],
|
||||
]),
|
||||
]),
|
||||
|
|
@ -49,23 +48,19 @@ export let ReactNavigationStack: typeof import('@react-navigation/stack') =
|
|||
byProps<typeof ReactNavigationStack>(
|
||||
'createStackNavigator',
|
||||
),
|
||||
every(
|
||||
byDependencies(
|
||||
looseDeps([
|
||||
looseDeps([
|
||||
byDependencies(
|
||||
loose([
|
||||
relative.withDependencies(
|
||||
loose([
|
||||
[[]],
|
||||
ReactModuleId,
|
||||
ReactJsxRuntimeModuleId,
|
||||
]),
|
||||
]),
|
||||
),
|
||||
byDependencies(
|
||||
looseDeps([
|
||||
relativeDep(1),
|
||||
undefined,
|
||||
relativeDep(3),
|
||||
]),
|
||||
),
|
||||
1,
|
||||
),
|
||||
undefined,
|
||||
relative(3),
|
||||
]),
|
||||
),
|
||||
),
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ export interface Filter<
|
|||
key: string
|
||||
}
|
||||
|
||||
// @ts-expect-error: This works perfectly fine!
|
||||
export interface FilterGenerator<G extends (...args: any[]) => Filter>
|
||||
extends G {
|
||||
keyFor: (args: Parameters<G>) => string
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a filter generator.
|
||||
*
|
||||
|
|
@ -54,29 +60,33 @@ export function createFilterGenerator<A extends any[]>(
|
|||
exports: Metro.ModuleExports,
|
||||
) => boolean,
|
||||
keyFor: (args: A) => string,
|
||||
): (...args: A) => Filter<object, true>
|
||||
): FilterGenerator<(...args: A) => Filter<any, true>>
|
||||
|
||||
export function createFilterGenerator<A extends any[]>(
|
||||
filter: (args: A, id: Metro.ModuleID) => boolean,
|
||||
keyFor: (args: A) => string,
|
||||
): (...args: A) => Filter<object, false>
|
||||
): FilterGenerator<(...args: A) => Filter<any, false>>
|
||||
|
||||
export function createFilterGenerator<A extends any[]>(
|
||||
f: (args: A, id: Metro.ModuleID, exports?: Metro.ModuleExports) => boolean,
|
||||
keyFor: (args: A) => string,
|
||||
): (...args: any[]) => Filter {
|
||||
return (...args: A) => {
|
||||
): FilterGenerator<(...args: A) => Filter> {
|
||||
const generator = (...args: A) => {
|
||||
const filter = (id: Metro.ModuleID, exports?: Metro.ModuleExports) =>
|
||||
f(args, id, exports)
|
||||
filter.key = keyFor(args)
|
||||
return filter
|
||||
}
|
||||
generator.keyFor = keyFor
|
||||
return generator
|
||||
}
|
||||
|
||||
export type ByProps = <T extends Record<string, any> = Record<string, any>>(
|
||||
prop: keyof T,
|
||||
...props: Array<keyof T>
|
||||
) => Filter<T, true>
|
||||
export type ByProps = FilterGenerator<
|
||||
<T extends Record<string, any> = Record<string, any>>(
|
||||
prop: keyof T,
|
||||
...props: Array<keyof T>
|
||||
) => Filter<T, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having all of the specified properties.
|
||||
|
|
@ -106,10 +116,12 @@ export const byProps = createFilterGenerator<Parameters<ByProps>>(
|
|||
props => `revenge.props(${props.join(',')})`,
|
||||
) as ByProps
|
||||
|
||||
export type WithoutProps = <T extends Record<string, any>>(
|
||||
prop: string,
|
||||
...props: string[]
|
||||
) => Filter<T, true>
|
||||
export type WithoutProps = FilterGenerator<
|
||||
<T extends Record<string, any>>(
|
||||
prop: string,
|
||||
...props: string[]
|
||||
) => Filter<T, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having none of the specified properties.
|
||||
|
|
@ -127,9 +139,9 @@ export const withoutProps = createFilterGenerator<Parameters<WithoutProps>>(
|
|||
props => `revenge.withoutProps(${props.join(',')})`,
|
||||
) as WithoutProps
|
||||
|
||||
export type BySingleProp = <T extends Record<string, any>>(
|
||||
prop: keyof T,
|
||||
) => Filter<T, true>
|
||||
export type BySingleProp = FilterGenerator<
|
||||
<T extends Record<string, any>>(prop: keyof T) => Filter<T, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having only the specified property.
|
||||
|
|
@ -151,9 +163,9 @@ export const bySingleProp = createFilterGenerator<Parameters<BySingleProp>>(
|
|||
([prop]) => `revenge.singleProp(${prop})`,
|
||||
) as BySingleProp
|
||||
|
||||
export type ByName = <T extends object = object>(
|
||||
name: string,
|
||||
) => Filter<T, true>
|
||||
export type ByName = FilterGenerator<
|
||||
<T extends object = object>(name: string) => Filter<T, true>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules by their exports having the specified name.
|
||||
|
|
@ -190,57 +202,63 @@ export const byName = createFilterGenerator<Parameters<ByName>>(
|
|||
([name]) => `revenge.name(${name})`,
|
||||
) as ByName
|
||||
|
||||
export interface RelativeDependency {
|
||||
valueOf(): Metro.ModuleID
|
||||
r?: boolean
|
||||
}
|
||||
|
||||
export interface ComparableDependencyMap
|
||||
extends Array<
|
||||
| Metro.ModuleID
|
||||
| RelativeDependency
|
||||
| undefined
|
||||
| ComparableDependencyMap
|
||||
Metro.ModuleID | number | undefined | ComparableDependencyMap
|
||||
> {
|
||||
l?: boolean
|
||||
r?: number
|
||||
}
|
||||
|
||||
type ComputedComparableDependencyMap = Exclude<
|
||||
ComparableDependencyMap,
|
||||
Array<RelativeDependency>
|
||||
>
|
||||
|
||||
type ByDependencies = <T>(deps: ComparableDependencyMap) => Filter<T, false>
|
||||
type ByDependencies = FilterGenerator<
|
||||
<T>(deps: ComparableDependencyMap) => Filter<T, false>
|
||||
> & {
|
||||
loose: typeof loose
|
||||
relative: typeof relative
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter modules by their dependency map.
|
||||
*
|
||||
* @param deps The dependency map to check for, can be a sparse array or have `undefined` to be any dependency ("dynamic"). **Order and size matters!**
|
||||
* @see {@link looseDeps} to make comparisons less strict.
|
||||
*
|
||||
* To do proper fingerprinting for modules:
|
||||
* @see {@link byDependencies.loose} to loosen the checks.
|
||||
* @see {@link byDependencies.relative} to compare dependencies relatively.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { loose, relative } = byDependencies
|
||||
*
|
||||
* // Logger's module ID is 5
|
||||
* // It has 3 dependencies [4, ?, 2]
|
||||
*
|
||||
* const [Logger] = lookupModule(byDependencies([4, undefined, 2]))
|
||||
* // or
|
||||
* const [Logger] = lookupModule(byDependencies([4, , 2]))
|
||||
* // or with relative dependencies
|
||||
* // If Logger's module ID is 20, [19, ..., ..., 2] would match:
|
||||
* const [Logger] = lookupModule(byDependencies([relativeDep(-1), , 2]))
|
||||
* // or with nested dependencies
|
||||
* // The last dependency would need to have zero dependencies:
|
||||
* const [Logger] = lookupModule(byDependencies([4, , []]))
|
||||
* // or with loose dependencies
|
||||
* // Module having 4 dependencies [4, ...], [4, ..., ...], [4, ..., ..., ...] etc. would match:
|
||||
* const [SomeOtherModule] = lookupModule(byDependencies(looseDeps([4, ,])))
|
||||
*
|
||||
* // Relative dependencies
|
||||
* const [Logger] = lookupModule(byDependencies([relative(-1), undefined, 2]))
|
||||
*
|
||||
* // Nested dependencies
|
||||
* // The last dependency (module ID 2) would need to have zero dependencies:
|
||||
* const [Logger] = lookupModule(byDependencies([4, undefined, []]))
|
||||
*
|
||||
* // Loose dependencies
|
||||
* // Module having these dependencies: [4, ...], [4, ..., ...], [4, ..., ..., ...], etc. would match:
|
||||
* const [SomeOtherModule] = lookupModule(byDependencies(loose([4])))
|
||||
* ```
|
||||
*/
|
||||
export const byDependencies = createFilterGenerator<Parameters<ByDependencies>>(
|
||||
([deps], id) => compareDeps(id, deps, deps.l),
|
||||
deps => `revenge.deps(${genDepsKey(deps)})`,
|
||||
([deps], id) => depCompare(getModuleDependencies(id)!, deps, id, id),
|
||||
deps => `revenge.deps(${depGenFilterKey(deps)})`,
|
||||
) as ByDependencies
|
||||
|
||||
byDependencies.loose = loose
|
||||
byDependencies.relative = relative
|
||||
|
||||
/**
|
||||
* Make this set of comparable dependencies as loose, to be used with {@link byDependencies}.
|
||||
* Make this set of comparable dependencies as loose.
|
||||
*
|
||||
* Making a dependency loose skips the exact length check, but the order of the set dependencies still matters.
|
||||
* If you mark an index as dynamic, the same index must also be present in the other map during comparison to pass.
|
||||
|
|
@ -248,93 +266,139 @@ export const byDependencies = createFilterGenerator<Parameters<ByDependencies>>(
|
|||
* @param deps The dependency map to make loose. This permanently modifies the array.
|
||||
* @returns The modified dependency map.
|
||||
*/
|
||||
export function looseDeps(deps: ComparableDependencyMap) {
|
||||
function loose(deps: ComparableDependencyMap) {
|
||||
deps.l = true
|
||||
return deps
|
||||
}
|
||||
|
||||
const RelativeSignBit = 1 << 30
|
||||
const RelativeBit = 1 << 29
|
||||
const RelativeRootBit = 1 << 28
|
||||
const RelativeBitMask = ~(RelativeSignBit | RelativeBit | RelativeRootBit)
|
||||
|
||||
/**
|
||||
* Marks this dependency to compare relatively to the module ID being compared.
|
||||
*
|
||||
* @param id The dependency ID to mark as relative.
|
||||
* @param root Whether this dependency should be relatively to the root module ID. Useless for single-depth comparisons.
|
||||
* @param root Marks this dependency to compare relatively to the root (returning) module ID being compared. Useful for nested comparisons where you want to compare by the root module ID instead of the parent's module ID of the nested dependency.
|
||||
*/
|
||||
export function relativeDep(
|
||||
id: Metro.ModuleID,
|
||||
root?: boolean,
|
||||
): RelativeDependency {
|
||||
const o = Object(id) as RelativeDependency
|
||||
if (root) o.r = true
|
||||
return o
|
||||
function relative(id: Metro.ModuleID, root?: boolean) {
|
||||
id = (id < 0 ? -id | RelativeSignBit : id) | RelativeBit
|
||||
if (root) id |= RelativeRootBit
|
||||
return id
|
||||
}
|
||||
|
||||
function compareDeps(
|
||||
rootOf: Metro.ModuleID,
|
||||
compare: ComparableDependencyMap,
|
||||
loose = false,
|
||||
/**
|
||||
* Marks this dependency to compare relatively to the module ID being compared, with an additional dependencies check.
|
||||
*
|
||||
* @param deps The dependency map to add the relative dependency to. This permanently modifies the array.
|
||||
* @param id The dependency ID to mark as relative.
|
||||
* @param root Whether to use {@link relative.toRoot} instead of {@link relative}. Defaults to `false`.
|
||||
* @returns The modified dependency map.
|
||||
*
|
||||
* @see {@link byDependencies}
|
||||
* @see {@link relative}
|
||||
* @see {@link relative.toRoot}
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { relative } = byDependencies
|
||||
*
|
||||
* // This filter will match modules having one dependency that is its module ID + 1
|
||||
* // And module ID + 1 would have exactly two dependencies: [Any, 2]
|
||||
* byDependencies(
|
||||
* relative.withDependencies(
|
||||
* [undefined, 2],
|
||||
* 1, // Always the next module to the one being compared
|
||||
* true, // The module ID being compared matches the returning (root) module ID
|
||||
* )
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
relative.withDependencies = (
|
||||
deps: ComparableDependencyMap,
|
||||
id: Metro.ModuleID,
|
||||
root?: boolean,
|
||||
) => {
|
||||
deps.r = relative(id, root)
|
||||
return deps
|
||||
}
|
||||
|
||||
function depCompare(
|
||||
a: Metro.ModuleID[],
|
||||
b: ComparableDependencyMap,
|
||||
root: Metro.ModuleID,
|
||||
parent: Metro.ModuleID,
|
||||
): boolean {
|
||||
const stack: Array<
|
||||
[
|
||||
of: Metro.ModuleID,
|
||||
deps: Metro.ModuleID[],
|
||||
compare: ComparableDependencyMap,
|
||||
loose?: boolean,
|
||||
]
|
||||
> = []
|
||||
if (b.l ? a.length < b.length : a.length !== b.length) return false
|
||||
|
||||
stack.push([rootOf, getModuleDependencies(rootOf)!, compare, loose])
|
||||
for (let i = 0; i < b.length; i++) {
|
||||
const compare = b[i]
|
||||
// Skip dynamic
|
||||
if (compare === undefined) continue
|
||||
|
||||
while (stack.length) {
|
||||
const [of, deps, compare, loose] = stack.pop()!
|
||||
if (
|
||||
loose
|
||||
? deps.length < compare.length
|
||||
: deps.length !== compare.length
|
||||
)
|
||||
return false
|
||||
const id = a[i]
|
||||
|
||||
const ccmp: ComputedComparableDependencyMap = []
|
||||
for (let i = 0; i < compare.length; i++) {
|
||||
const cmp = compare[i]
|
||||
if (cmp === undefined) continue
|
||||
if (Array.isArray(compare)) {
|
||||
// relative.withDependencies?
|
||||
if (compare.r && !depShallowCompare(compare.r, id, root, id))
|
||||
return false
|
||||
|
||||
if (Array.isArray(cmp)) ccmp[i] = cmp
|
||||
else if (typeof cmp === 'object')
|
||||
ccmp[i] = cmp.r ? rootOf + cmp.valueOf() : of + cmp.valueOf()
|
||||
else ccmp[i] = cmp
|
||||
}
|
||||
if (depCompare(getModuleDependencies(id)!, compare, root, id))
|
||||
continue
|
||||
} else if (depShallowCompare(compare, id, root, parent)) continue
|
||||
|
||||
for (let i = 0; i < ccmp.length; i++) {
|
||||
const orig = deps[i]
|
||||
const cmp = ccmp[i]
|
||||
if (cmp === undefined) continue
|
||||
|
||||
if (Array.isArray(cmp)) {
|
||||
const dependencies = getModuleDependencies(orig)
|
||||
if (!dependencies) return false
|
||||
stack.push([orig, dependencies, cmp, cmp.l])
|
||||
} else if (cmp !== orig) return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function genDepsKey(deps: ComparableDependencyMap): string {
|
||||
function depShallowCompare(
|
||||
compare: number,
|
||||
id: Metro.ModuleID,
|
||||
root: Metro.ModuleID,
|
||||
parent: Metro.ModuleID,
|
||||
) {
|
||||
// relative?
|
||||
if (compare & RelativeBit)
|
||||
compare =
|
||||
(compare & RelativeRootBit ? root : parent) +
|
||||
depGetRelMagnitude(compare)
|
||||
|
||||
return compare === id
|
||||
}
|
||||
|
||||
function depGetRelMagnitude(dep: number) {
|
||||
const rootRelative = dep & RelativeSignBit
|
||||
dep = dep & RelativeBitMask
|
||||
if (rootRelative) dep = -dep
|
||||
return dep
|
||||
}
|
||||
|
||||
function depGenFilterKey(deps: ComparableDependencyMap): string {
|
||||
let key = ''
|
||||
|
||||
for (const dep of deps)
|
||||
for (let i = 0; i < deps.length; i++) {
|
||||
const dep = deps[i]
|
||||
|
||||
if (dep === undefined) key += ','
|
||||
else if (Array.isArray(dep)) {
|
||||
if (dep.l) key += '#'
|
||||
key += `[${genDepsKey(dep)}],`
|
||||
} else
|
||||
key += `${(dep as RelativeDependency).r ? `r${dep.valueOf()}` : dep.valueOf()},`
|
||||
key += `[${depGenFilterKey(dep)}],`
|
||||
} else {
|
||||
if (dep & RelativeBit) {
|
||||
const magnitude = depGetRelMagnitude(dep)
|
||||
const prefix = dep & RelativeRootBit ? '~' : '^'
|
||||
key += `${prefix}${magnitude},`
|
||||
} else key += `${dep},`
|
||||
}
|
||||
}
|
||||
|
||||
return key.substring(0, key.length - 1)
|
||||
}
|
||||
|
||||
export type Every = {
|
||||
export type Every = FilterGenerator<{
|
||||
<F1 extends Filter, F2 extends Filter>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
|
|
@ -354,7 +418,7 @@ export type Every = {
|
|||
>
|
||||
>
|
||||
(...filters: Filter[]): Filter
|
||||
}
|
||||
}>
|
||||
|
||||
/**
|
||||
* Combines multiple filters into one, returning true if **every** filter matches.
|
||||
|
|
@ -382,7 +446,7 @@ export const every = createFilterGenerator<[...filters: Filter[]]>(
|
|||
filters => `revenge.every(${filtersToKey(filters)})`,
|
||||
) as Every
|
||||
|
||||
export type Some = {
|
||||
export type Some = FilterGenerator<{
|
||||
<F1 extends Filter, F2 extends Filter>(
|
||||
f1: F1,
|
||||
f2: F2,
|
||||
|
|
@ -401,7 +465,7 @@ export type Some = {
|
|||
| IsFilterWithExports<F3>
|
||||
>
|
||||
(...filters: Filter[]): Filter
|
||||
}
|
||||
}>
|
||||
|
||||
/**
|
||||
* Combines multiple filters into one, returning true if **some** filters match.
|
||||
|
|
@ -431,11 +495,13 @@ function filtersToKey(filters: Filter[]): string {
|
|||
return s.substring(0, s.length - 1)
|
||||
}
|
||||
|
||||
export type ModuleStateAware = <IF extends Filter>(
|
||||
initializedFilter: IF,
|
||||
uninitializedFilter: Filter<any, false>,
|
||||
strict?: boolean,
|
||||
) => Filter<FilterResult<IF>, false>
|
||||
export type ModuleStateAware = FilterGenerator<
|
||||
<IF extends Filter>(
|
||||
initializedFilter: IF,
|
||||
uninitializedFilter: Filter<any, false>,
|
||||
strict?: boolean,
|
||||
) => Filter<FilterResult<IF>, false>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules depending on their initialized state. **Initialized modules with bad exports are skipped.**
|
||||
|
|
@ -470,11 +536,13 @@ export const moduleStateAware = createFilterGenerator<
|
|||
([f1, f2]) => `revenge.moduleStateAware(${f1.key},${f2.key})`,
|
||||
) as ModuleStateAware
|
||||
|
||||
export type PreferExports = <WEF extends Filter>(
|
||||
withExportsFilter: WEF,
|
||||
exportslessFilter: Filter<any, false>,
|
||||
strict?: boolean,
|
||||
) => Filter<FilterResult<WEF>, false>
|
||||
export type PreferExports = FilterGenerator<
|
||||
<WEF extends Filter>(
|
||||
withExportsFilter: WEF,
|
||||
exportslessFilter: Filter<any, false>,
|
||||
strict?: boolean,
|
||||
) => Filter<FilterResult<WEF>, false>
|
||||
>
|
||||
|
||||
/**
|
||||
* Filter modules depending on if their exports are available and filterable.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue