eslint-plugin-unicorn 28.0.2 → 29.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/index.js CHANGED
@@ -71,6 +71,7 @@ module.exports = {
71
71
  'unicorn/no-null': 'error',
72
72
  'unicorn/no-object-as-default-parameter': 'error',
73
73
  'unicorn/no-process-exit': 'error',
74
+ 'unicorn/no-static-only-class': 'error',
74
75
  'unicorn/no-this-assignment': 'error',
75
76
  'unicorn/no-unreadable-array-destructuring': 'error',
76
77
  'unicorn/no-unsafe-regex': 'off',
@@ -83,6 +84,8 @@ module.exports = {
83
84
  'unicorn/prefer-add-event-listener': 'error',
84
85
  'unicorn/prefer-array-find': 'error',
85
86
  // TODO: Enable this by default when targeting Node.js 12.
87
+ 'unicorn/prefer-array-flat': 'off',
88
+ // TODO: Enable this by default when targeting Node.js 12.
86
89
  'unicorn/prefer-array-flat-map': 'off',
87
90
  'unicorn/prefer-array-index-of': 'error',
88
91
  'unicorn/prefer-array-some': 'error',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-unicorn",
3
- "version": "28.0.2",
3
+ "version": "29.0.0",
4
4
  "description": "Various awesome ESLint rules",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/eslint-plugin-unicorn",
@@ -16,7 +16,7 @@
16
16
  "scripts": {
17
17
  "test": "xo && nyc ava",
18
18
  "create-rule": "node ./scripts/create-rule.js",
19
- "run-rules-on-codebase": "node ./test/run-rules-on-codebase/lint.js",
19
+ "run-rules-on-codebase": "node ./test/run-rules-on-codebase/lint.mjs",
20
20
  "integration": "node ./test/integration/test.js",
21
21
  "smoke": "eslint-remote-tester --config ./test/smoke/eslint-remote-tester.config.js"
22
22
  },
@@ -35,52 +35,52 @@
35
35
  "xo"
36
36
  ],
37
37
  "dependencies": {
38
- "ci-info": "^2.0.0",
38
+ "ci-info": "^3.1.1",
39
39
  "clean-regexp": "^1.0.0",
40
- "eslint-template-visitor": "^2.2.2",
40
+ "eslint-template-visitor": "^2.3.2",
41
41
  "eslint-utils": "^2.1.0",
42
42
  "eslint-visitor-keys": "^2.0.0",
43
43
  "import-modules": "^2.1.0",
44
44
  "lodash": "^4.17.20",
45
45
  "pluralize": "^8.0.0",
46
46
  "read-pkg-up": "^7.0.1",
47
- "regexp-tree": "^0.1.22",
47
+ "regexp-tree": "^0.1.23",
48
48
  "reserved-words": "^0.1.2",
49
49
  "safe-regex": "^2.1.1",
50
50
  "semver": "^7.3.4"
51
51
  },
52
52
  "devDependencies": {
53
- "@ava/babel": "^1.0.1",
54
53
  "@babel/code-frame": "7.12.13",
55
- "@babel/core": "7.12.10",
56
- "@babel/eslint-parser": "7.12.1",
54
+ "@babel/core": "7.12.17",
55
+ "@babel/eslint-parser": "7.12.17",
57
56
  "@lubien/fixture-beta-package": "^1.0.0-beta.1",
58
- "@typescript-eslint/parser": "^4.12.0",
57
+ "@typescript-eslint/parser": "^4.15.1",
59
58
  "ava": "^3.15.0",
60
59
  "babel-eslint": "^10.1.0",
61
60
  "chalk": "^4.1.0",
62
61
  "enquirer": "2.3.6",
63
- "eslint": "^7.15.0",
62
+ "eslint": "^7.20.0",
64
63
  "eslint-ava-rule-tester": "^4.0.0",
65
64
  "eslint-plugin-eslint-plugin": "^2.3.0",
66
65
  "eslint-remote-tester": "^1.1.0",
67
66
  "execa": "^5.0.0",
68
67
  "listr": "^0.14.3",
68
+ "lodash-es": "4.17.20",
69
69
  "mem": "8.0.0",
70
70
  "nyc": "^15.1.0",
71
71
  "outdent": "^0.8.0",
72
72
  "pify": "^5.0.0",
73
- "typescript": "^4.1.3",
74
- "vue-eslint-parser": "^7.3.0",
75
- "xo": "^0.37.1"
73
+ "typescript": "^4.1.5",
74
+ "vue-eslint-parser": "^7.5.0",
75
+ "xo": "^0.38.1"
76
76
  },
77
77
  "peerDependencies": {
78
- "eslint": ">=7.17.0"
78
+ "eslint": ">=7.20.0"
79
79
  },
80
80
  "ava": {
81
- "babel": true,
82
81
  "files": [
83
- "test/*.js"
82
+ "test/*.mjs",
83
+ "test/unit/*.mjs"
84
84
  ]
85
85
  },
86
86
  "nyc": {
@@ -114,7 +114,6 @@
114
114
  },
115
115
  {
116
116
  "files": [
117
- "test/*.js",
118
117
  "**/*.mjs"
119
118
  ],
120
119
  "parserOptions": {
@@ -124,6 +123,14 @@
124
123
  ],
125
124
  "rules": {
126
125
  "strict": "error",
126
+ "node/no-unsupported-features/node-builtins": [
127
+ "off",
128
+ {
129
+ "ignores": [
130
+ "module.createRequire"
131
+ ]
132
+ }
133
+ ],
127
134
  "unicorn/no-null": "error",
128
135
  "unicorn/prevent-abbreviations": [
129
136
  "error",
package/readme.md CHANGED
@@ -65,6 +65,7 @@ Configure it in `package.json`.
65
65
  "unicorn/no-null": "error",
66
66
  "unicorn/no-object-as-default-parameter": "error",
67
67
  "unicorn/no-process-exit": "error",
68
+ "unicorn/no-static-only-class": "error",
68
69
  "unicorn/no-this-assignment": "error",
69
70
  "unicorn/no-unreadable-array-destructuring": "error",
70
71
  "unicorn/no-unsafe-regex": "off",
@@ -75,6 +76,7 @@ Configure it in `package.json`.
75
76
  "unicorn/numeric-separators-style": "off",
76
77
  "unicorn/prefer-add-event-listener": "error",
77
78
  "unicorn/prefer-array-find": "error",
79
+ "unicorn/prefer-array-flat": "error",
78
80
  "unicorn/prefer-array-flat-map": "error",
79
81
  "unicorn/prefer-array-index-of": "error",
80
82
  "unicorn/prefer-array-some": "error",
@@ -143,6 +145,7 @@ Configure it in `package.json`.
143
145
  - [no-null](docs/rules/no-null.md) - Disallow the use of the `null` literal.
144
146
  - [no-object-as-default-parameter](docs/rules/no-object-as-default-parameter.md) - Disallow the use of objects as default parameters.
145
147
  - [no-process-exit](docs/rules/no-process-exit.md) - Disallow `process.exit()`.
148
+ - [no-static-only-class](docs/rules/no-static-only-class.md) - Forbid classes that only have static members. *(partly fixable)*
146
149
  - [no-this-assignment](docs/rules/no-this-assignment.md) - Disallow assigning `this` to a variable.
147
150
  - [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) - Disallow unreadable array destructuring. *(partly fixable)*
148
151
  - [no-unsafe-regex](docs/rules/no-unsafe-regex.md) - Disallow unsafe regular expressions.
@@ -153,6 +156,7 @@ Configure it in `package.json`.
153
156
  - [numeric-separators-style](docs/rules/numeric-separators-style.md) - Enforce the style of numeric separators by correctly grouping digits. *(fixable)*
154
157
  - [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) - Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. *(partly fixable)*
155
158
  - [prefer-array-find](docs/rules/prefer-array-find.md) - Prefer `.find(…)` over the first element from `.filter(…)`. *(partly fixable)*
159
+ - [prefer-array-flat](docs/rules/prefer-array-flat.md) - Prefer `Array#flat()` over legacy techniques to flatten arrays. *(fixable)*
156
160
  - [prefer-array-flat-map](docs/rules/prefer-array-flat-map.md) - Prefer `.flatMap(…)` over `.map(…).flat()`. *(fixable)*
157
161
  - [prefer-array-index-of](docs/rules/prefer-array-index-of.md) - Prefer `Array#indexOf()` over `Array#findIndex()` when looking for the index of an item. *(partly fixable)*
158
162
  - [prefer-array-some](docs/rules/prefer-array-some.md) - Prefer `.some(…)` over `.find(…)`.
@@ -173,7 +177,7 @@ Configure it in `package.json`.
173
177
  - [prefer-reflect-apply](docs/rules/prefer-reflect-apply.md) - Prefer `Reflect.apply()` over `Function#apply()`. *(fixable)*
174
178
  - [prefer-regexp-test](docs/rules/prefer-regexp-test.md) - Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`. *(fixable)*
175
179
  - [prefer-set-has](docs/rules/prefer-set-has.md) - Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. *(fixable)*
176
- - [prefer-spread](docs/rules/prefer-spread.md) - Prefer the spread operator over `Array.from()` and `Array#concat()`. *(partly fixable)*
180
+ - [prefer-spread](docs/rules/prefer-spread.md) - Prefer the spread operator over `Array.from()`, `Array#concat(…)` and `Array#slice()`. *(partly fixable)*
177
181
  - [prefer-string-replace-all](docs/rules/prefer-string-replace-all.md) - Prefer `String#replaceAll()` over regex searches with the global flag. *(fixable)*
178
182
  - [prefer-string-slice](docs/rules/prefer-string-slice.md) - Prefer `String#slice()` over `String#substr()` and `String#substring()`. *(partly fixable)*
179
183
  - [prefer-string-starts-ends-with](docs/rules/prefer-string-starts-ends-with.md) - Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()`. *(fixable)*
@@ -137,9 +137,7 @@ function englishishJoinWords(words) {
137
137
  return `${words[0]} or ${words[1]}`;
138
138
  }
139
139
 
140
- words = words.slice();
141
- const last = words.pop();
142
- return `${words.join(', ')}, or ${last}`;
140
+ return `${words.slice(0, -1).join(', ')}, or ${words[words.length - 1]}`;
143
141
  }
144
142
 
145
143
  const create = context => {
@@ -3,7 +3,7 @@ const {isParenthesized} = require('eslint-utils');
3
3
  const getDocumentationUrl = require('./utils/get-documentation-url');
4
4
  const methodSelector = require('./utils/method-selector');
5
5
  const {notFunctionSelector} = require('./utils/not-function');
6
- const isNodeMatches = require('./utils/is-node-matches');
6
+ const {isNodeMatches} = require('./utils/is-node-matches');
7
7
 
8
8
  const ERROR_WITH_NAME_MESSAGE_ID = 'error-with-name';
9
9
  const ERROR_WITHOUT_NAME_MESSAGE_ID = 'error-without-name';
@@ -14,7 +14,8 @@ const shouldAddParenthesesToExpressionStatementExpression = require('./utils/sho
14
14
  const getParenthesizedTimes = require('./utils/get-parenthesized-times');
15
15
  const extendFixRange = require('./utils/extend-fix-range');
16
16
  const isFunctionSelfUsedInside = require('./utils/is-function-self-used-inside');
17
- const isNodeMatches = require('./utils/is-node-matches');
17
+ const {isNodeMatches} = require('./utils/is-node-matches');
18
+ const assertToken = require('./utils/assert-token');
18
19
 
19
20
  const MESSAGE_ID = 'no-array-for-each';
20
21
  const messages = {
@@ -23,7 +24,8 @@ const messages = {
23
24
 
24
25
  const arrayForEachCallSelector = methodSelector({
25
26
  name: 'forEach',
26
- includeOptional: true
27
+ includeOptionalCall: true,
28
+ includeOptionalMember: true
27
29
  });
28
30
 
29
31
  const continueAbleNodeTypes = new Set([
@@ -48,15 +50,16 @@ function getFixFunction(callExpression, sourceCode, functionInfo) {
48
50
  const [callback] = callExpression.arguments;
49
51
  const parameters = callback.params;
50
52
  const array = callExpression.callee.object;
51
- const {returnStatements} = functionInfo.get(callback);
53
+ const {returnStatements, scope} = functionInfo.get(callback);
52
54
 
53
55
  const getForOfLoopHeadText = () => {
54
56
  const [elementText, indexText] = parameters.map(parameter => sourceCode.getText(parameter));
55
57
  const useEntries = parameters.length === 2;
56
58
 
57
- let text = 'for (const ';
59
+ let text = 'for (';
60
+ text += parameters.some(parameter => isParameterReassigned(parameter, scope)) ? 'let' : 'const';
61
+ text += ' ';
58
62
  text += useEntries ? `[${indexText}, ${elementText}]` : elementText;
59
-
60
63
  text += ' of ';
61
64
 
62
65
  let arrayText = sourceCode.getText(array);
@@ -81,6 +84,9 @@ function getFixFunction(callExpression, sourceCode, functionInfo) {
81
84
  if (callback.body.type === 'BlockStatement') {
82
85
  end = callback.body.range[0];
83
86
  } else {
87
+ // In this case, parentheses are not included in body location, so we look for `=>` token
88
+ // foo.forEach(bar => ({bar}))
89
+ // ^
84
90
  const arrowToken = sourceCode.getTokenBefore(callback.body, isArrowToken);
85
91
  end = arrowToken.range[1];
86
92
  }
@@ -90,11 +96,10 @@ function getFixFunction(callExpression, sourceCode, functionInfo) {
90
96
 
91
97
  function * replaceReturnStatement(returnStatement, fixer) {
92
98
  const returnToken = sourceCode.getFirstToken(returnStatement);
93
-
94
- /* istanbul ignore next: `ReturnStatement` firstToken should be `return` */
95
- if (returnToken.value !== 'return') {
96
- throw new Error(`Unexpected token ${returnToken.value}.`);
97
- }
99
+ assertToken(returnToken, {
100
+ expected: 'return',
101
+ ruleId: 'no-array-for-each'
102
+ });
98
103
 
99
104
  if (!returnStatement.argument) {
100
105
  yield fixer.replaceText(returnToken, 'continue');
@@ -166,20 +171,35 @@ function getFixFunction(callExpression, sourceCode, functionInfo) {
166
171
  }
167
172
 
168
173
  return function * (fixer) {
174
+ // Replace these with `for (const … of …) `
175
+ // foo.forEach(bar => bar)
176
+ // ^^^^^^^^^^^^^^^^^^ (space after `=>` didn't included)
177
+ // foo.forEach(bar => {})
178
+ // ^^^^^^^^^^^^^^^^^^^^^^
179
+ // foo.forEach(function(bar) {})
180
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
169
181
  yield fixer.replaceTextRange(getForOfLoopHeadRange(), getForOfLoopHeadText());
170
182
 
183
+ // Parenthesized callback function
184
+ // foo.forEach( ((bar => {})) )
185
+ // ^^
171
186
  yield * removeCallbackParentheses(fixer);
172
187
 
173
- // Remove call expression trailing comma
174
188
  const [
175
189
  penultimateToken,
176
190
  lastToken
177
191
  ] = sourceCode.getLastTokens(callExpression, 2);
178
192
 
193
+ // The possible trailing comma token of `Array#forEach()` CallExpression
194
+ // foo.forEach(bar => {},)
195
+ // ^
179
196
  if (isCommaToken(penultimateToken)) {
180
197
  yield fixer.remove(penultimateToken);
181
198
  }
182
199
 
200
+ // The closing parenthesis token of `Array#forEach()` CallExpression
201
+ // foo.forEach(bar => {})
202
+ // ^
183
203
  yield fixer.remove(lastToken);
184
204
 
185
205
  for (const returnStatement of returnStatements) {
@@ -187,11 +207,14 @@ function getFixFunction(callExpression, sourceCode, functionInfo) {
187
207
  }
188
208
 
189
209
  const expressionStatementLastToken = sourceCode.getLastToken(callExpression.parent);
210
+ // Remove semicolon if it's not needed anymore
211
+ // foo.forEach(bar => {});
212
+ // ^
190
213
  if (shouldRemoveExpressionStatementLastToken(expressionStatementLastToken)) {
191
214
  yield fixer.remove(expressionStatementLastToken, fixer);
192
215
  }
193
216
 
194
- // Prevent possible conflicts
217
+ // Prevent possible variable conflicts
195
218
  yield * extendFixRange(fixer, callExpression.parent.range);
196
219
  };
197
220
  }
@@ -257,6 +280,17 @@ function isParameterSafeToFix(parameter, {scope, array, allIdentifiers}) {
257
280
  return true;
258
281
  }
259
282
 
283
+ function isParameterReassigned(parameter, scope) {
284
+ const variable = findVariable(scope, parameter);
285
+ const {references} = variable;
286
+ return references.some(reference => {
287
+ const node = reference.identifier;
288
+ const {parent} = node;
289
+ return parent.type === 'UpdateExpression' ||
290
+ (parent.type === 'AssignmentExpression' && parent.left === node);
291
+ });
292
+ }
293
+
260
294
  function isFixable(callExpression, sourceCode, {scope, functionInfo, allIdentifiers}) {
261
295
  // Check `CallExpression`
262
296
  if (
@@ -3,6 +3,7 @@ const {hasSideEffect, isCommaToken, isSemicolonToken} = require('eslint-utils');
3
3
  const getDocumentationUrl = require('./utils/get-documentation-url');
4
4
  const methodSelector = require('./utils/method-selector');
5
5
  const getCallExpressionArgumentsText = require('./utils/get-call-expression-arguments-text');
6
+ const isSameReference = require('./utils/is-same-reference');
6
7
 
7
8
  const ERROR = 'error';
8
9
  const SUGGESTION = 'suggestion';
@@ -53,7 +54,7 @@ function create(context) {
53
54
  const secondCallArray = secondCall.callee.object;
54
55
 
55
56
  // Not same array
56
- if (sourceCode.getText(firstCallArray) !== sourceCode.getText(secondCallArray)) {
57
+ if (!isSameReference(firstCallArray, secondCallArray)) {
57
58
  return;
58
59
  }
59
60
 
@@ -83,10 +84,7 @@ function create(context) {
83
84
  );
84
85
  };
85
86
 
86
- if (
87
- hasSideEffect(firstCallArray, sourceCode) ||
88
- secondCallArguments.some(element => hasSideEffect(element, sourceCode))
89
- ) {
87
+ if (secondCallArguments.some(element => hasSideEffect(element, sourceCode))) {
90
88
  problem.suggest = [
91
89
  {
92
90
  messageId: SUGGESTION,
@@ -0,0 +1,233 @@
1
+ 'use strict';
2
+ const {isSemicolonToken} = require('eslint-utils');
3
+ const getDocumentationUrl = require('./utils/get-documentation-url');
4
+ const getClassHeadLocation = require('./utils/get-class-head-location');
5
+ const removeSpacesAfter = require('./utils/remove-spaces-after');
6
+ const assertToken = require('./utils/assert-token');
7
+
8
+ const MESSAGE_ID = 'no-static-only-class';
9
+ const messages = {
10
+ [MESSAGE_ID]: 'Use an object instead of a class with only static members.'
11
+ };
12
+
13
+ const selector = [
14
+ ':matches(ClassDeclaration, ClassExpression)',
15
+ ':not([superClass], [decorators.length>0])',
16
+ '[body.type="ClassBody"]',
17
+ '[body.body.length>0]'
18
+ ].join('');
19
+
20
+ const isEqualToken = ({type, value}) => type === 'Punctuator' && value === '=';
21
+ const isDeclarationOfExportDefaultDeclaration = node =>
22
+ node.type === 'ClassDeclaration' &&
23
+ node.parent.type === 'ExportDefaultDeclaration' &&
24
+ node.parent.declaration === node;
25
+
26
+ function isStaticMember(node) {
27
+ const {
28
+ type,
29
+ private: isPrivate,
30
+ static: isStatic,
31
+ declare: isDeclare,
32
+ readonly: isReadonly,
33
+ accessibility,
34
+ decorators,
35
+ key
36
+ } = node;
37
+
38
+ // Avoid matching unexpected node. For example: https://github.com/tc39/proposal-class-static-block
39
+ /* istanbul ignore next */
40
+ if (type !== 'ClassProperty' && type !== 'MethodDefinition') {
41
+ return false;
42
+ }
43
+
44
+ if (!isStatic || isPrivate) {
45
+ return false;
46
+ }
47
+
48
+ // TypeScript class
49
+ if (
50
+ isDeclare ||
51
+ isReadonly ||
52
+ typeof accessibility !== 'undefined' ||
53
+ (Array.isArray(decorators) && decorators.length > 0) ||
54
+ key.type === 'TSPrivateIdentifier'
55
+ ) {
56
+ return false;
57
+ }
58
+
59
+ return true;
60
+ }
61
+
62
+ function * switchClassMemberToObjectProperty(node, sourceCode, fixer) {
63
+ const {type} = node;
64
+
65
+ const staticToken = sourceCode.getFirstToken(node);
66
+ assertToken(staticToken, {
67
+ expected: [
68
+ {type: 'Keyword', value: 'static'},
69
+ // `babel-eslint` and `@babel/eslint-parser` use `{type: 'Identifier', value: 'static'}`
70
+ {type: 'Identifier', value: 'static'}
71
+ ],
72
+ ruleId: 'no-static-only-class'
73
+ });
74
+
75
+ yield fixer.remove(staticToken);
76
+ yield removeSpacesAfter(staticToken, sourceCode, fixer);
77
+
78
+ const maybeSemicolonToken = type === 'ClassProperty' ?
79
+ sourceCode.getLastToken(node) :
80
+ sourceCode.getTokenAfter(node);
81
+ const hasSemicolonToken = isSemicolonToken(maybeSemicolonToken);
82
+
83
+ if (type === 'ClassProperty') {
84
+ const {key, value} = node;
85
+
86
+ if (value) {
87
+ // Computed key may have `]` after `key`
88
+ const equalToken = sourceCode.getTokenAfter(key, isEqualToken);
89
+ yield fixer.replaceText(equalToken, ':');
90
+ } else if (hasSemicolonToken) {
91
+ yield fixer.insertTextBefore(maybeSemicolonToken, ': undefined');
92
+ } else {
93
+ yield fixer.insertTextAfter(node, ': undefined');
94
+ }
95
+ }
96
+
97
+ yield (
98
+ hasSemicolonToken ?
99
+ fixer.replaceText(maybeSemicolonToken, ',') :
100
+ fixer.insertTextAfter(node, ',')
101
+ );
102
+ }
103
+
104
+ function switchClassToObject(node, sourceCode) {
105
+ const {
106
+ type,
107
+ id,
108
+ body,
109
+ declare: isDeclare,
110
+ abstract: isAbstract,
111
+ implements: classImplements,
112
+ parent
113
+ } = node;
114
+
115
+ if (
116
+ isDeclare ||
117
+ isAbstract ||
118
+ (Array.isArray(classImplements) && classImplements.length > 0)
119
+ ) {
120
+ return;
121
+ }
122
+
123
+ if (type === 'ClassExpression' && id) {
124
+ return;
125
+ }
126
+
127
+ const isExportDefault = isDeclarationOfExportDefaultDeclaration(node);
128
+
129
+ if (isExportDefault && id) {
130
+ return;
131
+ }
132
+
133
+ for (const node of body.body) {
134
+ if (
135
+ node.type === 'ClassProperty' &&
136
+ (
137
+ node.typeAnnotation ||
138
+ // This is a stupid way to check if `value` of `ClassProperty` uses `this`
139
+ (node.value && sourceCode.getText(node).includes('this'))
140
+ )
141
+ ) {
142
+ return;
143
+ }
144
+ }
145
+
146
+ return function * (fixer) {
147
+ const classToken = sourceCode.getFirstToken(node);
148
+ /* istanbul ignore next */
149
+ assertToken(classToken, {
150
+ expected: {type: 'Keyword', value: 'class'},
151
+ ruleId: 'no-static-only-class'
152
+ });
153
+
154
+ if (isExportDefault || type === 'ClassExpression') {
155
+ /*
156
+ There are comments after return, and `{` is not on same line
157
+
158
+ ```js
159
+ function a() {
160
+ return class // comment
161
+ {
162
+ static a() {}
163
+ }
164
+ }
165
+ ```
166
+ */
167
+ if (
168
+ type === 'ClassExpression' &&
169
+ parent.type === 'ReturnStatement' &&
170
+ body.loc.start.line !== parent.loc.start.line &&
171
+ sourceCode.text.slice(classToken.range[1], body.range[0]).trim()
172
+ ) {
173
+ yield fixer.replaceText(classToken, '{');
174
+
175
+ const openingBraceToken = sourceCode.getFirstToken(body);
176
+ yield fixer.remove(openingBraceToken);
177
+ } else {
178
+ yield fixer.replaceText(classToken, '');
179
+
180
+ /*
181
+ Avoid breaking case like
182
+
183
+ ```js
184
+ return class
185
+ {};
186
+ ```
187
+ */
188
+ yield removeSpacesAfter(classToken, sourceCode, fixer);
189
+ }
190
+
191
+ // There should not be ASI problem
192
+ } else {
193
+ yield fixer.replaceText(classToken, 'const');
194
+ yield fixer.insertTextBefore(body, '= ');
195
+ yield fixer.insertTextAfter(body, ';');
196
+ }
197
+
198
+ for (const node of body.body) {
199
+ yield * switchClassMemberToObjectProperty(node, sourceCode, fixer);
200
+ }
201
+ };
202
+ }
203
+
204
+ function create(context) {
205
+ const sourceCode = context.getSourceCode();
206
+
207
+ return {
208
+ [selector](node) {
209
+ if (node.body.body.some(node => !isStaticMember(node))) {
210
+ return;
211
+ }
212
+
213
+ context.report({
214
+ node,
215
+ loc: getClassHeadLocation(node, sourceCode),
216
+ messageId: MESSAGE_ID,
217
+ fix: switchClassToObject(node, sourceCode)
218
+ });
219
+ }
220
+ };
221
+ }
222
+
223
+ module.exports = {
224
+ create,
225
+ meta: {
226
+ type: 'suggestion',
227
+ docs: {
228
+ url: getDocumentationUrl(__filename)
229
+ },
230
+ fixable: 'code',
231
+ messages
232
+ }
233
+ };