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)
// 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)
// 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.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()
}
- 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 atsconfig.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 ats.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 inmacros.ts
files.
- Alternatively, we could consider giving the
- 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 namedFoo
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.
- Exception: The Decorator syntax might need to be a bit more choosy (no decorating
- This issue is likely to be updated quite a bit. For a log of changes, see the gist