From d55211caf63e4731f103e94237b3449e88322bb9 Mon Sep 17 00:00:00 2001 From: Hayden Date: Sun, 12 Mar 2023 22:34:21 -0700 Subject: [PATCH] feat(eslint-plugin): [lines-around-comment] add extension rule (#5327) * replicate eslint rule and add base test case * port the eslint lines-around-comment * check for comment in interface and allow options * check for comment in type literals and allow options * add lines-around-comment in index * add lines-around-comment in all config * add lines-around-comment doc * delegate non-TS check to the base rule * fix lint errors * fix doc lint errors * address comments * address jsdoc comments * proper comment language * Update packages/eslint-plugin/src/rules/lines-around-comment.ts Co-authored-by: Brad Zacher * remove irrevalant lines and improve test cov * skip more cases * add enum and module tests * implement enum and module * improve coverage * remove ESilnt base rule test casese * improve coverage --------- Co-authored-by: Brad Zacher Co-authored-by: Josh Goldberg --- .../docs/rules/lines-around-comment.md | 41 + packages/eslint-plugin/src/configs/all.ts | 2 + packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/lines-around-comment.ts | 456 +++++++ .../src/util/getESLintCoreRule.ts | 1 + .../tests/rules/lines-around-comment.test.ts | 1053 +++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 38 + 7 files changed, 1593 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/lines-around-comment.md create mode 100644 packages/eslint-plugin/src/rules/lines-around-comment.ts create mode 100644 packages/eslint-plugin/tests/rules/lines-around-comment.test.ts diff --git a/packages/eslint-plugin/docs/rules/lines-around-comment.md b/packages/eslint-plugin/docs/rules/lines-around-comment.md new file mode 100644 index 00000000000..60250323032 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/lines-around-comment.md @@ -0,0 +1,41 @@ +--- +description: 'Require empty lines around comments.' +--- + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/lines-around-comment** for documentation. + +## Rule Details + +This rule extends the base [`eslint/lines-around-comment`](https://eslint.org/docs/rules/lines-around-comment) rule. +It adds support for TypeScript syntax. + +See the [ESLint documentation](https://eslint.org/docs/rules/lines-around-comment) for more details on the `comma-dangle` rule. + +## Rule Changes + +```jsonc +{ + // note you must disable the base rule as it can report incorrect errors + "lines-around-comment": "off", + "@typescript-eslint/lines-around-comment": ["error"] +} +``` + +In addition to the options supported by the `lines-around-comment` rule in ESLint core, the rule adds the following options: + +## Options + +- `allowInterfaceStart: true` doesn't require a blank line after the interface body block start +- `allowInterfaceEnd: true` doesn't require a blank line before the interface body block end +- `allowTypeStart: true` doesn't require a blank line after the type literal block start +- `allowTypeEnd: true` doesn't require a blank line after the type literal block end + +[See the other options allowed](https://eslint.org/docs/rules/comma-dangle#options) + + + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/main/docs/rules/lines-around-comment.md) + + diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 7a99a483025..63ce9b1305a 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -43,6 +43,8 @@ export = { '@typescript-eslint/key-spacing': 'error', 'keyword-spacing': 'off', '@typescript-eslint/keyword-spacing': 'error', + 'lines-around-comment': 'off', + '@typescript-eslint/lines-around-comment': 'error', 'lines-between-class-members': 'off', '@typescript-eslint/lines-between-class-members': 'error', '@typescript-eslint/member-delimiter-style': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 7dc5488142e..e1d871103ec 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -25,6 +25,7 @@ import indent from './indent'; import initDeclarations from './init-declarations'; import keySpacing from './key-spacing'; import keywordSpacing from './keyword-spacing'; +import linesAroundComment from './lines-around-comment'; import linesBetweenClassMembers from './lines-between-class-members'; import memberDelimiterStyle from './member-delimiter-style'; import memberOrdering from './member-ordering'; @@ -160,6 +161,7 @@ export default { 'init-declarations': initDeclarations, 'key-spacing': keySpacing, 'keyword-spacing': keywordSpacing, + 'lines-around-comment': linesAroundComment, 'lines-between-class-members': linesBetweenClassMembers, 'member-delimiter-style': memberDelimiterStyle, 'member-ordering': memberOrdering, diff --git a/packages/eslint-plugin/src/rules/lines-around-comment.ts b/packages/eslint-plugin/src/rules/lines-around-comment.ts new file mode 100644 index 00000000000..478667040c5 --- /dev/null +++ b/packages/eslint-plugin/src/rules/lines-around-comment.ts @@ -0,0 +1,456 @@ +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; + +import * as util from '../util'; +import { getESLintCoreRule } from '../util/getESLintCoreRule'; + +const baseRule = getESLintCoreRule('lines-around-comment'); + +export type Options = util.InferOptionsTypeFromRule; +export type MessageIds = util.InferMessageIdsTypeFromRule; + +const COMMENTS_IGNORE_PATTERN = + /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u; + +/** + * @returns an array with with any line numbers that are empty. + */ +function getEmptyLineNums(lines: string[]): number[] { + const emptyLines = lines + .map((line, i) => ({ + code: line.trim(), + num: i + 1, + })) + .filter(line => !line.code) + .map(line => line.num); + + return emptyLines; +} + +/** + * @returns an array with with any line numbers that contain comments. + */ +function getCommentLineNums(comments: TSESTree.Comment[]): number[] { + const lines: number[] = []; + + comments.forEach(token => { + const start = token.loc.start.line; + const end = token.loc.end.line; + + lines.push(start, end); + }); + return lines; +} + +export default util.createRule({ + name: 'lines-around-comment', + meta: { + type: 'layout', + docs: { + description: 'Require empty lines around comments', + recommended: false, + extendsBaseRule: true, + }, + schema: { + type: 'array', + items: [ + { + type: 'object', + properties: { + beforeBlockComment: { + type: 'boolean', + default: true, + }, + afterBlockComment: { + type: 'boolean', + default: false, + }, + beforeLineComment: { + type: 'boolean', + default: false, + }, + afterLineComment: { + type: 'boolean', + default: false, + }, + allowBlockStart: { + type: 'boolean', + default: false, + }, + allowBlockEnd: { + type: 'boolean', + default: false, + }, + allowClassStart: { + type: 'boolean', + }, + allowClassEnd: { + type: 'boolean', + }, + allowObjectStart: { + type: 'boolean', + }, + allowObjectEnd: { + type: 'boolean', + }, + allowArrayStart: { + type: 'boolean', + }, + allowArrayEnd: { + type: 'boolean', + }, + allowInterfaceStart: { + type: 'boolean', + }, + allowInterfaceEnd: { + type: 'boolean', + }, + allowTypeStart: { + type: 'boolean', + }, + allowTypeEnd: { + type: 'boolean', + }, + allowEnumStart: { + type: 'boolean', + }, + allowEnumEnd: { + type: 'boolean', + }, + allowModuleStart: { + type: 'boolean', + }, + allowModuleEnd: { + type: 'boolean', + }, + ignorePattern: { + type: 'string', + }, + applyDefaultIgnorePatterns: { + type: 'boolean', + }, + }, + additionalProperties: false, + }, + ], + }, + fixable: baseRule.meta.fixable, + hasSuggestions: baseRule.meta.hasSuggestions, + messages: baseRule.meta.messages, + }, + defaultOptions: [ + { + beforeBlockComment: true, + }, + ], + create(context, [_options]) { + const options = _options!; + const defaultIgnoreRegExp = COMMENTS_IGNORE_PATTERN; + const customIgnoreRegExp = new RegExp(options.ignorePattern ?? '', 'u'); + + const sourceCode = context.getSourceCode(); + const comments = sourceCode.getAllComments(); + + const lines = sourceCode.lines; + const commentLines = getCommentLineNums(comments); + const emptyLines = getEmptyLineNums(lines); + const commentAndEmptyLines = new Set(commentLines.concat(emptyLines)); + + /** + * @returns whether comments are on lines starting with or ending with code. + */ + function codeAroundComment(token: TSESTree.Token): boolean { + let currentToken: TSESTree.Token | null = token; + + do { + currentToken = sourceCode.getTokenBefore(currentToken, { + includeComments: true, + }); + } while (currentToken && util.isCommentToken(currentToken)); + + if (currentToken && util.isTokenOnSameLine(currentToken, token)) { + return true; + } + + currentToken = token; + do { + currentToken = sourceCode.getTokenAfter(currentToken, { + includeComments: true, + }); + } while (currentToken && util.isCommentToken(currentToken)); + + if (currentToken && util.isTokenOnSameLine(token, currentToken)) { + return true; + } + + return false; + } + + /** + * @returns whether comments are inside a node type. + */ + function isParentNodeType( + parent: TSESTree.Node, + nodeType: T, + ): parent is Extract { + return parent.type === nodeType; + } + + /** + * @returns the parent node that contains the given token. + */ + function getParentNodeOfToken(token: TSESTree.Token): TSESTree.Node | null { + const node = sourceCode.getNodeByRangeIndex(token.range[0]); + + return node; + } + + /** + * @returns whether comments are at the parent start. + */ + function isCommentAtParentStart( + token: TSESTree.Token, + nodeType: TSESTree.AST_NODE_TYPES, + ): boolean { + const parent = getParentNodeOfToken(token); + + if (parent && isParentNodeType(parent, nodeType)) { + const parentStartNodeOrToken = parent; + + return ( + token.loc.start.line - parentStartNodeOrToken.loc.start.line === 1 + ); + } + + return false; + } + + /** + * @returns whether comments are at the parent end. + */ + function isCommentAtParentEnd( + token: TSESTree.Token, + nodeType: TSESTree.AST_NODE_TYPES, + ): boolean { + const parent = getParentNodeOfToken(token); + + return ( + !!parent && + isParentNodeType(parent, nodeType) && + parent.loc.end.line - token.loc.end.line === 1 + ); + } + + function isCommentAtInterfaceStart(token: TSESTree.Comment): boolean { + return isCommentAtParentStart(token, AST_NODE_TYPES.TSInterfaceBody); + } + + function isCommentAtInterfaceEnd(token: TSESTree.Comment): boolean { + return isCommentAtParentEnd(token, AST_NODE_TYPES.TSInterfaceBody); + } + + function isCommentAtTypeStart(token: TSESTree.Comment): boolean { + return isCommentAtParentStart(token, AST_NODE_TYPES.TSTypeLiteral); + } + + function isCommentAtTypeEnd(token: TSESTree.Comment): boolean { + return isCommentAtParentEnd(token, AST_NODE_TYPES.TSTypeLiteral); + } + + function isCommentAtEnumStart(token: TSESTree.Comment): boolean { + return isCommentAtParentStart(token, AST_NODE_TYPES.TSEnumDeclaration); + } + + function isCommentAtEnumEnd(token: TSESTree.Comment): boolean { + return isCommentAtParentEnd(token, AST_NODE_TYPES.TSEnumDeclaration); + } + + function isCommentAtModuleStart(token: TSESTree.Comment): boolean { + return isCommentAtParentStart(token, AST_NODE_TYPES.TSModuleBlock); + } + + function isCommentAtModuleEnd(token: TSESTree.Comment): boolean { + return isCommentAtParentEnd(token, AST_NODE_TYPES.TSModuleBlock); + } + + function isCommentNearTSConstruct(token: TSESTree.Comment): boolean { + return ( + isCommentAtInterfaceStart(token) || + isCommentAtInterfaceEnd(token) || + isCommentAtTypeStart(token) || + isCommentAtTypeEnd(token) || + isCommentAtEnumStart(token) || + isCommentAtEnumEnd(token) || + isCommentAtModuleStart(token) || + isCommentAtModuleEnd(token) + ); + } + + function checkForEmptyLine( + token: TSESTree.Comment, + { before, after }: { before?: boolean; after?: boolean }, + ): void { + // the base rule handles comments away from TS constructs blocks correctly, we skip those + if (!isCommentNearTSConstruct(token)) { + return; + } + + if ( + options.applyDefaultIgnorePatterns !== false && + defaultIgnoreRegExp.test(token.value) + ) { + return; + } + + if (options.ignorePattern && customIgnoreRegExp.test(token.value)) { + return; + } + + const prevLineNum = token.loc.start.line - 1; + const nextLineNum = token.loc.end.line + 1; + + // we ignore all inline comments + if (codeAroundComment(token)) { + return; + } + + const interfaceStartAllowed = + Boolean(options.allowInterfaceStart) && + isCommentAtInterfaceStart(token); + const interfaceEndAllowed = + Boolean(options.allowInterfaceEnd) && isCommentAtInterfaceEnd(token); + const typeStartAllowed = + Boolean(options.allowTypeStart) && isCommentAtTypeStart(token); + const typeEndAllowed = + Boolean(options.allowTypeEnd) && isCommentAtTypeEnd(token); + const enumStartAllowed = + Boolean(options.allowEnumStart) && isCommentAtEnumStart(token); + const enumEndAllowed = + Boolean(options.allowEnumEnd) && isCommentAtEnumEnd(token); + const moduleStartAllowed = + Boolean(options.allowModuleStart) && isCommentAtModuleStart(token); + const moduleEndAllowed = + Boolean(options.allowModuleEnd) && isCommentAtModuleEnd(token); + + const exceptionStartAllowed = + interfaceStartAllowed || + typeStartAllowed || + enumStartAllowed || + moduleStartAllowed; + const exceptionEndAllowed = + interfaceEndAllowed || + typeEndAllowed || + enumEndAllowed || + moduleEndAllowed; + + const previousTokenOrComment = sourceCode.getTokenBefore(token, { + includeComments: true, + }); + const nextTokenOrComment = sourceCode.getTokenAfter(token, { + includeComments: true, + }); + + // check for newline before + if ( + !exceptionStartAllowed && + before && + !commentAndEmptyLines.has(prevLineNum) && + !( + util.isCommentToken(previousTokenOrComment!) && + util.isTokenOnSameLine(previousTokenOrComment, token) + ) + ) { + const lineStart = token.range[0] - token.loc.start.column; + const range = [lineStart, lineStart] as const; + + context.report({ + node: token, + messageId: 'before', + fix(fixer) { + return fixer.insertTextBeforeRange(range, '\n'); + }, + }); + } + + // check for newline after + if ( + !exceptionEndAllowed && + after && + !commentAndEmptyLines.has(nextLineNum) && + !( + util.isCommentToken(nextTokenOrComment!) && + util.isTokenOnSameLine(token, nextTokenOrComment) + ) + ) { + context.report({ + node: token, + messageId: 'after', + fix(fixer) { + return fixer.insertTextAfter(token, '\n'); + }, + }); + } + } + + /** + * A custom report function for the baseRule to ignore false positive errors + * caused by TS-specific codes + */ + const customReport: typeof context.report = descriptor => { + if ('node' in descriptor) { + if ( + descriptor.node.type === AST_TOKEN_TYPES.Line || + descriptor.node.type === AST_TOKEN_TYPES.Block + ) { + if (isCommentNearTSConstruct(descriptor.node)) { + return; + } + } + } + return context.report(descriptor); + }; + + const customContext = { report: customReport }; + + // we can't directly proxy `context` because its `report` property is non-configurable + // and non-writable. So we proxy `customContext` and redirect all + // property access to the original context except for `report` + const proxiedContext = new Proxy( + customContext as typeof context, + { + get(target, path, receiver): unknown { + if (path !== 'report') { + return Reflect.get(context, path, receiver); + } + return Reflect.get(target, path, receiver); + }, + }, + ); + + const rules = baseRule.create(proxiedContext); + + return { + Program(): void { + rules.Program(); + + comments.forEach(token => { + if (token.type === AST_TOKEN_TYPES.Line) { + if (options.beforeLineComment || options.afterLineComment) { + checkForEmptyLine(token, { + after: options.afterLineComment, + before: options.beforeLineComment, + }); + } + } else if (token.type === AST_TOKEN_TYPES.Block) { + if (options.beforeBlockComment || options.afterBlockComment) { + checkForEmptyLine(token, { + after: options.afterBlockComment, + before: options.beforeBlockComment, + }); + } + } + }); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/util/getESLintCoreRule.ts b/packages/eslint-plugin/src/util/getESLintCoreRule.ts index ae68a317e08..96785ad6f15 100644 --- a/packages/eslint-plugin/src/util/getESLintCoreRule.ts +++ b/packages/eslint-plugin/src/util/getESLintCoreRule.ts @@ -15,6 +15,7 @@ interface RuleMap { 'init-declarations': typeof import('eslint/lib/rules/init-declarations'); 'key-spacing': typeof import('eslint/lib/rules/key-spacing'); 'keyword-spacing': typeof import('eslint/lib/rules/keyword-spacing'); + 'lines-around-comment': typeof import('eslint/lib/rules/lines-around-comment'); 'lines-between-class-members': typeof import('eslint/lib/rules/lines-between-class-members'); 'no-dupe-args': typeof import('eslint/lib/rules/no-dupe-args'); 'no-dupe-class-members': typeof import('eslint/lib/rules/no-dupe-class-members'); diff --git a/packages/eslint-plugin/tests/rules/lines-around-comment.test.ts b/packages/eslint-plugin/tests/rules/lines-around-comment.test.ts new file mode 100644 index 00000000000..a312bb19cd0 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/lines-around-comment.test.ts @@ -0,0 +1,1053 @@ +import { AST_TOKEN_TYPES } from '@typescript-eslint/utils'; + +import rule from '../../src/rules/lines-around-comment'; +import { RuleTester } from '../RuleTester'; +import { unIndent } from './indent/utils'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('lines-around-comment', rule, { + valid: [ + // Interface + { + code: unIndent` +interface A { + // line + a: string; +} +`, + options: [ + { + beforeLineComment: true, + allowInterfaceStart: true, + }, + ], + }, + { + code: unIndent` +interface A { + /* block + comment */ + a: string; +} +`, + options: [ + { + beforeBlockComment: true, + allowInterfaceStart: true, + }, + ], + }, + { + code: unIndent` +interface A { + a: string; + // line +} +`, + options: [ + { + afterLineComment: true, + allowInterfaceEnd: true, + }, + ], + }, + { + code: unIndent` +interface A { + a: string; + /* block + comment */ +} +`, + options: [ + { + beforeBlockComment: false, + afterBlockComment: true, + allowInterfaceEnd: true, + }, + ], + }, + // Type + { + code: unIndent` +type A = { + // line + a: string; +} +`, + options: [ + { + beforeLineComment: true, + allowTypeStart: true, + }, + ], + }, + { + code: unIndent` +type A = { + /* block + comment */ + a: string; +} +`, + options: [ + { + beforeBlockComment: true, + allowTypeStart: true, + }, + ], + }, + { + code: unIndent` +type A = { + a: string; + // line +} +`, + options: [ + { + afterLineComment: true, + allowTypeEnd: true, + }, + ], + }, + { + code: unIndent` +type A = { + a: string; + /* block + comment */ +} +`, + options: [ + { + beforeBlockComment: false, + afterBlockComment: true, + allowTypeEnd: true, + }, + ], + }, + + // Enum + { + code: unIndent` +enum A { + // line + a, +} +`, + options: [ + { + beforeLineComment: true, + allowEnumStart: true, + }, + ], + }, + { + code: unIndent` +enum A { + /* block + comment */ + a, +} +`, + options: [ + { + beforeBlockComment: true, + allowEnumStart: true, + }, + ], + }, + { + code: unIndent` +enum A { + a, + // line +} +`, + options: [ + { + afterLineComment: true, + allowEnumEnd: true, + }, + ], + }, + { + code: unIndent` +enum A { + a, + /* block + comment */ +} +`, + options: [ + { + beforeBlockComment: false, + afterBlockComment: true, + allowEnumEnd: true, + }, + ], + }, + + // TS module + { + code: unIndent` +declare module A { + // line + const a: string; +} +`, + options: [ + { + beforeLineComment: true, + allowModuleStart: true, + }, + ], + }, + { + code: unIndent` +declare module A { + /* block + comment */ + const a: string; +} +`, + options: [ + { + beforeBlockComment: true, + allowModuleStart: true, + }, + ], + }, + { + code: unIndent` +declare module A { + const a: string; + // line +} +`, + options: [ + { + afterLineComment: true, + allowModuleEnd: true, + }, + ], + }, + { + code: unIndent` +declare module A { + const a: string; + /* block + comment */ +} +`, + options: [ + { + beforeBlockComment: false, + afterBlockComment: true, + allowModuleEnd: true, + }, + ], + }, + // ignorePattern + { + code: + 'interface A {' + + 'foo: string;\n\n' + + '/* eslint-disable no-underscore-dangle */\n\n' + + '_values: 2;\n' + + '_values2: true;\n' + + '/* eslint-enable no-underscore-dangle */\n' + + 'bar: boolean' + + '}', + options: [ + { + beforeBlockComment: true, + afterBlockComment: true, + }, + ], + }, + ` +interface A { + foo; + /* eslint */ +} + `, + ` +interface A { + foo; + /* jshint */ +} + `, + ` +interface A { + foo; + /* jslint */ +} + `, + ` +interface A { + foo; + /* istanbul */ +} + `, + ` +interface A { + foo; + /* global */ +} + `, + ` +interface A { + foo; + /* globals */ +} + `, + ` +interface A { + foo; + /* exported */ +} + `, + ` +interface A { + foo; + /* jscs */ +} + `, + { + code: ` +interface A { + foo: boolean; + /* this is pragmatic */ +} + `, + options: [{ ignorePattern: 'pragma' }], + }, + { + code: ` +interface A { + foo; + /* this is pragmatic */ +} + `, + options: [{ applyDefaultIgnorePatterns: false, ignorePattern: 'pragma' }], + }, + { + code: ` +interface A { + foo: string; // this is inline line comment +} + `, + options: [{ beforeLineComment: true }], + }, + { + code: ` +interface A { + foo: string /* this is inline block comment */; +} + `, + }, + { + code: ` +interface A { + /* this is inline block comment */ foo: string; +} + `, + }, + { + code: ` +interface A { + /* this is inline block comment */ foo: string /* this is inline block comment */; +} + `, + }, + { + code: ` +interface A { + /* this is inline block comment */ foo: string; // this is inline line comment ; +} + `, + }, + ], + invalid: [ + // ESLint base rule test to cover the usage of the original reporter + { + code: ` +bar(); +/** block block block + * block + */ +var a = 1; + `, + output: ` +bar(); + +/** block block block + * block + */ +var a = 1; + `, + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Block }], + }, + + // interface + { + code: unIndent` +interface A { + a: string; + // line +} +`, + output: unIndent` +interface A { + a: string; + + // line +} +`, + options: [ + { + beforeLineComment: true, + allowInterfaceStart: true, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Line, line: 3 }], + }, + { + code: unIndent` +interface A { + a: string; + /* block + comment */ +} +`, + output: unIndent` +interface A { + a: string; + + /* block + comment */ +} +`, + options: [ + { + beforeBlockComment: true, + allowInterfaceStart: true, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Block, line: 3 }], + }, + { + code: unIndent` +interface A { + // line + a: string; +} +`, + output: unIndent` +interface A { + + // line + a: string; +} +`, + options: [ + { + beforeLineComment: true, + allowInterfaceStart: false, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Line, line: 2 }], + }, + { + code: unIndent` +interface A { + /* block + comment */ + a: string; +} +`, + output: unIndent` +interface A { + + /* block + comment */ + a: string; +} +`, + options: [ + { + beforeBlockComment: true, + allowInterfaceStart: false, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Block, line: 2 }], + }, + { + code: unIndent` +interface A { + a: string; + // line +} +`, + output: unIndent` +interface A { + a: string; + // line + +} +`, + options: [ + { + afterLineComment: true, + allowInterfaceEnd: false, + }, + ], + errors: [{ messageId: 'after', type: AST_TOKEN_TYPES.Line, line: 3 }], + }, + { + code: unIndent` +interface A { + a: string; + /* block + comment */ +} +`, + output: unIndent` +interface A { + a: string; + /* block + comment */ + +} +`, + options: [ + { + beforeBlockComment: false, + afterBlockComment: true, + allowInterfaceEnd: false, + }, + ], + errors: [{ messageId: 'after', type: AST_TOKEN_TYPES.Block, line: 3 }], + }, + + // type + { + code: unIndent` +type A = { + a: string; + // line +} +`, + output: unIndent` +type A = { + a: string; + + // line +} +`, + options: [ + { + beforeLineComment: true, + allowInterfaceStart: true, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Line, line: 3 }], + }, + { + code: unIndent` +type A = { + a: string; + /* block + comment */ +} +`, + output: unIndent` +type A = { + a: string; + + /* block + comment */ +} +`, + options: [ + { + beforeBlockComment: true, + allowInterfaceStart: true, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Block, line: 3 }], + }, + { + code: unIndent` +type A = { + // line + a: string; +} +`, + output: unIndent` +type A = { + + // line + a: string; +} +`, + options: [ + { + beforeLineComment: true, + allowInterfaceStart: false, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Line, line: 2 }], + }, + { + code: unIndent` +type A = { + /* block + comment */ + a: string; +} +`, + output: unIndent` +type A = { + + /* block + comment */ + a: string; +} +`, + options: [ + { + beforeBlockComment: true, + allowInterfaceStart: false, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Block, line: 2 }], + }, + { + code: unIndent` +type A = { + a: string; + // line +} +`, + output: unIndent` +type A = { + a: string; + // line + +} +`, + options: [ + { + afterLineComment: true, + allowInterfaceEnd: false, + }, + ], + errors: [{ messageId: 'after', type: AST_TOKEN_TYPES.Line, line: 3 }], + }, + { + code: unIndent` +type A = { + a: string; + /* block + comment */ +} +`, + output: unIndent` +type A = { + a: string; + /* block + comment */ + +} +`, + options: [ + { + beforeBlockComment: false, + afterBlockComment: true, + allowInterfaceEnd: false, + }, + ], + errors: [{ messageId: 'after', type: AST_TOKEN_TYPES.Block, line: 3 }], + }, + + // Enum + { + code: unIndent` +enum A { + a, + // line +} +`, + output: unIndent` +enum A { + a, + + // line +} +`, + options: [ + { + beforeLineComment: true, + allowEnumStart: true, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Line, line: 3 }], + }, + { + code: unIndent` +enum A { + a, + /* block + comment */ +} +`, + output: unIndent` +enum A { + a, + + /* block + comment */ +} +`, + options: [ + { + beforeBlockComment: true, + allowEnumStart: true, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Block, line: 3 }], + }, + { + code: unIndent` +enum A { + // line + a, +} +`, + output: unIndent` +enum A { + + // line + a, +} +`, + options: [ + { + beforeLineComment: true, + allowEnumStart: false, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Line, line: 2 }], + }, + { + code: unIndent` +enum A { + /* block + comment */ + a, +} +`, + output: unIndent` +enum A { + + /* block + comment */ + a, +} +`, + options: [ + { + beforeBlockComment: true, + allowEnumStart: false, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Block, line: 2 }], + }, + { + code: unIndent` +enum A { + a, + // line +} +`, + output: unIndent` +enum A { + a, + // line + +} +`, + options: [ + { + afterLineComment: true, + allowEnumEnd: false, + }, + ], + errors: [{ messageId: 'after', type: AST_TOKEN_TYPES.Line, line: 3 }], + }, + { + code: unIndent` +enum A { + a, + /* block + comment */ +} +`, + output: unIndent` +enum A { + a, + /* block + comment */ + +} +`, + options: [ + { + beforeBlockComment: false, + afterBlockComment: true, + allowEnumEnd: false, + }, + ], + errors: [{ messageId: 'after', type: AST_TOKEN_TYPES.Block, line: 3 }], + }, + + // TS module + { + code: unIndent` +module A { + const a: string; + // line +} +`, + output: unIndent` +module A { + const a: string; + + // line +} +`, + options: [ + { + beforeLineComment: true, + allowModuleStart: true, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Line, line: 3 }], + }, + { + code: unIndent` +module A { + const a: string; + /* block + comment */ +} +`, + output: unIndent` +module A { + const a: string; + + /* block + comment */ +} +`, + options: [ + { + beforeBlockComment: true, + allowModuleStart: true, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Block, line: 3 }], + }, + { + code: unIndent` +module A { + // line + const a: string; +} +`, + output: unIndent` +module A { + + // line + const a: string; +} +`, + options: [ + { + beforeLineComment: true, + allowModuleStart: false, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Line, line: 2 }], + }, + { + code: unIndent` +module A { + /* block + comment */ + const a: string; +} +`, + output: unIndent` +module A { + + /* block + comment */ + const a: string; +} +`, + options: [ + { + beforeBlockComment: true, + allowModuleStart: false, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Block, line: 2 }], + }, + { + code: unIndent` +module A { + const a: string; + // line +} +`, + output: unIndent` +module A { + const a: string; + // line + +} +`, + options: [ + { + afterLineComment: true, + allowModuleEnd: false, + }, + ], + errors: [{ messageId: 'after', type: AST_TOKEN_TYPES.Line, line: 3 }], + }, + { + code: unIndent` +module A { + const a: string; + /* block + comment */ +} +`, + output: unIndent` +module A { + const a: string; + /* block + comment */ + +} +`, + options: [ + { + beforeBlockComment: false, + afterBlockComment: true, + allowModuleEnd: false, + }, + ], + errors: [{ messageId: 'after', type: AST_TOKEN_TYPES.Block, line: 3 }], + }, + + // multiple comments in one line + { + code: unIndent` +interface A { + a: string; + /* block */ /* block */ +} +`, + output: unIndent` +interface A { + a: string; + + /* block */ /* block */ +} +`, + options: [ + { + beforeBlockComment: true, + allowInterfaceEnd: false, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Block, line: 3 }], + }, + { + code: unIndent` +interface A { + a: string; + /* block */ // line +} +`, + output: unIndent` +interface A { + a: string; + + /* block */ // line +} +`, + options: [ + { + beforeBlockComment: true, + allowInterfaceEnd: false, + }, + ], + errors: [{ messageId: 'before', type: AST_TOKEN_TYPES.Block, line: 3 }], + }, + { + code: unIndent` +interface A { + /* block */ /* block */ + a: string; +} +`, + output: unIndent` +interface A { + /* block */ /* block */ + + a: string; +} +`, + options: [ + { + beforeBlockComment: false, + afterBlockComment: true, + allowInterfaceStart: false, + }, + ], + errors: [{ messageId: 'after', type: AST_TOKEN_TYPES.Block, line: 2 }], + }, + { + code: unIndent` +interface A { + /* block */ // line + a: string; +} +`, + output: unIndent` +interface A { + /* block */ // line + + a: string; +} +`, + options: [ + { + beforeBlockComment: false, + afterLineComment: true, + allowInterfaceStart: false, + }, + ], + errors: [{ messageId: 'after', type: AST_TOKEN_TYPES.Line, line: 2 }], + }, + ], +}); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index f5e0f715f0b..1726745df3f 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -730,6 +730,44 @@ declare module 'eslint/lib/rules/no-extra-semi' { export = rule; } +declare module 'eslint/lib/rules/lines-around-comment' { + import type { TSESLint } from '@typescript-eslint/utils'; + + const rule: TSESLint.RuleModule< + 'after' | 'before', + [ + { + beforeBlockComment?: boolean; + afterBlockComment?: boolean; + beforeLineComment?: boolean; + afterLineComment?: boolean; + allowBlockStart?: boolean; + allowBlockEnd?: boolean; + allowClassStart?: boolean; + allowClassEnd?: boolean; + allowObjectStart?: boolean; + allowObjectEnd?: boolean; + allowArrayStart?: boolean; + allowArrayEnd?: boolean; + allowInterfaceStart?: boolean; + allowInterfaceEnd?: boolean; + allowTypeStart?: boolean; + allowTypeEnd?: boolean; + allowEnumStart?: boolean; + allowEnumEnd?: boolean; + allowModuleStart?: boolean; + allowModuleEnd?: boolean; + ignorePattern?: string; + applyDefaultIgnorePatterns?: boolean; + }?, + ], + { + Program(): void; + } + >; + export = rule; +} + declare module 'eslint/lib/rules/lines-between-class-members' { import type { TSESLint, TSESTree } from '@typescript-eslint/utils';