Skip to content

Instantly share code, notes, and snippets.

@karol-majewski
Last active September 9, 2021 14:19
Show Gist options
  • Save karol-majewski/818dc1a4569ac33c055830694ba726b3 to your computer and use it in GitHub Desktop.
Save karol-majewski/818dc1a4569ac33c055830694ba726b3 to your computer and use it in GitHub Desktop.
/**
* Returns a `string` if all parts of the created template string are guaranteed to be defined.
* If at least one partial string is nullable, the return value will be null.
*
* @example
*
* ```ts
* safestring`/api/v1/users/${user?.id}`; // string | null
* safestring`/api/v1/users/${"15"}`; // string
* ```
*/
function safestring(strings: TemplateStringsArray, ...args: Array<string | number>): string;
function safestring(strings: TemplateStringsArray, ...args: Array<string | number | null | undefined>): string | null;
function safestring(strings: TemplateStringsArray, ...args: Array<string | number | null | undefined>): string | null {
let result = strings[0];
for (let index = 0; index < args.length; index++) {
if (args[index] == null) return null;
result += args[index]! + strings[index + 1]!;
}
return result ?? null;
}
declare const user: {
id?: string;
};
const foo: string = safestring`/api/users/${'foo'}/projects.json`;
const bar: string | null = safestring`/api/users/${user.id}/projects.json`;
@karol-majewski
Copy link
Author

const marker: unique symbol = Symbol.for('safestring');

type SafeString = string & {
  brand: typeof marker;
}

/**
 * Returns a `string` if all parts of the created template string are guaranteed to be defined.
 * If at least one partial string is nullable, the return value will be null.
 *
 * @example
 *
 * ```ts
 * safestring`/api/v1/users/${user?.id}`; // SafeString | null
 * safestring`/api/v1/users/${"15"}`; // SafeString
 * ```
 */
export function safestring(strings: TemplateStringsArray, ...args: Array<string | number>): SafeString;
export function safestring(strings: TemplateStringsArray, ...args: Array<string | number | null | undefined>): SafeString | null;
export function safestring(strings: TemplateStringsArray, ...args: Array<string | number | null | undefined>): SafeString | null {
  let result = strings[0];

  for (let index = 0; index < args.length; index++) {
    if (args[index] == null) return null;
    result += args[index]! + strings[index + 1]!;
  }

  return isDefined(result) ? Object.assign(result, { brand: marker as typeof marker  }) : null;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment