diff --git a/CHANGELOG.md b/CHANGELOG.md index efa5b07a4..0d307881f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## Features - Extended reflection preview view for interfaces to include type parameters, #2455. +- Added special cases for converting methods which are documented as returning `this` or accepting `this` as a parameter, #2458. + Note: This will only happen if a method is declared as `method(): this`, it will not happen if the method implicitly returns `this` + as the compiler strips that information when creating types for a class instance. ### Bug Fixes diff --git a/package.json b/package.json index 9238c10eb..02347b3e4 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "test": "mocha --config .config/mocha.fast.json", "test:cov": "c8 mocha --config .config/mocha.fast.json", "doc:c": "node bin/typedoc --tsconfig src/test/converter/tsconfig.json", + "doc:cd": "node --inspect-brk 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", "example": "cd example && node ../bin/typedoc", diff --git a/src/lib/converter/factories/signature.ts b/src/lib/converter/factories/signature.ts index e9bd83492..673094cfe 100644 --- a/src/lib/converter/factories/signature.ts +++ b/src/lib/converter/factories/signature.ts @@ -96,6 +96,8 @@ export function createSignature( sigRef.type = convertPredicate(predicate, sigRefCtx); } else if (kind == ReflectionKind.SetSignature) { sigRef.type = new IntrinsicType("void"); + } else if (declaration?.type?.kind === ts.SyntaxKind.ThisType) { + sigRef.type = new IntrinsicType("this"); } else { sigRef.type = context.converter.convertType( sigRefCtx, @@ -171,10 +173,18 @@ function convertParameters( type = param.type; } - paramRefl.type = context.converter.convertType( - context.withScope(paramRefl), - type, - ); + if ( + declaration && + ts.isParameter(declaration) && + declaration.type?.kind === ts.SyntaxKind.ThisType + ) { + paramRefl.type = new IntrinsicType("this"); + } else { + paramRefl.type = context.converter.convertType( + context.withScope(paramRefl), + type, + ); + } let isOptional = false; if (declaration) { @@ -384,8 +394,6 @@ export function convertTemplateParameterNodes( return paramRefl; }); }); - const params = (nodes ?? []).flatMap((tag) => tag.typeParameters); - return convertTypeParameterNodes(context, params); } function getVariance( diff --git a/src/lib/models/reflections/abstract.ts b/src/lib/models/reflections/abstract.ts index 8e9aee843..44a8da444 100644 --- a/src/lib/models/reflections/abstract.ts +++ b/src/lib/models/reflections/abstract.ts @@ -527,6 +527,8 @@ export abstract class Reflection { /** * Return a string representation of this reflection and all of its children. * + * Note: This is intended as a debug tool only, output may change between patch versions. + * * @param indent Used internally to indent child reflections. */ toStringHierarchy(indent = "") { diff --git a/src/lib/models/reflections/declaration.ts b/src/lib/models/reflections/declaration.ts index 7ebaa64d3..ea316449a 100644 --- a/src/lib/models/reflections/declaration.ts +++ b/src/lib/models/reflections/declaration.ts @@ -279,7 +279,7 @@ export class DeclarationReflection extends ContainerReflection { } if (this.type) { - result += ":" + this.type.toString(); + result += ": " + this.type.toString(); } return result; diff --git a/src/lib/models/reflections/parameter.ts b/src/lib/models/reflections/parameter.ts index 6222fa490..42087e19b 100644 --- a/src/lib/models/reflections/parameter.ts +++ b/src/lib/models/reflections/parameter.ts @@ -33,7 +33,9 @@ export class ParameterReflection extends Reflection { * Return a string representation of this reflection. */ override toString() { - return super.toString() + (this.type ? ":" + this.type.toString() : ""); + return ( + super.toString() + (this.type ? ": " + this.type.toString() : "") + ); } override toObject(serializer: Serializer): JSONOutput.ParameterReflection { diff --git a/src/lib/models/reflections/signature.ts b/src/lib/models/reflections/signature.ts index d92397c8a..ec4178c6b 100644 --- a/src/lib/models/reflections/signature.ts +++ b/src/lib/models/reflections/signature.ts @@ -101,7 +101,7 @@ export class SignatureReflection extends Reflection { } if (this.type) { - result += ":" + this.type.toString(); + result += ": " + this.type.toString(); } return result; diff --git a/src/test/behavior.c2.test.ts b/src/test/behavior.c2.test.ts index 8af976d76..e092f015b 100644 --- a/src/test/behavior.c2.test.ts +++ b/src/test/behavior.c2.test.ts @@ -19,7 +19,7 @@ import { import { join } from "path"; import { existsSync } from "fs"; import { clearCommentCache } from "../lib/converter/comments"; -import { query } from "./utils"; +import { query, querySig } from "./utils"; type NameTree = { [name: string]: NameTree }; @@ -1004,4 +1004,25 @@ describe("Behavior Tests", () => { const MergedType = query(convert("resolutionMode"), "MergedType"); equal(MergedType.children?.map((child) => child.name), ["cjs", "esm"]); }); + + it("Special cases some `this` type occurrences", () => { + const project = convert("thisType"); + equal(query(project, "ThisClass.prop").type?.toString(), "ThisClass"); // Not special cased + equal( + querySig(project, "ThisClass.returnThisImplicit").type?.toString(), + "ThisClass", + ); // Not special cased + + equal( + querySig(project, "ThisClass.returnThis").type?.toString(), + "this", + ); + equal( + querySig( + project, + "ThisClass.paramThis", + ).parameters?.[0].type?.toString(), + "this", + ); + }); }); diff --git a/src/test/converter/class/specs-with-lump-categories.json b/src/test/converter/class/specs-with-lump-categories.json index 1571d606c..b36d7b69f 100644 --- a/src/test/converter/class/specs-with-lump-categories.json +++ b/src/test/converter/class/specs-with-lump-categories.json @@ -4996,168 +4996,34 @@ }, { "id": 205, - "name": "this", - "variant": "declaration", - "kind": 2, - "flags": {}, - "children": [ - { - "id": 206, - "name": "ChainClass", - "variant": "declaration", - "kind": 128, - "flags": {}, - "comment": { - "summary": [ - { - "kind": "text", - "text": "ChainClass comment short text.\n\nChainClass comment text." - } - ] - }, - "children": [ - { - "id": 207, - "name": "constructor", - "variant": "declaration", - "kind": 512, - "flags": {}, - "signatures": [ - { - "id": 208, - "name": "new ChainClass", - "variant": "signature", - "kind": 16384, - "flags": {}, - "type": { - "type": "reference", - "target": 206, - "name": "ChainClass", - "package": "typedoc" - } - } - ] - }, - { - "id": 209, - "name": "chain", - "variant": "declaration", - "kind": 2048, - "flags": { - "isPublic": true - }, - "sources": [ - { - "fileName": "this.ts", - "line": 10, - "character": 11, - "url": "typedoc://this.ts#L10" - } - ], - "signatures": [ - { - "id": 210, - "name": "chain", - "variant": "signature", - "kind": 4096, - "flags": {}, - "comment": { - "summary": [ - { - "kind": "text", - "text": "Chain method that returns this." - } - ] - }, - "sources": [ - { - "fileName": "this.ts", - "line": 10, - "character": 11, - "url": "typedoc://this.ts#L10" - } - ], - "type": { - "type": "reference", - "target": 206, - "name": "ChainClass", - "package": "typedoc" - } - } - ] - } - ], - "groups": [ - { - "title": "Constructors", - "children": [ - 207 - ] - }, - { - "title": "Methods", - "children": [ - 209 - ] - } - ], - "sources": [ - { - "fileName": "this.ts", - "line": 6, - "character": 13, - "url": "typedoc://this.ts#L6" - } - ] - } - ], - "groups": [ - { - "title": "Classes", - "children": [ - 206 - ] - } - ], - "sources": [ - { - "fileName": "this.ts", - "line": 1, - "character": 0, - "url": "typedoc://this.ts#L1" - } - ] - }, - { - "id": 211, "name": "type-operator", "variant": "declaration", "kind": 2, "flags": {}, "children": [ { - "id": 217, + "id": 211, "name": "GenericClass", "variant": "declaration", "kind": 128, "flags": {}, "children": [ { - "id": 218, + "id": 212, "name": "constructor", "variant": "declaration", "kind": 512, "flags": {}, "signatures": [ { - "id": 219, + "id": 213, "name": "new GenericClass", "variant": "signature", "kind": 16384, "flags": {}, "typeParameter": [ { - "id": 220, + "id": 214, "name": "T", "variant": "typeParam", "kind": 131072, @@ -5167,7 +5033,7 @@ "operator": "keyof", "target": { "type": "reference", - "target": 212, + "target": 206, "name": "TestClass", "package": "typedoc" } @@ -5176,7 +5042,7 @@ ], "type": { "type": "reference", - "target": 217, + "target": 211, "typeArguments": [ { "type": "reference", @@ -5192,7 +5058,7 @@ ] }, { - "id": 221, + "id": 215, "name": "c", "variant": "declaration", "kind": 1024, @@ -5217,13 +5083,13 @@ { "title": "Constructors", "children": [ - 218 + 212 ] }, { "title": "Properties", "children": [ - 221 + 215 ] } ], @@ -5237,7 +5103,7 @@ ], "typeParameters": [ { - "id": 222, + "id": 216, "name": "T", "variant": "typeParam", "kind": 131072, @@ -5247,7 +5113,7 @@ "operator": "keyof", "target": { "type": "reference", - "target": 212, + "target": 206, "name": "TestClass", "package": "typedoc" } @@ -5256,7 +5122,7 @@ ] }, { - "id": 212, + "id": 206, "name": "TestClass", "variant": "declaration", "kind": 128, @@ -5276,7 +5142,7 @@ "kind": "inline-tag", "tag": "@link", "text": "TestClass", - "target": 212 + "target": 206 }, { "kind": "text", @@ -5288,21 +5154,21 @@ }, "children": [ { - "id": 213, + "id": 207, "name": "constructor", "variant": "declaration", "kind": 512, "flags": {}, "signatures": [ { - "id": 214, + "id": 208, "name": "new TestClass", "variant": "signature", "kind": 16384, "flags": {}, "type": { "type": "reference", - "target": 212, + "target": 206, "name": "TestClass", "package": "typedoc" } @@ -5310,7 +5176,7 @@ ] }, { - "id": 215, + "id": 209, "name": "a", "variant": "declaration", "kind": 1024, @@ -5329,7 +5195,7 @@ } }, { - "id": 216, + "id": 210, "name": "b", "variant": "declaration", "kind": 1024, @@ -5352,14 +5218,14 @@ { "title": "Constructors", "children": [ - 213 + 207 ] }, { "title": "Properties", "children": [ - 215, - 216 + 209, + 210 ] } ], @@ -5377,8 +5243,8 @@ { "title": "Classes", "children": [ - 217, - 212 + 211, + 206 ] } ], @@ -5404,8 +5270,7 @@ 134, 166, 185, - 205, - 211 + 205 ] } ], @@ -6156,50 +6021,34 @@ "qualifiedName": "value" }, "205": { - "sourceFileName": "src/test/converter/class/this.ts", - "qualifiedName": "" - }, - "206": { - "sourceFileName": "src/test/converter/class/this.ts", - "qualifiedName": "ChainClass" - }, - "209": { - "sourceFileName": "src/test/converter/class/this.ts", - "qualifiedName": "ChainClass.chain" - }, - "210": { - "sourceFileName": "src/test/converter/class/this.ts", - "qualifiedName": "ChainClass.chain" - }, - "211": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "" }, - "212": { + "206": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "TestClass" }, - "215": { + "209": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "TestClass.a" }, - "216": { + "210": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "TestClass.b" }, - "217": { + "211": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "GenericClass" }, - "220": { + "214": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "GenericClass.T" }, - "221": { + "215": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "GenericClass.c" }, - "222": { + "216": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "GenericClass.T" } diff --git a/src/test/converter/class/specs.json b/src/test/converter/class/specs.json index 1571d606c..b36d7b69f 100644 --- a/src/test/converter/class/specs.json +++ b/src/test/converter/class/specs.json @@ -4996,168 +4996,34 @@ }, { "id": 205, - "name": "this", - "variant": "declaration", - "kind": 2, - "flags": {}, - "children": [ - { - "id": 206, - "name": "ChainClass", - "variant": "declaration", - "kind": 128, - "flags": {}, - "comment": { - "summary": [ - { - "kind": "text", - "text": "ChainClass comment short text.\n\nChainClass comment text." - } - ] - }, - "children": [ - { - "id": 207, - "name": "constructor", - "variant": "declaration", - "kind": 512, - "flags": {}, - "signatures": [ - { - "id": 208, - "name": "new ChainClass", - "variant": "signature", - "kind": 16384, - "flags": {}, - "type": { - "type": "reference", - "target": 206, - "name": "ChainClass", - "package": "typedoc" - } - } - ] - }, - { - "id": 209, - "name": "chain", - "variant": "declaration", - "kind": 2048, - "flags": { - "isPublic": true - }, - "sources": [ - { - "fileName": "this.ts", - "line": 10, - "character": 11, - "url": "typedoc://this.ts#L10" - } - ], - "signatures": [ - { - "id": 210, - "name": "chain", - "variant": "signature", - "kind": 4096, - "flags": {}, - "comment": { - "summary": [ - { - "kind": "text", - "text": "Chain method that returns this." - } - ] - }, - "sources": [ - { - "fileName": "this.ts", - "line": 10, - "character": 11, - "url": "typedoc://this.ts#L10" - } - ], - "type": { - "type": "reference", - "target": 206, - "name": "ChainClass", - "package": "typedoc" - } - } - ] - } - ], - "groups": [ - { - "title": "Constructors", - "children": [ - 207 - ] - }, - { - "title": "Methods", - "children": [ - 209 - ] - } - ], - "sources": [ - { - "fileName": "this.ts", - "line": 6, - "character": 13, - "url": "typedoc://this.ts#L6" - } - ] - } - ], - "groups": [ - { - "title": "Classes", - "children": [ - 206 - ] - } - ], - "sources": [ - { - "fileName": "this.ts", - "line": 1, - "character": 0, - "url": "typedoc://this.ts#L1" - } - ] - }, - { - "id": 211, "name": "type-operator", "variant": "declaration", "kind": 2, "flags": {}, "children": [ { - "id": 217, + "id": 211, "name": "GenericClass", "variant": "declaration", "kind": 128, "flags": {}, "children": [ { - "id": 218, + "id": 212, "name": "constructor", "variant": "declaration", "kind": 512, "flags": {}, "signatures": [ { - "id": 219, + "id": 213, "name": "new GenericClass", "variant": "signature", "kind": 16384, "flags": {}, "typeParameter": [ { - "id": 220, + "id": 214, "name": "T", "variant": "typeParam", "kind": 131072, @@ -5167,7 +5033,7 @@ "operator": "keyof", "target": { "type": "reference", - "target": 212, + "target": 206, "name": "TestClass", "package": "typedoc" } @@ -5176,7 +5042,7 @@ ], "type": { "type": "reference", - "target": 217, + "target": 211, "typeArguments": [ { "type": "reference", @@ -5192,7 +5058,7 @@ ] }, { - "id": 221, + "id": 215, "name": "c", "variant": "declaration", "kind": 1024, @@ -5217,13 +5083,13 @@ { "title": "Constructors", "children": [ - 218 + 212 ] }, { "title": "Properties", "children": [ - 221 + 215 ] } ], @@ -5237,7 +5103,7 @@ ], "typeParameters": [ { - "id": 222, + "id": 216, "name": "T", "variant": "typeParam", "kind": 131072, @@ -5247,7 +5113,7 @@ "operator": "keyof", "target": { "type": "reference", - "target": 212, + "target": 206, "name": "TestClass", "package": "typedoc" } @@ -5256,7 +5122,7 @@ ] }, { - "id": 212, + "id": 206, "name": "TestClass", "variant": "declaration", "kind": 128, @@ -5276,7 +5142,7 @@ "kind": "inline-tag", "tag": "@link", "text": "TestClass", - "target": 212 + "target": 206 }, { "kind": "text", @@ -5288,21 +5154,21 @@ }, "children": [ { - "id": 213, + "id": 207, "name": "constructor", "variant": "declaration", "kind": 512, "flags": {}, "signatures": [ { - "id": 214, + "id": 208, "name": "new TestClass", "variant": "signature", "kind": 16384, "flags": {}, "type": { "type": "reference", - "target": 212, + "target": 206, "name": "TestClass", "package": "typedoc" } @@ -5310,7 +5176,7 @@ ] }, { - "id": 215, + "id": 209, "name": "a", "variant": "declaration", "kind": 1024, @@ -5329,7 +5195,7 @@ } }, { - "id": 216, + "id": 210, "name": "b", "variant": "declaration", "kind": 1024, @@ -5352,14 +5218,14 @@ { "title": "Constructors", "children": [ - 213 + 207 ] }, { "title": "Properties", "children": [ - 215, - 216 + 209, + 210 ] } ], @@ -5377,8 +5243,8 @@ { "title": "Classes", "children": [ - 217, - 212 + 211, + 206 ] } ], @@ -5404,8 +5270,7 @@ 134, 166, 185, - 205, - 211 + 205 ] } ], @@ -6156,50 +6021,34 @@ "qualifiedName": "value" }, "205": { - "sourceFileName": "src/test/converter/class/this.ts", - "qualifiedName": "" - }, - "206": { - "sourceFileName": "src/test/converter/class/this.ts", - "qualifiedName": "ChainClass" - }, - "209": { - "sourceFileName": "src/test/converter/class/this.ts", - "qualifiedName": "ChainClass.chain" - }, - "210": { - "sourceFileName": "src/test/converter/class/this.ts", - "qualifiedName": "ChainClass.chain" - }, - "211": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "" }, - "212": { + "206": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "TestClass" }, - "215": { + "209": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "TestClass.a" }, - "216": { + "210": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "TestClass.b" }, - "217": { + "211": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "GenericClass" }, - "220": { + "214": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "GenericClass.T" }, - "221": { + "215": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "GenericClass.c" }, - "222": { + "216": { "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "GenericClass.T" } diff --git a/src/test/converter/class/this.ts b/src/test/converter/class/this.ts deleted file mode 100644 index 4864364bb..000000000 --- a/src/test/converter/class/this.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * ChainClass comment short text. - * - * ChainClass comment text. - */ -export class ChainClass { - /** - * Chain method that returns this. - */ - public chain(): this { - return this; - } -} diff --git a/src/test/converter2/behavior/thisType.ts b/src/test/converter2/behavior/thisType.ts new file mode 100644 index 000000000..31ec47d67 --- /dev/null +++ b/src/test/converter2/behavior/thisType.ts @@ -0,0 +1,17 @@ +export class ThisClass { + // TypeDoc documents as returning `this` + returnThis(): this { + return this; + } + + // TypeDoc documents as accepting `this` + paramThis(x: this) {} + + // TypeDoc documents as containing `ThisClass` + prop!: this; + + // TypeDoc documents as returning `ThisClass` + returnThisImplicit() { + return this; + } +} diff --git a/src/test/utils.ts b/src/test/utils.ts index 756dd3eae..53a630372 100644 --- a/src/test/utils.ts +++ b/src/test/utils.ts @@ -5,15 +5,32 @@ import { ProjectReflection, Reflection, ReflectionKind, + SignatureReflection, } from ".."; import { filterMap } from "../lib/utils"; -export function query(project: ProjectReflection, name: string) { +export function query( + project: ProjectReflection, + name: string, +): DeclarationReflection { const reflection = project.getChildByName(name); ok(reflection instanceof DeclarationReflection, `Failed to find ${name}`); return reflection; } +export function querySig( + project: ProjectReflection, + name: string, + index = 0, +): SignatureReflection { + const decl = query(project, name); + ok( + decl.signatures?.length ?? 0 > index, + `Reflection "${name}" does not contain signature`, + ); + return decl.signatures![index]; +} + export function getComment(project: ProjectReflection, name: string) { return Comment.combineDisplayParts(query(project, name).comment?.summary); }