Skip to content

⬅️ Back to Table of Contents

πŸ“„ method-signature-style

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 8
πŸ“¦ Imports 10
πŸ“‘ Type Aliases 2

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/method-signature-style.ts

πŸ“€ Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'method-signature-style'
meta.type 'suggestion'
meta.docs.description 'Enforce using a particular method signature syntax'
meta.fixable 'code'
meta.hasSuggestions true
meta.messages.convertToMethodSignature 'Convert to a method signature. This removes the readonly modifier, allowing the member to be reassigned.'
meta.messages.errorMethod 'Shorthand method signature is forbidden. Use a function property instead.'
meta.messages.errorProperty 'Function property signature is forbidden. Use a method shorthand instead.'
meta.schema [ { type: 'string', description: 'The method signature style to enforce using.', enum: ['property', 'method'], }, ]
defaultOptions ['property']

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
TSESTree @typescript-eslint/utils
ReportFixFunction @typescript-eslint/utils/ts-eslint
AST_NODE_TYPES @typescript-eslint/utils
createRule ../util
forEachChildESTree ../util
isClosingParenToken ../util
isCommaToken ../util
isOpeningParenToken ../util
isSemicolonToken ../util
nullThrows ../util

Functions

create(context: any, [mode]: any): { TSPropertySignature(propertyNode: any): void; TSMethodSig…

Parameters:

  • context any
  • [mode] any

Returns: { TSPropertySignature(propertyNode: any): void; TSMethodSignature(methodNode: any): void; }

Calls:

  • context.sourceCode.getText
  • nullThrows (from ../util)
  • context.sourceCode.getTokenBefore
  • context.sourceCode.getTokenAfter
  • context.sourceCode.text.substring
  • context.sourceCode.getLastToken
  • isSemicolonToken (from ../util)
  • isCommaToken (from ../util)
  • isNodeParentModuleDeclaration
  • returnTypeReferencesThisType
  • members.filter
  • getMethodKey
  • context.report
  • [ methodNode, ...duplicatedKeyMethodNodes, ].sort
  • methodNodes .map(node => { const params = getMethodParams(node); const returnType = getMethodReturnType(node); return(${params} => ${returnType}); }) .join
  • getDelimiter
  • fixer.replaceText
  • fixer.remove
  • fixer.replaceTextRange
  • getMethodParams
  • getMethodReturnType

Internal Comments:

// we just make it explicit here so we can do the fix
// There is no syntax for a `readonly` method signature, so converting
// a `readonly` function-typed property drops the `readonly` modifier.
// That is a behavioral change (a method may be reassigned, a
// `readonly` property may not), so it is offered as a suggestion
// rather than applied as an autofix.

Code
create(context, [mode]) {
    function getMethodKey(
      node: TSESTree.TSMethodSignature | TSESTree.TSPropertySignature,
    ): string {
      let key = context.sourceCode.getText(node.key);
      if (node.computed) {
        key = `[${key}]`;
      }
      if (node.optional) {
        key = `${key}?`;
      }
      return key;
    }

    function getMethodParams(
      node: TSESTree.TSFunctionType | TSESTree.TSMethodSignature,
    ): string {
      let params = '()';
      if (node.params.length > 0) {
        const openingParen = nullThrows(
          context.sourceCode.getTokenBefore(
            node.params[0],
            isOpeningParenToken,
          ),
          'Missing opening paren before first parameter',
        );
        const closingParen = nullThrows(
          context.sourceCode.getTokenAfter(
            node.params[node.params.length - 1],
            isClosingParenToken,
          ),
          'Missing closing paren after last parameter',
        );

        params = context.sourceCode.text.substring(
          openingParen.range[0],
          closingParen.range[1],
        );
      }
      if (node.typeParameters != null) {
        const typeParams = context.sourceCode.getText(node.typeParameters);
        params = `${typeParams}${params}`;
      }
      return params;
    }

    function getMethodReturnType(
      node: TSESTree.TSFunctionType | TSESTree.TSMethodSignature,
    ): string {
      return node.returnType == null
        ? // if the method has no return type, it implicitly has an `any` return type
          // we just make it explicit here so we can do the fix
          'any'
        : context.sourceCode.getText(node.returnType.typeAnnotation);
    }

    function getDelimiter(node: TSESTree.Node): string {
      const lastToken = context.sourceCode.getLastToken(node);
      if (
        lastToken &&
        (isSemicolonToken(lastToken) || isCommaToken(lastToken))
      ) {
        return lastToken.value;
      }

      return '';
    }

    function isNodeParentModuleDeclaration(node: TSESTree.Node): boolean {
      if (!node.parent) {
        return false;
      }

      if (node.parent.type === AST_NODE_TYPES.TSModuleDeclaration) {
        return true;
      }

      if (node.parent.type === AST_NODE_TYPES.Program) {
        return false;
      }
      return isNodeParentModuleDeclaration(node.parent);
    }

    return {
      ...(mode === 'property' && {
        TSMethodSignature(methodNode): void {
          if (methodNode.kind !== 'method') {
            return;
          }

          const skipFix = returnTypeReferencesThisType(methodNode.returnType);
          const parent = methodNode.parent;
          const members =
            parent.type === AST_NODE_TYPES.TSInterfaceBody
              ? parent.body
              : parent.members;

          const duplicatedKeyMethodNodes: TSESTree.TSMethodSignature[] =
            members.filter(
              (element): element is TSESTree.TSMethodSignature =>
                element.type === AST_NODE_TYPES.TSMethodSignature &&
                element !== methodNode &&
                getMethodKey(element) === getMethodKey(methodNode),
            );
          const isParentModule = isNodeParentModuleDeclaration(methodNode);

          if (duplicatedKeyMethodNodes.length > 0) {
            if (isParentModule) {
              context.report({
                node: methodNode,
                messageId: 'errorMethod',
              });
            } else {
              context.report({
                node: methodNode,
                messageId: 'errorMethod',
                fix: skipFix
                  ? undefined
                  : function* fix(fixer) {
                      const methodNodes = [
                        methodNode,
                        ...duplicatedKeyMethodNodes,
                      ].sort((a, b) => (a.range[0] < b.range[0] ? -1 : 1));
                      const typeString = methodNodes
                        .map(node => {
                          const params = getMethodParams(node);
                          const returnType = getMethodReturnType(node);
                          return `(${params} => ${returnType})`;
                        })
                        .join(' & ');
                      const key = getMethodKey(methodNode);
                      const delimiter = getDelimiter(methodNode);
                      yield fixer.replaceText(
                        methodNode,
                        `${key}: ${typeString}${delimiter}`,
                      );
                      for (const node of duplicatedKeyMethodNodes) {
                        const lastToken = context.sourceCode.getLastToken(node);
                        if (lastToken) {
                          const nextToken =
                            context.sourceCode.getTokenAfter(lastToken);
                          if (nextToken) {
                            yield fixer.remove(node);
                            yield fixer.replaceTextRange(
                              [lastToken.range[1], nextToken.range[0]],
                              '',
                            );
                          }
                        }
                      }
                    },
              });
            }
            return;
          }

          if (isParentModule) {
            context.report({
              node: methodNode,
              messageId: 'errorMethod',
            });
          } else {
            context.report({
              node: methodNode,
              messageId: 'errorMethod',
              fix: skipFix
                ? undefined
                : fixer => {
                    const key = getMethodKey(methodNode);
                    const params = getMethodParams(methodNode);
                    const returnType = getMethodReturnType(methodNode);
                    const delimiter = getDelimiter(methodNode);
                    return fixer.replaceText(
                      methodNode,
                      `${key}: ${params} => ${returnType}${delimiter}`,
                    );
                  },
            });
          }
        },
      }),
      ...(mode === 'method' && {
        TSPropertySignature(propertyNode): void {
          const typeNode = propertyNode.typeAnnotation?.typeAnnotation;
          if (typeNode?.type !== AST_NODE_TYPES.TSFunctionType) {
            return;
          }

          const fix: ReportFixFunction = fixer => {
            const key = getMethodKey(propertyNode);
            const params = getMethodParams(typeNode);
            const returnType = getMethodReturnType(typeNode);
            const delimiter = getDelimiter(propertyNode);
            return fixer.replaceText(
              propertyNode,
              `${key}${params}: ${returnType}${delimiter}`,
            );
          };

          // There is no syntax for a `readonly` method signature, so converting
          // a `readonly` function-typed property drops the `readonly` modifier.
          // That is a behavioral change (a method may be reassigned, a
          // `readonly` property may not), so it is offered as a suggestion
          // rather than applied as an autofix.
          if (propertyNode.readonly) {
            context.report({
              node: propertyNode,
              messageId: 'errorProperty',
              suggest: [{ messageId: 'convertToMethodSignature', fix }],
            });
            return;
          }

          context.report({
            node: propertyNode,
            messageId: 'errorProperty',
            fix,
          });
        },
      }),
    };
  }

returnTypeReferencesThisType(node: TSESTree.TSTypeAnnotation | undefined): boolean

Parameters:

  • node TSESTree.TSTypeAnnotation | undefined

Returns: boolean

Calls:

  • forEachChildESTree (from ../util)
Code
function returnTypeReferencesThisType(
  node: TSESTree.TSTypeAnnotation | undefined,
) {
  return (
    node &&
    forEachChildESTree(
      node.typeAnnotation,
      child => child.type === AST_NODE_TYPES.TSThisType,
    )
  );
}

Internal helpers

Declared inside another function in this file.

getMethodKey(node: TSESTree.TSMethodSignature | TSESTree.T…): string

Parameters:

  • node TSESTree.TSMethodSignature | TSESTree.TSPropertySignature

Returns: string

Calls:

  • context.sourceCode.getText
Code
function getMethodKey(
      node: TSESTree.TSMethodSignature | TSESTree.TSPropertySignature,
    ): string {
      let key = context.sourceCode.getText(node.key);
      if (node.computed) {
        key = `[${key}]`;
      }
      if (node.optional) {
        key = `${key}?`;
      }
      return key;
    }

getMethodParams(node: TSESTree.TSFunctionType | TSESTree.TSMe…): string

Parameters:

  • node TSESTree.TSFunctionType | TSESTree.TSMethodSignature

Returns: string

Calls:

  • nullThrows (from ../util)
  • context.sourceCode.getTokenBefore
  • context.sourceCode.getTokenAfter
  • context.sourceCode.text.substring
  • context.sourceCode.getText
Code
function getMethodParams(
      node: TSESTree.TSFunctionType | TSESTree.TSMethodSignature,
    ): string {
      let params = '()';
      if (node.params.length > 0) {
        const openingParen = nullThrows(
          context.sourceCode.getTokenBefore(
            node.params[0],
            isOpeningParenToken,
          ),
          'Missing opening paren before first parameter',
        );
        const closingParen = nullThrows(
          context.sourceCode.getTokenAfter(
            node.params[node.params.length - 1],
            isClosingParenToken,
          ),
          'Missing closing paren after last parameter',
        );

        params = context.sourceCode.text.substring(
          openingParen.range[0],
          closingParen.range[1],
        );
      }
      if (node.typeParameters != null) {
        const typeParams = context.sourceCode.getText(node.typeParameters);
        params = `${typeParams}${params}`;
      }
      return params;
    }

getMethodReturnType(node: TSESTree.TSFunctionType | TSESTree.TSMe…): string

Parameters:

  • node TSESTree.TSFunctionType | TSESTree.TSMethodSignature

Returns: string

Calls:

  • context.sourceCode.getText

Internal Comments:

// we just make it explicit here so we can do the fix

Code
function getMethodReturnType(
      node: TSESTree.TSFunctionType | TSESTree.TSMethodSignature,
    ): string {
      return node.returnType == null
        ? // if the method has no return type, it implicitly has an `any` return type
          // we just make it explicit here so we can do the fix
          'any'
        : context.sourceCode.getText(node.returnType.typeAnnotation);
    }

getDelimiter(node: TSESTree.Node): string

Parameters:

  • node TSESTree.Node

Returns: string

Calls:

  • context.sourceCode.getLastToken
  • isSemicolonToken (from ../util)
  • isCommaToken (from ../util)
Code
function getDelimiter(node: TSESTree.Node): string {
      const lastToken = context.sourceCode.getLastToken(node);
      if (
        lastToken &&
        (isSemicolonToken(lastToken) || isCommaToken(lastToken))
      ) {
        return lastToken.value;
      }

      return '';
    }

isNodeParentModuleDeclaration(node: TSESTree.Node): boolean

Parameters:

  • node TSESTree.Node

Returns: boolean

Calls:

  • isNodeParentModuleDeclaration
Code
function isNodeParentModuleDeclaration(node: TSESTree.Node): boolean {
      if (!node.parent) {
        return false;
      }

      if (node.parent.type === AST_NODE_TYPES.TSModuleDeclaration) {
        return true;
      }

      if (node.parent.type === AST_NODE_TYPES.Program) {
        return false;
      }
      return isNodeParentModuleDeclaration(node.parent);
    }

fix(fixer: any): any

Parameters:

  • fixer any

Returns: any

Calls:

  • getMethodKey
  • getMethodParams
  • getMethodReturnType
  • getDelimiter
  • fixer.replaceText
Code
fixer => {
            const key = getMethodKey(propertyNode);
            const params = getMethodParams(typeNode);
            const returnType = getMethodReturnType(typeNode);
            const delimiter = getDelimiter(propertyNode);
            return fixer.replaceText(
              propertyNode,
              `${key}${params}: ${returnType}${delimiter}`,
            );
          }

Type Aliases

Options

type Options = [('method' | 'property')?];

MessageIds

type MessageIds = 'convertToMethodSignature' | 'errorMethod' | 'errorProperty';

Generated by Syntax Scribe