Skip to content

Commit

Permalink
Add support for multiple index signatures
Browse files Browse the repository at this point in the history
Closes #2470
  • Loading branch information
Gerrit0 committed Mar 17, 2024
1 parent bd4decb commit 2e4eed3
Show file tree
Hide file tree
Showing 17 changed files with 717 additions and 609 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,12 @@
- Removed deprecated `navigation.fullTree` option.
- API: `MapOptionDeclaration.mapError` has been removed.
- API: Deprecated `BindOption` decorator has been removed.
- API: `DeclarationReflection.indexSignature` has been renamed to `DeclarationReflection.indexSignatures`.
Note: This also affects JSON serialization. TypeDoc will support JSON output from 0.25 until 0.28.

### Bug Fixes

- TypeDoc now supports objects with multiple index signatures, #2470.

# Unreleased

Expand Down
4 changes: 2 additions & 2 deletions src/lib/converter/comments/declarationReferenceResolver.ts
Expand Up @@ -177,8 +177,8 @@ function resolveKeyword(
return (refl as DeclarationReflection).signatures;

case "index":
if ((refl as DeclarationReflection).indexSignature) {
return [(refl as DeclarationReflection).indexSignature!];
if ((refl as DeclarationReflection).indexSignatures) {
return (refl as DeclarationReflection).indexSignatures;
}
break;

Expand Down
20 changes: 8 additions & 12 deletions src/lib/converter/factories/index-signature.ts
Expand Up @@ -9,27 +9,22 @@ import {
import type { Context } from "../context";
import { ConverterEvents } from "../converter-events";

export function convertIndexSignature(context: Context, symbol: ts.Symbol) {
export function convertIndexSignatures(context: Context, symbol: ts.Symbol) {
assert(context.scope instanceof DeclarationReflection);

const indexSymbol = symbol.members?.get("__index" as ts.__String);
if (indexSymbol) {
// Right now TypeDoc models don't have a way to distinguish between string
// and number index signatures... { [x: string]: 1 | 2; [x: number]: 2 }
// will be misrepresented.
const indexDeclaration = indexSymbol.getDeclarations()?.[0];
assert(
indexDeclaration &&
ts.isIndexSignatureDeclaration(indexDeclaration),
);
if (!indexSymbol) return;

for (const indexDeclaration of indexSymbol.getDeclarations() || []) {
assert(ts.isIndexSignatureDeclaration(indexDeclaration));
const param = indexDeclaration.parameters[0];
assert(param && ts.isParameter(param));
const index = new SignatureReflection(
"__index",
ReflectionKind.IndexSignature,
context.scope,
);
index.comment = context.getComment(indexSymbol, index.kind);
index.comment = context.getNodeComment(indexDeclaration, index.kind);
index.parameters = [
new ParameterReflection(
param.name.getText(),
Expand All @@ -46,7 +41,8 @@ export function convertIndexSignature(context: Context, symbol: ts.Symbol) {
indexDeclaration.type,
);
context.registerReflection(index, indexSymbol);
context.scope.indexSignature = index;
context.scope.indexSignatures ||= [];
context.scope.indexSignatures.push(index);

context.trigger(
ConverterEvents.CREATE_SIGNATURE,
Expand Down
4 changes: 3 additions & 1 deletion src/lib/converter/plugins/ImplementsPlugin.ts
Expand Up @@ -414,7 +414,9 @@ function createLink(
link(reflection);
link(reflection.getSignature);
link(reflection.setSignature);
link(reflection.indexSignature);
for (const sig of reflection.indexSignatures || []) {
link(sig);
}
for (const sig of reflection.signatures ?? []) {
link(sig);
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib/converter/symbols.ts
Expand Up @@ -18,7 +18,7 @@ import {
} from "../utils/enum";
import type { Context } from "./context";
import { convertDefaultValue } from "./convert-expression";
import { convertIndexSignature } from "./factories/index-signature";
import { convertIndexSignatures } from "./factories/index-signature";
import {
createConstructSignatureWithType,
createSignature,
Expand Down Expand Up @@ -413,7 +413,7 @@ function convertTypeAliasAsInterface(
convertConstructSignatures(rc, symbol);

// And finally, index signatures
convertIndexSignature(rc, symbol);
convertIndexSignatures(rc, symbol);
}

function convertFunctionOrMethod(
Expand Down Expand Up @@ -630,7 +630,7 @@ function convertClassOrInterface(
convertConstructSignatures(reflectionContext, symbol);

// And finally, index signatures
convertIndexSignature(reflectionContext, symbol);
convertIndexSignatures(reflectionContext, symbol);

// Normally this shouldn't matter, unless someone did something with skipLibCheck on.
return ts.SymbolFlags.Alias;
Expand Down
6 changes: 3 additions & 3 deletions src/lib/converter/types.ts
Expand Up @@ -31,7 +31,7 @@ import { ReflectionSymbolId } from "../models/reflections/ReflectionSymbolId";
import { zip } from "../utils/array";
import type { Context } from "./context";
import { ConverterEvents } from "./converter-events";
import { convertIndexSignature } from "./factories/index-signature";
import { convertIndexSignatures } from "./factories/index-signature";
import {
convertParameterNodes,
convertTypeParameterNodes,
Expand Down Expand Up @@ -611,7 +611,7 @@ const typeLiteralConverter: TypeConverter<ts.TypeLiteralNode> = {
);
}

convertIndexSignature(rc, symbol);
convertIndexSignatures(rc, symbol);

return new ReflectionType(reflection);
},
Expand Down Expand Up @@ -646,7 +646,7 @@ const typeLiteralConverter: TypeConverter<ts.TypeLiteralNode> = {
}

if (symbol) {
convertIndexSignature(context.withScope(reflection), symbol);
convertIndexSignatures(context.withScope(reflection), symbol);
}

return new ReflectionType(reflection);
Expand Down
29 changes: 17 additions & 12 deletions src/lib/models/reflections/declaration.ts
Expand Up @@ -91,7 +91,7 @@ export class DeclarationReflection extends ContainerReflection {
/**
* The index signature of this declaration.
*/
indexSignature?: SignatureReflection;
indexSignatures?: SignatureReflection[];

/**
* The get signature of this declaration.
Expand Down Expand Up @@ -187,8 +187,8 @@ export class DeclarationReflection extends ContainerReflection {
if (this.signatures) {
result = result.concat(this.signatures);
}
if (this.indexSignature) {
result.push(this.indexSignature);
if (this.indexSignatures) {
result = result.concat(this.indexSignatures);
}
if (this.getSignature) {
result.push(this.getSignature);
Expand Down Expand Up @@ -233,12 +233,9 @@ export class DeclarationReflection extends ContainerReflection {
}
}

if (this.indexSignature) {
for (const signature of this.indexSignatures?.slice() || []) {
if (
callback(
this.indexSignature,
TraverseProperty.IndexSignature,
) === false
callback(signature, TraverseProperty.IndexSignature) === false
) {
return;
}
Expand Down Expand Up @@ -298,7 +295,7 @@ export class DeclarationReflection extends ContainerReflection {
typeParameters: serializer.toObjectsOptional(this.typeParameters),
type: serializer.toObject(this.type),
signatures: serializer.toObjectsOptional(this.signatures),
indexSignature: serializer.toObject(this.indexSignature),
indexSignatures: serializer.toObjectsOptional(this.indexSignatures),
getSignature: serializer.toObject(this.getSignature),
setSignature: serializer.toObject(this.setSignature),
defaultValue: this.defaultValue,
Expand Down Expand Up @@ -367,9 +364,17 @@ export class DeclarationReflection extends ContainerReflection {
this.signatures = de.reviveMany(obj.signatures, (r) =>
de.constructReflection(r),
);
this.indexSignature = de.revive(obj.indexSignature, (r) =>
de.constructReflection(r),
);

// TypeDoc 0.25, remove check with 0.28.
if (obj.indexSignature) {
this.indexSignatures = [
de.revive(obj.indexSignature, (r) => de.constructReflection(r)),
];
} else {
this.indexSignatures = de.reviveMany(obj.indexSignatures, (r) =>
de.constructReflection(r),
);
}
this.getSignature = de.revive(obj.getSignature, (r) =>
de.constructReflection(r),
);
Expand Down
8 changes: 7 additions & 1 deletion src/lib/models/reflections/project.ts
Expand Up @@ -168,7 +168,13 @@ export class ProjectReflection extends ContainerReflection {
} else if (property === TraverseProperty.GetSignature) {
delete parent.getSignature;
} else if (property === TraverseProperty.IndexSignature) {
delete parent.indexSignature;
removeIfPresent(
parent.indexSignatures,
reflection as SignatureReflection,
);
if (!parent.indexSignatures?.length) {
delete parent.indexSignatures;
}
} else if (property === TraverseProperty.Parameters) {
removeIfPresent(
(reflection.parent as SignatureReflection).parameters,
Expand Down
48 changes: 24 additions & 24 deletions src/lib/output/themes/default/partials/parameter.tsx
@@ -1,7 +1,7 @@
import { classNames, getKindClass, wbr } from "../../lib";
import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext";
import { JSX } from "../../../../utils";
import { DeclarationReflection, ReflectionType } from "../../../../models";
import { DeclarationReflection, ReflectionType, SignatureReflection } from "../../../../models";

export const parameter = (context: DefaultThemeRenderContext, props: DeclarationReflection) => (
<>
Expand All @@ -26,29 +26,7 @@ export const parameter = (context: DefaultThemeRenderContext, props: Declaration
</ul>
</li>
)}
{!!props.indexSignature && (
<>
<li class="tsd-parameter-index-signature">
<h5>
<span class="tsd-signature-symbol">[</span>
{props.indexSignature?.parameters?.map((item) => (
<>
{!!item.flags.isRest && <span class="tsd-signature-symbol">...</span>}
<span class={getKindClass(item)}>{item.name}</span>
{": "}
{context.type(item.type)}
</>
))}
<span class="tsd-signature-symbol">{"]: "}</span>
{context.type(props.indexSignature.type)}
</h5>
{context.commentSummary(props.indexSignature)}
{context.commentTags(props.indexSignature)}
{props.indexSignature.type instanceof ReflectionType &&
context.parameter(props.indexSignature.type.declaration)}
</li>
</>
)}
{props.indexSignatures?.map((index) => renderParamIndexSignature(context, index))}
{props.children?.map((item) => (
<>
{item.signatures ? (
Expand Down Expand Up @@ -134,3 +112,25 @@ export const parameter = (context: DefaultThemeRenderContext, props: Declaration
</ul>
</>
);

function renderParamIndexSignature(context: DefaultThemeRenderContext, index: SignatureReflection) {
return (
<li class="tsd-parameter-index-signature">
<h5>
<span class="tsd-signature-symbol">[</span>
{index.parameters!.map((item) => (
<>
<span class={getKindClass(item)}>{item.name}</span>
{": "}
{context.type(item.type)}
</>
))}
<span class="tsd-signature-symbol">{"]: "}</span>
{context.type(index.type)}
</h5>
{context.commentSummary(index)}
{context.commentTags(index)}
{index.type instanceof ReflectionType && context.parameter(index.type.declaration)}
</li>
);
}
21 changes: 11 additions & 10 deletions src/lib/output/themes/default/partials/type.tsx
Expand Up @@ -394,16 +394,17 @@ const typeRenderers: {
);
}

if (type.declaration.indexSignature) {
const index = type.declaration.indexSignature;
members.push(
<>
[<span class={getKindClass(type.declaration.indexSignature)}>{index.parameters![0].name}</span>:{" "}
{renderType(context, index.parameters![0].type, TypeContext.none)}]
<span class="tsd-signature-symbol">: </span>
{renderType(context, index.type, TypeContext.none)}
</>,
);
if (type.declaration.indexSignatures) {
for (const index of type.declaration.indexSignatures) {
members.push(
<>
[<span class={getKindClass(index)}>{index.parameters![0].name}</span>:{" "}
{renderType(context, index.parameters![0].type, TypeContext.none)}]
<span class="tsd-signature-symbol">: </span>
{renderType(context, index.type, TypeContext.none)}
</>,
);
}
}

if (!members.length && type.declaration.signatures?.length === 1) {
Expand Down
49 changes: 31 additions & 18 deletions src/lib/output/themes/default/templates/reflection.tsx
@@ -1,7 +1,13 @@
import { classNames, hasTypeParameters } from "../../lib";
import { classNames, getKindClass, hasTypeParameters } from "../../lib";
import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext";
import type { PageEvent } from "../../../events";
import { ContainerReflection, DeclarationReflection, ReflectionKind, ReflectionType } from "../../../../models";
import {
ContainerReflection,
DeclarationReflection,
ReflectionKind,
ReflectionType,
SignatureReflection,
} from "../../../../models";
import { JSX, Raw } from "../../../../utils";

export function reflectionTemplate(context: DefaultThemeRenderContext, props: PageEvent<ContainerReflection>) {
Expand Down Expand Up @@ -55,26 +61,13 @@ export function reflectionTemplate(context: DefaultThemeRenderContext, props: Pa
</ul>
</section>
)}
{!!props.model.signatures && (
{!!props.model.signatures?.length && (
<section class="tsd-panel">{context.memberSignatures(props.model)}</section>
)}
{!!props.model.indexSignature && (
{!!props.model.indexSignatures?.length && (
<section class={classNames({ "tsd-panel": true }, context.getReflectionClasses(props.model))}>
<h4 class="tsd-before-signature">{context.i18n.theme_indexable()}</h4>
<div class="tsd-signature">
<span class="tsd-signature-symbol">[</span>
{props.model.indexSignature.parameters!.map((item) => (
<>
{item.name}: {context.type(item.type)}
</>
))}
<span class="tsd-signature-symbol">]: </span>
{context.type(props.model.indexSignature.type)}
</div>
{context.commentSummary(props.model.indexSignature)}
{context.commentTags(props.model.indexSignature)}
{props.model.indexSignature?.type instanceof ReflectionType &&
context.parameter(props.model.indexSignature.type.declaration)}
{props.model.indexSignatures.map((index) => renderIndexSignature(context, index))}
</section>
)}
{!props.model.signatures && context.memberSources(props.model)}
Expand All @@ -85,3 +78,23 @@ export function reflectionTemplate(context: DefaultThemeRenderContext, props: Pa
</>
);
}

function renderIndexSignature(context: DefaultThemeRenderContext, index: SignatureReflection) {
return (
<>
<div class="tsd-signature">
<span class="tsd-signature-symbol">[</span>
{index.parameters!.map((item) => (
<>
<span class={getKindClass(item)}>{item.name}</span>: {context.type(item.type)}
</>
))}
<span class="tsd-signature-symbol">]: </span>
{context.type(index.type)}
</div>
{context.commentSummary(index)}
{context.commentTags(index)}
{index.type instanceof ReflectionType && context.parameter(index.type.declaration)}
</>
);
}
7 changes: 5 additions & 2 deletions src/lib/serialization/schema.ts
Expand Up @@ -165,7 +165,7 @@ export interface DeclarationReflection
| "relevanceBoost"
| "type"
| "signatures"
| "indexSignature"
| "indexSignatures"
| "defaultValue"
| "overwrites"
| "inheritedFrom"
Expand All @@ -178,7 +178,10 @@ export interface DeclarationReflection
| "setSignature"
| "typeParameters"
| "readme"
> {}
> {
/** @deprecated moved to {@link indexSignatures} with 0.26. */
indexSignature?: SignatureReflection;
}

/** @category Reflections */
export interface TypeParameterReflection
Expand Down

0 comments on commit 2e4eed3

Please sign in to comment.