11import * as fs from 'fs' ;
22import { resolve } from 'path' ;
33import { 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+
67const parser = new Parser ( ) ;
78
89/**
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+ **/
1916
2017const query = new Query (
2118 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+ )
3231 )
3332 )
34- )
35- )
36- ` ,
33+ ` ,
3734) ;
35+
3836parser . setLanguage ( TS . tsx ) ;
37+
3938export default function autoAPI ( ) {
4039 return {
4140 name : 'watch-monorepo-changes' ,
@@ -49,11 +48,13 @@ export default function autoAPI() {
4948 } ,
5049 } ;
5150}
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+
5758export type ComponentParts = Record < string , SubComponents > ;
5859type SubComponents = SubComponent [ ] ;
5960export type SubComponent = Record < string , PublicType [ ] > ;
@@ -63,93 +64,119 @@ type ParsedProps = {
6364 prop : string ;
6465 type : string ;
6566} ;
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+
6693function parseSingleComponentFromDir (
6794 path : string ,
6895 ref : SubComponents ,
6996) : 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
73100 return ;
74101 }
102+ const componentName = componentNameMatch [ 1 ] ;
75103 const sourceCode = fs . readFileSync ( path , 'utf-8' ) ;
76104 const tree = parser . parse ( sourceCode ) ;
77105 const parsed : PublicType [ ] = [ ] ;
78- function topKey ( obj : { [ x : string ] : any } | undefined ) {
79- return obj ? Object . keys ( obj ) [ 0 ] : '' ;
80- }
106+
81107 const matches = query . matches ( tree . rootNode ) ;
82108 matches . forEach ( ( match ) => {
83- const last : PublicType = parsed [ parsed . length - 1 ] ;
109+ const last : PublicType | undefined = parsed [ parsed . length - 1 ] ;
84110 let subComponentName = '' ;
85111 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 ;
90116 }
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 ( ) ;
94120 parsedProps . comment = justText ;
95121 }
96-
97- if ( lol . name === 'prop' ) {
98- parsedProps . prop = lol . node . text ;
122+ if ( capture . name === 'prop' ) {
123+ parsedProps . prop = capture . node . text ;
99124 }
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 ) ;
105130 } else {
106131 parsed . push ( { [ subComponentName ] : [ parsedProps ] } ) ;
107132 }
108133 }
109134 } ) ;
110135 } ) ;
111136
112- const completeSubComponent : SubComponent = { [ component_name [ 1 ] ] : parsed } ;
137+ const completeSubComponent : SubComponent = { [ componentName ] : parsed } ;
113138 ref . push ( completeSubComponent ) ;
114139 return ref ;
115140}
116141
117142function writeToDocs ( fullPath : string , componentName : string , api : ComponentParts ) {
118143 if ( fullPath . includes ( 'kit-headless' ) ) {
119- const relDocPath = `../website/src/routes// docs/headless/${ componentName } ` ;
144+ const relDocPath = `../website/src/routes/docs/headless/${ componentName } ` ;
120145 const fullDocPath = resolve ( __dirname , relDocPath ) ;
121- const dirPath = fullDocPath . concat ( '/ auto-api') ;
146+ const dirPath = resolve ( fullDocPath , ' auto-api') ;
122147
123148 if ( ! fs . existsSync ( dirPath ) ) {
124149 fs . mkdirSync ( dirPath ) ;
125150 }
126151 const json = JSON . stringify ( api , null , 2 ) ;
127- const hacky = `export const api= ${ json } ` ;
152+ const exportedApi = `export const api = ${ json } ; ` ;
128153
129154 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! ' ) ;
132157 } catch ( err ) {
133- return ;
158+ console . error ( 'Error writing API file:' , err ) ;
134159 }
135160 }
136161}
162+
137163function 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 ) {
140166 return ;
141167 }
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 ) {
145171 return ;
146172 }
147- const componentName = componentRegex [ 1 ] ;
173+ const componentName = componentMatch [ 1 ] ;
148174 const allParts : SubComponents = [ ] ;
149175 const store : ComponentParts = { [ componentName ] : allParts } ;
176+
150177 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 ) ;
153180 parseSingleComponentFromDir ( fullPath , store [ componentName ] ) ;
154181 }
155182 } ) ;
0 commit comments