Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
71a1420
Update Node.js & NPM in project's engines
marinaaisa Jul 2, 2025
4660816
Update @vue/test-utils
marinaaisa Jul 3, 2025
e18bfac
Update tree-sitter-vue
marinaaisa Jul 3, 2025
dd6180a
Fix LanguageSwitcher.spec.js
marinaaisa Jul 14, 2025
f938323
Fix Column.spec.js
marinaaisa Jul 14, 2025
876545a
Fix Hero.spec.js
marinaaisa Jul 14, 2025
292b68e
Fix ColorSchemeToggle.spec.js
marinaaisa Jul 4, 2025
bfd0712
Fix ImageAsset.spec.js
marinaaisa Jul 4, 2025
3bef585
Fix Pager.spec.js
marinaaisa Jul 4, 2025
96d0c86
Fix Assessments.spec.js
marinaaisa Jul 4, 2025
0b093e6
Fix swift.spec.js
marinaaisa Jul 7, 2025
69cdb71
Fix FilterInput.spec.js
marinaaisa Jul 7, 2025
b7e29da
Replace wrapper.find
marinaaisa Jul 8, 2025
0b20a9c
Wait for wrapper.setProps
marinaaisa Jul 8, 2025
0bf9cdb
Wait for wrapper.setData
marinaaisa Jul 8, 2025
32cd38c
Await nextTick after trigger function
marinaaisa Jul 8, 2025
d5b10f9
Fix Navigator.spec.js
marinaaisa Jul 8, 2025
48f12ba
Fix ReplayableVideoAsset.spec.js
marinaaisa Jul 8, 2025
6d98b42
Replace 'Contains'
marinaaisa Jul 8, 2025
4cc85fe
Wait for nextTick after emitting an event
marinaaisa Jul 8, 2025
5bf2a80
Fix TopicsLinkCardGridItem.spec.js
marinaaisa Jul 9, 2025
a1eea86
Fix components/DocumentationTopic.spec.js
marinaaisa Jul 9, 2025
1975189
Fix Chapter.spec.js
marinaaisa Jul 9, 2025
f42bbb3
Fix AdjustableSidebarWidth.spec.js
marinaaisa Jul 9, 2025
3f06424
Fix Availability.spec.js
marinaaisa Jul 9, 2025
0be936e
Fix Hierarchy.spec.js
marinaaisa Jul 9, 2025
23d2032
Fix NavBase.spec.js
marinaaisa Jul 9, 2025
617ae25
Fix DocumentationLayout.spec.js
marinaaisa Jul 9, 2025
ba2a67e
Fix TopicsLinkBlock.spec.js
marinaaisa Jul 9, 2025
218a88b
Fix CodePreview.spec.js
marinaaisa Jul 9, 2025
78b73a6
Fix App.spec.js
marinaaisa Jul 9, 2025
5aeea0d
Fix DropdownCustom.spec.js
marinaaisa Jul 9, 2025
7bf8f94
Wait for .setValue()
marinaaisa Jul 9, 2025
838966a
Fix GenericModal.spec.js
marinaaisa Jul 9, 2025
a4e5f3e
Fix LinksBlock.spec.js
marinaaisa Jul 9, 2025
367878a
Fix ResourcesTile.spec.js
marinaaisa Jul 9, 2025
78877ea
Fix RelationshipsList.spec.js
marinaaisa Jul 9, 2025
13fddb6
Fix all deprecated .is() method warnings
marinaaisa Jul 11, 2025
c3ab58f
Fix .find() and .get() deprecation warnings
marinaaisa Jul 11, 2025
578c532
Fix .findAll() deprecation warnings
marinaaisa Jul 11, 2025
38523db
Fix .contains() deprecation warnings
marinaaisa Jul 11, 2025
a7c67c7
Fix .isEmpty() deprecation warnings
marinaaisa Jul 11, 2025
c37b5eb
Fix attachToDocument deprecation warnings
marinaaisa Jul 11, 2025
da3f2ec
Fix QuickNavigationModal.spec.js
marinaaisa Jul 11, 2025
75a36f3
Fix console error warnings for TopicsLinkBlock.spec.js
marinaaisa Jul 11, 2025
28340ca
Fix DocumentationTopic.spec.js
marinaaisa Jul 11, 2025
ca3acee
Fix ResourcesTileGroup.spec.js
marinaaisa Jul 11, 2025
954b484
Fix DeclarationTokenGroup.spec.js
marinaaisa Jul 14, 2025
5844d91
Remove redundant nextTick
marinaaisa Jul 24, 2025
a99139d
Update tree-sitter-javascript and tree-sitter-jsdoc
marinaaisa Jul 25, 2025
d801157
Remove tree-sitter-vue
marinaaisa Jul 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.16.1
22.17.0
282 changes: 0 additions & 282 deletions bin/generate-symbol-graph
Original file line number Diff line number Diff line change
Expand Up @@ -12,301 +12,19 @@

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

