Skip to content

Instantly share code, notes, and snippets.

@Gaelan
Last active September 21, 2015 14:37
Show Gist options
  • Save Gaelan/cdacb0cb7c19b0fd0860 to your computer and use it in GitHub Desktop.
Save Gaelan/cdacb0cb7c19b0fd0860 to your computer and use it in GitHub Desktop.

This one might be waaaaaay out of scope, but I think it is worth proposing. The idea here is to add support for macros, functions that run at compile time, taking one or more AST nodes and returning an AST node or array of AST nodes. Examples/use cases: (Syntax off the top of my head, open to better ideas)

Interface-Based Validation

// validation.macros.ts
function interfaceToValidatorCreator(interface: ts.InterfaceDeclaration): ts.Statement {
    // Would return something like "const [interface.name]Validator = new Validator({...});",
    // using types from the interface
}
macro CreateValidator(interface: ts.InterfaceDeclaration) {
    return [interface, interfaceToValidator(interface)];
}
// mainFile.ts
/// <macros path='./valididation.macros.ts' />
@#CreateValidator // Syntax 1: Decorator-Style
interface Person {
    name: string;
    age: number;
}

PersonValidator.validate(foo)

Type Providers

// swagger.macros.ts
macro GetSwaggerClient(url: ts.StringLiteral): AssertionExpression {
    // return something like "new SwaggerClient([url]) as SwaggerClientBase & {...}" where
    // ... is an object creating the methods generated from the URL.
}
// mainFile.ts
/// <macros path='./swagger.macros.ts' />
var fooClient = #GetSwaggerClient("http://foo.com/swagger.json"); // Syntax 2: Function call syntax
fooClient.getPeople((people) => {
    people.every((person) => console.log(person.firstName + "," + person.lastName);
});

Conditional Compilation

// conditional-compilation.macros.ts
macro IfFlagSet(flagName: ts.StringLiteral, code: ts.Statement[]): ts.Statement[] {
    return process.env[flagName.string] ? code : []
}
// mainFile.ts
/// <macros path='./compilation.macros.ts' />
#IfFlagSet("DEVELOPMENT") { // Syntax 3: Language Construct-Like (multiple arguments can be passed in parentheses)
    expensiveUnnecessarySanityCheck()
}

Notes

  • Macros would run right after parsing. Not sure how we would deal with macros that need type information.
  • This would make running tsc on unknown code as dangerous as running unknown code. It might be good to require a --unsafeAllowMacros argument, not settable from a tsconfig.json.
  • It might be worth nothing in the docs that the AST format may change at any time, or something along those likes
  • The macro keyword would probably compile to a function, followed by a ts.registerMacro(function, argumentTypes, returnType call.
  • Macros must be typed as returning a AST interface. This means that functions creating ASTs will probably need to have an explicit return type (or a calling function could have an explicit return type.
    • Alternatively, we could consider giving the kind property special treatment in macros.ts files.
  • Just because a proposed syntax looks like a normal typescript construct doesn't mean it behaves like one. #Foo(interface Bar{}) is valid syntax, as long as there is a macro named Foo that takes an interface.
    • Exception: The Decorator syntax might need to be a bit more choosy (no decorating 1 + 1, but decorating Interfaces, interface items, functions, etc. should be fine.
  • This issue is likely to be updated quite a bit. For a log of changes, see the gist
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment