Skip to content

Commit

Permalink
proposal-pipe: Add support for ^^ and @@ topics (#13973)
Browse files Browse the repository at this point in the history
Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
  • Loading branch information
js-choi and nicolo-ribaudo committed Feb 2, 2022
1 parent b91c3e1 commit df27d54
Show file tree
Hide file tree
Showing 694 changed files with 13,369 additions and 19 deletions.
2 changes: 2 additions & 0 deletions eslint/babel-eslint-parser/src/convert/convertTokens.cjs
Expand Up @@ -140,6 +140,8 @@ function convertToken(token, source, tl) {
label === tl.bracketHashL ||
label === tl.bracketBarL ||
label === tl.bracketBarR ||
label === tl.doubleCaret ||
label === tl.doubleAt ||
type.isAssign
) {
token.type = "Punctuator";
Expand Down
25 changes: 25 additions & 0 deletions eslint/babel-eslint-parser/test/index.js
Expand Up @@ -691,6 +691,31 @@ describe("Babel and Espree", () => {
expect(babylonAST.tokens[16]).toMatchObject(topicToken);
});

it.each(["^", "%", "^^", "@@"])("pipeline %s topic token", tok => {
const code = `
x |> ${tok}
y |> ${tok}[0]
`;

const babylonAST = parseForESLint(code, {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions: {
filename: "test.js",
parserOpts: {
plugins: [
["pipelineOperator", { proposal: "hack", topicToken: tok }],
],
tokens: true,
},
},
}).ast;

const topicToken = { type: "Punctuator", value: tok };
expect(babylonAST.tokens[2]).toMatchObject(topicToken);
expect(babylonAST.tokens[5]).toMatchObject(topicToken);
});

it("empty program with line comment", () => {
parseAndAssertSame("// single comment");
});
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-generator/src/generators/types.ts
Expand Up @@ -238,7 +238,7 @@ export function DecimalLiteral(this: Printer, node: t.DecimalLiteral) {
}

// Hack pipe operator
const validTopicTokenSet = new Set(["^", "%", "#"]);
const validTopicTokenSet = new Set(["^^", "@@", "^", "%", "#"]);
export function TopicReference(this: Printer) {
const { topicToken } = this.format;

Expand Down
2 changes: 1 addition & 1 deletion packages/babel-generator/src/index.ts
Expand Up @@ -203,7 +203,7 @@ export interface GeneratorOptions {
* For use with the Hack-style pipe operator.
* Changes what token is used for pipe bodies’ topic references.
*/
topicToken?: "^" | "%" | "#";
topicToken?: "^^" | "@@" | "^" | "%" | "#";
}

export interface GeneratorResult {
Expand Down
@@ -0,0 +1 @@
2 + 3 |> @@.toString(16);
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "@@" }]],
"topicToken": "@@"
}
@@ -0,0 +1 @@
2 + 3 |> @@.toString(16);
@@ -0,0 +1 @@
2 + 3 |> ^^.toString(16);
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "^^" }]],
"topicToken": "^^"
}
@@ -0,0 +1 @@
2 + 3 |> ^^.toString(16);
@@ -1,5 +1,5 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "#" }]],
"topicToken": "invalid",
"throws": "The \"topicToken\" generator option must be one of \"^\", \"%\", \"#\" (\"invalid\" received instead)."
"throws": "The \"topicToken\" generator option must be one of \"^^\", \"@@\", \"^\", \"%\", \"#\" (\"invalid\" received instead)."
}
@@ -1,4 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "#" }]],
"throws": "The \"topicToken\" generator option must be one of \"^\", \"%\", \"#\" (undefined received instead)."
"throws": "The \"topicToken\" generator option must be one of \"^^\", \"@@\", \"^\", \"%\", \"#\" (undefined received instead)."
}
17 changes: 11 additions & 6 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -1216,6 +1216,11 @@ export default class ExpressionParser extends LValParser {
return this.parseTopicReferenceThenEqualsSign(tt.bitwiseXOR, "^");
}