const fs = require('fs');
const path = require('path');

const Parser = require('tree-sitter');
const JavaScript = require('tree-sitter-javascript');
const JSDoc = require('tree-sitter-jsdoc');
const Vue = require('tree-sitter-vue');

async function* find(dir, predicate = () => true) {
const files = await fs.promises.readdir(dir);

for await (const file of files) {
const fpath = path.join(dir, file);
const fstat = await fs.promises.stat(fpath);

if (fstat.isDirectory()) {
yield* find(fpath, predicate);
} else if (predicate(fpath)) {
yield fpath;
}
}
}

async function* findVueFiles(dir) {
const isVueFile = fpath => path.extname(fpath) === '.vue';
yield* find(dir, isVueFile);
}

function uniqueCaptures(query, tree) {
const capturesArray = query.captures(tree.rootNode);
return capturesArray.reduce((obj, capture) => ({
...obj,
[capture.name]: capture.node,
}), {});
}

const line = (
text,
range = {
start: { // FIXME: use real ranges from parser in the future
line: 0,
character: 0,
},
end: {
line: 0,
character: 0,
},
},
) => ({ text, range });

function createDocComment(descriptionNode = { text: '' }, params = []) {
const lines = descriptionNode.text.split('\n').map((txt, i) => line((i === 0 ? (
txt
) : (
// this seems like a tree-sitter-jsdoc bug with handling
// multi-line descriptions
txt.replace(/^\s*\*/, '')
))));

// generate automated parameter description content that just shows the name
// of each prop and its type
const hasParamContent = lines.some(l => /^\s*- Parameter/.test(l.text));
if (params.length && !hasParamContent) {
lines.push(line(''));
lines.push(line('- Parameters:'));
params.forEach((param) => {
lines.push(line(` - ${param.name}: \`${param.type}\``));
});
}

return { lines };
}

const Token = {
identifier: spelling => ({ kind: 'identifier', spelling }),
string: spelling => ({ kind: 'string', spelling }),
text: spelling => ({ kind: 'text', spelling }),
typeIdentifier: spelling => ({ kind: 'typeIdentifier', spelling }),
};

function createDeclaration(componentName, slotNames = []) {
if (!slotNames.length) {
return [
Token.text('<'),
Token.typeIdentifier(componentName),
Token.text(' />'),
];
}
const isDefault = name => name === 'default';
return [
Token.text('<'),
Token.typeIdentifier(componentName),
Token.text('>\n'),
...slotNames.flatMap(name => (isDefault(name) ? ([
Token.text(' <slot />\n'),
]) : ([
Token.text(' <slot name='),
Token.string(`"${name}"`),
Token.text(' />\n'),
]))),
Token.text('</'),
Token.typeIdentifier(componentName),
Token.text('>'),
];
}

