Skip to content

Commit

Permalink
Use TS link resolution by default
Browse files Browse the repository at this point in the history
Closes #2141
  • Loading branch information
Gerrit0 committed Mar 25, 2023
1 parent fb1658f commit e1dc6a7
Show file tree
Hide file tree
Showing 20 changed files with 437 additions and 104 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

### Breaking Changes

- `@link`, `@linkcode` and `@linkplain` tags will now be resolved with TypeScript's link resolution by default. The `useTsLinkResolution` option
can be used to turn this behavior off, but be aware that doing so will mean your links will be resolved differently by editor tooling and TypeDoc.
- TypeDoc will no longer automatically load plugins from `node_modules`. Specify the `--plugin` option to indicate which modules should be loaded.
- The `packages` entry point strategy will now run TypeDoc in each provided package directory and then merge the results together.
The previous `packages` strategy has been preserved under `legacy-packages` and will be removed in 0.25. If the new strategy does not work
Expand Down
74 changes: 70 additions & 4 deletions src/lib/converter/comments/blockLexer.ts
@@ -1,13 +1,24 @@
import ts from "typescript";
import { Token, TokenSyntaxKind } from "./lexer";
import { ReflectionSymbolId } from "../../models/reflections/ReflectionSymbolId";
import { resolveAliasedSymbol } from "../utils/symbols";

