Skip to content

Simplify specifying block params #8

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

Merged
merged 3 commits into from
Jul 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"prefer-const": "off",
"@typescript-eslint/prefer-const": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-function-return-type": [
Expand Down
26 changes: 18 additions & 8 deletions packages/template/-private/blocks.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
/*
* This module contains types pertaining to defining and working with
* blocks in templates. In general, a block body is represented as a
* generator function that iterates over `BlockYield` values, whose
Expand All @@ -7,7 +7,7 @@
*
* For example, in an invocation like this:
*
* invokeBlock(resolve(BuiltIns['each'])({}, ['a', 'b', 'c']), {
* invokeBlock(resolve(Globals['each'])({}, ['a', 'b', 'c']), {
* *default(letter, index) {
* yield toBlock('body', `Letter #${index}: ${letter}`);
* }
Expand All @@ -20,24 +20,34 @@
* which could then be plumbed out to the surrounding context to inform
* the expected blocks the template in question expects to receive.
*/
declare const ModuleDocs: void;

/** The loose shape of the expected return type for a block body */
export type BlockResult = IterableIterator<BlockYield<string, unknown[]>>;
import { AnyBlocks } from './signature';

type Block<
Params extends any[] = any,
Yields extends BlockYield<string, unknown[]> = BlockYield<string, unknown[]>
> = (...params: Params) => IterableIterator<Yields>;

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

/**
* Given a mapping from block names to the parameters they'll receive, produces
* the corresponding type for the blocks hash that may be passed to the component
* in question.
*/
export type BlockBodies<Blocks extends AnyBlocks> = {
[BlockName in keyof Blocks]: Block<NonNullable<Blocks[BlockName]>>;
};

/**
* Given a block function, determines its `BlockYield`s based on its returned
* iterator type.
*/
export type YieldsFromBlock<T extends (...args: any) => BlockResult> = T extends (
...args: any
) => IterableIterator<infer U>
export type YieldsFromBlock<T extends Block> = T extends (...args: any) => IterableIterator<infer U>
? U
: never;

Expand Down
16 changes: 16 additions & 0 deletions packages/template/-private/built-ins/fn.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Yuck. This will work for generic functions if the types are fixed given the initial args,
// but otherwise they'll degrade to `unknown` in the type of the returned function.
// I don't think there's a better way to type `{{fn}}` though; this already maintains more type
// info than Ramda's `partial`, for instance.
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/539042117cd697da07daf93092bdf16bc14922d8/types/ramda/index.d.ts#L1310-L1324

import { NoNamedArgs, ReturnsValue } from '../signature';

// prettier-ignore
export default interface FnHelper {
<Ret, Args extends unknown[]>(args: NoNamedArgs, f: (...rest: Args) => Ret): ReturnsValue<(...rest: Args) => Ret>;
<A, Ret, Args extends unknown[]>(args: NoNamedArgs, f: (a: A, ...rest: Args) => Ret, a: A): ReturnsValue<(...rest: Args) => Ret>;
<A, B, Ret, Args extends unknown[]>(args: NoNamedArgs, f: (a: A, b: B, ...rest: Args) => Ret, a: A, b: B): ReturnsValue<(...rest: Args) => Ret>;
<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>;
<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>;
}
9 changes: 9 additions & 0 deletions packages/template/-private/built-ins/on.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { CreatesModifier } from '../signature';

export default interface OnModifier {
<K extends keyof HTMLElementEventMap>(
args: AddEventListenerOptions,
key: K,
eventHandler: (event: HTMLElementEventMap[K]) => void
): CreatesModifier;
}
167 changes: 0 additions & 167 deletions packages/template/-private/built-ins/primitives.d.ts

This file was deleted.

46 changes: 46 additions & 0 deletions packages/template/-private/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// According to the strict mode RFC, these identifiers are 'keywords' that are
// always implicitly in scope. While that's true for Ember applications, it doesn't
// necessarily apply to Glimmer.js or GlimmerX, so this likely needs to be factored
// into those environment-specific type declarations in the future.
// https://github.com/emberjs/rfcs/pull/496/files#diff-813a6bebec3bf341e6af852f27444bc0R437
interface Keywords {
action: void; // TODO
component: import('./keywords/component').default;
debugger: import('./keywords/debugger').default;
'each-in': import('./keywords/each-in').default;
each: import('./keywords/each').default;
'has-block-params': import('./keywords/has-block-params').default;
'has-block': import('./keywords/has-block').default;
hasBlock: import('./keywords/has-block').default;
// `if` is implemented directly in @glint/transform
'in-element': void; // TODO
let: import('./keywords/let').default;
'link-to': void; // TODO
loc: void; // TODO
log: void; // TODO
mount: void; // TODO
mut: void; // TODO
outlet: void; // TODO
'query-params': void; // TODO
readonly: void; // TODO
unbound: void; // TODO
unless: void; // TODO (maybe implement directly in @glint/transform?)
with: import('./keywords/with').default;
// `yield` is implemented directly in @glint/transform
}