case tt.doubleCaret:
case tt.doubleAt: {
return this.parseTopicReference("hack");
}

case tt.bitwiseXOR:
case tt.modulo:
case tt.hash: {
Expand Down Expand Up @@ -1318,10 +1323,10 @@ export default class ExpressionParser extends LValParser {
// that is followed by an equals sign.
// See <https://github.com/js-choi/proposal-hack-pipes>.
// If we find ^= or %= in an expression position
// (i.e., the tt.moduloAssign or tt.xorAssign token types),
// and if the Hack-pipes proposal is active with ^ or % as its topicToken,
// then the ^ or % could be the topic token (e.g., in x |> ^==y or x |> ^===y),
// and so we reparse the current token as ^ or %.
// (i.e., the tt.moduloAssign or tt.xorAssign token types), and if the
// Hack-pipes proposal is active with ^ or % as its topicToken, then the ^ or
// % could be the topic token (e.g., in x |> ^==y or x |> ^===y), and so we
// reparse the current token as ^ or %.
// Otherwise, this throws an unexpected-token error.
parseTopicReferenceThenEqualsSign(
topicTokenType: TokenType,
Expand All @@ -1336,8 +1341,8 @@ export default class ExpressionParser extends LValParser {
// will consume that “topic token”.
this.state.type = topicTokenType;
this.state.value = topicTokenValue;
// Rewind the tokenizer to the end of the “topic token”,
// so that the following token starts at the equals sign after that topic token.
// Rewind the tokenizer to the end of the “topic token”, so that the
// following token starts at the equals sign after that topic token.
this.state.pos--;
this.state.end--;
// This is safe to do since the preceding character was either ^ or %, and
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-parser/src/plugin-utils.js
Expand Up @@ -67,7 +67,7 @@ export function getPluginOption(
}

const PIPELINE_PROPOSALS = ["minimal", "fsharp", "hack", "smart"];
const TOPIC_TOKENS = ["^", "%", "#"];
const TOPIC_TOKENS = ["^^", "@@", "^", "%", "#"];
const RECORD_AND_TUPLE_SYNTAX_TYPES = ["hash", "bar"];

export function validatePlugins(plugins: PluginList) {
Expand Down
4 changes: 3 additions & 1 deletion packages/babel-parser/src/plugins/flow/index.js
Expand Up @@ -2256,7 +2256,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}
// allow double nullable types in Flow: ??string
return this.finishOp(tt.question, 1);
} else if (isIteratorStart(code, next)) {
} else if (
isIteratorStart(code, next, this.input.charCodeAt(this.state.pos + 2))
) {
this.state.pos += 2; // eat "@@"
return this.readIterator();
} else {
Expand Down
40 changes: 38 additions & 2 deletions packages/babel-parser/src/tokenizer/index.js
Expand Up @@ -725,12 +725,49 @@ export default class Tokenizer extends ParserErrors {
// it can be merged with tt.assign.
this.finishOp(tt.xorAssign, 2);
}
// '^^'
else if (
next === charCodes.caret &&
// If the ^^ token is not enabled, we don't throw but parse two single ^s
// because it could be a ^ hack token followed by a ^ binary operator.
this.hasPlugin([
"pipelineOperator",
{ proposal: "hack", topicToken: "^^" },
])
) {
this.finishOp(tt.doubleCaret, 2);

// `^^^` is forbidden and must be separated by a space.
const lookaheadCh = this.input.codePointAt(this.state.pos);
if (lookaheadCh === charCodes.caret) {
throw this.unexpected();
}
}
// '^'
else {
this.finishOp(tt.bitwiseXOR, 1);
}
}

readToken_atSign(): void {
const next = this.input.charCodeAt(this.state.pos + 1);

// '@@'
if (
next === charCodes.atSign &&
this.hasPlugin([
"pipelineOperator",
{ proposal: "hack", topicToken: "@@" },
])
) {
this.finishOp(tt.doubleAt, 2);
}
// '@'
else {
this.finishOp(tt.at, 1);
}
}

readToken_plus_min(code: number): void {
// '+-'
const next = this.input.charCodeAt(this.state.pos + 1);
Expand Down Expand Up @@ -1020,8 +1057,7 @@ export default class Tokenizer extends ParserErrors {
return;

case charCodes.atSign:
++this.state.pos;
this.finishToken(tt.at);
this.readToken_atSign();
return;

case charCodes.numberSign:
Expand Down
11 changes: 9 additions & 2 deletions packages/babel-parser/src/tokenizer/types.js
Expand Up @@ -186,15 +186,22 @@ export const tt: { [name: string]: TokenType } = {
eq: createToken("=", { beforeExpr, isAssign }),
assign: createToken("_=", { beforeExpr, isAssign }),
slashAssign: createToken("_=", { beforeExpr, isAssign }),
// These are only needed to support % and ^ as a Hack-pipe topic token. When the
// proposal settles on a token, the others can be merged with tt.assign.
// These are only needed to support % and ^ as a Hack-pipe topic token.
// When the proposal settles on a token, the others can be merged with
// tt.assign.
xorAssign: createToken("_=", { beforeExpr, isAssign }),
moduloAssign: createToken("_=", { beforeExpr, isAssign }),
// end: isAssign

incDec: createToken("++/--", { prefix, postfix, startsExpr }),
bang: createToken("!", { beforeExpr, prefix, startsExpr }),
tilde: createToken("~", { beforeExpr, prefix, startsExpr }),

// More possible topic tokens.
// When the proposal settles on a token, at least one of these may be removed.
doubleCaret: createToken("^^", { startsExpr }),
doubleAt: createToken("@@", { startsExpr }),

// start: isBinop
pipeline: createBinop("|>", 0),
nullishCoalescing: createBinop("??", 1),
Expand Down
13 changes: 11 additions & 2 deletions packages/babel-parser/src/util/identifier.js
Expand Up @@ -3,6 +3,7 @@
// @flow

import * as charCodes from "charcodes";
import { isIdentifierStart } from "@babel/helper-validator-identifier";

export {
isIdentifierStart,
Expand All @@ -18,8 +19,16 @@ export const keywordRelationalOperator = /^in(stanceof)?$/;

// Test whether a current state character code and next character code is @

export function isIteratorStart(current: number, next: number): boolean {
return current === charCodes.atSign && next === charCodes.atSign;
export function isIteratorStart(
current: number,
next: number,
next2: number,
): boolean {
return (
current === charCodes.atSign &&
next === charCodes.atSign &&
isIdentifierStart(next2)
);
}

// This is the comprehensive set of JavaScript reserved words
Expand Down
@@ -0,0 +1 @@
value |> @@ + 1
@@ -0,0 +1,3 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "@@" }]]
}
@@ -0,0 +1,45 @@
{
"type": "File",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"program": {
"type": "Program",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"expression": {
"type": "BinaryExpression",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"left": {
"type": "Identifier",
"start":0,"end":5,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":5},"identifierName":"value"},
"name": "value"
},
"operator": "|>",
"right": {
"type": "BinaryExpression",
"start":9,"end":15,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":15}},
"left": {
"type": "TopicReference",
"start":9,"end":11,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":11}}
},
"operator": "+",
"right": {
"type": "NumericLiteral",
"start":14,"end":15,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":15}},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
}
}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
value |> 1 + @@
@@ -0,0 +1,3 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "@@" }]]
}
@@ -0,0 +1,45 @@
{
"type": "File",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"program": {
"type": "Program",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"expression": {
"type": "BinaryExpression",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"left": {
"type": "Identifier",
"start":0,"end":5,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":5},"identifierName":"value"},
"name": "value"
},
"operator": "|>",
"right": {
"type": "BinaryExpression",
"start":9,"end":15,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":15}},
"left": {
"type": "NumericLiteral",
"start":9,"end":10,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":10}},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
},
"operator": "+",
"right": {
"type": "TopicReference",
"start":13,"end":15,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":15}}
}
}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
value |> a + b
@@ -0,0 +1,3 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "@@" }]]
}

0 comments on commit df27d54

Please sign in to comment.