Skip to content

Commit

Permalink
Allow super when using optional chaining (#498)
Browse files Browse the repository at this point in the history
Progress toward #461

Tech plan:
https://github.com/alangpierce/sucrase/wiki/Sucrase-Optional-Chaining-and-Nullish-Coalescing-Technical-Plan

To allow super, we mostly just need to skip the code transform on the first
subscript. We also need to add a `.bind(this)` when transforming the second
subscript, which can be determined through a relatively straightforward token
scan.
  • Loading branch information
alangpierce committed Jan 2, 2020
1 parent 626ee5e commit 3bd2674
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 1 deletion.
47 changes: 46 additions & 1 deletion src/transformers/OptionalChainingNullishTransformer.ts
Expand Up @@ -36,7 +36,13 @@ export default class OptionalChainingNullishTransformer extends Transformer {
}
const token = this.tokens.currentToken();
const chainStart = token.subscriptStartIndex;
if (chainStart != null && this.tokens.tokens[chainStart].isOptionalChainStart) {
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 !== tt._super
) {
const param = this.nameManager.claimFreeName("_");
let arrowStartSnippet;
if (
Expand All @@ -58,6 +64,9 @@ export default class OptionalChainingNullishTransformer extends Transformer {
this.tokens.matches2(tt.questionDot, tt.parenL) ||
this.tokens.matches2(tt.questionDot, tt.lessThan)
) {
if (this.justSkippedSuper()) {
this.tokens.appendCode(".bind(this)");
}
this.tokens.replaceTokenTrimmingLeftWhitespace(`, 'optionalCall', ${arrowStartSnippet}`);
} else if (this.tokens.matches2(tt.questionDot, tt.bracketL)) {
this.tokens.replaceTokenTrimmingLeftWhitespace(`, 'optionalAccess', ${arrowStartSnippet}`);
Expand All @@ -68,6 +77,9 @@ export default class OptionalChainingNullishTransformer extends Transformer {
} else if (this.tokens.matches1(tt.bracketL)) {
this.tokens.replaceTokenTrimmingLeftWhitespace(`, 'access', ${arrowStartSnippet}[`);
} else if (this.tokens.matches1(tt.parenL)) {
if (this.justSkippedSuper()) {
this.tokens.appendCode(".bind(this)");
}
this.tokens.replaceTokenTrimmingLeftWhitespace(`, 'call', ${arrowStartSnippet}(`);
} else {
throw new Error("Unexpected subscript operator in optional chain.");
Expand Down Expand Up @@ -107,4 +119,37 @@ export default class OptionalChainingNullishTransformer extends Transformer {
}
}
}

/**
* 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(): boolean {
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 === tt._super;
}
index--;
}
}
}
40 changes: 40 additions & 0 deletions test/sucrase-test.ts
Expand Up @@ -1092,4 +1092,44 @@ describe("sucrase", () => {
{transforms: []},
);
});

it("allows super in an optional chain", () => {
assertResult(
`
class A extends B {
foo() {
console.log(super.foo?.a);
}
}
`,
`${OPTIONAL_CHAIN_PREFIX}
class A extends B {
foo() {
console.log(_optionalChain([super.foo, 'optionalAccess', _ => _.a]));
}
}
`,
{transforms: []},
);
});

it("allows a super method call in an optional chain", () => {
assertResult(
`
class A extends B {
foo() {
console.log(super.a()?.b);
}
}
`,
`${OPTIONAL_CHAIN_PREFIX}
class A extends B {
foo() {
console.log(_optionalChain([super.a.bind(this), 'call', _ => _(), 'optionalAccess', _2 => _2.b]));
}
}
`,
{transforms: []},
);
});
});

0 comments on commit 3bd2674

Please sign in to comment.