Repository: TypeScript
Issue: [feature request] Support Custom Types for Tagged Template Expressions
Labels: Suggestion,In Discussion

## Problem Statement

It is common to embed queries in tagged template expressions within typescript.  For example, you might use sql:

```ts
const result = await querySQL(sql`SELECT * FROM users WHERE id = ${userID}`);
```

You also see a similar pattern with graphql:

```ts
const result = await queryGraph(graphql`query { user { id, name } }`);
```

Currently, there is no way to provide type information about what is returned by `queryGraph`/`querySQL`.  A lot of potential type information is lost at the boundary here.

I obviously don't expect typescript to read the graphql or SQL schema, as that's way out of scope. What I would like is a way to provide this information as a module author.

## Proposal

I believe that all that's needed is a way to write plugins that answer the question "given a tagged template expression, what type does it return".  This would only affect the type checker. It would not change generated code or add any new syntax.

A new `compilerConfig` option called `taggedTemplateHandlers` would be added, that would take an array of paths to typescript files.  An example might look something like:

```ts
import {parseSqlQuery, convertSqlTypeToTypeScriptType} from 'sql-helpers';
import * as ts_module from "typescript/lib/tsserverlibrary";

export default {
  tag: 'sql',
  templateHandler(modules: {typescript: typeof ts_module}, strings: string[], ...expressions: Array<ts_module.Expression>) {
    const ts = modules.typescript;

    const fields = parseSqlQuery(strings.join('"EXPRESSION"'));
    return ts.createTypeReferenceNode(
      ts.createIdentifier('SqlQuery'), // typeName
      ts.createNodeArray([ // typeArguments
        ts.createTypeLiteralNode(Object.keys(fields).map(field => {
          return ts.createPropertySignature(
            ts.createIdentifier(field),
            undefined, // question token
            convertSqlTypeToTypeScriptType(fields[field]), // type
            undefined, // initializer
          )
        }))
      ])
    );
  }
};
```

You could then define `querySQL` like:

```ts
define function querySQL<TResult>(query: SqlQuery<TResult>): Promise<TResult>;
```

## Language Feature Checklist

 * Syntactic - no new changes
 * Semantic
   - When a tagged template is encountered by the type-checker
      1. See if a `taggedTemplateHandlers` has been registered for that tag
      2. Call that `taggedTemplateHandler` if one exists.
      3. Use the `TypeNode` returned by the `taggedTemplateHandler` in place of the default behaviour.
 * Emit - no new changes
 * Compatibility - no new syntax is added/changed, so it should be fully backwards/forwards compatible.
 * Other
    - I expect there will be some performance impact, but hopefully the fact that both plugins and the compiler are written in typescript should make this minimal.
    - Ideally, this information would be used for autocomplete helpers as well as in the typechecker, I do not know if that would require extra work.


I'm happy to do my best to help implement this, but I would need some pointers on where to start.

P.S. would it be possible to pass in the **type** of the expressions, in place of the actual expressions themselves?