-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added transform for logical assignment operators
- Loading branch information
Showing
9 changed files
with
236 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import type {HelperManager} from "../HelperManager"; | ||
import type {Token} from "../parser/tokenizer"; | ||
import {TokenType as tt} from "../parser/tokenizer/types"; | ||
import type TokenProcessor from "../TokenProcessor"; | ||
import type RootTransformer from "./RootTransformer"; | ||
import Transformer from "./Transformer"; | ||
|
||
const LOGICAL_OPERATORS = ["&&=", "||=", "??="]; | ||
|
||
export default class LogicalAssignmentTransformer extends Transformer { | ||
constructor( | ||
readonly rootTransformer: RootTransformer, | ||
readonly tokens: TokenProcessor, | ||
readonly helperManager: HelperManager, | ||
) { | ||
super(); | ||
} | ||
|
||
process(): boolean { | ||
if (this.tokens.matches2(tt.name, tt.bracketL)) { | ||
// This searches for computed property access e.g. `_.x[y] &&=`, or `_.x[y = z + f()] &&=` | ||
|
||
// At this point we may end up at a logical assignment operator but | ||
// we don't know yet. We continue processing the code as usual, but save | ||
// some information that we will need later in case it is. | ||
// this.rootTransformer.processToken(); | ||
this.tokens.copyExpectedToken(tt.name); | ||
const snapshot = this.tokens.snapshot(); // A snapshot that we use to extract the code within `[]` | ||
this.rootTransformer.processToken(); | ||
// this.tokens.copyExpectedToken(tt.bracketL); | ||
const start = this.tokens.getResultCodeIndex(); // code position just after `[` | ||
this.rootTransformer.processBalancedCode(); | ||
const end = this.tokens.getResultCodeIndex(); // code position just before `]` | ||
this.rootTransformer.processToken(); | ||
// this.tokens.copyExpectedToken(tt.bracketR); | ||
|
||
if (!this.tokens.matches1(tt.assign)) { | ||
return false; | ||
} | ||
|
||
const op = this.findOpToken(); | ||
if (!op) { | ||
return false; | ||
} | ||
|
||
const propAccess = this.tokens.snapshot().resultCode.slice(start, end); | ||
|
||
// Skip forward to after the assignment operator | ||
snapshot.tokenIndex = this.tokens.currentIndex() + 1; | ||
this.tokens.restoreToSnapshot(snapshot); | ||
this.tokens.appendCode(`, ${propAccess}, '${op.code}', () => `); | ||
|
||
this.processRhs(op.token); | ||
|
||
this.tokens.appendCode(")"); | ||
|
||
return true; | ||
} else if (this.tokens.matches3(tt.dot, tt.name, tt.assign)) { | ||
// This searches for dot property access e.g. `_.key &&=` | ||
|
||
const op = this.findOpToken(2); | ||
if (!op) { | ||
return false; | ||
} | ||
|
||
// As opposed to the computed prop case, this is a lot simpler, because | ||
// we know upfront what tokens can be part of the access on the lhs. | ||
|
||
// We skip over the tokens and extract the name to ensure | ||
this.tokens.nextToken(); // Skip the tt.dot | ||
const propName = this.tokens.identifierName(); | ||
this.tokens.nextToken(); // Skip the tt.name | ||
this.tokens.nextToken(); // Skip the tt.assign | ||
this.tokens.appendCode(`, '${propName}', '${op.code}', () => `); | ||
|
||
this.processRhs(op.token); | ||
|
||
this.tokens.appendCode(")"); | ||
} else if (this.tokens.matches2(tt.name, tt.assign)) { | ||
// This searches for plain variable assignment, e.g. `a &&= b` | ||
|
||
const op = this.findOpToken(1); | ||
if (!op) { | ||
return false; | ||
} | ||
|
||
// At this point we know that this is a simple `a &&= b` to assignment, and we can | ||
// use a simple transform to e.g. `a && (a = b)` without using the helper function. | ||
|
||
const plainName = this.tokens.identifierName(); | ||
|
||
this.tokens.copyToken(); // Copy the identifier | ||
this.tokens.nextToken(); // Skip the original assignment operator | ||
|
||
if (op.code === "??=") { | ||
// We transform null coalesce ourselves here, e.g. `a != null ? a : (a = b)` | ||
this.tokens.appendCode(` != null ? ${plainName} : (${plainName} =`); | ||
} else { | ||
this.tokens.appendCode(` ${op.code.slice(0, 2)} (${plainName} =`); | ||
} | ||
|
||
this.processRhs(op.token); | ||
|
||
this.tokens.appendCode(")"); | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
// Checks whether there's a matching logical assignment operator token at provided relative token index | ||
private findOpToken(relativeIndex: number = 0): {token: Token; code: string} | undefined { | ||
const token = this.tokens.tokenAtRelativeIndex(relativeIndex); | ||
const code = this.tokens.rawCodeForToken(token); | ||
if (!LOGICAL_OPERATORS.includes(code)) { | ||
return undefined; | ||
} | ||
return {token, code}; | ||
} | ||
|
||
// This processes the right hand side of a logical assignment expression. We process | ||
// until the hit the rhsEndIndex as specified by the logical assignment operator token. | ||
private processRhs(token: Token): void { | ||
if (token.rhsEndIndex === null) { | ||
throw new Error("Unknown end of logical assignment, this is a bug in Sucrase"); | ||
} | ||
|
||
while (this.tokens.currentIndex() < token.rhsEndIndex) { | ||
this.rootTransformer.processToken(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters