Skip to content

Commit 015a35e

Browse files
add more langs
1 parent e195476 commit 015a35e

File tree

1 file changed

+51
-20
lines changed

1 file changed

+51
-20
lines changed

website/blog/typed-napi.md

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,10 @@ interface TypeMpa {
102102
}
103103
```
104104
What is `TypeMaps`? It is a type that contains all static node types. It is a map from kind to the static type of the kind.
105+
Here is a simplified example of the TypeScript static type.
105106

106107
```typescript
107-
type TypeScriptMap = {
108+
type TypeScript = {
108109
// AST node type definition
109110
function_declaration: {
110111
type: "function_declaration", // kind
@@ -135,22 +136,21 @@ Tree-sitter also provides alias types where a kind is an alias of a list of othe
135136
136137
We want to both type a node's kind and its fields.
137138
138-
## `SgNode<K>`
139+
## Give a type to `SgNode`
139140
140-
`SgNode<M, K>` is the main type in the new API. It is a generic type that represents a node with kind `K`. It is a union of all possible kinds of nodes.
141+
`SgNode<M, K>` is the main type in the new API. It is a generic type that represents a node with kind `K` of language type map `M`. It is a union of all possible kinds of nodes.
141142
142143
```typescript
143144
class SgNode<M extends TypesMap, K extends keyof M> {
144145
kind: K
145-
fields: M[K]['fields'] // for simplicity
146+
fields: M[K]['fields'] // demo definition, real one is more complex
146147
}
147148
```
148149

149150

150-
151151
### `ResolveType<M, T>`
152152

153-
TreeSitter's type alias is helpful to reduce the generated JSON file size but it is not useful to users because the alias is never directly used as a node's kind nor is used as `kind` in ast-grep rule.
153+
TreeSitter's type alias is helpful to reduce the generated JSON file size but it is not useful to users because the alias is never directly used as a node's kind nor is used as `kind` in ast-grep rule. For example, `declaration` mentioned above can never be used as `kind` in ast-grep rule.
154154

155155
We need to use a type alias to resolve the alias type to its concrete type.
156156

@@ -173,11 +173,10 @@ type LowPriorityString = string & {}
173173
174174
The above type is a linient string type that is compatible with any string type. But it also uses a well-known trick to take advantage of TypeScript's type priority to prefer the `keyof M` type in completion over the `string & {}` type. To make it more self-explanatory, the `stirng & {}` type is aliased to `LowPriorityString`.
175175
176-
Problem? open-ended union is not well supported in TypeScript
177-
178-
https://github.com/microsoft/TypeScript/issues/33471
179-
https://github.com/microsoft/TypeScript/issues/26277
176+
Problem? open-ended union is not [well](https://github.com/microsoft/TypeScript/issues/33471)
177+
[supported](https://github.com/microsoft/TypeScript/issues/26277)in TypeScript.
180178
179+
We need other tricks to make it work better. Introducing `RefineNode` type.
181180
182181
### Bridging general nodes and specific nodes via `RefineNode`
183182
@@ -196,6 +195,18 @@ Which kind of union should we use?
196195
197196
Note `SgNode<'expression' | 'type'>` is different from `SgNode<'expression'> | SgNode<'type'>`
198197
TypeScript has difficulty in narrowing the previous type, because it not safe to assume the former is equivalent to the later.
198+
199+
```typescript
200+
let single: SgNode<'expression' | 'type'>
201+
if (single.kind === 'expression') {
202+
single // Still SgNode<'expression' | 'type'>, not narrowed
203+
}
204+
let union: SgNode<'expression'> | SgNode<'type'>
205+
if (union.kind === 'expression') {
206+
union // SgNode<'expression'>, narrowed
207+
}
208+
```
209+
199210
However, `SgNode` is covariant in the kind parameter and this means it is okay.
200211
it is general okay to distribute the type constructor over union type if the parameter is covariant.
201212
but TypeScript does not support this feature.
@@ -220,22 +231,24 @@ Now let's talk about how to refine the general node to a specific node in ast-gr
220231
221232
Most AST traversal methods in ast-grep now can take a new type parameter to refine the node to a specific kind.
222233
223-
This is like the `document.querySelector<T>` method in the DOM API. It returns a general `Element` type, but you can refine it to a specific type like `HTMLDivElement` by providing generic argument.
234+
This is like the `document.querySelector<T>` method in the [DOM API](https://www.typescriptlang.org/docs/handbook/dom-manipulation.html#the-queryselector-and-queryselectorall-methods). It returns a general `Element` type, but you can refine it to a specific type like `HTMLDivElement` by providing generic argument.
224235
225-
For example `sgNode.find<"KIND">()`
236+
For example `sgNode.parent<"KIND">()`. This will refine the node to a specific kind.
226237
227-
This uses the intersting overloading feature of TypeScript
238+
This uses the interesting overloading feature of TypeScript
228239
229240
```typescript
230-
interface NodeMethod<K> {
241+
interface NodeMethod<M, K> {
231242
(): SgNode
232-
<T extends K>(): SgNode<T>
243+
<T extends K>(): RefineNode<M, T>
233244
}
234245
```
246+
If no type is provided, it returns a general node, `SgNode<M>`.
247+
If a type is provided, it returns a specific node, `SgNode<M, K>`.
235248

236-
If no type is provided, it returns a general node. If a type is provided, it returns a specific node.
249+
The reason why we use two overloading signatures here is to distinguish the two cases. If we use a single generic signature, TypeScript will always return the single version `SgNode<M, K1|K2>` or always returns a union of different `SgNode`s.
237250

238-
another way to do runtime checking is via `sgNode.is("kind")`, One time type narrowing
251+
another way to do runtime checking is via `sgNode.is("kind")`, one time type narrowing
239252

240253
```typescript
241254
if (sgNode.is("function_declaration")) {
@@ -250,6 +263,10 @@ The key feature of the new API is to automatically refine the node to a specific
250263
This is done by using the `field` method
251264

252265
`sgNode.field("kind")` will automatically check the field name and its corresponding types in the static type, and refine the node to the specific kind.
266+
```typescript
267+
let exportStmt: SgNode<'export_statement'>
268+
exportStmt.field('declaration') // refine to SgNde<'function_declaration'> | SgNode<'variable_declaration'> ...
269+
```
253270

254271

255272
### Exhaustive Checking via `sgNode.kindToRefine`
@@ -291,8 +308,12 @@ This is also the reason why we need to include `string` in the `Kinds`.
291308

292309
### Opt-in refinement for better compile time performance
293310

294-
The new API is designed to provide a better type checking and completion experience to the user. But it comes with a cost of performance. The more type information the user provides, the slower the compile time.
311+
The new API is designed to provide a better type checking and completion experience to the user. But it comes with a cost of performance.
312+
One type map for a single language can be several thousand lines of code with hundreds of kinds.
313+
The more type information the user provides, the slower the compile time.
295314

315+
316+
So you need to explicitly opt in type information by passing type parameters to `parse` method.
296317
```typescript
297318
import { parse } from '@ast-grep/napi'
298319
import TS from '@ast-grep/napi/lang/TypeScript'
@@ -304,6 +325,7 @@ const typed = parse<TS>(Lang.TypeScript, code)
304325

305326
The last feature worth mentioning is the typed rule! You can even type the `kind` in rule JSON!
306327

328+
You can look up the available kinds in the static type via the completion popup in your editor. (btw I use nvim)
307329
```typescript
308330
sgNode.find({
309331
rule: {
@@ -313,11 +335,20 @@ sgNode.find({
313335
})
314336
```
315337

338+
```typescript
339+
interface Rule<M> {
340+
kind: Kinds<M>
341+
... // other rules
342+
}
343+
```
344+
316345
## Ending
317346

318347
I'm very thrilled to see the future of AST manipulation in TypeScript.
319348
This feature enables users to switch freely between untyped AST and typed AST.
320349

350+
To use a quote from [Theo's video](https://www.youtube.com/clip/Ugkxn2oomDuyQjtaKXhYP1MU9TLEShf5m1nf):
351+
352+
> There are very few devs that understands Rust deeply enough and compiler deeply enough that also care about TypeScript in web dev enough to build something for web devs in Rust
321353
322-
https://x.com/hd_nvim/status/1868453729940500924
323-
There are very few devs that understands Rust deeply enough and compiler deeply enough that also care about TypeScript in web dev enough to build something for web devs in Rust
354+
ast-grep will strive to be the one that bridges the gap between Rust and TypeScript.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy