@hyper-light/vorpal-wasm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -0
- package/package.json +33 -0
- package/wasm.d.ts +146 -0
- package/wasm.js +9 -0
- package/wasm_bg.js +1429 -0
- package/wasm_bg.wasm +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# @hyper-light/vorpal-wasm
|
|
2
|
+
|
|
3
|
+
WebAssembly build of [vorpal](https://vorpal.github.io/) for use in browsers and Node.js.
|
|
4
|
+
|
|
5
|
+
This package provides the same API as [`@hyper-light/vorpal-node`](https://www.npmjs.com/package/@hyper-light/vorpal-node) but runs in any JavaScript environment that supports WebAssembly, including browsers, Deno, and edge runtimes.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
yarn add @hyper-light/vorpal-wasm web-tree-sitter
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`web-tree-sitter` is a required peer dependency.
|
|
14
|
+
|
|
15
|
+
### Vite
|
|
16
|
+
|
|
17
|
+
When using Vite, you need to make `tree-sitter.wasm` available in your public directory. You can automate this with a `postinstall` script in your `package.json` (see [web-tree-sitter setup](https://github.com/tree-sitter/tree-sitter/tree/master/lib/binding_web#setup)):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
"postinstall": "cp node_modules/web-tree-sitter/tree-sitter.wasm public"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Unlike `@hyper-light/vorpal-node`, this package has no predefined language support. All languages must be registered at runtime by loading their tree-sitter WASM parser.
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { initializeTreeSitter, registerDynamicLanguage, parse, kind } from '@hyper-light/vorpal-wasm'
|
|
29
|
+
|
|
30
|
+
// 1. Initialize the tree-sitter WASM runtime (once)
|
|
31
|
+
await initializeTreeSitter()
|
|
32
|
+
|
|
33
|
+
// 2. Register a language by loading its WASM parser
|
|
34
|
+
await registerDynamicLanguage({
|
|
35
|
+
javascript: { libraryPath: '/path/to/tree-sitter-javascript.wasm' },
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// 3. Parse and search code
|
|
39
|
+
const sg = parse('javascript', 'console.log("hello world")')
|
|
40
|
+
const node = sg.root().find('console.log($ARG)')
|
|
41
|
+
console.log(node.getMatch('ARG').text()) // "hello world"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Registering Languages
|
|
45
|
+
|
|
46
|
+
`registerDynamicLanguage` accepts a map of language name to registration config. It can be called multiple times to add or update languages.
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
await registerDynamicLanguage({
|
|
50
|
+
javascript: { libraryPath: '/path/to/tree-sitter-javascript.wasm' },
|
|
51
|
+
python: {
|
|
52
|
+
libraryPath: '/path/to/tree-sitter-python.wasm',
|
|
53
|
+
expandoChar: 'µ', // custom expando char for languages where $ is special
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The `expandoChar` option sets the character used internally to represent metavariables (defaults to `$`). Use a different character for languages where `$` is a valid identifier character (e.g. PHP, Bash).
|
|
59
|
+
|
|
60
|
+
### Pattern Matching
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// By pattern string
|
|
64
|
+
sg.root().find('console.log($$$ARGS)')
|
|
65
|
+
|
|
66
|
+
// By kind number
|
|
67
|
+
const k = kind('javascript', 'call_expression')
|
|
68
|
+
sg.root().find(k)
|
|
69
|
+
|
|
70
|
+
// By rule config (same as YAML rules)
|
|
71
|
+
sg.root().find({
|
|
72
|
+
rule: { pattern: 'console.log($A)' },
|
|
73
|
+
constraints: { A: { kind: 'string' } },
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Code Rewriting
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
const match = sg.root().find('console.log($A)')
|
|
81
|
+
const edit = match.replace('console.error($A)')
|
|
82
|
+
const newCode = sg.root().commitEdits([edit])
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## API Reference
|
|
86
|
+
|
|
87
|
+
### Top-level functions
|
|
88
|
+
|
|
89
|
+
#### `initializeTreeSitter(): Promise<void>`
|
|
90
|
+
|
|
91
|
+
Initializes the tree-sitter WASM runtime. Must be called once before any other function.
|
|
92
|
+
|
|
93
|
+
#### `registerDynamicLanguage(langs: Record<string, { libraryPath: string, expandoChar?: string }>): Promise<void>`
|
|
94
|
+
|
|
95
|
+
Registers one or more language parsers by loading their WASM binaries. Can be called multiple times; existing languages are updated.
|
|
96
|
+
|
|
97
|
+
#### `parse(lang: string, src: string): SgRoot`
|
|
98
|
+
|
|
99
|
+
Parses source code and returns an `SgRoot` instance. Throws if the language has not been registered.
|
|
100
|
+
|
|
101
|
+
#### `kind(lang: string, kindName: string): number`
|
|
102
|
+
|
|
103
|
+
Returns the numeric kind ID for a named node type in the given language. Useful for matching by node kind.
|
|
104
|
+
|
|
105
|
+
#### `pattern(lang: string, patternStr: string): object`
|
|
106
|
+
|
|
107
|
+
Compiles a pattern string into a rule config object (equivalent to `{ rule: { pattern: patternStr } }`). Useful for building rule configs programmatically.
|
|
108
|
+
|
|
109
|
+
#### `dumpPattern(lang: string, patternStr: string, selector?: string, strictness?: string): PatternTree`
|
|
110
|
+
|
|
111
|
+
Dumps the internal structure of a pattern for inspection and debugging. Returns a tree showing how vorpal parses the pattern, including source positions and node kinds.
|
|
112
|
+
|
|
113
|
+
- `selector`: optional kind name for contextual patterns (e.g. `'field_definition'`)
|
|
114
|
+
- `strictness`: one of `"cst"`, `"smart"` (default), `"ast"`, `"relaxed"`, `"signature"`, `"template"`
|
|
115
|
+
|
|
116
|
+
Each `PatternTree` node has:
|
|
117
|
+
- `kind`: the tree-sitter node kind string
|
|
118
|
+
- `pattern`: `"metaVar"`, `"terminal"`, or `"internal"`
|
|
119
|
+
- `isNamed`: whether the node is a named node
|
|
120
|
+
- `text`: source text (for metavar and terminal nodes)
|
|
121
|
+
- `children`: child `PatternTree` nodes
|
|
122
|
+
- `start`, `end`: `{ line, column }` positions in the pattern source
|
|
123
|
+
|
|
124
|
+
### `SgRoot`
|
|
125
|
+
|
|
126
|
+
Represents the parsed tree of code.
|
|
127
|
+
|
|
128
|
+
#### `root(): SgNode`
|
|
129
|
+
|
|
130
|
+
Returns the root `SgNode`.
|
|
131
|
+
|
|
132
|
+
#### `filename(): string`
|
|
133
|
+
|
|
134
|
+
Returns `"anonymous"` when the instance is created via `parse`.
|
|
135
|
+
|
|
136
|
+
#### `getInnerTree(): Tree`
|
|
137
|
+
|
|
138
|
+
Returns the underlying `web-tree-sitter` `Tree` object. Useful for low-level inspection or debugging.
|
|
139
|
+
|
|
140
|
+
### `SgNode`
|
|
141
|
+
|
|
142
|
+
Represents a single AST node.
|
|
143
|
+
|
|
144
|
+
#### Position and info
|
|
145
|
+
|
|
146
|
+
| Method | Description |
|
|
147
|
+
|--------|-------------|
|
|
148
|
+
| `range()` | Returns `{ start, end }` where each is `{ line, column, index }` |
|
|
149
|
+
| `isLeaf()` | True if the node has no children |
|
|
150
|
+
| `isNamed()` | True if the node is a named (non-anonymous) node |
|
|
151
|
+
| `isNamedLeaf()` | True if the node is a named node with no named children |
|
|
152
|
+
| `kind()` | Returns the node kind string |
|
|
153
|
+
| `is(kind: string)` | True if the node kind equals `kind` |
|
|
154
|
+
| `text()` | Returns the source text of the node |
|
|
155
|
+
| `id()` | Returns the unique node ID |
|
|
156
|
+
|
|
157
|
+
#### Searching
|
|
158
|
+
|
|
159
|
+
| Method | Description |
|
|
160
|
+
|--------|-------------|
|
|
161
|
+
| `find(matcher)` | Returns the first descendant matching the matcher, or `undefined` |
|
|
162
|
+
| `findAll(matcher)` | Returns all descendants matching the matcher |
|
|
163
|
+
|
|
164
|
+
Matchers can be a pattern string, a kind number (from `kind()`), or a rule config object.
|
|
165
|
+
|
|
166
|
+
#### Relational matchers
|
|
167
|
+
|
|
168
|
+
| Method | Description |
|
|
169
|
+
|--------|-------------|
|
|
170
|
+
| `matches(matcher)` | True if the node itself matches |
|
|
171
|
+
| `inside(matcher)` | True if the node is inside an ancestor matching the matcher |
|
|
172
|
+
| `has(matcher)` | True if the node has a descendant matching the matcher |
|
|
173
|
+
| `precedes(matcher)` | True if the node comes before a sibling matching the matcher |
|
|
174
|
+
| `follows(matcher)` | True if the node comes after a sibling matching the matcher |
|
|
175
|
+
|
|
176
|
+
#### Match environment
|
|
177
|
+
|
|
178
|
+
| Method | Description |
|
|
179
|
+
|--------|-------------|
|
|
180
|
+
| `getMatch(name: string)` | Returns the node bound to a metavariable (e.g. `$VAR`) |
|
|
181
|
+
| `getMultipleMatches(name: string)` | Returns nodes bound to a multi-metavariable (e.g. `$$$ARGS`) |
|
|
182
|
+
| `getTransformed(name: string)` | Returns the string value of a transformed variable |
|
|
183
|
+
|
|
184
|
+
#### Tree traversal
|
|
185
|
+
|
|
186
|
+
| Method | Description |
|
|
187
|
+
|--------|-------------|
|
|
188
|
+
| `children_nodes()` | Returns all child nodes |
|
|
189
|
+
| `parent_node()` | Returns the parent node, or `undefined` |
|
|
190
|
+
| `child(nth: number)` | Returns the nth child, or `undefined` |
|
|
191
|
+
| `ancestors()` | Returns all ancestors from parent to root |
|
|
192
|
+
| `next_node()` | Returns the next sibling, or `undefined` |
|
|
193
|
+
| `nextAll()` | Returns all following siblings |
|
|
194
|
+
| `prev()` | Returns the previous sibling, or `undefined` |
|
|
195
|
+
| `prevAll()` | Returns all preceding siblings |
|
|
196
|
+
| `field(name: string)` | Returns the child node for a named field, or `undefined` |
|
|
197
|
+
| `fieldChildren(name: string)` | Returns all child nodes for a named field |
|
|
198
|
+
|
|
199
|
+
#### Editing
|
|
200
|
+
|
|
201
|
+
| Method | Description |
|
|
202
|
+
|--------|-------------|
|
|
203
|
+
| `replace(text: string)` | Creates a `WasmEdit` replacing this node's range with `text` |
|
|
204
|
+
| `commitEdits(edits: WasmEdit[])` | Applies edits to the node's text and returns the new source string |
|
|
205
|
+
|
|
206
|
+
`WasmEdit` has `start_pos`, `end_pos` (character offsets), and `inserted_text`. These fields can be modified before calling `commitEdits`.
|
|
207
|
+
|
|
208
|
+
## Building from Source
|
|
209
|
+
|
|
210
|
+
Requires [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/).
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# For browser (ES module)
|
|
214
|
+
yarn build
|
|
215
|
+
|
|
216
|
+
# For Node.js (CommonJS)
|
|
217
|
+
yarn build:nodejs
|
|
218
|
+
|
|
219
|
+
# For bundlers
|
|
220
|
+
yarn build:bundler
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Testing
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
yarn install
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Rust WASM tests
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
yarn test
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
> **Note:** `wasm-pack test --node` runs the test harness from a temporary directory.
|
|
236
|
+
> The `test` script sets `NODE_PATH=$PWD/node_modules` so that `web-tree-sitter` and
|
|
237
|
+
> parser WASM files can be resolved. If you run `wasm-pack test --node` directly,
|
|
238
|
+
> set `NODE_PATH` yourself:
|
|
239
|
+
>
|
|
240
|
+
> ```bash
|
|
241
|
+
> NODE_PATH=$PWD/node_modules wasm-pack test --node
|
|
242
|
+
> ```
|
|
243
|
+
|
|
244
|
+
### JavaScript tests
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
yarn test:js
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
This builds the WASM package for Node.js and runs AVA tests against it.
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hyper-light/vorpal-wasm",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"collaborators": [
|
|
5
|
+
"Herrington Darkholme <2883231+HerringtonDarkholme@users.noreply.github.com>"
|
|
6
|
+
],
|
|
7
|
+
"description": "Search and Rewrite code at large scale using precise AST pattern",
|
|
8
|
+
"version": "0.1.0",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/hyper-light/vorpal"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"wasm_bg.wasm",
|
|
16
|
+
"wasm.js",
|
|
17
|
+
"wasm_bg.js",
|
|
18
|
+
"wasm.d.ts"
|
|
19
|
+
],
|
|
20
|
+
"main": "wasm.js",
|
|
21
|
+
"types": "wasm.d.ts",
|
|
22
|
+
"sideEffects": [
|
|
23
|
+
"./wasm.js",
|
|
24
|
+
"./snippets/*"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"ast",
|
|
28
|
+
"pattern",
|
|
29
|
+
"codemod",
|
|
30
|
+
"search",
|
|
31
|
+
"rewrite"
|
|
32
|
+
]
|
|
33
|
+
}
|
package/wasm.d.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
export function registerDynamicLanguage(map: Record<string, {libraryPath: string, expandoChar?: string}>): Promise<void>;
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export class Pos {
|
|
9
|
+
private constructor();
|
|
10
|
+
free(): void;
|
|
11
|
+
[Symbol.dispose](): void;
|
|
12
|
+
/**
|
|
13
|
+
* column number starting from 0
|
|
14
|
+
*/
|
|
15
|
+
column: number;
|
|
16
|
+
/**
|
|
17
|
+
* character offset of the position
|
|
18
|
+
*/
|
|
19
|
+
index: number;
|
|
20
|
+
/**
|
|
21
|
+
* line number starting from 0
|
|
22
|
+
*/
|
|
23
|
+
line: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class Range {
|
|
27
|
+
private constructor();
|
|
28
|
+
free(): void;
|
|
29
|
+
[Symbol.dispose](): void;
|
|
30
|
+
/**
|
|
31
|
+
* ending position of the range
|
|
32
|
+
*/
|
|
33
|
+
end: Pos;
|
|
34
|
+
/**
|
|
35
|
+
* starting position of the range
|
|
36
|
+
*/
|
|
37
|
+
start: Pos;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Represents a single AST node.
|
|
42
|
+
*/
|
|
43
|
+
export class SgNode {
|
|
44
|
+
private constructor();
|
|
45
|
+
free(): void;
|
|
46
|
+
[Symbol.dispose](): void;
|
|
47
|
+
ancestors(): SgNode[];
|
|
48
|
+
child(nth: number): SgNode | undefined;
|
|
49
|
+
children_nodes(): SgNode[];
|
|
50
|
+
commitEdits(edits: any): string;
|
|
51
|
+
field(name: string): SgNode | undefined;
|
|
52
|
+
fieldChildren(name: string): SgNode[];
|
|
53
|
+
find(matcher: any): SgNode | undefined;
|
|
54
|
+
findAll(matcher: any): SgNode[];
|
|
55
|
+
follows(m: any): boolean;
|
|
56
|
+
getMatch(m: string): SgNode | undefined;
|
|
57
|
+
getMultipleMatches(m: string): SgNode[];
|
|
58
|
+
getTransformed(m: string): string | undefined;
|
|
59
|
+
has(m: any): boolean;
|
|
60
|
+
id(): number;
|
|
61
|
+
inside(m: any): boolean;
|
|
62
|
+
is(kind: string): boolean;
|
|
63
|
+
isLeaf(): boolean;
|
|
64
|
+
isNamed(): boolean;
|
|
65
|
+
isNamedLeaf(): boolean;
|
|
66
|
+
kind(): string;
|
|
67
|
+
matches(m: any): boolean;
|
|
68
|
+
next(): SgNode | undefined;
|
|
69
|
+
nextAll(): SgNode[];
|
|
70
|
+
parent_node(): SgNode | undefined;
|
|
71
|
+
precedes(m: any): boolean;
|
|
72
|
+
prev(): SgNode | undefined;
|
|
73
|
+
prevAll(): SgNode[];
|
|
74
|
+
range(): Range;
|
|
75
|
+
replace(text: string): WasmEdit;
|
|
76
|
+
text(): string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Represents the parsed tree of code.
|
|
81
|
+
*/
|
|
82
|
+
export class SgRoot {
|
|
83
|
+
private constructor();
|
|
84
|
+
free(): void;
|
|
85
|
+
[Symbol.dispose](): void;
|
|
86
|
+
/**
|
|
87
|
+
* Returns the path of the file if it is discovered by vorpal's `findInFiles`.
|
|
88
|
+
* Returns `"anonymous"` if the instance is created by `parse`.
|
|
89
|
+
*/
|
|
90
|
+
filename(): string;
|
|
91
|
+
/**
|
|
92
|
+
* This method is mainly for debugging tree parsing result.
|
|
93
|
+
*/
|
|
94
|
+
getInnerTree(): any;
|
|
95
|
+
/**
|
|
96
|
+
* Returns the root SgNode of the Vorpal instance.
|
|
97
|
+
*/
|
|
98
|
+
root(): SgNode;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class WasmEdit {
|
|
102
|
+
private constructor();
|
|
103
|
+
free(): void;
|
|
104
|
+
[Symbol.dispose](): void;
|
|
105
|
+
/**
|
|
106
|
+
* The end position of the edit (character offset)
|
|
107
|
+
*/
|
|
108
|
+
end_pos: number;
|
|
109
|
+
/**
|
|
110
|
+
* The text to be inserted
|
|
111
|
+
*/
|
|
112
|
+
inserted_text: string;
|
|
113
|
+
/**
|
|
114
|
+
* The start position of the edit (character offset)
|
|
115
|
+
*/
|
|
116
|
+
start_pos: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Dump a pattern's internal structure for inspection.
|
|
121
|
+
* `selector` is an optional kind name for contextual patterns.
|
|
122
|
+
* `strictness` is one of: "cst", "smart", "ast", "relaxed", "signature", "template".
|
|
123
|
+
* Returns a tree structure showing how vorpal parses the pattern, including source positions.
|
|
124
|
+
*/
|
|
125
|
+
export function dumpPattern(lang: string, pattern_str: string, selector?: string | null, strictness?: string | null): any;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Initialize the tree-sitter WASM runtime.
|
|
129
|
+
* Must be called before any other function.
|
|
130
|
+
*/
|
|
131
|
+
export function initializeTreeSitter(): Promise<void>;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get the `kind` number from its string name.
|
|
135
|
+
*/
|
|
136
|
+
export function kind(lang: string, kind_name: string): number;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Parse a string to a Vorpal instance.
|
|
140
|
+
*/
|
|
141
|
+
export function parse(lang: string, src: string): SgRoot;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Compile a string to a Vorpal Pattern config.
|
|
145
|
+
*/
|
|
146
|
+
export function pattern(lang: string, pattern_str: string): any;
|
package/wasm.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/* @ts-self-types="./wasm.d.ts" */
|
|
2
|
+
import * as wasm from "./wasm_bg.wasm";
|
|
3
|
+
import { __wbg_set_wasm } from "./wasm_bg.js";
|
|
4
|
+
|
|
5
|
+
__wbg_set_wasm(wasm);
|
|
6
|
+
wasm.__wbindgen_start();
|
|
7
|
+
export {
|
|
8
|
+
Pos, Range, SgNode, SgRoot, WasmEdit, dumpPattern, initializeTreeSitter, kind, parse, pattern, registerDynamicLanguage
|
|
9
|
+
} from "./wasm_bg.js";
|