Skip to content

Commit

Permalink
feat: explicitly curry all functions (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
ForbesLindesay authored May 25, 2020
1 parent 7fba472 commit aea4d8f
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 90 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,27 @@ $ npm install babel-walk
var walk = require('babel-walk');
```

### walk.simple(node, visitors, state)
### walk.simple(visitors)(node, state)

Do a simple walk over the AST. `node` should be the AST node to walk, and `visitors` an object containing Babel [visitors]. Each visitor function will be called as `(node, state)`, where `node` is the AST node, and `state` is the same `state` passed to `walk.simple`.

When `walk.simple` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to [cache your visitors] and communicate state leveraging the `state` parameter. (One difference between the linked article and babel-walk is that the state is only accessible through the `state` variable, never as `this`.)
When `walk.simple` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to cache the result of calling `walk.simple(visitors)` and communicate state leveraging the `state` parameter.

All [babel-types] aliases (e.g. `Expression`) work, but the union syntax (e.g. `'Identifier|AssignmentPattern'(node, state) {}`) does not.

### walk.ancestor(node, visitors, state)
### walk.ancestor(visitors)(node, state)

Do a simple walk over the AST, but memoizing the ancestors of the node and making them available to the visitors. `node` should be the AST node to walk, and `visitors` an object containing Babel [visitors]. Each visitor function will be called as `(node, state, ancestors)`, where `node` is the AST node, `state` is the same `state` passed to `walk.ancestor`, and `ancestors` is an array of ancestors to the node (with the outermost node being `[0]` and the current node being `[ancestors.length - 1]`). If `state` is not specified in the call to `walk.ancestor`, the `state` parameter will be set to `ancestors`.

When `walk.ancestor` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to [cache your visitors] and communicate state leveraging the `state` parameter. (One difference between the linked article and babel-walk is that the state is only accessible through the `state` variable, never as `this`.)
When `walk.ancestor` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to cache the result of calling `walk.ancestor(visitors)` and communicate state leveraging the `state` parameter.

All [babel-types] aliases (e.g. `Expression`) work, but the union syntax (e.g. `'Identifier|AssignmentPattern'(node, state) {}`) does not.

### walk.recursive(node, visitors, state)
### walk.recursive(visitors)(node, state)

Do a recursive walk over the AST, where the visitors are responsible for continuing the walk on the child nodes of their target node. `node` should be the AST node to walk, and `visitors` an object containing Babel [visitors]. Each visitor function will be called as `(node, state, c)`, where `node` is the AST node, `state` is the same `state` passed to `walk.recursive`, and `c` is a function that takes a single node as argument and continues walking _that_ node. If no visitor for a node is provided, the default walker algorithm will still be used.

When `walk.recursive` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to [cache your visitors] and communicate state leveraging the `state` parameter. (One difference between the linked article and babel-walk is that the state is only accessible through the `state` variable, never as `this`.)
When `walk.recursive` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to cache the result of calling `walk.recursive(visitors)` and communicate state leveraging the `state` parameter.

Unlike other babel-walk walkers, `walk.recursive` does not call the `exit` visitor, only the `enter` (the default) visitor, of a specific node type.

Expand All @@ -61,7 +61,7 @@ import * as t from 'babel-types';
import {parse} from 'babel';
import * as walk from 'babel-walk';

const visitors = {
const visitors = walk.recursive({
Statement(node, state, c) {
if (t.isVariableDeclaration(node)) {
for (let declarator of node.declarations) {
Expand All @@ -78,13 +78,13 @@ const visitors = {
state.counter++;
}
},
};
});

function countFunctions(node) {
const state = {
counter: 0,
};
walk.recursive(node, visitors, state);
visitors(node, state);
return state.counter;
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
},
"devDependencies": {
"@forbeslindesay/tsconfig": "^2.0.0",
"@types/node": "^14.0.5",
"prettier": "^2.0.5",
"rimraf": "^3.0.2",
"tslint": "^6.1.2",
Expand Down
4 changes: 0 additions & 4 deletions src/explode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ if (
* 3. make the enter and exit handlers arrays, so that multiple handlers can be merged
*/
export default function explode(input: any): any {
if (input._babel_walk_exploded) {
return input._babel_walk_exploded;
}
const results: any = {};
for (const key in input) {
const aliases = FLIPPED_ALIAS_KEYS[key];
Expand Down Expand Up @@ -93,6 +90,5 @@ export default function explode(input: any): any {
}
}
}
input._babel_walk_exploded = results;
return results;
}
148 changes: 71 additions & 77 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,40 +38,38 @@ export type SimpleVisitors<TState = void> = {
};
};

export function simple<TState>(
node: t.Node,
visitors: SimpleVisitors<TState>,
state: TState,
) {
export function simple<TState = void>(visitors: SimpleVisitors<TState>) {
const vis = explode(visitors);
(function recurse(node) {
if (!node) return;
return (node: t.Node, state: TState) => {
(function recurse(node) {
if (!node) return;

const visitor = vis[node.type];
const visitor = vis[node.type];

if (visitor?.enter) {
for (const v of visitor.enter) {
v(node, state);
if (visitor?.enter) {
for (const v of visitor.enter) {
v(node, state);
}
}
}

for (const key of VISITOR_KEYS[node.type] || []) {
const subNode = (node as any)[key];
if (Array.isArray(subNode)) {
for (const subSubNode of subNode) {
recurse(subSubNode);
for (const key of VISITOR_KEYS[node.type] || []) {
const subNode = (node as any)[key];
if (Array.isArray(subNode)) {
for (const subSubNode of subNode) {
recurse(subSubNode);
}
} else {
recurse(subNode);
}
} else {
recurse(subNode);
}
}

if (visitor?.exit) {
for (const v of visitor.exit) {
v(node, state);
if (visitor?.exit) {
for (const v of visitor.exit) {
v(node, state);
}
}
}
})(node);
})(node);
};
}

export type AncestorFunction<TKey extends string, TState> = (
Expand All @@ -89,47 +87,45 @@ export type AncestorVisitor<TState = void> = {
};
};

export function ancestor<TState = void>(
node: t.Node,
visitors: AncestorVisitor<TState>,
state: TState,
) {
export function ancestor<TState = void>(visitors: AncestorVisitor<TState>) {
const vis = explode(visitors);
const ancestors: t.Node[] = [];
return (node: t.Node, state: TState) => {
const ancestors: t.Node[] = [];

(function recurse(node) {
if (!node) return;
(function recurse(node) {
if (!node) return;

const visitor = vis[node.type];
const visitor = vis[node.type];

const isNew = node !== ancestors[ancestors.length - 1];
if (isNew) ancestors.push(node);
const isNew = node !== ancestors[ancestors.length - 1];
if (isNew) ancestors.push(node);

if (visitor?.enter) {
for (const v of visitor.enter) {
v(node, state, ancestors);
if (visitor?.enter) {
for (const v of visitor.enter) {
v(node, state, ancestors);
}
}
}

for (const key of VISITOR_KEYS[node.type] || []) {
const subNode = (node as any)[key];
if (Array.isArray(subNode)) {
for (const subSubNode of subNode) {
recurse(subSubNode);
for (const key of VISITOR_KEYS[node.type] || []) {
const subNode = (node as any)[key];
if (Array.isArray(subNode)) {
for (const subSubNode of subNode) {
recurse(subSubNode);
}
} else {
recurse(subNode);
}
} else {
recurse(subNode);
}
}

if (visitor?.exit) {
for (const v of visitor.exit) {
v(node, state, ancestors);
if (visitor?.exit) {
for (const v of visitor.exit) {
v(node, state, ancestors);
}
}
}

if (isNew) ancestors.pop();
})(node);
if (isNew) ancestors.pop();
})(node);
};
}

export type RecursiveVisitors<TState = void> = {
Expand All @@ -140,31 +136,29 @@ export type RecursiveVisitors<TState = void> = {
) => void;
};

export function recursive<TState>(
node: t.Node,
visitors: RecursiveVisitors<TState>,
state: TState,
) {
export function recursive<TState = void>(visitors: RecursiveVisitors<TState>) {
const vis = explode(visitors);
(function recurse(node: t.Node) {
if (!node) return;

const visitor = vis[node.type];
if (visitor?.enter) {
for (const v of visitor.enter) {
v(node, state, recurse);
}
} else {
for (const key of VISITOR_KEYS[node.type] || []) {
const subNode = (node as any)[key];
if (Array.isArray(subNode)) {
for (const subSubNode of subNode) {
recurse(subSubNode);
return (node: t.Node, state: TState) => {
(function recurse(node: t.Node) {
if (!node) return;

const visitor = vis[node.type];
if (visitor?.enter) {
for (const v of visitor.enter) {
v(node, state, recurse);
}
} else {
for (const key of VISITOR_KEYS[node.type] || []) {
const subNode = (node as any)[key];
if (Array.isArray(subNode)) {
for (const subSubNode of subNode) {
recurse(subSubNode);
}
} else {
recurse(subNode);
}
} else {
recurse(subNode);
}
}
}
})(node);
})(node);
};
}
15 changes: 15 additions & 0 deletions src/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as t from '@babel/types';
import {ancestor} from './';

ancestor({})(t.program([]), undefined);
ancestor({
Function(node) {
console.info(node);
},
})(t.program([]), undefined);

ancestor<string>({
Function(node, state) {
console.info(node, state);
},
})(t.program([]), 'hello world');
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
resolved "https://registry.yarnpkg.com/@forbeslindesay/tsconfig/-/tsconfig-2.0.0.tgz#71a8a92afb366ea7ca05fe46e68bc033060c2e2d"
integrity sha512-SqkFDsM1vgB5iXCjJKnnvYwIlQZhLfGjKQfmwp3PZjvqoDVbng76iQvppJAG1pX2nSmkPz8Z1rmEylXop/Ma8A==

"@types/node@^14.0.5":
version "14.0.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.5.tgz#3d03acd3b3414cf67faf999aed11682ed121f22b"
integrity sha512-90hiq6/VqtQgX8Sp0EzeIsv3r+ellbGj4URKj5j30tLlZvRUpnAe9YbYnjl3pJM93GyXU0tghHhvXHq+5rnCKA==

ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
Expand Down

0 comments on commit aea4d8f

Please sign in to comment.