Eslint-plugin-jsdoc: require-jsdoc fails to work with multiple function signatures in typescript

Created on 17 May 2020  路  16Comments  路  Source: gajus/eslint-plugin-jsdoc

In typescript, you can declare multiple function signature to aid type resolution.
However, require-jsdoc fails to work in this scenario. See the following example:

/**
 * This is OK
 * @param arg test
 * @returns arg
 */
function foo(arg: boolean): boolean {
  return arg;
}

/**
 * Missing JSDoc comment
 * @param arg test
 * @returns arg
 */
function bar(arg: true): true;
function bar(arg: false): false;
function bar(arg: boolean): boolean {
  return arg;
}
released

Most helpful comment

I've added d090e9f4d8806c98114905c7e8f35c0efea02ae2 which should address your first question.

For the second though, I'll show you how I am figuring this out. Take a look at https://github.com/gajus/eslint-plugin-jsdoc/#eslint-plugin-jsdoc-advanced-ast-and-selectors , a section I've just added.

The ESLint version on the AST Explorer tool I mention there is unfortunately outdated (version 4), and it doesn't seem to allow esquery selectors against the TypeScript syntax needed for the TSDeclareFunction example (though the parser doesn't show any errors). Still, you can see the resulting AST, and if you need to experiment with esquery syntax, you can try on some plain JavaScript.

All 16 comments

It's already possible, but I've added a test commit showing how. You need to add TSDeclareFunction to contexts.

@brettz9 adding TSDeclareFunction to contexts seems to do the opposite. 馃

With TSDeclareFunction, eslint complains about all 3 bar signatures, while it only complains about the real function when it's unset. Am I doing anything wrong?

/**
 * Options: []
 * @param arg test
 * @returns arg
 */
function bar(arg: true): true;
function bar(arg: false): false;
function bar(arg: boolean): boolean { // Missing JSDoc comment
  return arg;
}

/**
 * Options: [{"contexts":["TSDeclareFunction"]}]
 * @param arg test
 * @returns arg
 */
function bar(arg: true): true; // Missing JSDoc comment
function bar(arg: false): false; // Missing JSDoc comment
function bar(arg: boolean): boolean { // Missing JSDoc comment
  return arg;
}

I am not clear whether the examples you are giving are what you expect to occur or what occurs. Please distinguish between what is happening and what you want to happen.

If you have no jsdoc's on any of them, e.g.,:

/*
Options: [
        {
          contexts: [
            'TSDeclareFunction',
          ],
        },
      ]
*/

      function foo(arg: boolean): boolean {
        return arg;
      }
      function bar(arg: true): true;
      function bar(arg: false): false;
      function bar(arg: boolean): boolean {
        return arg;
      }

...you should get 4 errors (since the above also implies the default of require: {FunctionDeclaration: true}).

Are you saying you want there to be only one jsdoc block for the bar (above the first bar function signature)?

If so, I've added 3b5087f3b4d086fec411e4fafe7b810c2773822d to show how one can use esquery selectors (specifically the sibling and :not:() selectors).

:tada: This issue has been resolved in version 25.4.2 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

@brettz9 Sorry. Got it. Was confused about what contexts do.

But I'm not sure if I got a separate bug. It works for non-exported functions, but not exported functions:

// OK EXPECTED

/**
 * Options: {"FunctionDeclaration": false" "contexts": ["TSDeclareFunction:not(TSDeclareFunction + TSDeclareFunction)", "FunctionDeclaration:not(TSDeclareFunction + FunctionDeclaration)"]}
 * @param arg test
 */
function bar(arg: false): false;
function bar(arg: true): true;
function bar(arg: boolean): boolean {
  return arg;
}

// UNEXPECTED ERRORS

/**
 * Options: {"FunctionDeclaration": false" "contexts": ["TSDeclareFunction:not(TSDeclareFunction + TSDeclareFunction)", "FunctionDeclaration:not(TSDeclareFunction + FunctionDeclaration)"]}
 * @param arg test
 */
export function foo(arg: false): false; // Missing JSDoc comment
export function foo(arg: true): true; // Missing JSDoc comment
export function foo(arg: boolean): boolean { // Missing JSDoc comment
  return arg;
}

Also, I'm not sure how to get docs on methods work. e.g

// EXPECT NO ERROR :/

class Foo {
  /**
   * Options: {"MethodDefinition": false" "contexts": ["TSMethodSignature:not(TSMethodSignature + TSMethodSignature)", "MethodDefinition:not(TSMethodSignature + MethodDefinition)"]}
   * @param arg test
   */
  bar(arg: false): false; // Missing JSDoc comment
  bar(arg: true): true; // Missing JSDoc comment
  bar(arg: boolean): boolean { // Missing JSDoc comment
    return arg;
  }
}

It seems like that the signatures are parsed as MethodDefinition?

I've added d090e9f4d8806c98114905c7e8f35c0efea02ae2 which should address your first question.

