UNPKG

50.5 kBJavaScriptView Raw
1import { __read } from "tslib";
2import { SourceType } from 'coffee-lex';
3import { FunctionApplication, NewOp, SoakedFunctionApplication } from 'decaffeinate-parser/dist/nodes';
4import { AVOID_IIFES, AVOID_INLINE_ASSIGNMENTS, CLEAN_UP_IMPLICIT_RETURNS } from '../suggestions';
5import adjustIndent from '../utils/adjustIndent';
6import { logger } from '../utils/debug';
7import notNull from '../utils/notNull';
8import PatcherError from '../utils/PatchError';
9import referencesArguments from '../utils/referencesArguments';
10import { isFunction, isSemanticToken } from '../utils/types';
11var NodePatcher = /** @class */ (function () {
12 function NodePatcher(_a) {
13 var _this = this;
14 var node = _a.node, context = _a.context, editor = _a.editor, options = _a.options, addSuggestion = _a.addSuggestion;
15 this.adjustedIndentLevel = 0;
16 this._assignee = false;
17 this._containsYield = false;
18 this._containsAwait = false;
19 this._deferredSuffix = '';
20 this._expression = false;
21 this._hadUnparenthesizedNegation = false;
22 this._implicitlyReturns = false;
23 this._repeatableOptions = null;
24 this._repeatCode = null;
25 this._returns = false;
26 // Temporary callbacks that can be added for inter-node communication.
27 this.addThisAssignmentAtScopeHeader = null;
28 this.addDefaultParamAssignmentAtScopeHeader = null;
29 this.log = logger(this.constructor.name);
30 this.node = node;
31 this.context = context;
32 this.editor = editor;
33 this.options = options;
34 this.addSuggestion = addSuggestion;
35 this.withPrettyErrors(function () { return _this.setupLocationInformation(); });
36 }
37 /**
38 * Allow patcher classes to override the class used to patch their children.
39 */
40 // eslint-disable-next-line @typescript-eslint/no-unused-vars
41 NodePatcher.patcherClassForChildNode = function (_node, _property) {
42 return null;
43 };
44 /**
45 * Allow patcher classes that would patch a node to chose a different class.
46 */
47 // eslint-disable-next-line @typescript-eslint/no-unused-vars
48 NodePatcher.patcherClassOverrideForNode = function (_node) {
49 return null;
50 };
51 /**
52 * @private
53 */
54 NodePatcher.prototype.setupLocationInformation = function () {
55 var _a = this, node = _a.node, context = _a.context;
56 /**
57 * `contentStart` and `contentEnd` is the exclusive range within the original source that
58 * composes this patcher's node. For example, here's the contentStart and contentEnd of
59 * `a + b` in the expression below:
60 *
61 * console.log(a + b)
62 * ^ ^
63 */
64 this.contentStart = node.start;
65 this.contentEnd = node.end;
66 if (this.shouldTrimContentRange()) {
67 this.trimContentRange();
68 }
69 var tokens = context.sourceTokens;
70 var firstSourceTokenIndex = tokens.indexOfTokenStartingAtSourceIndex(this.contentStart);
71 var lastSourceTokenIndex = tokens.indexOfTokenEndingAtSourceIndex(this.contentEnd);
72 if (!firstSourceTokenIndex || !lastSourceTokenIndex) {
73 if (node.type === 'Program') {
74 // Just an empty program.
75 return;
76 }
77 throw this.error("cannot find first or last token in " + node.type + " node");
78 }
79 this.contentStartTokenIndex = firstSourceTokenIndex;
80 this.contentEndTokenIndex = lastSourceTokenIndex;
81 var outerStartTokenIndex = firstSourceTokenIndex;
82 var outerEndTokenIndex = lastSourceTokenIndex;
83 var innerStartTokenIndex = firstSourceTokenIndex;
84 var innerEndTokenIndex = lastSourceTokenIndex;
85 for (;;) {
86 var previousSurroundingTokenIndex = tokens.lastIndexOfTokenMatchingPredicate(isSemanticToken, outerStartTokenIndex.previous());
87 var nextSurroundingTokenIndex = tokens.indexOfTokenMatchingPredicate(isSemanticToken, outerEndTokenIndex.next());
88 if (!previousSurroundingTokenIndex || !nextSurroundingTokenIndex) {
89 break;
90 }
91 var previousSurroundingToken = tokens.tokenAtIndex(previousSurroundingTokenIndex);
92 var nextSurroundingToken = tokens.tokenAtIndex(nextSurroundingTokenIndex);
93 if (!previousSurroundingToken ||
94 (previousSurroundingToken.type !== SourceType.LPAREN && previousSurroundingToken.type !== SourceType.CALL_START)) {
95 break;
96 }
97 if (!nextSurroundingToken ||
98 (nextSurroundingToken.type !== SourceType.RPAREN && nextSurroundingToken.type !== SourceType.CALL_END)) {
99 break;
100 }
101 if (innerStartTokenIndex === firstSourceTokenIndex) {
102 innerStartTokenIndex = previousSurroundingTokenIndex;
103 }
104 if (innerEndTokenIndex === lastSourceTokenIndex) {
105 innerEndTokenIndex = nextSurroundingTokenIndex;
106 }
107 outerStartTokenIndex = previousSurroundingTokenIndex;
108 outerEndTokenIndex = nextSurroundingTokenIndex;
109 }
110 this.innerStartTokenIndex = innerStartTokenIndex;
111 this.innerEndTokenIndex = innerEndTokenIndex;
112 this.outerStartTokenIndex = outerStartTokenIndex;
113 this.outerEndTokenIndex = outerEndTokenIndex;
114 /**
115 * `innerStart`, `innerEnd`, `outerStart` and `outerEnd` refer to the
116 * positions around surrounding parentheses. In most nodes they are the same
117 * as `contentStart` and `contentEnd`. For example:
118 *
119 * innerStart
120 * |
121 * outerStart | contentStart
122 * | | |
123 * ▼ ▼ ▼
124 * 1 * (( 2 + 3 ))
125 * ▲ ▲ ▲
126 * | | |
127 * contentEnd | outerEnd
128 * |
129 * innerEnd
130 */
131 if (innerStartTokenIndex === firstSourceTokenIndex) {
132 this.innerStart = this.contentStart;
133 }
134 else {
135 this.innerStart = notNull(tokens.tokenAtIndex(innerStartTokenIndex)).end;
136 }
137 if (innerEndTokenIndex === lastSourceTokenIndex) {
138 this.innerEnd = this.contentEnd;
139 }
140 else {
141 this.innerEnd = notNull(tokens.tokenAtIndex(innerEndTokenIndex)).start;
142 }
143 this.outerStart = notNull(tokens.tokenAtIndex(outerStartTokenIndex)).start;
144 this.outerEnd = notNull(tokens.tokenAtIndex(outerEndTokenIndex)).end;
145 };
146 /**
147 * Called to trim the range of content for this node. Override in subclasses
148 * to customize its behavior, or override `shouldTrimContentRange` to enable
149 * or disable it.
150 */
151 NodePatcher.prototype.trimContentRange = function () {
152 var context = this.context;
153 for (;;) {
154 var startChar = context.source[this.contentStart];
155 if (startChar === ' ' || startChar === '\t') {
156 this.contentStart++;
157 }
158 else {
159 break;
160 }
161 }
162 for (;;) {
163 var lastChar = context.source[this.contentEnd - 1];
164 if (lastChar === ' ' || lastChar === '\t') {
165 this.contentEnd--;
166 }
167 else {
168 break;
169 }
170 }
171 };
172 /**
173 * Decides whether to trim the content range of this node.
174 */
175 NodePatcher.prototype.shouldTrimContentRange = function () {
176 return false;
177 };
178 /**
179 * Called when the patcher tree is complete so we can do any processing that
180 * requires communication with other patchers.
181 */
182 NodePatcher.prototype.initialize = function () {
183 // intentionally left empty
184 };
185 /**
186 * Calls methods on `editor` to transform the source code represented by
187 * `node` from CoffeeScript to JavaScript. By default this method delegates
188 * to other patcher methods which can be overridden individually.
189 */
190 NodePatcher.prototype.patch = function (options) {
191 var _this = this;
192 if (options === void 0) { options = {}; }
193 this.withPrettyErrors(function () {
194 if (_this._repeatableOptions !== null) {
195 _this._repeatCode = _this.patchAsRepeatableExpression(_this._repeatableOptions, options);
196 }
197 else if (_this.forcedToPatchAsExpression()) {
198 _this.patchAsForcedExpression(options);
199 _this.commitDeferredSuffix();
200 }
201 else if (_this.willPatchAsExpression()) {
202 _this.patchAsExpression(options);
203 _this.commitDeferredSuffix();
204 }
205 else {
206 _this.patchAsStatement(options);
207 _this.commitDeferredSuffix();
208 }
209 });
210 };
211 /**
212 * Alternative to patch that patches the expression in a way that the result
213 * can be referenced later, then returns the code to reference it.
214 *
215 * This is a shorthand for the simplest use of the repeatable protocol. In
216 * more advanced cases (such as repeating code that is deep within the AST),
217 * setRequiresRepeatableExpression can be called before the node is patched
218 * and getRepeatCode can be called any time after.
219 *
220 * The actual implementation for making the node repeatable should be in
221 * patchAsRepeatableExpression.
222 */
223 NodePatcher.prototype.patchRepeatable = function (repeatableOptions) {
224 if (repeatableOptions === void 0) { repeatableOptions = {}; }
225 this.setRequiresRepeatableExpression(repeatableOptions);
226 this.patch();
227 return this.getRepeatCode();
228 };
229 /**
230 * Patch the given expression and get the underlying generated code. This is
231 * more robust than calling patch and slice directly, since it also includes
232 * code inserted at contentStart (which normally isn't picked up by slice
233 * because it's inserted to the left of the index boundary). To accomplish
234 * this, we look at the range from contentStart - 1 to contentStart before and
235 * after patching and include anything new that was added.
236 */
237 NodePatcher.prototype.patchAndGetCode = function (options) {
238 var _this = this;
239 if (options === void 0) { options = {}; }
240 return this.captureCodeForPatchOperation(function () { return _this.patch(options); });
241 };
242 NodePatcher.prototype.captureCodeForPatchOperation = function (patchFn) {
243 var sliceStart = this.contentStart > 0 ? this.contentStart - 1 : 0;
244 // Occasionally, sliceStart will be illegal because it will be in a range
245 // that has been removed or overwritten. If that's the case, subtract 1 from
246 // sliceStart until we find something that works.
247 var beforeCode = null;
248 while (beforeCode === null) {
249 try {
250 beforeCode = this.slice(sliceStart, this.contentStart);
251 }
252 catch (e) {
253 // Assume that this is because the index is an invalid start. It looks
254 // like there isn't a robust way to detect this case exactly, so just
255 // try a lower start for any error.
256 sliceStart -= 1;
257 if (sliceStart < 0) {
258 throw this.error('Could not find a valid index to slice for patch operation.');
259 }
260 }
261 }
262 patchFn();
263 var code = this.slice(sliceStart, this.contentEnd);
264 var startIndex = 0;
265 while (startIndex < beforeCode.length && startIndex < code.length && beforeCode[startIndex] === code[startIndex]) {
266 startIndex++;
267 }
268 return code.substr(startIndex);
269 };
270 /**
271 * Catch errors and throw them again annotated with the current node.
272 */
273 NodePatcher.prototype.withPrettyErrors = function (body) {
274 try {
275 body();
276 }
277 catch (err) {
278 if (!PatcherError.detect(err)) {
279 throw this.error(err.message, this.contentStart, this.contentEnd, err);
280 }
281 else {
282 throw err;
283 }
284 }
285 };
286 /**
287 * Internal patching method that should patch the current node as an
288 * expression and also, if necessary, alter it in a way that it can
289 *
290 * The return value of this function should be a code snippet that references
291 * the result of this expression without any further side-effects.
292 *
293 * In simple cases, such as identifiers, subclasses can override isRepeatable
294 * to declare themselves as already repeatable. In more advanced cases,
295 * subclasses can override this method to provide custom behavior.
296 *
297 * This function is also responsible for committing the deferred suffix if
298 * necessary.
299 *
300 * @protected
301 */
302 NodePatcher.prototype.patchAsRepeatableExpression = function (repeatableOptions, patchOptions) {
303 var _this = this;
304 if (repeatableOptions === void 0) { repeatableOptions = {}; }
305 if (patchOptions === void 0) { patchOptions = {}; }
306 if (this.isRepeatable() && !repeatableOptions.forceRepeat) {
307 return this.captureCodeForPatchOperation(function () {
308 _this.patchAsForcedExpression(patchOptions);
309 _this.commitDeferredSuffix();
310 });
311 }
312 else {
313 this.addSuggestion(AVOID_INLINE_ASSIGNMENTS);
314 // Can't repeat it, so we assign it to a free variable and return that,
315 // i.e. `a + b` → `(ref = a + b)`.
316 if (repeatableOptions.parens) {
317 this.insert(this.innerStart, '(');
318 }
319 var ref = this.claimFreeBinding(repeatableOptions.ref);
320 this.insert(this.innerStart, ref + " = ");
321 this.patchAsForcedExpression(patchOptions);
322 this.commitDeferredSuffix();
323 if (repeatableOptions.parens) {
324 this.insert(this.innerEnd, ')');
325 }
326 return ref;
327 }
328 };
329 /**
330 * Override this to patch the node as an expression.
331 */
332 // eslint-disable-next-line @typescript-eslint/no-unused-vars
333 NodePatcher.prototype.patchAsExpression = function (_options) {
334 if (_options === void 0) { _options = {}; }
335 throw this.error("'patchAsExpression' must be overridden in subclasses");
336 };
337 /**
338 * Override this to patch the node as a statement.
339 */
340 NodePatcher.prototype.patchAsStatement = function (options) {
341 if (options === void 0) { options = {}; }
342 var addParens = this.statementShouldAddParens();
343 if (addParens) {
344 this.insert(this.outerStart, '(');
345 }
346 this.patchAsExpression(options);
347 if (addParens) {
348 this.insert(this.outerEnd, ')');
349 }
350 };
351 /**
352 * Override this to patch the node as an expression that would not normally be
353 * an expression, often by wrapping it in an immediately invoked function
354 * expression (IIFE).
355 */
356 NodePatcher.prototype.patchAsForcedExpression = function (options) {
357 if (options === void 0) { options = {}; }
358 this.patchAsExpression(options);
359 };
360 /**
361 * Insert content at the specified index.
362 */
363 NodePatcher.prototype.insert = function (index, content) {
364 if (typeof index !== 'number') {
365 throw new Error("cannot insert " + JSON.stringify(content) + " at non-numeric index " + index);
366 }
367 this.log('INSERT', index, JSON.stringify(content), 'BEFORE', JSON.stringify(this.context.source.slice(index, index + 8)));
368 this.adjustBoundsToInclude(index);
369 this.editor.appendLeft(index, content);
370 };
371 /**
372 * Insert content at the specified index, before any content normally
373 * specified with `insert`. Note that this should be used sparingly. In almost
374 * every case, the correct behavior is to do all patching operations in order
375 * and always use `insert`. However, in some cases (like a constructor that
376 * needs the patched contents of the methods below it), we need to do patching
377 * out of order, so it's ok to use `prependLeft` to ensure that the code ends
378 * up before the later values.
379 */
380 NodePatcher.prototype.prependLeft = function (index, content) {
381 if (typeof index !== 'number') {
382 throw new Error("cannot insert " + JSON.stringify(content) + " at non-numeric index " + index);
383 }
384 this.log('PREPEND LEFT', index, JSON.stringify(content), 'BEFORE', JSON.stringify(this.context.source.slice(index, index + 8)));
385 this.adjustBoundsToInclude(index);
386 this.editor.prependLeft(index, content);
387 };
388 NodePatcher.prototype.allowPatchingOuterBounds = function () {
389 return false;
390 };
391 /**
392 * @protected
393 */
394 NodePatcher.prototype.getEditingBounds = function () {
395 var boundingPatcher = this.getBoundingPatcher();
396 // When we're a function arg, there isn't a great patcher to use to
397 // determine our bounds (we're allowed to patch from the previous
398 // comma/paren to the next comma/paren), so loosen the restriction to the
399 // entire function.
400 if (boundingPatcher.parent &&
401 (this.isNodeFunctionApplication(boundingPatcher.parent.node) ||
402 boundingPatcher.parent.node.type === 'ArrayInitialiser')) {
403 boundingPatcher = boundingPatcher.parent;
404 }
405 if (this.allowPatchingOuterBounds()) {
406 return [boundingPatcher.outerStart, boundingPatcher.outerEnd];
407 }
408 else {
409 return [boundingPatcher.innerStart, boundingPatcher.innerEnd];
410 }
411 };
412 /**
413 * @protected
414 */
415 NodePatcher.prototype.isIndexEditable = function (index) {
416 var _a = __read(this.getEditingBounds(), 2), start = _a[0], end = _a[1];
417 return index >= start && index <= end;
418 };
419 /**
420 * @protected
421 */
422 NodePatcher.prototype.assertEditableIndex = function (index) {
423 if (!this.isIndexEditable(index)) {
424 var _a = __read(this.getEditingBounds(), 2), start = _a[0], end = _a[1];
425 throw this.error("cannot edit index " + index + " because it is not editable (i.e. outside [" + start + ", " + end + "))", start, end);
426 }
427 };
428 /**
429 * When editing outside a node's bounds we expand the bounds to fit, if
430 * possible. Note that if a node or a node's parent is wrapped in parentheses
431 * we cannot adjust the bounds beyond the inside of the parentheses.
432 */
433 NodePatcher.prototype.adjustBoundsToInclude = function (index) {
434 this.assertEditableIndex(index);
435 if (index < this.innerStart) {
436 this.log('Moving `innerStart` from', this.innerStart, 'to', index);
437 this.innerStart = index;
438 }
439 if (index > this.innerEnd) {
440 this.log('Moving `innerEnd` from', this.innerEnd, 'to', index);
441 this.innerEnd = index;
442 }
443 if (index < this.outerStart) {
444 this.log('Moving `outerStart` from', this.outerStart, 'to', index);
445 this.outerStart = index;
446 }
447 if (index > this.outerEnd) {
448 this.log('Moving `outerEnd` from', this.outerEnd, 'to', index);
449 this.outerEnd = index;
450 }
451 if (this.parent) {
452 this.parent.adjustBoundsToInclude(index);
453 }
454 };
455 /**
456 * Replace the content between the start and end indexes with new content.
457 */
458 NodePatcher.prototype.overwrite = function (start, end, content) {
459 if (typeof start !== 'number' || typeof end !== 'number') {
460 throw new Error("cannot overwrite non-numeric range [" + start + ", " + end + ") " + ("with " + JSON.stringify(content)));
461 }
462 this.log('OVERWRITE', "[" + start + ", " + end + ")", JSON.stringify(this.context.source.slice(start, end)), '→', JSON.stringify(content));
463 this.editor.overwrite(start, end, content);
464 };
465 /**
466 * Remove the content between the start and end indexes.
467 */
468 NodePatcher.prototype.remove = function (start, end) {
469 if (typeof start !== 'number' || typeof end !== 'number') {
470 throw new Error("cannot remove non-numeric range [" + start + ", " + end + ")");
471 }
472 this.log('REMOVE', "[" + start + ", " + end + ")", JSON.stringify(this.context.source.slice(start, end)));
473 this.editor.remove(start, end);
474 };
475 /**
476 * Moves content in a range to another index.
477 */
478 NodePatcher.prototype.move = function (start, end, index) {
479 if (typeof start !== 'number' || typeof end !== 'number') {
480 throw this.error("cannot remove non-numeric range [" + start + ", " + end + ")");
481 }
482 if (typeof index !== 'number') {
483 throw this.error("cannot move to non-numeric index: " + index);
484 }
485 this.log('MOVE', "[" + start + ", " + end + ") \u2192 " + index, JSON.stringify(this.context.source.slice(start, end)), 'BEFORE', JSON.stringify(this.context.source.slice(index, index + 8)));
486 this.editor.move(start, end, index);
487 };
488 /**
489 * Get the current content between the start and end indexes.
490 */
491 NodePatcher.prototype.slice = function (start, end) {
492 // magic-string treats 0 as the end of the string, which we don't want to do.
493 if (end === 0) {
494 return '';
495 }
496 return this.editor.slice(start, end);
497 };
498 /**
499 * Determines whether this node starts with a string.
500 */
501 NodePatcher.prototype.startsWith = function (string) {
502 return this.context.source.slice(this.contentStart, this.contentStart + string.length) === string;
503 };
504 /**
505 * Determines whether this node ends with a string.
506 */
507 NodePatcher.prototype.endsWith = function (string) {
508 return this.context.source.slice(this.contentEnd - string.length, this.contentEnd) === string;
509 };
510 /**
511 * Tells us to force this patcher to generate an expression, or else throw.
512 */
513 NodePatcher.prototype.setRequiresExpression = function () {
514 this.setExpression(true);
515 };
516 /**
517 * Tells us to try to patch as an expression, returning whether it can.
518 */
519 NodePatcher.prototype.setExpression = function (force) {
520 if (force === void 0) { force = false; }
521 if (force) {
522 if (!this.canPatchAsExpression()) {
523 throw this.error("cannot represent " + this.node.type + " as an expression");
524 }
525 }
526 else if (!this.prefersToPatchAsExpression()) {
527 return false;
528 }
529 this._expression = true;
530 return true;
531 };
532 /**
533 * Override this to express whether the patcher prefers to be represented as
534 * an expression. By default it's simply an alias for `canPatchAsExpression`.
535 */
536 NodePatcher.prototype.prefersToPatchAsExpression = function () {
537 return this.canPatchAsExpression();
538 };
539 /**
540 * Override this if a node cannot be represented as an expression.
541 */
542 NodePatcher.prototype.canPatchAsExpression = function () {
543 return true;
544 };
545 /**
546 * Gets whether this patcher is working on a statement or an expression.
547 */
548 NodePatcher.prototype.willPatchAsExpression = function () {
549 return this._expression;
550 };
551 /**
552 * Gets whether this patcher was forced to patch its node as an expression.
553 */
554 NodePatcher.prototype.forcedToPatchAsExpression = function () {
555 return this.willPatchAsExpression() && !this.prefersToPatchAsExpression();
556 };
557 /**
558 * Marks this node as an assignee. Nested assignees, like destructure
559 * operations, should override this method and propagate it to the children.
560 */
561 NodePatcher.prototype.setAssignee = function () {
562 this._assignee = true;
563 };
564 /**
565 * Checks if this node has been marked as an assignee. This is particularly
566 * useful for distinguishing rest from spread operations.
567 */
568 NodePatcher.prototype.isAssignee = function () {
569 return this._assignee;
570 };
571 /**
572 * Gets whether this patcher's node implicitly returns.
573 */
574 NodePatcher.prototype.implicitlyReturns = function () {
575 return this._implicitlyReturns || false;
576 };
577 /**
578 * Causes the node to be returned from its function.
579 */
580 NodePatcher.prototype.setImplicitlyReturns = function () {
581 this._implicitlyReturns = true;
582 };
583 /**
584 * Gets the ancestor that will decide the current implicit return behavior.
585 * That ancestor will then have implicitReturnWillBreak,
586 * patchImplicitReturnStart, and patchImplicitReturnEnd methods that describe
587 * how to handle expressions in an implicit return position (usually they are
588 * just returned, but in the case of loop IIFEs, they will be added to a
589 * list).
590 */
591 NodePatcher.prototype.implicitReturnPatcher = function () {
592 if (this.canHandleImplicitReturn()) {
593 return this;
594 }
595 else {
596 return notNull(this.parent).implicitReturnPatcher();
597 }
598 };
599 /**
600 * Subclasses should return true to declare themselves as the "handler" in an
601 * implicit return situation.
602 */
603 NodePatcher.prototype.canHandleImplicitReturn = function () {
604 return false;
605 };
606 /**
607 * Determines whether the current patcher (which has already declared that it
608 * can be an implicit return patcher) will generate code that stops execution
609 * in the current block. In the normal case of a return statement, this is
610 * true, but in loop IIFEs, there might be e.g. an assignment, which means
611 * that the control flow won't necessarily stop.
612 */
613 NodePatcher.prototype.implicitReturnWillBreak = function () {
614 return true;
615 };
616 /**
617 * Patch the beginning of an implicitly-returned descendant. Unlike most
618 * statements, implicitly-returned statements will not have their surrounding
619 * parens removed, so the implicit return patching may need to remove
620 * surrounding parens.
621 */
622 NodePatcher.prototype.patchImplicitReturnStart = function (patcher) {
623 if (patcher.node.type === 'Break' || patcher.node.type === 'Continue') {
624 if (patcher.isSurroundedByParentheses()) {
625 this.remove(patcher.outerStart, patcher.innerStart);
626 this.remove(patcher.innerEnd, patcher.outerEnd);
627 }
628 return;
629 }
630 if (isFunction(this.node) && this.isMultiline()) {
631 this.addSuggestion(CLEAN_UP_IMPLICIT_RETURNS);
632 }
633 patcher.setRequiresExpression();
634 this.insert(patcher.outerStart, 'return ');
635 };
636 /**
637 * Return null to indicate that no empty case code should be generated.
638 */
639 NodePatcher.prototype.getEmptyImplicitReturnCode = function () {
640 return null;
641 };
642 /**
643 * Patch the end of an implicitly-returned descendant.
644 */
645 // eslint-disable-next-line @typescript-eslint/no-unused-vars
646 NodePatcher.prototype.patchImplicitReturnEnd = function (_patcher) {
647 // Nothing to do.
648 };
649 /**
650 * Gets whether this patcher's node returns explicitly from its function.
651 */
652 NodePatcher.prototype.explicitlyReturns = function () {
653 return this._returns || false;
654 };
655 /**
656 * Marks this patcher's as containing a node that explicitly returns.
657 */
658 NodePatcher.prototype.setExplicitlyReturns = function () {
659 this._returns = true;
660 if (this.parent) {
661 this.parent.setExplicitlyReturns();
662 }
663 };
664 /**
665 * Mark that this node should have the given suffix appended at the end of
666 * patching. For example, this allows a child node to indicate that this node
667 * should end with a close-paren, and to do so in a way that respects patching
668 * order (doesn't add the close-paren too early).
669 */
670 NodePatcher.prototype.appendDeferredSuffix = function (suffix) {
671 this._deferredSuffix += suffix;
672 };
673 /**
674 * Internal method that should be called at the end of patching to actually
675 * place the deferred suffix in the right place.
676 *
677 * @protected
678 */
679 NodePatcher.prototype.commitDeferredSuffix = function () {
680 if (this._deferredSuffix) {
681 this.insert(this.innerEnd, this._deferredSuffix);
682 }
683 };
684 /**
685 * Determines whether this patcher's node needs a semicolon after it. This
686 * should be overridden in subclasses as appropriate.
687 */
688 NodePatcher.prototype.statementNeedsSemicolon = function () {
689 return true;
690 };
691 /**
692 * Determines whether, when appearing as a statement, this patcher's node
693 * needs to be surrounded by parentheses.
694 *
695 * Subclasses should override this and, typically, delegate to their leftmost
696 * child patcher. Subclasses may return `false` when they will insert text at
697 * the start of the node.
698 */
699 NodePatcher.prototype.statementNeedsParens = function () {
700 return false;
701 };
702 /**
703 * Determines whether this patcher's node should add parentheses when used in
704 * a statement context.
705 */
706 NodePatcher.prototype.statementShouldAddParens = function () {
707 return this.statementNeedsParens() && !this.isSurroundedByParentheses();
708 };
709 /**
710 * Gets the tokens for the whole program.
711 */
712 NodePatcher.prototype.getProgramSourceTokens = function () {
713 return this.context.sourceTokens;
714 };
715 /**
716 * Gets the index of the token starting at a particular source index.
717 */
718 NodePatcher.prototype.indexOfSourceTokenStartingAtSourceIndex = function (index) {
719 return this.getProgramSourceTokens().indexOfTokenStartingAtSourceIndex(index);
720 };
721 /**
722 * Gets the index of the token between left and right patchers that matches
723 * a predicate function.
724 */
725 NodePatcher.prototype.indexOfSourceTokenBetweenPatchersMatching = function (left, right, predicate) {
726 return this.indexOfSourceTokenBetweenSourceIndicesMatching(left.outerEnd, right.outerStart, predicate);
727 };
728 /**
729 * Gets the index of the token between source locations that matches a
730 * predicate function.
731 */
732 NodePatcher.prototype.indexOfSourceTokenBetweenSourceIndicesMatching = function (left, right, predicate) {
733 var tokenList = this.getProgramSourceTokens();
734 return tokenList.indexOfTokenMatchingPredicate(function (token) {
735 return token.start >= left && token.start <= right && predicate(token);
736 }, tokenList.indexOfTokenNearSourceIndex(left), tokenList.indexOfTokenNearSourceIndex(right).next());
737 };
738 /**
739 * Gets the token at a particular index.
740 */
741 NodePatcher.prototype.sourceTokenAtIndex = function (index) {
742 return this.getProgramSourceTokens().tokenAtIndex(index);
743 };
744 /**
745 * Gets the source encompassed by the given token.
746 */
747 NodePatcher.prototype.sourceOfToken = function (token) {
748 return this.context.source.slice(token.start, token.end);
749 };
750 /**
751 * Gets the first token in the content of this node.
752 */
753 NodePatcher.prototype.firstToken = function () {
754 var token = this.sourceTokenAtIndex(this.contentStartTokenIndex);
755 if (!token) {
756 throw this.error('Expected to find a first token for node.');
757 }
758 return token;
759 };
760 /**
761 * Gets the last token in the content of this node.
762 */
763 NodePatcher.prototype.lastToken = function () {
764 var token = this.sourceTokenAtIndex(this.contentEndTokenIndex);
765 if (!token) {
766 throw this.error('Expected to find a last token for node.');
767 }
768 return token;
769 };
770 /**
771 * Gets the token after the end of this node, or null if there is none.
772 */
773 NodePatcher.prototype.nextSemanticToken = function () {
774 return this.getFirstSemanticToken(this.contentEnd, this.context.source.length);
775 };
776 /**
777 * Gets the original source of this patcher's node.
778 */
779 NodePatcher.prototype.getOriginalSource = function () {
780 return this.context.source.slice(this.contentStart, this.contentEnd);
781 };
782 /**
783 * Determines whether this patcher's node spanned multiple lines.
784 */
785 NodePatcher.prototype.isMultiline = function () {
786 return /\n/.test(this.getOriginalSource());
787 };
788 /**
789 * Gets the patched source of this patcher's node.
790 */
791 NodePatcher.prototype.getPatchedSource = function () {
792 return this.slice(this.contentStart, this.contentEnd);
793 };
794 /**
795 * Gets the index of a token after `contentStart` with the matching type, ignoring
796 * non-semantic types by default.
797 */
798 NodePatcher.prototype.indexOfSourceTokenAfterSourceTokenIndex = function (start, type, predicate) {
799 if (predicate === void 0) { predicate = isSemanticToken; }
800 var index = this.getProgramSourceTokens().indexOfTokenMatchingPredicate(predicate, start.next());
801 if (!index) {
802 return null;
803 }
804 var token = this.sourceTokenAtIndex(index);
805 if (!token || token.type !== type) {
806 return null;
807 }
808 return index;
809 };
810 /**
811 * Determines whether this patcher's node is followed by a particular token.
812 */
813 NodePatcher.prototype.hasSourceTokenAfter = function (type, predicate) {
814 if (predicate === void 0) { predicate = isSemanticToken; }
815 return this.indexOfSourceTokenAfterSourceTokenIndex(this.outerEndTokenIndex, type, predicate) !== null;
816 };
817 /**
818 * Determines whether this patcher's node is surrounded by parentheses.
819 * Also check if these parents are matching, to avoid false positives on things like `(a) && (b)`
820 */
821 NodePatcher.prototype.isSurroundedByParentheses = function () {
822 // Surrounding parens will extend outer start/end beyond content start/end,
823 // so only consider parens in that case. If we didn't exit early here, we'd
824 // get false positives for nodes that start and end with parens without
825 // actually being surrounded by parens.
826 if (this.contentStart === this.outerStart && this.contentEnd === this.outerEnd) {
827 return false;
828 }
829 var beforeToken = this.sourceTokenAtIndex(this.outerStartTokenIndex);
830 var afterToken = this.sourceTokenAtIndex(this.outerEndTokenIndex);
831 if (!beforeToken || !afterToken) {
832 return false;
833 }
834 var leftTokenType = SourceType.LPAREN;
835 var rightTokenType = SourceType.RPAREN;
836 if (beforeToken.type === SourceType.LPAREN && afterToken.type === SourceType.RPAREN) {
837 // nothing
838 }
839 else if (beforeToken.type === SourceType.CALL_START && afterToken.type === SourceType.CALL_END) {
840 leftTokenType = SourceType.CALL_START;
841 rightTokenType = SourceType.CALL_END;
842 }
843 else {
844 return false;
845 }
846 var parenRange = this.getProgramSourceTokens().rangeOfMatchingTokensContainingTokenIndex(leftTokenType, rightTokenType, this.outerStartTokenIndex);
847 if (!parenRange) {
848 return false;
849 }
850 var rparenIndex = parenRange[1].previous();
851 var rparen = this.sourceTokenAtIndex(notNull(rparenIndex));
852 return rparen === afterToken;
853 };
854 NodePatcher.prototype.surroundInParens = function () {
855 if (!this.isSurroundedByParentheses()) {
856 this.insert(this.outerStart, '(');
857 this.insert(this.outerEnd, ')');
858 }
859 };
860 NodePatcher.prototype.getBoundingPatcher = function () {
861 var _this = this;
862 if (this.isSurroundedByParentheses()) {
863 return this;
864 }
865 else if (this.parent) {
866 if (this.isNodeFunctionApplication(this.parent.node) &&
867 this.parent.node.arguments.some(function (arg) { return arg === _this.node; })) {
868 return this;
869 }
870 else if (this.parent.node.type === 'ArrayInitialiser') {
871 return this;
872 }
873 else if (this.parent.node.type === 'ObjectInitialiser') {
874 return this;
875 }
876 return this.parent.getBoundingPatcher();
877 }
878 else {
879 return this;
880 }
881 };
882 NodePatcher.prototype.isNodeFunctionApplication = function (node) {
883 return node instanceof FunctionApplication || node instanceof SoakedFunctionApplication || node instanceof NewOp;
884 };
885 /**
886 * Determines whether this patcher's node can be negated without prepending
887 * a `!`, which turns it into a unary operator node.
888 */
889 NodePatcher.prototype.canHandleNegationInternally = function () {
890 return false;
891 };
892 /**
893 * Negates this patcher's node when patching. Note that we add the `!` inside
894 * any parens, since it's generally unsafe to insert code outside our
895 * enclosing parens, and we need to handle the non-parenthesized case anyway.
896 * Subclasses that need to worry about precedence (e.g. binary operators)
897 * should override this method and do something more appropriate.
898 */
899 NodePatcher.prototype.negate = function () {
900 this.insert(this.contentStart, '!');
901 this._hadUnparenthesizedNegation = true;
902 };
903 /**
904 * Check if this node has been negated by simply adding a `!` to the front.
905 * In some cases, this node may be later changed into an expression that would
906 * require additional parens, e.g. a soak container being transformed into a
907 * ternary expression, so track the negation so we know to add parens if
908 * necessary.
909 *
910 * Note that most custom negate() implementations already add parens, so they
911 * don't need to return true here.
912 */
913 NodePatcher.prototype.hadUnparenthesizedNegation = function () {
914 return this._hadUnparenthesizedNegation;
915 };
916 NodePatcher.prototype.getScope = function () {
917 return this.context.getScope(this.node);
918 };
919 /**
920 * Gets the indent string for the line that starts this patcher's node.
921 */
922 NodePatcher.prototype.getIndent = function (offset) {
923 if (offset === void 0) { offset = 0; }
924 return adjustIndent(this.context.source, this.contentStart, this.getAdjustedIndentLevel() + offset);
925 };
926 /**
927 * Force the indentation level of this node, adjusting it forward or backward
928 * if necessary. This also sets the "adjusted indent" level, so that later
929 * calls to getIndent will return this value.
930 */
931 NodePatcher.prototype.setIndent = function (indentStr) {
932 var currentIndent = this.getIndent();
933 var indentLength = this.getProgramIndentString().length;
934 var currentIndentLevel = currentIndent.length / indentLength;
935 var desiredIndentLevel = indentStr.length / indentLength;
936 this.indent(desiredIndentLevel - currentIndentLevel);
937 };
938 /**
939 * Get the amount the adjusted indent level differs from the original level.
940 */
941 NodePatcher.prototype.getAdjustedIndentLevel = function () {
942 return this.adjustedIndentLevel + (this.parent ? this.parent.getAdjustedIndentLevel() : 0);
943 };
944 /**
945 * Gets the indent string used for each indent in this program.
946 */
947 NodePatcher.prototype.getProgramIndentString = function () {
948 return notNull(this.parent).getProgramIndentString();
949 };
950 /**
951 * Indent this node a number of times. To unindent, pass a negative number.
952 *
953 * Note that because this method inserts indents immediately before the first
954 * non-whitespace character of each line in the node's source, it should be
955 * called *before* any other editing is done to the node's source to ensure
956 * that strings inserted before child nodes appear after the indent, not
957 * before.
958 */
959 NodePatcher.prototype.indent = function (offset, _a) {
960 if (offset === void 0) { offset = 1; }
961 var _b = (_a === void 0 ? {} : _a).skipFirstLine, skipFirstLine = _b === void 0 ? false : _b;
962 if (offset === 0) {
963 return;
964 }
965 this.adjustedIndentLevel += offset;
966 var indentString = this.getProgramIndentString();
967 var indentToChange = indentString.repeat(Math.abs(offset));
968 var start = this.outerStart;
969 var end = this.outerEnd;
970 var source = this.context.source;
971 // See if there are already non-whitespace characters before the start. If
972 // so, skip the start to the next line, since we don't want to put
973 // indentation in the middle of a line.
974 if (skipFirstLine || !this.isFirstNodeInLine()) {
975 while (start < end && source[start] !== '\n') {
976 start++;
977 }
978 }
979 var hasIndentedThisLine = false;
980 for (var i = start; i < end; i++) {
981 switch (source[i]) {
982 case '\n':
983 hasIndentedThisLine = false;
984 break;
985 case ' ':
986 case '\t':
987 break;
988 default:
989 if (!hasIndentedThisLine) {
990 if (offset > 0) {
991 this.insert(i, indentToChange);
992 }
993 else if (source.slice(i - indentToChange.length, i) === indentToChange) {
994 this.remove(i - indentToChange.length, i);
995 }
996 else {
997 // Ignore this case: we're trying to unindent a line that doesn't
998 // start with enough indentation, or doesn't start with the right
999 // type of indentation, e.g. it starts with spaces when the
1000 // program indent string is a tab. This can happen when a file
1001 // uses inconsistent indentation in different parts. We only
1002 // expect this to come up in the main stage, so getting
1003 // indentation wrong means ugly JS code that's still correct.
1004 this.log('Warning: Ignoring an unindent operation because the line ' +
1005 'did not start with the proper indentation.');
1006 }
1007 hasIndentedThisLine = true;
1008 }
1009 break;
1010 }
1011 }
1012 };
1013 NodePatcher.prototype.isFirstNodeInLine = function (startingPoint) {
1014 if (startingPoint === void 0) { startingPoint = this.outerStart; }
1015 var source = this.context.source;
1016 for (var i = startingPoint - 1; i >= 0 && source[i] !== '\n'; i--) {
1017 if (source[i] !== '\t' && source[i] !== ' ') {
1018 return false;
1019 }
1020 }
1021 return true;
1022 };
1023 /**
1024 * Gets the index ending the line following this patcher's node.
1025 *
1026 * @private
1027 */
1028 NodePatcher.prototype.getEndOfLine = function () {
1029 var source = this.context.source;
1030 for (var i = this.outerEnd - '\n'.length; i < source.length; i++) {
1031 if (source[i] === '\n') {
1032 return i;
1033 }
1034 }
1035 return source.length;
1036 };
1037 /**
1038 * Appends the given content on a new line after the end of the current line.
1039 */
1040 NodePatcher.prototype.appendLineAfter = function (content, indentOffset) {
1041 if (indentOffset === void 0) { indentOffset = 0; }
1042 var boundingPatcher = this.getBoundingPatcher();
1043 var endOfLine = this.getEndOfLine();
1044 var nextToken = this.nextSemanticToken();
1045 var insertPoint = Math.min(Math.min(endOfLine, boundingPatcher.innerEnd));
1046 if (nextToken) {
1047 insertPoint = Math.min(insertPoint, nextToken.start);
1048 }
1049 this.insert(insertPoint, "\n" + this.getIndent(indentOffset) + content);
1050 };
1051 /**
1052 * Generate an error referring to a particular section of the source.
1053 */
1054 NodePatcher.prototype.error = function (message, start, end, error) {
1055 if (start === void 0) { start = this.contentStart; }
1056 if (end === void 0) { end = this.contentEnd; }
1057 if (error === void 0) { error = null; }
1058 var patcherError = new PatcherError(message, this.context.source, start, end);
1059 if (error) {
1060 patcherError.stack = error.stack;
1061 }
1062 return patcherError;
1063 };
1064 /**
1065 * Register a helper to be reused in several places.
1066 */
1067 NodePatcher.prototype.registerHelper = function (name, code) {
1068 return notNull(this.parent).registerHelper(name, code);
1069 };
1070 /**
1071 * Determines whether this code might have side-effects when run. Most of the
1072 * time this is the same as isRepeatable, but sometimes the node is
1073 * long/complicated enough that it's better to extract it as a variable rather
1074 * than repeat the expression. In that case, a node may declare itself as pure
1075 * but not repeatable.
1076 */
1077 NodePatcher.prototype.isPure = function () {
1078 return this.isRepeatable();
1079 };
1080 /**
1081 * Determines whether this node can be repeated without side-effects. Most
1082 * nodes are not repeatable, so that is the default. Subclasses should
1083 * override this to indicate whether they are repeatable without any changes.
1084 */
1085 NodePatcher.prototype.isRepeatable = function () {
1086 return false;
1087 };
1088 /**
1089 * Indicate to this patcher that patching should be done in a way that makes
1090 * it possible to reference the value afterward with no additional
1091 * side-effects.
1092 */
1093 NodePatcher.prototype.setRequiresRepeatableExpression = function (repeatableOptions) {
1094 if (repeatableOptions === void 0) { repeatableOptions = {}; }
1095 this._repeatableOptions = repeatableOptions;
1096 };
1097 /**
1098 * Check if this expression has been marked as repeatable, and if so, the
1099 * repeat options used. Generally this should only be used for advanced cases,
1100 * like transferring the repeat code result from one patcher to another.
1101 */
1102 NodePatcher.prototype.getRepeatableOptions = function () {
1103 return this._repeatableOptions;
1104 };
1105 /**
1106 * Get the code snippet computed from patchAsRepeatableExpression that can be
1107 * used to refer to the result of this expression without further
1108 * side-effects.
1109 */
1110 NodePatcher.prototype.getRepeatCode = function () {
1111 if (this._repeatCode === null) {
1112 throw new Error('Must patch as a repeatable expression to access repeat code.');
1113 }
1114 return this._repeatCode;
1115 };
1116 /**
1117 * Explicitly set the repeatable result. Generally this should only be used
1118 * for advanced cases, like transferring the repeat code result from one
1119 * patcher to another.
1120 */
1121 NodePatcher.prototype.overrideRepeatCode = function (repeatCode) {
1122 this._repeatCode = repeatCode;
1123 };
1124 /**
1125 * Claim a binding that is unique in the current scope.
1126 */
1127 NodePatcher.prototype.claimFreeBinding = function (ref) {
1128 if (ref === void 0) { ref = null; }
1129 return this.getScope().claimFreeBinding(this.node, ref);
1130 };
1131 /**
1132 * Determines whether all the possible code paths in this node are present.
1133 */
1134 NodePatcher.prototype.allCodePathsPresent = function () {
1135 return true;
1136 };
1137 /**
1138 * Gets the first "interesting token" in the indexed range (default range is `this` + parent)
1139 */
1140 NodePatcher.prototype.getFirstSemanticToken = function (from, to) {
1141 if (from === void 0) { from = this.contentStart; }
1142 if (to === void 0) { to = notNull(this.parent).contentEnd; }
1143 var nextSemanticIdx = this.indexOfSourceTokenBetweenSourceIndicesMatching(from, to, isSemanticToken);
1144 return nextSemanticIdx && this.sourceTokenAtIndex(nextSemanticIdx);
1145 };
1146 /**
1147 * Determine if we need to do a `typeof` check in a conditional for this
1148 * value, to guard against the case where this node is a variable that doesn't
1149 * exist. IdentifierPatcher overrides this to check the current scope.
1150 */
1151 NodePatcher.prototype.mayBeUnboundReference = function () {
1152 return false;
1153 };
1154 NodePatcher.prototype.patchInIIFE = function (innerPatchFn) {
1155 this.addSuggestion(AVOID_IIFES);
1156 if (this.containsYield()) {
1157 this.insert(this.innerStart, 'yield* (function*() {');
1158 }
1159 else if (this.containsAwait()) {
1160 this.insert(this.innerStart, 'await (async () => {');
1161 }
1162 else {
1163 this.insert(this.innerStart, '(() => {');
1164 }
1165 innerPatchFn();
1166 if (this.containsYield()) {
1167 if (referencesArguments(this.node)) {
1168 this.insert(this.innerEnd, '}).apply(this, arguments)');
1169 }
1170 else {
1171 this.insert(this.innerEnd, '}).call(this)');
1172 }
1173 }
1174 else if (this.containsAwait()) {
1175 this.insert(this.innerEnd, '})()');
1176 }
1177 else {
1178 this.insert(this.innerEnd, '})()');
1179 }
1180 };
1181 /**
1182 * Call to indicate that this node yields.
1183 */
1184 NodePatcher.prototype.yields = function () {
1185 this._containsYield = true;
1186 if (this.parent && !isFunction(this.parent.node)) {
1187 this.parent.yields();
1188 }
1189 };
1190 /**
1191 * Determine if this node or one of its children within the function is a
1192 * yield statement.
1193 */
1194 NodePatcher.prototype.containsYield = function () {
1195 return this._containsYield;
1196 };
1197 NodePatcher.prototype.awaits = function () {
1198 this._containsAwait = true;
1199 if (this.parent && !isFunction(this.parent.node)) {
1200 this.parent.awaits();
1201 }
1202 };
1203 NodePatcher.prototype.containsAwait = function () {
1204 return this._containsAwait;
1205 };
1206 return NodePatcher;
1207}());
1208export default NodePatcher;