Skip to content

Commit

Permalink
Correctly handle transient symbols
Browse files Browse the repository at this point in the history
Resolves #2444
  • Loading branch information
Gerrit0 committed Nov 25, 2023
1 parent 25f1beb commit 2cae791
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Improved handling of function-modules created with `Object.assign`, #2436.
- TypeDoc will no longer warn about duplicate comments with warnings which point to a single comment, #2437
- Fixed an infinite loop when `skipLibCheck` is used to ignore some compiler errors, #2438.
- Correctly handle transient symbols in `@namespace`-created namespaces, #2444.

### Thanks!

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"test:cov": "c8 mocha --config .config/mocha.fast.json",
"doc:c": "node bin/typedoc --tsconfig src/test/converter/tsconfig.json",
"doc:c2": "node bin/typedoc --tsconfig src/test/converter2/tsconfig.json",
"doc:c2d": "node --inspect-brk bin/typedoc --tsconfig src/test/converter2/tsconfig.json",
"test:full": "c8 mocha --config .config/mocha.full.json",
"test:visual": "ts-node ./src/test/capture-screenshots.ts && ./scripts/compare_screenshots.sh",
"test:visual:accept": "node scripts/accept_visual_regression.js",
Expand Down
22 changes: 20 additions & 2 deletions src/lib/models/reflections/ReflectionSymbolId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { isAbsolute, join, relative, resolve } from "path";
import ts from "typescript";
import type { JSONOutput, Serializer } from "../../serialization/index";
import { getCommonDirectory, readFile } from "../../utils/fs";
import { normalizePath } from "../../utils/paths";
import { getQualifiedName } from "../../utils/tsutils";
import { optional, validate } from "../../utils/validation";
import { normalizePath } from "../../utils/paths";

/**
* See {@link ReflectionSymbolId}
Expand All @@ -14,6 +14,9 @@ export type ReflectionSymbolIdString = string & {
readonly __reflectionSymbolId: unique symbol;
};

let transientCount = 0;
const transientIds = new WeakMap<ts.Symbol, number>();

/**
* This exists so that TypeDoc can store a unique identifier for a `ts.Symbol` without
* keeping a reference to the `ts.Symbol` itself. This identifier should be stable across
Expand All @@ -25,8 +28,16 @@ export class ReflectionSymbolId {
/**
* Note: This is **not** serialized. It exists for sorting by declaration order, but
* should not be needed when deserializing from JSON.
* Will be set to `Infinity` if the ID was deserialized from JSON.
*/
pos: number;
/**
* Note: This is **not** serialized. It exists to support detection of the differences between
* symbols which share declarations, but are instantiated with different type parameters.
* This will be `NaN` if the symbol reference is not transient.
* Note: This can only be non-NaN if {@link pos} is finite.
*/
transientId: number;

constructor(symbol: ts.Symbol, declaration?: ts.Declaration);
constructor(json: JSONOutput.ReflectionSymbolId);
Expand All @@ -45,16 +56,23 @@ export class ReflectionSymbolId {
this.qualifiedName = getQualifiedName(symbol, symbol.name);
}
this.pos = declaration?.pos ?? Infinity;
if (symbol.flags & ts.SymbolFlags.Transient) {
this.transientId = transientIds.get(symbol) ?? ++transientCount;
transientIds.set(symbol, this.transientId);
} else {
this.transientId = NaN;
}
} else {
this.fileName = symbol.sourceFileName;
this.qualifiedName = symbol.qualifiedName;
this.pos = Infinity;
this.transientId = NaN;
}
}

getStableKey(): ReflectionSymbolIdString {
if (Number.isFinite(this.pos)) {
return `${this.fileName}\0${this.qualifiedName}\0${this.pos}` as ReflectionSymbolIdString;
return `${this.fileName}\0${this.qualifiedName}\0${this.pos}\0${this.transientId}` as ReflectionSymbolIdString;
} else {
return `${this.fileName}\0${this.qualifiedName}` as ReflectionSymbolIdString;
}
Expand Down
37 changes: 37 additions & 0 deletions src/test/converter2/issues/gh2444.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
function Comparable<T>(impl: { compare(a: T, b: T): number }) {
return {
...impl,

equal(a: T, b: T) {
return impl.compare(a, b) === 0;
},
};
}

const BooleanComparable = Comparable<boolean>({
compare(a, b) {
return +a - +b;
},
});

/** @namespace */
export const Boolean = {
...BooleanComparable,
hasInstance(value: unknown): value is boolean {
return typeof value === "boolean";
},
};

const NumberComparable = Comparable<number>({
compare(left, right) {
return left === right ? 0 : left < right ? -1 : 1;
},
});

/** @namespace */
export const Number = {
...NumberComparable,
hasInstance(value: unknown): value is number {
return typeof value === "number";
},
};
26 changes: 17 additions & 9 deletions src/test/issues.c2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import {
notDeepStrictEqual as notEqual,
ok,
} from "assert";
import { existsSync } from "fs";
import { join } from "path";
import { clearCommentCache } from "../lib/converter/comments";
import {
Comment,
CommentTag,
DeclarationReflection,
IntrinsicType,
LiteralType,
ProjectReflection,
QueryType,
ReferenceReflection,
ReflectionKind,
SignatureReflection,
ReflectionType,
Comment,
CommentTag,
SignatureReflection,
UnionType,
LiteralType,
IntrinsicType,
ReferenceReflection,
} from "../lib/models";
import type { InlineTagDisplayPart } from "../lib/models/comments/comment";
import {
Expand All @@ -24,9 +27,6 @@ import {
getConverter2Program,
} from "./programs";
import { TestLogger } from "./TestLogger";
import { clearCommentCache } from "../lib/converter/comments";
import { join } from "path";
import { existsSync } from "fs";
import { getComment, getLinks, query } from "./utils";

const base = getConverter2Base();
Expand Down Expand Up @@ -1222,4 +1222,12 @@ describe("Issue Tests", () => {
const bad = query(convert(), "Bad");
equal(bad.kind, ReflectionKind.Interface);
});

it("Handles transient symbols correctly, #2444", () => {
const project = convert();
const boolEq = query(project, "Boolean.equal");
const numEq = query(project, "Number.equal");
equal(boolEq.signatures![0].parameters![0].type?.toString(), "boolean");
equal(numEq.signatures![0].parameters![0].type?.toString(), "number");
});
});

0 comments on commit 2cae791

Please sign in to comment.