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