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

Improve pipeline-operator typings #14629

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,58 +1,73 @@
import { types as t } from "@babel/core";
import type { NodePath } from "@babel/traverse";

// tries to optimize sequence expressions in the format
// (a = b, (c => c + e)(a))
// to
// (a = b, a + e)
const buildOptimizedSequenceExpression = ({ call, path, placeholder }) => {

type Options = {
call: t.CallExpression | t.AwaitExpression;
path: NodePath<t.BinaryExpression & { operator: "|>" }>;
placeholder: t.Identifier;
};

function isConciseArrowExpression(
node: t.Node,
): node is t.ArrowFunctionExpression & { body: t.Expression } {
return (
t.isArrowFunctionExpression(node) &&
t.isExpression(node.body) &&
!node.async
);
}

const buildOptimizedSequenceExpression = ({
call,
path,
placeholder,
}: Options) => {
// @ts-expect-error AwaitExpression does not have callee property
const { callee: calledExpression } = call;
const pipelineLeft = path.node.left;
// pipelineLeft must not be a PrivateName
const pipelineLeft = path.node.left as t.Expression;
const assign = t.assignmentExpression(
"=",
t.cloneNode(placeholder),
pipelineLeft,
);

let optimizeArrow =
t.isArrowFunctionExpression(calledExpression) &&
t.isExpression(calledExpression.body) &&
!calledExpression.async &&
!calledExpression.generator;
let param;
const expressionIsArrow = isConciseArrowExpression(calledExpression);

if (optimizeArrow) {
if (expressionIsArrow) {
let param;
let optimizeArrow = true;
const { params } = calledExpression;
if (params.length === 1 && t.isIdentifier(params[0])) {
param = params[0];
} else if (params.length > 0) {
optimizeArrow = false;
}
if (optimizeArrow && !param) {
// fixme: arrow function with 1 pattern argument will also go into this branch
// Arrow function with 0 arguments
return t.sequenceExpression([pipelineLeft, calledExpression.body]);
} else if (param) {
path.scope.push({ id: t.cloneNode(placeholder) });
path.get("right").scope.rename(param.name, placeholder.name);

return t.sequenceExpression([assign, calledExpression.body]);
}
} else if (t.isIdentifier(calledExpression, { name: "eval" })) {
const evalSequence = t.sequenceExpression([
t.numericLiteral(0),
calledExpression,
]);

call.callee = evalSequence;

path.scope.push({ id: t.cloneNode(placeholder) });

return t.sequenceExpression([assign, call]);
}

if (optimizeArrow && !param) {
// Arrow function with 0 arguments
return t.sequenceExpression([pipelineLeft, calledExpression.body]);
(call as t.CallExpression).callee = evalSequence;
}

path.scope.push({ id: t.cloneNode(placeholder) });

if (param) {
path.get("right").scope.rename(param.name, placeholder.name);

return t.sequenceExpression([assign, calledExpression.body]);
}

return t.sequenceExpression([assign, call]);
};

Expand Down
@@ -1,7 +1,8 @@
import { types as t } from "@babel/core";
import { types as t, type PluginObject } from "@babel/core";
import type { NodePath } from "@babel/traverse";
import buildOptimizedSequenceExpression from "./buildOptimizedSequenceExpression";

const fsharpVisitor = {
const fsharpVisitor: PluginObject["visitor"] = {
BinaryExpression(path) {
const { scope, node } = path;
const { operator, left, right } = node;
Expand All @@ -16,7 +17,7 @@ const fsharpVisitor = {
const sequence = buildOptimizedSequenceExpression({
placeholder,
call,
path,
path: path as NodePath<t.BinaryExpression & { operator: "|>" }>,
});
path.replaceWith(sequence);
},
Expand Down
Expand Up @@ -33,7 +33,7 @@ const topicReferenceVisitor: Visitor<State> = {
// with sequence expressions containing assignment expressions
// with automatically generated variables,
// from inside to outside, from left to right.
export default {
const visitor: Visitor<PluginPass> = {
BinaryExpression: {
exit(path) {
const { scope, node } = path;
Expand All @@ -52,7 +52,7 @@ export default {
return;
}

const visitorState = {
const visitorState: State = {
topicReferences: [],
// pipeBodyPath might be a function, and it won't be visited by
// topicReferenceVisitor because traverse() skips the top-level
Expand Down Expand Up @@ -93,4 +93,6 @@ export default {
);
},
},
} as Visitor<PluginPass>;
};

export default visitor;
@@ -1,7 +1,8 @@
import { types as t } from "@babel/core";
import { types as t, type PluginPass } from "@babel/core";
import type { NodePath, Visitor } from "@babel/traverse";
import buildOptimizedSequenceExpression from "./buildOptimizedSequenceExpression";

const minimalVisitor = {
const minimalVisitor: Visitor<PluginPass> = {
BinaryExpression(path) {
const { scope, node } = path;
const { operator, left, right } = node;
Expand All @@ -14,7 +15,7 @@ const minimalVisitor = {
buildOptimizedSequenceExpression({
placeholder,
call,
path,
path: path as NodePath<t.BinaryExpression & { operator: "|>" }>,
}),
);
},
Expand Down