Skip to content

Commit 7e96c3e

Browse files
committed
Remove tree-sitter-vue
tree-sitter-vue [1] is not compatible with Node.js 22. This is because it uses tree-sitter [2] v0.19.x, which does not support Node.js 22. The version of tree-sitter that works with Node.js 22 is v0.21.0 or later, as it uses the new Node-API [3] instead of NAN [4] for its Node bindings. Since tree-sitter-vue hasn't been updated to use v0.21.0+, it doesn't work with Node.js 22. There is no official grammar for Vue that uses tree-sitter v0.21+ and Node-API yet. [1] https://github.com/ikatyang/tree-sitter-vue [2] https://github.com/tree-sitter/tree-sitter [3] https://nodejs.github.io/node-addon-examples/about/what/ [4] https://github.com/nodejs/nan
1 parent f2faaa2 commit 7e96c3e

File tree

3 files changed

+110
-378
lines changed

3 files changed

+110
-378
lines changed

bin/generate-symbol-graph

Lines changed: 0 additions & 282 deletions
Original file line numberDiff line numberDiff line change
@@ -12,301 +12,19 @@
1212

1313
/* eslint-disable no-restricted-syntax, import/no-extraneous-dependencies */
1414

15-
const fs = require('fs');
16-
const path = require('path');
17-
1815
const Parser = require('tree-sitter');
1916
const JavaScript = require('tree-sitter-javascript');
2017
const JSDoc = require('tree-sitter-jsdoc');
21-
const Vue = require('tree-sitter-vue');
22-
23-
async function* find(dir, predicate = () => true) {
24-
const files = await fs.promises.readdir(dir);
25-
26-
for await (const file of files) {
27-
const fpath = path.join(dir, file);
28-
const fstat = await fs.promises.stat(fpath);
29-
30-
if (fstat.isDirectory()) {
31-
yield* find(fpath, predicate);
32-
} else if (predicate(fpath)) {
33-
yield fpath;
34-
}
35-
}
36-
}
37-
38-
async function* findVueFiles(dir) {
39-
const isVueFile = fpath => path.extname(fpath) === '.vue';
40-
yield* find(dir, isVueFile);
41-
}
42-
43-
function uniqueCaptures(query, tree) {
44-
const capturesArray = query.captures(tree.rootNode);
45-
return capturesArray.reduce((obj, capture) => ({
46-
...obj,
47-
[capture.name]: capture.node,
48-
}), {});
49-
}
50-
51-
const line = (
52-
text,
53-
range = {
54-
start: { // FIXME: use real ranges from parser in the future
55-
line: 0,
56-
character: 0,
57-
},
58-
end: {
59-
line: 0,
60-
character: 0,
61-
},
62-
},
63-
) => ({ text, range });
64-
65-
function createDocComment(descriptionNode = { text: '' }, params = []) {
66-
const lines = descriptionNode.text.split('\n').map((txt, i) => line((i === 0 ? (
67-
txt
68-
) : (
69-
// this seems like a tree-sitter-jsdoc bug with handling
70-
// multi-line descriptions
71-
txt.replace(/^\s*\*/, '')
72-
))));
73-
74-
// generate automated parameter description content that just shows the name
75-
// of each prop and its type
76-
const hasParamContent = lines.some(l => /^\s*- Parameter/.test(l.text));
77-
if (params.length && !hasParamContent) {
78-
lines.push(line(''));
79-
lines.push(line('- Parameters:'));
80-
params.forEach((param) => {
81-
lines.push(line(` - ${param.name}: \`${param.type}\``));
82-
});
83-
}
84-
85-
return { lines };
86-
}
87-
88-
const Token = {
89-
identifier: spelling => ({ kind: 'identifier', spelling }),
90-
string: spelling => ({ kind: 'string', spelling }),
91-
text: spelling => ({ kind: 'text', spelling }),
92-
typeIdentifier: spelling => ({ kind: 'typeIdentifier', spelling }),
93-
};
94-
95-
function createDeclaration(componentName, slotNames = []) {
96-
if (!slotNames.length) {
97-
return [
98-
Token.text('<'),
99-
Token.typeIdentifier(componentName),
100-
Token.text(' />'),
101-
];
102-
}
103-
const isDefault = name => name === 'default';
104-
return [
105-
Token.text('<'),
106-
Token.typeIdentifier(componentName),
107-
Token.text('>\n'),
108-
...slotNames.flatMap(name => (isDefault(name) ? ([
109-
Token.text(' <slot />\n'),
110-
]) : ([
111-
Token.text(' <slot name='),
112-
Token.string(`"${name}"`),
113-
Token.text(' />\n'),
114-
]))),
115-
Token.text('</'),
116-
Token.typeIdentifier(componentName),
117-
Token.text('>'),
118-
];
119-
}
12018