For the second though, I'll show you how I am figuring this out. Take a look at https://github.com/gajus/eslint-plugin-jsdoc/#eslint-plugin-jsdoc-advanced-ast-and-selectors , a section I've just added.

The ESLint version on the AST Explorer tool I mention there is unfortunately outdated (version 4), and it doesn't seem to allow esquery selectors against the TypeScript syntax needed for the TSDeclareFunction example (though the parser doesn't show any errors). Still, you can see the resulting AST, and if you need to experiment with esquery syntax, you can try on some plain JavaScript.

@brettz9 Thanks much for the notes. It's much clear how it works now. :+1:
Let me do some exploration and sharing my finding later.

Just want to share my config in case someone like me just wants JSDocs checking the first function/method declaration only

require:
  FunctionDeclaration: false
  MethodDefinition: false
contexts:
  # for non-exported functions
  - Program > TSDeclareFunction:not(TSDeclareFunction + TSDeclareFunction)
  - Program > FunctionDeclaration:not(TSDeclareFunction + FunctionDeclaration)
  # for exported functions
  - ExportNamedDeclaration[declaration.type=TSDeclareFunction]:not(ExportNamedDeclaration[declaration.type=TSDeclareFunction]
    + ExportNamedDeclaration[declaration.type=TSDeclareFunction])
  - ExportNamedDeclaration[declaration.type=FunctionDeclaration]:not(ExportNamedDeclaration[declaration.type=TSDeclareFunction]
    + ExportNamedDeclaration[declaration.type=FunctionDeclaration])
  # for class methods
  - MethodDefinition[value.body=null]:not(MethodDefinition[value.body=null] + MethodDefinition[value.body=null])
  - MethodDefinition[value.body]:not(MethodDefinition[value.body=null] + MethodDefinition)
/**
 * NO ERROR
 * @param arg description
 */
function fn0(arg: boolean): boolean {
  return arg;
}

/**
 * NO ERROR
 * @param arg description
 */
function fn1(arg: false): false;
function fn1(arg: true): true;
function fn1(arg: boolean): boolean {
  return arg;
}

function fn2(arg: boolean): boolean { // Missing JSDoc comment
  return arg;
}

function fn3(arg: false): false; // Missing JSDoc comment
function fn3(arg: true): true;
function fn3(arg: boolean): boolean {
  return arg;
}

class Foo {
  /**
   * NO ERROR
   * @param arg test
   */
  fn1(arg: false): false;
  fn1(arg: true): true;
  fn1(arg: boolean): boolean {
    return arg;
  }

  fn2(arg: false): false; // Missing JSDoc comment
  fn2(arg: true): true;
  fn2(arg: boolean): boolean {
    return arg;
  }
}

PROTIPS: as suggested by @brettz9, you can customise the selection by using esquery syntax with an AST parsed by your configured parser. In my case, I use @typescript-eslint/parser which uses @typescript-eslint/typescript-estree for generating the AST. To print the AST, you can use the following example code:

import { parse } from '@typescript-eslint/typescript-estree';

console.dir(parse(`
// your code here
`), {depth: 10});

Excellent, thanks very much for reporting back with your findings!

Btw, as I've just added mention on the README, you can also use https://github.com/brettz9/eslint-plugin-query to get a deeper understanding of your projects' AST.

Though it also works as a plugin, one can now use it with its own CLI, e.g., to search your files for matching esquery selectors, optionally showing as AST JSON. If installed globally, you can do the following within a project, for example:

esq -q FunctionDeclaration -q FunctionExpression

or target a specific file:

esq -q  ClassDeclaration -i test/myFile.js

...and optionally slicing characters so it doesn't show too much context:

esq -q FunctionDeclaration --end 100

Such queries show results like this (though marked as "errors", they don't need to be--it is just because the plugin uses the ESLint formatter):

image

Ideally there'd be a way to make such queries directly in one's IDE, and in many cases https://astexplorer.net/ will be easier for introspection, but when installed globally, this plugin may help one find AST features in one's files.

(The CLI version was just released, so though it is tested, it may have bugs.)

Btw, note that this syntax won't work if you have a different intervening name, as the example given will not complain here:

      export function bar(arg: true): true;
      export function foo(arg: false): false;
      export function bar(arg: boolean): boolean {
        return arg;
      }

query is quite an interesting plugin as it effectively allows any arbitrary rules to be developed in a fraction of typically development time! :+1:

As for the last example, I'm not worried since typescript would have raised errors at the first place as it's not allowed.
In this case, the following 2 errors from ts are observed

  • TS2391: Function implementation is missing or not immediately following the declaration.
  • TS2389: Function implementation name must be 'foo'.
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ZacBrownBand picture ZacBrownBand  路  6Comments

MaxMilton picture MaxMilton  路  4Comments

OmgImAlexis picture OmgImAlexis  路  5Comments

hipstersmoothie picture hipstersmoothie  路  3Comments

yvele picture yvele  路  5Comments