Skip to content

Commit 437cee0

Browse files
complete
1 parent 1d21f83 commit 437cee0

File tree

1 file changed

+38
-38
lines changed

1 file changed

+38
-38
lines changed

website/blog/typed-napi.md

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -312,84 +312,82 @@ ast-grep -p '$NODE.$METHOD<$K>($$$)'
312312

313313
### Refine Node, Automatically
314314

315-
The key feature of the new API is to automatically refine the node to a specific kind when the user gives more type information.
315+
A standout feature of our new API is automatic type refinement based on contextual information. This happens seamlessly through the `field` method.
316316

317-
This is done by using the `field` method
317+
When you access a node's field using `field("name")`, the system automatically examines the static type information and refines the node type accordingly:
318318

319-
`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.
320319
```typescript
321320
let exportStmt: SgNode<'export_statement'>
322-
exportStmt.field('declaration') // refine to SgNde<'function_declaration'> | SgNode<'variable_declaration'> ...
321+
exportStmt.field('declaration') // Automatically refines to union:
322+
// SgNode<'function_declaration'> |
323+
// SgNode<'variable_declaration'> | ...
323324
```
324325

325-
You don't need to explicitly spell out the kind! It is both **concise** and **correct**.
326+
The magic here is that you never need to specify the possible types explicitly - the system infers them automatically. This approach is both **concise** in usage and **correct** in type inference.
326327

328+
### Exhaustive Pattern Matching with kindToRefine
327329

328-
### Exhaustive Check via `sgNode.kindToRefine`
330+
We've also introduced a new `kindToRefine` property for comprehensive type checking. You might wonder: why add this when we already have a `kind()` method?
329331

330-
ast-grep/napi also introduced a new property `kindToRefine` to refine the node to a specific kind.
332+
There are two key reasons:
333+
1. Preserving backward compatibility with the existing `kind()` method
334+
2. Enabling TypeScript's type narrowing, which works with properties but not method calls
331335

332-
Why do we need the `kindToRefine` property given that we already have a `kind()` method?
336+
While `kindToRefine` is implemented as a getter that calls into Rust code (making it as computationally expensive as the `kind()` method), it enables powerful type checking capabilities. To ensure developers are aware of this **performance** characteristic, we deliberately chose a _distinct and longer_ property name.
333337

334-
First, `kind` is a method in the existing API and we prefer not to have a breaking change.
335-
336-
Secondly, TypeScript cannot narrow type via a method call. It can only narrow type via a property access.
337-
338-
In terms of implementation, `kindToRefine` is a getter under the hood powered by napi. It is less efficient thant JavaScript's object property access.
339-
Actually, it will call Rust function from JavaScript, which is as expensive as the `kind()` method.
340-
341-
To bring user's awareness to this **performance** implication and to make a backward compatible API change, we introduce the `kindToRefine` property.
342-
343-
It is mostly useful for a union type of nodes with specific kinds, guiding you to write a **correct** AST program. You can use it in tandem with the union type returned by `RefinedNode` to exhaustively check all possible kinds of nodes.
338+
This property really shines when working in tandem union types returned by `RefineNode`, helping you write **correct** AST transformations through exhaustive pattern matching:
344339

345340
```typescript
346341
const func: SgNode<'function_declaration'> | SgNode<'arrow_function'>
347342