(async () => {
const vueParser = new Parser();
vueParser.setLanguage(Vue);
const scriptTextQuery = new Parser.Query(Vue,
`(script_element
(raw_text) @script)`);

const jsParser = new Parser();
jsParser.setLanguage(JavaScript);
const exportNameQuery = new Parser.Query(JavaScript,
`(
(comment)? @comment (#match? @comment "^/[*]{2}")
.
(export_statement
(object
(pair
(property_identifier) @key (#eq? @key "name")
.
(string (string_fragment) @component)))))`);
const exportPropsQuery = new Parser.Query(JavaScript,
`(export_statement
(object
(pair
(property_identifier) @key (#eq? @key "props")
.
(object
(pair
(property_identifier) @prop.name
.
(object
(pair
(property_identifier) @key2 (#eq? @key2 "type")
.
(_) @prop.type)))))))`);

const jsDocParser = new Parser();
jsDocParser.setLanguage(JSDoc);
const commentDescriptionQuery = new Parser.Query(JSDoc,
`(document
(description) @description)`);
const slotsQuery = new Parser.Query(Vue,
`[
(self_closing_tag
(tag_name) @tag
(attribute
(attribute_name) @attr.name
(quoted_attribute_value (attribute_value) @attr.value))?
(#eq? @tag "slot")
(#eq? @attr.name "name"))
(start_tag
(tag_name) @tag
(attribute
(attribute_name) @attr.name
(quoted_attribute_value (attribute_value) @attr.value))?
(#eq? @tag "slot")
(#eq? @attr.name "name"))
]`);

const symbols = [];
const relationships = [];
const identifiers = new Set();

const rootDir = path.join(__dirname, '..');
const componentsDir = path.join(rootDir, 'src/components');
for await (const filepath of findVueFiles(componentsDir)) {
const contents = await fs.promises.readFile(filepath, { encoding: 'utf8' });
const vueTree = vueParser.parse(contents);

const { script } = uniqueCaptures(scriptTextQuery, vueTree);
if (script) {
const jsTree = jsParser.parse(script.text);

const { comment, component } = uniqueCaptures(exportNameQuery, jsTree);

if (component) {
const componentName = component.text;
const pathComponents = filepath
.replace(componentsDir, '')
.split('/')
.filter(part => part.length)
.map(part => path.parse(part).name);
const preciseIdentifier = pathComponents.join('');

const subHeading = [
Token.text('<'),
Token.identifier(componentName),
Token.text('>'),
];

let functionSignature;
const captures = exportPropsQuery.captures(jsTree.rootNode);
const params = captures.reduce((memo, capture) => {
if (capture.name === 'prop.name') {
memo.push({ name: capture.node.text });
}
if (capture.name === 'prop.type') {
// eslint-disable-next-line no-param-reassign
memo[memo.length - 1].type = capture.node.text;
}
return memo;
}, []);
if (params.length) {
// not sure if DocC actually uses `functionSignature` or not...
functionSignature = {
parameters: params.map(param => ({
name: param.name,
declarationFragments: [Token.text(param.type)],
})),
};
}

// TODO: eventually we should also capture slots that are expressed in
// a render function instead of the template
const slots = slotsQuery.captures(vueTree.rootNode).reduce((memo, capture) => {
if (capture.name === 'tag') {
memo.push({ name: 'default' });
}
if (capture.name === 'attr.value') {
// eslint-disable-next-line no-param-reassign
memo[memo.length - 1].name = capture.node.text;
}
return memo;
}, []);
const slotNames = [...new Set(slots.map(slot => slot.name))];
const declarationFragments = createDeclaration(componentName, slotNames);

let docComment;
let description;
if (comment) {
const jsDocTree = jsDocParser.parse(comment.text);
description = uniqueCaptures(commentDescriptionQuery, jsDocTree).description;
}
if (!!description || params.length) {
docComment = createDocComment(description, params);
}

symbols.push({
accessLevel: 'public',
identifier: {
interfaceLanguage: 'vue',
precise: preciseIdentifier,
},
kind: {
identifier: 'class', // FIXME
displayName: 'Component',
},
names: {
title: componentName,
subHeading,
},
pathComponents,
docComment,
declarationFragments,
functionSignature,
});
identifiers.add(preciseIdentifier);
}
}
}

// construct parent/child relationships and fixup the `pathComponents` for
// each symbol so that it only contains items that map to real symbols (TODO:
// this could probably be done in the first loop depending on the order that
// `find` traverses the filesystem (breadth vs depth))
for (let i = 0; i < symbols.length; i += 1) {
const symbol = symbols[i];
const {
identifier: { precise: childIdentifier },
pathComponents,
} = symbol;
const parentPathComponents = pathComponents.slice(0, pathComponents.length - 1);
if (!parentPathComponents.length) {
// eslint-disable-next-line no-continue
continue;
}

const parentIdentifier = parentPathComponents.join('');
if (identifiers.has(parentIdentifier)) {
relationships.push({
source: childIdentifier,
target: parentIdentifier,
kind: 'memberOf',
});
} else {
symbol.pathComponents = pathComponents.filter((_, j) => (
identifiers.has(pathComponents.slice(0, j + 1).join(''))
));
}
}

const formatVersion = {
major: 0,
Expand Down
2 changes: 1 addition & 1 deletion build-script-helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def ensure_npm_is_installed(verbose=False):
try:
node_version = check_output(['node', '--version'], verbose=verbose)
if not node_version.startswith('v18.16.'):
warn_msg = "Unexpected version of 'node' installed. Swift-DocC-Render requires node 18.16.1. "\
warn_msg = "Unexpected version of 'node' installed. Swift-DocC-Render requires node 22.17.0. "\
"See the README.md file for more information about building Swift-DocC-Render."
printerr('-- Warning: %s' % warn_msg)
except:
Expand Down
Loading