summaryrefslogtreecommitdiff
path: root/node_modules/sucrase/dist/transformers/OptionalChainingNullishTransformer.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/sucrase/dist/transformers/OptionalChainingNullishTransformer.js')
-rw-r--r--node_modules/sucrase/dist/transformers/OptionalChainingNullishTransformer.js155
1 files changed, 155 insertions, 0 deletions
diff --git a/node_modules/sucrase/dist/transformers/OptionalChainingNullishTransformer.js b/node_modules/sucrase/dist/transformers/OptionalChainingNullishTransformer.js
new file mode 100644
index 0000000..3a68a0a
--- /dev/null
+++ b/node_modules/sucrase/dist/transformers/OptionalChainingNullishTransformer.js
@@ -0,0 +1,155 @@
+"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;