Skip to content

Js doc overloads - #50789

Closed
Tomasz Lenarcik (apendua) wants to merge 4 commits into
microsoft:mainfrom
apendua:js-doc-overloads
Closed

Js doc overloads#50789
Tomasz Lenarcik (apendua) wants to merge 4 commits into
microsoft:mainfrom
apendua:js-doc-overloads

Conversation

@apendua

Copy link
Copy Markdown
Contributor

Fixes #25590

Since there was no final consensus, I ended up using a new @overload tag to ensure backwards compatibility, e.g.

/**
 * @overload
 * @param {number} x
 * @returns {'number'}
 */
/**
 * @overload
 * @param {string} x
 * @returns {'string'}
 */
/**
 * @overload
 * @param {boolean} x
 * @returns {'boolean'}
 */
/**
 * @param {unknown} x
 * @returns {string}
 */
function typeName(x) {
  return typeof x;
}

I would be happy to modify the implementation if someone suggests a better approach.

@typescript-bot TypeScript Bot (typescript-bot) added the For Backlog Bug PRs that fix a backlog bug label Sep 15, 2022
@ghost

Deleted user (ghost) commented Sep 15, 2022

Copy link
Copy Markdown

CLA assistant check
All CLA requirements met.

@ValeTheVioletMote

Copy link
Copy Markdown

Looking forward to this!

Comment thread src/compiler/emitter.ts

function hasTrailingCommentsAtPosition(pos: number) {
let result = false;
forEachTrailingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are these additional ends needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nathan Shively-Sanders (@sandersn) I did this because I wanted to avoid JSDoc nodes duplication in .d.ts file, e.g.

// file.js
/**
 * @overload
 * @param {string} value
 * @return {string}
 */
/**
 * @overload
 * @param {number} value
 * @return {number}
 */
/**
 * @overload
 * @param {unknown} value
 */
function myFunction(value) {}

would result in:

// file.d.ts
/**
 * @overload
 * @param {string} value
 */
declare function myFunction(value: string): string;
/**
 * @overload
 * @param {number} value
 */
declare function myFunction(value: number): number;
/**
 * @overload
 * @param {string} value
 */
/**
 * @overload
 * @param {number} value
 */
/**
 * @overload
 * @param {unknown} value
 */
declare function myFunction(value);

Being able to specify end allowed me to limit the JSDocs that are emitted for a given parent node.

However this seems to only be one of many weird consequences of synthesizing declarations, which as you suggested does not seem to be the proper approach anyway.

Comment thread src/compiler/scanner.ts
* return value of the callback.
*/
function iterateCommentRanges<T, U>(reduce: boolean, text: string, pos: number, trailing: boolean, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U | undefined) => U, state: T, initial?: U): U | undefined {
function iterateCommentRanges<T, U>(reduce: boolean, text: string, pos: number, end: number | undefined, trailing: boolean, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U | undefined) => U, state: T, initial?: U): U | undefined {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • same question about end in the file
  • extra space typo:
Suggested change
function iterateCommentRanges<T, U>(reduce: boolean, text: string, pos: number, end: number | undefined, trailing: boolean, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U | undefined) => U, state: T, initial?: U): U | undefined {
function iterateCommentRanges<T, U>(reduce: boolean, text: string, pos: number, end: number | undefined, trailing: boolean, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U | undefined) => U, state: T, initial?: U): U | undefined {

Comment thread src/services/utilities.ts

export function copyLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment));
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, /*end*/ undefined, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question about end here


declare namespace ts {
const versionMajorMinor = "4.9";
const versionMajorMinor = "4.8";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to merge from main at some point

@@ -0,0 +1,50 @@
// @allowJs: true
// @noEmit: true
// @filename: 0.js

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// @filename: 0.js
// @filename: jsFileMethodsOverloads.js

@@ -0,0 +1,61 @@
// @allowJs: true
// @noEmit: true
// @filename: 0.js

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// @filename: 0.js
// @filename: jsFileFunctionOverloads.js

@@ -0,0 +1,50 @@
// @allowJs: true
// @noEmit: true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use // @outDir: dist/ and // @declaration: true to make sure that js->d.ts conversion works too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nathan Shively-Sanders (@sandersn) I tried this but didn't see any changes in the baseline files. What outcome I should be observing?

@sandersn Nathan Shively-Sanders (sandersn) Oct 18, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you remove @noEmit: true? You should see a baseline named jsFileMethodsOverloads.js and its last section should be a .d.ts file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nathan Shively-Sanders (@sandersn) Yup, that was it. Seems like it's working fine now. Thank you!

Comment thread src/compiler/binder.ts
const newParameters: ParameterDeclaration[] = [];
for (const tag of jsDoc.tags) {
if (isJSDocParameterTag(tag) && tag.name.kind === SyntaxKind.Identifier) {
const newParameter = factory.createParameterDeclaration(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Synthesising declarations is not a technique that works in the typescript compiler. A better approach would be to make sure all the jsdoc nodes are still available to the checker. In the checker, make sure that each @overload results in a signature. You'll want to start near getSignatureFromDeclaration, or its callers, I think.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nathan Shively-Sanders (@sandersn) Agreed. I've also observed other BUGs related to IDE integrations that are coming from the fact that I am trying to synthesize those declarations, e.g. "did not expect identifier in trivia".

You'll want to start near getSignatureFromDeclaration, or its callers, I think.

Thank you for this suggestion! I've already tried that and it seems to be working much better:

https://github.com/microsoft/TypeScript/compare/main...apendua:js-doc-overload-tag?expand=1

How we should go about that? Should I close this pull request and open a new one?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is up to you. I am fine with proceeding with this one; I squash merge PRs since it makes the git history easier to search.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nathan Shively-Sanders (@sandersn) I ended up opening a new PR here: #51234

I also used this opportunity to rebase onto the latest main. Thank you again for all your suggestions!

@apendua

Copy link
Copy Markdown
Contributor Author

Closing this PR in favor of: #51234

@microsoft Microsoft (microsoft) locked as resolved and limited conversation to collaborators Oct 22, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

For Backlog Bug PRs that fix a backlog bug

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

In JS, jsdoc should be able to declare functions as overloaded

4 participants