Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81169 views
1
var assert = require("assert");
2
var types = require("./types");
3
var n = types.namedTypes;
4
var isArray = types.builtInTypes.array;
5
var isObject = types.builtInTypes.object;
6
var linesModule = require("./lines");
7
var fromString = linesModule.fromString;
8
var Lines = linesModule.Lines;
9
var concat = linesModule.concat;
10
var comparePos = require("./util").comparePos;
11
var childNodesCacheKey = require("private").makeUniqueKey();
12
13
// TODO Move a non-caching implementation of this function into ast-types,
14
// and implement a caching wrapper function here.
15
function getSortedChildNodes(node, resultArray) {
16
if (!node) {
17
return;
18
}
19
20
if (resultArray) {
21
if (n.Node.check(node) &&
22
n.SourceLocation.check(node.loc)) {
23
// This reverse insertion sort almost always takes constant
24
// time because we almost always (maybe always?) append the
25
// nodes in order anyway.
26
for (var i = resultArray.length - 1; i >= 0; --i) {
27
if (comparePos(resultArray[i].loc.end,
28
node.loc.start) <= 0) {
29
break;
30
}
31
}
32
resultArray.splice(i + 1, 0, node);
33
return;
34
}
35
} else if (node[childNodesCacheKey]) {
36
return node[childNodesCacheKey];
37
}
38
39
var names;
40
if (isArray.check(node)) {
41
names = Object.keys(node);
42
} else if (isObject.check(node)) {
43
names = types.getFieldNames(node);
44
} else {
45
return;
46
}
47
48
if (!resultArray) {
49
Object.defineProperty(node, childNodesCacheKey, {
50
value: resultArray = [],
51
enumerable: false
52
});
53
}
54
55
for (var i = 0, nameCount = names.length; i < nameCount; ++i) {
56
getSortedChildNodes(node[names[i]], resultArray);
57
}
58
59
return resultArray;
60
}
61
62
// As efficiently as possible, decorate the comment object with
63
// .precedingNode, .enclosingNode, and/or .followingNode properties, at
64
// least one of which is guaranteed to be defined.
65
function decorateComment(node, comment) {
66
var childNodes = getSortedChildNodes(node);
67
68
// Time to dust off the old binary search robes and wizard hat.
69
var left = 0, right = childNodes.length;
70
while (left < right) {
71
var middle = (left + right) >> 1;
72
var child = childNodes[middle];
73
74
if (comparePos(child.loc.start, comment.loc.start) <= 0 &&
75
comparePos(comment.loc.end, child.loc.end) <= 0) {
76
// The comment is completely contained by this child node.
77
decorateComment(comment.enclosingNode = child, comment);
78
return; // Abandon the binary search at this level.
79
}
80
81
if (comparePos(child.loc.end, comment.loc.start) <= 0) {
82
// This child node falls completely before the comment.
83
// Because we will never consider this node or any nodes
84
// before it again, this node must be the closest preceding
85
// node we have encountered so far.
86
var precedingNode = child;
87
left = middle + 1;
88
continue;
89
}
90
91
if (comparePos(comment.loc.end, child.loc.start) <= 0) {
92
// This child node falls completely after the comment.
93
// Because we will never consider this node or any nodes after
94
// it again, this node must be the closest following node we
95
// have encountered so far.
96
var followingNode = child;
97
right = middle;
98
continue;
99
}
100
101
throw new Error("Comment location overlaps with node location");
102
}
103
104
if (precedingNode) {
105
comment.precedingNode = precedingNode;
106
}
107
108
if (followingNode) {
109
comment.followingNode = followingNode;
110
}
111
}
112
113
exports.attach = function(comments, ast, lines) {
114
if (!isArray.check(comments)) {
115
return;
116
}
117
118
var tiesToBreak = [];
119
120
comments.forEach(function(comment) {
121
comment.loc.lines = lines;
122
decorateComment(ast, comment);
123
124
var pn = comment.precedingNode;
125
var en = comment.enclosingNode;
126
var fn = comment.followingNode;
127
128
if (pn && fn) {
129
var tieCount = tiesToBreak.length;
130
if (tieCount > 0) {
131
var lastTie = tiesToBreak[tieCount - 1];
132
133
assert.strictEqual(
134
lastTie.precedingNode === comment.precedingNode,
135
lastTie.followingNode === comment.followingNode
136
);
137
138
if (lastTie.followingNode !== comment.followingNode) {
139
breakTies(tiesToBreak, lines);
140
}
141
}
142
143
tiesToBreak.push(comment);
144
145
} else if (pn) {
146
// No contest: we have a trailing comment.
147
breakTies(tiesToBreak, lines);
148
Comments.forNode(pn).addTrailing(comment);
149
150
} else if (fn) {
151
// No contest: we have a leading comment.
152
breakTies(tiesToBreak, lines);
153
Comments.forNode(fn).addLeading(comment);
154
155
} else if (en) {
156
// The enclosing node has no child nodes at all, so what we
157
// have here is a dangling comment, e.g. [/* crickets */].
158
breakTies(tiesToBreak, lines);
159
Comments.forNode(en).addDangling(comment);
160
161
} else {
162
throw new Error("AST contains no nodes at all?");
163
}
164
});
165
166
breakTies(tiesToBreak, lines);
167
};
168
169
function breakTies(tiesToBreak, lines) {
170
var tieCount = tiesToBreak.length;
171
if (tieCount === 0) {
172
return;
173
}
174
175
var pn = tiesToBreak[0].precedingNode;
176
var fn = tiesToBreak[0].followingNode;
177
var gapEndPos = fn.loc.start;
178
179
// Iterate backwards through tiesToBreak, examining the gaps
180
// between the tied comments. In order to qualify as leading, a
181
// comment must be separated from fn by an unbroken series of
182
// whitespace-only gaps (or other comments).
183
for (var indexOfFirstLeadingComment = tieCount;
184
indexOfFirstLeadingComment > 0;
185
--indexOfFirstLeadingComment) {
186
var comment = tiesToBreak[indexOfFirstLeadingComment - 1];
187
assert.strictEqual(comment.precedingNode, pn);
188
assert.strictEqual(comment.followingNode, fn);
189
190
var gap = lines.sliceString(comment.loc.end, gapEndPos);
191
if (/\S/.test(gap)) {
192
// The gap string contained something other than whitespace.
193
break;
194
}
195
196
gapEndPos = comment.loc.start;
197
}
198
199
while (indexOfFirstLeadingComment <= tieCount &&
200
(comment = tiesToBreak[indexOfFirstLeadingComment]) &&
201
// If the comment is a //-style comment and indented more
202
// deeply than the node itself, reconsider it as trailing.
203
comment.type === "Line" &&
204
comment.loc.start.column > fn.loc.start.column) {
205
++indexOfFirstLeadingComment;
206
}
207
208
tiesToBreak.forEach(function(comment, i) {
209
if (i < indexOfFirstLeadingComment) {
210
Comments.forNode(pn).addTrailing(comment);
211
} else {
212
Comments.forNode(fn).addLeading(comment);
213
}
214
});
215
216
tiesToBreak.length = 0;
217
}
218
219
function Comments() {
220
assert.ok(this instanceof Comments);
221
this.leading = [];
222
this.dangling = [];
223
this.trailing = [];
224
}
225
226
var Cp = Comments.prototype;
227
228
Comments.forNode = function forNode(node) {
229
var comments = node.comments;
230
if (!comments) {
231
Object.defineProperty(node, "comments", {
232
value: comments = new Comments,
233
enumerable: false
234
});
235
}
236
return comments;
237
};
238
239
Cp.forEach = function forEach(callback, context) {
240
this.leading.forEach(callback, context);
241
// this.dangling.forEach(callback, context);
242
this.trailing.forEach(callback, context);
243
};
244
245
Cp.addLeading = function addLeading(comment) {
246
this.leading.push(comment);
247
};
248
249
Cp.addDangling = function addDangling(comment) {
250
this.dangling.push(comment);
251
};
252
253
Cp.addTrailing = function addTrailing(comment) {
254
comment.trailing = true;
255
if (comment.type === "Block") {
256
this.trailing.push(comment);
257
} else {
258
this.leading.push(comment);
259
}
260
};
261
262
/**
263
* @param {Object} options - Options object that configures printing.
264
*/
265
function printLeadingComment(comment, options) {
266
var loc = comment.loc;
267
var lines = loc && loc.lines;
268
var parts = [];
269
270
if (comment.type === "Block") {
271
parts.push("/*", fromString(comment.value, options), "*/");
272
} else if (comment.type === "Line") {
273
parts.push("//", fromString(comment.value, options));
274
} else assert.fail(comment.type);
275
276
if (comment.trailing) {
277
// When we print trailing comments as leading comments, we don't
278
// want to bring any trailing spaces along.
279
parts.push("\n");
280
281
} else if (lines instanceof Lines) {
282
var trailingSpace = lines.slice(
283
loc.end,
284
lines.skipSpaces(loc.end)
285
);
286
287
if (trailingSpace.length === 1) {
288
// If the trailing space contains no newlines, then we want to
289
// preserve it exactly as we found it.
290
parts.push(trailingSpace);
291
} else {
292
// If the trailing space contains newlines, then replace it
293
// with just that many newlines, with all other spaces removed.
294
parts.push(new Array(trailingSpace.length).join("\n"));
295
}
296
297
} else {
298
parts.push("\n");
299
}
300
301
return concat(parts).stripMargin(loc ? loc.start.column : 0);
302
}
303
304
/**
305
* @param {Object} options - Options object that configures printing.
306
*/
307
function printTrailingComment(comment, options) {
308
var loc = comment.loc;
309
var lines = loc && loc.lines;
310
var parts = [];
311
312
if (lines instanceof Lines) {
313
var fromPos = lines.skipSpaces(loc.start, true) || lines.firstPos();
314
var leadingSpace = lines.slice(fromPos, loc.start);
315
316
if (leadingSpace.length === 1) {
317
// If the leading space contains no newlines, then we want to
318
// preserve it exactly as we found it.
319
parts.push(leadingSpace);
320
} else {
321
// If the leading space contains newlines, then replace it
322
// with just that many newlines, sans all other spaces.
323
parts.push(new Array(leadingSpace.length).join("\n"));
324
}
325
}
326
327
if (comment.type === "Block") {
328
parts.push("/*", fromString(comment.value, options), "*/");
329
} else if (comment.type === "Line") {
330
parts.push("//", fromString(comment.value, options), "\n");
331
} else assert.fail(comment.type);
332
333
return concat(parts).stripMargin(
334
loc ? loc.start.column : 0,
335
true // Skip the first line, in case there were leading spaces.
336
);
337
}
338
339
/**
340
* @param {Object} options - Options object that configures printing.
341
*/
342
exports.printComments = function(comments, innerLines, options) {
343
if (innerLines) {
344
assert.ok(innerLines instanceof Lines);
345
} else {
346
innerLines = fromString("");
347
}
348
349
if (!comments || !(comments.leading.length +
350
comments.trailing.length)) {
351
return innerLines;
352
}
353
354
var parts = [];
355
356
comments.leading.forEach(function(comment) {
357
parts.push(printLeadingComment(comment, options));
358
});
359
360
parts.push(innerLines);
361
362
comments.trailing.forEach(function(comment) {
363
assert.strictEqual(comment.type, "Block");
364
parts.push(printTrailingComment(comment, options));
365
});
366
367
return concat(parts);
368
};
369
370