156 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 | 
						|
var _types = require('../parser/tokenizer/types');
 | 
						|
 | 
						|
var _Transformer = require('./Transformer'); var _Transformer2 = _interopRequireDefault(_Transformer);
 | 
						|
 | 
						|
/**
 | 
						|
 * Transformer supporting the optional chaining and nullish coalescing operators.
 | 
						|
 *
 | 
						|
 * Tech plan here:
 | 
						|
 * https://github.com/alangpierce/sucrase/wiki/Sucrase-Optional-Chaining-and-Nullish-Coalescing-Technical-Plan
 | 
						|
 *
 | 
						|
 * The prefix and suffix code snippets are handled by TokenProcessor, and this transformer handles
 | 
						|
 * the operators themselves.
 | 
						|
 */
 | 
						|
 class OptionalChainingNullishTransformer extends _Transformer2.default {
 | 
						|
  constructor( tokens,  nameManager) {
 | 
						|
    super();this.tokens = tokens;this.nameManager = nameManager;;
 | 
						|
  }
 | 
						|
 | 
						|
  process() {
 | 
						|
    if (this.tokens.matches1(_types.TokenType.nullishCoalescing)) {
 | 
						|
      const token = this.tokens.currentToken();
 | 
						|
      if (this.tokens.tokens[token.nullishStartIndex].isAsyncOperation) {
 | 
						|
        this.tokens.replaceTokenTrimmingLeftWhitespace(", async () => (");
 | 
						|
      } else {
 | 
						|
        this.tokens.replaceTokenTrimmingLeftWhitespace(", () => (");
 | 
						|
      }
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    if (this.tokens.matches1(_types.TokenType._delete)) {
 | 
						|
      const nextToken = this.tokens.tokenAtRelativeIndex(1);
 | 
						|
      if (nextToken.isOptionalChainStart) {
 | 
						|
        this.tokens.removeInitialToken();
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    const token = this.tokens.currentToken();
 | 
						|
    const chainStart = token.subscriptStartIndex;
 | 
						|
    if (
 | 
						|
      chainStart != null &&
 | 
						|
      this.tokens.tokens[chainStart].isOptionalChainStart &&
 | 
						|
      // Super subscripts can't be optional (since super is never null/undefined), and the syntax
 | 
						|
      // relies on the subscript being intact, so leave this token alone.
 | 
						|
      this.tokens.tokenAtRelativeIndex(-1).type !== _types.TokenType._super
 | 
						|
    ) {
 | 
						|
      const param = this.nameManager.claimFreeName("_");
 | 
						|
      let arrowStartSnippet;
 | 
						|
      if (
 | 
						|
        chainStart > 0 &&
 | 
						|
        this.tokens.matches1AtIndex(chainStart - 1, _types.TokenType._delete) &&
 | 
						|
        this.isLastSubscriptInChain()
 | 
						|
      ) {
 | 
						|
        // Delete operations are special: we already removed the delete keyword, and to still
 | 
						|
        // perform a delete, we need to insert a delete in the very last part of the chain, which
 | 
						|
        // in correct code will always be a property access.
 | 
						|
        arrowStartSnippet = `${param} => delete ${param}`;
 | 
						|
      } else {
 | 
						|
        arrowStartSnippet = `${param} => ${param}`;
 | 
						|
      }
 | 
						|
      if (this.tokens.tokens[chainStart].isAsyncOperation) {
 | 
						|
        arrowStartSnippet = `async ${arrowStartSnippet}`;
 | 
						|
      }
 | 
						|
      if (
 | 
						|
        this.tokens.matches2(_types.TokenType.questionDot, _types.TokenType.parenL) ||
 | 
						|
        this.tokens.matches2(_types.TokenType.questionDot, _types.TokenType.lessThan)
 | 
						|
      ) {
 | 
						|
        if (this.justSkippedSuper()) {
 | 
						|
          this.tokens.appendCode(".bind(this)");
 | 
						|
        }
 | 
						|
        this.tokens.replaceTokenTrimmingLeftWhitespace(`, 'optionalCall', ${arrowStartSnippet}`);
 | 
						|
      } else if (this.tokens.matches2(_types.TokenType.questionDot, _types.TokenType.bracketL)) {
 | 
						|
        this.tokens.replaceTokenTrimmingLeftWhitespace(`, 'optionalAccess', ${arrowStartSnippet}`);
 | 
						|
      } else if (this.tokens.matches1(_types.TokenType.questionDot)) {
 | 
						|
        this.tokens.replaceTokenTrimmingLeftWhitespace(`, 'optionalAccess', ${arrowStartSnippet}.`);
 | 
						|
      } else if (this.tokens.matches1(_types.TokenType.dot)) {
 | 
						|
        this.tokens.replaceTokenTrimmingLeftWhitespace(`, 'access', ${arrowStartSnippet}.`);
 | 
						|
      } else if (this.tokens.matches1(_types.TokenType.bracketL)) {
 | 
						|
        this.tokens.replaceTokenTrimmingLeftWhitespace(`, 'access', ${arrowStartSnippet}[`);
 | 
						|
      } else if (this.tokens.matches1(_types.TokenType.parenL)) {
 | 
						|
        if (this.justSkippedSuper()) {
 | 
						|
          this.tokens.appendCode(".bind(this)");
 | 
						|
        }
 | 
						|
        this.tokens.replaceTokenTrimmingLeftWhitespace(`, 'call', ${arrowStartSnippet}(`);
 | 
						|
      } else {
 | 
						|
        throw new Error("Unexpected subscript operator in optional chain.");
 | 
						|
      }
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Determine if the current token is the last of its chain, so that we know whether it's eligible
 | 
						|
   * to have a delete op inserted.
 | 
						|
   *
 | 
						|
   * We can do this by walking forward until we determine one way or another. Each
 | 
						|
   * isOptionalChainStart token must be paired with exactly one isOptionalChainEnd token after it in
 | 
						|
   * a nesting way, so we can track depth and walk to the end of the chain (the point where the
 | 
						|
   * depth goes negative) and see if any other subscript token is after us in the chain.
 | 
						|
   */
 | 
						|
  isLastSubscriptInChain() {
 | 
						|
    let depth = 0;
 | 
						|
    for (let i = this.tokens.currentIndex() + 1; ; i++) {
 | 
						|
      if (i >= this.tokens.tokens.length) {
 | 
						|
        throw new Error("Reached the end of the code while finding the end of the access chain.");
 | 
						|
      }
 | 
						|
      if (this.tokens.tokens[i].isOptionalChainStart) {
 | 
						|
        depth++;
 | 
						|
      } else if (this.tokens.tokens[i].isOptionalChainEnd) {
 | 
						|
        depth--;
 | 
						|
      }
 | 
						|
      if (depth < 0) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
 | 
						|
      // This subscript token is a later one in the same chain.
 | 
						|
      if (depth === 0 && this.tokens.tokens[i].subscriptStartIndex != null) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Determine if we are the open-paren in an expression like super.a()?.b.
 | 
						|
   *
 | 
						|
   * We can do this by walking backward to find the previous subscript. If that subscript was
 | 
						|
   * preceded by a super, then we must be the subscript after it, so if this is a call expression,
 | 
						|
   * we'll need to attach the right context.
 | 
						|
   */
 | 
						|
  justSkippedSuper() {
 | 
						|
    let depth = 0;
 | 
						|
    let index = this.tokens.currentIndex() - 1;
 | 
						|
    while (true) {
 | 
						|
      if (index < 0) {
 | 
						|
        throw new Error(
 | 
						|
          "Reached the start of the code while finding the start of the access chain.",
 | 
						|
        );
 | 
						|
      }
 | 
						|
      if (this.tokens.tokens[index].isOptionalChainStart) {
 | 
						|
        depth--;
 | 
						|
      } else if (this.tokens.tokens[index].isOptionalChainEnd) {
 | 
						|
        depth++;
 | 
						|
      }
 | 
						|
      if (depth < 0) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
 | 
						|
      // This subscript token is a later one in the same chain.
 | 
						|
      if (depth === 0 && this.tokens.tokens[index].subscriptStartIndex != null) {
 | 
						|
        return this.tokens.tokens[index - 1].type === _types.TokenType._super;
 | 
						|
      }
 | 
						|
      index--;
 | 
						|
    }
 | 
						|
  }
 | 
						|
} exports.default = OptionalChainingNullishTransformer;
 |