Skip to content

Commit b420456

Browse files
committed
refactor: use existing utils directly
and adopt logic from existing official rules, like [`newline-before-return`](https://github.com/eslint/eslint/blob/main/lib/rules/newline-before-return.js)
1 parent 15ae0db commit b420456

File tree

2 files changed

+137
-145
lines changed

2 files changed

+137
-145
lines changed

src/rules/consistent-spacing-between-blocks.ts

Lines changed: 137 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,167 @@
1-
import type { AST } from 'eslint'
2-
import type { Comment, Expression, Node } from 'estree'
1+
import type { Rule } from 'eslint'
2+
import type { CallExpression } from 'estree'
3+
import { getParent } from '../utils/ast.js'
34
import { createRule } from '../utils/createRule.js'
4-
import { isTestExpression, unwrapExpression } from '../utils/test-expression.js'
5+
import { parseFnCall } from '../utils/parseFnCall.js'
56

67
export default createRule({
78
create(context) {
8-
function getPreviousToken(
9-
node: AST.Token | Node,
10-
start?: AST.Token | Comment | Node,
11-
): {
12-
origin: AST.Token | Node
13-
previous: AST.Token | null
14-
start: AST.Token | Comment | Node
15-
} {
16-
const current = start ?? node
17-
const previous = context.sourceCode.getTokenBefore(current, {
18-
includeComments: true,
19-
})
9+
const { sourceCode } = context
10+
11+
function isPrecededByTokens(node: Rule.Node, testTokens: string[]) {
12+
const tokenBefore = sourceCode.getTokenBefore(node)
13+
return tokenBefore && testTokens.includes(tokenBefore.value as string)
14+
}
2015

16+
function isFirstNode(node: Rule.Node) {
17+
const parent = getParent(node)
18+
if (!parent) return true
19+
20+
const parentType = parent.type
2121
if (
22-
previous === null ||
23-
previous === undefined ||
24-
previous.value === '{'
22+
parentType === 'ExpressionStatement' ||
23+
parentType === 'VariableDeclaration'
2524
) {
26-
return {
27-
origin: node,
28-
previous: null,
29-
start: current,
25+
const realParent = getParent(parent)
26+
if ('body' in realParent && realParent.body) {
27+
const body = realParent.body as unknown
28+
return Array.isArray(body) ? body[0] === node : body === parent
3029
}
30+
return false
3131
}
3232

33-
if (
34-
previous.type === 'Line' ||
35-
previous.type === 'Block' ||
36-
previous.value === '('
37-
) {
38-
return getPreviousToken(node, previous)
33+
if (parentType === 'IfStatement') {
34+
return isPrecededByTokens(node as any, ['else', ')'])
3935
}
4036

41-
return {
42-
origin: node,
43-
previous: previous as AST.Token,
44-
start: current,
37+
if (parentType === 'DoWhileStatement') {
38+
return isPrecededByTokens(node as any, ['do'])
4539
}
40+
41+
if (parentType === 'SwitchCase') {
42+
return isPrecededByTokens(node as any, [':'])
43+
}
44+
45+
if ('body' in parent && parent.body) {
46+
const body = parent.body as unknown
47+
return Array.isArray(body) ? body[0] === node : body === node
48+
}
49+
50+
return isPrecededByTokens(node as any, [')'])
51+
}
52+
53+
function calcCommentLines(node: Rule.Node, lineNumTokenBefore: number) {
54+
const comments = sourceCode.getCommentsBefore(node)
55+
let numLinesComments = 0
56+
57+
if (!comments.length) {
58+
return numLinesComments
59+
}
60+
61+
comments.forEach((comment) => {
62+
numLinesComments++
63+
64+
if (comment.type === 'Block') {
65+
numLinesComments += comment.loc!.end.line - comment.loc!.start.line
66+
}
67+
68+
// avoid counting lines with inline comments twice
69+
if (comment.loc!.start.line === lineNumTokenBefore) {
70+
numLinesComments--
71+
}
72+
73+
if (comment.loc!.end.line === node.loc!.start.line) {
74+
numLinesComments--
75+
}
76+
})
77+
78+
return numLinesComments
79+
}
80+
81+
function hasNewlineBefore(node: Rule.Node) {
82+
const tokenBefore = sourceCode.getTokenBefore(node)
83+
const lineNumTokenBefore = !tokenBefore ? 0 : tokenBefore.loc.end.line
84+
const lineNumNode = node.loc!.start.line
85+
const commentLines = calcCommentLines(node, lineNumTokenBefore)
86+
87+
return lineNumNode - lineNumTokenBefore - commentLines > 1
4688
}
4789

48-
function checkSpacing(node: Expression, offset?: AST.Token | Node) {
49-
const { previous, start } = getPreviousToken(node, offset)
50-
if (previous === null) return
51-
if (previous.loc.end.line < start.loc!.start.line - 1) {
52-
return
90+
function getRealNodeToCheck(
91+
node: CallExpression & Rule.NodeParentExtension,
92+
) {
93+
const parent = getParent(node)
94+
if (!parent) return node
95+
96+
if (parent.type === 'ExpressionStatement') {
97+
return parent
98+
}
99+
if (parent.type === 'AwaitExpression') {
100+
const awaitParent = getParent(parent)
101+
return awaitParent.type === 'ExpressionStatement'
102+
? awaitParent
103+
: getParent(awaitParent)
104+
}
105+
if (
106+
parent.type === 'VariableDeclarator' ||
107+
parent.type === 'AssignmentExpression'
108+
) {
109+
return getParent(parent)
53110
}
54111

55-
const source = context.sourceCode.getText(unwrapExpression(node))
112+
return node
113+
}
114+
115+
function checkSpacing(node: CallExpression & Rule.NodeParentExtension) {
116+
const nodeToCheck = getRealNodeToCheck(node)
117+
118+
if (isFirstNode(nodeToCheck)) return
119+
if (hasNewlineBefore(nodeToCheck)) return
120+
121+
const leadingComments = sourceCode.getCommentsBefore(nodeToCheck)
122+
const firstComment = leadingComments[0]
123+
const reportLoc = firstComment?.loc ?? nodeToCheck.loc
124+
56125
context.report({
57-
data: { source },
58-
fix(fixer) {
59-
return fixer.insertTextAfter(previous, '\n')
126+
data: {
127+
source: sourceCode.getText(nodeToCheck).split('\n')[0],
60128
},
61-
loc: {
62-
end: {
63-
column: start.loc!.start.column,
64-
line: start.loc!.start.line,
65-
},
66-
start: {
67-
column: 0,
68-
line: previous.loc.end.line + 1,
69-
},
129+
fix(fixer) {
130+
const tokenBefore = sourceCode.getTokenBefore(nodeToCheck)
131+
if (!tokenBefore) return null
132+
133+
const newlines =
134+
nodeToCheck.loc?.start.line === tokenBefore.loc.end.line
135+
? '\n\n'
136+
: '\n'
137+
const targetNode = firstComment ?? nodeToCheck
138+
const nodeStart = targetNode.range?.[0] ?? 0
139+
const textBeforeNode = sourceCode.text.substring(0, nodeStart)
140+
const lastNewlineIndex = textBeforeNode.lastIndexOf('\n')
141+
const insertPosition = lastNewlineIndex + 1
142+
143+
return fixer.insertTextBeforeRange(
144+
[insertPosition, nodeStart],
145+
newlines,
146+
)
70147
},
148+
loc: reportLoc!,
71149
messageId: 'missingWhitespace',
72-
node,
150+
node: nodeToCheck,
73151
})
74152
}
75153

76154
return {
77-
ExpressionStatement(node) {
78-
if (isTestExpression(context, node.expression)) {
79-
checkSpacing(node.expression)
155+
CallExpression(node) {
156+
const call = parseFnCall(context, node)
157+
if (
158+
call?.type === 'test' ||
159+
call?.type === 'hook' ||
160+
call?.type === 'step'
161+
) {
162+
checkSpacing(node)
80163
}
81164
},
82-
VariableDeclaration(node) {
83-
node.declarations.forEach((declaration) => {
84-
if (declaration.init && isTestExpression(context, declaration.init)) {
85-
const offset = context.sourceCode.getTokenBefore(declaration)
86-
checkSpacing(declaration.init, offset ?? undefined)
87-
}
88-
})
89-
},
90165
}
91166
},
92167
meta: {

src/utils/test-expression.ts

Lines changed: 0 additions & 83 deletions
This file was deleted.

0 commit comments

Comments
 (0)