UNPKG

@stylistic/eslint-plugin-js

Version:

JavaScript stylistic rules for ESLint, migrated from [eslint core](https://github.com/eslint/eslint).

259 lines (256 loc) 10.3 kB
import { c as createRule, F as COMMENTS_IGNORE_PATTERN, a as isCommentToken, i as isTokenOnSameLine, v as isOpeningBraceToken } from '../utils.js'; import 'eslint-visitor-keys'; import 'espree'; 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; } 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; } var linesAroundComment = createRule({ name: "lines-around-comment", package: "js", meta: { type: "layout", docs: { description: "Require empty lines around comments" }, fixable: "whitespace", 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" }, ignorePattern: { type: "string" }, applyDefaultIgnorePatterns: { type: "boolean" }, afterHashbangComment: { type: "boolean", default: false } }, additionalProperties: false } ], messages: { after: "Expected line after comment.", before: "Expected line before comment." } }, create(context) { const options = Object.assign({}, context.options[0]); const ignorePattern = options.ignorePattern; const defaultIgnoreRegExp = COMMENTS_IGNORE_PATTERN; const customIgnoreRegExp = ignorePattern && new RegExp(ignorePattern, "u"); const applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false; options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true; const sourceCode = context.sourceCode; const lines = sourceCode.lines; const numLines = lines.length + 1; const comments = sourceCode.getAllComments(); const commentLines = getCommentLineNums(comments); const emptyLines = getEmptyLineNums(lines); const commentAndEmptyLines = new Set(commentLines.concat(emptyLines)); function codeAroundComment(token) { let currentToken = token; do currentToken = sourceCode.getTokenBefore(currentToken, { includeComments: true }); while (currentToken && isCommentToken(currentToken)); if (currentToken && isTokenOnSameLine(currentToken, token)) return true; currentToken = token; do currentToken = sourceCode.getTokenAfter(currentToken, { includeComments: true }); while (currentToken && isCommentToken(currentToken)); if (currentToken && isTokenOnSameLine(token, currentToken)) return true; return false; } function isParentNodeType(parent, nodeType) { return parent.type === nodeType; } function getParentNodeOfToken(token) { const node = sourceCode.getNodeByRangeIndex(token.range[0]); if (node && node.type === "StaticBlock") { const openingBrace = sourceCode.getFirstToken(node, { skip: 1 }); return openingBrace && token.range[0] >= openingBrace.range[0] ? node : null; } return node; } function isCommentAtParentStart(token, nodeType) { const parent = getParentNodeOfToken(token); if (parent && isParentNodeType(parent, nodeType)) { let parentStartNodeOrToken = parent; if (parent.type === "StaticBlock") { parentStartNodeOrToken = sourceCode.getFirstToken(parent, { skip: 1 }); } else if (parent.type === "SwitchStatement") { parentStartNodeOrToken = sourceCode.getTokenAfter(parent.discriminant, { filter: isOpeningBraceToken }); } return !!parentStartNodeOrToken && token.loc.start.line - parentStartNodeOrToken.loc.start.line === 1; } return false; } function isCommentAtParentEnd(token, nodeType) { const parent = getParentNodeOfToken(token); return !!parent && isParentNodeType(parent, nodeType) && parent.loc.end.line - token.loc.end.line === 1; } function isCommentAtBlockStart(token) { return isCommentAtParentStart(token, "ClassBody") || isCommentAtParentStart(token, "BlockStatement") || isCommentAtParentStart(token, "StaticBlock") || isCommentAtParentStart(token, "SwitchCase") || isCommentAtParentStart(token, "SwitchStatement"); } function isCommentAtBlockEnd(token) { return isCommentAtParentEnd(token, "ClassBody") || isCommentAtParentEnd(token, "BlockStatement") || isCommentAtParentEnd(token, "StaticBlock") || isCommentAtParentEnd(token, "SwitchCase") || isCommentAtParentEnd(token, "SwitchStatement"); } function isCommentAtClassStart(token) { return isCommentAtParentStart(token, "ClassBody"); } function isCommentAtClassEnd(token) { return isCommentAtParentEnd(token, "ClassBody"); } function isCommentAtObjectStart(token) { return isCommentAtParentStart(token, "ObjectExpression") || isCommentAtParentStart(token, "ObjectPattern"); } function isCommentAtObjectEnd(token) { return isCommentAtParentEnd(token, "ObjectExpression") || isCommentAtParentEnd(token, "ObjectPattern"); } function isCommentAtArrayStart(token) { return isCommentAtParentStart(token, "ArrayExpression") || isCommentAtParentStart(token, "ArrayPattern"); } function isCommentAtArrayEnd(token) { return isCommentAtParentEnd(token, "ArrayExpression") || isCommentAtParentEnd(token, "ArrayPattern"); } function checkForEmptyLine(token, opts) { if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(token.value)) return; if (customIgnoreRegExp && customIgnoreRegExp.test(token.value)) return; let after = opts.after; let before = opts.before; const prevLineNum = token.loc.start.line - 1; const nextLineNum = token.loc.end.line + 1; const commentIsNotAlone = codeAroundComment(token); const blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(token) && !(options.allowClassStart === false && isCommentAtClassStart(token)); const blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token) && !(options.allowClassEnd === false && isCommentAtClassEnd(token)); const classStartAllowed = options.allowClassStart && isCommentAtClassStart(token); const classEndAllowed = options.allowClassEnd && isCommentAtClassEnd(token); const objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(token); const objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(token); const arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(token); const arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(token); const exceptionStartAllowed = blockStartAllowed || classStartAllowed || objectStartAllowed || arrayStartAllowed; const exceptionEndAllowed = blockEndAllowed || classEndAllowed || objectEndAllowed || arrayEndAllowed; if (prevLineNum < 1) before = false; if (nextLineNum >= numLines) after = false; if (commentIsNotAlone) return; const previousTokenOrComment = sourceCode.getTokenBefore(token, { includeComments: true }); const nextTokenOrComment = sourceCode.getTokenAfter(token, { includeComments: true }); if (!exceptionStartAllowed && before && !commentAndEmptyLines.has(prevLineNum) && !(isCommentToken(previousTokenOrComment) && 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"); } }); } if (!exceptionEndAllowed && after && !commentAndEmptyLines.has(nextLineNum) && !(isCommentToken(nextTokenOrComment) && isTokenOnSameLine(token, nextTokenOrComment))) { context.report({ node: token, messageId: "after", fix(fixer) { return fixer.insertTextAfter(token, "\n"); } }); } } return { Program() { comments.forEach((token) => { if (token.type === "Line") { if (options.beforeLineComment || options.afterLineComment) { checkForEmptyLine(token, { after: options.afterLineComment, before: options.beforeLineComment }); } } else if (token.type === "Block") { if (options.beforeBlockComment || options.afterBlockComment) { checkForEmptyLine(token, { after: options.afterBlockComment, before: options.beforeBlockComment }); } } else if (token.type === "Shebang") { if (options.afterHashbangComment) { checkForEmptyLine(token, { after: options.afterHashbangComment, before: false }); } } }); } }; } }); export { linesAroundComment as default };