Skip to content

Commit 5305cbf

Browse files
committed
Improve destructured property support
Ref: #2430 (non-fix, but makes some things nicer)
1 parent 41e6b33 commit 5305cbf

File tree

10 files changed

+632
-351
lines changed

10 files changed

+632
-351
lines changed

.eslintrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@
7070
{
7171
"selector": "ImportDeclaration[source.value=/.*perf$/]",
7272
"message": "Benchmark calls must be removed before committing."
73+
},
74+
{
75+
"selector": "MemberExpression[object.name=type][property.name=symbol]",
76+
"message": "Use type.getSymbol() instead, Type.symbol is not properly typed."
7377
}
7478
]
7579
},

scripts/testcase.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// @ts-check
2+
const marked = require("marked");
3+
const cp = require("child_process");
4+
const { writeFile } = require("fs/promises");
5+
6+
const curl = `curl -s -L -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/typestrong/typedoc/issues/ISSUE`;
7+
8+
/**
9+
* @param {string} cmd
10+
* @returns {Promise<string>}
11+
*/
12+
function exec(cmd) {
13+
return new Promise((resolve, reject) => {
14+
cp.exec(cmd, { encoding: "utf-8" }, (err, stdout, stderr) => {
15+
if (err) return reject(err);
16+
17+
if (stderr.trim().length) {
18+
return reject(new Error(stderr));
19+
}
20+
21+
resolve(stdout.trim());
22+
});
23+
});
24+
}
25+
26+
/** @param {marked.marked.Tokens.Code} code */
27+
function guessExtension(code) {
28+
switch (code.lang) {
29+
case "js":
30+
case "jsx":
31+
return ".js";
32+
case "tsx":
33+
return ".tsx";
34+
}
35+
36+
return ".ts";
37+
}
38+
39+
async function main() {
40+
if (process.argv.length !== 3) {
41+
console.log("Usage: node scripts/testcase.js <issue number>");
42+
process.exit(1);
43+
}
44+
45+
const issue = process.argv[2];
46+
const data = JSON.parse(await exec(curl.replace("ISSUE", issue)));
47+
48+
const lexer = new marked.Lexer({ gfm: true });
49+
const tokens = lexer.lex(data.body);
50+
51+
const code = /** @type {marked.marked.Tokens.Code} */ (
52+
tokens.find((tok) => tok.type === "code")
53+
);
54+
if (!code) {
55+
console.log("No codeblock found");
56+
return;
57+
}
58+
59+
const file = `src/test/converter2/issues/gh${issue}${guessExtension(code)}`;
60+
await writeFile(file, code.text);
61+
await exec(`code ${file} src/test/issues.c2.test.ts`);
62+
}
63+
64+
void main();

