-
Notifications
You must be signed in to change notification settings - Fork 187
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[BREAKING] Use plain functions for AST plugins.
This changes AST Plugins to be "plain functions" with the following interface: ```ts import { AST, Syntax } from '@glimmer/syntax'; export type NodeVisitor = { [P in keyof AST.Nodes]?: NodeHandler<AST.Nodes[P]>; }; export interface ASTPluginResult { name: string; visitors: NodeVisitor; } export interface ASTPlugin { (env: ASTPluginEnvironment): ASTPluginResult; } export interface ASTPluginEnvironment { meta?: any; syntax: Syntax; } ``` The current system of instantiation is fairly confusing and makes a number of use cases pretty difficult (where you need to preserve some other outside state that you access from within your plugin's `transform`). The old API can easily be reimplemented in terms of the plain functions in consumers (e.g. Ember for compatibility): ```js let uuid = 0; function ensurePlugin(FunctionOrPlugin: any): ASTPlugin { if (FunctionOrPlugin.prototype && FunctionOrPlugin.prototype.transform) { return (env) => { return { name: 'legacy-transform' + (++uuid) , visitors: { Program(node: AST.Program) { let plugin = new FunctionOrPlugin(env); plugin.syntax = env.syntax; return plugin.transform(node); } } }; }; } else { return FunctionOrPlugin; } } ``` A test exists for this (to ensure Ember can provide backwards compat).
- Loading branch information
Showing
5 changed files
with
134 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,10 +6,11 @@ import * as AST from "../types/nodes"; | |
import SyntaxError from '../errors/syntax-error'; | ||
import { Tag } from "../parser"; | ||
import builders from "../builders"; | ||
import traverse from "../traversal/traverse"; | ||
import traverse, { NodeVisitor } from "../traversal/traverse"; | ||
import print from "../generation/print"; | ||
import Walker from "../traversal/walker"; | ||
import * as handlebars from "handlebars"; | ||
import { assign } from '@glimmer/util'; | ||
|
||
const voidMap: { | ||
[tagName: string]: boolean | ||
|
@@ -307,38 +308,43 @@ export const syntax: Syntax = { | |
}; | ||
|
||
/** | ||
* TransformASTPlugins can make changes to the Glimmer template AST before | ||
* compilation begins. Plugins implement a `transform()` method that takes a | ||
* `Program` AST node and should return the modified program. | ||
*/ | ||
export interface TransformASTPlugin { | ||
syntax: Syntax; | ||
transform(program: AST.Program): AST.Program; | ||
ASTPlugins can make changes to the Glimmer template AST before | ||
compilation begins. | ||
*/ | ||
export interface ASTPlugin { | ||
(env: ASTPluginEnvironment): ASTPluginResult; | ||
} | ||
|
||
export interface TransformASTPluginFactory { | ||
new (options: PreprocessOptions): TransformASTPlugin; | ||
export interface ASTPluginResult { | ||
name: string; | ||
visitors: NodeVisitor; | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
mmun
Member
|
||
} | ||
|
||
export interface ASTPluginEnvironment { | ||
meta?: any; | ||
syntax: Syntax; | ||
} | ||
|
||
export interface PreprocessOptions { | ||
plugins?: { | ||
ast?: TransformASTPluginFactory[] | ||
ast?: ASTPlugin[]; | ||
}; | ||
} | ||
|
||
export function preprocess(html: string, options?: PreprocessOptions): AST.Program { | ||
let ast = (typeof html === 'object') ? html : handlebars.parse(html); | ||
let combined = new TokenizerEventHandlers(html, options).acceptNode(ast); | ||
let program = new TokenizerEventHandlers(html, options).acceptNode(ast); | ||
|
||
if (options && options.plugins && options.plugins.ast) { | ||
for (let i = 0, l = options.plugins.ast.length; i < l; i++) { | ||
let plugin = new options.plugins.ast[i](options); | ||
let transform = options.plugins.ast[i]; | ||
let env = assign({}, options, { syntax }, { plugins: undefined }); | ||
|
||
plugin.syntax = syntax; | ||
let pluginResult = transform(env); | ||
|
||
combined = plugin.transform(combined); | ||
traverse(program, pluginResult.visitors); | ||
} | ||
} | ||
|
||
return combined; | ||
} | ||
return program; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,118 +1,134 @@ | ||
import { preprocess as parse, Walker, Syntax, AST } from '@glimmer/syntax'; | ||
import { | ||
preprocess, | ||
Syntax, | ||
Walker, | ||
AST, | ||
ASTPlugin | ||
} from '@glimmer/syntax'; | ||
|
||
const { test } = QUnit; | ||
|
||
QUnit.module('[glimmer-syntax] Plugins - AST Transforms'); | ||
|
||
test('AST plugins can be provided to the compiler', assert => { | ||
test('function based AST plugins can be provided to the compiler', assert => { | ||
assert.expect(1); | ||
|
||
class Plugin { | ||
syntax: Syntax; | ||
|
||
transform(program: AST.Program): AST.Program { | ||
assert.ok(true, 'transform was called!'); | ||
return program; | ||
} | ||
} | ||
|
||
parse('<div></div>', { | ||
preprocess('<div></div>', { | ||
plugins: { | ||
ast: [ Plugin ] | ||
ast: [ | ||
() => ({ | ||
name: 'plugin-a', | ||
visitors: { | ||
Program() { | ||
assert.ok(true, 'transform was called!'); | ||
} | ||
} | ||
}) | ||
] | ||
} | ||
}); | ||
}); | ||
|
||
test('provides syntax package as `syntax` prop if value is null', assert => { | ||
test('plugins are provided the syntax package', assert => { | ||
assert.expect(1); | ||
|
||
class Plugin { | ||
syntax: Syntax; | ||
transform(program: AST.Program): AST.Program { | ||
assert.equal(this.syntax.Walker, Walker); | ||
return program; | ||
} | ||
} | ||
|
||
parse('<div></div>', { | ||
preprocess('<div></div>', { | ||
plugins: { | ||
ast: [ Plugin ] | ||
ast: [ | ||
({ syntax }) => { | ||
assert.equal(syntax.Walker, Walker); | ||
|
||
return { name: 'plugin-a', visitors: {} }; | ||
} | ||
] | ||
} | ||
}); | ||
}); | ||
|
||
test('AST plugins can modify the AST', assert => { | ||
assert.expect(1); | ||
test('can support the legacy AST transform API via ASTPlugin', assert => { | ||
function ensurePlugin(FunctionOrPlugin: any): ASTPlugin { | ||
if (FunctionOrPlugin.prototype && FunctionOrPlugin.prototype.transform) { | ||
return (env) => { | ||
return { | ||
name: 'plugin-a', | ||
|
||
visitors: { | ||
Program(node: AST.Program) { | ||
let plugin = new FunctionOrPlugin(env); | ||
|
||
plugin.syntax = env.syntax; | ||
|
||
return plugin.transform(node); | ||
} | ||
} | ||
}; | ||
}; | ||
} else { | ||
return FunctionOrPlugin; | ||
} | ||
} | ||
|
||
class Plugin { | ||
syntax: Syntax; | ||
transform(): AST.Program { | ||
return { | ||
type: 'Program', | ||
body: [], | ||
blockParams: [], | ||
isSynthetic: true, | ||
loc: { | ||
start: { line: 0, column: 0 }, | ||
end: { line: 0, column: 1 } | ||
} | ||
} as AST.Program; | ||
|
||
transform(program: AST.Program): AST.Program { | ||
assert.ok(true, 'transform was called!'); | ||
return program; | ||
} | ||
} | ||
|
||
let ast = parse('<div></div>', { | ||
preprocess('<div></div>', { | ||
plugins: { | ||
ast: [ Plugin ] | ||
ast: [ ensurePlugin(Plugin) ] | ||
} | ||
}); | ||
|
||
assert.ok(ast['isSynthetic'], 'return value from AST transform is used'); | ||
}); | ||
|
||
test('AST plugins can be chained', assert => { | ||
assert.expect(2); | ||
|
||
class Plugin { | ||
syntax: Syntax; | ||
transform(): AST.Program { | ||
return { | ||
type: 'Program', | ||
body: [], | ||
blockParams: [], | ||
isFromFirstPlugin: true, | ||
loc: { | ||
start: { line: 0, column: 0 }, | ||
end: { line: 0, column: 1 } | ||
assert.expect(3); | ||
|
||
let first = () => { | ||
return { | ||
name: 'first', | ||
visitors: { | ||
Program(program: AST.Program) { | ||
program['isFromFirstPlugin'] = true; | ||
} | ||
} as AST.Program; | ||
} | ||
} | ||
|
||
class SecondaryPlugin { | ||
syntax: Syntax; | ||
transform(program: AST.Program) { | ||
assert.equal(program['isFromFirstPlugin'], true, 'AST from first plugin is passed to second'); | ||
return { | ||
type: 'Program', | ||
body: [], | ||
blockParams: [], | ||
isFromSecondPlugin: true, | ||
loc: { | ||
start: { line: 0, column: 0 }, | ||
end: { line: 0, column: 1 } | ||
} | ||
}; | ||
}; | ||
|
||
let second = () => { | ||
return { | ||
name: 'second', | ||
visitors: { | ||
Program(node: AST.Program) { | ||
assert.equal(node['isFromFirstPlugin'], true, 'AST from first plugin is passed to second'); | ||
|
||
node['isFromSecondPlugin'] = true; | ||
} | ||
} as AST.Program; | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
|
||
let third = () => { | ||
return { | ||
name: 'third', | ||
visitors: { | ||
Program(node: AST.Program) { | ||
assert.equal(node['isFromSecondPlugin'], true, 'AST from second plugin is passed to third'); | ||
|
||
node['isFromThirdPlugin'] = true; | ||
} | ||
} | ||
}; | ||
}; | ||
|
||
let ast = parse('<div></div>', { | ||
let ast = preprocess('<div></div>', { | ||
plugins: { | ||
ast: [ | ||
Plugin, | ||
SecondaryPlugin | ||
] | ||
ast: [first, second, third] | ||
} | ||
}); | ||
|
||
assert.equal(ast['isFromSecondPlugin'], true, 'return value from last AST transform is used'); | ||
assert.equal(ast['isFromThirdPlugin'], true, 'return value from last AST transform is used'); | ||
}); |
The plural here is confusing and seems to imply
NodeVisitor[]
instead ofNodeVisitor
.