// This `Globals` interface dictates what identifiers will always be in scope
// even when not statically visible. In principle it can be extended outside
// this package, and could even be used to implement support for today's
// resolver-based template entity lookup via a type registry-style system.
interface Globals extends Keywords {
// The strict-mode RFC proposes that these be importable since they're
// theoretically implementable in userland, but for simplicity for now we're
// just including them in `Globals`.
fn: import('./built-ins/fn').default;
on: import('./built-ins/on').default;
}

declare const Globals: Globals;

export default Globals;
1 change: 0 additions & 1 deletion packages/template/-private/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { BlockResult } from './blocks';
export { Invokable } from './invoke';
export { AcceptsBlocks, CreatesModifier, ReturnsValue, NoNamedArgs } from './signature';
export { TemplateContext } from './template';
Expand Down
12 changes: 5 additions & 7 deletions packages/template/-private/invoke.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
/**
*
*/
declare const ModuleDocs: void;

import { Return, ReturnsValue, CreatesModifier, AcceptsBlocks, AnySignature } from './signature';
import { YieldsFromBlock } from './blocks';
import { YieldsFromBlock, BlockBodies } from './blocks';

/**
* This wrapping type indicates a value that may be invoked in a template.
Expand Down Expand Up @@ -51,7 +46,10 @@ export declare function invokeModifier<T extends CreatesModifier>(value: T): voi
* This form of invocation is the only one in a template that accepts
* blocks.
*/
export declare function invokeBlock<T extends AcceptsBlocks<any>, Impls extends Parameters<T>[0]>(
export declare function invokeBlock<
T extends AcceptsBlocks<any>,
Impls extends BlockBodies<Parameters<T>[0]>
>(
value: T,
blocks: Impls,
// It doesn't seem to be possible to get the typechecker to infer the
Expand Down
34 changes: 34 additions & 0 deletions packages/template/-private/keywords/component.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { AnySignature, AnyBlocks, ReturnsValue, AcceptsBlocks } from '../signature';
import { ResolveSignature } from '../resolution';
import { Invokable } from '../invoke';

type SignatureFor<T> = T extends AnySignature ? T : ResolveSignature<T>;

type ArgsFor<T> = SignatureFor<T> extends (args: infer Args) => unknown ? Args : {};

type PositionalFor<T> = SignatureFor<T> extends (
args: never,
...positional: infer Positional
) => unknown
? Positional
: never[];

type BlocksFor<T> = SignatureFor<T> extends (...params: never) => (blocks: infer Blocks) => unknown
? Blocks extends Partial<AnyBlocks>
? Blocks
: {}
: {};

export default interface ComponentKeyword {
<Component, GivenArgs extends keyof ArgsFor<Component>>(
args: { [Arg in GivenArgs]: ArgsFor<Component>[Arg] },
component: Component
): ReturnsValue<
Invokable<
(
args: Omit<ArgsFor<Component>, GivenArgs> & Partial<Pick<ArgsFor<Component>, GivenArgs>>,
...positional: PositionalFor<Component>
) => AcceptsBlocks<BlocksFor<Component>>
>
>;
}
5 changes: 5 additions & 0 deletions packages/template/-private/keywords/debugger.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NoNamedArgs, ReturnsValue } from '../signature';

export default interface DebuggerKeyword {
(args: NoNamedArgs): ReturnsValue<void>;
}
7 changes: 7 additions & 0 deletions packages/template/-private/keywords/each-in.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NoNamedArgs, AcceptsBlocks } from '../signature';

export default interface EachInKeyword {
<T>(args: NoNamedArgs, object: T): AcceptsBlocks<{
default: [keyof T, T[keyof T]];
}>;
}
8 changes: 8 additions & 0 deletions packages/template/-private/keywords/each.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AcceptsBlocks } from '../signature';

export default interface EachKeyword {
<T>(args: { key?: string }, items: T[]): AcceptsBlocks<{
default: [T, number];
inverse?: [];
}>;
}
5 changes: 5 additions & 0 deletions packages/template/-private/keywords/has-block-params.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NoNamedArgs, ReturnsValue } from '../signature';

export default interface HasBlocParamskKeyword {
(args: NoNamedArgs, blockName?: string): ReturnsValue<boolean>;
}
Loading