1
1
import * as fs from 'fs' ;
2
2
import { resolve } from 'path' ;
3
3
import { ViteDevServer } from 'vite' ;
4
- import { default as Parser , Query } from 'tree-sitter' ;
5
- import { default as TS } from 'tree-sitter-typescript' ;
4
+ import Parser , { Query } from 'tree-sitter' ;
5
+ import TS from 'tree-sitter-typescript' ;
6
+
6
7
const parser = new Parser ( ) ;
7
8
8
9
/**
9
- TO WHOM IT MAY CONCERN:
10
-
11
- if by some reason you need to refactor the query below and don't know where to starts, below are what I consider to be the must-know parts.
12
-
13
- 1) Tree-Sitter query docs: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
14
- 1b) Put particular attention to the following sections: capturing nodes, wildcard nodes, and anchors
15
-
16
- 2) Have a way of being able to see the tree-sitter AST in realtime. The ideal setup comes included in Neovim. In ex mode, simply run
17
- the command below and you'll have the file's AST viewer open in realtime: InspectTree
18
- **/
10
+ * Tree-Sitter query docs: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
11
+ * Pay particular attention to the following sections: capturing nodes, wildcard nodes, and anchors.
12
+ *
13
+ * To have a way of being able to see the Tree-Sitter AST in real-time: the ideal setup comes included in Neovim. In ex mode, simply run
14
+ * the command below and you'll have the file's AST viewer open in real-time: `:InspectTree`
15
+ **/
19
16
20
17
const query = new Query (
21
18
TS . tsx ,
22
- `declaration: (type_alias_declaration
23
- name: (type_identifier) @subComponentName
24
- (intersection_type
25
- (object_type
26
-
27
- (comment) @comment
28
- .
29
- (property_signature
30
- name: (_) @prop
31
- type: (type_annotation (_) @type)
19
+ `declaration:
20
+ (type_alias_declaration
21
+ name: (type_identifier) @subComponentName
22
+ (intersection_type
23
+ (object_type
24
+ (comment) @comment
25
+ .
26
+ (property_signature
27
+ name: (_) @prop
28
+ type: (type_annotation (_) @type)
29
+ )
30
+ )
32
31
)
33
32
)
34
- )
35
- )
36
- ` ,
33
+ ` ,
37
34
) ;
35
+
38
36
parser . setLanguage ( TS . tsx ) ;
37
+
39
38
export default function autoAPI ( ) {
40
39
return {
41
40
name : 'watch-monorepo-changes' ,
@@ -49,11 +48,13 @@ export default function autoAPI() {
49
48
} ,
50
49
} ;
51
50
}
52
- // the object should have this general structure, arranged from parent to child
53
- // componentName:[subComponent,subcomponent,...] & componentName comes from the dir
54
- // subComponentName/type alias:[publicType,publicType,...] & subcomponent comes from the file under dir
55
- // publicType:[{ comment,prop,type },{ comment,prop,type },...] & publicType comes from type inside file
56
- // THEY UPPER-MOST KEY IS ALWAYS USED AS A HEADING
51
+
52
+ // The object should have this general structure, arranged from parent to child
53
+ // componentName: [subComponent, subComponent, ...] & componentName comes from the directory
54
+ // subComponentName/type alias: [PublicType, PublicType, ...] & subComponent comes from the file under directory
55
+ // PublicType: [{ comment, prop, type }, { comment, prop, type }, ...] & PublicType comes from type inside file
56
+ // THE UPPER-MOST KEY IS ALWAYS USED AS A HEADING
57
+
57
58
export type ComponentParts = Record < string , SubComponents > ;
58
59
type SubComponents = SubComponent [ ] ;
59
60
export type SubComponent = Record < string , PublicType [ ] > ;
@@ -63,93 +64,119 @@ type ParsedProps = {
63
64
prop : string ;
64
65
type : string ;
65
66
} ;
67
+
68
+ /**
69
+ * Note: For this code to run, you need to prefix the type with 'Public' (e.g., 'PublicMyType') in your TypeScript files
70
+ *
71
+ * * e.g:
72
+ *
73
+ * ```tsx
74
+ * type PublicModalRootProps = {
75
+ * //blablabla
76
+ * onShow$?: QRL<() => void>;
77
+ * //blablabla
78
+ * onClose$?: QRL<() => void>;
79
+ * //blablabla
80
+ * 'bind:show'?: Signal<boolean>;
81
+ * //blablabla
82
+ * closeOnBackdropClick?: boolean;
83
+ * //blablabla
84
+ * alert?: boolean;
85
+ * };
86
+ * ```
87
+ * This convention helps the parser identify and process the public types correctly.
88
+ *
89
+ * Now when you save the corresponding .mdx file, the API will be updated accordingly.
90
+ *
91
+ **/
92
+
66
93
function parseSingleComponentFromDir (
67
94
path : string ,
68
95
ref : SubComponents ,
69
96
) : SubComponents | undefined {
70
- const component_name = / \/ ( [ \w - ] * ) .t s x / . exec ( path ) ;
71
- if ( component_name === null || component_name [ 1 ] === null ) {
72
- // may need better behavior
97
+ const componentNameMatch = / [ \\ / ] ( \w [ \w - ] * ) \ .t s x $ / . exec ( path ) ;
98
+ if ( ! componentNameMatch ) {
99
+ // May need better behavior
73
100
return ;
74
101
}
102
+ const componentName = componentNameMatch [ 1 ] ;
75
103
const sourceCode = fs . readFileSync ( path , 'utf-8' ) ;
76
104
const tree = parser . parse ( sourceCode ) ;
77
105
const parsed : PublicType [ ] = [ ] ;
78
- function topKey ( obj : { [ x : string ] : any } | undefined ) {
79
- return obj ? Object . keys ( obj ) [ 0 ] : '' ;
80
- }
106
+
81
107
const matches = query . matches ( tree . rootNode ) ;
82
108
matches . forEach ( ( match ) => {
83
- const last : PublicType = parsed [ parsed . length - 1 ] ;
109
+ const last : PublicType | undefined = parsed [ parsed . length - 1 ] ;
84
110
let subComponentName = '' ;
85
111
const parsedProps : ParsedProps = { comment : '' , prop : '' , type : '' } ;
86
- match . captures . forEach ( ( lol ) => {
87
- //statetements are ordered as they appear in capture array
88
- if ( lol . name === 'subComponentName' && subComponentName != lol . node . text ) {
89
- subComponentName = lol . node . text ;
112
+ match . captures . forEach ( ( capture ) => {
113
+ // Statements are ordered as they appear in capture array
114
+ if ( capture . name === 'subComponentName' && subComponentName !== capture . node . text ) {
115
+ subComponentName = capture . node . text ;
90
116
}
91
- if ( lol . name === 'comment' ) {
92
- //this removes the comment syntax
93
- const justText = lol . node . text . replaceAll ( / [ / * ] / g, '' ) ;
117
+ if ( capture . name === 'comment' ) {
118
+ // This removes the comment syntax
119
+ const justText = capture . node . text . replace ( / [ / * ] / g, '' ) . trim ( ) ;
94
120
parsedProps . comment = justText ;
95
121
}
96
-
97
- if ( lol . name === 'prop' ) {
98
- parsedProps . prop = lol . node . text ;
122
+ if ( capture . name === 'prop' ) {
123
+ parsedProps . prop = capture . node . text ;
99
124
}
100
-
101
- if ( lol . name === 'type' ) {
102
- parsedProps . type = lol . node . text ;
103
- if ( subComponentName === topKey ( last ) ) {
104
- last [ topKey ( last ) ] . push ( parsedProps ) ;
125
+ if ( capture . name === 'type' ) {
126
+ parsedProps . type = capture . node . text ;
127
+ const topKey = last ? Object . keys ( last ) [ 0 ] : '' ;
128
+ if ( subComponentName === topKey ) {
129
+ last [ topKey ] . push ( parsedProps ) ;
105
130
} else {
106
131
parsed . push ( { [ subComponentName ] : [ parsedProps ] } ) ;
107
132
}
108
133
}
109
134
} ) ;
110
135
} ) ;
111
136
112
- const completeSubComponent : SubComponent = { [ component_name [ 1 ] ] : parsed } ;
137
+ const completeSubComponent : SubComponent = { [ componentName ] : parsed } ;
113
138
ref . push ( completeSubComponent ) ;
114
139
return ref ;
115
140
}
116
141
117
142
function writeToDocs ( fullPath : string , componentName : string , api : ComponentParts ) {
118
143
if ( fullPath . includes ( 'kit-headless' ) ) {
119
- const relDocPath = `../website/src/routes// docs/headless/${ componentName } ` ;
144
+ const relDocPath = `../website/src/routes/docs/headless/${ componentName } ` ;
120
145
const fullDocPath = resolve ( __dirname , relDocPath ) ;
121
- const dirPath = fullDocPath . concat ( '/ auto-api') ;
146
+ const dirPath = resolve ( fullDocPath , ' auto-api') ;
122
147
123
148
if ( ! fs . existsSync ( dirPath ) ) {
124
149
fs . mkdirSync ( dirPath ) ;
125
150
}
126
151
const json = JSON . stringify ( api , null , 2 ) ;
127
- const hacky = `export const api= ${ json } ` ;
152
+ const exportedApi = `export const api = ${ json } ; ` ;
128
153
129
154
try {
130
- fs . writeFileSync ( dirPath . concat ( '/ api.ts') , hacky ) ;
131
- console . log ( 'auto-api: succesfully genereated new json!!! :) ' ) ;
155
+ fs . writeFileSync ( resolve ( dirPath , ' api.ts') , exportedApi ) ;
156
+ console . log ( 'auto-api: successfully generated new JSON! ' ) ;
132
157
} catch ( err ) {
133
- return ;
158
+ console . error ( 'Error writing API file:' , err ) ;
134
159
}
135
160
}
136
161
}
162
+
137
163
function loopOnAllChildFiles ( filePath : string ) {
138
- const childComponentRegex = / \/ ( [ \w - ] * ) .t s x $ / . exec ( filePath ) ;
139
- if ( childComponentRegex === null ) {
164
+ const childComponentMatch = / [ \\ / ] ( \w [ \w - ] * ) \ .t s x $ / . exec ( filePath ) ;
165
+ if ( ! childComponentMatch ) {
140
166
return ;
141
167
}
142
- const parentDir = filePath . replace ( childComponentRegex [ 0 ] , '' ) ;
143
- const componentRegex = / \/ ( \w * ) $ / . exec ( parentDir ) ;
144
- if ( ! fs . existsSync ( parentDir ) || componentRegex == null ) {
168
+ const parentDir = filePath . replace ( childComponentMatch [ 0 ] , '' ) ;
169
+ const componentMatch = / [ \\ / ] ( \w + ) $ / . exec ( parentDir ) ;
170
+ if ( ! fs . existsSync ( parentDir ) || ! componentMatch ) {
145
171
return ;
146
172
}
147
- const componentName = componentRegex [ 1 ] ;
173
+ const componentName = componentMatch [ 1 ] ;
148
174
const allParts : SubComponents = [ ] ;
149
175
const store : ComponentParts = { [ componentName ] : allParts } ;
176
+
150
177
fs . readdirSync ( parentDir ) . forEach ( ( fileName ) => {
151
- if ( / t s x $ / . test ( fileName ) ) {
152
- const fullPath = parentDir + '/' + fileName ;
178
+ if ( / \. t s x $ / . test ( fileName ) ) {
179
+ const fullPath = resolve ( parentDir , fileName ) ;
153
180
parseSingleComponentFromDir ( fullPath , store [ componentName ] ) ;
154
181
}
155
182
} ) ;
0 commit comments