Skip to content

Commit 2cae791

Browse files
committed
Correctly handle transient symbols
Resolves #2444
1 parent 25f1beb commit 2cae791

File tree

5 files changed

+76
-11
lines changed

5 files changed

+76
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Improved handling of function-modules created with `Object.assign`, #2436.
88
- TypeDoc will no longer warn about duplicate comments with warnings which point to a single comment, #2437
99
- Fixed an infinite loop when `skipLibCheck` is used to ignore some compiler errors, #2438.
10+
- Correctly handle transient symbols in `@namespace`-created namespaces, #2444.
1011

1112
### Thanks!
1213

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"test:cov": "c8 mocha --config .config/mocha.fast.json",
6666
"doc:c": "node bin/typedoc --tsconfig src/test/converter/tsconfig.json",
6767
"doc:c2": "node bin/typedoc --tsconfig src/test/converter2/tsconfig.json",
68+
"doc:c2d": "node --inspect-brk bin/typedoc --tsconfig src/test/converter2/tsconfig.json",
6869
"test:full": "c8 mocha --config .config/mocha.full.json",
6970
"test:visual": "ts-node ./src/test/capture-screenshots.ts && ./scripts/compare_screenshots.sh",
7071
"test:visual:accept": "node scripts/accept_visual_regression.js",

src/lib/models/reflections/ReflectionSymbolId.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { isAbsolute, join, relative, resolve } from "path";
33
import ts from "typescript";
44
import type { JSONOutput, Serializer } from "../../serialization/index";
55
import { getCommonDirectory, readFile } from "../../utils/fs";
6+
import { normalizePath } from "../../utils/paths";
67
import { getQualifiedName } from "../../utils/tsutils";
78
import { optional, validate } from "../../utils/validation";
8-
import { normalizePath } from "../../utils/paths";
99

1010
/**
1111
* See {@link ReflectionSymbolId}
@@ -14,6 +14,9 @@ export type ReflectionSymbolIdString = string & {
1414
readonly __reflectionSymbolId: unique symbol;
1515
};
1616

17+
let transientCount = 0;
18+
const transientIds = new WeakMap<ts.Symbol, number>();
19+
1720
/**
1821
* This exists so that TypeDoc can store a unique identifier for a `ts.Symbol` without
1922
* keeping a reference to the `ts.Symbol` itself. This identifier should be stable across
@@ -25,8 +28,16 @@ export class ReflectionSymbolId {
2528
/**
2629
* Note: This is **not** serialized. It exists for sorting by declaration order, but
2730
* should not be needed when deserializing from JSON.
31+
* Will be set to `Infinity` if the ID was deserialized from JSON.
2832
*/
2933
pos: number;
34+
/**
35+
* Note: This is **not** serialized. It exists to support detection of the differences between
36+
* symbols which share declarations, but are instantiated with different type parameters.
37+
* This will be `NaN` if the symbol reference is not transient.
38+
* Note: This can only be non-NaN if {@link pos} is finite.
39+
*/
40+
transientId: number;
3041

3142
constructor(symbol: ts.Symbol, declaration?: ts.Declaration);
3243
constructor(json: JSONOutput.ReflectionSymbolId);
@@ -45,16 +56,23 @@ export class ReflectionSymbolId {
4556
this.qualifiedName = getQualifiedName(symbol, symbol.name);
4657
}
4758
this.pos = declaration?.pos ?? Infinity;
59+
if (symbol.flags & ts.SymbolFlags.Transient) {
60+
this.transientId = transientIds.get(symbol) ?? ++transientCount;
61+
transientIds.set(symbol, this.transientId);
62+
} else {
63+
this.transientId = NaN;
64+
}
4865
} else {
4966
this.fileName = symbol.sourceFileName;
5067
this.qualifiedName = symbol.qualifiedName;
5168
this.pos = Infinity;
69+
this.transientId = NaN;
5270
}
5371
}
5472

5573
getStableKey(): ReflectionSymbolIdString {
5674
if (Number.isFinite(this.pos)) {
57-
return `${this.fileName}\0${this.qualifiedName}\0${this.pos}` as ReflectionSymbolIdString;
75+
return `${this.fileName}\0${this.qualifiedName}\0${this.pos}\0${this.transientId}` as ReflectionSymbolIdString;
5876
} else {
5977
return `${this.fileName}\0${this.qualifiedName}` as ReflectionSymbolIdString;
6078
}

src/test/converter2/issues/gh2444.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
function Comparable<T>(impl: { compare(a: T, b: T): number }) {
2+
return {
3+
...impl,
4+
5+
equal(a: T, b: T) {
6+
return impl.compare(a, b) === 0;
7+
},
8+
};
9+
}
10+
11+
const BooleanComparable = Comparable<boolean>({
12+
compare(a, b) {
13+
return +a - +b;
14+
},
15+
});
16+
17+
/** @namespace */
18+
export const Boolean = {
19+
...BooleanComparable,
20+
hasInstance(value: unknown): value is boolean {
21+
return typeof value === "boolean";
22+
},
23+
};
24+
25+
const NumberComparable = Comparable<number>({
26+
compare(left, right) {
27+
return left === right ? 0 : left < right ? -1 : 1;
28+
},
29+
});
30+
31+
/** @namespace */
32+
export const Number = {
33+
...NumberComparable,
34+
hasInstance(value: unknown): value is number {
35+
return typeof value === "number";
36+
},
37+
};

src/test/issues.c2.test.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,22 @@ import {
33
notDeepStrictEqual as notEqual,
44
ok,
55
} from "assert";
6+
import { existsSync } from "fs";
7+
import { join } from "path";
8+
import { clearCommentCache } from "../lib/converter/comments";
69
import {
10+
Comment,
11+
CommentTag,
712
DeclarationReflection,
13+
IntrinsicType,
14+
LiteralType,
815
ProjectReflection,
916
QueryType,
17+
ReferenceReflection,
1018
ReflectionKind,
11-
SignatureReflection,
1219
ReflectionType,
13-
Comment,
14-
CommentTag,
20+
SignatureReflection,
1521
UnionType,
16-
LiteralType,
17-
IntrinsicType,
18-
ReferenceReflection,
1922
} from "../lib/models";
2023
import type { InlineTagDisplayPart } from "../lib/models/comments/comment";
2124
import {
@@ -24,9 +27,6 @@ import {
2427
getConverter2Program,
2528
} from "./programs";
2629
import { TestLogger } from "./TestLogger";
27-
import { clearCommentCache } from "../lib/converter/comments";
28-
import { join } from "path";
29-
import { existsSync } from "fs";
3030
import { getComment, getLinks, query } from "./utils";
3131

3232
const base = getConverter2Base();
@@ -1222,4 +1222,12 @@ describe("Issue Tests", () => {
12221222
const bad = query(convert(), "Bad");
12231223
equal(bad.kind, ReflectionKind.Interface);
12241224
});
1225+
1226+
it("Handles transient symbols correctly, #2444", () => {
1227+
const project = convert();
1228+
const boolEq = query(project, "Boolean.equal");
1229+
const numEq = query(project, "Number.equal");
1230+
equal(boolEq.signatures![0].parameters![0].type?.toString(), "boolean");
1231+
equal(numEq.signatures![0].parameters![0].type?.toString(), "number");
1232+
});
12251233
});

0 commit comments

Comments
 (0)