UNPKG

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