Skip to content

Commit 59bf76c

Browse files
authored
Merge pull request #8 from typed-ember/block-params-refactor
2 parents c7da8f0 + 6427285 commit 59bf76c

40 files changed

+250
-287
lines changed

.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"prefer-const": "off",
1212
"@typescript-eslint/prefer-const": "off",
1313
"@typescript-eslint/no-empty-function": "off",
14+
"@typescript-eslint/no-empty-interface": "off",
1415
"@typescript-eslint/no-use-before-define": "off",
1516
"@typescript-eslint/no-non-null-assertion": "off",
1617
"@typescript-eslint/explicit-function-return-type": [

packages/template/-private/blocks.d.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/**
1+
/*
22
* This module contains types pertaining to defining and working with
33
* blocks in templates. In general, a block body is represented as a
44
* generator function that iterates over `BlockYield` values, whose
@@ -7,7 +7,7 @@
77
*
88
* For example, in an invocation like this:
99
*
10-
* invokeBlock(resolve(BuiltIns['each'])({}, ['a', 'b', 'c']), {
10+
* invokeBlock(resolve(Globals['each'])({}, ['a', 'b', 'c']), {
1111
* *default(letter, index) {
1212
* yield toBlock('body', `Letter #${index}: ${letter}`);
1313
* }
@@ -20,24 +20,34 @@
2020
* which could then be plumbed out to the surrounding context to inform
2121
* the expected blocks the template in question expects to receive.
2222
*/
23-
declare const ModuleDocs: void;
2423

25-
/** The loose shape of the expected return type for a block body */
26-
export type BlockResult = IterableIterator<BlockYield<string, unknown[]>>;
24+
import { AnyBlocks } from './signature';
25+
26+
type Block<
27+
Params extends any[] = any,
28+
Yields extends BlockYield<string, unknown[]> = BlockYield<string, unknown[]>
29+
> = (...params: Params) => IterableIterator<Yields>;
2730

2831
/**
2932
* A type that encapsulates the act of `{{yield}}`ing in a template, encoding
3033
* the name of the block that was yielded to and the type(s) of its param(s)
3134
*/
3235
export type BlockYield<K extends string, V extends unknown[]> = { to: K; values: V };
3336

37+
/**
38+
* Given a mapping from block names to the parameters they'll receive, produces
39+
* the corresponding type for the blocks hash that may be passed to the component
40+
* in question.
41+
*/
42+
export type BlockBodies<Blocks extends AnyBlocks> = {
43+
[BlockName in keyof Blocks]: Block<NonNullable<Blocks[BlockName]>>;
44+
};
45+
3446
/**
3547
* Given a block function, determines its `BlockYield`s based on its returned
3648
* iterator type.
3749
*/
38-
export type YieldsFromBlock<T extends (...args: any) => BlockResult> = T extends (
39-
...args: any
40-
) => IterableIterator<infer U>
50+
export type YieldsFromBlock<T extends Block> = T extends (...args: any) => IterableIterator<infer U>
4151
? U
4252
: never;
4353

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Yuck. This will work for generic functions if the types are fixed given the initial args,
2+
// but otherwise they'll degrade to `unknown` in the type of the returned function.
3+
// I don't think there's a better way to type `{{fn}}` though; this already maintains more type
4+
// info than Ramda's `partial`, for instance.
5+
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/539042117cd697da07daf93092bdf16bc14922d8/types/ramda/index.d.ts#L1310-L1324
6+
7+
import { NoNamedArgs, ReturnsValue } from '../signature';
8+
9+
// prettier-ignore
10+
export default interface FnHelper {
11+
<Ret, Args extends unknown[]>(args: NoNamedArgs, f: (...rest: Args) => Ret): ReturnsValue<(...rest: Args) => Ret>;
12+
<A, Ret, Args extends unknown[]>(args: NoNamedArgs, f: (a: A, ...rest: Args) => Ret, a: A): ReturnsValue<(...rest: Args) => Ret>;
13+
<A, B, Ret, Args extends unknown[]>(args: NoNamedArgs, f: (a: A, b: B, ...rest: Args) => Ret, a: A, b: B): ReturnsValue<(...rest: Args) => Ret>;
14+
<A, B, C, Ret, Args extends unknown[]>(args: NoNamedArgs, f: (a: A, b: B, c: C, ...rest: Args) => Ret, a: A, b: B, c: C): ReturnsValue<(...rest: Args) => Ret>;
15+
<A, B, C, D, Ret, Args extends unknown[]>(args: NoNamedArgs, f: (a: A, b: B, c: C, d: D, ...rest: Args) => Ret, a: A, b: B, c: C, d: D): ReturnsValue<(...rest: Args) => Ret>;
16+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { CreatesModifier } from '../signature';
2+
3+
export default interface OnModifier {
4+
<K extends keyof HTMLElementEventMap>(
5+
args: AddEventListenerOptions,
6+
key: K,
7+
eventHandler: (event: HTMLElementEventMap[K]) => void
8+
): CreatesModifier;
9+
}

packages/template/-private/built-ins/primitives.d.ts

Lines changed: 0 additions & 167 deletions
This file was deleted.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// According to the strict mode RFC, these identifiers are 'keywords' that are
2+
// always implicitly in scope. While that's true for Ember applications, it doesn't
3+
// necessarily apply to Glimmer.js or GlimmerX, so this likely needs to be factored
4+
// into those environment-specific type declarations in the future.
5+
// https://github.com/emberjs/rfcs/pull/496/files#diff-813a6bebec3bf341e6af852f27444bc0R437
6+
interface Keywords {
7+
action: void; // TODO
8+
component: import('./keywords/component').default;
9+
debugger: import('./keywords/debugger').default;
10+
'each-in': import('./keywords/each-in').default;
11+
each: import('./keywords/each').default;
12+
'has-block-params': import('./keywords/has-block-params').default;
13+
'has-block': import('./keywords/has-block').default;
14+
hasBlock: import('./keywords/has-block').default;
15+
// `if` is implemented directly in @glint/transform
16+
'in-element': void; // TODO
17+
let: import('./keywords/let').default;
18+
'link-to': void; // TODO
19+
loc: void; // TODO
20+
log: void; // TODO
21+
mount: void; // TODO
22+
mut: void; // TODO
23+
outlet: void; // TODO
24+
'query-params': void; // TODO
25+
readonly: void; // TODO
26+
unbound: void; // TODO
27+
unless: void; // TODO (maybe implement directly in @glint/transform?)
28+
with: import('./keywords/with').default;
29+
// `yield` is implemented directly in @glint/transform
30+
}
31+
32+
// This `Globals` interface dictates what identifiers will always be in scope
33+
// even when not statically visible. In principle it can be extended outside
34+
// this package, and could even be used to implement support for today's
35+
// resolver-based template entity lookup via a type registry-style system.
36+
interface Globals extends Keywords {
37+
// The strict-mode RFC proposes that these be importable since they're
38+
// theoretically implementable in userland, but for simplicity for now we're
39+
// just including them in `Globals`.
40+
fn: import('./built-ins/fn').default;
41+
on: import('./built-ins/on').default;
42+
}
43+
44+
declare const Globals: Globals;
45+
46+
export default Globals;

packages/template/-private/index.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
export { BlockResult } from './blocks';
21
export { Invokable } from './invoke';
32
export { AcceptsBlocks, CreatesModifier, ReturnsValue, NoNamedArgs } from './signature';
43
export { TemplateContext } from './template';

packages/template/-private/invoke.d.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
/**
2-
*
3-
*/
4-
declare const ModuleDocs: void;
5-
61
import { Return, ReturnsValue, CreatesModifier, AcceptsBlocks, AnySignature } from './signature';
7-
import { YieldsFromBlock } from './blocks';
2+
import { YieldsFromBlock, BlockBodies } from './blocks';
83

94
/**
105
* This wrapping type indicates a value that may be invoked in a template.
@@ -51,7 +46,10 @@ export declare function invokeModifier<T extends CreatesModifier>(value: T): voi
5146
* This form of invocation is the only one in a template that accepts
5247
* blocks.
5348
*/
54-
export declare function invokeBlock<T extends AcceptsBlocks<any>, Impls extends Parameters<T>[0]>(
49+
export declare function invokeBlock<
50+
T extends AcceptsBlocks<any>,
51+
Impls extends BlockBodies<Parameters<T>[0]>
52+
>(
5553
value: T,
5654
blocks: Impls,
5755
// It doesn't seem to be possible to get the typechecker to infer the
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { AnySignature, AnyBlocks, ReturnsValue, AcceptsBlocks } from '../signature';
2+
import { ResolveSignature } from '../resolution';
3+
import { Invokable } from '../invoke';
4+
5+
type SignatureFor<T> = T extends AnySignature ? T : ResolveSignature<T>;
6+
7+
type ArgsFor<T> = SignatureFor<T> extends (args: infer Args) => unknown ? Args : {};
8+
9+
type PositionalFor<T> = SignatureFor<T> extends (
10+
args: never,
11+
...positional: infer Positional
12+
) => unknown
13+
? Positional
14+
: never[];
15+
16+
type BlocksFor<T> = SignatureFor<T> extends (...params: never) => (blocks: infer Blocks) => unknown
17+
? Blocks extends Partial<AnyBlocks>
18+
? Blocks
19+
: {}
20+
: {};
21+
22+
export default interface ComponentKeyword {
23+
<Component, GivenArgs extends keyof ArgsFor<Component>>(
24+
args: { [Arg in GivenArgs]: ArgsFor<Component>[Arg] },
25+
component: Component
26+
): ReturnsValue<
27+
Invokable<
28+
(
29+
args: Omit<ArgsFor<Component>, GivenArgs> & Partial<Pick<ArgsFor<Component>, GivenArgs>>,
30+
...positional: PositionalFor<Component>
31+
) => AcceptsBlocks<BlocksFor<Component>>
32+
>
33+
>;
34+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { NoNamedArgs, ReturnsValue } from '../signature';
2+
3+
export default interface DebuggerKeyword {
4+
(args: NoNamedArgs): ReturnsValue<void>;
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { NoNamedArgs, AcceptsBlocks } from '../signature';
2+
3+
export default interface EachInKeyword {
4+
<T>(args: NoNamedArgs, object: T): AcceptsBlocks<{
5+
default: [keyof T, T[keyof T]];
6+
}>;
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { AcceptsBlocks } from '../signature';
2+
3+
export default interface EachKeyword {
4+
<T>(args: { key?: string }, items: T[]): AcceptsBlocks<{
5+
default: [T, number];
6+
inverse?: [];
7+
}>;
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { NoNamedArgs, ReturnsValue } from '../signature';
2+
3+
export default interface HasBlocParamskKeyword {
4+
(args: NoNamedArgs, blockName?: string): ReturnsValue<boolean>;
5+
}

0 commit comments

Comments
 (0)