@unix/eslint 0.2.0 → 1.0.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
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import eslint from '@eslint/js'
|
|
3
|
-
import
|
|
3
|
+
import prettierRecommended from 'eslint-plugin-prettier/recommended'
|
|
4
|
+
|
|
5
|
+
import internal from './plugins/index.js'
|
|
4
6
|
|
|
5
7
|
const jsFiles = ['**/*.{js,mjs,cjs,jsx}']
|
|
8
|
+
const prettierPrintWidth = 85
|
|
6
9
|
|
|
7
10
|
const ignores = [
|
|
8
11
|
'**/node_modules/**',
|
|
@@ -40,12 +43,14 @@ export default [
|
|
|
40
43
|
languageOptions: {
|
|
41
44
|
ecmaVersion: 'latest',
|
|
42
45
|
},
|
|
46
|
+
plugins: {
|
|
47
|
+
'@unix': internal,
|
|
48
|
+
},
|
|
43
49
|
},
|
|
44
50
|
{
|
|
45
51
|
...eslint.configs.recommended,
|
|
46
52
|
files: jsFiles,
|
|
47
53
|
},
|
|
48
|
-
withJsFiles(stylistic.configs['disable-legacy']),
|
|
49
54
|
{
|
|
50
55
|
files: jsFiles,
|
|
51
56
|
rules: {
|
|
@@ -85,7 +90,6 @@ export default [
|
|
|
85
90
|
'no-template-curly-in-string': 'error',
|
|
86
91
|
'no-unassigned-vars': 'warn',
|
|
87
92
|
'no-undef': 'error',
|
|
88
|
-
'no-unexpected-multiline': 'error',
|
|
89
93
|
'no-unmodified-loop-condition': 'error',
|
|
90
94
|
'no-unreachable': 'warn',
|
|
91
95
|
'no-unsafe-finally': 'error',
|
|
@@ -97,7 +101,6 @@ export default [
|
|
|
97
101
|
'require-atomic-updates': 'error',
|
|
98
102
|
'use-isnan': 'error',
|
|
99
103
|
'valid-typeof': 'error',
|
|
100
|
-
'arrow-body-style': ['error', 'as-needed'],
|
|
101
104
|
'block-scoped-var': 'error',
|
|
102
105
|
complexity: ['error', 3],
|
|
103
106
|
'consistent-return': 'off',
|
|
@@ -120,18 +123,14 @@ export default [
|
|
|
120
123
|
'no-regex-spaces': 'error',
|
|
121
124
|
'no-useless-return': 'off',
|
|
122
125
|
'no-var': 'error',
|
|
123
|
-
'prefer-arrow-callback': 'error',
|
|
124
126
|
'prefer-rest-params': 'error',
|
|
125
127
|
'no-else-return': ['error', { allowElseIf: false }],
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
'@
|
|
131
|
-
'@stylistic/linebreak-style': ['error', 'unix'],
|
|
132
|
-
'@stylistic/multiline-comment-style': ['error', 'bare-block'],
|
|
133
|
-
'@stylistic/no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1 }],
|
|
134
|
-
'@stylistic/nonblock-statement-body-position': ['error', 'beside'],
|
|
128
|
+
'@unix/compact-nonblock-statement': [
|
|
129
|
+
'error',
|
|
130
|
+
{ maxLineLength: prettierPrintWidth },
|
|
131
|
+
],
|
|
132
|
+
'@unix/compact-return-if': 'error',
|
|
135
133
|
},
|
|
136
134
|
},
|
|
135
|
+
withJsFiles(prettierRecommended),
|
|
137
136
|
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const DEFAULT_MAX_LINE_LENGTH = 85
|
|
2
|
+
|
|
3
|
+
const isSingleLine = node => node.loc.start.line === node.loc.end.line
|
|
4
|
+
|
|
5
|
+
const hasOnlyWhitespaceBetween = (sourceCode, left, right) =>
|
|
6
|
+
sourceCode.text.slice(left.range[1], right.range[0]).trim().length === 0
|
|
7
|
+
|
|
8
|
+
const linePrefixUntil = (sourceCode, token) => {
|
|
9
|
+
const line = sourceCode.lines[token.loc.end.line - 1] ?? ''
|
|
10
|
+
|
|
11
|
+
return line.slice(0, token.loc.end.column)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const compactLineLength = (sourceCode, tokenBeforeBody, body) =>
|
|
15
|
+
`${linePrefixUntil(sourceCode, tokenBeforeBody)} ${sourceCode.getText(body)}`
|
|
16
|
+
.length
|
|
17
|
+
|
|
18
|
+
const isCompactableBody = (sourceCode, body, maxLineLength, headerStartLine) => {
|
|
19
|
+
if (body.type === 'BlockStatement') return false
|
|
20
|
+
if (!isSingleLine(body)) return false
|
|
21
|
+
|
|
22
|
+
const tokenBeforeBody = sourceCode.getTokenBefore(body)
|
|
23
|
+
|
|
24
|
+
if (!tokenBeforeBody) return false
|
|
25
|
+
if (
|
|
26
|
+
headerStartLine !== undefined &&
|
|
27
|
+
headerStartLine !== tokenBeforeBody.loc.end.line
|
|
28
|
+
)
|
|
29
|
+
return false
|
|
30
|
+
if (tokenBeforeBody.loc.end.line === body.loc.start.line) return false
|
|
31
|
+
if (!hasOnlyWhitespaceBetween(sourceCode, tokenBeforeBody, body)) return false
|
|
32
|
+
|
|
33
|
+
return compactLineLength(sourceCode, tokenBeforeBody, body) <= maxLineLength
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const reportCompactableBody = (
|
|
37
|
+
context,
|
|
38
|
+
sourceCode,
|
|
39
|
+
body,
|
|
40
|
+
maxLineLength,
|
|
41
|
+
headerStartLine,
|
|
42
|
+
) => {
|
|
43
|
+
if (!isCompactableBody(sourceCode, body, maxLineLength, headerStartLine)) return
|
|
44
|
+
|
|
45
|
+
const tokenBeforeBody = sourceCode.getTokenBefore(body)
|
|
46
|
+
|
|
47
|
+
context.report({
|
|
48
|
+
fix(fixer) {
|
|
49
|
+
return fixer.replaceTextRange([tokenBeforeBody.range[1], body.range[0]], ' ')
|
|
50
|
+
},
|
|
51
|
+
messageId: 'compactNonblockStatement',
|
|
52
|
+
node: body,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const compactNonblockStatement = {
|
|
57
|
+
meta: {
|
|
58
|
+
type: 'layout',
|
|
59
|
+
docs: {
|
|
60
|
+
description: 'Require short non-block control bodies to stay on one line',
|
|
61
|
+
},
|
|
62
|
+
fixable: 'whitespace',
|
|
63
|
+
messages: {
|
|
64
|
+
compactNonblockStatement: 'Move this short statement onto the control line.',
|
|
65
|
+
},
|
|
66
|
+
schema: [
|
|
67
|
+
{
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
properties: {
|
|
70
|
+
maxLineLength: {
|
|
71
|
+
minimum: 1,
|
|
72
|
+
type: 'integer',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
type: 'object',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
create(context) {
|
|
80
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode()
|
|
81
|
+
const [{ maxLineLength = DEFAULT_MAX_LINE_LENGTH } = {}] = context.options
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
DoWhileStatement(node) {
|
|
85
|
+
reportCompactableBody(
|
|
86
|
+
context,
|
|
87
|
+
sourceCode,
|
|
88
|
+
node.body,
|
|
89
|
+
maxLineLength,
|
|
90
|
+
node.loc.start.line,
|
|
91
|
+
)
|
|
92
|
+
},
|
|
93
|
+
ForInStatement(node) {
|
|
94
|
+
reportCompactableBody(
|
|
95
|
+
context,
|
|
96
|
+
sourceCode,
|
|
97
|
+
node.body,
|
|
98
|
+
maxLineLength,
|
|
99
|
+
node.loc.start.line,
|
|
100
|
+
)
|
|
101
|
+
},
|
|
102
|
+
ForOfStatement(node) {
|
|
103
|
+
reportCompactableBody(
|
|
104
|
+
context,
|
|
105
|
+
sourceCode,
|
|
106
|
+
node.body,
|
|
107
|
+
maxLineLength,
|
|
108
|
+
node.loc.start.line,
|
|
109
|
+
)
|
|
110
|
+
},
|
|
111
|
+
ForStatement(node) {
|
|
112
|
+
reportCompactableBody(
|
|
113
|
+
context,
|
|
114
|
+
sourceCode,
|
|
115
|
+
node.body,
|
|
116
|
+
maxLineLength,
|
|
117
|
+
node.loc.start.line,
|
|
118
|
+
)
|
|
119
|
+
},
|
|
120
|
+
IfStatement(node) {
|
|
121
|
+
reportCompactableBody(
|
|
122
|
+
context,
|
|
123
|
+
sourceCode,
|
|
124
|
+
node.consequent,
|
|
125
|
+
maxLineLength,
|
|
126
|
+
node.loc.start.line,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if (!node.alternate || node.alternate.type === 'IfStatement') return
|
|
130
|
+
|
|
131
|
+
reportCompactableBody(context, sourceCode, node.alternate, maxLineLength)
|
|
132
|
+
},
|
|
133
|
+
WhileStatement(node) {
|
|
134
|
+
reportCompactableBody(
|
|
135
|
+
context,
|
|
136
|
+
sourceCode,
|
|
137
|
+
node.body,
|
|
138
|
+
maxLineLength,
|
|
139
|
+
node.loc.start.line,
|
|
140
|
+
)
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export default compactNonblockStatement
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const getIfPrefix = (sourceCode, node) =>
|
|
2
|
+
sourceCode.text.slice(node.range[0], node.consequent.range[0]).trimEnd()
|
|
3
|
+
|
|
4
|
+
const hasBlockComment = (sourceCode, block) =>
|
|
5
|
+
sourceCode.getCommentsInside(block).length > 0
|
|
6
|
+
|
|
7
|
+
const isBlockWithOneStatement = node => {
|
|
8
|
+
if (node.type !== 'BlockStatement') return false
|
|
9
|
+
|
|
10
|
+
return node.body.length === 1
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const isSingleLineReturn = statement => {
|
|
14
|
+
if (statement.type !== 'ReturnStatement') return false
|
|
15
|
+
|
|
16
|
+
return statement.loc.start.line === statement.loc.end.line
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const getReturnStatement = node => {
|
|
20
|
+
if (node.alternate) return undefined
|
|
21
|
+
if (!isBlockWithOneStatement(node.consequent)) return undefined
|
|
22
|
+
|
|
23
|
+
return node.consequent.body[0]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const isCompactableReturn = (sourceCode, node, statement) => {
|
|
27
|
+
if (!isSingleLineReturn(statement)) return false
|
|
28
|
+
if (hasBlockComment(sourceCode, node.consequent)) return false
|
|
29
|
+
|
|
30
|
+
return node.loc.start.line === node.consequent.loc.start.line
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const getCompactReturnStatement = (sourceCode, node) => {
|
|
34
|
+
const statement = getReturnStatement(node)
|
|
35
|
+
|
|
36
|
+
if (!statement) return undefined
|
|
37
|
+
if (!isCompactableReturn(sourceCode, node, statement)) return undefined
|
|
38
|
+
|
|
39
|
+
return statement
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const compactReturnIf = {
|
|
43
|
+
meta: {
|
|
44
|
+
type: 'layout',
|
|
45
|
+
docs: {
|
|
46
|
+
description: 'Require single-return if statements to use a compact body',
|
|
47
|
+
},
|
|
48
|
+
fixable: 'code',
|
|
49
|
+
messages: {
|
|
50
|
+
compactReturnIf: 'Unnecessary braces around single-line return.',
|
|
51
|
+
},
|
|
52
|
+
schema: [],
|
|
53
|
+
},
|
|
54
|
+
create(context) {
|
|
55
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode()
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
IfStatement(node) {
|
|
59
|
+
const statement = getCompactReturnStatement(sourceCode, node)
|
|
60
|
+
|
|
61
|
+
if (!statement) return
|
|
62
|
+
|
|
63
|
+
context.report({
|
|
64
|
+
fix(fixer) {
|
|
65
|
+
return fixer.replaceTextRange(
|
|
66
|
+
[node.range[0], node.consequent.range[1]],
|
|
67
|
+
`${getIfPrefix(sourceCode, node)} ${sourceCode.getText(statement)}`,
|
|
68
|
+
)
|
|
69
|
+
},
|
|
70
|
+
messageId: 'compactReturnIf',
|
|
71
|
+
node: node.consequent,
|
|
72
|
+
})
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default compactReturnIf
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import compactNonblockStatement from './compact-nonblock-statement.js'
|
|
2
|
+
import compactReturnIf from './compact-return-if.js'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
rules: {
|
|
6
|
+
'compact-nonblock-statement': compactNonblockStatement,
|
|
7
|
+
'compact-return-if': compactReturnIf,
|
|
8
|
+
},
|
|
9
|
+
}
|
package/configs/ts.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import eslint from '@eslint/js'
|
|
3
|
-
import
|
|
3
|
+
import prettierRecommended from 'eslint-plugin-prettier/recommended'
|
|
4
4
|
import tseslint from 'typescript-eslint'
|
|
5
5
|
|
|
6
6
|
import js from './js.js'
|
|
7
|
+
import internal from './plugins/index.js'
|
|
7
8
|
|
|
8
9
|
const tsFiles = ['**/*.{ts,mts,cts,tsx}']
|
|
10
|
+
const prettierPrintWidth = 85
|
|
9
11
|
|
|
10
12
|
const classMemberOrder = [
|
|
11
13
|
[
|
|
@@ -98,9 +100,10 @@ export default tseslint.config(
|
|
|
98
100
|
projectService: true,
|
|
99
101
|
},
|
|
100
102
|
},
|
|
103
|
+
plugins: {
|
|
104
|
+
'@unix': internal,
|
|
105
|
+
},
|
|
101
106
|
},
|
|
102
|
-
withTsFiles(stylistic.configs['disable-legacy']),
|
|
103
|
-
withTsFiles(stylistic.configs.recommended),
|
|
104
107
|
{
|
|
105
108
|
files: tsFiles,
|
|
106
109
|
rules: {
|
|
@@ -120,6 +123,11 @@ export default tseslint.config(
|
|
|
120
123
|
'@typescript-eslint/no-floating-promises': 'warn',
|
|
121
124
|
'func-style': ['error', 'expression', { allowArrowFunctions: true }],
|
|
122
125
|
'no-else-return': ['error', { allowElseIf: false }],
|
|
126
|
+
'@unix/compact-nonblock-statement': [
|
|
127
|
+
'error',
|
|
128
|
+
{ maxLineLength: prettierPrintWidth },
|
|
129
|
+
],
|
|
130
|
+
'@unix/compact-return-if': 'error',
|
|
123
131
|
'@typescript-eslint/await-thenable': 'error',
|
|
124
132
|
'@typescript-eslint/consistent-generic-constructors': ['error', 'constructor'],
|
|
125
133
|
'@typescript-eslint/consistent-type-exports': 'error',
|
|
@@ -188,4 +196,5 @@ export default tseslint.config(
|
|
|
188
196
|
'@typescript-eslint/unified-signatures': 'error',
|
|
189
197
|
},
|
|
190
198
|
},
|
|
199
|
+
withTsFiles(prettierRecommended),
|
|
191
200
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unix/eslint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "ESLint config for all @unix projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -36,12 +36,13 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@eslint/js": "^10.0.1",
|
|
39
|
-
"
|
|
39
|
+
"eslint-config-prettier": "^10.1.8",
|
|
40
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
41
|
+
"prettier": "^3.8.4",
|
|
40
42
|
"typescript-eslint": "^8.59.2"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
|
-
"@unix/prettier": "^0.1.0"
|
|
44
|
-
"prettier": "^3.8.4"
|
|
45
|
+
"@unix/prettier": "^0.1.0"
|
|
45
46
|
},
|
|
46
47
|
"license": "MIT",
|
|
47
48
|
"scripts": {
|