Skip to content

Commit f366873

Browse files
authored
Add tree sitter to auto api (#984)
* feat(auto-api): adds tree sitter & query working tree-sitter instance and finally figured out how to query * feat(auto-api): saves tree-sitter query as obj * fix(auto-api): general minor improvements Adds better comments and rms console logs * feat(auto api): use AST instead of regex Adds tree-sitter to parse and query the file's AST instead of relying on regex. Unfortunately, because type declarations are object-like and type declarations can have have objects, regex cannot handle the recursive nature of TS types. An oversight that has been corrected. * fix(auto api): removes comment syntax from table text * fix(auto api): adds dependencies
1 parent 35c923e commit f366873

File tree

3 files changed

+8332
-10064
lines changed

3 files changed

+8332
-10064
lines changed

apps/website/auto-api.ts

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,41 @@
11
import * as fs from 'fs';
22
import { resolve } from 'path';
33
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);
439
export default function autoAPI() {
540
return {
641
name: 'watch-monorepo-changes',
@@ -15,9 +50,9 @@ export default function autoAPI() {
1550
};
1651
}
1752
// 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
2156
// THEY UPPER-MOST KEY IS ALWAYS USED AS A HEADING
2257
export type ComponentParts = Record<string, SubComponents>;
2358
type SubComponents = SubComponent[];
@@ -28,51 +63,57 @@ type ParsedProps = {
2863
prop: string;
2964
type: string;
3065
};
31-
function parseSingleComponentFromDir(path: string, ref: SubComponents) {
66+
function parseSingleComponentFromDir(
67+
path: string,
68+
ref: SubComponents,
69+
): SubComponents | undefined {
3270
const component_name = /\/([\w-]*).tsx/.exec(path);
3371
if (component_name === null || component_name[1] === null) {
3472
// may need better behavior
3573
return;
3674
}
3775
const sourceCode = fs.readFileSync(path, 'utf-8');
38-
const comments = extractPublicTypes(sourceCode);
76+
const tree = parser.parse(sourceCode);
3977
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] : '';
4480
}
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+
45112
const completeSubComponent: SubComponent = { [component_name[1]]: parsed };
46113
ref.push(completeSubComponent);
47114
return ref;
48115
}
49116

50-
function extractPublicTypes(strg: string) {
51-
const getPublicTypes = /type Public([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-
}
76117
function writeToDocs(fullPath: string, componentName: string, api: ComponentParts) {
77118
if (fullPath.includes('kit-headless')) {
78119
const relDocPath = `../website/src/routes//docs/headless/${componentName}`;

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@
119119
"tailwind-merge": "^2.3.0",
120120
"tailwindcss": "^3.4.3",
121121
"tailwindcss-animate": "^1.0.7",
122+
"tree-sitter": "0.21.1",
123+
"tree-sitter-typescript": "0.23.0",
122124
"ts-jest": "^29.1.3",
123125
"tslib": "^2.6.2",
124126
"typescript": "5.4.5",

0 commit comments

Comments
 (0)