Skip to content

Commit 713df74

Browse files
committed
Add @alias tag, fixes #3096
This probably isn't the name I'll end up using as it conflicts badly with JSDoc's `@alias` tag.... `@reExport`?
1 parent 66d0935 commit 713df74

9 files changed

Lines changed: 143 additions & 24 deletions

File tree

site/tags.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
title: Tags
33
children:
44
- tags/abstract.md
5+
- tags/alias.md
56
- tags/alpha.md
67
- tags/author.md
78
- tags/beta.md
@@ -195,6 +196,7 @@ export type Foo = { a: string };
195196
```
196197

197198
- [`@abstract`](./tags/abstract.md)
199+
- [`@alias`](./tags/alias.md)
198200
- [`@alpha`](./tags/alpha.md)
199201
- [`@beta`](./tags/beta.md)
200202
- [`@class`](./tags/class.md)

site/tags/alias.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
title: "@alias"
3+
---
4+
5+
# @alias
6+
7+
**Tag Kind:** [Modifier](../tags.md#modifier-tags)
8+
9+
The `@alias` tag is valid on type alias and variable declarations which directly reference another
10+
symbol. When present, it instructs TypeDoc that the variable it is present on should be treated as
11+
if it was a re-export of the referenced symbol rather than being converted directly.
12+
13+
> [!note]
14+
> It should generally be preferred to use real re-exports to construct your public API instead
15+
> of introducing new symbols with type aliases or variables. Using real re-exports will result
16+
> in a better developer experience for consumers of your library using Go To Definition to navigate
17+
> within your library.
18+
19+
20+
## Example
21+
22+
Assuming that `Vector` in `utils.js` is declared as a class, this will cause TypeDoc to convert
23+
the `Math` namespace as if the `Vector` class was declared directly within the namespace rather than
24+
being imported from another file. Similarly, the `IsInt` type alias will be treated as a re-export.
25+
26+
```ts
27+
import { Vector as _Vector, IsInt as _IsInt } from "./utils.js"
28+
29+
export namespace Math {
30+
/** @alias */
31+
export const Vector = _Vector;
32+
export type Vector = typeof _Vector;
33+
34+
/** @alias */
35+
export type IsInt<T extends number> = _IsInt<T>;
36+
37+
// The following are all invalid usage of the @alias tag and will produce a warning
38+
/** @alias */
39+
export type BadAlias1 = 123;
40+
/** @alias */
41+
export const BadAlias2 = { someImportedFunction };
42+
}
43+
```
44+
45+
## See Also
46+
47+
- The [`@inline`](inline.md) tag

src/lib/application.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ export class Application extends AbstractComponent<
690690
const start = Date.now();
691691

692692
// No point in validating exports when merging. Warnings will have already been emitted when
693-
// creating the project jsons that this run merges together.
693+
// creating the project JSONs that this run merges together.
694694
if (
695695
checks.notExported &&
696696
this.entryPointStrategy !== EntryPointStrategy.Merge

src/lib/converter/symbols.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -349,11 +349,29 @@ function convertTypeAlias(
349349
assert(declaration);
350350

351351
if (ts.isTypeAliasDeclaration(declaration)) {
352-
if (
353-
context
354-
.getComment(symbol, ReflectionKind.TypeAlias)
355-
?.hasModifier("@interface")
356-
) {
352+
const comment = context.getComment(symbol, ReflectionKind.TypeAlias);
353+
354+
if (comment?.hasModifier("@alias")) {
355+
if (ts.isTypeReferenceNode(declaration.type)) {
356+
// Note: Get the symbol from the typeName here, NOT the type! The type refers to this alias,
357+
// not the type alias we want to convert.
358+
const type = context.getTypeAtLocation(declaration.type.typeName);
359+
const aliasedSymbol = type?.aliasSymbol ?? type?.getSymbol() ?? declaration.type.typeName.symbol;
360+
if (aliasedSymbol) {
361+
convertSymbol(context, aliasedSymbol, exportSymbol || symbol);
362+
return;
363+
} else {
364+
context.logger.warn(
365+
i18n.failed_to_convert_0_as_alias(exportSymbol?.name ?? symbol.name),
366+
declaration,
367+
);
368+
}
369+
} else {
370+
context.logger.warn(i18n.failed_to_convert_0_as_alias(exportSymbol?.name ?? symbol.name), declaration);
371+
}
372+
}
373+
374+
if (comment?.hasModifier("@interface")) {
357375
return convertTypeAliasAsInterface(
358376
context,
359377
symbol,
@@ -1005,6 +1023,19 @@ function convertVariable(
10051023
? context.checker.getTypeOfSymbolAtLocation(symbol, declaration)
10061024
: context.checker.getTypeOfSymbol(symbol);
10071025

1026+
if (comment?.hasModifier("@alias")) {
1027+
if (
1028+
declaration && ts.isVariableDeclaration(declaration) && declaration.initializer &&
1029+
(ts.isIdentifier(declaration.initializer) || ts.isPropertyAccessExpression(declaration.initializer))
1030+
) {
1031+
const aliasedSymbol = context.expectSymbolAtLocation(declaration.initializer);
1032+
convertSymbol(context, aliasedSymbol, exportSymbol || symbol);
1033+
return ts.SymbolFlags.Property | ts.SymbolFlags.ValueModule | ts.SymbolFlags.TypeAlias;
1034+
} else {
1035+
context.logger.warn(i18n.failed_to_convert_0_as_alias(exportSymbol?.name ?? symbol.name), declaration);
1036+
}
1037+
}
1038+
10081039
if (
10091040
isEnumLike(context.checker, type, declaration) &&
10101041
comment?.hasModifier("@enum")

src/lib/internationalization/locales/en.cts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export = {
5353
`{0} is being converted as a class, but does not have any construct signatures`,
5454
converting_0_as_enum_requires_value_declaration:
5555
`Converting {0} as an enum requires a declaration which represents a non-type value`,
56+
failed_to_convert_0_as_alias: "Failed to convert {0} as an alias because it is not direct reference.",
5657

5758
comment_for_0_should_not_contain_block_or_modifier_tags:
5859
`The comment for {0} should not contain any block or modifier tags`,

src/lib/utils/options/tsdoc-defaults.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export const tsdocModifierTags = [
7575
export const modifierTags = [
7676
...tsdocModifierTags,
7777
"@abstract",
78+
"@alias",
7879
"@class",
7980
"@disableGroups",
8081
"@enum",

src/test/behavior.c2.test.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { deepStrictEqual as equal, ok } from "assert";
22
import {
33
Comment,
44
CommentTag,
5-
type ContainerReflection,
65
LiteralType,
76
Reflection,
87
ReflectionKind,
@@ -14,20 +13,6 @@ import { TestLogger } from "./TestLogger.js";
1413
import { getComment, getSigComment, query, querySig, reflToTree } from "./utils.js";
1514
import { getConverter2App, getConverter2Project } from "./programs.js";
1615

17-
type NameTree = { [name: string]: NameTree | undefined };
18-
19-
function buildNameTree(
20-
refl: ContainerReflection,
21-
tree: NameTree = {},
22-
): NameTree {
23-
for (const child of refl.children || []) {
24-
tree[child.name] ||= {};
25-
buildNameTree(child, tree[child.name]);
26-
}
27-
28-
return tree;
29-
}
30-
3116
function getLinks(refl: Reflection) {
3217
ok(refl.comment);
3318
return filterMap(refl.comment.summary, (p) => {
@@ -419,11 +404,11 @@ describe("Behavior Tests", () => {
419404
app.options.setValue("excludeNotDocumented", true);
420405
app.options.setValue("excludeNotDocumentedKinds", ["Property"]);
421406
const project = convert("excludeNotDocumentedKinds");
422-
equal(buildNameTree(project), {
407+
equal(reflToTree(project), {
423408
NotDoc: {
424-
prop: {},
409+
prop: "Property",
425410
},
426-
identity: {},
411+
identity: "Function",
427412
});
428413
});
429414

@@ -1526,4 +1511,25 @@ describe("Behavior Tests", () => {
15261511
// Inherits description from class's @template
15271512
equal(Comment.combineDisplayParts(ctor.signatures[1].typeParameters?.[0].comment?.summary), "class docs");
15281513
});
1514+
1515+
it("Supports the @alias tag", () => {
1516+
const project = convert("alias");
1517+
1518+
logger.expectMessage("warn: Failed to convert BadAlias as an alias because it is not direct reference.");
1519+
1520+
equal(reflToTree(project), {
1521+
Math: {
1522+
BadAlias: "TypeAlias",
1523+
IsInt: "TypeAlias",
1524+
Vec: {
1525+
"Constructor:constructor": "Constructor",
1526+
mag: "Method",
1527+
},
1528+
makeVec: "Function",
1529+
},
1530+
});
1531+
1532+
const bad = query(project, "Math.BadAlias");
1533+
equal(bad.comment?.modifierTags, new Set(["@alias"]));
1534+
});
15291535
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/** Class comment */
2+
class _Vec {
3+
mag() {}
4+
}
5+
6+
/** Type comment */
7+
type _IsInt<T extends number> = `${T}` extends `${bigint}` ? true : false;
8+
9+
/** Func comment */
10+
function _makeVec(x: number, y: number, z: number): _Vec {
11+
return new _Vec();
12+
}
13+
14+
export namespace Math {
15+
// /** Alias comment @alias */
16+
export type IsInt<T extends number> = _IsInt<T>;
17+
18+
/** Alias comment @alias */
19+
export const Vec = _Vec;
20+
export type Vec = typeof _Vec;
21+
22+
/** Alias comment @alias */
23+
export const makeVec = _makeVec;
24+
25+
/** @alias */
26+
export type BadAlias = 123;
27+
}

tsdoc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@
8080
"tagName": "@abstract",
8181
"syntaxKind": "modifier"
8282
},
83+
{
84+
"tagName": "@alias",
85+
"syntaxKind": "modifier"
86+
},
8387
{
8488
"tagName": "@document",
8589
"syntaxKind": "block"

0 commit comments

Comments
 (0)