export function* lexBlockComment(
file: string,
pos = 0,
end = file.length
end = file.length,
jsDoc: ts.JSDoc | undefined = undefined,
checker: ts.TypeChecker | undefined = undefined
): Generator<Token, undefined, undefined> {
// Wrapper around our real lex function to collapse adjacent text tokens.
let textToken: Token | undefined;
for (const token of lexBlockComment2(file, pos, end)) {
for (const token of lexBlockComment2(
file,
pos,
end,
getLinkTags(jsDoc),
checker
)) {
if (token.kind === TokenSyntaxKind.Text) {
if (textToken) {
textToken.text += token.text;
Expand All @@ -29,10 +40,33 @@ export function* lexBlockComment(
return;
}

function getLinkTags(
jsDoc: ts.JSDoc | undefined
): ReadonlyArray<ts.JSDocLink | ts.JSDocLinkCode | ts.JSDocLinkPlain> {
const result: (ts.JSDocLink | ts.JSDocLinkCode | ts.JSDocLinkPlain)[] = [];

if (!jsDoc || typeof jsDoc.comment !== "object") return result;

for (const part of jsDoc.comment) {
switch (part.kind) {
case ts.SyntaxKind.JSDocLink:
case ts.SyntaxKind.JSDocLinkCode:
case ts.SyntaxKind.JSDocLinkPlain:
result.push(part);
}
}

return result;
}

function* lexBlockComment2(
file: string,
pos: number,
end: number
end: number,
linkTags: ReadonlyArray<
ts.JSDocLink | ts.JSDocLinkCode | ts.JSDocLinkPlain
>,
checker: ts.TypeChecker | undefined
): Generator<Token, undefined, undefined> {
pos += 2; // Leading '/*'
end -= 2; // Trailing '*/'
Expand All @@ -57,6 +91,7 @@ function* lexBlockComment2(

let lineStart = true;
let braceStartsType = false;
let linkTagIndex = 0;

for (;;) {
if (pos >= end) {
Expand Down Expand Up @@ -206,7 +241,12 @@ function* lexBlockComment2(
(lookahead === end || /\s/.test(file[lookahead]))
) {
braceStartsType = true;
yield makeToken(TokenSyntaxKind.Tag, lookahead - pos);
const token = makeToken(
TokenSyntaxKind.Tag,
lookahead - pos
);
attachLinkTagResult(token);
yield token;
break;
}
}
Expand Down Expand Up @@ -263,6 +303,32 @@ function* lexBlockComment2(
}
}

function attachLinkTagResult(token: Token) {
// We might need to skip link tags if someone has link tags inside of an example comment
// pos-1 for opening brace, TS doesn't allow spaces between opening brace and @ sign as of 5.0.2
while (
linkTagIndex < linkTags.length &&
linkTags[linkTagIndex].pos < token.pos - 1
) {
linkTagIndex++;
}

if (
linkTagIndex < linkTags.length &&
linkTags[linkTagIndex].pos === token.pos - 1
) {
const link = linkTags[linkTagIndex];
if (link.name) {
const tsTarget = checker?.getSymbolAtLocation(link.name);
if (tsTarget) {
token.linkTarget = new ReflectionSymbolId(
resolveAliasedSymbol(tsTarget, checker!)
);
}
}
}
}

function makeToken(kind: TokenSyntaxKind, size: number): Token {
const start = pos;
pos += size;
Expand Down
53 changes: 41 additions & 12 deletions src/lib/converter/comments/discovery.ts
Expand Up @@ -85,17 +85,23 @@ const wantedKinds: Record<ReflectionKind, ts.SyntaxKind[]> = {
],
};

export interface DiscoveredComment {
file: ts.SourceFile;
ranges: ts.CommentRange[];
jsDoc: ts.JSDoc | undefined;
}

export function discoverComment(
symbol: ts.Symbol,
kind: ReflectionKind,
logger: Logger,
commentStyle: CommentStyle
): [ts.SourceFile, ts.CommentRange[]] | undefined {
): DiscoveredComment | undefined {
// For a module comment, we want the first one defined in the file,
// not the last one, since that will apply to the import or declaration.
const reverse = !symbol.declarations?.some(ts.isSourceFile);

const discovered: [ts.SourceFile, ts.CommentRange[]][] = [];
const discovered: DiscoveredComment[] = [];

for (const decl of symbol.declarations || []) {
const text = decl.getSourceFile().text;
Expand Down Expand Up @@ -135,7 +141,11 @@ export function discoverComment(
);

if (selectedDocComment) {
discovered.push([decl.getSourceFile(), selectedDocComment]);
discovered.push({
file: decl.getSourceFile(),
ranges: selectedDocComment,
jsDoc: findJsDocForComment(node, selectedDocComment),
});
}
}
}
Expand All @@ -149,9 +159,10 @@ export function discoverComment(
logger.warn(
`${symbol.name} has multiple declarations with a comment. An arbitrary comment will be used.`
);
const locations = discovered.map(([sf, [{ pos }]]) => {
const path = nicePath(sf.fileName);
const line = ts.getLineAndCharacterOfPosition(sf, pos).line + 1;
const locations = discovered.map(({ file, ranges: [{ pos }] }) => {
const path = nicePath(file.fileName);
const line =
ts.getLineAndCharacterOfPosition(file, pos).line + 1;
return `${path}:${line}`;
});
logger.info(
Expand All @@ -167,7 +178,7 @@ export function discoverComment(
export function discoverSignatureComment(
declaration: ts.SignatureDeclaration | ts.JSDocSignature,
commentStyle: CommentStyle
): [ts.SourceFile, ts.CommentRange[]] | undefined {
): DiscoveredComment | undefined {
const node = declarationToCommentNode(declaration);
if (!node) {
return;
Expand All @@ -177,16 +188,17 @@ export function discoverSignatureComment(
const comment = node.parent.parent;
ok(ts.isJSDoc(comment));

return [
node.getSourceFile(),
[
return {
file: node.getSourceFile(),
ranges: [
{
kind: ts.SyntaxKind.MultiLineCommentTrivia,
pos: comment.pos,
end: comment.end,
},
],
];
jsDoc: undefined,
};
}

const text = node.getSourceFile().text;
Expand All @@ -200,7 +212,24 @@ export function discoverSignatureComment(
permittedRange(text, ranges, commentStyle)
);
if (comment) {
return [node.getSourceFile(), comment];
return {
file: node.getSourceFile(),
ranges: comment,
jsDoc: findJsDocForComment(node, comment),
};
}
}

function findJsDocForComment(
node: ts.Node,
ranges: ts.CommentRange[]
): ts.JSDoc | undefined {
if (ranges[0].kind === ts.SyntaxKind.MultiLineCommentTrivia) {
const jsDocs = ts
.getJSDocCommentsAndTags(node)
.map((doc) => ts.findAncestor(doc, ts.isJSDoc)) as ts.JSDoc[];

return jsDocs.find((doc) => doc.pos === ranges[0].pos);
}
}

Expand Down

0 comments on commit e1dc6a7

Please sign in to comment.