@unix/eslint 0.2.1 → 1.0.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
@@ -1,8 +1,11 @@
1
1
  // @ts-check
2
2
  import eslint from '@eslint/js'
3
- import stylistic from '@stylistic/eslint-plugin'
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/**',
@@ -41,14 +44,13 @@ export default [
41
44
  ecmaVersion: 'latest',
42
45
  },
43
46
  plugins: {
44
- '@stylistic': stylistic,
47
+ '@unix': internal,
45
48
  },
46
49
  },
47
50
  {
48
51
  ...eslint.configs.recommended,
49
52
  files: jsFiles,
50
53
  },
51
- withJsFiles(stylistic.configs['disable-legacy']),
52
54
  {
53
55
  files: jsFiles,
54
56
  rules: {
@@ -88,7 +90,6 @@ export default [
88
90
  'no-template-curly-in-string': 'error',
89
91
  'no-unassigned-vars': 'warn',
90
92
  'no-undef': 'error',
91
- 'no-unexpected-multiline': 'error',
92
93
  'no-unmodified-loop-condition': 'error',
93
94
  'no-unreachable': 'warn',
94
95
  'no-unsafe-finally': 'error',
@@ -100,7 +101,6 @@ export default [
100
101
  'require-atomic-updates': 'error',
101
102
  'use-isnan': 'error',
102
103
  'valid-typeof': 'error',
103
- 'arrow-body-style': ['error', 'as-needed'],
104
104
  'block-scoped-var': 'error',
105
105
  complexity: ['error', 3],
106
106
  'consistent-return': 'off',
@@ -112,7 +112,7 @@ export default [
112
112
  'max-classes-per-file': ['error', 3],
113
113
  'max-depth': ['error', 3],
114
114
  'max-nested-callbacks': ['error', 4],
115
- 'max-params': ['error', 4],
115
+ 'max-params': ['error', 5],
116
116
  'no-alert': 'error',
117
117
  'no-case-declarations': 'off',
118
118
  'no-console': 'off',
@@ -123,18 +123,14 @@ export default [
123
123
  'no-regex-spaces': 'error',
124
124
  'no-useless-return': 'off',
125
125
  'no-var': 'error',
126
- 'prefer-arrow-callback': 'error',
127
126
  'prefer-rest-params': 'error',
128
127
  'no-else-return': ['error', { allowElseIf: false }],
129
- curly: ['error', 'multi'],
130
- '@stylistic/array-bracket-newline': ['error', 'consistent'],
131
- '@stylistic/array-element-newline': ['error', 'consistent'],
132
- '@stylistic/arrow-parens': ['error', 'as-needed'],
133
- '@stylistic/implicit-arrow-linebreak': ['error', 'beside'],
134
- '@stylistic/linebreak-style': ['error', 'unix'],
135
- '@stylistic/multiline-comment-style': ['error', 'bare-block'],
136
- '@stylistic/no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1 }],
137
- '@stylistic/nonblock-statement-body-position': ['error', 'beside'],
128
+ '@unix/compact-nonblock-statement': [
129
+ 'error',
130
+ { maxLineLength: prettierPrintWidth },
131
+ ],
132
+ '@unix/compact-return-if': 'error',
138
133
  },
139
134
  },
135
+ withJsFiles(prettierRecommended),
140
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 stylistic from '@stylistic/eslint-plugin'
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',
@@ -138,7 +146,7 @@ export default tseslint.config(
138
146
  'error',
139
147
  {
140
148
  countVoidThis: false,
141
- max: 4,
149
+ max: 5,
142
150
  },
143
151
  ],
144
152
  '@typescript-eslint/no-array-delete': 'error',
@@ -157,7 +165,7 @@ export default tseslint.config(
157
165
  'no-shadow': 'off',
158
166
  '@typescript-eslint/no-shadow': 'error',
159
167
  '@typescript-eslint/no-this-alias': 'error',
160
- '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error',
168
+ '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off',
161
169
  '@typescript-eslint/no-unnecessary-condition': 'error',
162
170
  '@typescript-eslint/no-unnecessary-parameter-property-assignment': 'error',
163
171
  '@typescript-eslint/no-unnecessary-template-expression': '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.2.1",
3
+ "version": "1.0.1",
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
- "@stylistic/eslint-plugin": "^5.10.0",
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": {