1
+
2
+ import { readFileSync , writeFileSync } from 'node:fs'
3
+ import path from 'node:path'
1
4
import { parse , registerDynamicLanguage , SgRoot , DynamicLangRegistrations } from '@ast-grep/napi'
2
5
3
6
/** Setup ast-grep/lang package's pre-release */
4
7
interface SetupConfig {
8
+ /** the root directory of the package */
9
+ dirname : string
5
10
/** Name of the language. e.g. toml */
6
11
name : string
7
12
/** Language registration object, usually the export of index.js */
8
13
languageRegistration : DynamicLangRegistrations [ string ]
9
- packageName : string
14
+ /** Package name of tree-sitter package. e.g. tree-sitter-css */
15
+ treeSitterPackage : string
10
16
/** Test cases running in CI */
11
17
testRunner : ( parse : ( c : string ) => SgRoot ) => void
12
18
}
@@ -22,5 +28,79 @@ export function setup(setupConfig: SetupConfig) {
22
28
const arg = process . argv [ 2 ]
23
29
if ( arg === 'test' ) {
24
30
test ( setupConfig )
31
+ } else if ( arg === 'source' ) {
32
+ generateLangNodeTypes ( setupConfig )
33
+ }
34
+ }
35
+
36
+ interface NodeBasicInfo {
37
+ type : string
38
+ named : boolean
39
+ }
40
+
41
+ interface NodeFieldInfo {
42
+ multiple : boolean
43
+ required : boolean
44
+ types : NodeBasicInfo [ ]
45
+ }
46
+
47
+ interface NodeType extends NodeBasicInfo {
48
+ root ?: boolean
49
+ fields ?: {
50
+ [ fieldName : string ] : NodeFieldInfo
51
+ }
52
+ children ?: NodeFieldInfo
53
+ subtypes ?: NodeBasicInfo [ ]
54
+ }
55
+
56
+ function filterOutUnNamedNode ( node : NodeType ) : NodeType | null {
57
+ if ( ! node . named ) {
58
+ return null
59
+ }
60
+ if ( node . fields ) {
61
+ for ( const field of Object . keys ( node . fields ) ) {
62
+ node . fields [ field ] . types = node . fields [ field ] . types . filter ( n => n . named )
63
+ }
64
+ }
65
+ if ( node . children ) {
66
+ node . children . types = node . children . types . filter ( n => n . named )
67
+ }
68
+ if ( node . subtypes ) {
69
+ node . subtypes = node . subtypes . filter ( n => n . named )
70
+ }
71
+ return node
72
+ }
73
+
74
+ function processNodeTypes ( nodeTypes : NodeType [ ] ) : Record < string , NodeType > {
75
+ const filteredNodeTypes = nodeTypes
76
+ . map ( filterOutUnNamedNode )
77
+ . filter ( node => ! ! node )
78
+ const nodeTypeMap = Object . fromEntries (
79
+ filteredNodeTypes . map ( node => [ node . type , node ] ) ,
80
+ )
81
+ return nodeTypeMap
82
+ }
83
+
84
+ function readLangNodeTypes ( dirname : string ) : NodeType [ ] {
85
+ const staticNodePath = path . join ( dirname , 'src' , 'node-types.json' )
86
+ const content = readFileSync ( staticNodePath , 'utf-8' )
87
+ return JSON . parse ( content )
88
+ }
89
+
90
+ function generateLangNodeTypes ( setupConfig : SetupConfig ) {
91
+ const { name : lang , treeSitterPackage, dirname } = setupConfig
92
+ try {
93
+ const nodeTypes = readLangNodeTypes ( dirname )
94
+ const nodeTypeMap = processNodeTypes ( nodeTypes )
95
+ const fileContent =
96
+ `// Auto-generated from ${ treeSitterPackage } ` +
97
+ '\n' +
98
+ `type ${ lang } Types = ${ JSON . stringify ( nodeTypeMap , null , 2 ) } ;` +
99
+ '\n' +
100
+ `export default ${ lang } Types;`
101
+ writeFileSync ( path . join ( dirname , `type.d.ts` ) , fileContent )
102
+ } catch ( e ) {
103
+ console . error ( `Error while generating node types for ${ lang } ` )
104
+ throw e
25
105
}
26
106
}
0 commit comments