11import type { Rule } from 'eslint'
2- import type { CallExpression } from 'estree'
2+ import type { BlockStatement , CallExpression , Program } from 'estree'
33import { getParent } from '../utils/ast.js'
44import { createRule } from '../utils/createRule.js'
55import { parseFnCall } from '../utils/parseFnCall.js'
@@ -8,146 +8,59 @@ export default createRule({
88 create ( context ) {
99 const { sourceCode } = context
1010
11- function isPrecededByTokens ( node : Rule . Node , testTokens : string [ ] ) {
12- const tokenBefore = sourceCode . getTokenBefore ( node )
13- return tokenBefore && testTokens . includes ( tokenBefore . value )
11+ function isBlockOrProgram (
12+ node : Rule . Node ,
13+ ) : node is
14+ | ( BlockStatement & Rule . NodeParentExtension )
15+ | ( Program & Rule . NodeParentExtension ) {
16+ return node . type === 'BlockStatement' || node . type === 'Program'
1417 }
1518
16- function isFirstNode ( node : Rule . Node ) {
19+ function getStatementNode ( node : Rule . Node ) : Rule . Node {
1720 const parent = getParent ( node )
18- if ( ! parent ) return true
19-
20- const parentType = parent . type
21- if (
22- parentType === 'ExpressionStatement' ||
23- parentType === 'VariableDeclaration'
24- ) {
25- const realParent = getParent ( parent )
26- if ( 'body' in realParent && realParent . body ) {
27- const { body } = realParent
28- return Array . isArray ( body ) ? body [ 0 ] === node : body === parent
29- }
30- return false
31- }
32-
33- if ( parentType === 'IfStatement' ) {
34- return isPrecededByTokens ( node , [ 'else' , ')' ] )
35- }
36-
37- if ( parentType === 'DoWhileStatement' ) {
38- return isPrecededByTokens ( node , [ 'do' ] )
39- }
40-
41- if ( parentType === 'SwitchCase' ) {
42- return isPrecededByTokens ( node , [ ':' ] )
43- }
44-
45- if ( 'body' in parent && parent . body ) {
46- const { body } = parent
47- return Array . isArray ( body ) ? body [ 0 ] === node : body === node
48- }
49-
50- return isPrecededByTokens ( node , [ ')' ] )
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
21+ if ( ! parent ) return node
22+ if ( isBlockOrProgram ( parent ) ) return node
23+ return getStatementNode ( parent )
7924 }
8025
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
26+ function isFirstStatementInBlock ( node : Rule . Node ) : boolean {
27+ const parent = getParent ( node )
28+ if ( ! parent ) return true
29+ if ( isBlockOrProgram ( parent ) ) return parent . body [ 0 ] === node
30+ return false
8831 }
8932
90- function getRealNodeToCheck (
91- node : CallExpression & Rule . NodeParentExtension ,
92- ) {
93- const parent = getParent ( node )
94- if ( ! parent ) return node
33+ function checkSpacing ( node : CallExpression & Rule . NodeParentExtension ) {
34+ const statementNode = getStatementNode ( node )
9535
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 )
110- }
36+ if ( isFirstStatementInBlock ( statementNode ) ) return
11137
112- return node
113- }
38+ const comments = sourceCode . getCommentsBefore ( statementNode )
39+ const nodeToCheck = comments . length > 0 ? comments [ 0 ] : statementNode
40+ const lineNumber = nodeToCheck . loc ! . start . line
11441
115- function checkSpacing ( node : CallExpression & Rule . NodeParentExtension ) {
116- const nodeToCheck = getRealNodeToCheck ( node )
42+ if ( lineNumber === 1 ) return
11743
118- if ( isFirstNode ( nodeToCheck ) ) return
119- if ( hasNewlineBefore ( nodeToCheck ) ) return
44+ const lines = sourceCode . lines
45+ const previousLine = lines [ lineNumber - 2 ]
12046
121- const leadingComments = sourceCode . getCommentsBefore ( nodeToCheck )
122- const firstComment = leadingComments [ 0 ]
123- const reportLoc = firstComment ?. loc ?? nodeToCheck . loc
47+ if ( previousLine . trim ( ) === '' ) return
12448
12549 context . report ( {
12650 data : {
127- source : sourceCode . getText ( nodeToCheck ) . split ( '\n' ) [ 0 ] ,
51+ source : sourceCode . getText ( statementNode ) . split ( '\n' ) [ 0 ] ,
12852 } ,
12953 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
54+ const nodeStart = nodeToCheck . range ! [ 0 ]
55+ const textBefore = sourceCode . text . substring ( 0 , nodeStart )
56+ const lastNewlineIndex = textBefore . lastIndexOf ( '\n' )
57+ const lineStart = lastNewlineIndex + 1
14258
143- return fixer . insertTextBeforeRange (
144- [ insertPosition , nodeStart ] ,
145- newlines ,
146- )
59+ return fixer . insertTextBeforeRange ( [ lineStart , nodeStart ] , '\n' )
14760 } ,
148- loc : reportLoc ! ,
61+ loc : nodeToCheck . loc ! ,
14962 messageId : 'missingWhitespace' ,
150- node : nodeToCheck ,
63+ node : statementNode ,
15164 } )
15265 }
15366
0 commit comments