12119
(async () => {
122-
const vueParser = new Parser();
123-
vueParser.setLanguage(Vue);
124-
const scriptTextQuery = new Parser.Query(Vue,
125-
`(script_element
126-
(raw_text) @script)`);
127-
12820
const jsParser = new Parser();
12921
jsParser.setLanguage(JavaScript);
130-
const exportNameQuery = new Parser.Query(JavaScript,
131-
`(
132-
(comment)? @comment (#match? @comment "^/[*]{2}")
133-
.
134-
(export_statement
135-
(object
136-
(pair
137-
(property_identifier) @key (#eq? @key "name")
138-
.
139-
(string (string_fragment) @component)))))`);
140-
const exportPropsQuery = new Parser.Query(JavaScript,
141-
`(export_statement
142-
(object
143-
(pair
144-
(property_identifier) @key (#eq? @key "props")
145-
.
146-
(object
147-
(pair
148-
(property_identifier) @prop.name
149-
.
150-
(object
151-
(pair
152-
(property_identifier) @key2 (#eq? @key2 "type")
153-
.
154-
(_) @prop.type)))))))`);
15522

15623
const jsDocParser = new Parser();
15724
jsDocParser.setLanguage(JSDoc);
158-
const commentDescriptionQuery = new Parser.Query(JSDoc,
159-
`(document
160-
(description) @description)`);
161-
const slotsQuery = new Parser.Query(Vue,
162-
`[
163-
(self_closing_tag
164-
(tag_name) @tag
165-
(attribute
166-
(attribute_name) @attr.name
167-
(quoted_attribute_value (attribute_value) @attr.value))?
168-
(#eq? @tag "slot")
169-
(#eq? @attr.name "name"))
170-
(start_tag
171-
(tag_name) @tag
172-
(attribute
173-
(attribute_name) @attr.name
174-
(quoted_attribute_value (attribute_value) @attr.value))?
175-
(#eq? @tag "slot")
176-
(#eq? @attr.name "name"))
177-
]`);
17825

17926
const symbols = [];
18027
const relationships = [];
181-
const identifiers = new Set();
182-
183-
const rootDir = path.join(__dirname, '..');
184-
const componentsDir = path.join(rootDir, 'src/components');
185-
for await (const filepath of findVueFiles(componentsDir)) {
186-
const contents = await fs.promises.readFile(filepath, { encoding: 'utf8' });
187-
const vueTree = vueParser.parse(contents);
188-
189-
const { script } = uniqueCaptures(scriptTextQuery, vueTree);
190-
if (script) {
191-
const jsTree = jsParser.parse(script.text);
192-
193-
const { comment, component } = uniqueCaptures(exportNameQuery, jsTree);
194-
195-
if (component) {
196-
const componentName = component.text;
197-
const pathComponents = filepath
198-
.replace(componentsDir, '')
199-
.split('/')
200-
.filter(part => part.length)
201-
.map(part => path.parse(part).name);
202-
const preciseIdentifier = pathComponents.join('');
203-
204-
const subHeading = [
205-
Token.text('<'),
206-
Token.identifier(componentName),
207-
Token.text('>'),
208-
];
209-
210-
let functionSignature;
211-
const captures = exportPropsQuery.captures(jsTree.rootNode);
212-
const params = captures.reduce((memo, capture) => {
213-
if (capture.name === 'prop.name') {
214-
memo.push({ name: capture.node.text });
215-
}
216-
if (capture.name === 'prop.type') {
217-
// eslint-disable-next-line no-param-reassign
218-
memo[memo.length - 1].type = capture.node.text;
219-
}
220-
return memo;
221-
}, []);
222-
if (params.length) {
223-
// not sure if DocC actually uses `functionSignature` or not...
224-
functionSignature = {
225-
parameters: params.map(param => ({
226-
name: param.name,
227-
declarationFragments: [Token.text(param.type)],
228-
})),
229-
};
230-
}
231-
232-
// TODO: eventually we should also capture slots that are expressed in
233-
// a render function instead of the template
234-
const slots = slotsQuery.captures(vueTree.rootNode).reduce((memo, capture) => {
235-
if (capture.name === 'tag') {
236-
memo.push({ name: 'default' });
237-
}
238-
if (capture.name === 'attr.value') {
239-
// eslint-disable-next-line no-param-reassign
240-
memo[memo.length - 1].name = capture.node.text;
241-
}
242-
return memo;
243-
}, []);
244-
const slotNames = [...new Set(slots.map(slot => slot.name))];
245-
const declarationFragments = createDeclaration(componentName, slotNames);
246-
247-
let docComment;
248-
let description;
249-
if (comment) {
250-
const jsDocTree = jsDocParser.parse(comment.text);
251-
description = uniqueCaptures(commentDescriptionQuery, jsDocTree).description;
252-
}
253-
if (!!description || params.length) {
254-
docComment = createDocComment(description, params);
255-
}
256-
257-
symbols.push({
258-
accessLevel: 'public',
259-
identifier: {
260-
interfaceLanguage: 'vue',
261-
precise: preciseIdentifier,
262-
},
263-
kind: {
264-
identifier: 'class', // FIXME
265-
displayName: 'Component',
266-
},
267-
names: {
268-
title: componentName,
269-
subHeading,
270-
},
271-
pathComponents,
272-
docComment,
273-
declarationFragments,
274-
functionSignature,
275-
});
276-
identifiers.add(preciseIdentifier);
277-
}
278-
}
279-
}
280-
281-
// construct parent/child relationships and fixup the `pathComponents` for
282-
// each symbol so that it only contains items that map to real symbols (TODO:
283-
// this could probably be done in the first loop depending on the order that
284-
// `find` traverses the filesystem (breadth vs depth))
285-
for (let i = 0; i < symbols.length; i += 1) {
286-
const symbol = symbols[i];
287-
const {
288-
identifier: { precise: childIdentifier },
289-
pathComponents,
290-
} = symbol;
291-
const parentPathComponents = pathComponents.slice(0, pathComponents.length - 1);
292-
if (!parentPathComponents.length) {
293-
// eslint-disable-next-line no-continue
294-
continue;
295-
}
296-
297-
const parentIdentifier = parentPathComponents.join('');
298-
if (identifiers.has(parentIdentifier)) {
299-
relationships.push({
300-
source: childIdentifier,
301-
target: parentIdentifier,
302-
kind: 'memberOf',
303-
});
304-
} else {
305-
symbol.pathComponents = pathComponents.filter((_, j) => (
306-
identifiers.has(pathComponents.slice(0, j + 1).join(''))
307-
));
308-
}
309-
}
31028

31129
const formatVersion = {
31230
major: 0,

0 commit comments

Comments
 (0)