Skip to content

Commit 11e3783

Browse files
committed
Implement handling of remapping unresolved types
1 parent 235e064 commit 11e3783

15 files changed

+350
-21
lines changed

example/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
"license": "ISC",
1313
"devDependencies": {},
1414
"dependencies": {
15-
"typedoc-plugin-typescript-declaration": "^0.2.7"
15+
"typedoc-plugin-typescript-declaration": "^0.2.9"
1616
}
1717
}

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "typedoc-plugin-typescript-declaration",
3-
"version": "0.2.8",
3+
"version": "0.2.9",
44
"description": "Typedoc plugin to render to typescript declaration file",
55
"main": "dist/index.js",
66
"bin": {
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import {
2+
ArrayType,
3+
ConditionalType,
4+
IndexedAccessType,
5+
IntersectionType,
6+
IntrinsicType,
7+
PredicateType,
8+
ProjectReflection,
9+
ReferenceType,
10+
Reflection,
11+
ReflectionKind,
12+
SignatureReflection,
13+
TupleType,
14+
Type,
15+
TypeOperatorType,
16+
UnionType,
17+
UnknownType,
18+
} from 'typedoc/dist/lib/models';
19+
import { DeclarationReflection } from 'typedoc';
20+
21+
export default class UnresolvedTypesMapper {
22+
private _project: ProjectReflection;
23+
private _knownReflectionNames: Set<string>;
24+
private _knownReflections: Set<Reflection>;
25+
private _currentReflections: Set<Reflection>;
26+
27+
constructor(project: ProjectReflection) {
28+
this._project = project;
29+
this._knownReflectionNames = new Set<string>();
30+
this._knownReflections = new Set<Reflection>();
31+
this._currentReflections = new Set<Reflection>();
32+
}
33+
34+
public clearKnownReflections() {
35+
this._knownReflectionNames.clear();
36+
this._knownReflections.clear();
37+
}
38+
39+
public registerKnownReflection(reflection: Reflection) {
40+
this._knownReflections.add(reflection);
41+
if (reflection.kindOf(ReflectionKind.ClassOrInterface | ReflectionKind.TypeAlias | ReflectionKind.SomeModule)) {
42+
this._knownReflectionNames.add(reflection.name);
43+
}
44+
}
45+
46+
public registerKnownReflections(reflections: Reflection[]) {
47+
reflections.forEach(reflection => {
48+
this.registerKnownReflection(reflection);
49+
});
50+
}
51+
52+
public resolve() {
53+
this._currentReflections.clear();
54+
Object.values(this._project.reflections).forEach(reflection => this._currentReflections.add(reflection));
55+
56+
this.getReflectionsByInstanceType(this._project, DeclarationReflection).forEach(reflection => {
57+
reflection.extendedTypes = reflection.extendedTypes?.filter(t => this.isMapped(t));
58+
reflection.type = this.remapType(reflection.type);
59+
});
60+
61+
this.getReflectionsByInstanceType(this._project, SignatureReflection).forEach(reflection => {
62+
reflection.type = this.remapType(reflection.type);
63+
reflection.parameters?.forEach(p => p.type = this.remapType(p.type));
64+
reflection.typeParameters?.forEach(p => p.type = this.remapType(p.type));
65+
});
66+
}
67+
68+
private isKnown(type: Type) {
69+
if (type instanceof ReferenceType) {
70+
return this._knownReflectionNames.has(type.name);
71+
}
72+
return false;
73+
}
74+
75+
private isRemoved(reflection: Reflection) {
76+
return this._knownReflections.has(reflection) && !this._currentReflections.has(reflection);
77+
}
78+
79+
private isUnmapped(type: Type | undefined) {
80+
return (type instanceof ReferenceType)
81+
&& (!type.reflection || this.isRemoved(type.reflection))
82+
&& !type.typeArguments
83+
&& this.isKnown(type);
84+
}
85+
86+
private isMapped(type: Type | undefined) {
87+
return !this.isUnmapped(type);
88+
}
89+
90+
private remapType<T extends Type | undefined>(type: T): T extends undefined ? undefined : T;
91+
private remapType(type: Type | undefined) {
92+
if (type instanceof UnionType) {
93+
let items = type.types.filter(t => this.isMapped(t));
94+
if (items.length === 0) {
95+
items = type.types.map(t => this.remapType(t));
96+
} else if (items.length === 1) {
97+
const item = items[0];
98+
if ((item instanceof IntrinsicType) && (item.name === 'undefined' || item.name === 'null')) {
99+
items = type.types.map(t => this.remapType(t));
100+
}
101+
}
102+
103+
if (items.length === 1) {
104+
return items[0];
105+
} else {
106+
type.types = items;
107+
}
108+
} else if (type instanceof IntersectionType) {
109+
type.types = type.types.map(t => this.remapType(t));
110+
} else if (type instanceof ArrayType) {
111+
type.elementType = this.remapType(type.elementType);
112+
} else if (type instanceof PredicateType) {
113+
type.targetType = this.remapType(type.targetType);
114+
} else if (type instanceof TupleType) {
115+
type.elements = type.elements.map(t => this.remapType(t));
116+
} else if (type instanceof ReferenceType && type.typeArguments) {
117+
type.typeArguments = type.typeArguments.map(t => this.remapType(t)!);
118+
} else if (type instanceof ConditionalType) {
119+
type.checkType = this.remapType(type.checkType);
120+
type.extendsType = this.remapType(type.extendsType);
121+
type.trueType = this.remapType(type.trueType);
122+
type.falseType = this.remapType(type.falseType)
123+
} else if ((type instanceof TypeOperatorType) && this.isUnmapped(type.target)) {
124+
return new UnknownType('unknown');
125+
} else if ((type instanceof IndexedAccessType) && this.isUnmapped(type.objectType)) {
126+
return new UnknownType('unknown');
127+
}
128+
129+
return this.isUnmapped(type) ? new UnknownType('unknown') : type;
130+
}
131+
132+
private getReflectionsByInstanceType<T extends Reflection>(project: ProjectReflection, func: { new(...args: any[]): T }): T[] {
133+
return Object.values(project.reflections).filter(r => r instanceof func) as unknown as T[];
134+
}
135+
}

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import { CliApplication } from './cli-application';
33
import { KeyOfPlugin } from './keyof-plugin';
44
import { OmitTagsPlugin } from './omit-tags-plugin';
55
import { TypeScriptDeclarationPlugin } from './typescript-declaration-plugin';
6+
import { UnresolvedTypesPlugin } from './unresolved-types-plugin';
67
import { VersionFilterPlugin } from './version-filter-plugin';
78

89
const options = [
910
...VersionFilterPlugin.options,
1011
...KeyOfPlugin.options,
1112
...OmitTagsPlugin.options,
13+
...UnresolvedTypesPlugin.options,
1214
...TypeScriptDeclarationPlugin.options,
1315
];
1416

@@ -20,6 +22,7 @@ module.exports = (PluginHost: Application) => {
2022
app.converter.addComponent('version-filter', new VersionFilterPlugin(app.converter));
2123
app.converter.addComponent('keyof-comment', new KeyOfPlugin(app.converter));
2224
app.converter.addComponent('omit-tags', new OmitTagsPlugin(app.converter));
25+
app.converter.addComponent('unresolved-types', new UnresolvedTypesPlugin(app.converter));
2326

2427
const declarationPlugin = app.renderer.addComponent('typescript-declaration', new TypeScriptDeclarationPlugin(app.renderer));
2528

src/render/signature-renderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default abstract class SignatureRenderer extends ReflectionRenderer {
2222

2323
protected renderReturnType(method: SignatureReflection): string {
2424
if (method.type) {
25-
return `${method.parent?.kind === ReflectionKind.TypeLiteral ? ' =>': ':'} ${TypeFormatter.format(method.type, { includeConstraints: false })}`;
25+
return `${method.parent?.kind === ReflectionKind.TypeLiteral && method.kind !== ReflectionKind.IndexSignature ? ' =>': ':'} ${TypeFormatter.format(method.type, { includeConstraints: false })}`;
2626
}
2727
return '';
2828
}

src/render/type-formatter.spec.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
import { ArrayType, ConditionalType, IndexedAccessType, IntrinsicType, PredicateType, ReferenceType, TypeOperatorType, TypeParameterType, UnionType } from 'typedoc/dist/lib/models';
1+
import {
2+
ArrayType,
3+
ConditionalType,
4+
IndexedAccessType,
5+
IntrinsicType,
6+
PredicateType,
7+
ReferenceType,
8+
StringLiteralType,
9+
TypeOperatorType,
10+
TypeParameterType,
11+
UnionType,
12+
} from 'typedoc/dist/lib/models';
213
import TypeFormatter from './type-formatter';
314

415
it('should format intrinsic type', () => {
@@ -61,9 +72,9 @@ it('should format conditional type', () => {
6172
it('should format indexed access type', () => {
6273
const type = new IndexedAccessType(
6374
new IntrinsicType('object'), // object type
64-
new IntrinsicType('string'), // index type
75+
new StringLiteralType('string'), // index type
6576
);
66-
expect(TypeFormatter.format(type)).toEqual('{ [key: string]: object }');
77+
expect(TypeFormatter.format(type)).toEqual('object["string"]');
6778
});
6879

6980
it('should format predecate type', () => {

src/render/type-formatter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default class TypeFormatter {
2929

3030
if (type.type === 'indexedAccess') {
3131
const indexType = type as IndexedAccessType;
32-
return `{ [key: ${TypeFormatter.format(indexType.indexType)}]: ${TypeFormatter.format(indexType.objectType)} }`;
32+
return `${TypeFormatter.format(indexType.objectType)}[${TypeFormatter.format(indexType.indexType)}]`;
3333
}
3434

3535
if (type.type === 'intersection') {

src/render/type-literal-renderer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ export default class TypeLiteralRenderer extends ContainerRenderer {
1717

1818
const member = node as DeclarationReflection;
1919

20-
if (member.children) {
20+
if (member.children || member.indexSignature) {
21+
const children = [...(member.children || []), member.indexSignature].filter(c => c);
2122
if (node.parent?.kind === ReflectionKind.TypeAlias) {
2223
const body = this.renderBody(member, 1, ',');
2324
if (body) {
2425
lines.push('{', body, '}');
2526
}
2627
} else {
27-
lines.push(join(' ', '{', member.children.map(c => ReflectionFormatter.instance().render(c)).join(', '), '}'));
28+
lines.push(join(' ', '{', children.map(c => ReflectionFormatter.instance().render(c)).join(', '), '}'));
2829
}
2930
} else if (member.signatures) {
3031
lines.push(ReflectionFormatter.instance().render(member.signatures[0]));

0 commit comments

Comments
 (0)