Skip to content

Instantly share code, notes, and snippets.

@atruskie
Created January 22, 2020 04:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atruskie/12abf8cc7979eec14fbee956f13e943c to your computer and use it in GitHub Desktop.
Save atruskie/12abf8cc7979eec14fbee956f13e943c to your computer and use it in GitHub Desktop.
Typescript: strongly typed, lazy evaluated, interpolated strings
let id = (x: number) => x;
let param = (x: string) => x;
type ParamType<T> = T extends (arg: infer R) => any ? R : never;
/**
* Templates a string by substituting placeholders for tokens later in execution.
* It is designed to work as the tag function for tagged interpolated strings.
* @returns A reusable template function that is statically checked for arity and
* parameter type compatibility with the placeholders from the interpolated string.
* @param strings The strings around the tokens to template
* @param placeholders Placeholders that are substituted for token when
* templating is done. Note these are transform functions.
*/
function makeTemplate<T extends ((any: any) => any)[]>(
strings: TemplateStringsArray,
...placeholders: T) {
// https://github.com/microsoft/TypeScript/issues/12754 should improve this even more
return function template(...tokens: { [K in keyof T]: ParamType<T[K]> }) {
// interleave the strings with the parameters
let result = Array(strings.length + tokens.length);
for (let i = 0; i < tokens.length; i++) {
result[i * 2] = strings[i];
result[i * 2 + 1] = placeholders[i](tokens[i]);
}
result[result.length - 1] = strings[strings.length - 1];
return result.join('');
}
}
// let url: (tokens_0: number, tokens_1: string, tokens_2: number, tokens_3: string) => string
let url = makeTemplate`${id}/projects/${param}/sites/${id}/new/${param}`
class AudioRecording { id; }
let audioRecordingId = (p: AudioRecording) => p.id;
// let url2: (tokens_0: AudioRecording, tokens_1: number) => string
let url2 = makeTemplate`/audio_recordings/${audioRecordingId}/audio_events/${id}/new/`
console.log("final:");
console.log(url); // [Function: template]
console.log(url(3, "hello", 2, "world")); // 3/projects/hello/sites/2/new/world
//console.log(url(3, "hello", 2, "world", 6)); // error: Expected 4 arguments, but got 5.ts(2554)
//console.log(url()); // error: Expected 4 arguments, but got 0.ts(2554)
//console.log(url(3, 4, 2, "world")); // error: Argument of type '4' is not assignable to parameter of type 'string'.ts(2345)
//console.log(url2(3, "hello")); // error: Argument of type '3' is not assignable to parameter of type 'AudioRecording'.ts(2345)
let ar = { id: 12345} as AudioRecording
console.log(url2(ar, 6789)); // /audio_recordings/12345/audio_events/6789/new/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment