Skip to content

⬅️ Back to Table of Contents

📄 ban-ts-comment

📊 Analysis Summary

Metric Count
🔧 Functions 3
📦 Imports 7
📊 Variables & Constants 1
📐 Interfaces 2
📑 Type Aliases 3

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/ban-ts-comment.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'ban-ts-comment'
meta.type 'problem'
meta.docs.description 'Disallow @ts-<directive> comments or require descriptions after directives'
meta.docs.recommended.recommended true
meta.docs.recommended.strict [{ minimumDescriptionLength: 10 }]
meta.hasSuggestions true
meta.messages.replaceTsIgnoreWithTsExpectError 'Replace "@ts-ignore" with "@ts-expect-error".'
meta.messages.tsDirectiveComment 'Do not use "@ts-{{directive}}" because it alters compilation errors.'
meta.messages.tsDirectiveCommentDescriptionNotMatchPattern 'The description for the "@ts-{{directive}}" directive must match the {{format}} format.'
meta.messages.tsDirectiveCommentRequiresDescription 'Include a description after the "@ts-{{directive}}" directive to explain why the @ts-{{directive}} is necessary. The...
meta.messages.tsIgnoreInsteadOfExpectError 'Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free.'
meta.schema [ { type: 'object', $defs: { directiveConfigSchema: { oneOf: [ { type: 'boolean', default: true, }, { type: 'string',...
defaultOptions [ { minimumDescriptionLength: defaultMinimumDescriptionLength, 'ts-check': false, 'ts-expect-error': 'allow-with-desc...

Entry point: create — documented under Functions.


📦 Imports

Name Source
TSESLint @typescript-eslint/utils
TSESTree @typescript-eslint/utils
AST_TOKEN_TYPES @typescript-eslint/utils
ASTUtils @typescript-eslint/utils
createRule ../util
getStringLength ../util
nullThrows ../util

Variables & Constants

Name Type Kind Value Exported
defaultMinimumDescriptionLe... 3 const 3

Functions

create(context: any, [options]: any): { Program(node: any): void; }

Parameters:

  • context any
  • [options] any

Returns: { Program(node: any): void; }

Calls:

  • descriptionFormats.set
  • regex.exec
  • nullThrows (from ../util)
  • execDirectiveRegEx
  • comment.value.split
  • node.body.at
  • context.sourceCode.getAllComments
  • comments.forEach
  • findDirectiveInComment
  • context.report
  • comment.value.replace
  • fixer.replaceText
  • descriptionFormats.get
  • getStringLength (from ../util)
  • description.trim
  • format.test

Internal Comments:

// https://github.com/microsoft/TypeScript/blob/6f1ad5ad8bec5671f7e951a3524b62d82ec4be68/src/compiler/parser.ts#L10591 (x2)
/*
      The regex used are taken from the ones used in the official TypeScript repo -
      https://github.com/microsoft/TypeScript/blob/6f1ad5ad8bec5671f7e951a3524b62d82ec4be68/src/compiler/scanner.ts#L340-L348
    */ (x2)
// Special case to suggest @ts-expect-error instead of @ts-ignore (x4)

Code
create(context, [options]) {
    // https://github.com/microsoft/TypeScript/blob/6f1ad5ad8bec5671f7e951a3524b62d82ec4be68/src/compiler/parser.ts#L10591
    const singleLinePragmaRegEx =
      /^\/\/\/?\s*@ts-(?<directive>check|nocheck)(?<description>.*)$/;

    /*
      The regex used are taken from the ones used in the official TypeScript repo -
      https://github.com/microsoft/TypeScript/blob/6f1ad5ad8bec5671f7e951a3524b62d82ec4be68/src/compiler/scanner.ts#L340-L348
    */
    const commentDirectiveRegExSingleLine =
      /^\/*\s*@ts-(?<directive>expect-error|ignore)(?<description>.*)/;
    const commentDirectiveRegExMultiLine =
      /^\s*(?:\/|\*)*\s*@ts-(?<directive>expect-error|ignore)(?<description>.*)/;

    const descriptionFormats = new Map<string, RegExp>();
    for (const directive of [
      'ts-expect-error',
      'ts-ignore',
      'ts-nocheck',
      'ts-check',
    ] as const) {
      const option = options[directive];
      if (typeof option === 'object' && option.descriptionFormat) {
        descriptionFormats.set(directive, new RegExp(option.descriptionFormat));
      }
    }

    function execDirectiveRegEx(
      regex: RegExp,
      str: string,
    ): MatchedTSDirective | null {
      const match = regex.exec(str);
      if (!match) {
        return null;
      }

      const { description, directive } = nullThrows(
        match.groups,
        'RegExp should contain groups',
      );
      return {
        description: nullThrows(
          description,
          'RegExp should contain "description" group',
        ),
        directive: nullThrows(
          directive,
          'RegExp should contain "directive" group',
        ),
      };
    }

    function findDirectiveInComment(
      comment: TSESTree.Comment,
    ): MatchedTSDirective | null {
      if (comment.type === AST_TOKEN_TYPES.Line) {
        const matchedPragma = execDirectiveRegEx(
          singleLinePragmaRegEx,
          `//${comment.value}`,
        );
        if (matchedPragma) {
          return matchedPragma;
        }

        return execDirectiveRegEx(
          commentDirectiveRegExSingleLine,
          comment.value,
        );
      }

      const commentLines = comment.value.split(ASTUtils.LINEBREAK_MATCHER);
      return execDirectiveRegEx(
        commentDirectiveRegExMultiLine,
        commentLines[commentLines.length - 1],
      );
    }

    return {
      Program(node): void {
        const firstStatement = node.body.at(0);

        const comments = context.sourceCode.getAllComments();

        comments.forEach(comment => {
          const match = findDirectiveInComment(comment);
          if (!match) {
            return;
          }
          const { description, directive } = match;

          if (
            directive === 'nocheck' &&
            firstStatement &&
            firstStatement.loc.start.line <= comment.loc.start.line
          ) {
            return;
          }

          const fullDirective = `ts-${directive}` as keyof OptionsShape;

          const option = options[fullDirective];
          if (option === true) {
            if (directive === 'ignore') {
              // Special case to suggest @ts-expect-error instead of @ts-ignore
              context.report({
                node: comment,
                messageId: 'tsIgnoreInsteadOfExpectError',
                suggest: [
                  {
                    messageId: 'replaceTsIgnoreWithTsExpectError',
                    fix(fixer): TSESLint.RuleFix {
                      const commentText = comment.value.replace(
                        /@ts-ignore/,
                        '@ts-expect-error',
                      );
                      return fixer.replaceText(
                        comment,
                        comment.type === AST_TOKEN_TYPES.Line
                          ? `//${commentText}`
                          : `/*${commentText}*/`,
                      );
                    },
                  },
                ],
              });
            } else {
              context.report({
                node: comment,
                messageId: 'tsDirectiveComment',
                data: { directive },
              });
            }
          }

          if (
            option === 'allow-with-description' ||
            (typeof option === 'object' && option.descriptionFormat)
          ) {
            const { minimumDescriptionLength } = options;
            const format = descriptionFormats.get(fullDirective);
            if (
              getStringLength(description.trim()) <
              nullThrows(
                minimumDescriptionLength,
                'Expected minimumDescriptionLength to be set',
              )
            ) {
              context.report({
                node: comment,
                messageId: 'tsDirectiveCommentRequiresDescription',
                data: { directive, minimumDescriptionLength },
              });
            } else if (format && !format.test(description)) {
              context.report({
                node: comment,
                messageId: 'tsDirectiveCommentDescriptionNotMatchPattern',
                data: { directive, format: format.source },
              });
            }
          }
        });
      },
    };
  }

Internal helpers

Declared inside another function in this file.

execDirectiveRegEx(regex: RegExp, str: string): MatchedTSDirective | null

Parameters:

  • regex RegExp
  • str string

Returns: MatchedTSDirective | null

Calls:

  • regex.exec
  • nullThrows (from ../util)
Code
function execDirectiveRegEx(
      regex: RegExp,
      str: string,
    ): MatchedTSDirective | null {
      const match = regex.exec(str);
      if (!match) {
        return null;
      }

      const { description, directive } = nullThrows(
        match.groups,
        'RegExp should contain groups',
      );
      return {
        description: nullThrows(
          description,
          'RegExp should contain "description" group',
        ),
        directive: nullThrows(
          directive,
          'RegExp should contain "directive" group',
        ),
      };
    }

findDirectiveInComment(comment: TSESTree.Comment): MatchedTSDirective | null

Parameters:

  • comment TSESTree.Comment

Returns: MatchedTSDirective | null

Calls:

  • execDirectiveRegEx
  • comment.value.split
Code
function findDirectiveInComment(
      comment: TSESTree.Comment,
    ): MatchedTSDirective | null {
      if (comment.type === AST_TOKEN_TYPES.Line) {
        const matchedPragma = execDirectiveRegEx(
          singleLinePragmaRegEx,
          `//${comment.value}`,
        );
        if (matchedPragma) {
          return matchedPragma;
        }

        return execDirectiveRegEx(
          commentDirectiveRegExSingleLine,
          comment.value,
        );
      }

      const commentLines = comment.value.split(ASTUtils.LINEBREAK_MATCHER);
      return execDirectiveRegEx(
        commentDirectiveRegExMultiLine,
        commentLines[commentLines.length - 1],
      );
    }

Interfaces

OptionsShape

Interface Code
export interface OptionsShape {
  minimumDescriptionLength?: number;
  'ts-check'?: DirectiveConfig;
  'ts-expect-error'?: DirectiveConfig;
  'ts-ignore'?: DirectiveConfig;
  'ts-nocheck'?: DirectiveConfig;
}

Properties

Name Type Optional Description
minimumDescriptionLength number not shown
'ts-check' DirectiveConfig not shown
'ts-expect-error' DirectiveConfig not shown
'ts-ignore' DirectiveConfig not shown
'ts-nocheck' DirectiveConfig not shown

MatchedTSDirective

Interface Code
interface MatchedTSDirective {
  description: string;
  directive: string;
}

Properties

Name Type Optional Description
description string not shown
directive string not shown

Type Aliases

DirectiveConfig

type DirectiveConfig = boolean | 'allow-with-description' | { descriptionFormat: string };

Options

type Options = [OptionsShape];

MessageIds

type MessageIds = | 'replaceTsIgnoreWithTsExpectError'
  | 'tsDirectiveComment'
  | 'tsDirectiveCommentDescriptionNotMatchPattern'
  | 'tsDirectiveCommentRequiresDescription'
  | 'tsIgnoreInsteadOfExpectError';

Generated by Syntax Scribe