Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[smart pipes] Add support for question-mark tokens #9209

Closed
wants to merge 8 commits into from
  •  
  •  
  •  
108 changes: 83 additions & 25 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -983,29 +983,11 @@ export default class ExpressionParser extends LValParser {
}

case tt.hash: {
if (this.state.inPipeline) {
node = this.startNode();

if (
this.getPluginOption("pipelineOperator", "proposal") !== "smart"
) {
this.raise(
node.start,
"Primary Topic Reference found but pipelineOperator not passed 'smart' for 'proposal' option.",
);
}
return this.parsePrimaryTopicReference("#");
}

this.next();
if (this.primaryTopicReferenceIsAllowedInCurrentTopicContext()) {
this.registerTopicReference();
return this.finishNode(node, "PipelinePrimaryTopicReference");
} else {
throw this.raise(
node.start,
`Topic reference was used in a lexical context without topic binding`,
);
}
}
case tt.question: {
return this.parsePrimaryTopicReference("?");
}

default:
Expand Down Expand Up @@ -2110,6 +2092,82 @@ export default class ExpressionParser extends LValParser {
return this.finishNode(node, "YieldExpression");
}

// Parses a smart-pipeline primary topic reference.
// The pipelineOperator plugin must be active, and its proposal option must be "smart".
// The topicToken argument is "#" or "?" and is matched against the topicToken option
// given to the pipelineOperator plugin's topicToken option (which is "#" by default).

parsePrimaryTopicReference(
topicToken: N.PipelineProposedTopicToken,
): N.PipelinePrimaryTopicReference {
if (this.state.inPipeline) {
const node = this.startNode();

if (this.getPluginOption("pipelineOperator", "proposal") !== "smart") {
this.raise(
node.start,
"Topic reference found but pipelineOperator plugin was not passed 'smart' " +
"for 'proposal' option.",
);
}

this.validateTopicToken(topicToken);
this.next();

if (this.primaryTopicReferenceIsAllowedInCurrentTopicContext()) {
this.registerTopicReference();
return this.finishNode(node, "PipelinePrimaryTopicReference");
} else {
throw this.raise(
node.start,
`Topic reference was used in a lexical context without topic binding`,
);
}
} else {
throw this.unexpected();
}
}

// Asserts that the given topic token is valid under the pipelineOperator plugin's
// topicToken configuration option.

validateTopicToken(topicToken: N.PipelineProposedTopicToken): void {
if (!this.matchesTopicTokenConfiguration(topicToken)) {
const node = this.startNode();
const topicTokenOption = this.getPipelineOperatorPluginTopicTokenOption();
const message = topicTokenOption
? `Pipeline was used with ${topicToken} topic token, but ` +
`pipelineOperator plugin was passed a "topicToken": ` +
`"${topicTokenOption}" option`
: `Pipeline was used with ${topicToken} topic token, but ` +
`pipelineOperator plugin was not passed a "topicToken" option, which ` +
`is "#" by default`;
this.raise(node.start, message);
}
}

// Tests whether a topic-token string is the same as the pipelineOperator plugin's
// topicToken configuration option.

matchesTopicTokenConfiguration(
topicToken: N.PipelineProposedTopicToken,
): boolean {
const defaultTopicToken = "#";
const pluginConfigurationTopicToken = this.getPluginOption(
"pipelineOperator",
"topicToken",
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a parameter to the getPluginOption function for the default value? Then it would be enough to do

this.getPluginOption("pipelineOperator", "topicToken", "#") !== topicToken

and it wouldn't be necessary to use this separate function (that check could directly go in the if statement).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood; I’ll replace this.

const effectivePluginConfigurationTopicToken =
pluginConfigurationTopicToken || defaultTopicToken;
return effectivePluginConfigurationTopicToken === topicToken;
}

// Returns the value of the pipelineOperator plugin's topicToken configuration option.

getPipelineOperatorPluginTopicTokenOption(): ?string {
return this.getPluginOption("pipelineOperator", "topicToken");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that having functions like this one is overkill: it has a name so long that it doesn't gain anything in readability neither in typing. Also, the name is basically the same as getPluginOption("pipelineOperator", "topicToken") but with less punctation, it doesn't abstract anything more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood; I’ll inline this.

}

// Validates a pipeline (for any of the pipeline Babylon plugins) at the point
// of the infix operator `|>`.

Expand Down Expand Up @@ -2191,7 +2249,7 @@ export default class ExpressionParser extends LValParser {
if (!this.topicReferenceWasUsedInCurrentTopicContext()) {
throw this.raise(
startPos,
`Pipeline is in topic style but does not use topic reference`,
`Pipeline is in topic style but does not use any topic reference`,
);
}
bodyNode.expression = childExpression;
Expand Down Expand Up @@ -2269,8 +2327,8 @@ export default class ExpressionParser extends LValParser {
}
}

// Register the use of a primary topic reference (`#`) within the current
// topic context.
// For the smart-pipelines plugin.
// Register the use of a primary topic reference within the current topic context.
registerTopicReference(): void {
this.state.topicContext.maxTopicIndex = 0;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-parser/src/tokenizer/index.js
Expand Up @@ -375,7 +375,7 @@ export default class Tokenizer extends LocationParser {
//
// All in the name of speed.

// number sign is "#"
// Number sign is "#", aka the hash sign.
readToken_numberSign(): void {
if (this.state.pos === 0 && this.readToken_interpreter()) {
return;
Expand Down
6 changes: 6 additions & 0 deletions packages/babel-parser/src/types.js
Expand Up @@ -598,6 +598,12 @@ export type PipelineStyle =
| "PipelineBareAwaitedFunction"
| "PipelineTopicExpression";

export type PipelinePrimaryTopicReference = NodeBase & {
type: "PipelinePrimaryTopicReference",
};

export type PipelineProposedTopicToken = "#" | "?";

// Template Literals

export type TemplateLiteral = NodeBase & {
Expand Down
@@ -0,0 +1 @@
x |> ?;
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "smart" }]],
"throws": "Pipeline was used with ? topic token, but pipelineOperator plugin was not passed a \"topicToken\" option, which is \"#\" by default (1:5)"
}
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "smart" }]],
"throws": "Pipeline is in topic style but does not use any topic reference (1:5)"
}
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "smart" }]],
"throws": "Pipeline is in topic style but does not use any topic reference (1:16)"
}
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "smart" }]],
"throws": "Pipeline is in topic style but does not use any topic reference (1:5)"
}
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "smart" }]],
"throws": "Pipeline is in topic style but does not use any topic reference (1:11)"
}
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "smart" }]],
"throws": "Pipeline is in topic style but does not use any topic reference (1:5)"
}
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "smart" }]],
"throws": "Pipeline is in topic style but does not use any topic reference (1:9)"
}
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "smart" }]],
"throws": "Pipeline is in topic style but does not use any topic reference (1:9)"
}
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "smart" }]],
"throws": "Pipeline is in topic style but does not use any topic reference (1:9)"
}
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "smart" }]],
"throws": "Pipeline is in topic style but does not use any topic reference (1:9)"
}
@@ -0,0 +1,4 @@
let result = "hello"
|> doubleSay
|> text.capitalize
|> a.b.exclaim;
@@ -0,0 +1,3 @@
{
"plugins": [["pipelineOperator", { "proposal": "smart", "topicToken": "?" }]]
}