-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbuild.mts
156 lines (139 loc) · 4.4 KB
/
build.mts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import ts from 'typescript';
import fse from "fs-extra";
type Module = {
name: string
types: Type[]
}
type Type = {
name: string
description: string
tests: Test[]
}
type Test = {
expression: string
value: string
}
async function main() {
const modules = await loadModules()
await fse.remove('docs')
await fse.mkdir('docs')
await generateReadme(modules)
await Promise.all(modules.map(generateDocs))
}
main()
async function loadModules() {
const names = await getModulesNames()
return Promise.all(names.map(loadModule))
}
async function generateReadme(modules: Module[]) {
let content = await fse.readFile('src/README.md', 'utf-8')
const reference = modules.map(m => {
return [
`- [${m.name}](https://github.com/webNeat/just-types/blob/main/docs/${m.name}.md)`,
... m.types.map(t => ` - [${t.name}](https://github.com/webNeat/just-types/blob/main/docs/${m.name}.md#${t.name.toLowerCase()})`)
].join(`\n`)
}).join(`\n\n`)
content = content.replace('{{types-reference}}', reference)
await fse.writeFile('README.md', content);
}
async function generateDocs({name, types}: Module) {
const content = [
`# ${name}`,
`## Table Of Contents`,
types.map(x => `- [${x.name}](#${x.name.toLowerCase()})`).join(`\n`),
... types.map(x => generateTypeDocs(name, x))
].join(`\n\n`)
await fse.writeFile(`docs/${name}.md`, content)
}
function generateTypeDocs(mod: string, {name, description, tests}: Type) {
return [
`## ${name}`,
description,
"```ts",
`import {${name}} from 'just-types/${mod}'`,
`// or`,
`import {${mod}} from 'just-types' // and use ${mod}.${name}\n`,
... tests.map(x => `${x.expression} //=> ${commentAdditionalLines(x.value)}`),
"```",
].join(`\n`)
}
async function loadModule(name: string) {
const types: Type[] = []
for (const filename of await getTypesFilenames(name)) {
types.push(await loadType(name, filename) as any)
}
return {name, types}
}
async function loadType(moduleName: string, filename: string) {
const {description, tests} = await parseFile(`src/${moduleName}/${filename}`)
return {
name: filename.split('.')[0],
description,
tests
}
}
async function getModulesNames() {
const entries = await fse.readdir('src', {withFileTypes: true})
return entries.filter(x => x.isDirectory()).map(x => x.name)
}
async function getTypesFilenames(module: string) {
const entries = await fse.readdir(`src/${module}`, {withFileTypes: true})
return entries.filter(x => x.isFile() && x.name !== 'index.ts').map(x => x.name)
}
async function parseFile(filePath: string) {
try {
const content = await fse.readFile(filePath, 'utf-8')
const sourceFile = ts.createSourceFile('temp.ts', content, 99, true, 3)
let description = ''
let tests: Test[] = []
const visit = (node: ts.Node) => {
if (isExportedType(node)) description = extractDescription(node)
if (isTestsType(node)) tests = extractTests(node.type as any)
ts.forEachChild(node, visit)
}
visit(sourceFile)
return {description, tests}
} catch (err) {
console.error(`Error parsing file ${filePath}:`, err)
return {description: '', tests: []}
}
}
function isExportedType(node: ts.Node) {
return (
ts.isTypeAliasDeclaration(node) &&
ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export &&
(node as any).jsDoc && (node as any).jsDoc.length > 0
);
}
function isTestsType(node: ts.Node): node is ts.TypeAliasDeclaration {
return (
ts.isTypeAliasDeclaration(node) &&
node.name.text === 'Tests' &&
ts.isTupleTypeNode(node.type)
);
}
function extractDescription(node: any) {
return node.jsDoc.map((x: any) => x.comment).join(`\n`)
}
function extractTests(node: ts.TupleTypeNode) {
return node.elements.map(e => {
const children = e.getChildAt(2).getChildAt(0).getChildAt(2).getChildren()
return {
expression: unindentCode(children[0].getFullText()),
value: unindentCode(children[2].getFullText())
}
})
}
function unindentCode(text: string) {
while(text[0] === `\n`) text = text.slice(1)
let i = 0
while (i < text.length && text.charAt(i) === ' ') i ++
const spaces = i
return text.split(`\n`).map(line => {
if (line.slice(0, spaces).trim() === '') return line.slice(spaces)
return line.trimStart()
}).join(`\n`)
}
function commentAdditionalLines(text: string) {
return text.split(`\n`).map(line => `// ${line}`).join(`\n`).slice(3)
}