Skip to content

Commit fedb9c8

Browse files
committed
perf(lexer): improve efficiency
Signed-off-by: Lexus Drumgold <[email protected]>
1 parent b64cb0d commit fedb9c8

21 files changed

+952
-540
lines changed

.dprint.jsonc

+6-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"!**/typings/**/dist/",
77
"**/*.patch",
88
"**/*.snap",
9+
"**/*.ts.txt",
910
"**/*config.*.timestamp*",
1011
"**/.temp/",
1112
"**/.vercel/",
@@ -81,9 +82,9 @@
8182
},
8283
"newLineKind": "lf",
8384
"plugins": [
84-
"https://plugins.dprint.dev/typescript-0.88.10.wasm",
85-
"https://plugins.dprint.dev/json-0.19.1.wasm",
86-
"https://plugins.dprint.dev/markdown-0.16.3.wasm",
85+
"https://plugins.dprint.dev/typescript-0.90.5.wasm",
86+
"https://plugins.dprint.dev/json-0.19.2.wasm",
87+
"https://plugins.dprint.dev/markdown-0.17.0.wasm",
8788
"https://plugins.dprint.dev/exec-0.4.4.json@c207bf9b9a4ee1f0ecb75c594f774924baf62e8e53a2ce9d873816a408cecbf7"
8889
],
8990
"typescript": {
@@ -98,7 +99,7 @@
9899
"constructorType.spaceAfterNewKeyword": true,
99100
"doWhileStatement.spaceAfterWhileKeyword": true,
100101
"enumDeclaration.memberSpacing": "maintain",
101-
"exportDeclaration.forceMultiLine": false,
102+
"exportDeclaration.forceMultiLine": "never",
102103
"exportDeclaration.forceSingleLine": false,
103104
"exportDeclaration.sortNamedExports": "maintain",
104105
"exportDeclaration.spaceSurroundingNamedExports": true,
@@ -113,7 +114,7 @@
113114
"ifStatement.spaceAfterIfKeyword": true,
114115
"ignoreFileCommentText": "dprint-ignore-file",
115116
"ignoreNodeCommentText": "dprint-ignore",
116-
"importDeclaration.forceMultiLine": false,
117+
"importDeclaration.forceMultiLine": "never",
117118
"importDeclaration.forceSingleLine": false,
118119
"importDeclaration.sortNamedImports": "maintain",
119120
"importDeclaration.spaceSurroundingNamedImports": true,

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
**/node_modules/
1717
**/tsconfig*temp.json
1818
Brewfile
19+
__fixtures__/visit.ts.txt
1920
yarn.lock
2021

2122
# NEGATED PATTERNS

.github/infrastructure.yml

-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ branches:
4141
- context: test (18)
4242
- context: test (19)
4343
- context: test (20)
44-
- context: typescript (5.3.3)
4544
- context: typescript (5.4.3)
4645
- context: typescript (latest)
4746
strict: true

.github/workflows/ci.yml

-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ jobs:
245245
matrix:
246246
typescript-version:
247247
- ${{ needs.preflight.outputs.version-typescript }}
248-
- 5.3.3
249248
- latest
250249
steps:
251250
- id: checkout

.vscode/settings.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@
111111
"emmet.triggerExpansionOnTab": true,
112112
"eslint.enable": true,
113113
"eslint.lintTask.enable": true,
114-
"eslint.nodePath": "node_modules/.bin/eslint",
115114
"eslint.options": {
116115
"extensions": [
117116
"css",
@@ -137,6 +136,7 @@
137136
"overrideConfigFile": ".eslintrc.cjs"
138137
},
139138
"eslint.runtime": "node",
139+
"eslint.trace.server": "messages",
140140
"eslint.validate": [
141141
"css",
142142
"github-actions-workflow",
@@ -154,6 +154,7 @@
154154
"files.associations": {
155155
"*.log": "log",
156156
"*.snap": "jest-snapshot",
157+
"*.ts.txt": "assemblyscript",
157158
".env.zsh": "shellscript",
158159
".markdownlintignore": "ignore",
159160
".npmrc": "ini",
@@ -287,6 +288,11 @@
287288
"format": "svg",
288289
"icon": "testts"
289290
},
291+
{
292+
"extensions": ["ts.txt"],
293+
"format": "svg",
294+
"icon": "typescript"
295+
},
290296
{
291297
"extensions": ["yarnrc.yml"],
292298
"format": "svg",

__fixtures__/visit.ts.txt

+299
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
/**
2+
* @file visit
3+
* @module unist-util-visit/visit
4+
*/
5+
6+
import {
7+
define,
8+
isArray,
9+
isBoolean,
10+
isFalsy,
11+
isFunction,
12+
isNIL,
13+
isNumber,
14+
sift,
15+
type EmptyArray,
16+
type Fn,
17+
type Nullable,
18+
type Optional
19+
} from '@flex-development/tutils'
20+
import type { Index, Test } from '@flex-development/unist-util-types'
21+
import color from '@flex-development/unist-util-visit/color'
22+
import type { Node, Parent } from 'unist'
23+
import { convert, type Check } from 'unist-util-is'
24+
import { CONTINUE, EXIT, SKIP } from './actions'
25+
import type { ActionTuple, Visitor, VisitorResult, Visitors } from './types'
26+
import { nodelike, nodename, parentlike } from './utils'
27+
28+
/**
29+
* Visit nodes, with ancestral information.
30+
*
31+
* This algorithm performs [*depth-first tree traversal*][dft] in
32+
* [*preorder*][preorder] (**NLR**) and/or [*postorder*][postorder] (**LRN**),
33+
* or if `reverse` is given, *reverse preorder* (**NRL**) and/or *reverse
34+
* postorder* (**RLN**). Nodes are handled on [*enter*][enter] during *preorder*
35+
* traversals and on [*exit*][exit] during *postorder* traversals.
36+
*
37+
* Walking the `tree` is an intensive task. Make use of `visitor` return values
38+
* whenever possible. Instead of walking `tree` multiple times, walk it once,
39+
* use [`unist-util-is`][unist-util-is] to check if a node matches, and then
40+
* perform different operations.
41+
*
42+
* You can change `tree`. See {@linkcode Visitor} for more info.
43+
*
44+
* [dft]: https://github.com/syntax-tree/unist#depth-first-traversal
45+
* [enter]: https://github.com/syntax-tree/unist#enter
46+
* [exit]: https://github.com/syntax-tree/unist#exit
47+
* [postorder]: https://github.com/syntax-tree/unist#postorder
48+
* [preorder]: https://github.com/syntax-tree/unist#preorder
49+
* [unist-util-is]: https://github.com/syntax-tree/unist-util-is
50+
*
51+
* @see {@linkcode Node}
52+
* @see {@linkcode Visitors}
53+
*
54+
* @template {Node} [Tree=Node] - Tree to traverse
55+
*
56+
* @param {Tree} tree - Tree to traverse
57+
* @param {Visitor<Tree> | Visitors<Tree>} visitor - A function to handle nodes
58+
* when entering (*preorder*), or an object to handle nodes when entering and
59+
* leaving (*preorder* and *postorder*)
60+
* @param {(boolean | null)?} [reverse] - Traverse in reverse preorder (NRL)
61+
* and/or postorder (RLN) instead of default traversal order(s)
62+
* @return {void} Nothing
63+
*/
64+
function visit<Tree extends Node = Node>(
65+
this: void,
66+
tree: Tree,
67+
visitor: Visitor<Tree> | Visitors<Tree>,
68+
reverse?: boolean | null | undefined
69+
): void
70+
71+
/**
72+
* Visit nodes, with ancestral information.
73+
*
74+
* This algorithm performs [*depth-first tree traversal*][dft] in
75+
* [*preorder*][preorder] (**NLR**) and/or [*postorder*][postorder] (**LRN**),
76+
* or if `reverse` is given, *reverse preorder* (**NRL**) and/or *reverse
77+
* postorder* (**RLN**). Nodes are handled on [*enter*][enter] during *preorder*
78+
* traversals and on [*exit*][exit] during *postorder* traversals.
79+
*
80+
* You can choose which nodes visitor functions handle by passing a
81+
* [`test`][test]. For complex tests, you should test yourself in `visitor`
82+
* instead, as it will be faster and also have improved type information.
83+
*
84+
* Walking the `tree` is an intensive task. Make use of visitor return values
85+
* whenever possible. Instead of walking the `tree` multiple times, walk it
86+
* once, use [`unist-util-is`][unist-util-is] to check if a node matches, and
87+
* then perform different operations.
88+
*
89+
* You can change `tree`. See {@linkcode Visitor} for more info.
90+
*
91+
* [dft]: https://github.com/syntax-tree/unist#depth-first-traversal
92+
* [enter]: https://github.com/syntax-tree/unist#enter
93+
* [exit]: https://github.com/syntax-tree/unist#exit
94+
* [postorder]: https://github.com/syntax-tree/unist#postorder
95+
* [preorder]: https://github.com/syntax-tree/unist#preorder
96+
* [test]: https://github.com/syntax-tree/unist-util-is#test
97+
* [unist-util-is]: https://github.com/syntax-tree/unist-util-is
98+
*
99+
* @see {@linkcode Node}
100+
* @see {@linkcode Test}
101+
* @see {@linkcode Visitors}
102+
*
103+
* @template {Node} [Tree=Node] - Tree to traverse
104+
* @template {Test} [Check=Test] - Visited node test
105+
*
106+
* @param {Tree} tree - Tree to traverse
107+
* @param {Check} test - [`unist-util-is`][unist-util-is]-compatible test
108+
* @param {Visitor<Tree, Check> | Visitors<Tree, Check>} visitor - A function to
109+
* handle nodes passing `test` when entering (*preorder*), or an object to
110+
* handle passing nodes when entering and leaving (*preorder* and *postorder*)
111+
* @param {(boolean | null)?} [reverse] - Traverse in reverse preorder (NRL)
112+
* and/or postorder (RLN) instead of default traversal order(s)
113+
* @return {void} Nothing
114+
*/
115+
function visit<Tree extends Node = Node, Check extends Test = Test>(
116+
this: void,
117+
tree: Tree,
118+
test: Check,
119+
visitor: Visitor<Tree, Check> | Visitors<Tree, Check>,
120+
reverse?: boolean | null
121+
): void
122+
123+
/**
124+
* Visit nodes, with ancestral information.
125+
*
126+
* @see {@linkcode Node}
127+
* @see {@linkcode Test}
128+
* @see {@linkcode Visitor}
129+
* @see {@linkcode Visitors}
130+
*
131+
* @param {Node} tree - Tree to traverse
132+
* @param {Test | Visitor | Visitors} test - Visited node test or `visitor`
133+
* @param {(Visitor | Visitors | boolean | null)?} [visitor] - A function to
134+
* handle entering nodes, an object containing functions to handle entering and
135+
* leaving nodes, or `reverse`
136+
* @param {(boolean | null)?} [reverse] - Traverse in reverse order
137+
* @return {void} Nothing
138+
*/
139+
function visit(
140+
tree: Node,
141+
test: Test | Visitor | Visitors,
142+
visitor?: Visitor | Visitors | boolean | null,
143+
reverse?: boolean | null
144+
): void {
145+
if (isBoolean(visitor) || isNIL(visitor)) {
146+
reverse = visitor
147+
visitor = test
148+
if (isFunction(visitor)) visitor = { enter: visitor }
149+
test = null
150+
} else {
151+
if (isFunction(visitor)) visitor = { enter: visitor }
152+
test = <Test>test
153+
}
154+
155+
/**
156+
* Node checker.
157+
*
158+
* @const {Check} check
159+
*/
160+
const check: Check = convert(<Test>test)
161+
162+
/**
163+
* Default value used to move between child nodes.
164+
*
165+
* @const {number} step
166+
*/
167+
const step: number = reverse ? -1 : 1
168+
169+
/**
170+
* Convert a visitor `result` to an {@linkcode ActionTuple}.
171+
*
172+
* @this {void}
173+
*
174+
* @param {VisitorResult} result - Result returned from visitor function
175+
* @return {ActionTuple} Visitor result as action tuple
176+
*/
177+
function cleanResult(this: void, result: VisitorResult): ActionTuple {
178+
switch (true) {
179+
case isArray(result):
180+
return result
181+
case isNIL(result):
182+
return []
183+
case isNumber(result):
184+
return [CONTINUE, result]
185+
default:
186+
return [result]
187+
}
188+
}
189+
190+
/**
191+
* Build a function to visit a node.
192+
*
193+
* @see {@linkcode ActionTuple}
194+
* @see {@linkcode Index}
195+
* @see {@linkcode Node}
196+
* @see {@linkcode Parent}
197+
*
198+
* @this {void}
199+
*
200+
* @param {Node} node - Found node
201+
* @param {Optional<Index>} index - Index of `node` in `parent.children`
202+
* @param {Optional<Parent>} parent - Parent of `node`
203+
* @param {Parent[]} ancestors - Ancestors of node
204+
* @return {Fn<EmptyArray, Readonly<ActionTuple>>} Visitor function
205+
*/
206+
function factory(
207+
this: void,
208+
node: Node,
209+
index: Optional<Index>,
210+
parent: Optional<Parent>,
211+
ancestors: Parent[]
212+
): Fn<EmptyArray, Readonly<ActionTuple>> {
213+
if (nodelike(node)) {
214+
/**
215+
* Node name.
216+
*
217+
* @const {Nullable<string>} name
218+
*/
219+
const name: Nullable<string> = nodename(node)
220+
221+
// set name of factory visit function to display name for node
222+
define(visit, 'name', {
223+
value: `node(${color(`${node.type}${name ? `<${name}>` : ''}`)})`
224+
})
225+
}
226+
227+
return visit
228+
229+
/**
230+
* Visit `node`.
231+
*
232+
* @see {@linkcode ActionTuple}
233+
*
234+
* @return {Readonly<ActionTuple>} Clean visitor result
235+
*/
236+
function visit(): Readonly<ActionTuple> {
237+
const { enter, leave } = <Visitors>visitor
238+
239+
/**
240+
* Index of current child node.
241+
*
242+
* @var {number} offset
243+
*/
244+
let offset: number = 0
245+
246+
/**
247+
* Clean visitor result.
248+
*
249+
* @var {Readonly<ActionTuple>} result
250+
*/
251+
let result: Readonly<ActionTuple> = cleanResult(null)
252+
253+
// visit node on enter
254+
if (isFalsy(test) || check(node, index, parent)) {
255+
result = cleanResult(enter?.(node, index, parent, ancestors))
256+
if (result[0] === EXIT) return result
257+
}
258+
259+
// visit each child in node.children
260+
if (parentlike(node) && result[0] !== SKIP) {
261+
offset = (reverse ? node.children.length : -1) + step
262+
while (offset > -1 && offset < node.children.length) {
263+
/**
264+
* Child node.
265+
*
266+
* @const {Node} child
267+
*/
268+
const child: Node = node.children[offset]
269+
270+
/**
271+
* Clean visitor result for {@linkcode child}.
272+
*
273+
* @const {Readonly<ActionTuple>} subresult
274+
*/
275+
const subresult: Readonly<ActionTuple> = factory(
276+
child,
277+
offset,
278+
node,
279+
sift([...ancestors, parent])
280+
)()
281+
282+
if (subresult[0] === EXIT) return subresult
283+
offset = isNumber(subresult[1]) ? subresult[1] : offset + step
284+
}
285+
}
286+
287+
// visit node on leave
288+
if (leave && (isFalsy(test) || check(node, index, parent))) {
289+
result = cleanResult(leave(node, index, parent, ancestors))
290+
}
291+
292+
return result
293+
}
294+
}
295+
296+
return void factory(tree, undefined, undefined, [])()
297+
}
298+
299+
export default visit

0 commit comments

Comments
 (0)