@unix/eslint 1.0.2 → 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 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,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
@@ -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.0",
4
4
  "description": "ESLint config for all @unix projects.",
5
5
  "type": "module",
6
6
  "main": "index.js",