Skip to content

Commit 372b406

Browse files
committed
feat(context): fix generic typing for BindingFilter
1 parent cb308ad commit 372b406

File tree

3 files changed

+36
-8
lines changed

3 files changed

+36
-8
lines changed

packages/context/src/binding-filter.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,37 @@ import {BindingAddress} from './binding-key';
99
/**
1010
* A function that filters bindings. It returns `true` to select a given
1111
* binding.
12+
*
13+
* TODO(semver-major): We might change this type in the future to either remove
14+
* the `<ValueType>` or make it as type guard by asserting the matched binding
15+
* to be typed with `<ValueType>`.
16+
*
17+
* **NOTE**: Originally, we allow filters to be tied with a single value type.
18+
* This actually does not make much sense - the filter function is typically
19+
* invoked on all bindings to find those ones matching the given criteria.
20+
* Filters must be prepared to handle bindings of any value type. We learned
21+
* about this problem after enabling TypeScript's `strictFunctionTypes` check,
22+
* but decided to preserve `ValueType` argument for backwards compatibility.
23+
* The `<ValueType>` represents the value type for matched bindings but it's
24+
* not used for checking.
25+
*
26+
* Ideally, `BindingFilter` should be declared as a type guard as follows:
27+
* ```ts
28+
* export type BindingFilterGuard<ValueType = unknown> = (
29+
* binding: Readonly<Binding<unknown>>,
30+
* ) => binding is Readonly<Binding<ValueType>>;
31+
* ```
32+
*
33+
* But TypeScript treats the following types as incompatible and does not accept
34+
* type 1 for type 2.
35+
*
36+
* 1. `(binding: Readonly<Binding<unknown>>) => boolean`
37+
* 2. `(binding: Readonly<Binding<unknown>>) => binding is Readonly<Binding<ValueType>>`
38+
*
1239
*/
40+
// tslint:disable-next-line:no-unused
1341
export type BindingFilter<ValueType = unknown> = (
14-
binding: Readonly<Binding<ValueType>>,
42+
binding: Readonly<Binding<unknown>>,
1543
) => boolean;
1644

1745
/**

packages/context/src/context-view.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class ContextView<T = unknown> extends EventEmitter
4242

4343
constructor(
4444
protected readonly context: Context,
45-
public readonly filter: BindingFilter<T>,
45+
public readonly filter: BindingFilter,
4646
) {
4747
super();
4848
}
@@ -161,10 +161,10 @@ export class ContextView<T = unknown> extends EventEmitter
161161
*/
162162
export function createViewGetter<T = unknown>(
163163
ctx: Context,
164-
bindingFilter: BindingFilter<T>,
164+
bindingFilter: BindingFilter,
165165
session?: ResolutionSession,
166166
): Getter<T[]> {
167-
const view = new ContextView(ctx, bindingFilter);
167+
const view = new ContextView<T>(ctx, bindingFilter);
168168
view.open();
169169
return view.asGetter(session);
170170
}

packages/context/src/context.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ if (!Symbol.asyncIterator) {
3939
(Symbol as any).asyncIterator = Symbol.for('Symbol.asyncIterator');
4040
}
4141
/**
42-
* This import must happen after the polyfill.
43-
*
44-
* WARNING: VSCode organize import may change the order of this import
42+
* WARNING: This following import must happen after the polyfill. The
43+
* `auto-import` by an IDE such as VSCode may move the import before the
44+
* polyfill. It must be then fixed manually.
4545
*/
4646
import {iterator, multiple} from 'p-event';
4747

@@ -522,7 +522,7 @@ export class Context extends EventEmitter {
522522
find<ValueType = BoundValue>(
523523
pattern?: string | RegExp | BindingFilter,
524524
): Readonly<Binding<ValueType>>[] {
525-
const bindings: Readonly<Binding>[] = [];
525+
const bindings: Readonly<Binding<ValueType>>[] = [];
526526
const filter = filterByKey(pattern);
527527

528528
for (const b of this.registry.values()) {

0 commit comments

Comments
 (0)