"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("@typescript-eslint/utils"); const eslint_utils_1 = require("@typescript-eslint/utils/eslint-utils"); const util_1 = require("../util"); const getESLintCoreRule_1 = require("../util/getESLintCoreRule"); const baseRule = (0, getESLintCoreRule_1.getESLintCoreRule)('lines-around-comment'); 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) { 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) { const lines = []; comments.forEach(token => { const start = token.loc.start.line; const end = token.loc.end.line; lines.push(start, end); }); return lines; } exports.default = (0, util_1.createRule)({ name: 'lines-around-comment', meta: { deprecated: true, replacedBy: ['@stylistic/ts/lines-around-comment'], type: 'layout', docs: { description: 'Require empty lines around comments', extendsBaseRule: true, }, schema: [ { 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 = (0, eslint_utils_1.getSourceCode)(context); 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) { let currentToken = token; do { currentToken = sourceCode.getTokenBefore(currentToken, { includeComments: true, }); } while (currentToken && (0, util_1.isCommentToken)(currentToken)); if (currentToken && (0, util_1.isTokenOnSameLine)(currentToken, token)) { return true; } currentToken = token; do { currentToken = sourceCode.getTokenAfter(currentToken, { includeComments: true, }); } while (currentToken && (0, util_1.isCommentToken)(currentToken)); if (currentToken && (0, util_1.isTokenOnSameLine)(token, currentToken)) { return true; } return false; } /** * @returns whether comments are inside a node type. */ function isParentNodeType(parent, nodeType) { return parent.type === nodeType; } /** * @returns the parent node that contains the given token. */ function getParentNodeOfToken(token) { const node = sourceCode.getNodeByRangeIndex(token.range[0]); return node; } /** * @returns whether comments are at the parent start. */ function isCommentAtParentStart(token, nodeType) { 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, nodeType) { const parent = getParentNodeOfToken(token); return (!!parent && isParentNodeType(parent, nodeType) && parent.loc.end.line - token.loc.end.line === 1); } function isCommentAtInterfaceStart(token) { return isCommentAtParentStart(token, utils_1.AST_NODE_TYPES.TSInterfaceBody); } function isCommentAtInterfaceEnd(token) { return isCommentAtParentEnd(token, utils_1.AST_NODE_TYPES.TSInterfaceBody); } function isCommentAtTypeStart(token) { return isCommentAtParentStart(token, utils_1.AST_NODE_TYPES.TSTypeLiteral); } function isCommentAtTypeEnd(token) { return isCommentAtParentEnd(token, utils_1.AST_NODE_TYPES.TSTypeLiteral); } function isCommentAtEnumStart(token) { return isCommentAtParentStart(token, utils_1.AST_NODE_TYPES.TSEnumDeclaration); } function isCommentAtEnumEnd(token) { return isCommentAtParentEnd(token, utils_1.AST_NODE_TYPES.TSEnumDeclaration); } function isCommentAtModuleStart(token) { return isCommentAtParentStart(token, utils_1.AST_NODE_TYPES.TSModuleBlock); } function isCommentAtModuleEnd(token) { return isCommentAtParentEnd(token, utils_1.AST_NODE_TYPES.TSModuleBlock); } function isCommentNearTSConstruct(token) { return (isCommentAtInterfaceStart(token) || isCommentAtInterfaceEnd(token) || isCommentAtTypeStart(token) || isCommentAtTypeEnd(token) || isCommentAtEnumStart(token) || isCommentAtEnumEnd(token) || isCommentAtModuleStart(token) || isCommentAtModuleEnd(token)); } function checkForEmptyLine(token, { before, after }) { // 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) && !((0, util_1.isCommentToken)(previousTokenOrComment) && (0, util_1.isTokenOnSameLine)(previousTokenOrComment, token))) { const lineStart = token.range[0] - token.loc.start.column; const range = [lineStart, lineStart]; context.report({ node: token, messageId: 'before', fix(fixer) { return fixer.insertTextBeforeRange(range, '\n'); }, }); } // check for newline after if (!exceptionEndAllowed && after && !commentAndEmptyLines.has(nextLineNum) && !((0, util_1.isCommentToken)(nextTokenOrComment) && (0, util_1.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 = descriptor => { if ('node' in descriptor) { if (descriptor.node.type === utils_1.AST_TOKEN_TYPES.Line || descriptor.node.type === utils_1.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, { get(target, path, receiver) { if (path !== 'report') { return Reflect.get(context, path, receiver); } return Reflect.get(target, path, receiver); }, }); const rules = baseRule.create(proxiedContext); return { Program() { rules.Program(); comments.forEach(token => { if (token.type === utils_1.AST_TOKEN_TYPES.Line) { if (options.beforeLineComment || options.afterLineComment) { checkForEmptyLine(token, { after: options.afterLineComment, before: options.beforeLineComment, }); } } else if (options.beforeBlockComment || options.afterBlockComment) { checkForEmptyLine(token, { after: options.afterBlockComment, before: options.beforeBlockComment, }); } }); }, }; }, }); //# sourceMappingURL=lines-around-comment.js.map