Skip to content

Commit f316678

Browse files
author
andy.patterson
committed
docs: add initial documentation
1 parent 04f9247 commit f316678

21 files changed

+471
-104
lines changed

README.md

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# validtyped
2+
[![Build Status](https://travis-ci.org/andnp/ValidTyped.svg?branch=master)](https://travis-ci.org/andnp/ValidTyped)
3+
4+
A runtime and compile-time type checker library.
5+
6+
---
7+
8+
9+
10+
### any
11+
12+
### array
13+
14+
### boolean
15+
16+
### intersect
17+
18+
### nominal
19+
20+
### number
21+
22+
### object
23+
24+
### partial
25+
26+
### record
27+
28+
### string
29+
Creates a validator instance that is true when the given type is a string.
30+
31+
| Param | Description |
32+
| --- | --- |
33+
| union | [optional] an array of possible string literals that the valid data could take. |
34+
35+
### union
36+
37+
### Validator
38+
A `Valdiator<T>` instance is an encapsulated pair of some TS type `T` and a corresponding JSON schema.
39+
40+
A `Validator` already knows how to map from `T` to its json schema: using a json schema validator.
41+
Additionally, a `Validator` can perform simple algebraic operations with other `Validator`s resulting in more complex validatable types.
42+
43+
A `Validator` will always maintain a valid json schema representation of the type, `T`, that it encapsulates.
44+
The underlying json schema can always be accessed with `getSchema()`.
45+
#### Example
46+
```typescript
47+
const stringValidator = new Validator<string>({ type: 'string' });
48+
const numberValidator = new Validator<number>({ type: 'number' });
49+
const strOrNum = new Validator<string | number>({ oneOf: [ { type: 'string' }, { type: 'number' } ]});
50+
const strOrNum2 = stringValidator.or(numberValidator);
51+
```
52+
53+
### Validator.and
54+
Creates a new validator that is true whenever the data matches `this` _and_ `v`.
55+
56+
| Param | Description |
57+
| --- | --- |
58+
| v | Another validator instance whose type will form an intersection with `this` encapsulated type. |
59+
#### Example
60+
```typescript
61+
import * as v from 'validtyped';
62+
63+
const v1 = v.object({ a: v.string() });
64+
const v2 = v.object({ b: v.number() });
65+
const v3 = v.object({ c: v.boolean() });
66+
67+
const and = v1.and(v2).and(v3); // { a: string, b: number, c: boolean }
68+
```
69+
70+
### Validator.getSchema
71+
Returns the underlying JSON schema.
72+
73+
### Validator.isValid
74+
Predicate returning `true` if the given data matches the JSON schema.
75+
Acts as a type guard for the encapsulated typescript type.
76+
77+
| Param | Description |
78+
| --- | --- |
79+
| thing | Any data of unknown type which will be validated. |
80+
81+
### Validator.or
82+
Creates a new validator that is true whenever the data matches `this` _or_ `v`.
83+
84+
| Param | Description |
85+
| --- | --- |
86+
| v | Another validator instance whose type will form a union with `this` encapsulated type. |
87+
88+
### Validator.setSchemaMetaData
89+
Add meta-data to the underlying JSON schema.
90+
This is entirely un-observable information to the validator,
91+
but supplies the `getSchema` method to have a complete JSON schema with meta-data annotations.
92+
93+
| Param | Description |
94+
| --- | --- |
95+
| meta | JSON schema meta-data (name, description, etc.) |
96+
97+
### Validator.validate
98+
Takes data of unknown type and returns a discriminated union with either the data as the valid type,
99+
or an error object describing what part of the data did not match.
100+
101+
| Param | Description |
102+
| --- | --- |
103+
| data | Any data of unknown type which will be validated. |
104+
105+
### ValidType
106+

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"main": "dist/src/index.js",
66
"types": "dist/src/index.d.ts",
77
"scripts": {
8+
"doc": "ts-node scripts/generateDocumentation.ts > README.md",
89
"commitmsg": "commitlint -e $GIT_PARAMS",
910
"lint": "tslint --config tslint.json --project . --format stylish",
1011
"test": "jest",
@@ -32,6 +33,7 @@
3233
"devDependencies": {
3334
"@commitlint/config-conventional": "^7.0.0",
3435
"@types/jest": "~23.0.0",
36+
"@types/node": "^10.3.5",
3537
"commitlint": "^7.0.0",
3638
"husky": "^0.14.3",
3739
"jest": "^22.4.3",

scripts/generateDocumentation.ts

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// tslint:disable no-console
2+
import * as tsc from 'typescript';
3+
4+
interface TypeInfo {
5+
typeName: string;
6+
parameterDocs: Array<{ name: string, text: string }>;
7+
returnDocs?: string;
8+
example?: string;
9+
description: string;
10+
fileName: string;
11+
}
12+
13+
const tsConfig = {
14+
baseUrl: "src/",
15+
};
16+
17+
const files = [
18+
{ path: 'src/index.ts' },
19+
];
20+
21+
const classIsExported = (node: tsc.Node): node is tsc.ClassDeclaration => {
22+
return !!node.parent && isNodeExported(node.parent) && tsc.isClassDeclaration(node.parent);
23+
};
24+
25+
const buildTypeInfoFromNode = (fileName: string, checker: tsc.TypeChecker, node: tsc.Node, parentName?: string): TypeInfo | undefined => {
26+
if (!isNodeExported(node) && !classIsExported(node)) return; // we only need to document exported types
27+
if (!(tsc.isTypeAliasDeclaration(node) || tsc.isFunctionDeclaration(node) || tsc.isClassDeclaration(node) || tsc.isMethodDeclaration(node))) return; // we only need to document types, functions, and classes
28+
29+
const symbol = checker.getSymbolAtLocation(node.name!);
30+
if (!symbol) return; // we should never get into this state because we aren't dealing with .d.ts files
31+
32+
// Get the JSDoc description
33+
const description = tsc.displayPartsToString(symbol.getDocumentationComment(checker));
34+
35+
// Don't document things with `no-doc` at start of description
36+
if (description.trim().startsWith('no-doc')) return;
37+
38+
const typeName = parentName ? `${parentName}.${symbol.name}` : symbol.name;
39+
40+
const jsDocTags = symbol.getJsDocTags();
41+
42+
const parameterDocs = jsDocTags
43+
.filter(tag => tag.name === 'param')
44+
.map(tag => tag.text)
45+
.filter(text => !!text)
46+
.map(text => {
47+
const [ name, ...docWords] = text!.split(' ');
48+
return {
49+
name,
50+
text: docWords.join(' '),
51+
};
52+
});
53+
54+
const returnDocs = jsDocTags
55+
.filter(tag => tag.name === 'returns')
56+
.map(tag => tag.text)
57+
.filter(text => !!text);
58+
59+
const example = jsDocTags
60+
.filter(tag => tag.name === 'example')
61+
.map(tag => tag.text)
62+
.filter(text => !!text)[0];
63+
64+
const typeInfo: TypeInfo = {
65+
typeName,
66+
parameterDocs,
67+
returnDocs: returnDocs[0],
68+
example,
69+
description,
70+
fileName,
71+
};
72+
73+
return typeInfo;
74+
};
75+
76+
const generateMarkdown = (fileName: string) => {
77+
const program = tsc.createProgram([fileName], tsConfig);
78+
const checker = program.getTypeChecker();
79+
80+
for (const sourceFile of program.getSourceFiles()) {
81+
if (sourceFile.fileName !== fileName) continue; // we don't care about imported source files, only the single file we are inspecting
82+
83+
const sourceDocs = [] as TypeInfo[];
84+
85+
tsc.forEachChild(sourceFile, (node) => {
86+
const typeInfo = buildTypeInfoFromNode(fileName, checker, node);
87+
88+
if (typeInfo) sourceDocs.push(typeInfo);
89+
90+
if (tsc.isClassDeclaration(node) && typeInfo) {
91+
// iterate over class members
92+
node.members
93+
.map((node) => buildTypeInfoFromNode(fileName, checker, node, typeInfo.typeName))
94+
.forEach(typeInfo => typeInfo && sourceDocs.push(typeInfo));
95+
96+
}
97+
});
98+
99+
// drop any repeated definitions (for overloaded functions)
100+
const filteredSourceDocs = sourceDocs
101+
.reduce((coll, item) => {
102+
const exists = coll.find(s => s.typeName === item.typeName);
103+
if (exists) return coll;
104+
105+
return [...coll, item];
106+
}, [] as TypeInfo[])
107+
.sort((a, b) => a.typeName.localeCompare(b.typeName));
108+
109+
const renderedSections = filteredSourceDocs
110+
.map(typeInfo => {
111+
const header = `### ${typeInfo.typeName}`;
112+
const description = `${typeInfo.description}`;
113+
114+
const parameters = typeInfo.parameterDocs.map(param => {
115+
return `| ${param.name} | ${param.text} |`;
116+
}).join('\n');
117+
118+
const table = typeInfo.parameterDocs.length > 0 ?
119+
`\n| Param | Description |\n| --- | --- |\n${parameters}`
120+
: '';
121+
122+
const example = typeInfo.example
123+
? `#### Example\n ${typeInfo.example}`
124+
: '';
125+
126+
const parts = [
127+
header,
128+
description,
129+
table,
130+
example,
131+
].filter(part => !!part);
132+
133+
return parts.join('\n');
134+
});
135+
136+
const markdown = renderedSections.join('\n\n');
137+
138+
return {
139+
markdown,
140+
};
141+
}
142+
};
143+
144+
const headerString =
145+
`# validtyped
146+
[![Build Status](https://travis-ci.org/andnp/ValidTyped.svg?branch=master)](https://travis-ci.org/andnp/ValidTyped)
147+
148+
A runtime and compile-time type checker library.
149+
150+
---
151+
152+
153+
154+
`;
155+
156+
const docString = files.map(file => {
157+
const doc = generateMarkdown(file.path);
158+
159+
if (!doc) return console.log(file);
160+
161+
return `${doc.markdown}\n`;
162+
}).join('\n');
163+
164+
const markdown = headerString + docString;
165+
console.log(markdown);
166+
167+
168+
/** True if this is visible outside this file, false otherwise */
169+
function isNodeExported(node: tsc.Node): boolean {
170+
return (tsc.getCombinedModifierFlags(node) & tsc.ModifierFlags.Export) !== 0 || (!!node.parent && node.parent.kind === tsc.SyntaxKind.SourceFile); // tslint:disable-line no-bitwise
171+
}

0 commit comments

Comments
 (0)