From e2249400e5fe63cf01be40f5bc2752aa96e55ba4 Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Mon, 24 Jul 2023 22:16:49 +1200 Subject: [PATCH 1/3] feat: add flow node type guards --- src/flowNodes/index.ts | 1 + src/flowNodes/typeGuards/index.ts | 204 ++++++++++++++++++++++++++++++ src/index.ts | 1 + 3 files changed, 206 insertions(+) create mode 100644 src/flowNodes/index.ts create mode 100644 src/flowNodes/typeGuards/index.ts diff --git a/src/flowNodes/index.ts b/src/flowNodes/index.ts new file mode 100644 index 00000000..c1122eb9 --- /dev/null +++ b/src/flowNodes/index.ts @@ -0,0 +1 @@ +export * from "./typeGuards"; diff --git a/src/flowNodes/typeGuards/index.ts b/src/flowNodes/typeGuards/index.ts new file mode 100644 index 00000000..c756a023 --- /dev/null +++ b/src/flowNodes/typeGuards/index.ts @@ -0,0 +1,204 @@ +import ts from "typescript"; + +/** + * Test if a flow node is a `FlowStart`. + * + * @category Flow Nodes - Type Guards + * @example + * ```ts + * declare const flowNode: ts.FlowNode; + * + * if (isFlowStart(flowNode)) { + * // ... + * } + * ``` + * + * @returns Whether the given node appears to be a `FlowStart`. + */ +export function isFlowStart(flowNode: ts.FlowNode): flowNode is ts.FlowStart { + return ( + "node" in flowNode && + (flowNode.node === undefined || + ts.isFunctionExpression(flowNode.node) || + ts.isArrowFunction(flowNode.node) || + ts.isMethodDeclaration(flowNode.node) || + ts.isGetAccessorDeclaration(flowNode.node) || + ts.isSetAccessorDeclaration(flowNode.node)) + ); +} + +/** + * Test if a flow node is a `FlowLabel`. + * + * @category Flow Nodes - Type Guards + * @example + * ```ts + * declare const flowNode: ts.FlowNode; + * + * if (isFlowLabel(flowNode)) { + * // ... + * } + * ``` + * + * @returns Whether the given node appears to be a `FlowLabel`. + */ +export function isFlowLabel(flowNode: ts.FlowNode): flowNode is ts.FlowLabel { + return "antecedents" in flowNode && !("antecedent" in flowNode); +} + +/** + * Test if a flow node is a `FlowAssignment`. + * + * @category Flow Nodes - Type Guards + * @example + * ```ts + * declare const flowNode: ts.FlowNode; + * + * if (isFlowAssignment(flowNode)) { + * // ... + * } + * ``` + * + * @returns Whether the given node appears to be a `FlowAssignment`. + */ +export function isFlowAssignment( + flowNode: ts.FlowNode +): flowNode is ts.FlowAssignment { + return ( + "node" in flowNode && + "antecedent" in flowNode && + !("antecedents" in flowNode) && + (ts.isExpression(flowNode.node) || + ts.isVariableDeclaration(flowNode.node) || + ts.isBindingElement(flowNode.node)) + ); +} + +/** + * Test if a flow node is a `FlowCall`. + * + * @category Flow Nodes - Type Guards + * @example + * ```ts + * declare const flowNode: ts.FlowNode; + * + * if (isFlowCall(flowNode)) { + * // ... + * } + * ``` + * + * @returns Whether the given node appears to be a `FlowCall`. + */ +export function isFlowCall(flowNode: ts.FlowNode): flowNode is ts.FlowCall { + return ( + "node" in flowNode && + "antecedent" in flowNode && + !("antecedents" in flowNode) && + ts.isCallExpression(flowNode.node) + ); +} + +/** + * Test if a flow node is a `FlowCondition`. + * + * @category Flow Nodes - Type Guards + * @example + * ```ts + * declare const flowNode: ts.FlowNode; + * + * if (isFlowCondition(flowNode)) { + * // ... + * } + * ``` + * + * @returns Whether the given node appears to be a `FlowCondition`. + */ +export function isFlowCondition( + flowNode: ts.FlowNode +): flowNode is ts.FlowCondition { + return ( + "node" in flowNode && + "antecedent" in flowNode && + !("antecedents" in flowNode) && + ts.isExpression(flowNode.node) + ); +} + +/** + * Test if a flow node is a `FlowSwitchClause`. + * + * @category Flow Nodes - Type Guards + * @example + * ```ts + * declare const flowNode: ts.FlowNode; + * + * if (isFlowSwitchClause(flowNode)) { + * // ... + * } + * ``` + * + * @returns Whether the given node appears to be a `FlowSwitchClause`. + */ +export function isFlowSwitchClause( + flowNode: ts.FlowNode +): flowNode is ts.FlowSwitchClause { + return ( + "switchStatement" in flowNode && + "clauseStart" in flowNode && + "clauseEnd" in flowNode && + "antecedent" in flowNode && + !("antecedents" in flowNode) && + ts.isSwitchStatement(flowNode.switchStatement) + ); +} + +/** + * Test if a flow node is a `FlowArrayMutation`. + * + * @category Flow Nodes - Type Guards + * @example + * ```ts + * declare const flowNode: ts.FlowNode; + * + * if (isFlowArrayMutation(flowNode)) { + * // ... + * } + * ``` + * + * @returns Whether the given node appears to be a `FlowArrayMutation`. + */ +export function isFlowArrayMutation( + flowNode: ts.FlowNode +): flowNode is ts.FlowArrayMutation { + return ( + "node" in flowNode && + "antecedent" in flowNode && + !("antecedents" in flowNode) && + (ts.isCallExpression(flowNode.node) || ts.isBinaryExpression(flowNode.node)) + ); +} + +/** + * Test if a flow node is a `FlowReduceLabel`. + * + * @category Flow Nodes - Type Guards + * @example + * ```ts + * declare const flowNode: ts.FlowNode; + * + * if (isFlowReduceLabel(flowNode)) { + * // ... + * } + * ``` + * + * @returns Whether the given node appears to be a `FlowReduceLabel`. + */ +export function isFlowReduceLabel( + flowNode: ts.FlowNode +): flowNode is ts.FlowReduceLabel { + return ( + "target" in flowNode && + "antecedents" in flowNode && + "antecedent" in flowNode + ); +} diff --git a/src/index.ts b/src/index.ts index 8e2644c9..e5dc3f0b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export * from "./comments"; export * from "./compilerOptions"; export * from "./flags"; +export * from "./flowNodes"; export * from "./modifiers"; export * from "./nodes"; export * from "./scopes"; From 667945459104889b5c1c0ea2215996b543bab7cd Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Mon, 24 Jul 2023 22:22:46 +1200 Subject: [PATCH 2/3] feat: add hasFlowNode type guard --- src/nodes/typeGuards/single.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/nodes/typeGuards/single.ts b/src/nodes/typeGuards/single.ts index 0002fedf..ebf6e88e 100644 --- a/src/nodes/typeGuards/single.ts +++ b/src/nodes/typeGuards/single.ts @@ -1187,3 +1187,24 @@ export function isUnparsedSyntheticReference( export function isVoidKeyword(node: ts.Node): node is VoidKeyword { return node.kind === ts.SyntaxKind.VoidKeyword; } + +/** + * Test if a node has a `flowNode` property. + * + * @category Nodes - Type Guards + * @example + * ```ts + * declare const node: ts.Node; + * + * if (hasFlowNode(node)) { + * // ... + * } + * ``` + * + * @returns Whether the given node has a `flowNode` property. + */ +export function hasFlowNode( + node: ts.Node +): node is ts.Node & { flowNode: ts.FlowNode } { + return "flowNode" in node; +} From ab1cade355f40136dba24d0d7bc508d1c653b183 Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Mon, 24 Jul 2023 22:48:55 +1200 Subject: [PATCH 3/3] feat: add `isFlowFlagSet` --- src/flags.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/flags.ts b/src/flags.ts index e2db1df6..aea3e996 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -21,6 +21,22 @@ function isFlagSetOnObject(obj: { flags: number }, flag: number): boolean { return isFlagSet(obj.flags, flag); } +/** + * Test if the given node has the given `FlowFlags` set. + * + * @category Flow Nodes - Flag Utilities + * @example + * ```ts + * declare const flowNode: ts.FlowNode; + * + * if (isFlowFlagSet(flowNode, ts.FlowFlags.Referenced)) { + * // ... + * } + * ``` + */ +export const isFlowFlagSet: (node: ts.Node, flag: ts.FlowFlags) => boolean = + isFlagSetOnObject; + /** * Test if the given node has the given `ModifierFlags` set. *