UNPKG

3.65 kBJavaScriptView Raw
1'use strict'
2
3const espree = require('espree')
4const esquery = require('esquery')
5const escodegen = require('escodegen')
6const estraverse = require('estraverse')
7const template = require('estemplate')
8const comparify = require('comparify')
9const toAST = require('to-ast')
10const prettier = require('prettier')
11
12class AbstractSyntaxTree {
13 constructor (source, options) {
14 options = options || {}
15 this.source = source
16 this.ast = this.constructor.parse(source, {
17 sourceType: 'module',
18 comment: options.comments,
19 attachComment: options.comments
20 })
21 }
22
23 query (node, selector) {
24 return esquery(node, selector)
25 }
26
27 find (selector, options) {
28 return this.query(this.ast, selector, options)
29 }
30
31 each (selector, callback) {
32 return this.find(selector).forEach(callback)
33 }
34
35 first (selector) {
36 return this.find(selector)[0]
37 }
38
39 last (selector) {
40 var nodes = this.find(selector)
41 return nodes[nodes.length - 1]
42 }
43
44 count (selector) {
45 return this.find(selector).length
46 }
47
48 has (selector) {
49 return this.count(selector) > 0
50 }
51
52 is (node, expected) {
53 return comparify(node, expected)
54 }
55
56 remove (target, options) {
57 options = options || {}
58 if (typeof target === 'string') {
59 return this._removeBySelector(target, options)
60 }
61 this._removeByNode(target, options)
62 }
63
64 _removeBySelector (target, options) {
65 var nodes = this.find(target)
66 // this could be improved by traversing once and
67 // comparing the current node to the found nodes
68 // one by one while making the array of nodes smaller too
69 nodes.forEach(node => this._removeByNode(node, options))
70 }
71
72 _removeByNode (node, options) {
73 var count = 0
74 estraverse.replace(this.ast, {
75 enter: function (current, parent) {
76 if (options.first && count === 1) {
77 return this.break()
78 }
79 if (comparify(current, node)) {
80 count += 1
81 return this.remove()
82 }
83 },
84 leave: function (current, parent) {
85 if (current.expression === null ||
86 (current.type === 'VariableDeclaration' && current.declarations.length === 0)) {
87 return this.remove()
88 }
89 }
90 })
91 }
92
93 walk (callback) {
94 return estraverse.traverse(this.ast, { enter: callback })
95 }
96
97 traverse (options) {
98 return estraverse.traverse(this.ast, options)
99 }
100
101 replace (options) {
102 return estraverse.replace(this.ast, options)
103 }
104
105 prepend (node) {
106 this.ast.body.unshift(node)
107 }
108
109 append (node) {
110 this.ast.body.push(node)
111 }
112
113 wrap (callback) {
114 this.ast.body = callback(this.ast.body)
115 }
116
117 unwrap () {
118 let block = this.first('BlockStatement')
119 this.ast.body = block.body
120 }
121
122 template (source, options) {
123 options = options || {}
124 if (typeof source === 'string') {
125 return template(source, options).body
126 }
127 return toAST(source, options)
128 }
129
130 beautify (source, options) {
131 return prettier.format(source, options)
132 }
133
134 minify (ast) {
135 return ast
136 }
137
138 toSource (options) {
139 options = options || {}
140
141 if (options.minify) {
142 this.ast = this.minify(this.ast)
143 }
144
145 var source = escodegen.generate(this.ast, {
146 comment: options.comments,
147 format: {
148 quotes: options.quotes || 'auto'
149 }
150 })
151
152 if (options.beautify) {
153 source = this.beautify(source, options.beautify)
154 }
155 this.source = source
156
157 return source
158 }
159
160 toString (options) {
161 return this.toSource(options)
162 }
163
164 static parse (source, options) {
165 return espree.parse(source, options)
166 }
167}
168
169module.exports = AbstractSyntaxTree