refactor(compiler): add optional `visit()` to html AST `Visitor` (#12135)
This commit is contained in:
parent
e1faca6386
commit
4202936bbf
|
@ -54,6 +54,10 @@ export class Comment implements Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Visitor {
|
export interface Visitor {
|
||||||
|
// Returning a truthy value from `visit()` will prevent `visitAll()` from the call to the typed
|
||||||
|
// method and result returned will become the result included in `visitAll()`s result array.
|
||||||
|
visit?(node: Node, context: any): any;
|
||||||
|
|
||||||
visitElement(element: Element, context: any): any;
|
visitElement(element: Element, context: any): any;
|
||||||
visitAttribute(attribute: Attribute, context: any): any;
|
visitAttribute(attribute: Attribute, context: any): any;
|
||||||
visitText(text: Text, context: any): any;
|
visitText(text: Text, context: any): any;
|
||||||
|
@ -64,8 +68,12 @@ export interface Visitor {
|
||||||
|
|
||||||
export function visitAll(visitor: Visitor, nodes: Node[], context: any = null): any[] {
|
export function visitAll(visitor: Visitor, nodes: Node[], context: any = null): any[] {
|
||||||
let result: any[] = [];
|
let result: any[] = [];
|
||||||
|
|
||||||
|
let visit = visitor.visit ?
|
||||||
|
(ast: Node) => visitor.visit(ast, context) || ast.visit(visitor, context) :
|
||||||
|
(ast: Node) => ast.visit(visitor, context);
|
||||||
nodes.forEach(ast => {
|
nodes.forEach(ast => {
|
||||||
const astResult = ast.visit(visitor, context);
|
const astResult = visit(ast);
|
||||||
if (astResult) {
|
if (astResult) {
|
||||||
result.push(astResult);
|
result.push(astResult);
|
||||||
}
|
}
|
||||||
|
|
|
@ -390,6 +390,70 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('visitor', () => {
|
||||||
|
it('should visit text nodes', () => {
|
||||||
|
const result = humanizeDom(parser.parse('text', 'TestComp'));
|
||||||
|
expect(result).toEqual([[html.Text, 'text', 0]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should visit element nodes', () => {
|
||||||
|
const result = humanizeDom(parser.parse('<div></div>', 'TestComp'));
|
||||||
|
expect(result).toEqual([[html.Element, 'div', 0]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should visit attribute nodes', () => {
|
||||||
|
const result = humanizeDom(parser.parse('<div id="foo"></div>', 'TestComp'));
|
||||||
|
expect(result).toContain([html.Attribute, 'id', 'foo']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should visit all nodes', () => {
|
||||||
|
const result =
|
||||||
|
parser.parse('<div id="foo"><span id="bar">a</span><span>b</span></div>', 'TestComp');
|
||||||
|
const accumulator: html.Node[] = [];
|
||||||
|
const visitor = new class {
|
||||||
|
visit(node: html.Node, context: any) { accumulator.push(node); }
|
||||||
|
visitElement(element: html.Element, context: any): any {
|
||||||
|
html.visitAll(this, element.attrs);
|
||||||
|
html.visitAll(this, element.children);
|
||||||
|
}
|
||||||
|
visitAttribute(attribute: html.Attribute, context: any): any {}
|
||||||
|
visitText(text: html.Text, context: any): any {}
|
||||||
|
visitComment(comment: html.Comment, context: any): any {}
|
||||||
|
visitExpansion(expansion: html.Expansion, context: any): any {
|
||||||
|
html.visitAll(this, expansion.cases);
|
||||||
|
}
|
||||||
|
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any {}
|
||||||
|
};
|
||||||
|
|
||||||
|
html.visitAll(visitor, result.rootNodes);
|
||||||
|
expect(accumulator.map(n => n.constructor)).toEqual([
|
||||||
|
html.Element, html.Attribute, html.Element, html.Attribute, html.Text, html.Element,
|
||||||
|
html.Text
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip typed visit if visit() returns a truthy value', () => {
|
||||||
|
const visitor = new class {
|
||||||
|
visit(node: html.Node, context: any) { return true; }
|
||||||
|
visitElement(element: html.Element, context: any): any { throw Error('Unexpected'); }
|
||||||
|
visitAttribute(attribute: html.Attribute, context: any): any {
|
||||||
|
throw Error('Unexpected');
|
||||||
|
}
|
||||||
|
visitText(text: html.Text, context: any): any { throw Error('Unexpected'); }
|
||||||
|
visitComment(comment: html.Comment, context: any): any { throw Error('Unexpected'); }
|
||||||
|
visitExpansion(expansion: html.Expansion, context: any): any {
|
||||||
|
throw Error('Unexpected');
|
||||||
|
}
|
||||||
|
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any {
|
||||||
|
throw Error('Unexpected');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const result = parser.parse('<div id="foo"></div><div id="bar"></div>', 'TestComp');
|
||||||
|
const traversal = html.visitAll(visitor, result.rootNodes);
|
||||||
|
expect(traversal).toEqual([true, true]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
it('should report unexpected closing tags', () => {
|
it('should report unexpected closing tags', () => {
|
||||||
let errors = parser.parse('<div></p></div>', 'TestComp').errors;
|
let errors = parser.parse('<div></p></div>', 'TestComp').errors;
|
||||||
|
|
Loading…
Reference in New Issue