1/** 2 * @fileoverview Rule to check empty newline between class members 3 * @author 薛定谔的猫<hh_2013@foxmail.com> 4 */ 5"use strict"; 6 7const astUtils = require("./utils/ast-utils"); 8 9//------------------------------------------------------------------------------ 10// Rule Definition 11//------------------------------------------------------------------------------ 12 13module.exports = { 14 meta: { 15 type: "layout", 16 17 docs: { 18 description: "require or disallow an empty line between class members", 19 category: "Stylistic Issues", 20 recommended: false, 21 url: "https://eslint.org/docs/rules/lines-between-class-members" 22 }, 23 24 fixable: "whitespace", 25 26 schema: [ 27 { 28 enum: ["always", "never"] 29 }, 30 { 31 type: "object", 32 properties: { 33 exceptAfterSingleLine: { 34 type: "boolean", 35 default: false 36 } 37 }, 38 additionalProperties: false 39 } 40 ], 41 messages: { 42 never: "Unexpected blank line between class members.", 43 always: "Expected blank line between class members." 44 } 45 }, 46 47 create(context) { 48 49 const options = []; 50 51 options[0] = context.options[0] || "always"; 52 options[1] = context.options[1] || { exceptAfterSingleLine: false }; 53 54 const sourceCode = context.getSourceCode(); 55 56 /** 57 * Return the last token among the consecutive tokens that have no exceed max line difference in between, before the first token in the next member. 58 * @param {Token} prevLastToken The last token in the previous member node. 59 * @param {Token} nextFirstToken The first token in the next member node. 60 * @param {number} maxLine The maximum number of allowed line difference between consecutive tokens. 61 * @returns {Token} The last token among the consecutive tokens. 62 */ 63 function findLastConsecutiveTokenAfter(prevLastToken, nextFirstToken, maxLine) { 64 const after = sourceCode.getTokenAfter(prevLastToken, { includeComments: true }); 65 66 if (after !== nextFirstToken && after.loc.start.line - prevLastToken.loc.end.line <= maxLine) { 67 return findLastConsecutiveTokenAfter(after, nextFirstToken, maxLine); 68 } 69 return prevLastToken; 70 } 71 72 /** 73 * Return the first token among the consecutive tokens that have no exceed max line difference in between, after the last token in the previous member. 74 * @param {Token} nextFirstToken The first token in the next member node. 75 * @param {Token} prevLastToken The last token in the previous member node. 76 * @param {number} maxLine The maximum number of allowed line difference between consecutive tokens. 77 * @returns {Token} The first token among the consecutive tokens. 78 */ 79 function findFirstConsecutiveTokenBefore(nextFirstToken, prevLastToken, maxLine) { 80 const before = sourceCode.getTokenBefore(nextFirstToken, { includeComments: true }); 81 82 if (before !== prevLastToken && nextFirstToken.loc.start.line - before.loc.end.line <= maxLine) { 83 return findFirstConsecutiveTokenBefore(before, prevLastToken, maxLine); 84 } 85 return nextFirstToken; 86 } 87 88 /** 89 * Checks if there is a token or comment between two tokens. 90 * @param {Token} before The token before. 91 * @param {Token} after The token after. 92 * @returns {boolean} True if there is a token or comment between two tokens. 93 */ 94 function hasTokenOrCommentBetween(before, after) { 95 return sourceCode.getTokensBetween(before, after, { includeComments: true }).length !== 0; 96 } 97 98 return { 99 ClassBody(node) { 100 const body = node.body; 101 102 for (let i = 0; i < body.length - 1; i++) { 103 const curFirst = sourceCode.getFirstToken(body[i]); 104 const curLast = sourceCode.getLastToken(body[i]); 105 const nextFirst = sourceCode.getFirstToken(body[i + 1]); 106 const isMulti = !astUtils.isTokenOnSameLine(curFirst, curLast); 107 const skip = !isMulti && options[1].exceptAfterSingleLine; 108 const beforePadding = findLastConsecutiveTokenAfter(curLast, nextFirst, 1); 109 const afterPadding = findFirstConsecutiveTokenBefore(nextFirst, curLast, 1); 110 const isPadded = afterPadding.loc.start.line - beforePadding.loc.end.line > 1; 111 const hasTokenInPadding = hasTokenOrCommentBetween(beforePadding, afterPadding); 112 const curLineLastToken = findLastConsecutiveTokenAfter(curLast, nextFirst, 0); 113 114 if ((options[0] === "always" && !skip && !isPadded) || 115 (options[0] === "never" && isPadded)) { 116 context.report({ 117 node: body[i + 1], 118 messageId: isPadded ? "never" : "always", 119 fix(fixer) { 120 if (hasTokenInPadding) { 121 return null; 122 } 123 return isPadded 124 ? fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n") 125 : fixer.insertTextAfter(curLineLastToken, "\n"); 126 } 127 }); 128 } 129 } 130 } 131 }; 132 } 133}; 134