src/lib/converter/symbols.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,10 +1071,9 @@ function convertAccessor(
10711071

10721072
function isInherited(context: Context, symbol: ts.Symbol) {
10731073
const parentSymbol = context.project.getSymbolFromReflection(context.scope);
1074-
assert(
1075-
parentSymbol,
1076-
`No parent symbol found for ${symbol.name} in ${context.scope.name}`,
1077-
);
1074+
// It'd be nice to be able to assert that this is true, but sometimes object
1075+
// types don't get symbols if they are inferred.
1076+
if (!parentSymbol) return false;
10781077

10791078
const parents = parentSymbol.declarations?.slice() || [];
10801079
const constructorDecls = parents.flatMap((parent) =>

src/lib/converter/types.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ const constructorConverter: TypeConverter<ts.ConstructorTypeNode, ts.Type> = {
268268
return new ReflectionType(reflection);
269269
},
270270
convertType(context, type) {
271-
if (!type.symbol) {
271+
const symbol = type.getSymbol();
272+
if (!symbol) {
272273
return new IntrinsicType("Function");
273274
}
274275

@@ -277,14 +278,14 @@ const constructorConverter: TypeConverter<ts.ConstructorTypeNode, ts.Type> = {
277278
ReflectionKind.Constructor,
278279
context.scope,
279280
);
280-
context.registerReflection(reflection, type.symbol);
281+
context.registerReflection(reflection, symbol);
281282
context.trigger(ConverterEvents.CREATE_DECLARATION, reflection);
282283

283284
createSignature(
284285
context.withScope(reflection),
285286
ReflectionKind.ConstructorSignature,
286287
type.getConstructSignatures()[0],
287-
type.symbol,
288+
symbol,
288289
);
289290

290291
return new ReflectionType(reflection);
@@ -363,7 +364,8 @@ const functionTypeConverter: TypeConverter<ts.FunctionTypeNode, ts.Type> = {
363364
return new ReflectionType(reflection);
364365
},
365366
convertType(context, type) {
366-
if (!type.symbol) {
367+
const symbol = type.getSymbol();
368+
if (!symbol) {
367369
return new IntrinsicType("Function");
368370
}
369371

@@ -372,7 +374,7 @@ const functionTypeConverter: TypeConverter<ts.FunctionTypeNode, ts.Type> = {
372374
ReflectionKind.TypeLiteral,
373375
context.scope,
374376
);
375-
context.registerReflection(reflection, type.symbol);
377+
context.registerReflection(reflection, symbol);
376378
context.trigger(ConverterEvents.CREATE_DECLARATION, reflection);
377379

378380
createSignature(
@@ -438,7 +440,7 @@ const inferredConverter: TypeConverter<ts.InferTypeNode> = {
438440
},
439441
convertType(context, type) {
440442
return new InferredType(
441-
type.symbol.name,
443+
type.getSymbol()!.name,
442444
maybeConvertType(context, type.getConstraint()),
443445
);
444446
},
@@ -601,16 +603,13 @@ const typeLiteralConverter: TypeConverter<ts.TypeLiteralNode> = {
601603
return new ReflectionType(reflection);
602604
},
603605
convertType(context, type) {
604-
if (!type.symbol) {
605-
return new IntrinsicType("Object");
606-
}
607-
606+
const symbol = type.getSymbol();
608607
const reflection = new DeclarationReflection(
609608
"__type",
610609
ReflectionKind.TypeLiteral,
611610
context.scope,
612611
);
613-
context.registerReflection(reflection, type.symbol);
612+
context.registerReflection(reflection, symbol);
614613
context.trigger(ConverterEvents.CREATE_DECLARATION, reflection);
615614

616615
for (const prop of context.checker.getPropertiesOfType(type)) {
@@ -621,19 +620,21 @@ const typeLiteralConverter: TypeConverter<ts.TypeLiteralNode> = {
621620
context.withScope(reflection),
622621
ReflectionKind.CallSignature,
623622
signature,
624-
type.symbol,
623+
symbol,
625624
);
626625
}
627626
for (const signature of type.getConstructSignatures()) {
628627
createSignature(
629628
context.withScope(reflection),
630629
ReflectionKind.ConstructorSignature,
631630
signature,
632-
type.symbol,
631+
symbol,
633632
);
634633
}
635634

636-
convertIndexSignature(context.withScope(reflection), type.symbol);
635+
if (symbol) {
636+
convertIndexSignature(context.withScope(reflection), symbol);
637+
}
637638

638639
return new ReflectionType(reflection);
639640
},

src/lib/models/reflections/ReflectionSymbolId.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class ReflectionSymbolId {
3737
if ("name" in symbol) {
3838
declaration ??= symbol?.declarations?.[0];
3939
this.fileName = normalizePath(
40-
declaration?.getSourceFile().fileName ?? "\0",
40+
declaration?.getSourceFile().fileName ?? "",
4141
);
4242
if (symbol.declarations?.some(ts.isSourceFile)) {
4343
this.qualifiedName = "";

src/lib/models/reflections/abstract.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ReflectionKind } from "./kind";
77
import type { Serializer, Deserializer, JSONOutput } from "../../serialization";
88
import type { ReflectionVariant } from "./variant";
99
import type { DeclarationReflection } from "./declaration";
10+
import { NonEnumerable } from "../../utils/general";
1011

1112
/**
1213
* Current reflection id.
@@ -274,6 +275,7 @@ export abstract class Reflection {
274275
/**
275276
* The reflection this reflection is a child of.
276277
*/
278+
@NonEnumerable // So that it doesn't show up in console.log
277279
parent?: Reflection;
278280

279281
get project(): ProjectReflection {

src/lib/utils/general.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ export function assertNever(x: never): never {
5454
);
5555
}
5656

57+
export function NonEnumerable(
58+
_cls: unknown,
59+
context: ClassFieldDecoratorContext,
60+
) {
61+
context.addInitializer(function () {
62+
Object.defineProperty(this, context.name, {
63+
enumerable: false,
64+
configurable: true,
65+
writable: true,
66+
});
67+
});
68+
}
69+
5770
/**
5871
* This is a hack to make it possible to detect and warn about installation setups
5972
* which result in TypeDoc being installed multiple times. If TypeDoc has been loaded

src/test/converter/mixin/specs.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2218,7 +2218,7 @@
22182218
"qualifiedName": "Mixin1Class"
22192219
},
22202220
"24": {
2221-
"sourceFileName": "\u0000",
2221+
"sourceFileName": "",
22222222
"qualifiedName": "Mixin1Class.prototype"
22232223
},
22242224
"25": {
@@ -2282,7 +2282,7 @@
22822282
"qualifiedName": "Mixin2"
22832283
},
22842284
"40": {
2285-
"sourceFileName": "\u0000",
2285+
"sourceFileName": "",
22862286
"qualifiedName": "Mixin2.prototype"
22872287
},
22882288
"41": {
@@ -2362,7 +2362,7 @@
23622362
"qualifiedName": "Mixin3"
23632363
},
23642364
"60": {
2365-
"sourceFileName": "\u0000",
2365+
"sourceFileName": "",
23662366
"qualifiedName": "Mixin3.prototype"
23672367
},
23682368
"61": {
@@ -2386,7 +2386,7 @@
23862386
"qualifiedName": "Mixin1Class"
23872387
},
23882388
"66": {
2389-
"sourceFileName": "\u0000",
2389+
"sourceFileName": "",
23902390
"qualifiedName": "Mixin1Class.prototype"
23912391
},
23922392
"67": {

0 commit comments

Comments
 (0)