diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md new file mode 100644 index 0000000000..0e65dd26fc --- /dev/null +++ b/docs/rules/prefer-class-fields.md @@ -0,0 +1,73 @@ +# Prefer class field declarations over `this` assignments in constructors + +💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config). + +🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). + + + + +Enforces declaring property defaults with class fields instead of setting them inside the constructor. + +> To avoid leaving empty constructors after autofixing, use the [`no-useless-constructor` rule](https://eslint.org/docs/latest/rules/no-useless-constructor). + +## Examples + +```js +// ❌ +class Foo { + constructor() { + this.foo = 'foo'; + } +} + +// ✅ +class Foo { + foo = 'foo'; +} +``` + +```js +// ❌ +class MyError extends Error { + constructor(message: string) { + super(message); + this.name = 'MyError'; + } +} + +// ✅ +class MyError extends Error { + name = 'MyError' +} +``` + +```js +// ❌ +class Foo { + foo = 'foo'; + constructor() { + this.foo = 'bar'; + } +} + +// ✅ +class Foo { + foo = 'bar'; +} +``` + +```js +// ❌ +class Foo { + #foo = 'foo'; + constructor() { + this.#foo = 'bar'; + } +} + +// ✅ +class Foo { + #foo = 'bar'; +} +``` diff --git a/readme.md b/readme.md index 7b9dd9c850..6f6107c26a 100644 --- a/readme.md +++ b/readme.md @@ -132,6 +132,7 @@ export default [ | [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast,findIndex,findLastIndex}(…)`. | ✅ | 🔧 | 💡 | | [prefer-at](docs/rules/prefer-at.md) | Prefer `.at()` method for index access and `String#charAt()`. | ✅ | 🔧 | 💡 | | [prefer-blob-reading-methods](docs/rules/prefer-blob-reading-methods.md) | Prefer `Blob#arrayBuffer()` over `FileReader#readAsArrayBuffer(…)` and `Blob#text()` over `FileReader#readAsText(…)`. | ✅ | | | +| [prefer-class-fields](docs/rules/prefer-class-fields.md) | Prefer class field declarations over `this` assignments in constructors. | ✅ | 🔧 | 💡 | | [prefer-code-point](docs/rules/prefer-code-point.md) | Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)`. | ✅ | | 💡 | | [prefer-date-now](docs/rules/prefer-date-now.md) | Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch. | ✅ | 🔧 | | | [prefer-default-parameters](docs/rules/prefer-default-parameters.md) | Prefer default parameters over reassignment. | ✅ | | 💡 | diff --git a/rules/index.js b/rules/index.js index 272c04c9f1..347c945a40 100644 --- a/rules/index.js +++ b/rules/index.js @@ -76,6 +76,7 @@ export {default as 'prefer-array-index-of'} from './prefer-array-index-of.js'; export {default as 'prefer-array-some'} from './prefer-array-some.js'; export {default as 'prefer-at'} from './prefer-at.js'; export {default as 'prefer-blob-reading-methods'} from './prefer-blob-reading-methods.js'; +export {default as 'prefer-class-fields'} from './prefer-class-fields.js'; export {default as 'prefer-code-point'} from './prefer-code-point.js'; export {default as 'prefer-date-now'} from './prefer-date-now.js'; export {default as 'prefer-default-parameters'} from './prefer-default-parameters.js'; diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js new file mode 100644 index 0000000000..e180480c3d --- /dev/null +++ b/rules/prefer-class-fields.js @@ -0,0 +1,147 @@ +import getIndentString from './utils/get-indent-string.js'; + +const MESSAGE_ID_ERROR = 'prefer-class-fields/error'; +const MESSAGE_ID_SUGGESTION = 'prefer-class-fields/suggestion'; +const messages = { + [MESSAGE_ID_ERROR]: + 'Prefer class field declaration over `this` assignment in constructor for static values.', + [MESSAGE_ID_SUGGESTION]: + 'Encountered same-named class field declaration and `this` assignment in constructor. Replace the class field declaration with the value from `this` assignment.', +}; + +/** +@param {import('eslint').Rule.Node} node +@param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode +@param {import('eslint').Rule.RuleFixer} fixer +*/ +const removeFieldAssignment = (node, sourceCode, fixer) => { + const {line} = sourceCode.getLoc(node).start; + const nodeText = sourceCode.getText(node); + const lineText = sourceCode.lines[line - 1]; + const isOnlyNodeOnLine = lineText.trim() === nodeText; + + return isOnlyNodeOnLine + ? fixer.removeRange([ + sourceCode.getIndexFromLoc({line, column: 0}), + sourceCode.getIndexFromLoc({line: line + 1, column: 0}), + ]) + : fixer.remove(node); +}; + +/** +@type {import('eslint').Rule.RuleModule['create']} +*/ +const create = context => { + const {sourceCode} = context; + + return { + ClassBody(classBody) { + const constructor = classBody.body.find(node => + node.kind === 'constructor' + && !node.computed + && !node.static + && node.type === 'MethodDefinition' + && node.value.type === 'FunctionExpression', + ); + + if (!constructor) { + return; + } + + const node = constructor.value.body.body.find(node => node.type !== 'EmptyStatement'); + + if (!( + node?.type === 'ExpressionStatement' + && node.expression.type === 'AssignmentExpression' + && node.expression.operator === '=' + && node.expression.left.type === 'MemberExpression' + && node.expression.left.object.type === 'ThisExpression' + && !node.expression.left.computed + && ['Identifier', 'PrivateIdentifier'].includes(node.expression.left.property.type) + && node.expression.right.type === 'Literal' + )) { + return; + } + + const propertyName = node.expression.left.property.name; + const propertyValue = node.expression.right.raw; + const propertyType = node.expression.left.property.type; + const existingProperty = classBody.body.find(node => + node.type === 'PropertyDefinition' + && !node.computed + && !node.static + && node.key.type === propertyType + && node.key.name === propertyName, + ); + + const problem = { + node, + messageId: MESSAGE_ID_ERROR, + }; + + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ + function * fix(fixer) { + yield removeFieldAssignment(node, sourceCode, fixer); + + if (existingProperty) { + yield existingProperty.value + ? fixer.replaceText(existingProperty.value, propertyValue) + : fixer.insertTextAfter(existingProperty.key, ` = ${propertyValue}`); + return; + } + + const closingBrace = sourceCode.getLastToken(classBody); + const indent = getIndentString(constructor, sourceCode); + + let text = `${indent}${propertyName} = ${propertyValue};\n`; + + const characterBefore = sourceCode.getText()[sourceCode.getRange(closingBrace)[0] - 1]; + if (characterBefore !== '\n') { + text = `\n${text}`; + } + + const lastProperty = classBody.body.at(-1); + if ( + lastProperty.type === 'PropertyDefinition' + && sourceCode.getLastToken(lastProperty).value !== ';' + ) { + text = `;${text}`; + } + + yield fixer.insertTextBefore(closingBrace, text); + } + + if (existingProperty?.value) { + problem.suggest = [ + { + messageId: MESSAGE_ID_SUGGESTION, + fix, + }, + ]; + return problem; + } + + problem.fix = fix; + return problem; + }, + }; +}; + +/** @type {import('eslint').Rule.RuleModule} */ +const config = { + create, + meta: { + type: 'suggestion', + docs: { + description: 'Prefer class field declarations over `this` assignments in constructors.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + messages, + }, +}; + +export default config; diff --git a/rules/utils/rule.js b/rules/utils/rule.js index 9584ad7886..c726c03cd0 100644 --- a/rules/utils/rule.js +++ b/rules/utils/rule.js @@ -3,10 +3,7 @@ import getDocumentationUrl from './get-documentation-url.js'; const isIterable = object => typeof object?.[Symbol.iterator] === 'function'; class FixAbortError extends Error { - constructor() { - super(); - this.name = 'FixAbortError'; - } + name = 'FixAbortError'; } const fixOptions = { abort() { diff --git a/test/package.js b/test/package.js index f05f9bf6ba..f555d81483 100644 --- a/test/package.js +++ b/test/package.js @@ -31,6 +31,7 @@ const RULES_WITHOUT_EXAMPLES_SECTION = new Set([ 'prefer-modern-math-apis', 'prefer-math-min-max', 'consistent-existence-index-check', + 'prefer-class-fields', 'prefer-global-this', 'no-instanceof-builtins', 'no-named-default', diff --git a/test/prefer-class-fields.js b/test/prefer-class-fields.js new file mode 100644 index 0000000000..91c7238d8e --- /dev/null +++ b/test/prefer-class-fields.js @@ -0,0 +1,199 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.js'; + +const {test} = getTester(import.meta); + +const MESSAGE_ID = 'prefer-class-fields/error'; + +test.snapshot({ + valid: [ + 'class Foo {bar = 1}', + 'class Foo {static bar = 1}', + 'class Foo {#bar = 1}', + 'class Foo {static #bar = 1}', + // Not `=` assign + 'class Foo {constructor() {this.bar += 1}}', + // Computed + 'class Foo {constructor() {this[bar] = 1}}', + // Not `this` + 'class Foo {constructor() {notThis.bar = 1}}', + // Not `Literal` + 'class Foo {constructor() {notThis.bar = 1 + 2}}', + outdent` + class Foo { + constructor() { + if (something) { return; } + this.bar = 1; + } + } + `, + ], + invalid: [ + outdent` + class Foo { + constructor() { + this.bar = 1; + } + } + `, + outdent` + class Foo { + constructor() { + ; + this.bar = 1; + } + } + `, + outdent` + class Foo { + constructor() { + this.bar = 1; + this.baz = 2; + } + } + `, + outdent` + class Foo { + constructor() { + this.bar = 1; + this.bar = 2; + } + } + `, + outdent` + class Foo { + bar; + constructor() { + this.bar = 1; + } + } + `, + outdent` + class Foo { + #bar; + constructor() { + this.#bar = 1; + } + } + `, + outdent` + class Foo { + bar = 0; + constructor() { + this.bar = 1; + } + } + `, + outdent` + class Foo { + #bar = 0; + constructor() { + this.#bar = 1; + } + } + `, + outdent` + class Foo { + [bar]; + constructor() { + this.bar = 1; + } + } + `, + outdent` + class Foo { + [bar] = 0; + constructor() { + this.bar = 1; + } + } + `, + outdent` + class Foo { + static bar; + constructor() { + this.bar = 1; + } + } + `, + outdent` + class Foo { + static bar = 0; + constructor() { + this.bar = 1; + } + } + `, + outdent` + class Foo { + static [bar]; + constructor() { + this.bar = 1; + } + } + `, + outdent` + class Foo { + static [bar] = 1; + constructor() { + this.bar = 1; + } + } + `, + outdent` + class Foo { + constructor() { + this.bar = 1; + }} + `, + outdent` + class Foo { + constructor() { + this.bar = 1; + } + static} + `, + outdent` + class Foo { + constructor() { + this.bar = 1; + } + static// comment; + } + `, + ], +}); + +test.typescript({ + valid: [ + outdent` + class Foo { + foo: string = 'foo'; + } + `, + outdent` + declare class Foo { + constructor(foo?: string); + } + `, + ], + invalid: [ + { + code: outdent` + class MyError extends Error { + constructor(message: string) { + this.name = "MyError"; + } + } + `, + errors: [{messageId: MESSAGE_ID}], + output: outdent` + class MyError extends Error { + constructor(message: string) { + } + name = "MyError"; + } + `, + }, + ], +}); diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md new file mode 100644 index 0000000000..f5197c6671 --- /dev/null +++ b/test/snapshots/prefer-class-fields.js.md @@ -0,0 +1,599 @@ +# Snapshot report for `test/prefer-class-fields.js` + +The actual snapshot is saved in `prefer-class-fields.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): class Foo { constructor() { this.bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this.bar = 1;␊ + 4 | }␊ + 5 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | }␊ + 4 | bar = 1;␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + > 3 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 4 | }␊ + 5 | }␊ + ` + +## invalid(2): class Foo { constructor() { ; this.bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | ;␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | ;␊ + 4 | }␊ + 5 | bar = 1;␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | ;␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(3): class Foo { constructor() { this.bar = 1; this.baz = 2; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this.bar = 1;␊ + 4 | this.baz = 2;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | }␊ + 4 | bar = 1;␊ + 5 | baz = 2;␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + > 3 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 4 | this.baz = 2;␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(4): class Foo { constructor() { this.bar = 1; this.bar = 2; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this.bar = 1;␊ + 4 | this.bar = 2;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this.bar = 2;␊ + 4 | }␊ + 5 | bar = 1;␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + > 3 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 4 | this.bar = 2;␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(5): class Foo { bar; constructor() { this.bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | bar;␊ + 3 | constructor() {␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | bar = 1;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | bar;␊ + 3 | constructor() {␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(6): class Foo { #bar; constructor() { this.#bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | #bar;␊ + 3 | constructor() {␊ + 4 | this.#bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | #bar = 1;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | #bar;␊ + 3 | constructor() {␊ + > 4 | this.#bar = 1;␊ + | ^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(7): class Foo { bar = 0; constructor() { this.bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | bar = 0;␊ + 3 | constructor() {␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | bar = 0;␊ + 3 | constructor() {␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Encountered same-named class field declaration and \`this\` assignment in constructor. Replace the class field declaration with the value from \`this\` assignment.␊ + 1 | class Foo {␊ + 2 | bar = 1;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | }␊ + ` + +## invalid(8): class Foo { #bar = 0; constructor() { this.#bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | #bar = 0;␊ + 3 | constructor() {␊ + 4 | this.#bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | #bar = 0;␊ + 3 | constructor() {␊ + > 4 | this.#bar = 1;␊ + | ^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Encountered same-named class field declaration and \`this\` assignment in constructor. Replace the class field declaration with the value from \`this\` assignment.␊ + 1 | class Foo {␊ + 2 | #bar = 1;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | }␊ + ` + +## invalid(9): class Foo { [bar]; constructor() { this.bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | [bar];␊ + 3 | constructor() {␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | [bar];␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | [bar];␊ + 3 | constructor() {␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(10): class Foo { [bar] = 0; constructor() { this.bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | [bar] = 0;␊ + 3 | constructor() {␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | [bar] = 0;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | [bar] = 0;␊ + 3 | constructor() {␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(11): class Foo { static bar; constructor() { this.bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | static bar;␊ + 3 | constructor() {␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | static bar;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | static bar;␊ + 3 | constructor() {␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(12): class Foo { static bar = 0; constructor() { this.bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | static bar = 0;␊ + 3 | constructor() {␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | static bar = 0;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | static bar = 0;␊ + 3 | constructor() {␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(13): class Foo { static [bar]; constructor() { this.bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | static [bar];␊ + 3 | constructor() {␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | static [bar];␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | static [bar];␊ + 3 | constructor() {␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(14): class Foo { static [bar] = 1; constructor() { this.bar = 1; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | static [bar] = 1;␊ + 3 | constructor() {␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | static [bar] = 1;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | static [bar] = 1;␊ + 3 | constructor() {␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(15): class Foo { constructor() { this.bar = 1; }} + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this.bar = 1;␊ + 4 | }}␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | }␊ + 4 | bar = 1;␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + > 3 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 4 | }}␊ + ` + +## invalid(16): class Foo { constructor() { this.bar = 1; } static} + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this.bar = 1;␊ + 4 | }␊ + 5 | static}␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | }␊ + 4 | static;␊ + 5 | bar = 1;␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + > 3 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 4 | }␊ + 5 | static}␊ + ` + +## invalid(17): class Foo { constructor() { this.bar = 1; } static// comment; } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this.bar = 1;␊ + 4 | }␊ + 5 | static// comment;␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | }␊ + 4 | static// comment;␊ + 5 | ;bar = 1;␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + > 3 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 4 | }␊ + 5 | static// comment;␊ + 6 | }␊ + ` diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap new file mode 100644 index 0000000000..b5dca6df59 Binary files /dev/null and b/test/snapshots/prefer-class-fields.js.snap differ