348343
switch (func.kindToRefine) {
349344
case 'function_declaration':
350-
func.kindToRefine // narrow to 'function_declaration'
345+
func.kindToRefine // Narrowed to function_declaration
351346
break
352347
case 'arrow_function':
353-
func.kindToRefine // narrow to 'arrow_function'
348+
func.kindToRefine // Narrowed to arrow_function
354349
break
355-
// ....
356350
default:
357-
func satisfies never // exhaustiveness, checked!
351+
func satisfies never // TypeScript ensures we handled all cases
358352
}
359353
```
360354

355+
The combination of automatic type refinement and exhaustive pattern matching makes it easier to write **correct** AST transformations while catching potential errors at compile time.
356+
361357
## Confine Types
362358

363-
Be austere of type level programming. Too much type level programming can make the compiler explode, as well as users' brain.
359+
Always bear in mind this mantra: _Be austere with type level programming._
360+
361+
Overdoing type level programming can overload the compiler as well as overwhelm users.
362+
It is a good practice to confine the API type to a reasonable complexity level.
364363

365364
### Prune unnamed kinds
366-
Tree-sitter's static type contains a lot of unnamed kinds, which are not useful to users.
367365

368-
For example `+`/`-`/`*`/`/` is too noisy for an AST library.
366+
Tree-sitter's static type includes many unnamed kinds, which are not user-friendly.
367+
368+
For instance, operators like `+`/`-`/`*`/`/` are too verbose for an AST library. We're building a compiler plugin, not solving elementary school math problems, right?
369369

370-
This is also the reason why we need to include `string` in the `Kinds`.
370+
This is why we exclude the unnamed kinds and include `string` in the `Kinds`.
371371

372372
In the type generation step, ast-grep filters out these unnamed kinds to make the type more **concise**.
373373

374374
### Opt-in refinement for better compile time performance
375375

376-
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**.
377-
One type map for a single language can be several thousand lines of code with hundreds of kinds.
378-
The more type information the user provides, the slower the compile time.
376+
The new API is designed to provide a better type checking and autocompletion experience for users.
377+
However, this improvement comes at the cost of **performance**. A single type map for one language can span several thousand lines of code with hundreds of kinds. The more type information the user provides, the slower the compile time.
379378

379+
To manage this, you need to explicitly opt into type information by passing type parameters to the `parse` method.
380380

381-
So you need to explicitly opt in type information by passing type parameters to `parse` method.
382381
```typescript
383382
import { parse } from '@ast-grep/napi'
384-
import TS from '@ast-grep/napi/lang/TypeScript'
383+
import TS from '@ast-grep/napi/lang/TypeScript' // import this can be slow
385384
const untyped = parse(Lang.TypeScript, code)
386385
const typed = parse<TS>(Lang.TypeScript, code)
387386
```
388387

389388
### Typed Rule!
390389

391-
The last feature worth mentioning is the typed rule! You can even type the `kind` in rule JSON!
392-
390+
The last notable feature is the typed rule. You can even type the `kind` in rule JSON!
393391

394392
```typescript
395393
interface Rule<M extends TypeMaps> {
@@ -398,9 +396,10 @@ interface Rule<M extends TypeMaps> {
398396
}
399397
```
400398

401-
Of course this is not to _confine_ the type, but let the type creep into the rule greatly improving the UX and rule **correctness**.
399+
Of course, this isn't about _confining_ the type but allowing type information to enhance rules, significantly improving UX and rule **correctness**.
402400

403401
You can look up the available kinds in the static type via the completion popup in your editor. (btw I use nvim)
402+
404403
```typescript
405404
sgNode.find({
406405
rule: {
@@ -412,11 +411,12 @@ sgNode.find({
412411

413412
## Ending
414413

415-
I'm very thrilled to see the future of AST manipulation in TypeScript.
416-
This feature enables users to switch freely between untyped AST and typed AST.
414+
I'm incredibly excited about the future of AST manipulation in TypeScript.
415+
416+
This feature empowers users to seamlessly switch between untyped and typed AST, offering flexibility and enhanced capabilities, an innovation that has not been seen in other AST libraries, especially not in native language based ones.
417417

418-
To use a quote from [Theo's video](https://www.youtube.com/clip/Ugkxn2oomDuyQjtaKXhYP1MU9TLEShf5m1nf):
418+
As Theo aptly puts it in [his video](https://www.youtube.com/clip/Ugkxn2oomDuyQjtaKXhYP1MU9TLEShf5m1nf):
419419

420420
> 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
421421
422-
ast-grep will strive to be the one that bridges the gap between Rust and TypeScript.
422+
ast-grep is determined to bridge that gap between Rust and TypeScript!

0 commit comments

Comments
 (0)