@unix/eslint 1.0.2 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/configs/js.js +1 -0
- package/configs/plugins/function-blank-lines.js +270 -0
- package/configs/plugins/index.js +2 -0
- package/configs/ts.js +1 -0
- package/package.json +1 -1
package/configs/js.js
CHANGED
|
@@ -127,6 +127,7 @@ export default [
|
|
|
127
127
|
'no-var': 'error',
|
|
128
128
|
'prefer-rest-params': 'error',
|
|
129
129
|
'no-else-return': ['error', { allowElseIf: false }],
|
|
130
|
+
'@unix/function-blank-lines': 'error',
|
|
130
131
|
'@unix/compact-nonblock-statement': [
|
|
131
132
|
'error',
|
|
132
133
|
{ maxLineLength: prettierPrintWidth },
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
const lineBreakFor = sourceCode => (sourceCode.text.includes('\r\n') ? '\r\n' : '\n')
|
|
2
|
+
|
|
3
|
+
const LONG_BLOCK_MIN_LINES = 4
|
|
4
|
+
|
|
5
|
+
const indentationFor = (sourceCode, token) => {
|
|
6
|
+
const line = sourceCode.lines[token.loc.start.line - 1] ?? ''
|
|
7
|
+
|
|
8
|
+
return line.slice(0, token.loc.start.column)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const spacingBefore = (sourceCode, token, blankLines) =>
|
|
12
|
+
`${lineBreakFor(sourceCode).repeat(blankLines + 1)}${indentationFor(
|
|
13
|
+
sourceCode,
|
|
14
|
+
token,
|
|
15
|
+
)}`
|
|
16
|
+
|
|
17
|
+
const paddingLineSequencesBetween = (sourceCode, leftToken, rightToken) => {
|
|
18
|
+
const sequences = []
|
|
19
|
+
let previousToken = leftToken
|
|
20
|
+
|
|
21
|
+
while (previousToken.range[0] < rightToken.range[0]) {
|
|
22
|
+
const nextToken = sourceCode.getTokenAfter(previousToken, {
|
|
23
|
+
includeComments: true,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
if (!nextToken || nextToken.range[0] > rightToken.range[0]) break
|
|
27
|
+
|
|
28
|
+
const blankLines = nextToken.loc.start.line - previousToken.loc.end.line - 1
|
|
29
|
+
|
|
30
|
+
if (blankLines > 0) {
|
|
31
|
+
sequences.push({
|
|
32
|
+
blankLines,
|
|
33
|
+
leftToken: previousToken,
|
|
34
|
+
rightToken: nextToken,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
previousToken = nextToken
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return sequences
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const blankLineCount = sequences =>
|
|
45
|
+
sequences.reduce((count, sequence) => count + sequence.blankLines, 0)
|
|
46
|
+
|
|
47
|
+
const hasTokensBetween = (sourceCode, leftToken, rightToken) =>
|
|
48
|
+
sourceCode.getTokensBetween(leftToken, rightToken, { includeComments: true })
|
|
49
|
+
.length > 0
|
|
50
|
+
|
|
51
|
+
const reportPadding = (
|
|
52
|
+
context,
|
|
53
|
+
sourceCode,
|
|
54
|
+
node,
|
|
55
|
+
leftToken,
|
|
56
|
+
rightToken,
|
|
57
|
+
maxBlankLines,
|
|
58
|
+
messageId,
|
|
59
|
+
) => {
|
|
60
|
+
const sequences = paddingLineSequencesBetween(sourceCode, leftToken, rightToken)
|
|
61
|
+
|
|
62
|
+
if (blankLineCount(sequences) <= maxBlankLines) return
|
|
63
|
+
|
|
64
|
+
const [firstSequence] = sequences
|
|
65
|
+
|
|
66
|
+
context.report({
|
|
67
|
+
fix(fixer) {
|
|
68
|
+
if (hasTokensBetween(sourceCode, leftToken, rightToken)) return null
|
|
69
|
+
|
|
70
|
+
return fixer.replaceTextRange(
|
|
71
|
+
[leftToken.range[1], rightToken.range[0]],
|
|
72
|
+
spacingBefore(sourceCode, rightToken, maxBlankLines),
|
|
73
|
+
)
|
|
74
|
+
},
|
|
75
|
+
loc: {
|
|
76
|
+
end: firstSequence.rightToken.loc.start,
|
|
77
|
+
start: {
|
|
78
|
+
column: 0,
|
|
79
|
+
line: firstSequence.leftToken.loc.end.line + 1,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
messageId,
|
|
83
|
+
node,
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const isReturnStatement = node => node.type === 'ReturnStatement'
|
|
88
|
+
|
|
89
|
+
const lineCountFor = node => node.loc.end.line - node.loc.start.line + 1
|
|
90
|
+
|
|
91
|
+
const hasBlockBody = node => {
|
|
92
|
+
if (node.type === 'BlockStatement') return true
|
|
93
|
+
if (node.type === 'FunctionDeclaration') return true
|
|
94
|
+
if (node.type === 'SwitchStatement') return true
|
|
95
|
+
if (node.type === 'TryStatement') return true
|
|
96
|
+
if (node.type === 'IfStatement') {
|
|
97
|
+
if (hasBlockBody(node.consequent)) return true
|
|
98
|
+
|
|
99
|
+
return Boolean(node.alternate && hasBlockBody(node.alternate))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return node.body?.type === 'BlockStatement'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const isLongBlockStatement = node =>
|
|
106
|
+
hasBlockBody(node) && lineCountFor(node) >= LONG_BLOCK_MIN_LINES
|
|
107
|
+
|
|
108
|
+
const messageIdForStatementGap = (previousStatement, nextStatement) => {
|
|
109
|
+
if (isReturnStatement(previousStatement)) return 'unexpectedBlankLineAfterReturn'
|
|
110
|
+
if (isReturnStatement(nextStatement)) return 'tooManyBlankLinesBeforeReturn'
|
|
111
|
+
|
|
112
|
+
return 'unexpectedBlankLine'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const maxBlankLinesForStatementGap = (previousStatement, nextStatement) => {
|
|
116
|
+
if (isReturnStatement(previousStatement)) return 0
|
|
117
|
+
if (isReturnStatement(nextStatement)) return 1
|
|
118
|
+
if (isLongBlockStatement(previousStatement)) return 1
|
|
119
|
+
if (isLongBlockStatement(nextStatement)) return 1
|
|
120
|
+
|
|
121
|
+
return 0
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const checkStatementList = (
|
|
125
|
+
context,
|
|
126
|
+
sourceCode,
|
|
127
|
+
node,
|
|
128
|
+
statements,
|
|
129
|
+
leftToken,
|
|
130
|
+
rightToken,
|
|
131
|
+
) => {
|
|
132
|
+
if (statements.length === 0) {
|
|
133
|
+
reportPadding(
|
|
134
|
+
context,
|
|
135
|
+
sourceCode,
|
|
136
|
+
node,
|
|
137
|
+
leftToken,
|
|
138
|
+
rightToken,
|
|
139
|
+
0,
|
|
140
|
+
'unexpectedBlankLine',
|
|
141
|
+
)
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const [firstStatement] = statements
|
|
146
|
+
const lastStatement = statements[statements.length - 1]
|
|
147
|
+
|
|
148
|
+
reportPadding(
|
|
149
|
+
context,
|
|
150
|
+
sourceCode,
|
|
151
|
+
firstStatement,
|
|
152
|
+
leftToken,
|
|
153
|
+
sourceCode.getFirstToken(firstStatement),
|
|
154
|
+
0,
|
|
155
|
+
'unexpectedBlankLine',
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
for (let index = 1; index < statements.length; index += 1) {
|
|
159
|
+
const previousStatement = statements[index - 1]
|
|
160
|
+
const nextStatement = statements[index]
|
|
161
|
+
|
|
162
|
+
reportPadding(
|
|
163
|
+
context,
|
|
164
|
+
sourceCode,
|
|
165
|
+
nextStatement,
|
|
166
|
+
sourceCode.getLastToken(previousStatement),
|
|
167
|
+
sourceCode.getFirstToken(nextStatement),
|
|
168
|
+
maxBlankLinesForStatementGap(previousStatement, nextStatement),
|
|
169
|
+
messageIdForStatementGap(previousStatement, nextStatement),
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
reportPadding(
|
|
174
|
+
context,
|
|
175
|
+
sourceCode,
|
|
176
|
+
lastStatement,
|
|
177
|
+
sourceCode.getLastToken(lastStatement),
|
|
178
|
+
rightToken,
|
|
179
|
+
0,
|
|
180
|
+
isReturnStatement(lastStatement)
|
|
181
|
+
? 'unexpectedBlankLineAfterReturn'
|
|
182
|
+
: 'unexpectedBlankLine',
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const caseColonToken = (sourceCode, node) => {
|
|
187
|
+
const tokenBeforeConsequent = sourceCode.getTokenBefore(node.consequent[0])
|
|
188
|
+
|
|
189
|
+
if (tokenBeforeConsequent?.value === ':') return tokenBeforeConsequent
|
|
190
|
+
|
|
191
|
+
const firstToken = sourceCode.getFirstToken(node)
|
|
192
|
+
|
|
193
|
+
return sourceCode.getTokenAfter(firstToken, {
|
|
194
|
+
filter: token => token.value === ':',
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const caseEndToken = (sourceCode, node) => {
|
|
199
|
+
const lastStatement = node.consequent[node.consequent.length - 1]
|
|
200
|
+
|
|
201
|
+
return sourceCode.getTokenAfter(sourceCode.getLastToken(lastStatement))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const functionBlankLines = {
|
|
205
|
+
meta: {
|
|
206
|
+
type: 'layout',
|
|
207
|
+
docs: {
|
|
208
|
+
description:
|
|
209
|
+
'Disallow extra blank lines in functions except before return statements',
|
|
210
|
+
},
|
|
211
|
+
fixable: 'whitespace',
|
|
212
|
+
messages: {
|
|
213
|
+
tooManyBlankLinesBeforeReturn:
|
|
214
|
+
'Return statements may have at most one blank line above them.',
|
|
215
|
+
unexpectedBlankLine: 'Unexpected blank line inside function.',
|
|
216
|
+
unexpectedBlankLineAfterReturn: 'Unexpected blank line after return.',
|
|
217
|
+
},
|
|
218
|
+
schema: [],
|
|
219
|
+
},
|
|
220
|
+
create(context) {
|
|
221
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode()
|
|
222
|
+
let functionDepth = 0
|
|
223
|
+
|
|
224
|
+
const enterFunction = () => {
|
|
225
|
+
functionDepth += 1
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const exitFunction = () => {
|
|
229
|
+
functionDepth -= 1
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const isInsideFunction = () => functionDepth > 0
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
ArrowFunctionExpression: enterFunction,
|
|
236
|
+
'ArrowFunctionExpression:exit': exitFunction,
|
|
237
|
+
BlockStatement(node) {
|
|
238
|
+
if (!isInsideFunction()) return
|
|
239
|
+
|
|
240
|
+
checkStatementList(
|
|
241
|
+
context,
|
|
242
|
+
sourceCode,
|
|
243
|
+
node,
|
|
244
|
+
node.body,
|
|
245
|
+
sourceCode.getFirstToken(node),
|
|
246
|
+
sourceCode.getLastToken(node),
|
|
247
|
+
)
|
|
248
|
+
},
|
|
249
|
+
FunctionDeclaration: enterFunction,
|
|
250
|
+
'FunctionDeclaration:exit': exitFunction,
|
|
251
|
+
FunctionExpression: enterFunction,
|
|
252
|
+
'FunctionExpression:exit': exitFunction,
|
|
253
|
+
SwitchCase(node) {
|
|
254
|
+
if (!isInsideFunction()) return
|
|
255
|
+
if (node.consequent.length === 0) return
|
|
256
|
+
|
|
257
|
+
checkStatementList(
|
|
258
|
+
context,
|
|
259
|
+
sourceCode,
|
|
260
|
+
node,
|
|
261
|
+
node.consequent,
|
|
262
|
+
caseColonToken(sourceCode, node),
|
|
263
|
+
caseEndToken(sourceCode, node),
|
|
264
|
+
)
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export default functionBlankLines
|
package/configs/plugins/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import compactNonblockStatement from './compact-nonblock-statement.js'
|
|
2
2
|
import compactReturnIf from './compact-return-if.js'
|
|
3
|
+
import functionBlankLines from './function-blank-lines.js'
|
|
3
4
|
|
|
4
5
|
export default {
|
|
5
6
|
rules: {
|
|
6
7
|
'compact-nonblock-statement': compactNonblockStatement,
|
|
7
8
|
'compact-return-if': compactReturnIf,
|
|
9
|
+
'function-blank-lines': functionBlankLines,
|
|
8
10
|
},
|
|
9
11
|
}
|
package/configs/ts.js
CHANGED
|
@@ -123,6 +123,7 @@ export default tseslint.config(
|
|
|
123
123
|
'@typescript-eslint/no-floating-promises': 'warn',
|
|
124
124
|
'func-style': ['error', 'expression', { allowArrowFunctions: true }],
|
|
125
125
|
'no-else-return': ['error', { allowElseIf: false }],
|
|
126
|
+
'@unix/function-blank-lines': 'error',
|
|
126
127
|
'@unix/compact-nonblock-statement': [
|
|
127
128
|
'error',
|
|
128
129
|
{ maxLineLength: prettierPrintWidth },
|