From b6d1cff9f50ad24c9244f8c13cfe510aaae043a0 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 28 Oct 2025 01:01:17 +0700 Subject: [PATCH] feat(lib/modules)!: filter scopes + fixes and improvements # Features - Filters now control what types of modules they see. - Filters can be reinstantiated via `.new()` - Added the `Not` type in @revenge-mod/utils/types # Fixes - Pitfall with `lookupModules` and `withDependencies` without `uninitialize: true` fixed. - Filter type-metadata is now stored in a single object instead of multiple parameters. - Incorrect caching behavior for `lookupModules` where cache always reports not found if you finish the iterator. - Also attempt to report not found when using `lookupModule`. --- lib/assets/src/preinit.ts | 1 - lib/discord/src/actions.ts | 9 - lib/discord/src/common/flux.ts | 3 - lib/discord/src/common/utils.ts | 3 - lib/discord/src/design.ts | 7 - lib/discord/src/flux/stores.ts | 33 +++- lib/discord/src/modules/main_tabs_v2.ts | 3 - lib/discord/src/modules/settings/renderer.ts | 3 - lib/discord/src/preinit.ts | 3 - lib/externals/src/browserify.ts | 3 - lib/externals/src/react-native-clipboard.ts | 3 - lib/externals/src/react-navigation.ts | 6 - lib/externals/src/shopify.ts | 3 - lib/modules/src/finders/_internal.ts | 4 +- lib/modules/src/finders/filters/composite.ts | 32 ++-- lib/modules/src/finders/filters/constants.ts | 47 +++++ lib/modules/src/finders/filters/dynamic.ts | 12 +- lib/modules/src/finders/filters/index.ts | 20 ++- lib/modules/src/finders/filters/utils.ts | 134 +++++++++++---- lib/modules/src/finders/get.ts | 31 ++-- lib/modules/src/finders/lookup.ts | 170 +++++-------------- lib/modules/src/finders/wait.ts | 27 +-- lib/utils/src/discord.ts | 27 +-- lib/utils/src/types.ts | 1 + 24 files changed, 287 insertions(+), 298 deletions(-) diff --git a/lib/assets/src/preinit.ts b/lib/assets/src/preinit.ts index 8d2e585..6b4c71a 100644 --- a/lib/assets/src/preinit.ts +++ b/lib/assets/src/preinit.ts @@ -102,7 +102,6 @@ export let AssetsRegistryModuleId: Metro.ModuleID | undefined export let AssetsRegistry: ReactNative.AssetsRegistry = proxify(() => { for (const [, id] of lookupModules(withDependencies([[]]), { initialize: false, - uninitialized: true, })) { const deps = getModuleDependencies(id)! if (deps.length !== 1) continue diff --git a/lib/discord/src/actions.ts b/lib/discord/src/actions.ts index 4debb6e..3875fda 100644 --- a/lib/discord/src/actions.ts +++ b/lib/discord/src/actions.ts @@ -34,9 +34,6 @@ export let ActionSheetActionCreators: DiscordModules.Actions.ActionSheetActionCr 2, ]), ), - { - uninitialized: true, - }, ) if (module) return (ActionSheetActionCreators = module) @@ -67,9 +64,6 @@ export let AlertActionCreators: DiscordModules.Actions.AlertActionCreators = ]), ), ), - { - uninitialized: true, - }, ) if (module) return (AlertActionCreators = module) @@ -89,9 +83,6 @@ export let ToastActionCreators: DiscordModules.Actions.ToastActionCreators = withProps('open') .and(withoutProps('init')) .and(withDependencies([DispatcherModuleId, 2])), - { - uninitialized: true, - }, ) for (const [module] of generator) diff --git a/lib/discord/src/common/flux.ts b/lib/discord/src/common/flux.ts index 75876c6..dd77e39 100644 --- a/lib/discord/src/common/flux.ts +++ b/lib/discord/src/common/flux.ts @@ -14,7 +14,4 @@ export const [Dispatcher, DispatcherModuleId] = lookupModule( withProps('_interceptors').and( withDependencies([relative(1), null, null, null, null, 2]), ), - { - uninitialized: true, - }, ) as [DiscordModules.Flux.Dispatcher, Metro.ModuleID] diff --git a/lib/discord/src/common/utils.ts b/lib/discord/src/common/utils.ts index bdc389f..bfdfd4e 100644 --- a/lib/discord/src/common/utils.ts +++ b/lib/discord/src/common/utils.ts @@ -26,9 +26,6 @@ export let TypedEventEmitter: typeof DiscordModules.Utils.TypedEventEmitter = 2, ]), ), - { - uninitialized: true, - }, ) if (module) return (TypedEventEmitter = module) diff --git a/lib/discord/src/design.ts b/lib/discord/src/design.ts index 614fbed..06cf538 100644 --- a/lib/discord/src/design.ts +++ b/lib/discord/src/design.ts @@ -52,10 +52,6 @@ export let Design: Design = proxify( ), ) .keyAs('revenge.discord.design.Design'), - - { - uninitialized: true, - }, ) if (module) return (Design = module) @@ -93,9 +89,6 @@ export let FormSwitch: DiscordModules.Components.FormSwitch = proxify(() => { 2, ]), ), - { - uninitialized: true, - }, ) if (module) return (FormSwitch = module.FormSwitch) diff --git a/lib/discord/src/flux/stores.ts b/lib/discord/src/flux/stores.ts index e78baec..d756c19 100644 --- a/lib/discord/src/flux/stores.ts +++ b/lib/discord/src/flux/stores.ts @@ -15,7 +15,10 @@ import { Uncached, } from '../../../modules/src/caches' import { FilterResultFlags } from '../../../modules/src/finders/_internal' -import { FilterFlag } from '../../../modules/src/finders/filters/constants' +import { + FilterFlag, + FilterScopes, +} from '../../../modules/src/finders/filters/constants' import type { Filter, FilterGenerator, @@ -36,9 +39,7 @@ const _stores: Record = {} export const Stores = new Proxy(_stores, { ownKeys: target => Reflect.ownKeys(target), get: (target, prop: string) => - target[prop] ?? - // @ts-expect-error: This always uses cache - lookupModule(withStoreName(prop), { uninitialized: true })[0], + target[prop] ?? lookupModule(withStoreName(prop))[0], }) /** @@ -94,7 +95,14 @@ const FluxStoreLeadingDeps = [ ] export type WithStore = FilterGenerator< - () => Filter, boolean> + () => Filter<{ + Result: DiscordModules.Flux.Store + RequiresExports: boolean + Scopes: [ + typeof FilterScopes.Uninitialized, + typeof FilterScopes.Initialized, + ] + }> > /** @@ -115,10 +123,18 @@ export const withStore = createFilterGenerator( }, () => 'revenge.discord.store', FilterFlag.Dynamic, + FilterScopes.Uninitialized | FilterScopes.Initialized, ) as WithStore export type WithStoreName = FilterGenerator< - (name: string) => Filter, true> + (name: string) => Filter<{ + Result: DiscordModules.Flux.Store + RequiresExports: true + Scopes: [ + typeof FilterScopes.Uninitialized, + typeof FilterScopes.Initialized, + ] + }> > /** @@ -129,6 +145,7 @@ export const withStoreName = createFilterGenerator( exports.getName?.length === 0 && exports.getName() === name, ([name]) => `revenge.discord.storeName(${name})`, FilterFlag.RequiresExports, + FilterScopes.Uninitialized | FilterScopes.Initialized, ) as WithStoreName /// STORE CACHING @@ -146,9 +163,7 @@ waitForModules(withStore(), (store, id) => { if (cache === Uncached) asap(() => { - const lookup = lookupModules(withStore(), { - uninitialized: true, - }) + const lookup = lookupModules(withStore()) while (lookup.next().done); }) diff --git a/lib/discord/src/modules/main_tabs_v2.ts b/lib/discord/src/modules/main_tabs_v2.ts index 8bf4d19..a5a5663 100644 --- a/lib/discord/src/modules/main_tabs_v2.ts +++ b/lib/discord/src/modules/main_tabs_v2.ts @@ -32,9 +32,6 @@ export let RootNavigationRef: RootNavigationRef = proxify( ]), ) .keyAs('revenge.discord.modules.mainTabsV2.RootNavigationRef'), - { - uninitialized: true, - }, ) if (module) return (RootNavigationRef = module) diff --git a/lib/discord/src/modules/settings/renderer.ts b/lib/discord/src/modules/settings/renderer.ts index bcb9467..20c791b 100644 --- a/lib/discord/src/modules/settings/renderer.ts +++ b/lib/discord/src/modules/settings/renderer.ts @@ -32,9 +32,6 @@ export let SettingListRenderer: SettingListRenderer = proxify( ]), ), ), - { - uninitialized: true, - }, ) if (module) return (SettingListRenderer = module) diff --git a/lib/discord/src/preinit.ts b/lib/discord/src/preinit.ts index 9b68e71..72e27c9 100644 --- a/lib/discord/src/preinit.ts +++ b/lib/discord/src/preinit.ts @@ -13,9 +13,6 @@ export const [AppStartPerformance] = lookupModule( withProps('markAndLog').and( withDependencies([relative(-1), null, null, 2]), ), - { - uninitialized: true, - }, ) as [DiscordModules.AppStartPerformance, Metro.ModuleID] AppStartPerformance.mark('👊', 'Pre-init') diff --git a/lib/externals/src/browserify.ts b/lib/externals/src/browserify.ts index d02a823..fe3ece8 100644 --- a/lib/externals/src/browserify.ts +++ b/lib/externals/src/browserify.ts @@ -17,9 +17,6 @@ export let nodeUtil: typeof import('node:util') = proxify( [], ]), ), - { - uninitialized: true, - }, ) if (module) return (nodeUtil = module) diff --git a/lib/externals/src/react-native-clipboard.ts b/lib/externals/src/react-native-clipboard.ts index 4bb0bb4..7a759af 100644 --- a/lib/externals/src/react-native-clipboard.ts +++ b/lib/externals/src/react-native-clipboard.ts @@ -35,9 +35,6 @@ let ClipboardModule: typeof import('@react-native-clipboard/clipboard') = ), ]), ), - { - uninitialized: true, - }, ) if (module) { diff --git a/lib/externals/src/react-navigation.ts b/lib/externals/src/react-navigation.ts index d4ff033..78aa94e 100644 --- a/lib/externals/src/react-navigation.ts +++ b/lib/externals/src/react-navigation.ts @@ -26,9 +26,6 @@ export let ReactNavigationNative: typeof import('@react-navigation/native') = ]), ), ), - { - uninitialized: true, - }, ) if (module) return (ReactNavigationNative = module) @@ -52,9 +49,6 @@ export let ReactNavigationStack: typeof import('@react-navigation/stack') = .keyAs( 'revenge.externals.ReactNavigation.ReactNavigationStack', ), - { - uninitialized: true, - }, ) if (module) return (ReactNavigationStack = module) diff --git a/lib/externals/src/shopify.ts b/lib/externals/src/shopify.ts index 3246054..7c248cd 100644 --- a/lib/externals/src/shopify.ts +++ b/lib/externals/src/shopify.ts @@ -27,9 +27,6 @@ export let FlashList: typeof import('@shopify/flash-list') = proxify( null, ]), ), - { - uninitialized: true, - }, ) if (module) return (FlashList = module) diff --git a/lib/modules/src/finders/_internal.ts b/lib/modules/src/finders/_internal.ts index 365e424..32fbab0 100644 --- a/lib/modules/src/finders/_internal.ts +++ b/lib/modules/src/finders/_internal.ts @@ -16,7 +16,7 @@ export interface RunFilterOptions { */ skipDefault?: boolean /** - * Whether to allow initializing modules to check their exports. + * Whether to allow initializing modules to confirm their exports. * * @default true */ @@ -72,7 +72,7 @@ export const FilterResultFlagToHumanReadable: Record = // If we add more options later on, do NOT forget about // adding them here, and passing them in the lookup* functions export function runFilter( - filter: Filter, + filter: Filter<{ Result: any; RequiresExports: false; Scopes: any[] }>, id: Metro.ModuleID, ): FilterResultFlag | undefined diff --git a/lib/modules/src/finders/filters/composite.ts b/lib/modules/src/finders/filters/composite.ts index 2a3b36a..6bfe13c 100644 --- a/lib/modules/src/finders/filters/composite.ts +++ b/lib/modules/src/finders/filters/composite.ts @@ -1,13 +1,13 @@ import { FilterFlag } from './constants' import { createFilterGenerator } from './utils' import type { Metro } from '@revenge-mod/modules/types' -import type { LogicalAnd } from '@revenge-mod/utils/types' import type { Filter, FilterBase, FilterGenerator, - FilterRequiresExports, - FilterResult, + FilterInfoOf, + MergeFilterInfo, + UnionFilterInfo, } from '.' const compositeHandler = any>( @@ -38,19 +38,19 @@ export type And = FilterGenerator< ( f1: F1, f2: F2, - ) => Filter< - FilterResult & FilterResult, - LogicalAnd, FilterRequiresExports> - > + ) => Filter, FilterInfoOf>> > const andKeyGenerator = ([a, b]: Parameters) => `revenge.and(${a.key},${b.key})` +const andScopesGenerator = ([a, b]: Parameters) => a.scopes | b.scopes + const sameFlagsAnd = createFilterGenerator( ([a, b], id, exports) => a(id, exports) && b(id, exports), andKeyGenerator, ([a]) => a.flags, + andScopesGenerator, ) as And const andFallbackCache = new WeakMap>() @@ -82,6 +82,7 @@ const dynamicFlagsAnd = createFilterGenerator( }, andKeyGenerator, FilterFlag.Dynamic, + andScopesGenerator, ) as And /** @@ -121,6 +122,10 @@ export const and = Object.assign( sameFlagsAnd.flagsFor, dynamicFlagsAnd.flagsFor, ), + defaultScopesFor: compositeArrayHandler( + sameFlagsAnd.defaultScopesFor, + dynamicFlagsAnd.defaultScopesFor, + ), }, ) satisfies And @@ -128,19 +133,19 @@ export type Or = FilterGenerator< ( f1: F1, f2: F2, - ) => Filter< - FilterResult | FilterResult, - LogicalAnd, FilterRequiresExports> - > + ) => Filter, FilterInfoOf>> > const orKeyGenerator = ([a, b]: Parameters) => `revenge.or(${a.key},${b.key})` +const orScopesGenerator = ([a, b]: Parameters) => a.scopes | b.scopes + const sameFlagOr = createFilterGenerator( ([a, b], id, exports) => a(id, exports) || b(id, exports), orKeyGenerator, ([a]) => a.flags, + orScopesGenerator, ) as Or const dynamicFlagOr = createFilterGenerator( @@ -151,6 +156,7 @@ const dynamicFlagOr = createFilterGenerator( }, orKeyGenerator, FilterFlag.Dynamic, + orScopesGenerator, ) as Or /** @@ -185,5 +191,9 @@ export const or = Object.assign( sameFlagOr.flagsFor, dynamicFlagOr.flagsFor, ), + defaultScopesFor: compositeArrayHandler( + sameFlagOr.defaultScopesFor, + dynamicFlagOr.defaultScopesFor, + ), }, ) satisfies Or diff --git a/lib/modules/src/finders/filters/constants.ts b/lib/modules/src/finders/filters/constants.ts index e1ad7fd..5871853 100644 --- a/lib/modules/src/finders/filters/constants.ts +++ b/lib/modules/src/finders/filters/constants.ts @@ -15,3 +15,50 @@ export const FilterFlag = { * @see {@link FilterFlag} */ export type FilterFlag = number + +/** + * Scopes to limit filters to certain module states. + */ +export const FilterScopes = { + /** + * Include all modules (both initialized and uninitialized, including blacklisted). + * This overrides {@link FilterScopes.Uninitialized} and {@link FilterScopes.Initialized}. + */ + All: 1, + /** + * Include uninitialized modules in the search. + */ + Uninitialized: 2, + /** + * Include initialized modules from the search. + */ + Initialized: 4, +} as const + +export type FilterScope = (typeof FilterScopes)[keyof typeof FilterScopes] + +/** + * @see {@link FilterScopes} + */ +export type FilterScopeValue = number + +export interface FilterInfo { + /** + * The result type of the filter. + */ + Result: any + /** + * Whether the filter requires exports to work. + */ + RequiresExports: boolean + /** + * Scopes the filter matches modules in. + */ + Scopes: FilterScope[] +} + +export interface DefaultFilterInfo extends FilterInfo { + Result: any + RequiresExports: boolean + Scopes: FilterScope[] +} diff --git a/lib/modules/src/finders/filters/dynamic.ts b/lib/modules/src/finders/filters/dynamic.ts index cdea86a..28cb739 100644 --- a/lib/modules/src/finders/filters/dynamic.ts +++ b/lib/modules/src/finders/filters/dynamic.ts @@ -1,6 +1,6 @@ import { getCurrentStack } from '@revenge-mod/utils/error' import { getModuleDependencies } from '../../metro/utils' -import { FilterFlag } from './constants' +import { FilterFlag, FilterScopes } from './constants' import { createFilterGenerator } from './utils' import type { Metro } from '../../types' import type { Filter, FilterGenerator } from './utils' @@ -62,13 +62,21 @@ export const withDependencies = createFilterGenerator< ([deps], id) => depCompare(getModuleDependencies(id)!, deps, id, id), deps => `revenge.deps(${depGenFilterKey(deps)})`, FilterFlag.Dynamic, + FilterScopes.Uninitialized | FilterScopes.Initialized, ) as WithDependencies withDependencies.loose = loose withDependencies.relative = relative type WithDependencies = FilterGenerator< - (deps: ComparableDependencyMap) => Filter + (deps: ComparableDependencyMap) => Filter<{ + Result: T + RequiresExports: false + Scopes: [ + typeof FilterScopes.Uninitialized, + typeof FilterScopes.Initialized, + ] + }> > & { loose: typeof loose relative: typeof relative diff --git a/lib/modules/src/finders/filters/index.ts b/lib/modules/src/finders/filters/index.ts index bf1a542..4944170 100644 --- a/lib/modules/src/finders/filters/index.ts +++ b/lib/modules/src/finders/filters/index.ts @@ -1,10 +1,16 @@ -import { FilterFlag } from './constants' +import { FilterFlag, FilterScopes } from './constants' import { createFilterGenerator } from './utils' import type { Filter, FilterGenerator } from './utils' export * from './constants' export * from './utils' +type FilterRequiringExports = Filter<{ + Result: T + RequiresExports: true + Scopes: [typeof FilterScopes.Initialized] +}> + /** * Filter modules by their exports having all of the specified properties. * @@ -33,13 +39,14 @@ export const withProps = createFilterGenerator>( }, props => `revenge.props(${props.join(',')})`, FilterFlag.RequiresExports, + FilterScopes.Initialized, ) as WithProps export type WithProps = FilterGenerator< = Record>( prop: keyof T, ...props: Array - ) => Filter + ) => FilterRequiringExports > /** @@ -58,13 +65,14 @@ export const withoutProps = createFilterGenerator>( }, props => `revenge.withoutProps(${props.join(',')})`, FilterFlag.RequiresExports, + FilterScopes.Initialized, ) as WithoutProps export type WithoutProps = FilterGenerator< >( prop: string, ...props: string[] - ) => Filter + ) => FilterRequiringExports > /** @@ -87,10 +95,11 @@ export const withSingleProp = createFilterGenerator>( }, ([prop]) => `revenge.singleProp(${prop})`, FilterFlag.RequiresExports, + FilterScopes.Initialized, ) as WithSingleProp export type WithSingleProp = FilterGenerator< - >(prop: keyof T) => Filter + >(prop: keyof T) => FilterRequiringExports > /** @@ -127,10 +136,11 @@ export const withName = createFilterGenerator>( ([name], _, exports) => exports.name === name, ([name]) => `revenge.name(${name})`, FilterFlag.RequiresExports, + FilterScopes.Initialized, ) as WithName export type WithName = FilterGenerator< - (name: string) => Filter + (name: string) => FilterRequiringExports > export * from './composite' diff --git a/lib/modules/src/finders/filters/utils.ts b/lib/modules/src/finders/filters/utils.ts index 7e72b53..2da1eff 100644 --- a/lib/modules/src/finders/filters/utils.ts +++ b/lib/modules/src/finders/filters/utils.ts @@ -1,84 +1,116 @@ +import { withProps } from '.' import { and, or } from './composite' +import { FilterScopes } from './constants' import type { Metro } from '@revenge-mod/modules/types' import type { If, LogicalAnd } from '@revenge-mod/utils/types' -import type { FilterFlag } from './constants' +import type { + DefaultFilterInfo, + FilterFlag, + FilterInfo, + FilterScope, + FilterScopes as FilterScopesObject, + FilterScopeValue, +} from './constants' -export type FilterResult = F extends Filter - ? R - : F extends FilterBase - ? R - : never +export type FilterResult = F extends Filter ? I['Result'] : never -export type FilterRequiresExports = F extends Filter - ? RE - : F extends FilterBase - ? RE - : never +export type FilterRequiresExports = F extends Filter + ? I['RequiresExports'] + : never -export interface FilterBase< - _Result = any, - RequiresExports extends boolean = boolean, -> { +export type FilterInfoOf = F extends Filter ? I : FilterInfo + +export interface FilterBase { ( ...args: If< - RequiresExports, + Info['RequiresExports'], [id: Metro.ModuleID, exports: Metro.ModuleExports], [id: Metro.ModuleID, exports?: never] > ): boolean key: string flags: If< - RequiresExports, + Info['RequiresExports'], (typeof FilterFlag)['RequiresExports'], FilterFlag > + scopes: FilterScopeValue } -export type Filter< - Result = any, - RequiresExports extends boolean = boolean, -> = FilterHelpers & FilterBase +export type Filter = + FilterHelpers & FilterBase -export interface FilterHelpers { +export type MergeFilterInfo = { + Result: I1['Result'] & I2['Result'] + RequiresExports: LogicalAnd + Scopes: [...I1['Scopes'], ...I2['Scopes']] +} + +export type UnionFilterInfo = { + Result: I1['Result'] | I2['Result'] + RequiresExports: LogicalAnd + Scopes: [...I1['Scopes'], ...I2['Scopes']] +} + +export interface FilterHelpers { /** * Manually the key for this filter. * - * **Don't use this unless you know what you're doing.** Only API exports should be using + * **Don't use this unless you know what you're doing.** Only API exports should be using this. * * @param key The key to set for this filter. */ - keyAs(this: T, key: string): T + keyAs>(this: T, key: string): T /** * Combines this filter with another filter, returning a new filter that matches if **both** filters match. * * @param filter The filter to combine with. */ - and( + and, F extends FilterBase>( this: T, filter: F, - ): Filter< - FilterResult & FilterResult, - LogicalAnd, FilterRequiresExports> - > + ): Filter>> /** * Combines this filter with another filter, returning a new filter that matches if **either** filter matches. * - * Note that exportsless filters must come first to avoid gotchas with uninitialized modules. - * * @param filter The filter to combine with. */ - or( + or, F extends FilterBase>( this: T, filter: F, + ): Filter>> + /** + * Creates a new instance of this filter. + */ + // biome-ignore format: Preserve special property name syntax for 'new' keyword + 'new'(this: Filter): Filter + /** + * Scopes this filter to match specific modules. + * + * @param scopes The scopes of modules to match. + */ + scope, const S extends FilterScope[]>( + this: T, + ...scopes: If< + Info['RequiresExports'], + [typeof FilterScopesObject.Initialized], + S + > ): Filter< - FilterResult | FilterResult, - LogicalAnd, FilterRequiresExports> + Info & { + Scopes: If< + Info['RequiresExports'], + [typeof FilterScopesObject.Initialized], + S + > + } > } export type FilterGenerator Filter> = G & { keyFor(args: Parameters): string flagsFor(args: Parameters): FilterFlag + defaultScopesFor(args: Parameters): FilterScopeValue } const Helpers: FilterHelpers = Object.setPrototypeOf( @@ -93,6 +125,22 @@ const Helpers: FilterHelpers = Object.setPrototypeOf( or(filter) { return or(this, filter) }, + new() { + const newFilter = (( + id: Metro.ModuleID, + exports?: Metro.ModuleExports, + ) => this(id, exports)) as typeof this + + Object.assign(newFilter, this) + + return Object.setPrototypeOf(newFilter, Helpers) + }, + scope(...scopes) { + const filter = this.new() + filter.scopes = 0 + for (const scope of scopes) filter.scopes |= scope + return filter + }, } satisfies FilterHelpers, Function.prototype, ) @@ -103,6 +151,7 @@ const Helpers: FilterHelpers = Object.setPrototypeOf( * @param filter The function that filters the modules. * @param keyFor The function that generates the key for the filter. * @param flagFor The function that generates the flags for the filter, or a static flag. + * @param defaultScopesFor The function that generates the default scopes for the filter, or static scopes. Defaults to {@link FilterScopes.Initialized}. * @returns A function that generates a filter with the specified arguments. * * @example @@ -126,13 +175,15 @@ export function createFilterGenerator( ) => boolean, keyFor: (args: A) => string, flagFor: ((args: A) => FilterFlag) | FilterFlag, -): FilterGenerator<(...args: A) => Filter> + defaultScopesFor?: ((args: A) => FilterScopeValue) | FilterScopeValue, +): FilterGenerator<(...args: A) => Filter> export function createFilterGenerator( filter: (args: A, id: Metro.ModuleID) => boolean, keyFor: (args: A) => string, flagFor: ((args: A) => FilterFlag) | FilterFlag, -): FilterGenerator<(...args: A) => Filter> + defaultScopesFor?: ((args: A) => FilterScopeValue) | FilterScopeValue, +): FilterGenerator<(...args: A) => Filter> export function createFilterGenerator( filter: ( @@ -142,17 +193,24 @@ export function createFilterGenerator( ) => boolean, keyFor: (args: A) => string, flagFor: ((args: A) => FilterFlag) | FilterFlag, + defaultScopesFor?: ((args: A) => FilterScopeValue) | FilterScopeValue, ): FilterGenerator<(...args: A) => Filter> { type GeneratorType = ReturnType> const isFlagsStatic = typeof flagFor === 'number' + const defaultScopes = defaultScopesFor ?? FilterScopes.Initialized + const isDefaultScopesStatic = typeof defaultScopes === 'number' + const generator: GeneratorType = (...args: A) => { const filter_ = ((id: Metro.ModuleID, exports?: Metro.ModuleExports) => filter(args, id, exports)) as ReturnType filter_.key = keyFor(args) filter_.flags = isFlagsStatic ? flagFor : flagFor(args) + filter_.scopes = isDefaultScopesStatic + ? defaultScopes + : defaultScopes(args) return Object.setPrototypeOf(filter_, Helpers) } @@ -160,6 +218,10 @@ export function createFilterGenerator( ? () => flagFor : (args: A) => flagFor(args) + generator.defaultScopesFor = isDefaultScopesStatic + ? () => defaultScopes + : defaultScopes + generator.keyFor = keyFor return generator diff --git a/lib/modules/src/finders/get.ts b/lib/modules/src/finders/get.ts index 948a6d3..ccaf356 100644 --- a/lib/modules/src/finders/get.ts +++ b/lib/modules/src/finders/get.ts @@ -5,25 +5,21 @@ import { lookupModuleWithImportedPath, } from './lookup' import { waitForModules, waitForModuleWithImportedPath } from './wait' -import type { If } from '@revenge-mod/utils/types' import type { Metro } from '../types' import type { Filter, FilterResult } from './filters' import type { LookupModulesOptions } from './lookup' import type { WaitForModulesOptions, WaitForModulesResult } from './wait' -export type GetModulesOptions< - ReturnNamespace extends boolean = boolean, - Uninitialized extends boolean = boolean, - All extends boolean = boolean, -> = WaitForModulesOptions & - LookupModulesOptions & { - /** - * The maximum number of modules to get. - * - * @default 1 - */ - max?: number - } +export type GetModulesOptions = + WaitForModulesOptions & + LookupModulesOptions & { + /** + * The maximum number of modules to get. + * + * @default 1 + */ + max?: number + } export type GetModulesResult< F extends Filter, @@ -64,12 +60,7 @@ export function getModules( callback: GetModulesCallback>, ): GetModulesUnsubscribeFunction -export function getModules< - F extends Filter, - const O extends F extends Filter - ? If, GetModulesOptions> - : never, ->( +export function getModules( filter: F, callback: GetModulesCallback>, options: O, diff --git a/lib/modules/src/finders/lookup.ts b/lib/modules/src/finders/lookup.ts index 0835cab..bac3d10 100644 --- a/lib/modules/src/finders/lookup.ts +++ b/lib/modules/src/finders/lookup.ts @@ -17,7 +17,8 @@ import { FilterResultFlagToHumanReadable, runFilter, } from './_internal' -import type { If, Nullish } from '@revenge-mod/utils/types' +import { FilterScopes } from './filters' +import type { If, Not } from '@revenge-mod/utils/types' import type { MaybeDefaultExportMatched, Metro } from '../types' import type { FilterResultFlag, @@ -25,55 +26,8 @@ import type { } from './_internal' import type { Filter, FilterResult } from './filters' -type LookupModulesOptionsWithAll = If< - A, - { - /** - * Whether to include all modules in the lookup, including blacklisted ones. - * - * **This overrides {@link BaseLookupModulesOptions.initialized} and {@link BaseLookupModulesOptions.uninitialized}.** - */ - all: A - }, - { - /** - * You can only use `all` with exportsless filters! - */ - all?: false - } -> - -type LookupModulesOptionsWithInitializedUninitialized = { - /** - * Whether to include initialized modules in the lookup. - * - * @default true - */ - initialized?: boolean -} & If< - U, - { - /** - * Whether to include uninitialized modules in the lookup. - * - * Set {@link BaseLookupModulesOptions.initialize} `true` to initialize uninitialized modules. - * - * @default false - */ - uninitialized: U - }, - { - /** - * You can only use `uninitialized` with exportsless filters! - */ - uninitialized?: false - } -> - export type LookupModulesOptions< ReturnNamespace extends boolean = boolean, - Uninitialized extends boolean = boolean, - All extends boolean = boolean, Initialize extends boolean = boolean, > = RunFilterReturnExportsOptions & { /** @@ -81,42 +35,33 @@ export type LookupModulesOptions< */ cached?: boolean } & If< - Initialize, + Not, { /** * Whether to initialize matching uninitialized modules. * * **This will initialize any modules that match the exportsless filter and may cause unintended side effects.** */ - initialize?: Initialize - }, - { initialize: false - } - > & - If< - All, - LookupModulesOptionsWithAll & { - [K in keyof LookupModulesOptionsWithInitializedUninitialized]?: never }, - LookupModulesOptionsWithInitializedUninitialized & { - [K in keyof LookupModulesOptionsWithAll]?: never - } + { initialize?: true } > export type LookupModulesResult< F extends Filter, O extends LookupModulesOptions, -> = [ - exports: O extends LookupModulesOptions - ? LookupFilterResult | Nullish - : LookupFilterResult, - id: Metro.ModuleID, -] +> = [exports: LookupFilterResult, id: Metro.ModuleID] type LookupFilterResult< F extends Filter, O extends LookupModulesOptions, +> = O extends LookupModulesOptions + ? InitializedLookupFilterResult | undefined + : InitializedLookupFilterResult + +type InitializedLookupFilterResult< + F extends Filter, + O extends LookupModulesOptions, > = O extends RunFilterReturnExportsOptions ? MaybeDefaultExportMatched> : FilterResult @@ -128,8 +73,6 @@ type LookupNotFoundResult = typeof NotFoundResult /** * Lookup modules. * - * You can lookup uninitialized modules by passing `options.uninitialized` when filtering via exportsless filters (eg. `withDependencies`). - * * @param filter The filter to use. * @param options The options to use for the lookup. * @returns A generator that yields the module exports that match the filter. @@ -147,23 +90,10 @@ export function lookupModules( export function lookupModules< F extends Filter, - const O extends F extends Filter - ? If< - RE, - LookupModulesOptions, - LookupModulesOptions - > - : never, + const O extends LookupModulesOptions, >(filter: F, options: O): Generator, undefined> export function* lookupModules(filter: Filter, options?: LookupModulesOptions) { - if ( - __DEV__ && - filter.key.includes('revenge.deps') && - !(options?.uninitialized ?? false) - ) - DEBUG_warnFilterWithoutUninitialized(filter.key) - let notFound = true let cached: Set | undefined @@ -199,8 +129,10 @@ export function* lookupModules(filter: Filter, options?: LookupModulesOptions) { } } - // Full lookup - if (options?.all) { + const scopes = filter.scopes + const includeAll = scopes & FilterScopes.All + + if (includeAll) { for (const id of mList.keys()) { // biome-ignore lint/complexity/useOptionalChain: Hot path should be optimized if (cached && cached.has(id)) continue @@ -228,12 +160,11 @@ export function* lookupModules(filter: Filter, options?: LookupModulesOptions) { ] } } + } else { + const includeInit = scopes & FilterScopes.Initialized + const includeUninit = scopes & FilterScopes.Uninitialized - if (notFound) cacheFilterNotFound(filter.key) // Full lookup, and still not found! - } - // Partial lookup - else { - if (options?.initialized ?? true) + if (includeInit) for (const id of mInitialized) { // biome-ignore lint/complexity/useOptionalChain: Hot path should be optimized if (cached && cached.has(id)) continue @@ -253,7 +184,7 @@ export function* lookupModules(filter: Filter, options?: LookupModulesOptions) { } } - if (options?.uninitialized) + if (includeUninit) for (const id of mUninitialized) { // biome-ignore lint/complexity/useOptionalChain: Hot path should be optimized if (cached && cached.has(id)) continue @@ -277,8 +208,10 @@ export function* lookupModules(filter: Filter, options?: LookupModulesOptions) { } } - if (__BUILD_FLAG_DEBUG_MODULE_LOOKUPS__) - if (notFound) DEBUG_warnLookupNoMatch(filter.key) + // Cache if nothing was found in a full lookup + if (notFound && includeAll) cacheFilterNotFound(filter.key) + if (__BUILD_FLAG_DEBUG_MODULE_LOOKUPS__ && notFound) + DEBUG_warnLookupNoMatch(filter.key) } /** @@ -301,23 +234,10 @@ export function lookupModule( export function lookupModule< F extends Filter, - const O extends F extends Filter - ? If< - RE, - LookupModulesOptions, - LookupModulesOptions - > - : never, + const O extends LookupModulesOptions, >(filter: F, options: O): LookupModulesResult | LookupNotFoundResult export function lookupModule(filter: Filter, options?: LookupModulesOptions) { - if ( - __DEV__ && - filter.key.includes('revenge.deps') && - !(options?.uninitialized ?? false) - ) - DEBUG_warnFilterWithoutUninitialized(filter.key) - if (options?.cached ?? true) { const notInit = !(options?.initialize ?? true) @@ -345,8 +265,10 @@ export function lookupModule(filter: Filter, options?: LookupModulesOptions) { } } - // Full lookup - if (options?.all) { + const scopes = filter.scopes + const includeAll = scopes & FilterScopes.All + + if (includeAll) { for (const id of mList.keys()) { const flag = runFilter( filter, @@ -369,17 +291,11 @@ export function lookupModule(filter: Filter, options?: LookupModulesOptions) { ] } } + } else { + const includeInit = scopes & FilterScopes.Initialized + const includeUninit = scopes & FilterScopes.Uninitialized - if (__BUILD_FLAG_DEBUG_MODULE_LOOKUPS__) - DEBUG_warnLookupNoMatch(filter.key) - - cacheFilterNotFound(filter.key) // Full lookup, and still not found! - - return NotFoundResult - } - // Partial lookup - else { - if (options?.initialized ?? true) + if (includeInit) for (const id of mInitialized) { const exports = getInitializedModuleExports(id) const flag = runFilter(filter, id, exports, options) @@ -394,7 +310,7 @@ export function lookupModule(filter: Filter, options?: LookupModulesOptions) { } } - if (options?.uninitialized) + if (includeUninit) for (const id of mUninitialized) { const flag = runFilter(filter, id, undefined, options) if (flag) { @@ -413,6 +329,8 @@ export function lookupModule(filter: Filter, options?: LookupModulesOptions) { } } + // Cache if nothing was found in a full lookup + if (includeAll) cacheFilterNotFound(filter.key) if (__BUILD_FLAG_DEBUG_MODULE_LOOKUPS__) DEBUG_warnLookupNoMatch(filter.key) return NotFoundResult @@ -478,15 +396,3 @@ function DEBUG_warnLookupNoMatch(key: string) { nativeLoggingHook(`\u001b[31mFailed lookup: ${key}\n${stack}\u001b[0m`, 2) } - -/** - * Warns the developer about a potentially bad filter for lookups that do not have uninitialized modules. - */ -function DEBUG_warnFilterWithoutUninitialized(key: string) { - const stack = getCurrentStack() - - nativeLoggingHook( - `\u001b[33mPotentially bad filter without uninitialized modules lookup: ${key}\n${stack}\u001b[0m`, - 2, - ) -} diff --git a/lib/modules/src/finders/wait.ts b/lib/modules/src/finders/wait.ts index 9b675e2..69e82bb 100644 --- a/lib/modules/src/finders/wait.ts +++ b/lib/modules/src/finders/wait.ts @@ -11,6 +11,7 @@ import { FilterResultFlagToHumanReadable, runFilter, } from './_internal' +import { FilterScopes } from './filters' import type { MaybeDefaultExportMatched, Metro } from '../types' import type { FilterResultFlag, @@ -18,24 +19,12 @@ import type { } from './_internal' import type { Filter, FilterResult } from './filters' -export interface BaseWaitForModulesOptions { - /** - * Whether to include all modules, including blacklisted ones. - * - * @default false - */ - all?: All -} - export type WaitForModulesUnsubscribeFunction = () => void export type WaitForModulesCallback = (exports: T, id: Metro.ModuleID) => any -export type WaitForModulesOptions< - ReturnNamespace extends boolean = boolean, - All extends boolean = boolean, -> = RunFilterReturnExportsOptions & - BaseWaitForModulesOptions & { +export type WaitForModulesOptions = + RunFilterReturnExportsOptions & { /** * Use cached results **only** (if possible). * If there is no cache result, this works as if you did not pass this option at all. @@ -81,9 +70,7 @@ export function waitForModules( ): WaitForModulesUnsubscribeFunction export function waitForModules< - F extends O extends WaitForModulesOptions - ? Filter - : Filter, + F extends Filter, O extends WaitForModulesOptions, >( filter: F, @@ -144,7 +131,7 @@ export function waitForModules( ) return onAnyModuleInitialized( - options?.all + filter.scopes & FilterScopes.All ? (id, exports) => { const flag = runFilter(filter, id, exports, options) if (flag) { @@ -187,7 +174,6 @@ export function waitForModules( * * @param path The path to wait for. * @param callback The callback to call once the module is initialized. - * @param options The options to use for the wait. * @returns A function to unsubscribe. * * @example @@ -203,14 +189,13 @@ export function waitForModules( export function waitForModuleWithImportedPath( path: string, callback: WaitForModulesCallback, - options?: BaseWaitForModulesOptions, ): WaitForModulesUnsubscribeFunction { const unsub = onModuleFinishedImporting((id, cmpPath) => { if (path === cmpPath) { unsub() // Module is not fully initialized yet, so we need to wait for it onModuleInitialized(id, (id, exports) => { - if (!options?.all || mInitialized.has(id)) callback(exports, id) + callback(exports, id) }) } }) diff --git a/lib/utils/src/discord.ts b/lib/utils/src/discord.ts index 3121cca..719fc56 100644 --- a/lib/utils/src/discord.ts +++ b/lib/utils/src/discord.ts @@ -10,7 +10,10 @@ import { ReactModuleId, ReactNativeModuleId, } from '@revenge-mod/react' -import { FilterFlag } from '../../modules/src/finders/filters/constants' +import { + FilterFlag, + FilterScopes, +} from '../../modules/src/finders/filters/constants' import type { Filter, FilterGenerator, @@ -43,22 +46,24 @@ export type WithGeneratedIconComponent = FilterGenerator< ( name: N, ...assets: string[] - ) => Filter<{ [K in N]: FC }> + ) => Filter<{ + Result: { [K in N]: FC } + RequiresExports: boolean + Scopes: [ + typeof FilterScopes.Uninitialized, + typeof FilterScopes.Initialized, + ] + }> > /** * Filter by icon component name and asset names. * - * **Make sure to set `uninitialized: true` when using this filter in `lookupModule`!** - * * @param names The component name, then the asset names if the component has multiple assets. * * @example * ```ts * const [CopyIconModule] = lookupModule( * withGeneratedIconComponent('CopyIcon'), - * { - * uninitialized: true, - * } * ) * if (CopyIconModule) { * const { CopyIcon } = CopyIconModule @@ -73,9 +78,6 @@ export type WithGeneratedIconComponent = FilterGenerator< * 'CircleXIcon-secondary', * 'CircleXIcon-primary', * ), - * { - * uninitialized: true, - * } * ) * ``` */ @@ -119,6 +121,7 @@ export const withGeneratedIconComponent = createFilterGenerator< }, names => `revenge.utils.discord.generatedIconComponent(${names.join(',')})`, FilterFlag.Dynamic, + FilterScopes.Uninitialized | FilterScopes.Initialized, ) as WithGeneratedIconComponent /** @@ -141,9 +144,7 @@ export function lookupGeneratedIconComponent( if (__DEV__ && badFind) return } - const [module] = lookupModule(withGeneratedIconComponent(...names), { - uninitialized: true, - }) + const [module] = lookupModule(withGeneratedIconComponent(...names)) return module?.[names[0]] as FC | undefined } diff --git a/lib/utils/src/types.ts b/lib/utils/src/types.ts index 6f1cc71..7844d10 100644 --- a/lib/utils/src/types.ts +++ b/lib/utils/src/types.ts @@ -1,5 +1,6 @@ export type Nullish = null | undefined export type If = T extends true ? Then : Else +export type Not = T extends true ? false : true export type AnyObject = Record export type LogicalOr = T1 extends true ? true