Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81169 views
1
var assert = require("assert");
2
var linesModule = require("./lines");
3
var types = require("./types");
4
var getFieldValue = types.getFieldValue;
5
var Node = types.namedTypes.Node;
6
var Expression = types.namedTypes.Expression;
7
var SourceLocation = types.namedTypes.SourceLocation;
8
var util = require("./util");
9
var comparePos = util.comparePos;
10
var FastPath = require("./fast-path");
11
var isObject = types.builtInTypes.object;
12
var isArray = types.builtInTypes.array;
13
var isString = types.builtInTypes.string;
14
15
function Patcher(lines) {
16
assert.ok(this instanceof Patcher);
17
assert.ok(lines instanceof linesModule.Lines);
18
19
var self = this,
20
replacements = [];
21
22
self.replace = function(loc, lines) {
23
if (isString.check(lines))
24
lines = linesModule.fromString(lines);
25
26
replacements.push({
27
lines: lines,
28
start: loc.start,
29
end: loc.end
30
});
31
};
32
33
self.get = function(loc) {
34
// If no location is provided, return the complete Lines object.
35
loc = loc || {
36
start: { line: 1, column: 0 },
37
end: { line: lines.length,
38
column: lines.getLineLength(lines.length) }
39
};
40
41
var sliceFrom = loc.start,
42
toConcat = [];
43
44
function pushSlice(from, to) {
45
assert.ok(comparePos(from, to) <= 0);
46
toConcat.push(lines.slice(from, to));
47
}
48
49
replacements.sort(function(a, b) {
50
return comparePos(a.start, b.start);
51
}).forEach(function(rep) {
52
if (comparePos(sliceFrom, rep.start) > 0) {
53
// Ignore nested replacement ranges.
54
} else {
55
pushSlice(sliceFrom, rep.start);
56
toConcat.push(rep.lines);
57
sliceFrom = rep.end;
58
}
59
});
60
61
pushSlice(sliceFrom, loc.end);
62
63
return linesModule.concat(toConcat);
64
};
65
}
66
exports.Patcher = Patcher;
67
68
exports.getReprinter = function(path) {
69
assert.ok(path instanceof FastPath);
70
71
// Make sure that this path refers specifically to a Node, rather than
72
// some non-Node subproperty of a Node.
73
var node = path.getValue();
74
if (!Node.check(node))
75
return;
76
77
var orig = node.original;
78
var origLoc = orig && orig.loc;
79
var lines = origLoc && origLoc.lines;
80
var reprints = [];
81
82
if (!lines || !findReprints(path, reprints))
83
return;
84
85
return function(print) {
86
var patcher = new Patcher(lines);
87
88
reprints.forEach(function(reprint) {
89
var old = reprint.oldNode;
90
SourceLocation.assert(old.loc, true);
91
patcher.replace(
92
old.loc,
93
print(reprint.newPath).indentTail(old.loc.indent)
94
);
95
});
96
97
return patcher.get(origLoc).indentTail(-orig.loc.indent);
98
};
99
};
100
101
function findReprints(newPath, reprints) {
102
var newNode = newPath.getValue();
103
Node.assert(newNode);
104
105
var oldNode = newNode.original;
106
Node.assert(oldNode);
107
108
assert.deepEqual(reprints, []);
109
110
if (newNode.type !== oldNode.type) {
111
return false;
112
}
113
114
var oldPath = new FastPath(oldNode);
115
var canReprint = findChildReprints(newPath, oldPath, reprints);
116
117
if (!canReprint) {
118
// Make absolutely sure the calling code does not attempt to reprint
119
// any nodes.
120
reprints.length = 0;
121
}
122
123
return canReprint;
124
}
125
126
function findAnyReprints(newPath, oldPath, reprints) {
127
var newNode = newPath.getValue();
128
var oldNode = oldPath.getValue();
129
130
if (newNode === oldNode)
131
return true;
132
133
if (isArray.check(newNode))
134
return findArrayReprints(newPath, oldPath, reprints);
135
136
if (isObject.check(newNode))
137
return findObjectReprints(newPath, oldPath, reprints);
138
139
return false;
140
}
141
142
function findArrayReprints(newPath, oldPath, reprints) {
143
var newNode = newPath.getValue();
144
var oldNode = oldPath.getValue();
145
isArray.assert(newNode);
146
var len = newNode.length;
147
148
if (!(isArray.check(oldNode) &&
149
oldNode.length === len))
150
return false;
151
152
for (var i = 0; i < len; ++i) {
153
newPath.stack.push(i, newNode[i]);
154
oldPath.stack.push(i, oldNode[i]);
155
var canReprint = findAnyReprints(newPath, oldPath, reprints);
156
newPath.stack.length -= 2;
157
oldPath.stack.length -= 2;
158
if (!canReprint) {
159
return false;
160
}
161
}
162
163
return true;
164
}
165
166
function findObjectReprints(newPath, oldPath, reprints) {
167
var newNode = newPath.getValue();
168
isObject.assert(newNode);
169
170
if (newNode.original === null) {
171
// If newNode.original node was set to null, reprint the node.
172
return false;
173
}
174
175
var oldNode = oldPath.getValue();
176
if (!isObject.check(oldNode))
177
return false;
178
179
if (Node.check(newNode)) {
180
if (!Node.check(oldNode)) {
181
return false;
182
}
183
184
if (!oldNode.loc) {
185
// If we have no .loc information for oldNode, then we won't
186
// be able to reprint it.
187
return false;
188
}
189
190
// Here we need to decide whether the reprinted code for newNode
191
// is appropriate for patching into the location of oldNode.
192
193
if (newNode.type === oldNode.type) {
194
var childReprints = [];
195
196
if (findChildReprints(newPath, oldPath, childReprints)) {
197
reprints.push.apply(reprints, childReprints);
198
} else {
199
reprints.push({
200
oldNode: oldNode,
201
newPath: newPath.copy()
202
});
203
}
204
205
return true;
206
}
207
208
if (Expression.check(newNode) &&
209
Expression.check(oldNode)) {
210
211
// If both nodes are subtypes of Expression, then we should be
212
// able to fill the location occupied by the old node with
213
// code printed for the new node with no ill consequences.
214
reprints.push({
215
oldNode: oldNode,
216
newPath: newPath.copy()
217
});
218
219
return true;
220
}
221
222
// The nodes have different types, and at least one of the types
223
// is not a subtype of the Expression type, so we cannot safely
224
// assume the nodes are syntactically interchangeable.
225
return false;
226
}
227
228
return findChildReprints(newPath, oldPath, reprints);
229
}
230
231
// This object is reused in hasOpeningParen and hasClosingParen to avoid
232
// having to allocate a temporary object.
233
var reusablePos = { line: 1, column: 0 };
234
235
function hasOpeningParen(oldPath) {
236
var oldNode = oldPath.getValue();
237
var loc = oldNode.loc;
238
var lines = loc && loc.lines;
239
240
if (lines) {
241
var pos = reusablePos;
242
pos.line = loc.start.line;
243
pos.column = loc.start.column;
244
245
while (lines.prevPos(pos)) {
246
var ch = lines.charAt(pos);
247
248
if (ch === "(") {
249
// If we found an opening parenthesis but it occurred before
250
// the start of the original subtree for this reprinting, then
251
// we must not return true for hasOpeningParen(oldPath).
252
return comparePos(oldPath.getRootValue().loc.start, pos) <= 0;
253
}
254
255
if (ch !== " ") {
256
return false;
257
}
258
}
259
}
260
261
return false;
262
}
263
264
function hasClosingParen(oldPath) {
265
var oldNode = oldPath.getValue();
266
var loc = oldNode.loc;
267
var lines = loc && loc.lines;
268
269
if (lines) {
270
var pos = reusablePos;
271
pos.line = loc.end.line;
272
pos.column = loc.end.column;
273
274
do {
275
var ch = lines.charAt(pos);
276
277
if (ch === ")") {
278
// If we found a closing parenthesis but it occurred after the
279
// end of the original subtree for this reprinting, then we
280
// must not return true for hasClosingParen(oldPath).
281
return comparePos(pos, oldPath.getRootValue().loc.end) <= 0;
282
}
283
284
if (ch !== " ") {
285
return false;
286
}
287
288
} while (lines.nextPos(pos));
289
}
290
291
return false;
292
}
293
294
function hasParens(oldPath) {
295
// This logic can technically be fooled if the node has parentheses
296
// but there are comments intervening between the parentheses and the
297
// node. In such cases the node will be harmlessly wrapped in an
298
// additional layer of parentheses.
299
return hasOpeningParen(oldPath) && hasClosingParen(oldPath);
300
}
301
302
function findChildReprints(newPath, oldPath, reprints) {
303
var newNode = newPath.getValue();
304
var oldNode = oldPath.getValue();
305
306
isObject.assert(newNode);
307
isObject.assert(oldNode);
308
309
if (newNode.original === null) {
310
// If newNode.original node was set to null, reprint the node.
311
return false;
312
}
313
314
// If this type of node cannot come lexically first in its enclosing
315
// statement (e.g. a function expression or object literal), and it
316
// seems to be doing so, then the only way we can ignore this problem
317
// and save ourselves from falling back to the pretty printer is if an
318
// opening parenthesis happens to precede the node. For example,
319
// (function(){ ... }()); does not need to be reprinted, even though
320
// the FunctionExpression comes lexically first in the enclosing
321
// ExpressionStatement and fails the hasParens test, because the
322
// parent CallExpression passes the hasParens test. If we relied on
323
// the path.needsParens() && !hasParens(oldNode) check below, the
324
// absence of a closing parenthesis after the FunctionExpression would
325
// trigger pretty-printing unnecessarily.
326
if (!newPath.canBeFirstInStatement() &&
327
newPath.firstInStatement() &&
328
!hasOpeningParen(oldPath))
329
return false;
330
331
// If this node needs parentheses and will not be wrapped with
332
// parentheses when reprinted, then return false to skip reprinting
333
// and let it be printed generically.
334
if (newPath.needsParens(true) && !hasParens(oldPath)) {
335
return false;
336
}
337
338
for (var k in util.getUnionOfKeys(newNode, oldNode)) {
339
if (k === "loc")
340
continue;
341
342
newPath.stack.push(k, types.getFieldValue(newNode, k));
343
oldPath.stack.push(k, types.getFieldValue(oldNode, k));
344
var canReprint = findAnyReprints(newPath, oldPath, reprints);
345
newPath.stack.length -= 2;
346
oldPath.stack.length -= 2;
347
348
if (!canReprint) {
349
return false;
350
}
351
}
352
353
return true;
354
}
355
356