diff options
| author | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:54:57 +0100 |
|---|---|---|
| committer | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:57:48 +0100 |
| commit | b1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c (patch) | |
| tree | 49d360fd6cbc6a2754efe93524ac47ff0fbe0f7d /node_modules/sucrase/dist/transformers/OptionalChainingNullishTransformer.js | |
Docs
Diffstat (limited to 'node_modules/sucrase/dist/transformers/OptionalChainingNullishTransformer.js')
| -rw-r--r-- | node_modules/sucrase/dist/transformers/OptionalChainingNullishTransformer.js | 155 |
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; |