@unix/eslint 1.0.1 → 1.1.0
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 +5 -2
- package/configs/plugins/function-blank-lines.js +247 -0
- package/configs/plugins/index.js +2 -0
- package/configs/ts.js +1 -0
- package/package.json +1 -1
package/configs/js.js
CHANGED
|
@@ -10,6 +10,7 @@ const prettierPrintWidth = 85
|
|
|
10
10
|
const ignores = [
|
|
11
11
|
'**/node_modules/**',
|
|
12
12
|
'**/.git/**',
|
|
13
|
+
'**/.github/**',
|
|
13
14
|
'**/.hg/**',
|
|
14
15
|
'**/.svn/**',
|
|
15
16
|
'**/dist/**',
|
|
@@ -20,12 +21,13 @@ const ignores = [
|
|
|
20
21
|
'**/.next/**',
|
|
21
22
|
'**/.nuxt/**',
|
|
22
23
|
'**/.astro/**',
|
|
24
|
+
'**/.wrangler/**',
|
|
23
25
|
'**/.output/**',
|
|
24
|
-
'**/.svelte-kit/**',
|
|
25
|
-
'**/.turbo/**',
|
|
26
26
|
'**/.vite/**',
|
|
27
|
+
'**/.idea/**',
|
|
27
28
|
'**/storybook-static/**',
|
|
28
29
|
'**/tmp/**',
|
|
30
|
+
'**/.tmp/**',
|
|
29
31
|
'**/temp/**',
|
|
30
32
|
]
|
|
31
33
|
|
|
@@ -125,6 +127,7 @@ export default [
|
|
|
125
127
|
'no-var': 'error',
|
|
126
128
|
'prefer-rest-params': 'error',
|
|
127
129
|
'no-else-return': ['error', { allowElseIf: false }],
|
|
130
|
+
'@unix/function-blank-lines': 'error',
|
|
128
131
|
'@unix/compact-nonblock-statement': [
|
|
129
132
|
'error',
|
|
130
133
|
{ maxLineLength: prettierPrintWidth },
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
const lineBreakFor = sourceCode => (sourceCode.text.includes('\r\n') ? '\r\n' : '\n')
|
|
2
|
+
|
|
3
|
+
const indentationFor = (sourceCode, token) => {
|
|
4
|
+
const line = sourceCode.lines[token.loc.start.line - 1] ?? ''
|
|
5
|
+
|
|
6
|
+
return line.slice(0, token.loc.start.column)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const spacingBefore = (sourceCode, token, blankLines) =>
|
|
10
|
+
`${lineBreakFor(sourceCode).repeat(blankLines + 1)}${indentationFor(
|
|
11
|
+
sourceCode,
|
|
12
|
+
token,
|
|
13
|
+
)}`
|
|
14
|
+
|
|
15
|
+
const paddingLineSequencesBetween = (sourceCode, leftToken, rightToken) => {
|
|
16
|
+
const sequences = []
|
|
17
|
+
let previousToken = leftToken
|
|
18
|
+
|
|
19
|
+
while (previousToken.range[0] < rightToken.range[0]) {
|
|
20
|
+
const nextToken = sourceCode.getTokenAfter(previousToken, {
|
|
21
|
+
includeComments: true,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if (!nextToken || nextToken.range[0] > rightToken.range[0]) break
|
|
25
|
+
|
|
26
|
+
const blankLines = nextToken.loc.start.line - previousToken.loc.end.line - 1
|
|
27
|
+
|
|
28
|
+
if (blankLines > 0) {
|
|
29
|
+
sequences.push({
|
|
30
|
+
blankLines,
|
|
31
|
+
leftToken: previousToken,
|
|
32
|
+
rightToken: nextToken,
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
previousToken = nextToken
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return sequences
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const blankLineCount = sequences =>
|
|
43
|
+
sequences.reduce((count, sequence) => count + sequence.blankLines, 0)
|
|
44
|
+
|
|
45
|
+
const hasTokensBetween = (sourceCode, leftToken, rightToken) =>
|
|
46
|
+
sourceCode.getTokensBetween(leftToken, rightToken, { includeComments: true })
|
|
47
|
+
.length > 0
|
|
48
|
+
|
|
49
|
+
const reportPadding = (
|
|
50
|
+
context,
|
|
51
|
+
sourceCode,
|
|
52
|
+
node,
|
|
53
|
+
leftToken,
|
|
54
|
+
rightToken,
|
|
55
|
+
maxBlankLines,
|
|
56
|
+
messageId,
|
|
57
|
+
) => {
|
|
58
|
+
const sequences = paddingLineSequencesBetween(sourceCode, leftToken, rightToken)
|
|
59
|
+
|
|
60
|
+
if (blankLineCount(sequences) <= maxBlankLines) return
|
|
61
|
+
|
|
62
|
+
const [firstSequence] = sequences
|
|
63
|
+
|
|
64
|
+
context.report({
|
|
65
|
+
fix(fixer) {
|
|
66
|
+
if (hasTokensBetween(sourceCode, leftToken, rightToken)) return null
|
|
67
|
+
|
|
68
|
+
return fixer.replaceTextRange(
|
|
69
|
+
[leftToken.range[1], rightToken.range[0]],
|
|
70
|
+
spacingBefore(sourceCode, rightToken, maxBlankLines),
|
|
71
|
+
)
|
|
72
|
+
},
|
|
73
|
+
loc: {
|
|
74
|
+
end: firstSequence.rightToken.loc.start,
|
|
75
|
+
start: {
|
|
76
|
+
column: 0,
|
|
77
|
+
line: firstSequence.leftToken.loc.end.line + 1,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
messageId,
|
|
81
|
+
node,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const isReturnStatement = node => node.type === 'ReturnStatement'
|
|
86
|
+
|
|
87
|
+
const messageIdForStatementGap = (previousStatement, nextStatement) => {
|
|
88
|
+
if (isReturnStatement(previousStatement)) return 'unexpectedBlankLineAfterReturn'
|
|
89
|
+
if (isReturnStatement(nextStatement)) return 'tooManyBlankLinesBeforeReturn'
|
|
90
|
+
|
|
91
|
+
return 'unexpectedBlankLine'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const maxBlankLinesForStatementGap = (previousStatement, nextStatement) => {
|
|
95
|
+
if (isReturnStatement(previousStatement)) return 0
|
|
96
|
+
if (isReturnStatement(nextStatement)) return 1
|
|
97
|
+
|
|
98
|
+
return 0
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const checkStatementList = (
|
|
102
|
+
context,
|
|
103
|
+
sourceCode,
|
|
104
|
+
node,
|
|
105
|
+
statements,
|
|
106
|
+
leftToken,
|
|
107
|
+
rightToken,
|
|
108
|
+
) => {
|
|
109
|
+
if (statements.length === 0) {
|
|
110
|
+
reportPadding(
|
|
111
|
+
context,
|
|
112
|
+
sourceCode,
|
|
113
|
+
node,
|
|
114
|
+
leftToken,
|
|
115
|
+
rightToken,
|
|
116
|
+
0,
|
|
117
|
+
'unexpectedBlankLine',
|
|
118
|
+
)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const [firstStatement] = statements
|
|
123
|
+
const lastStatement = statements[statements.length - 1]
|
|
124
|
+
|
|
125
|
+
reportPadding(
|
|
126
|
+
context,
|
|
127
|
+
sourceCode,
|
|
128
|
+
firstStatement,
|
|
129
|
+
leftToken,
|
|
130
|
+
sourceCode.getFirstToken(firstStatement),
|
|
131
|
+
0,
|
|
132
|
+
'unexpectedBlankLine',
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
for (let index = 1; index < statements.length; index += 1) {
|
|
136
|
+
const previousStatement = statements[index - 1]
|
|
137
|
+
const nextStatement = statements[index]
|
|
138
|
+
|
|
139
|
+
reportPadding(
|
|
140
|
+
context,
|
|
141
|
+
sourceCode,
|
|
142
|
+
nextStatement,
|
|
143
|
+
sourceCode.getLastToken(previousStatement),
|
|
144
|
+
sourceCode.getFirstToken(nextStatement),
|
|
145
|
+
maxBlankLinesForStatementGap(previousStatement, nextStatement),
|
|
146
|
+
messageIdForStatementGap(previousStatement, nextStatement),
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
reportPadding(
|
|
151
|
+
context,
|
|
152
|
+
sourceCode,
|
|
153
|
+
lastStatement,
|
|
154
|
+
sourceCode.getLastToken(lastStatement),
|
|
155
|
+
rightToken,
|
|
156
|
+
0,
|
|
157
|
+
isReturnStatement(lastStatement)
|
|
158
|
+
? 'unexpectedBlankLineAfterReturn'
|
|
159
|
+
: 'unexpectedBlankLine',
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const caseColonToken = (sourceCode, node) => {
|
|
164
|
+
const tokenBeforeConsequent = sourceCode.getTokenBefore(node.consequent[0])
|
|
165
|
+
|
|
166
|
+
if (tokenBeforeConsequent?.value === ':') return tokenBeforeConsequent
|
|
167
|
+
|
|
168
|
+
const firstToken = sourceCode.getFirstToken(node)
|
|
169
|
+
|
|
170
|
+
return sourceCode.getTokenAfter(firstToken, {
|
|
171
|
+
filter: token => token.value === ':',
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const caseEndToken = (sourceCode, node) => {
|
|
176
|
+
const lastStatement = node.consequent[node.consequent.length - 1]
|
|
177
|
+
|
|
178
|
+
return sourceCode.getTokenAfter(sourceCode.getLastToken(lastStatement))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const functionBlankLines = {
|
|
182
|
+
meta: {
|
|
183
|
+
type: 'layout',
|
|
184
|
+
docs: {
|
|
185
|
+
description:
|
|
186
|
+
'Disallow extra blank lines in functions except before return statements',
|
|
187
|
+
},
|
|
188
|
+
fixable: 'whitespace',
|
|
189
|
+
messages: {
|
|
190
|
+
tooManyBlankLinesBeforeReturn:
|
|
191
|
+
'Return statements may have at most one blank line above them.',
|
|
192
|
+
unexpectedBlankLine: 'Unexpected blank line inside function.',
|
|
193
|
+
unexpectedBlankLineAfterReturn: 'Unexpected blank line after return.',
|
|
194
|
+
},
|
|
195
|
+
schema: [],
|
|
196
|
+
},
|
|
197
|
+
create(context) {
|
|
198
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode()
|
|
199
|
+
let functionDepth = 0
|
|
200
|
+
|
|
201
|
+
const enterFunction = () => {
|
|
202
|
+
functionDepth += 1
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const exitFunction = () => {
|
|
206
|
+
functionDepth -= 1
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const isInsideFunction = () => functionDepth > 0
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
ArrowFunctionExpression: enterFunction,
|
|
213
|
+
'ArrowFunctionExpression:exit': exitFunction,
|
|
214
|
+
BlockStatement(node) {
|
|
215
|
+
if (!isInsideFunction()) return
|
|
216
|
+
|
|
217
|
+
checkStatementList(
|
|
218
|
+
context,
|
|
219
|
+
sourceCode,
|
|
220
|
+
node,
|
|
221
|
+
node.body,
|
|
222
|
+
sourceCode.getFirstToken(node),
|
|
223
|
+
sourceCode.getLastToken(node),
|
|
224
|
+
)
|
|
225
|
+
},
|
|
226
|
+
FunctionDeclaration: enterFunction,
|
|
227
|
+
'FunctionDeclaration:exit': exitFunction,
|
|
228
|
+
FunctionExpression: enterFunction,
|
|
229
|
+
'FunctionExpression:exit': exitFunction,
|
|
230
|
+
SwitchCase(node) {
|
|
231
|
+
if (!isInsideFunction()) return
|
|
232
|
+
if (node.consequent.length === 0) return
|
|
233
|
+
|
|
234
|
+
checkStatementList(
|
|
235
|
+
context,
|
|
236
|
+
sourceCode,
|
|
237
|
+
node,
|
|
238
|
+
node.consequent,
|
|
239
|
+
caseColonToken(sourceCode, node),
|
|
240
|
+
caseEndToken(sourceCode, node),
|
|
241
|
+
)
|
|
242
|
+
},
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
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 },
|