UNPKG

4.3 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 beautify = require('js-beautify').js_beautify;
11
12class 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 // this could be improved by traversing once and
68 // comparing the current node to the found nodes
69 // one by one while making the array of nodes smaller too
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
173module.exports = AbstractSyntaxTree;
174