@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 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
@@ -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 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unix/eslint",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "ESLint config for all @unix projects.",
5
5
  "type": "module",
6
6
  "main": "index.js",