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 acorn-walk types, add ".d.ts tests" using tsd #1344

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module.exports = {
}
}
],
ignorePatterns: ["*.ts"],
plugins: ["eslint-plugin-import"],
rules: {
"no-unreachable-loop": "off",
Expand Down
69 changes: 40 additions & 29 deletions acorn-walk/src/walk.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
import * as acorn from "acorn"

export type FullWalkerCallback<TState> = (
node: acorn.Node,
state: TState,
type: string
) => void

export type FullAncestorWalkerCallback<TState> = (
node: acorn.Node,
state: TState,
ancestors: acorn.Node[],
type: string
) => void

type AggregateType = {
Expression: acorn.Expression,
Statement: acorn.Statement,
Expand All @@ -22,31 +9,55 @@ type AggregateType = {
ForInit: acorn.VariableDeclaration | acorn.Expression
}

type NodeTypeName = acorn.AnyNode["type"]
type AggregateTypeName = keyof AggregateType
type NodeOrAggregateTypeName = NodeTypeName | AggregateTypeName

export type FullWalkerCallback<TState> = (
node: acorn.AnyNode,
state: TState,
type: NodeOrAggregateTypeName,
) => void

export type FullAncestorWalkerCallback<TState> = (
node: acorn.AnyNode,
state: TState,
ancestors: acorn.AnyNode[],
type: NodeOrAggregateTypeName,
) => void

type ActualNodeForType<Name extends NodeOrAggregateTypeName> = Name extends AggregateTypeName
? AggregateType[Name]
: Extract<acorn.AnyNode, { type: Name }>

type SimpleVisitorFunction<Name extends NodeOrAggregateTypeName, TState> =
(node: ActualNodeForType<Name>, state: TState) => void

type AncestorVisitorFunction<Name extends NodeOrAggregateTypeName, TState> =
(node: ActualNodeForType<Name>, state: TState, ancestors: acorn.AnyNode[]) => void

export type WalkerCallback<Name extends NodeOrAggregateTypeName, TState> =
(node: ActualNodeForType<Name>, state: TState) => void

type RecursiveVisitorFunction<Name extends NodeOrAggregateTypeName, TState> =
(node: ActualNodeForType<Name>, state: TState, callback: WalkerCallback<Name, TState>) => void

export type SimpleVisitors<TState> = {
[type in acorn.AnyNode["type"]]?: (node: Extract<acorn.AnyNode, { type: type }>, state: TState) => void
} & {
[type in keyof AggregateType]?: (node: AggregateType[type], state: TState) => void
[typeName in NodeOrAggregateTypeName]?: SimpleVisitorFunction<typeName, TState>
}

export type AncestorVisitors<TState> = {
[type in acorn.AnyNode["type"]]?: ( node: Extract<acorn.AnyNode, { type: type }>, state: TState, ancestors: acorn.Node[]
) => void
} & {
[type in keyof AggregateType]?: (node: AggregateType[type], state: TState, ancestors: acorn.Node[]) => void
[typeName in NodeOrAggregateTypeName]?: AncestorVisitorFunction<typeName, TState>
}

export type WalkerCallback<TState> = (node: acorn.Node, state: TState) => void

export type RecursiveVisitors<TState> = {
[type in acorn.AnyNode["type"]]?: ( node: Extract<acorn.AnyNode, { type: type }>, state: TState, callback: WalkerCallback<TState>) => void
} & {
[type in keyof AggregateType]?: (node: AggregateType[type], state: TState, callback: WalkerCallback<TState>) => void
[typeName in NodeOrAggregateTypeName]?: RecursiveVisitorFunction<typeName, TState>
}

export type FindPredicate = (type: string, node: acorn.Node) => boolean
export type FindPredicate = (type: NodeOrAggregateTypeName, node: acorn.AnyNode) => boolean

export interface Found<TState> {
node: acorn.Node,
node: acorn.AnyNode,
state: TState
}

Expand Down Expand Up @@ -123,7 +134,7 @@ export function findNodeAt<TState>(
node: acorn.Node,
start: number | undefined | null,
end?: number | undefined | null,
type?: FindPredicate | string,
type?: FindPredicate | NodeOrAggregateTypeName,
base?: RecursiveVisitors<TState>,
state?: TState
): Found<TState> | undefined
Expand All @@ -134,7 +145,7 @@ export function findNodeAt<TState>(
export function findNodeAround<TState>(
node: acorn.Node,
start: number | undefined | null,
type?: FindPredicate | string,
type?: FindPredicate | NodeOrAggregateTypeName,
base?: RecursiveVisitors<TState>,
state?: TState
): Found<TState> | undefined
Expand Down
202 changes: 202 additions & 0 deletions acorn-walk/test-d/walk.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import {
expectAssignable,
expectError,
expectNotAssignable,
expectType
} from "tsd"

import * as acorn from "acorn"
import * as walk from ".."

type simpleParams = Parameters<typeof walk.simple>
type ancestorParams = Parameters<typeof walk.ancestor>
type recursiveParams = Parameters<typeof walk.recursive>
type makeParams = Parameters<typeof walk.make>
type fullParams = Parameters<typeof walk.full>
type fullAncestorParams = Parameters<typeof walk.fullAncestor>
type findNodeAtParams = Parameters<typeof walk.findNodeAt>
type findNodeAroundParams = Parameters<typeof walk.findNodeAround>
type findNodeAfterParams = Parameters<typeof walk.findNodeAfter>
type findNodeBeforeParams = Parameters<typeof walk.findNodeBefore>

const ast = acorn.parse("", {ecmaVersion: "latest"})

const v = () => {}

/*
Functions who accept visitor objects or "walker objects" should allow both Node
type names and Aggregate type names in the object.
*/
expectAssignable<simpleParams>([ast,
{Super: v, Class: v},
{Super: v, Class: v}
])

expectAssignable<ancestorParams>([ast,
{Super: v, Class: v},
{Super: v, Class: v}
])

expectAssignable<recursiveParams>([ast, null,
{Super: v, Class: v},
{Super: v, Class: v}
])

expectAssignable<makeParams>([
{Super: v, Class: v},
{Super: v, Class: v}
])

expectAssignable<fullParams>([ast, v,
{Super: v, Class: v}
])

expectAssignable<fullAncestorParams>([ast, v,
{Super: v, Class: v}
])

expectAssignable<findNodeAtParams>([ast, null, null, "Super",
{Super: v, Class: v}
])

expectAssignable<findNodeAroundParams>([ast, null, "Super",
{Super: v, Class: v}
])

expectAssignable<findNodeAfterParams>([ast, null, "Super",
{Super: v, Class: v}
])

expectAssignable<findNodeBeforeParams>([ast, null, "Super",
{Super: v, Class: v}
])

/*
Functions who accept visitor objects or "walker objects" should not allow
additional properties who are not valid Node/Aggregate type names
*/
expectNotAssignable<simpleParams>([ast, {NotANode: v}])
expectNotAssignable<simpleParams>([ast, {}, {NotANode: v}])

expectNotAssignable<ancestorParams>([ast, {NotANode: v}])
expectNotAssignable<ancestorParams>([ast, {}, {NotANode: v}])

expectNotAssignable<recursiveParams>([ast, null, {NotANode: v}])
expectNotAssignable<recursiveParams>([ast, null, {}, {NotANode: v}])

expectNotAssignable<makeParams>([{NotANode: v}])
expectNotAssignable<makeParams>([{}, {NotANode: v}])

expectNotAssignable<fullParams>([ast, v, {NotANode: v}])

expectNotAssignable<fullAncestorParams>([ast, v, {NotANode: v}])

expectNotAssignable<findNodeAtParams>([ast, null, null, "Super",
{NotANode: v}
])

expectNotAssignable<findNodeAroundParams>([ast, null, "Super",
{NotANode: v}
])

expectNotAssignable<findNodeAfterParams>([ast, null, "Super",
{NotANode: v}
])

expectNotAssignable<findNodeBeforeParams>([ast, null, "Super",
{NotANode: v}
])

/*
When we supply a visitor function to a walker function, and the walker function
calls us with a given Node, the Node should be of the type we specified as the
visitor function name.

If our visitor function is for an Aggregate, the Node we receive should be a
member of a discriminated union, who allows us to determine the actual type
of the Node.
*/
walk.simple(ast, {
Super: (node) => { expectType<acorn.Super>(node) },
Pattern: (node) => {
expectType<acorn.Pattern>(node)
if (node.type === "Identifier") { expectType<acorn.Identifier>(node) }
}
})

walk.ancestor(ast, {
Super: (node) => { expectType<acorn.Super>(node) },
Pattern: (node) => {
expectType<acorn.Pattern>(node)
if (node.type === "Identifier") { expectType<acorn.Identifier>(node) }
}
})

walk.recursive(ast, null, {
Super: (node) => { expectType<acorn.Super>(node) },
Pattern: (node) => {
expectType<acorn.Pattern>(node)
if (node.type === "Identifier") { expectType<acorn.Identifier>(node) }
}
})

/*
Whenever we receive a Node as a parameter to a callback, it should be a member
of a discriminated union, who allows us to determine the actual type of the Node.
*/
walk.ancestor(ast, {
Super: (_node, _state, ancestors) => {
if (ancestors[0].type === "Super") { expectType<acorn.Super>(ancestors[0]) }
},
})

walk.full(ast, (node) => {
if (node.type === "Super") { expectType<acorn.Super>(node) }
})

walk.fullAncestor(ast, (node, _, ancestors) => {
if (node.type === "Super") { expectType<acorn.Super>(node) }
if (ancestors[0].type === "Super") { expectType<acorn.Super>(ancestors[0]) }
})

walk.findNodeAt(ast, null, null, (_, node) => {
if (node.type === "Super") { expectType<acorn.Super>(node) }
return true;
})

/*
Whenever we receive a type name as a parameter to a callback, it should be a
valid Node/Aggregate type name
*/
walk.full(ast, (_node, _state, type) => {
expectAssignable<typeof type>("Super")
expectAssignable<typeof type>("Pattern")
expectNotAssignable<typeof type>("NotANode")
})

walk.fullAncestor(ast, (_node, _state, _ancestors, type) => {
expectAssignable<typeof type>("Super")
expectAssignable<typeof type>("Pattern")
expectNotAssignable<typeof type>("NotANode")
})

walk.findNodeAt(ast, null, null, (type) => {
expectAssignable<typeof type>("Super")
expectAssignable<typeof type>("Pattern")
expectNotAssignable<typeof type>("NotANode")
return true;
})

/*
findNodeAt should reject an input string "type" who is not a valid
Node/Aggregate type name
*/
expectError(walk.findNodeAt(ast, null, null, "NotANode"))
expectError(walk.findNodeAround(ast, null, "NotANode"))

/*
findNodeAt should return a Node who's a member of a discriminated union, who
allows us to determine the actual type of the Node.
*/
const found = walk.findNodeAt(ast, null, null, () => true)
if (found?.node.type === "Literal") { expectType<acorn.Literal>(found?.node) }
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"eslint-plugin-n": "^16.6.2",
"rollup": "^3.29.4",
"test262": "git+https://github.com/tc39/test262.git#867ca540d6cc991a82b41a3b7f9ccb9e93efe803",
"test262-parser-runner": "^0.5.0"
"test262-parser-runner": "^0.5.0",
"tsd": "^0.31.2"
}
}