jevstrudel.git / jsdoc / undocumented.mjs
1import { readFile } from 'node:fs/promises';
2import { dirname, resolve } from 'path';
3import { fileURLToPath } from 'url';
4import { parse } from 'acorn';
5import dependencyTree from 'dependency-tree';
6const __dirname = dirname(fileURLToPath(import.meta.url));
7
8function getExports(code) {
9  // parse it with acorn
10  let ast;
11  try {
12    ast = parse(code, {
13      ecmaVersion: 11,
14      sourceType: 'module',
15    });
16  } catch (err) {
17    return [`acorn parse error: ${err.name}: ${err.messsage}`];
18  }
19
20  // find all names exported in the file
21  return ast.body
22    .filter((node) => node?.type === 'ExportNamedDeclaration')
23    .map((node) => {
24      const { declaration } = node;
25      if (!declaration) {
26        // e.g. "export { Fraction, controls }"
27        return [];
28      }
29      switch (declaration.type) {
30        case 'VariableDeclaration':
31          return declaration.declarations
32            .map((d) => {
33              switch (d.id.type) {
34                case 'Identifier':
35                  return d.id.name;
36                case 'ObjectPattern':
37                  return d.id.properties.map((p) => p.value.name);
38                default:
39                  return 'unknown declaration: ' + declaration;
40              }
41            })
42            .flat();
43        default:
44          // FunctionDeclaration, ClassDeclaration
45          return declaration.id.name;
46      }
47    })
48    .flat();
49}
50
51function isDocumented(name, docs) {
52  return docs.find(
53    (d) => d.name === name || d.tags?.find((t) => t.title === 'synonyms' && t.value.split(', ').includes(name)),
54  );
55}
56
57async function getUndocumented(path, docs) {
58  try {
59    // load the code of pattern.mjs as a string
60    const code = await readFile(path, 'utf8');
61    return getExports(code).filter((name) => !isDocumented(name, docs));
62  } catch (err) {
63    return [`parse error: ${err.name}: ${err.message}`];
64  }
65}
66
67// read doc.json file
68const { docs } = JSON.parse(await readFile(resolve(__dirname, '..', 'doc.json'), 'utf8'));
69
70const paths = dependencyTree.toList({
71  filename: 'index.mjs',
72  // filename: 'packages/core/index.mjs',
73  directory: resolve(__dirname),
74  filter: (path) => !path.includes('node_modules'),
75});
76// const paths = ['../packages/core/pattern.mjs', '../packages/core/hap.mjs'].map((rel) => resolve(__dirname, rel));
77
78const undocumented = Object.fromEntries(
79  await Promise.all(
80    paths.map(async (path) => [path.replace(resolve(__dirname, '..'), ''), await getUndocumented(path, docs)]),
81  ),
82);
83
84console.log(JSON.stringify(undocumented, null, 2));