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' ;
6
+ const parser = new Parser ( ) ;
7
+
8
+ /**
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
+ **/
19
+
20
+ const query = new Query (
21
+ 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)
32
+ )
33
+ )
34
+ )
35
+ )
36
+ ` ,
37
+ ) ;
38
+ parser . setLanguage ( TS . tsx ) ;
4
39
export default function autoAPI ( ) {
5
40
return {
6
41
name : 'watch-monorepo-changes' ,
@@ -15,9 +50,9 @@ export default function autoAPI() {
15
50
} ;
16
51
}
17
52
// the object should have this general structure, arranged from parent to child
18
- // componentName:[subComponent,subcomponent,...]
19
- // subComponentName:[publicType,publicType,...]
20
- // publicType:[{ comment,prop,type },{ comment,prop,type },...]
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
21
56
// THEY UPPER-MOST KEY IS ALWAYS USED AS A HEADING
22
57
export type ComponentParts = Record < string , SubComponents > ;
23
58
type SubComponents = SubComponent [ ] ;
@@ -28,51 +63,57 @@ type ParsedProps = {
28
63
prop : string ;
29
64
type : string ;
30
65
} ;
31
- function parseSingleComponentFromDir ( path : string , ref : SubComponents ) {
66
+ function parseSingleComponentFromDir (
67
+ path : string ,
68
+ ref : SubComponents ,
69
+ ) : SubComponents | undefined {
32
70
const component_name = / \/ ( [ \w - ] * ) .t s x / . exec ( path ) ;
33
71
if ( component_name === null || component_name [ 1 ] === null ) {
34
72
// may need better behavior
35
73
return ;
36
74
}
37
75
const sourceCode = fs . readFileSync ( path , 'utf-8' ) ;
38
- const comments = extractPublicTypes ( sourceCode ) ;
76
+ const tree = parser . parse ( sourceCode ) ;
39
77
const parsed : PublicType [ ] = [ ] ;
40
- for ( const comment of comments ) {
41
- const api = extractComments ( comment . string ) ;
42
- const pair : PublicType = { [ comment . label ] : api } ;
43
- parsed . push ( pair ) ;
78
+ function topKey ( obj : { [ x : string ] : any } | undefined ) {
79
+ return obj ? Object . keys ( obj ) [ 0 ] : '' ;
44
80
}
81
+ const matches = query . matches ( tree . rootNode ) ;
82
+ matches . forEach ( ( match ) => {
83
+ const last : PublicType = parsed [ parsed . length - 1 ] ;
84
+ let subComponentName = '' ;
85
+ 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 ;
90
+ }
91
+ if ( lol . name === 'comment' ) {
92
+ //this removes the comment syntax
93
+ const justText = lol . node . text . replaceAll ( / [ / * ] / g, '' ) ;
94
+ parsedProps . comment = justText ;
95
+ }
96
+
97
+ if ( lol . name === 'prop' ) {
98
+ parsedProps . prop = lol . node . text ;
99
+ }
100
+
101
+ if ( lol . name === 'type' ) {
102
+ parsedProps . type = lol . node . text ;
103
+ if ( subComponentName === topKey ( last ) ) {
104
+ last [ topKey ( last ) ] . push ( parsedProps ) ;
105
+ } else {
106
+ parsed . push ( { [ subComponentName ] : [ parsedProps ] } ) ;
107
+ }
108
+ }
109
+ } ) ;
110
+ } ) ;
111
+
45
112
const completeSubComponent : SubComponent = { [ component_name [ 1 ] ] : parsed } ;
46
113
ref . push ( completeSubComponent ) ;
47
114
return ref ;
48
115
}
49
116
50
- function extractPublicTypes ( strg : string ) {
51
- const getPublicTypes = / t y p e P u b l i c ( [ A - Z ] [ \w ] * ) * [ \w \W ] * ?{ ( [ \w | \W ] * ?) } ( ; | & ) / gm;
52
- const cms = [ ] ;
53
- let groups ;
54
- while ( ( groups = getPublicTypes . exec ( strg ) ) !== null ) {
55
- const string = groups [ 2 ] ;
56
- cms . push ( { label : groups [ 1 ] , string } ) ;
57
- }
58
- return cms ;
59
- }
60
- function extractComments ( strg : string ) : ParsedProps [ ] {
61
- const magical_regex =
62
- / ^ \s * ?\/ [ * ] { 2 } \n ? ( [ \w | \W | ] * ?) \s * [ * ] { 1 , 2 } [ / ] \n [ ] * ( [ \w | \W ] * ?) : ( [ \w | \W ] * ?) ; ? $ / gm;
63
-
64
- const cms = [ ] ;
65
- let groups ;
66
-
67
- while ( ( groups = magical_regex . exec ( strg ) ) !== null ) {
68
- const trimStart = / ^ * | ( \* * ) / g;
69
- const comment = groups [ 1 ] . replaceAll ( trimStart , '' ) ;
70
- const prop = groups [ 2 ] ;
71
- const type = groups [ 3 ] ;
72
- cms . push ( { comment, prop, type } ) ;
73
- }
74
- return cms ;
75
- }
76
117
function writeToDocs ( fullPath : string , componentName : string , api : ComponentParts ) {
77
118
if ( fullPath . includes ( 'kit-headless' ) ) {
78
119
const relDocPath = `../website/src/routes//docs/headless/${ componentName } ` ;
0 commit comments