Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reflect template parameter default values and use them as type arguments #1348

Merged
merged 27 commits into from Aug 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
474606f
Changed convert logic for map type
krisztianb Mar 27, 2020
294a675
Undo changes of last commit: Changed convert logic for map type
krisztianb Mar 27, 2020
58fb056
No need to convert defaultValue for options of type map
krisztianb Mar 27, 2020
cf16a00
Added test for map declaration option with foreign default value
krisztianb Mar 29, 2020
f9b73a4
Merge branch 'master' of https://github.com/TypeStrong/typedoc
krisztianb Apr 22, 2020
3095a52
Added possible min and max values for a number declaration
krisztianb Apr 24, 2020
52eeb65
Merge branch 'master' of https://github.com/TypeStrong/typedoc
krisztianb Apr 25, 2020
1fa1bd1
Error message creation extracted into a helper function.
krisztianb Apr 25, 2020
95bf83d
Merge branch 'master' of https://github.com/TypeStrong/typedoc
krisztianb Apr 26, 2020
dc33b99
Fixed test for map declaration with foreign default value
krisztianb Apr 26, 2020
1b5fcc8
Merge branch 'master' of https://github.com/TypeStrong/typedoc
krisztianb Apr 28, 2020
52d6e26
Allow every possible number as a defaultValue for a number option
krisztianb May 1, 2020
1b0a71e
Fix indent
krisztianb May 6, 2020
d3761d4
Merge branch 'master' of https://github.com/TypeStrong/typedoc
krisztianb Aug 3, 2020
a42ac8a
Add defaultType to TypeParameterType
krisztianb Aug 5, 2020
99a5510
Use defaults for type parameters when inheriting
krisztianb Aug 6, 2020
c064fd9
Simpler check for undefined
krisztianb Aug 6, 2020
134bae7
Move methods to utils
krisztianb Aug 7, 2020
0984316
Fix: return in forEach is no good
krisztianb Aug 7, 2020
4283fa6
Merge branch 'master' of https://github.com/TypeStrong/typedoc
krisztianb Aug 11, 2020
707fc63
Merge branch 'master' into fix-react-template-parameters
krisztianb Aug 11, 2020
c154ae3
Rename defaultType to default
krisztianb Aug 11, 2020
80120fe
Fix mixin test specs
krisztianb Aug 11, 2020
9aa5e35
Combine type parameters of all mixin types
krisztianb Aug 14, 2020
2516f8a
Fix converter alias test
krisztianb Aug 14, 2020
1f36591
Interfaces can have type parameters too
krisztianb Aug 15, 2020
262f3ac
Simplify array types
krisztianb Aug 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/lib/converter/context.ts
Expand Up @@ -296,7 +296,7 @@ export class Context {
* @param typeArguments The type arguments that apply while inheriting the given node.
* @return The resulting reflection / the current scope.
*/
inherit(baseNode: ts.Node, typeArguments?: ts.NodeArray<ts.TypeNode>): Reflection {
inherit(baseNode: ts.Node, typeArguments?: ReadonlyArray<ts.TypeNode>): Reflection {
const wasInherit = this.isInherit;
const oldInherited = this.inherited;
const oldInheritParent = this.inheritParent;
Expand Down
3 changes: 3 additions & 0 deletions src/lib/converter/factories/type-parameter.ts
Expand Up @@ -20,6 +20,9 @@ export function createTypeParameter(context: Context, node: ts.TypeParameterDecl
if (node.constraint) {
typeParameter.constraint = context.converter.convertType(context, node.constraint);
}
if (node.default) {
typeParameter.default = context.converter.convertType(context, node.default);
}

const reflection = <TypeParameterContainer> context.scope;
const typeParameterReflection = new TypeParameterReflection(typeParameter, reflection);
Expand Down
13 changes: 12 additions & 1 deletion src/lib/converter/nodes/class.ts
Expand Up @@ -6,6 +6,7 @@ import { createDeclaration } from '../factories/index';
import { Context } from '../context';
import { Component, ConverterNodeComponent } from '../components';
import { toArray } from 'lodash';
import { getTypeArgumentsWithDefaults, getTypeParametersOfType } from '../utils/types';

@Component({name: 'node:class'})
export class ClassConverter extends ConverterNodeComponent<ts.ClassDeclaration> {
Expand Down Expand Up @@ -64,14 +65,24 @@ export class ClassConverter extends ConverterNodeComponent<ts.ClassDeclaration>
if (type) {
const typesToInheritFrom: ts.Type[] = type.isIntersection() ? type.types : [ type ];

// Get type parameters of all types
let typeParams: ts.TypeParameterDeclaration[] = [];
for (const typeToInheritFrom of typesToInheritFrom) {
typeParams = typeParams.concat(getTypeParametersOfType(typeToInheritFrom));
}

const typeArguments = typeParams.length > 0
? getTypeArgumentsWithDefaults(typeParams, baseType.typeArguments)
: undefined;

typesToInheritFrom.forEach((typeToInheritFrom: ts.Type) => {
// TODO: The TS declaration file claims that:
// 1. type.symbol is non-nullable
// 2. symbol.declarations is non-nullable
// These are both incorrect, GH#1207 for #2 and existing tests for #1.
// Figure out why this is the case and document.
typeToInheritFrom.symbol?.declarations?.forEach((declaration) => {
context.inherit(declaration, baseType.typeArguments);
context.inherit(declaration, typeArguments);
});
});
}
Expand Down
51 changes: 51 additions & 0 deletions src/lib/converter/utils/types.ts
@@ -0,0 +1,51 @@
import * as ts from 'typescript';

/**
* Returns the type parameters of a given type.
* @param type The type whos type parameters are wanted.
* @returns The type parameters of the type. An empty array if the type has no type parameters.
*/
export function getTypeParametersOfType(type: ts.Type): ReadonlyArray<ts.TypeParameterDeclaration> {
const declarations = type.getSymbol()?.getDeclarations() ?? [];

for (const declaration of declarations) {
if ((ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) &&
declaration.typeParameters) {
return declaration.typeParameters;
}
}

return [];
}

/**
* Returns a list of type arguments. If a type parameter has no corresponding type argument, the default type
* for that type parameter is used as the type argument.
* @param typeParams The type parameters for which the type arguments are wanted.
* @param typeArguments The type arguments as provided in the declaration.
* @returns The complete list of type arguments with possible default values if type arguments are missing.
*/
export function getTypeArgumentsWithDefaults(
typeParams: ts.TypeParameterDeclaration[],
typeArguments?: ReadonlyArray<ts.TypeNode>
): ReadonlyArray<ts.TypeNode> {
if (!typeArguments || typeParams.length > typeArguments.length) {
const typeArgumentsWithDefaults = new Array<ts.TypeNode>();

for (let i = 0; i < typeParams.length; ++i) {
krisztianb marked this conversation as resolved.
Show resolved Hide resolved
if (typeArguments && typeArguments[i]) {
typeArgumentsWithDefaults.push(typeArguments[i]);
} else {
const defaultType = typeParams[i].default;

if (defaultType) {
typeArgumentsWithDefaults.push(defaultType);
}
}
}

return typeArgumentsWithDefaults;
}

return typeArguments;
}
3 changes: 3 additions & 0 deletions src/lib/models/reflections/type-parameter.ts
Expand Up @@ -7,11 +7,14 @@ export class TypeParameterReflection extends Reflection implements TypeContainer

type?: Type;

default?: Type;

/**
* Create a new TypeParameterReflection instance.
*/
constructor(type: TypeParameterType, parent?: Reflection) {
super(type.name, ReflectionKind.TypeParameter, parent);
this.type = type.constraint;
this.default = type.default;
}
}
30 changes: 25 additions & 5 deletions src/lib/models/types/type-parameter.ts
Expand Up @@ -15,6 +15,15 @@ export class TypeParameterType extends Type {

constraint?: Type;

/**
* Default type for the type parameter.
*
* ```
* class SomeClass<T = {}>
* ```
*/
default?: Type;

/**
* The type name identifier.
*/
Expand All @@ -33,6 +42,7 @@ export class TypeParameterType extends Type {
clone(): Type {
const clone = new TypeParameterType(this.name);
clone.constraint = this.constraint;
clone.default = this.default;
return clone;
}

Expand All @@ -47,19 +57,29 @@ export class TypeParameterType extends Type {
return false;
}

let constraintEquals = false;

if (this.constraint && type.constraint) {
return type.constraint.equals(this.constraint);
constraintEquals = type.constraint.equals(this.constraint);
} else if (!this.constraint && !type.constraint) {
return true;
} else {
return false;
constraintEquals = true;
}

let defaultEquals = false;

if (this.default && type.default) {
defaultEquals = type.default.equals(this.default);
} else if (!this.default && !type.default) {
defaultEquals = true;
}

return constraintEquals && defaultEquals;
}

/**
* Return a string representation of this type.
*/
toString() {
toString(): string {
return this.name;
}
}
2 changes: 1 addition & 1 deletion src/lib/serialization/schema.ts
Expand Up @@ -222,7 +222,7 @@ export interface TupleType extends Type, S<M.TupleType, 'type'> {
export interface TypeOperatorType extends Type, S<M.TypeOperatorType, 'type' | 'operator' | 'target'> {
}

export interface TypeParameterType extends Type, S<M.TypeParameterType, 'type' | 'name' | 'constraint'> {
export interface TypeParameterType extends Type, S<M.TypeParameterType, 'type' | 'name' | 'constraint' | 'default'> {
}

export interface UnionType extends Type, S<M.UnionType, 'type' | 'types'> {
Expand Down
3 changes: 3 additions & 0 deletions src/lib/serialization/serializers/types/type-parameter.ts
Expand Up @@ -17,6 +17,9 @@ export class TypeParameterTypeSerializer extends TypeSerializerComponent<TypePar
if (type.constraint) {
result.constraint = this.owner.toObject(type.constraint);
}
if (type.default) {
result.default = this.owner.toObject(type.default);
}

return result;
}
Expand Down