UNPKG

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