abstract-syntax-tree
Advanced tools
Comparing version 1.1.2 to 2.0.0
# abstract-syntax-tree changelog | ||
## 2.0.0 | ||
* add: static methods: find, each, first, last, count, has, remove, equal and traverse | ||
* add: getters -> type, body, source and map | ||
* edit: replace method now accepts options object instead of a callback | ||
* edit: removed the `is` method, please use static `equal` method instead | ||
* edit: ast property has been replaced with _tree | ||
* remove: `minify` and `beautify` methods | ||
* remove: `toSource`, `toSourceMap` and `toString` methods | ||
## 1.1.2 | ||
@@ -4,0 +14,0 @@ |
238
index.js
@@ -1,207 +0,171 @@ | ||
const cherow = require('cherow') | ||
const esquery = require('esquery') | ||
const astring = require('astring') | ||
const estraverse = require('estraverse') | ||
const template = require('estemplate') | ||
const comparify = require('comparify') | ||
const toAST = require('to-ast') | ||
const prettier = require('prettier') | ||
const sourcemap = require('source-map') | ||
const find = require('./src/find') | ||
const each = require('./src/each') | ||
const first = require('./src/first') | ||
const last = require('./src/last') | ||
const count = require('./src/count') | ||
const equal = require('./src/equal') | ||
const has = require('./src/has') | ||
const remove = require('./src/remove') | ||
const prepend = require('./src/prepend') | ||
const append = require('./src/append') | ||
const replace = require('./src/replace') | ||
const walk = require('./src/walk') | ||
const traverse = require('./src/traverse') | ||
const generate = require('./src/generate') | ||
const parse = require('./src/parse') | ||
const template = require('./src/template') | ||
const sourcemap = require('./src/sourcemap') | ||
const mark = require('./src/mark') | ||
class AbstractSyntaxTree { | ||
constructor (source, options) { | ||
options = options || {} | ||
if (typeof source === 'string') { | ||
this.source = source | ||
this.ast = this.constructor.parse(source, { | ||
loc: true, | ||
jsx: options.jsx | ||
}) | ||
} else { | ||
this.ast = source | ||
} | ||
static find (tree, selector) { | ||
return find(tree, selector) | ||
} | ||
query (node, selector) { | ||
return esquery(node, selector) | ||
static each (tree, selector, callback) { | ||
return each(tree, selector, callback) | ||
} | ||
find (selector, options) { | ||
return this.query(this.ast, selector, options) | ||
static first (tree, selector) { | ||
return first(tree, selector) | ||
} | ||
each (selector, callback) { | ||
return this.find(selector).forEach(callback) | ||
static last (tree, selector) { | ||
return last(tree, selector) | ||
} | ||
first (selector) { | ||
return this.find(selector)[0] | ||
static count (tree, selector) { | ||
return count(tree, selector) | ||
} | ||
last (selector) { | ||
var nodes = this.find(selector) | ||
return nodes[nodes.length - 1] | ||
static has (tree, selector) { | ||
return has(tree, selector) | ||
} | ||
count (selector) { | ||
return this.find(selector).length | ||
static remove (tree, target, options) { | ||
return remove(tree, target, options) | ||
} | ||
has (selector) { | ||
return this.count(selector) > 0 | ||
static prepend (tree, node) { | ||
return prepend(tree, node) | ||
} | ||
is (node, expected) { | ||
return comparify(node, expected) | ||
static append (tree, node) { | ||
return append(tree, node) | ||
} | ||
remove (target, options) { | ||
options = options || {} | ||
if (typeof target === 'string') { | ||
return this.removeBySelector(target, options) | ||
} | ||
this.removeByNode(target, options) | ||
static equal (node1, node2) { | ||
return equal(node1, node2) | ||
} | ||
removeBySelector (target, options) { | ||
var nodes = this.find(target) | ||
// this could be improved by traversing once and | ||
// comparing the current node to the found nodes | ||
// one by one while making the array of nodes smaller too | ||
nodes.forEach(node => this.removeByNode(node, options)) | ||
static generate (tree, options) { | ||
return generate(tree, options) | ||
} | ||
removeByNode (node, options) { | ||
var count = 0 | ||
estraverse.replace(this.ast, { | ||
enter: function (current, parent) { | ||
if (options.first && count === 1) { | ||
return this.break() | ||
} | ||
if (comparify(current, node)) { | ||
count += 1 | ||
return this.remove() | ||
} | ||
}, | ||
leave: function (current, parent) { | ||
if (current.expression === null || | ||
(current.type === 'VariableDeclaration' && current.declarations.length === 0)) { | ||
return this.remove() | ||
} | ||
} | ||
}) | ||
static parse (source, options) { | ||
return parse(source, options) | ||
} | ||
walk (callback) { | ||
return estraverse.traverse(this.ast, { enter: callback }) | ||
static walk (tree, callback) { | ||
return walk(tree, callback) | ||
} | ||
traverse (options) { | ||
return estraverse.traverse(this.ast, options) | ||
static traverse (tree, callback) { | ||
return traverse(tree, callback) | ||
} | ||
replace (options) { | ||
return replace(this.ast, options) | ||
static replace (tree, callback) { | ||
return replace(tree, callback) | ||
} | ||
prepend (node) { | ||
this.ast.body.unshift(node) | ||
static template (source, options) { | ||
return template(source, options) | ||
} | ||
append (node) { | ||
this.ast.body.push(node) | ||
constructor (source, options = {}) { | ||
if (typeof source === 'string') { | ||
this._tree = typeof source === 'string' ? parse(source, { loc: true, ...options }) : source | ||
} else { | ||
this._tree = source | ||
} | ||
} | ||
body () { | ||
return this.ast.body | ||
get type () { | ||
return this._tree.type | ||
} | ||
wrap (callback) { | ||
this.ast.body = callback(this.ast.body) | ||
get body () { | ||
return this._tree.body | ||
} | ||
unwrap () { | ||
let block = this.first('BlockStatement') | ||
this.ast.body = block.body | ||
get source () { | ||
return generate(this._tree) | ||
} | ||
template (source, options) { | ||
options = options || {} | ||
if (typeof source === 'string') { | ||
return template(source, options).body | ||
} | ||
return toAST(source, options) | ||
get map () { | ||
return sourcemap(this._tree).map | ||
} | ||
beautify (source, options) { | ||
if (typeof options === 'boolean') { options = {} } | ||
options = { parser: 'babylon', ...options } | ||
return prettier.format(source, options) | ||
set body (body) { | ||
this._tree.body = body | ||
} | ||
mark () { | ||
let cid = 1 | ||
this.walk(node => { | ||
node.cid = cid | ||
cid += 1 | ||
}) | ||
find (selector) { | ||
return find(this._tree, selector) | ||
} | ||
minify (ast) { | ||
return ast | ||
each (selector, callback) { | ||
return each(this._tree, selector, callback) | ||
} | ||
toSource (options) { | ||
options = options || {} | ||
first (selector) { | ||
return first(this._tree, selector) | ||
} | ||
if (options.minify) { | ||
this.ast = this.minify(this.ast) | ||
} | ||
last (selector) { | ||
return last(this._tree, selector) | ||
} | ||
var source = this.constructor.generate(this.ast) | ||
count (selector) { | ||
return count(this._tree, selector) | ||
} | ||
if (options.beautify) { | ||
source = this.beautify(source, options.beautify) | ||
} | ||
has (selector) { | ||
return has(this._tree, selector) | ||
} | ||
var map | ||
if (options.sourceMap) { | ||
map = this.toSourceMap(options) | ||
} | ||
remove (target, options) { | ||
return remove(this._tree, target, options) | ||
} | ||
this.source = source | ||
walk (callback) { | ||
return walk(this._tree, callback) | ||
} | ||
if (map) { return { map, source } } | ||
return source | ||
traverse (options) { | ||
return traverse(this._tree, options) | ||
} | ||
toSourceMap (options) { | ||
const map = new sourcemap.SourceMapGenerator({ | ||
file: options.sourceFile || 'UNKNOWN' | ||
}) | ||
this.constructor.generate(this.ast, { | ||
sourceMap: map | ||
}) | ||
return map.toString() | ||
replace (options) { | ||
return replace(this._tree, options) | ||
} | ||
toString (options) { | ||
return this.toSource(options) | ||
prepend (node) { | ||
return prepend(this._tree, node) | ||
} | ||
static generate (tree, options) { | ||
return astring.generate(tree, options) | ||
append (node) { | ||
return append(this._tree, node) | ||
} | ||
static parse (source, options) { | ||
return cherow.parseModule(source, options) | ||
wrap (callback) { | ||
this._tree.body = callback(this._tree.body) | ||
} | ||
static walk (node, callback) { | ||
return estraverse.traverse(node, { enter: callback }) | ||
unwrap () { | ||
this._tree.body = first(this._tree, 'BlockStatement').body | ||
} | ||
static replace (node, callback) { | ||
return replace(node, { enter: callback }) | ||
mark () { | ||
return mark(this._tree) | ||
} | ||
@@ -208,0 +172,0 @@ } |
{ | ||
"name": "abstract-syntax-tree", | ||
"version": "1.1.2", | ||
"description": "abstract syntax tree class for js", | ||
"version": "2.0.0", | ||
"description": "abstract syntax tree", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "node test/basic.js && node test/examples.js && node test/jsx.js && node test/static.js", | ||
"test": "ava 'test/*.js'", | ||
"lint": "standard", | ||
"benchmark": "node test/benchmark.js", | ||
"memory": "node test/memory.js" | ||
"benchmark": "ava 'test/benchmarks/*.js'", | ||
"coverage": "nyc npm test" | ||
}, | ||
@@ -20,5 +20,5 @@ "engines": { | ||
"keywords": [ | ||
"ast", | ||
"abstract syntax tree", | ||
"javascript", | ||
"abstract syntax tree" | ||
"ast" | ||
], | ||
@@ -36,4 +36,6 @@ "author": "Emil Ajdyna <emil@ajdyna.com>", | ||
"devDependencies": { | ||
"ava": "^1.0.1", | ||
"benchmark": "^2.1.4", | ||
"standard": "^11.0.1" | ||
"nyc": "^13.1.0", | ||
"standard": "^12.0.1" | ||
}, | ||
@@ -48,5 +50,4 @@ "dependencies": { | ||
"source-map": "^0.7.2", | ||
"prettier": "^1.15.1", | ||
"to-ast": "^1.0.0" | ||
} | ||
} |
304
README.md
# abstract-syntax-tree | ||
![npm](https://img.shields.io/npm/v/abstract-syntax-tree.svg) [![Build Status](https://travis-ci.org/buxlabs/abstract-syntax-tree.svg?branch=master)](https://travis-ci.org/buxlabs/ast) | ||
![npm](https://img.shields.io/npm/v/abstract-syntax-tree.svg) ![build](https://img.shields.io/codeship/c6391230-e90c-0136-c202-269c372fd6f7/master.svg) | ||
## What is an abstract syntax tree? | ||
@@ -42,15 +43,2 @@ | ||
## What are abstract syntax trees used for? | ||
They're used e.g. for code's: | ||
- highligthing | ||
- linting | ||
- refactoring | ||
- transformations | ||
- analysis | ||
- minification | ||
- obfuscation | ||
- generation | ||
- source maps | ||
## Installation | ||
@@ -60,135 +48,180 @@ | ||
## Key Features | ||
## How does it work? | ||
- source to ast conversion | ||
- ast to source generation | ||
- ast traversal | ||
- ast manipulation | ||
The library exposes a set of utility methods that can be useful for analysis or transformation of abstract syntax trees. It supports functional and object-oriented programming style. | ||
## Methods | ||
## Examples | ||
### find | ||
```js | ||
const { parse, find } = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = parse(source) | ||
console.log(find(tree, 'Literal')) // [ { type: 'Literal', value: 42 } ] | ||
``` | ||
Find all nodes of given type. | ||
```js | ||
const AbstractSyntaxTree = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = new AbstractSyntaxTree(source) | ||
console.log(tree.find('Literal')) // [ { type: 'Literal', value: 42 } ] | ||
``` | ||
## API | ||
### Static Methods | ||
#### parse | ||
```javascript | ||
const source = 'const a = "x";' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.find('VariableDeclaration') | ||
const { parse } = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = parse(source) | ||
console.log(tree) // { type: 'Program', body: [ ... ] } | ||
``` | ||
### each | ||
#### generate | ||
Iterate over all nodes of given type. | ||
```javascript | ||
const source = 'const a = "x";' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.each('VariableDeclaration', node => {}) | ||
const { parse, generate } = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = parse(source) | ||
console.log(generate(tree)) // 'const answer = 42;' | ||
``` | ||
### has | ||
#### walk | ||
Check if ast contains a node of given type. | ||
```javascript | ||
const source = 'const a = "x";' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.has('VariableDeclaration') | ||
const { parse, walk } = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = parse(source) | ||
walk(tree, (node, parent) => { | ||
console.log(node) | ||
console.log(parent) | ||
}) | ||
``` | ||
### count | ||
#### find | ||
Count ast nodes of given type. | ||
```javascript | ||
const source = 'const a = "x"; const b = "y";' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.count('VariableDeclaration') | ||
const { parse, find } = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = parse(source) | ||
const nodes = find(tree, 'VariableDeclaration') | ||
console.log(nodes) | ||
``` | ||
### first | ||
#### traverse | ||
First first node of given type. | ||
```javascript | ||
const source = 'var a = "x";' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.first('VariableDeclaration') | ||
const { parse, traverse } = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = parse(source) | ||
traverse(tree, { | ||
enter (node) {}, | ||
leave (node) {} | ||
}) | ||
``` | ||
### last | ||
#### replace | ||
Find last node of given type. | ||
```javascript | ||
const source = 'const a = "x";' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.last('VariableDeclaration') | ||
const { parse, replace } = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = parse(source) | ||
replace(tree, { | ||
enter (node) { | ||
if (node.type === 'VariableDeclaration') { | ||
node.kind = 'let' | ||
} | ||
return node | ||
} | ||
}) | ||
``` | ||
### remove | ||
#### remove | ||
Remove all nodes that match the criteria. | ||
```javascript | ||
const { parse, remove, generate } = require('abstract-syntax-tree') | ||
const source = '"use strict"; const b = 4;' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.remove({ type: 'Literal', value: 'use strict' }) | ||
const ast = parse(source) | ||
remove(tree, { type: 'Literal', value: 'use strict' }) | ||
console.log(generate(tree)) // 'const b = 4;' | ||
``` | ||
#### each | ||
```javascript | ||
const source = 'function hello () { const foo = "bar"; return "world"; }' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.remove('BlockStatement > VariableDeclaration') | ||
const { parse, each } = require('abstract-syntax-tree') | ||
const source = 'const foo = 1; const bar = 2;' | ||
const tree = parse(source) | ||
each(tree, 'VariableDeclaration', node => { | ||
console.log(node) | ||
}) | ||
``` | ||
### walk | ||
#### first | ||
Walks over all nodes | ||
```javascript | ||
const { parse, first } = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = parse(source) | ||
console.log(first(tree, 'VariableDeclaration')) // { type: 'VariableDeclaration', ... } | ||
``` | ||
#### last | ||
```javascript | ||
const source = 'const a = 1' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.walk((node, parent) => {}) | ||
const { parse, last } = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = parse(source) | ||
console.log(last(tree, 'VariableDeclaration')) // { type: 'VariableDeclaration', ... } | ||
``` | ||
### traverse | ||
#### has | ||
Walks over all nodes | ||
```javascript | ||
const { parse, has } = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = parse(source) | ||
console.log(has(tree, 'VariableDeclaration')) // true | ||
``` | ||
#### count | ||
```javascript | ||
const source = 'const a = 1' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.walk({ | ||
enter (node) {}, | ||
leave (node) {} | ||
}) | ||
const { parse, count } = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = parse(source) | ||
console.log(count(tree, 'VariableDeclaration')) // 1 | ||
``` | ||
### replace | ||
#### equal | ||
Replace all nodes that match the criteria. | ||
```javascript | ||
const { equal } = require('abstract-syntax-tree') | ||
console.log(equal({ type: 'Literal', value: 42 }, { type: 'Literal', value: 42 })) // true | ||
console.log(equal({ type: 'Literal', value: 41 }, { type: 'Literal', value: 42 })) // false | ||
```` | ||
#### template | ||
```javascript | ||
const source = 'const a = 1' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.replace({ | ||
enter (node) { | ||
if (node.type === 'VariableDeclaration') { | ||
node.kind = 'let' | ||
} | ||
return node | ||
} | ||
}) | ||
const { template } = require('abstract-syntax-tree') | ||
const literal = template(42) | ||
const nodes = template('const foo = <%= bar %>;', { bar: { type: 'Literal', value: 1 } }) | ||
``` | ||
### prepend | ||
### Instance Methods | ||
Almost all of the static methods (excluding parse, generate, template and equal) have their instance equivalents. There are few extra instance methods: | ||
#### prepend | ||
Prepend a node to the body. | ||
```javascript | ||
const AbstractSyntaxTree = require('abstract-syntax-tree') | ||
const source = 'const a = 1;' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.prepend({ | ||
const tree = new AbstractSyntaxTree(source) | ||
tree.prepend({ | ||
type: 'ExpressionStatement', | ||
@@ -202,3 +235,3 @@ expression: { | ||
### append | ||
#### append | ||
@@ -208,5 +241,6 @@ Append a node to the body. | ||
```javascript | ||
const source = 'const a = 1;' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.append({ | ||
const AbstractSyntaxTree = require('abstract-syntax-tree') | ||
const source = 'const answer = 42' | ||
const tree = new AbstractSyntaxTree(source) | ||
tree.append({ | ||
type: 'ExpressionStatement', | ||
@@ -220,10 +254,23 @@ expression: { | ||
### wrap | ||
#### mark | ||
Add cid to all nodes | ||
```javascript | ||
const AbstractSyntaxTree = require('abstract-syntax-tree') | ||
const tree = new AbstractSyntaxTree('const a = 1') | ||
tree.mark() | ||
console.log(tree.first('Program').cid) // 1 | ||
console.log(tree.first('VariableDeclaration').cid) // 2 | ||
``` | ||
#### wrap | ||
Wrap body with given node. | ||
```javascript | ||
const source = 'const a = 1;' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.wrap(body => { | ||
const AbstractSyntaxTree = require('abstract-syntax-tree') | ||
const source = 'const a = 1' | ||
const tree = new AbstractSyntaxTree(source) | ||
tree.wrap(body => { | ||
return [ | ||
@@ -249,59 +296,24 @@ { | ||
### unwrap | ||
#### unwrap | ||
Change the code to the first BlockStatement body | ||
```javascript | ||
const AbstractSyntaxTree = require('abstract-syntax-tree') | ||
const source = '(function () { console.log(1); }())' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.unwrap() | ||
ast.toSource() | ||
const tree = new AbstractSyntaxTree(source) | ||
tree.unwrap() | ||
console.log(tree.source) // 'console.log(1);' | ||
``` | ||
### template | ||
### Getters | ||
Create ast partials from templates | ||
#### type | ||
```javascript | ||
const source = 'console.log(1);' | ||
const ast = new AbstractSyntaxTree(source) | ||
ast.template('const foo = <%= bar %>;' { bar: { type: 'Literal', value: 1 } }) | ||
``` | ||
#### body | ||
### mark | ||
#### source | ||
Add cid to all nodes | ||
#### map | ||
```javascript | ||
const ast = new AbstractSyntaxTree('const a = 1;') | ||
ast.mark() | ||
assert(ast.first('Program').cid === 1) | ||
assert(ast.first('VariableDeclaration').cid === 2) | ||
``` | ||
### Setters | ||
### toSource | ||
Convert the ast to string. | ||
```javascript | ||
const source = 'const a = 1;' | ||
const ast = new AbstractSyntaxTree(source) | ||
const source = ast.toSource() | ||
``` | ||
```javascript | ||
const source = 'const a = 1;' | ||
const ast = new AbstractSyntaxTree(source) | ||
const { source, map } = ast.toSource({ sourceMap: true }) | ||
``` | ||
### toSourceMap | ||
Generates a source map. | ||
```javascript | ||
const source = 'const a = 1;' | ||
const ast = new AbstractSyntaxTree(source) | ||
const map = ast.toSourceMap() | ||
``` | ||
#### body |
@@ -18,4 +18,4 @@ const estraverse = require('estraverse') | ||
module.exports = function replace (ast, options) { | ||
return estraverse.replace(ast, { | ||
module.exports = function replace (tree, options) { | ||
return estraverse.replace(tree, { | ||
enter (node, parent) { | ||
@@ -34,2 +34,2 @@ if (options.enter) { | ||
}) | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
19516
8
23
293
314
4
1
- Removedprettier@^1.15.1
- Removedprettier@1.19.1(transitive)