Skip to content

Commit

Permalink
Fix namespaced names passed to addUnknownSymbolResolver
Browse files Browse the repository at this point in the history
Resolves #1832
  • Loading branch information
Gerrit0 committed Jan 4, 2022
1 parent 3f0dbea commit 52c8c4f
Show file tree
Hide file tree
Showing 21 changed files with 223 additions and 72 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,8 +1,13 @@
# Unreleased

### Features

- `ReferenceType`s which reference an external symbol will now include `qualifiedName` and `package` in their serialized JSON.

### Bug Fixes

- Fixed line height of `h1` and `h2` elements being too low, #1796.
- Symbol names passed to `addUnknownSymbolResolver` will now be correctly given the qualified name to the symbol being referenced.

## v0.22.10 (2021-11-25)

Expand Down
8 changes: 4 additions & 4 deletions src/lib/converter/plugins/DecoratorPlugin.ts
Expand Up @@ -86,10 +86,10 @@ export class DecoratorPlugin extends ConverterComponent {

const type = context.checker.getTypeAtLocation(identifier);
if (type && type.symbol) {
info.type = new ReferenceType(
info.name,
info.type = ReferenceType.createSymbolReference(
context.resolveAliasedSymbol(type.symbol),
context.project
context,
info.name
);

if (callExpression && callExpression.arguments) {
Expand All @@ -105,7 +105,7 @@ export class DecoratorPlugin extends ConverterComponent {

const usages = this.usages.get(type.symbol) ?? [];
usages.push(
new ReferenceType(
ReferenceType.createResolvedReference(
reflection.name,
reflection,
context.project
Expand Down
26 changes: 14 additions & 12 deletions src/lib/converter/plugins/ImplementsPlugin.ts
Expand Up @@ -86,11 +86,12 @@ export class ImplementsPlugin extends ConverterComponent {

const interfaceMemberName =
interfaceReflection.name + "." + interfaceMember.name;
classMember.implementationOf = new ReferenceType(
interfaceMemberName,
interfaceMember,
context.project
);
classMember.implementationOf =
ReferenceType.createResolvedReference(
interfaceMemberName,
interfaceMember,
context.project
);
copyComment(classMember, interfaceMember);

if (
Expand Down Expand Up @@ -119,11 +120,12 @@ export class ImplementsPlugin extends ConverterComponent {
interfaceMember.signatures
)) {
if (clsSig.implementationOf) {
clsSig.implementationOf = new ReferenceType(
clsSig.implementationOf.name,
intSig,
context.project
);
clsSig.implementationOf =
ReferenceType.createResolvedReference(
clsSig.implementationOf.name,
intSig,
context.project
);
}
copyComment(clsSig, intSig);
}
Expand Down Expand Up @@ -164,15 +166,15 @@ export class ImplementsPlugin extends ConverterComponent {
child.signatures ?? [],
parentMember.signatures ?? []
)) {
childSig[key] = new ReferenceType(
childSig[key] = ReferenceType.createResolvedReference(
`${parent.name}.${parentMember.name}`,
parentSig,
context.project
);
copyComment(childSig, parentSig);
}

child[key] = new ReferenceType(
child[key] = ReferenceType.createResolvedReference(
`${parent.name}.${parentMember.name}`,
parentMember,
context.project
Expand Down
10 changes: 7 additions & 3 deletions src/lib/converter/plugins/TypePlugin.ts
Expand Up @@ -42,7 +42,7 @@ export class TypePlugin extends ConverterComponent {
target.implementedBy = [];
}
target.implementedBy.push(
new ReferenceType(
ReferenceType.createResolvedReference(
reflection.name,
reflection,
context.project
Expand All @@ -56,7 +56,7 @@ export class TypePlugin extends ConverterComponent {
target.extendedBy = [];
}
target.extendedBy.push(
new ReferenceType(
ReferenceType.createResolvedReference(
reflection.name,
reflection,
context.project
Expand Down Expand Up @@ -122,7 +122,11 @@ export class TypePlugin extends ConverterComponent {
}

push([
new ReferenceType(reflection.name, reflection, context.project),
ReferenceType.createResolvedReference(
reflection.name,
reflection,
context.project
),
]);
hierarchy.isTarget = true;

Expand Down
39 changes: 18 additions & 21 deletions src/lib/converter/types.ts
Expand Up @@ -281,10 +281,9 @@ const exprWithTypeArgsConverter: TypeConverter<
}
const parameters =
node.typeArguments?.map((type) => convertType(context, type)) ?? [];
const ref = new ReferenceType(
targetSymbol.name,
const ref = ReferenceType.createSymbolReference(
context.resolveAliasedSymbol(targetSymbol),
context.project
context
);
ref.typeArguments = parameters;
return ref;
Expand Down Expand Up @@ -362,19 +361,19 @@ const importType: TypeConverter<ts.ImportTypeNode> = {
const name = node.qualifier?.getText() ?? "__module";
const symbol = context.checker.getSymbolAtLocation(node);
assert(symbol, "Missing symbol when converting import type node");
return new ReferenceType(
name,
return ReferenceType.createSymbolReference(
context.resolveAliasedSymbol(symbol),
context.project
context,
name
);
},
convertType(context, type) {
const symbol = type.getSymbol();
assert(symbol, "Missing symbol when converting import type"); // Should be a compiler error
return new ReferenceType(
"__module",
return ReferenceType.createSymbolReference(
context.resolveAliasedSymbol(symbol),
context.project
context,
"__module"
);
},
};
Expand Down Expand Up @@ -584,10 +583,10 @@ const queryConverter: TypeConverter<ts.TypeQueryNode> = {
}

return new QueryType(
new ReferenceType(
node.exprName.getText(),
ReferenceType.createSymbolReference(
context.resolveAliasedSymbol(querySymbol),
context.project
context,
node.exprName.getText()
)
);
},
Expand All @@ -600,10 +599,9 @@ const queryConverter: TypeConverter<ts.TypeQueryNode> = {
)}. This is a bug.`
);
return new QueryType(
new ReferenceType(
symbol.name,
ReferenceType.createSymbolReference(
context.resolveAliasedSymbol(symbol),
context.project
context
)
);
},
Expand All @@ -630,10 +628,10 @@ const referenceConverter: TypeConverter<

const name = node.typeName.getText();

const type = new ReferenceType(
name,
const type = ReferenceType.createSymbolReference(
context.resolveAliasedSymbol(symbol),
context.project
context,
name
);
type.typeArguments = node.typeArguments?.map((type) =>
convertType(context, type)
Expand All @@ -651,10 +649,9 @@ const referenceConverter: TypeConverter<
);
}

const ref = new ReferenceType(
symbol.name,
const ref = ReferenceType.createSymbolReference(
context.resolveAliasedSymbol(symbol),
context.project
context
);
ref.typeArguments = (
type.aliasSymbol ? type.aliasTypeArguments : type.typeArguments
Expand Down
74 changes: 73 additions & 1 deletion src/lib/models/types.ts
@@ -1,4 +1,5 @@
import type * as ts from "typescript";
import type { Context } from "../converter";
import { Reflection } from "./reflections/abstract";
import type { DeclarationReflection } from "./reflections/declaration";
import type { ProjectReflection } from "./reflections/project";
Expand Down Expand Up @@ -512,17 +513,36 @@ export class ReferenceType extends Type {
return resolved;
}

/**
* Don't use this if at all possible. It will eventually go away since models may not
* retain information from the original TS objects to enable documentation generation from
* previously generated JSON.
* @internal
*/
getSymbol(): ts.Symbol | undefined {
if (typeof this._target === "number") {
return;
}
return this._target;
}

/**
* The fully qualified name of the referenced type, relative to the file it is defined in.
* This will usually be the same as `name`, unless namespaces are used.
* Will only be set for `ReferenceType`s pointing to a symbol within `node_modules`.
*/
qualifiedName?: string;

/**
* The package that this type is referencing.
* Will only be set for `ReferenceType`s pointing to a symbol within `node_modules`.
*/
package?: string;

private _target: ts.Symbol | number;
private _project: ProjectReflection | null;

constructor(
private constructor(
name: string,
target: ts.Symbol | Reflection | number,
project: ProjectReflection | null
Expand All @@ -533,6 +553,58 @@ export class ReferenceType extends Type {
this._project = project;
}

static createResolvedReference(
name: string,
target: Reflection | number,
project: ProjectReflection | null
) {
return new ReferenceType(name, target, project);
}

static createSymbolReference(
symbol: ts.Symbol,
context: Context,
name?: string
) {
const ref = new ReferenceType(
name ?? symbol.name,
symbol,
context.project
);

const symbolPath = symbol?.declarations?.[0]
?.getSourceFile()
.fileName.replace(/\\/g, "/");
if (!symbolPath) return ref;

let startIndex = symbolPath.indexOf("node_modules/");
if (startIndex === -1) return ref;
startIndex += "node_modules/".length;
let stopIndex = symbolPath.indexOf("/", startIndex);
// Scoped package, e.g. `@types/node`
if (symbolPath[startIndex] === "@") {
stopIndex = symbolPath.indexOf("/", stopIndex + 1);
}

const packageName = symbolPath.substring(startIndex, stopIndex);
ref.package = packageName;

const qualifiedName = context.checker.getFullyQualifiedName(symbol);
// I think this is less bad than depending on symbol.parent...
// https://github.com/microsoft/TypeScript/issues/38344
// It will break if someone names a directory with a quote in it, but so will lots
// of other things including other parts of TypeDoc. Until it *actually* breaks someone...
if (qualifiedName.startsWith('"')) {
ref.qualifiedName = qualifiedName.substring(
qualifiedName.indexOf('".', 1) + 2
);
} else {
ref.qualifiedName = qualifiedName;
}

return ref;
}

/** @internal this is used for type parameters, which don't actually point to something */
static createBrokenReference(name: string, project: ProjectReflection) {
return new ReferenceType(name, -1, project);
Expand Down
27 changes: 5 additions & 22 deletions src/lib/output/renderer.ts
Expand Up @@ -6,7 +6,6 @@
* series of {@link RendererEvent} events. Instances of {@link BasePlugin} can listen to these events and
* alter the generated output.
*/
import type * as ts from "typescript";
import * as fs from "fs";
import * as path from "path";

Expand All @@ -22,7 +21,7 @@ import { Component, ChildableComponent } from "../utils/component";
import { BindOption, EventHooks } from "../utils";
import { loadHighlighter } from "../utils/highlighter";
import type { Theme as ShikiTheme } from "shiki";
import { Reflection } from "../models";
import { ReferenceType, Reflection } from "../models";
import type { JsxElement } from "../utils/jsx.elements";
import type { DefaultThemeRenderContext } from "./themes/default/DefaultThemeRenderContext";

Expand Down Expand Up @@ -194,31 +193,15 @@ export class Renderer extends ChildableComponent<
* symbols so that we don't need to keep the program around forever.
* @internal
*/
attemptExternalResolution(
symbol: ts.Symbol | undefined
): string | undefined {
const symbolPath = symbol?.declarations?.[0]
?.getSourceFile()
.fileName.replace(/\\/g, "/");
if (!symbolPath) {
attemptExternalResolution(type: ReferenceType): string | undefined {
if (!type.qualifiedName || !type.package) {
return;
}
let startIndex = symbolPath.indexOf("node_modules/");
if (startIndex === -1) {
return;
}
startIndex += "node_modules/".length;
let stopIndex = symbolPath.indexOf("/", startIndex);
// Scoped package, e.g. `@types/node`
if (symbolPath[startIndex] === "@") {
stopIndex = symbolPath.indexOf("/", stopIndex + 1);
}

const packageName = symbolPath.substring(startIndex, stopIndex);
const resolvers = this.unknownSymbolResolvers.get(packageName);
const resolvers = this.unknownSymbolResolvers.get(type.package);

for (const resolver of resolvers || []) {
const resolved = resolver(symbol!.name);
const resolved = resolver(type.qualifiedName);
if (resolved) return resolved;
}
}
Expand Down
7 changes: 3 additions & 4 deletions src/lib/output/themes/default/DefaultThemeRenderContext.ts
@@ -1,6 +1,5 @@
import type * as ts from "typescript";
import type { RendererHooks } from "../..";
import type { Reflection } from "../../../models";
import type { ReferenceType, Reflection } from "../../../models";
import type { Options } from "../../../utils";
import type { DefaultTheme } from "./DefaultTheme";
import { defaultLayout } from "./layouts/default";
Expand Down Expand Up @@ -54,8 +53,8 @@ export class DefaultThemeRenderContext {
return md ? this.theme.markedPlugin.parseMarkdown(md) : "";
};

attemptExternalResolution = (symbol: ts.Symbol | undefined) => {
return this.theme.owner.attemptExternalResolution(symbol);
attemptExternalResolution = (type: ReferenceType) => {
return this.theme.owner.attemptExternalResolution(type);
};

reflectionTemplate = bind(reflectionTemplate, this);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/output/themes/default/partials/type.tsx
Expand Up @@ -255,7 +255,7 @@ const typeRenderers: {
name = renderUniquePath(context, reflection);
}
} else {
const externalUrl = context.attemptExternalResolution(type.getSymbol());
const externalUrl = context.attemptExternalResolution(type);
if (externalUrl) {
name = (
<a href={externalUrl} class="tsd-signature-type external" target="_blank">
Expand Down

0 comments on commit 52c8c4f

Please sign in to comment.