1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 | const astUtils = require("./utils/ast-utils");
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | module.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 |
|
58 |
|
59 |
|
60 |
|
61 |
|
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 |
|
74 |
|
75 |
|
76 |
|
77 |
|
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 |
|
90 |
|
91 |
|
92 |
|
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 | };
|