From 76519efb941b68390e4d262a8588e4851e394406 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Mon, 16 Dec 2024 03:35:52 +0100 Subject: [PATCH 01/54] feat: add `prefer-class-fields` rule --- docs/rules/prefer-class-fields.md | 41 ++++++ readme.md | 1 + rules/prefer-class-fields.js | 111 +++++++++++++++ test/prefer-class-fields.mjs | 86 +++++++++++ test/snapshots/prefer-class-fields.mjs.md | 149 ++++++++++++++++++++ test/snapshots/prefer-class-fields.mjs.snap | Bin 0 -> 642 bytes 6 files changed, 388 insertions(+) create mode 100644 docs/rules/prefer-class-fields.md create mode 100644 rules/prefer-class-fields.js create mode 100644 test/prefer-class-fields.mjs create mode 100644 test/snapshots/prefer-class-fields.mjs.md create mode 100644 test/snapshots/prefer-class-fields.mjs.snap diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md new file mode 100644 index 0000000000..e14478eddd --- /dev/null +++ b/docs/rules/prefer-class-fields.md @@ -0,0 +1,41 @@ +# Prefer class field declarations over assigning static values in constructor using `this` + +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + + + +This rule will enforce the use of field declarations over `this` assignments in contructors for static values. + +> To avoid leaving empty constructors after autofix use [`no-useless-contructor` rule](https://eslint.org/docs/latest/rules/no-useless-constructor). + +## Fail + +```js +class Foo { + constructor() { + this.foo = 'foo'; + } +} + +class MyError extends Error { + constructor(message: string) { + super(message); + this.name = "MyError"; + } +} +``` + +## Pass + +```js +class Foo { + foo = 'foo'; +} + +class MyError extends Error { + name = "MyError" +} +``` diff --git a/readme.md b/readme.md index 7b9dd9c850..4cb21c5269 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 assigning static values in constructor using `this`. | βœ… | πŸ”§ | | | [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/prefer-class-fields.js b/rules/prefer-class-fields.js new file mode 100644 index 0000000000..bcaae2f9b9 --- /dev/null +++ b/rules/prefer-class-fields.js @@ -0,0 +1,111 @@ +'use strict'; +const getIndentString = require('./utils/get-indent-string.js'); + +const MESSAGE_ID = 'prefer-class-fields/error'; +const messages = { + [MESSAGE_ID]: + 'Prefer class field declaration over `this` assignment in constructor for static values.', +}; + +/** + * @param {import('eslint').Rule.Node} node + * @returns {node is import('estree').ExpressionStatement & {expression: import('estree').AssignmentExpression & {left: import('estree').MemberExpression & {object: import('estree').ThisExpression}}}} + */ +const isThisAssignmentExpression = node => { + if ( + node.type !== 'ExpressionStatement' + || node.expression.type !== 'AssignmentExpression' + ) { + return false; + } + + const lhs = node.expression.left; + + if (!lhs.object || lhs.object.type !== 'ThisExpression') { + return false; + } + + return true; +}; + +/** + * @template Array + * @param {Array} array + * @returns {Array} + */ +const reverseArray = array => [...array].reverse(); + +/** + * @param {import('eslint').Rule.Node} node + * @param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode + * @param {import('eslint').Rule.RuleFixer} fixer + */ +const removeThisFieldAssignment = (node, sourceCode, fixer) => { + const {line} = node.loc.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(node) { + const constructor = node.body.find(x => x.kind === 'constructor'); + + if (!constructor || constructor.type !== 'MethodDefinition') { + return; + } + + const constructorBody = constructor.value.body.body; + const classBodyStartRange = [node.range[0], node.range[0] + 1]; + const indent = getIndentString(constructor, sourceCode); + + for (const node of reverseArray(constructorBody)) { + if ( + isThisAssignmentExpression(node) + && node.expression.right?.type === 'Literal' + ) { + return { + node, + messageId: MESSAGE_ID, + + /** @param {import('eslint').Rule.RuleFixer} fixer */ + * fix(fixer) { + yield removeThisFieldAssignment(node, sourceCode, fixer); + yield fixer.insertTextAfterRange( + classBodyStartRange, + `\n${indent}${node.expression.left.property.name} = ${node.expression.right.raw};`, + ); + }, + }; + } + } + }, + }; +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + description: + 'Prefer class field declarations over assigning static values in constructor using `this`.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: false, + messages, + }, +}; diff --git a/test/prefer-class-fields.mjs b/test/prefer-class-fields.mjs new file mode 100644 index 0000000000..ef827cd1ff --- /dev/null +++ b/test/prefer-class-fields.mjs @@ -0,0 +1,86 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +const MESSAGE_ID = 'prefer-class-fields/error'; + +test.snapshot({ + valid: [ + outdent` + class Foo { + foo = 'foo'; + } + `, + outdent` + class MyError extends Error { + name = "MyError"; + } + `, + ], + invalid: [ + outdent` + class Foo { + constructor() { + this.foo = 'foo'; + } + } + `, + outdent` + class Foo { + constructor() { + this.foo = 'foo'; + this.foo2 = 'foo2'; + } + } + `, + outdent` + class Foo { + constructor(argument) { + this.foo = 'foo'; + this.foo2 = argument + 'test'; + this.foo3 = 'foo3'; + } + } + `, + outdent` + class MyError extends Error { + constructor(message) { + super(message); + this.name = "MyError"; + } + } + `, + ], +}); + +test.typescript({ + valid: [ + outdent` + class Foo { + foo: string = 'foo'; + } + `, + ], + invalid: [ + { + code: outdent` + class MyError extends Error { + constructor(message: string) { + super(message); + this.name = "MyError"; + } + } + `, + errors: [{messageId: MESSAGE_ID}], + output: outdent` + class MyError extends Error { + name = "MyError"; + constructor(message: string) { + super(message); + } + } + `, + }, + ], +}); diff --git a/test/snapshots/prefer-class-fields.mjs.md b/test/snapshots/prefer-class-fields.mjs.md new file mode 100644 index 0000000000..39fde7e9e7 --- /dev/null +++ b/test/snapshots/prefer-class-fields.mjs.md @@ -0,0 +1,149 @@ +# Snapshot report for `test/prefer-class-fields.mjs` + +The actual snapshot is saved in `prefer-class-fields.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): class Foo { constructor() { this.foo = 'foo'; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this.foo = 'foo';␊ + 4 | }␊ + 5 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | foo = 'foo';␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + > 3 | this.foo = 'foo';␊ + | ^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 4 | }␊ + 5 | }␊ + ` + +## invalid(2): class Foo { constructor() { this.foo = 'foo'; this.foo2 = 'foo2'; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this.foo = 'foo';␊ + 4 | this.foo2 = 'foo2';␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | foo = 'foo';␊ + 3 | foo2 = 'foo2';␊ + 4 | constructor() {␊ + 5 | }␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this.foo = 'foo';␊ + > 4 | this.foo2 = 'foo2';␊ + | ^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ` + +## invalid(3): class Foo { constructor(argument) { this.foo = 'foo'; this.foo2 = argument + 'test'; this.foo3 = 'foo3'; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor(argument) {␊ + 3 | this.foo = 'foo';␊ + 4 | this.foo2 = argument + 'test';␊ + 5 | this.foo3 = 'foo3';␊ + 6 | }␊ + 7 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | foo = 'foo';␊ + 3 | foo3 = 'foo3';␊ + 4 | constructor(argument) {␊ + 5 | this.foo2 = argument + 'test';␊ + 6 | }␊ + 7 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor(argument) {␊ + 3 | this.foo = 'foo';␊ + 4 | this.foo2 = argument + 'test';␊ + > 5 | this.foo3 = 'foo3';␊ + | ^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 6 | }␊ + 7 | }␊ + ` + +## invalid(4): class MyError extends Error { constructor(message) { super(message); this.name = "MyError"; } } + +> Input + + `␊ + 1 | class MyError extends Error {␊ + 2 | constructor(message) {␊ + 3 | super(message);␊ + 4 | this.name = "MyError";␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class MyError extends Error {␊ + 2 | name = "MyError";␊ + 3 | constructor(message) {␊ + 4 | super(message);␊ + 5 | }␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class MyError extends Error {␊ + 2 | constructor(message) {␊ + 3 | super(message);␊ + > 4 | this.name = "MyError";␊ + | ^^^^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ + ` diff --git a/test/snapshots/prefer-class-fields.mjs.snap b/test/snapshots/prefer-class-fields.mjs.snap new file mode 100644 index 0000000000000000000000000000000000000000..708c7022c00767eb63068ae80f2e7d9b47cfa28e GIT binary patch literal 642 zcmV-|0)72KRzVGR52CNQHRAt60u;rx$^{mM5M$4=QxMYW2r>qD=xNoa1M3K|>0HOCxeZBl-z%vOwo;bEd?ioc7B8^3DHSK6(p6w{q|^ABDM z8d8y+9x@sQ2&f+sK@{UL_>c-5rw1N`NXdF}pT-I-F18UN7m$i5=3_{rgvvczsqC2$ zT%h@|nRD_g=cKhVC;3V%TWZp5-g1Cf+fcFV)vIT$!J>}i6iHMX8gXh zhU~kQ*{@@SjKP&h=qw+h_RR>r_;CFe9Vr?I64$@!^H?m}JsXO3ObNhKEw Date: Mon, 16 Dec 2024 03:52:15 +0100 Subject: [PATCH 02/54] fix: handle edge case of constructor without body fixup "contructor" typos --- docs/rules/prefer-class-fields.md | 4 ++-- rules/prefer-class-fields.js | 7 ++++++- test/prefer-class-fields.mjs | 5 +++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index e14478eddd..2149b70616 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -7,9 +7,9 @@ -This rule will enforce the use of field declarations over `this` assignments in contructors for static values. +This rule will enforce the use of field declarations over `this` assignments in constructors for static values. -> To avoid leaving empty constructors after autofix use [`no-useless-contructor` rule](https://eslint.org/docs/latest/rules/no-useless-constructor). +> To avoid leaving empty constructors after autofix use [`no-useless-constructor` rule](https://eslint.org/docs/latest/rules/no-useless-constructor). ## Fail diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index bcaae2f9b9..fd4edefabe 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -66,7 +66,12 @@ const create = context => { return; } - const constructorBody = constructor.value.body.body; + const constructorBody = constructor.value.body?.body; + + if (!constructorBody) { + return; + } + const classBodyStartRange = [node.range[0], node.range[0] + 1]; const indent = getIndentString(constructor, sourceCode); diff --git a/test/prefer-class-fields.mjs b/test/prefer-class-fields.mjs index ef827cd1ff..5600a8b0b1 100644 --- a/test/prefer-class-fields.mjs +++ b/test/prefer-class-fields.mjs @@ -61,6 +61,11 @@ test.typescript({ foo: string = 'foo'; } `, + outdent` + declare class Foo { + constructor(foo?: string); + } + `, ], invalid: [ { From cd3149a51de1bd55c2522821e4a4c39caeff6d75 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 18 Dec 2024 14:42:33 +0100 Subject: [PATCH 03/54] Update prefer-class-fields.md --- docs/rules/prefer-class-fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index 2149b70616..f8afafb525 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -7,9 +7,9 @@ -This rule will enforce the use of field declarations over `this` assignments in constructors for static values. +This rule enforces the use of class field declarations for static values, instead of assigning them in constructors using `this`. -> To avoid leaving empty constructors after autofix use [`no-useless-constructor` rule](https://eslint.org/docs/latest/rules/no-useless-constructor). +> To avoid leaving empty constructors after autofixing, use the [`no-useless-constructor` rule](https://eslint.org/docs/latest/rules/no-useless-constructor). ## Fail From 04ce3661639e0c5a45c3b9530f8732e50b6d6bbb Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 18 Dec 2024 14:44:46 +0100 Subject: [PATCH 04/54] Update prefer-class-fields.js --- rules/prefer-class-fields.js | 40 ++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index fd4edefabe..a9c886a863 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -3,14 +3,13 @@ const getIndentString = require('./utils/get-indent-string.js'); const MESSAGE_ID = 'prefer-class-fields/error'; const messages = { - [MESSAGE_ID]: - 'Prefer class field declaration over `this` assignment in constructor for static values.', + [MESSAGE_ID]: 'Prefer class field declaration over `this` assignment in constructor for static values.', }; /** - * @param {import('eslint').Rule.Node} node - * @returns {node is import('estree').ExpressionStatement & {expression: import('estree').AssignmentExpression & {left: import('estree').MemberExpression & {object: import('estree').ThisExpression}}}} - */ +@param {import('eslint').Rule.Node} node +@returns {node is import('estree').ExpressionStatement & {expression: import('estree').AssignmentExpression & {left: import('estree').MemberExpression & {object: import('estree').ThisExpression}}}} +*/ const isThisAssignmentExpression = node => { if ( node.type !== 'ExpressionStatement' @@ -29,17 +28,17 @@ const isThisAssignmentExpression = node => { }; /** - * @template Array - * @param {Array} array - * @returns {Array} - */ +@template Array +@param {Array} array +@returns {Array} +*/ const reverseArray = array => [...array].reverse(); /** - * @param {import('eslint').Rule.Node} node - * @param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode - * @param {import('eslint').Rule.RuleFixer} fixer - */ +@param {import('eslint').Rule.Node} node +@param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode +@param {import('eslint').Rule.RuleFixer} fixer +*/ const removeThisFieldAssignment = (node, sourceCode, fixer) => { const {line} = node.loc.start; const nodeText = sourceCode.getText(node); @@ -54,7 +53,9 @@ const removeThisFieldAssignment = (node, sourceCode, fixer) => { : fixer.remove(node); }; -/** @type {import('eslint').Rule.RuleModule['create']} */ +/** +@type {import('eslint').Rule.RuleModule['create']} +*/ const create = context => { const {sourceCode} = context; @@ -84,7 +85,9 @@ const create = context => { node, messageId: MESSAGE_ID, - /** @param {import('eslint').Rule.RuleFixer} fixer */ + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ * fix(fixer) { yield removeThisFieldAssignment(node, sourceCode, fixer); yield fixer.insertTextAfterRange( @@ -99,14 +102,15 @@ const create = context => { }; }; -/** @type {import('eslint').Rule.RuleModule} */ +/** +@type {import('eslint').Rule.RuleModule} +*/ module.exports = { create, meta: { type: 'suggestion', docs: { - description: - 'Prefer class field declarations over assigning static values in constructor using `this`.', + description: 'Prefer class field declarations over assigning static values in constructor using `this`.', recommended: true, }, fixable: 'code', From 2dba3a38c0850032f1fb0c78c51240a6b27a6393 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Fri, 20 Dec 2024 01:36:04 +0100 Subject: [PATCH 05/54] perf: iterate over original array instead of copying it over and reversing --- rules/prefer-class-fields.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index a9c886a863..a66cd2f176 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -27,12 +27,6 @@ const isThisAssignmentExpression = node => { return true; }; -/** -@template Array -@param {Array} array -@returns {Array} -*/ -const reverseArray = array => [...array].reverse(); /** @param {import('eslint').Rule.Node} node @@ -60,8 +54,8 @@ const create = context => { const {sourceCode} = context; return { - ClassBody(node) { - const constructor = node.body.find(x => x.kind === 'constructor'); + ClassBody(classBody) { + const constructor = classBody.body.find(x => x.kind === 'constructor'); if (!constructor || constructor.type !== 'MethodDefinition') { return; @@ -73,10 +67,11 @@ const create = context => { return; } - const classBodyStartRange = [node.range[0], node.range[0] + 1]; + const classBodyStartRange = [classBody.range[0], classBody.range[0] + 1]; const indent = getIndentString(constructor, sourceCode); - for (const node of reverseArray(constructorBody)) { + for (let i = constructorBody.length - 1; i >= 0; i--) { + const node = constructorBody[i]; if ( isThisAssignmentExpression(node) && node.expression.right?.type === 'Literal' From 78ca6ff72b78b6bdb2f25764c7c14aaec18e1b53 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Fri, 20 Dec 2024 01:40:31 +0100 Subject: [PATCH 06/54] chore: handle only simple assignments --- rules/prefer-class-fields.js | 1 + test/prefer-class-fields.mjs | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index a66cd2f176..6e317699e5 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -75,6 +75,7 @@ const create = context => { if ( isThisAssignmentExpression(node) && node.expression.right?.type === 'Literal' + && node.expression.operator === '=' ) { return { node, diff --git a/test/prefer-class-fields.mjs b/test/prefer-class-fields.mjs index 5600a8b0b1..8dfcb4dc01 100644 --- a/test/prefer-class-fields.mjs +++ b/test/prefer-class-fields.mjs @@ -17,6 +17,20 @@ test.snapshot({ name = "MyError"; } `, + outdent` + class Foo { + constructor() { + this.foo += 'foo'; + } + } + `, + outdent` + class Foo { + constructor() { + this.foo ??= 'foo'; + } + } + `, ], invalid: [ outdent` From 4136f2f1a4562283ab1bb70b779a3aaa67d218b2 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Fri, 20 Dec 2024 01:52:36 +0100 Subject: [PATCH 07/54] lint: fix --- rules/prefer-class-fields.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 6e317699e5..16fdeda9e2 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -27,7 +27,6 @@ const isThisAssignmentExpression = node => { return true; }; - /** @param {import('eslint').Rule.Node} node @param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode @@ -98,9 +97,7 @@ const create = context => { }; }; -/** -@type {import('eslint').Rule.RuleModule} -*/ +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { create, meta: { From 3e632b820190bf56a6a408dfebc9059dfb722ca8 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Fri, 20 Dec 2024 01:54:00 +0100 Subject: [PATCH 08/54] chore: spaces to tabs --- docs/rules/prefer-class-fields.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index f8afafb525..660898df43 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -15,16 +15,16 @@ This rule enforces the use of class field declarations for static values, instea ```js class Foo { - constructor() { - this.foo = 'foo'; - } + constructor() { + this.foo = 'foo'; + } } class MyError extends Error { - constructor(message: string) { - super(message); - this.name = "MyError"; - } + constructor(message: string) { + super(message); + this.name = "MyError"; + } } ``` @@ -32,10 +32,10 @@ class MyError extends Error { ```js class Foo { - foo = 'foo'; + foo = 'foo'; } class MyError extends Error { - name = "MyError" + name = "MyError" } ``` From 9ae80ff3cb54e9f366ebd0dc0600bf20e0c9785a Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Fri, 20 Dec 2024 01:56:26 +0100 Subject: [PATCH 09/54] chore: add dynamic field names to valid test cases --- test/prefer-class-fields.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/prefer-class-fields.mjs b/test/prefer-class-fields.mjs index 8dfcb4dc01..61c5996e45 100644 --- a/test/prefer-class-fields.mjs +++ b/test/prefer-class-fields.mjs @@ -31,6 +31,14 @@ test.snapshot({ } } `, + outdent` + class Foo { + something = 'a'; + constructor() { + this[this.something] = 'foo'; + } + } + `, ], invalid: [ outdent` From 621041e37aa4b5853688323ee86e0548b9a7a2af Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Fri, 20 Dec 2024 02:05:18 +0100 Subject: [PATCH 10/54] feat: support strings and template strings --- rules/prefer-class-fields.js | 66 ++++++++++++++------ test/prefer-class-fields.mjs | 14 +++++ test/snapshots/prefer-class-fields.mjs.md | 66 ++++++++++++++++++++ test/snapshots/prefer-class-fields.mjs.snap | Bin 642 -> 726 bytes 4 files changed, 127 insertions(+), 19 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 16fdeda9e2..f93dcad002 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -27,12 +27,33 @@ const isThisAssignmentExpression = node => { return true; }; +/** +@param {import('estree').Expression | import('estree').PrivateIdentifier} node +*/ +const getPropertyName = node => { + if (node.type === 'Identifier') { + return node.name; + } + + if (node.type === 'Literal') { + return `[${node.raw}]`; + } + + if ( + node.type === 'TemplateLiteral' + && node.expressions.length === 0 + && node.quasis.length === 1 + ) { + return `[\`${node.quasis[0].value.raw}\`]`; + } +}; + /** @param {import('eslint').Rule.Node} node @param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode @param {import('eslint').Rule.RuleFixer} fixer */ -const removeThisFieldAssignment = (node, sourceCode, fixer) => { +const removeFieldAssignment = (node, sourceCode, fixer) => { const {line} = node.loc.start; const nodeText = sourceCode.getText(node); const lineText = sourceCode.lines[line - 1]; @@ -72,26 +93,33 @@ const create = context => { for (let i = constructorBody.length - 1; i >= 0; i--) { const node = constructorBody[i]; if ( - isThisAssignmentExpression(node) - && node.expression.right?.type === 'Literal' - && node.expression.operator === '=' + !isThisAssignmentExpression(node) + || node.expression.right?.type !== 'Literal' + || node.expression.operator !== '=' ) { - return { - node, - messageId: MESSAGE_ID, - - /** - @param {import('eslint').Rule.RuleFixer} fixer - */ - * fix(fixer) { - yield removeThisFieldAssignment(node, sourceCode, fixer); - yield fixer.insertTextAfterRange( - classBodyStartRange, - `\n${indent}${node.expression.left.property.name} = ${node.expression.right.raw};`, - ); - }, - }; + continue; } + + const propertyName = getPropertyName(node.expression.left.property); + if (!propertyName) { + continue; + } + + return { + node, + messageId: MESSAGE_ID, + + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ + * fix(fixer) { + yield removeFieldAssignment(node, sourceCode, fixer); + yield fixer.insertTextAfterRange( + classBodyStartRange, + `\n${indent}${propertyName} = ${node.expression.right.raw};`, + ); + }, + }; } }, }; diff --git a/test/prefer-class-fields.mjs b/test/prefer-class-fields.mjs index 61c5996e45..52beae2b7f 100644 --- a/test/prefer-class-fields.mjs +++ b/test/prefer-class-fields.mjs @@ -73,6 +73,20 @@ test.snapshot({ } } `, + outdent` + class Foo { + constructor() { + this['foo'] = 'foo'; + } + } + `, + outdent` + class Foo { + constructor() { + this[\`foo\`] = 'foo'; + } + } + `, ], }); diff --git a/test/snapshots/prefer-class-fields.mjs.md b/test/snapshots/prefer-class-fields.mjs.md index 39fde7e9e7..d3b6e6ab0e 100644 --- a/test/snapshots/prefer-class-fields.mjs.md +++ b/test/snapshots/prefer-class-fields.mjs.md @@ -147,3 +147,69 @@ Generated by [AVA](https://avajs.dev). 5 | }␊ 6 | }␊ ` + +## invalid(5): class Foo { constructor() { this['foo'] = 'foo'; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this['foo'] = 'foo';␊ + 4 | }␊ + 5 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | ['foo'] = 'foo';␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + > 3 | this['foo'] = 'foo';␊ + | ^^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 4 | }␊ + 5 | }␊ + ` + +## invalid(6): class Foo { constructor() { this[`foo`] = 'foo'; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | this[\`foo\`] = 'foo';␊ + 4 | }␊ + 5 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | [\`foo\`] = 'foo';␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + > 3 | this[\`foo\`] = 'foo';␊ + | ^^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 4 | }␊ + 5 | }␊ + ` diff --git a/test/snapshots/prefer-class-fields.mjs.snap b/test/snapshots/prefer-class-fields.mjs.snap index 708c7022c00767eb63068ae80f2e7d9b47cfa28e..fa6d236595b0f1ead9f1fab31ea1fadaa3cfb661 100644 GIT binary patch literal 726 zcmV;{0xA7LRzVH)9dFoOM?p=*0l|f9Pn@7U0FS|mD<`hJ0S|zj+DXRVO`NDD%`IzZzHerJlbx^L zkdGA3nfLRntEJ~ ze^E6d`yKV=OU}Mbd0UX(tkCw|MeW}%&XC#!r`3F*F&qq{&F{TS}ND_OOWyl zv$7`o@(b{91l!Lg7hsJgGdZhYgU8(LGR52CNQHRAt60u;rx$^{mM5M$4=QxMYW2r>qD=xNoa1M3K|>0HOCxeZBl-z%vOwo;bEd?ioc7B8^3DHSK6(p6w{q|^ABDM z8d8y+9x@sQ2&f+sK@{UL_>c-5rw1N`NXdF}pT-I-F18UN7m$i5=3_{rgvvczsqC2$ zT%h@|nRD_g=cKhVC;3V%TWZp5-g1Cf+fcFV)vIT$!J>}i6iHMX8gXh zhU~kQ*{@@SjKP&h=qw+h_RR>r_;CFe9Vr?I64$@!^H?m}JsXO3ObNhKEw Date: Fri, 20 Dec 2024 02:48:04 +0100 Subject: [PATCH 11/54] chore: replace existing field if present --- rules/prefer-class-fields.js | 124 ++++++++++++-------- test/prefer-class-fields.mjs | 10 +- test/snapshots/prefer-class-fields.mjs.md | 55 ++------- test/snapshots/prefer-class-fields.mjs.snap | Bin 726 -> 706 bytes 4 files changed, 89 insertions(+), 100 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index f93dcad002..201e448467 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -27,27 +27,6 @@ const isThisAssignmentExpression = node => { return true; }; -/** -@param {import('estree').Expression | import('estree').PrivateIdentifier} node -*/ -const getPropertyName = node => { - if (node.type === 'Identifier') { - return node.name; - } - - if (node.type === 'Literal') { - return `[${node.raw}]`; - } - - if ( - node.type === 'TemplateLiteral' - && node.expressions.length === 0 - && node.quasis.length === 1 - ) { - return `[\`${node.quasis[0].value.raw}\`]`; - } -}; - /** @param {import('eslint').Rule.Node} node @param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode @@ -67,6 +46,58 @@ const removeFieldAssignment = (node, sourceCode, fixer) => { : fixer.remove(node); }; +/** +@param {string} propertyName +@param {import('estree').ClassBody} classBody +*/ +const findClassFieldNamed = (propertyName, classBody) => { + for (const classBodyChild of classBody.body) { + if ( + classBodyChild.type === 'PropertyDefinition' + && classBodyChild.key.type === 'Identifier' + && classBodyChild.key.name === propertyName + ) { + return classBodyChild; + } + } +}; + +/** +@param {string} propertyName +@param {string} propertyValue +@param {import('estree').ClassBody} classBody +@param {import('estree').MethodDefinition} constructor +@param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode +@param {import('eslint').Rule.RuleFixer} fixer +*/ +const addOrReplaceClassFieldDeclaration = ( + propertyName, + propertyValue, + classBody, + constructor, + sourceCode, + fixer, +) => { + const alreadyExistingDeclaration = findClassFieldNamed( + propertyName, + classBody, + ); + + if (alreadyExistingDeclaration) { + return fixer.replaceText( + alreadyExistingDeclaration, + `${propertyName} = ${propertyValue}`, + ); + } + + const classBodyStartRange = [classBody.range[0], classBody.range[0] + 1]; + const indent = getIndentString(constructor, sourceCode); + return fixer.insertTextAfterRange( + classBodyStartRange, + `\n${indent}${propertyName} = ${propertyValue};`, + ); +}; + /** @type {import('eslint').Rule.RuleModule['create']} */ @@ -87,39 +118,34 @@ const create = context => { return; } - const classBodyStartRange = [classBody.range[0], classBody.range[0] + 1]; - const indent = getIndentString(constructor, sourceCode); - for (let i = constructorBody.length - 1; i >= 0; i--) { const node = constructorBody[i]; if ( - !isThisAssignmentExpression(node) - || node.expression.right?.type !== 'Literal' - || node.expression.operator !== '=' + isThisAssignmentExpression(node) + && node.expression.right?.type === 'Literal' + && node.expression.operator === '=' + && node.expression.left.property.type === 'Identifier' ) { - continue; + return { + node, + messageId: MESSAGE_ID, + + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ + * fix(fixer) { + yield removeFieldAssignment(node, sourceCode, fixer); + yield addOrReplaceClassFieldDeclaration( + node.expression.left.property.name, + node.expression.right.raw, + classBody, + constructor, + sourceCode, + fixer, + ); + }, + }; } - - const propertyName = getPropertyName(node.expression.left.property); - if (!propertyName) { - continue; - } - - return { - node, - messageId: MESSAGE_ID, - - /** - @param {import('eslint').Rule.RuleFixer} fixer - */ - * fix(fixer) { - yield removeFieldAssignment(node, sourceCode, fixer); - yield fixer.insertTextAfterRange( - classBodyStartRange, - `\n${indent}${propertyName} = ${node.expression.right.raw};`, - ); - }, - }; } }, }; diff --git a/test/prefer-class-fields.mjs b/test/prefer-class-fields.mjs index 52beae2b7f..823e55919d 100644 --- a/test/prefer-class-fields.mjs +++ b/test/prefer-class-fields.mjs @@ -75,15 +75,9 @@ test.snapshot({ `, outdent` class Foo { + foo = 'test'; constructor() { - this['foo'] = 'foo'; - } - } - `, - outdent` - class Foo { - constructor() { - this[\`foo\`] = 'foo'; + this.foo = 'foo'; } } `, diff --git a/test/snapshots/prefer-class-fields.mjs.md b/test/snapshots/prefer-class-fields.mjs.md index d3b6e6ab0e..c21d297e0e 100644 --- a/test/snapshots/prefer-class-fields.mjs.md +++ b/test/snapshots/prefer-class-fields.mjs.md @@ -148,56 +148,24 @@ Generated by [AVA](https://avajs.dev). 6 | }␊ ` -## invalid(5): class Foo { constructor() { this['foo'] = 'foo'; } } +## invalid(5): class Foo { foo = 'test'; constructor() { this.foo = 'foo'; } } > Input `␊ 1 | class Foo {␊ - 2 | constructor() {␊ - 3 | this['foo'] = 'foo';␊ - 4 | }␊ - 5 | }␊ - ` - -> Output - - `␊ - 1 | class Foo {␊ - 2 | ['foo'] = 'foo';␊ + 2 | foo = 'test';␊ 3 | constructor() {␊ - 4 | }␊ - 5 | }␊ - ` - -> Error 1/1 - - `␊ - 1 | class Foo {␊ - 2 | constructor() {␊ - > 3 | this['foo'] = 'foo';␊ - | ^^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ - 4 | }␊ - 5 | }␊ - ` - -## invalid(6): class Foo { constructor() { this[`foo`] = 'foo'; } } - -> Input - - `␊ - 1 | class Foo {␊ - 2 | constructor() {␊ - 3 | this[\`foo\`] = 'foo';␊ - 4 | }␊ - 5 | }␊ + 4 | this.foo = 'foo';␊ + 5 | }␊ + 6 | }␊ ` > Output `␊ 1 | class Foo {␊ - 2 | [\`foo\`] = 'foo';␊ + 2 | foo = 'foo'␊ 3 | constructor() {␊ 4 | }␊ 5 | }␊ @@ -207,9 +175,10 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | constructor() {␊ - > 3 | this[\`foo\`] = 'foo';␊ - | ^^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ - 4 | }␊ - 5 | }␊ + 2 | foo = 'test';␊ + 3 | constructor() {␊ + > 4 | this.foo = 'foo';␊ + | ^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ ` diff --git a/test/snapshots/prefer-class-fields.mjs.snap b/test/snapshots/prefer-class-fields.mjs.snap index fa6d236595b0f1ead9f1fab31ea1fadaa3cfb661..445d2abffe984f19b39ed030016b2d0963a8766c 100644 GIT binary patch literal 706 zcmV;z0zLgfRzVIEN0@JkD!( zVFD8-12T|DT*-Imu1geo=3J>ZNEMdV8Uod;FI6xc0TOc%OJz};h&+p7<; zXqu4yg6i^d!MdchEy-?PYm&CAMsu9WX-aRK5dVxqI4cOjz&Li8V;l$DUMwW7Y}69_ z)(s^5y0wDbcMEgBiWbr)F5g1ONei`REwuaQ;ww5*G;k$c{K8Gur&5w0je1Ik13Ev5 zTP7fV3LV&pZEYC3<^!tSdyJexhpv`$IkQq`WZ^>Qk{+YEz7GB{{LcxJWTj+XGTbT3&eBa50~H(+8SP!-l(%{uV@|B-csfC oK1W&}Ep)s8)nfb;LHNCVo)kx)-BMP&EtlZ+8H)9dFoOM?p=*0l|f9Pn@7U0FS|mD<`hJ0S|zj+DXRVO`NDD%`IzZzHerJlbx^L zkdGA3nfLRntEJ~ ze^E6d`yKV=OU}Mbd0UX(tkCw|MeW}%&XC#!r`3F*F&qq{&F{TS}ND_OOWyl zv$7`o@(b{91l!Lg7hsJgGdZhYgU8(L Date: Fri, 20 Dec 2024 03:02:01 +0100 Subject: [PATCH 12/54] chore: lint fix --- rules/prefer-class-fields.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 201e448467..f33cfc3c70 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -118,8 +118,8 @@ const create = context => { return; } - for (let i = constructorBody.length - 1; i >= 0; i--) { - const node = constructorBody[i]; + for (let index = constructorBody.length - 1; index >= 0; index--) { + const node = constructorBody[index]; if ( isThisAssignmentExpression(node) && node.expression.right?.type === 'Literal' From ed45d3d922e2801443c4dfcbb5a592f7e08a285d Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Sat, 21 Dec 2024 13:53:02 +0100 Subject: [PATCH 13/54] feat: handle only non-computed properties --- rules/prefer-class-fields.js | 57 +++++++++++++++++++----------------- test/prefer-class-fields.mjs | 17 +++++++---- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index f33cfc3c70..b839b83241 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -1,26 +1,27 @@ -'use strict'; -const getIndentString = require('./utils/get-indent-string.js'); +"use strict"; +const getIndentString = require("./utils/get-indent-string.js"); -const MESSAGE_ID = 'prefer-class-fields/error'; +const MESSAGE_ID = "prefer-class-fields/error"; const messages = { - [MESSAGE_ID]: 'Prefer class field declaration over `this` assignment in constructor for static values.', + [MESSAGE_ID]: + "Prefer class field declaration over `this` assignment in constructor for static values.", }; /** @param {import('eslint').Rule.Node} node @returns {node is import('estree').ExpressionStatement & {expression: import('estree').AssignmentExpression & {left: import('estree').MemberExpression & {object: import('estree').ThisExpression}}}} */ -const isThisAssignmentExpression = node => { +const isThisAssignmentExpression = (node) => { if ( - node.type !== 'ExpressionStatement' - || node.expression.type !== 'AssignmentExpression' + node.type !== "ExpressionStatement" || + node.expression.type !== "AssignmentExpression" ) { return false; } const lhs = node.expression.left; - if (!lhs.object || lhs.object.type !== 'ThisExpression') { + if (!lhs.object || lhs.object.type !== "ThisExpression") { return false; } @@ -33,16 +34,16 @@ const isThisAssignmentExpression = node => { @param {import('eslint').Rule.RuleFixer} fixer */ const removeFieldAssignment = (node, sourceCode, fixer) => { - const {line} = node.loc.start; + const { line } = node.loc.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}), - ]) + sourceCode.getIndexFromLoc({ line, column: 0 }), + sourceCode.getIndexFromLoc({ line: line + 1, column: 0 }), + ]) : fixer.remove(node); }; @@ -53,9 +54,9 @@ const removeFieldAssignment = (node, sourceCode, fixer) => { const findClassFieldNamed = (propertyName, classBody) => { for (const classBodyChild of classBody.body) { if ( - classBodyChild.type === 'PropertyDefinition' - && classBodyChild.key.type === 'Identifier' - && classBodyChild.key.name === propertyName + classBodyChild.type === "PropertyDefinition" && + classBodyChild.key.type === "Identifier" && + classBodyChild.key.name === propertyName ) { return classBodyChild; } @@ -101,14 +102,14 @@ const addOrReplaceClassFieldDeclaration = ( /** @type {import('eslint').Rule.RuleModule['create']} */ -const create = context => { - const {sourceCode} = context; +const create = (context) => { + const { sourceCode } = context; return { ClassBody(classBody) { - const constructor = classBody.body.find(x => x.kind === 'constructor'); + const constructor = classBody.body.find((x) => x.kind === "constructor"); - if (!constructor || constructor.type !== 'MethodDefinition') { + if (!constructor || constructor.type !== "MethodDefinition") { return; } @@ -121,10 +122,11 @@ const create = context => { for (let index = constructorBody.length - 1; index >= 0; index--) { const node = constructorBody[index]; if ( - isThisAssignmentExpression(node) - && node.expression.right?.type === 'Literal' - && node.expression.operator === '=' - && node.expression.left.property.type === 'Identifier' + isThisAssignmentExpression(node) && + node.expression.right?.type === "Literal" && + node.expression.operator === "=" && + node.expression.left.property.type === "Identifier" && + !node.expression.left.computed ) { return { node, @@ -133,7 +135,7 @@ const create = context => { /** @param {import('eslint').Rule.RuleFixer} fixer */ - * fix(fixer) { + *fix(fixer) { yield removeFieldAssignment(node, sourceCode, fixer); yield addOrReplaceClassFieldDeclaration( node.expression.left.property.name, @@ -155,12 +157,13 @@ const create = context => { module.exports = { create, meta: { - type: 'suggestion', + type: "suggestion", docs: { - description: 'Prefer class field declarations over assigning static values in constructor using `this`.', + description: + "Prefer class field declarations over assigning static values in constructor using `this`.", recommended: true, }, - fixable: 'code', + fixable: "code", hasSuggestions: false, messages, }, diff --git a/test/prefer-class-fields.mjs b/test/prefer-class-fields.mjs index 823e55919d..a96585ad2c 100644 --- a/test/prefer-class-fields.mjs +++ b/test/prefer-class-fields.mjs @@ -1,9 +1,9 @@ -import outdent from 'outdent'; -import {getTester} from './utils/test.mjs'; +import outdent from "outdent"; +import { getTester } from "./utils/test.mjs"; -const {test} = getTester(import.meta); +const { test } = getTester(import.meta); -const MESSAGE_ID = 'prefer-class-fields/error'; +const MESSAGE_ID = "prefer-class-fields/error"; test.snapshot({ valid: [ @@ -31,6 +31,13 @@ test.snapshot({ } } `, + outdent` + class Foo { + constructor() { + this[foo] = 'foo'; + } + } + `, outdent` class Foo { something = 'a'; @@ -107,7 +114,7 @@ test.typescript({ } } `, - errors: [{messageId: MESSAGE_ID}], + errors: [{ messageId: MESSAGE_ID }], output: outdent` class MyError extends Error { name = "MyError"; From faf6c2cf310fbc9483ac73f0b6efaad9e1c99790 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Sun, 22 Dec 2024 18:53:16 +0100 Subject: [PATCH 14/54] chore: stop static analysis on unsupported cases --- rules/prefer-class-fields.js | 72 +++++++++++++++++++++--------------- test/prefer-class-fields.mjs | 18 ++++++--- 2 files changed, 55 insertions(+), 35 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index b839b83241..2c90a46c81 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -1,27 +1,27 @@ -"use strict"; -const getIndentString = require("./utils/get-indent-string.js"); +'use strict'; +const getIndentString = require('./utils/get-indent-string.js'); -const MESSAGE_ID = "prefer-class-fields/error"; +const MESSAGE_ID = 'prefer-class-fields/error'; const messages = { [MESSAGE_ID]: - "Prefer class field declaration over `this` assignment in constructor for static values.", + 'Prefer class field declaration over `this` assignment in constructor for static values.', }; /** @param {import('eslint').Rule.Node} node @returns {node is import('estree').ExpressionStatement & {expression: import('estree').AssignmentExpression & {left: import('estree').MemberExpression & {object: import('estree').ThisExpression}}}} */ -const isThisAssignmentExpression = (node) => { +const isThisAssignmentExpression = node => { if ( - node.type !== "ExpressionStatement" || - node.expression.type !== "AssignmentExpression" + node.type !== 'ExpressionStatement' + || node.expression.type !== 'AssignmentExpression' ) { return false; } const lhs = node.expression.left; - if (!lhs.object || lhs.object.type !== "ThisExpression") { + if (!lhs.object || lhs.object.type !== 'ThisExpression') { return false; } @@ -34,16 +34,16 @@ const isThisAssignmentExpression = (node) => { @param {import('eslint').Rule.RuleFixer} fixer */ const removeFieldAssignment = (node, sourceCode, fixer) => { - const { line } = node.loc.start; + const {line} = node.loc.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 }), - ]) + sourceCode.getIndexFromLoc({line, column: 0}), + sourceCode.getIndexFromLoc({line: line + 1, column: 0}), + ]) : fixer.remove(node); }; @@ -54,9 +54,9 @@ const removeFieldAssignment = (node, sourceCode, fixer) => { const findClassFieldNamed = (propertyName, classBody) => { for (const classBodyChild of classBody.body) { if ( - classBodyChild.type === "PropertyDefinition" && - classBodyChild.key.type === "Identifier" && - classBodyChild.key.name === propertyName + classBodyChild.type === 'PropertyDefinition' + && classBodyChild.key.type === 'Identifier' + && classBodyChild.key.name === propertyName ) { return classBodyChild; } @@ -102,14 +102,14 @@ const addOrReplaceClassFieldDeclaration = ( /** @type {import('eslint').Rule.RuleModule['create']} */ -const create = (context) => { - const { sourceCode } = context; +const create = context => { + const {sourceCode} = context; return { ClassBody(classBody) { - const constructor = classBody.body.find((x) => x.kind === "constructor"); + const constructor = classBody.body.find(x => x.kind === 'constructor'); - if (!constructor || constructor.type !== "MethodDefinition") { + if (!constructor || constructor.type !== 'MethodDefinition') { return; } @@ -119,14 +119,26 @@ const create = (context) => { return; } - for (let index = constructorBody.length - 1; index >= 0; index--) { - const node = constructorBody[index]; + const firstInvalidProperty = constructorBody.findIndex( + node => node.type !== 'ExpressionStatement', + ); + const validConstructorProperties + = firstInvalidProperty === -1 + ? constructorBody + : constructorBody.slice(0, firstInvalidProperty); + + for ( + let index = validConstructorProperties.length - 1; + index >= 0; + index-- + ) { + const node = validConstructorProperties[index]; if ( - isThisAssignmentExpression(node) && - node.expression.right?.type === "Literal" && - node.expression.operator === "=" && - node.expression.left.property.type === "Identifier" && - !node.expression.left.computed + isThisAssignmentExpression(node) + && node.expression.right?.type === 'Literal' + && node.expression.operator === '=' + && node.expression.left.property.type === 'Identifier' + && !node.expression.left.computed ) { return { node, @@ -135,7 +147,7 @@ const create = (context) => { /** @param {import('eslint').Rule.RuleFixer} fixer */ - *fix(fixer) { + * fix(fixer) { yield removeFieldAssignment(node, sourceCode, fixer); yield addOrReplaceClassFieldDeclaration( node.expression.left.property.name, @@ -157,13 +169,13 @@ const create = (context) => { module.exports = { create, meta: { - type: "suggestion", + type: 'suggestion', docs: { description: - "Prefer class field declarations over assigning static values in constructor using `this`.", + 'Prefer class field declarations over assigning static values in constructor using `this`.', recommended: true, }, - fixable: "code", + fixable: 'code', hasSuggestions: false, messages, }, diff --git a/test/prefer-class-fields.mjs b/test/prefer-class-fields.mjs index a96585ad2c..dfe06842ba 100644 --- a/test/prefer-class-fields.mjs +++ b/test/prefer-class-fields.mjs @@ -1,9 +1,9 @@ -import outdent from "outdent"; -import { getTester } from "./utils/test.mjs"; +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; -const { test } = getTester(import.meta); +const {test} = getTester(import.meta); -const MESSAGE_ID = "prefer-class-fields/error"; +const MESSAGE_ID = 'prefer-class-fields/error'; test.snapshot({ valid: [ @@ -46,6 +46,14 @@ test.snapshot({ } } `, + outdent` + class Foo { + constructor() { + if (something) { return; } + this.elo = 'foo'; + } + } + `, ], invalid: [ outdent` @@ -114,7 +122,7 @@ test.typescript({ } } `, - errors: [{ messageId: MESSAGE_ID }], + errors: [{messageId: MESSAGE_ID}], output: outdent` class MyError extends Error { name = "MyError"; From d77f630ec19385db1f3c9586e66fb288adc38b13 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Wed, 25 Dec 2024 13:39:21 +0100 Subject: [PATCH 15/54] chore: add missing semicolon --- rules/prefer-class-fields.js | 2 +- test/snapshots/prefer-class-fields.mjs.md | 2 +- test/snapshots/prefer-class-fields.mjs.snap | Bin 706 -> 699 bytes 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 2c90a46c81..74c332354a 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -87,7 +87,7 @@ const addOrReplaceClassFieldDeclaration = ( if (alreadyExistingDeclaration) { return fixer.replaceText( alreadyExistingDeclaration, - `${propertyName} = ${propertyValue}`, + `${propertyName} = ${propertyValue};`, ); } diff --git a/test/snapshots/prefer-class-fields.mjs.md b/test/snapshots/prefer-class-fields.mjs.md index c21d297e0e..c58e5ec771 100644 --- a/test/snapshots/prefer-class-fields.mjs.md +++ b/test/snapshots/prefer-class-fields.mjs.md @@ -165,7 +165,7 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | foo = 'foo'␊ + 2 | foo = 'foo';␊ 3 | constructor() {␊ 4 | }␊ 5 | }␊ diff --git a/test/snapshots/prefer-class-fields.mjs.snap b/test/snapshots/prefer-class-fields.mjs.snap index 445d2abffe984f19b39ed030016b2d0963a8766c..5324ad249c94292c8952c738cd467c3f70960785 100644 GIT binary patch literal 699 zcmV;s0z~~mRzVOMae%q99uOS*;A1aD97)0`urcOv5F0!IBm^1IA*K=tO_vb@#z9CVi3ej8Mp*3H zLM8V(XB?Whnh7UQ6HZ!{IY~}h>ZoO7=dD)6*ME_nx<18&^V;gsX9t^Vczp zH_Fgy`=!zLqXKRA-1G}`V1SIPajk>t@iq=u%EmcnM hzu%01Y7~AiA1KA)XV;YVuFEBS{RWJ@5olly000ZFO%VV9 literal 706 zcmV;z0zLgfRzVIEN0@JkD!( zVFD8-12T|DT*-Imu1geo=3J>ZNEMdV8Uod;FI6xc0TOc%OJz};h&+p7<; zXqu4yg6i^d!MdchEy-?PYm&CAMsu9WX-aRK5dVxqI4cOjz&Li8V;l$DUMwW7Y}69_ z)(s^5y0wDbcMEgBiWbr)F5g1ONei`REwuaQ;ww5*G;k$c{K8Gur&5w0je1Ik13Ev5 zTP7fV3LV&pZEYC3<^!tSdyJexhpv`$IkQq`WZ^>Qk{+YEz7GB{{LcxJWTj+XGTbT3&eBa50~H(+8SP!-l(%{uV@|B-csfC oK1W&}Ep)s8)nfb;LHNCVo)kx)-BMP&EtlZ+8 Date: Sat, 18 Jan 2025 00:30:15 +0100 Subject: [PATCH 16/54] docs: update docs/rules/prefer-class-fields.md Co-authored-by: Sindre Sorhus --- docs/rules/prefer-class-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index 660898df43..628ed19b0f 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -1,4 +1,4 @@ -# Prefer class field declarations over assigning static values in constructor using `this` +# Prefer class field declarations over `this` assignments in constructor πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). From 34f55d1e2bde040304f51b4691e06b2bb69ace45 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Sat, 18 Jan 2025 00:49:21 +0100 Subject: [PATCH 17/54] docs: update rule description --- readme.md | 2 +- rules/prefer-class-fields.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 4cb21c5269..0935dc3e93 100644 --- a/readme.md +++ b/readme.md @@ -132,7 +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 assigning static values in constructor using `this`. | βœ… | πŸ”§ | | +| [prefer-class-fields](docs/rules/prefer-class-fields.md) | Prefer class field declarations over `this` assignments in constructor | βœ… | πŸ”§ | | | [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/prefer-class-fields.js b/rules/prefer-class-fields.js index 74c332354a..6b57174866 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -172,7 +172,7 @@ module.exports = { type: 'suggestion', docs: { description: - 'Prefer class field declarations over assigning static values in constructor using `this`.', + 'Prefer class field declarations over `this` assignments in constructor', recommended: true, }, fixable: 'code', From 4d412a51d95710381709b24c0eafc176e96f295b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 18 Jan 2025 14:21:37 +0700 Subject: [PATCH 18/54] Update prefer-class-fields.md --- docs/rules/prefer-class-fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index 628ed19b0f..7c8cde2e13 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -23,7 +23,7 @@ class Foo { class MyError extends Error { constructor(message: string) { super(message); - this.name = "MyError"; + this.name = 'MyError'; } } ``` @@ -36,6 +36,6 @@ class Foo { } class MyError extends Error { - name = "MyError" + name = 'MyError' } ``` From ad13ba76d28aa9aaae956aedaa880b954816f798 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 18 Jan 2025 14:48:58 +0700 Subject: [PATCH 19/54] Update prefer-class-fields.md --- docs/rules/prefer-class-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index 7c8cde2e13..48a6f3a9e5 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -1,4 +1,4 @@ -# Prefer class field declarations over `this` assignments in constructor +# Prefer class field declarations over `this` assignments in constructors πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). From 980bc4288ed17481bec460a96bce5417a4c62015 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 19 Jan 2025 20:12:16 +0700 Subject: [PATCH 20/54] Update prefer-class-fields.js --- rules/prefer-class-fields.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 6b57174866..2fbffee003 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -1,5 +1,4 @@ -'use strict'; -const getIndentString = require('./utils/get-indent-string.js'); +import getIndentString from './utils/get-indent-string.js'; const MESSAGE_ID = 'prefer-class-fields/error'; const messages = { @@ -166,7 +165,7 @@ const create = context => { }; /** @type {import('eslint').Rule.RuleModule} */ -module.exports = { +const config = { create, meta: { type: 'suggestion', @@ -180,3 +179,5 @@ module.exports = { messages, }, }; + +export default config; From a691cf6719e1eab3b7c6428b9682e8677ef5758b Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Mon, 20 Jan 2025 19:46:17 +0100 Subject: [PATCH 21/54] chore: update rules/prefer-class-fields.js Co-authored-by: Sindre Sorhus --- rules/prefer-class-fields.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 2fbffee003..b0fa61826d 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -170,8 +170,7 @@ const config = { meta: { type: 'suggestion', docs: { - description: - 'Prefer class field declarations over `this` assignments in constructor', + description: 'Prefer class field declarations over `this` assignments in constructors.', recommended: true, }, fixable: 'code', From 1bbd138fdac0bb407e04219d39a567240f876b07 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Tue, 21 Jan 2025 01:32:55 +0100 Subject: [PATCH 22/54] chore: change examples style --- docs/rules/prefer-class-fields.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index 48a6f3a9e5..bdd3835e1c 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -14,28 +14,45 @@ This rule enforces the use of class field declarations for static values, instea ## Fail ```js +// ❌ class Foo { constructor() { this.foo = 'foo'; } } +// βœ… +class Foo { + foo = 'foo'; +} +``` + +```js +// ❌ class MyError extends Error { constructor(message: string) { super(message); this.name = 'MyError'; } } -``` -## Pass +// βœ… +class MyError extends Error { + name = 'MyError' +} +``` ```js +// ❌ class Foo { foo = 'foo'; + constructor() { + this.foo = 'bar'; + } } -class MyError extends Error { - name = 'MyError' +// βœ… +class Foo { + foo = 'bar'; } ``` From 03866026e924725acaede57c1d774b74ca130100 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Tue, 21 Jan 2025 01:38:37 +0100 Subject: [PATCH 23/54] chore: rename test files --- rules/utils/rule.js | 5 +---- test/package.js | 1 + ...efer-class-fields.mjs => prefer-class-fields.js} | 2 +- ...lass-fields.mjs.md => prefer-class-fields.js.md} | 4 ++-- ...-fields.mjs.snap => prefer-class-fields.js.snap} | Bin 5 files changed, 5 insertions(+), 7 deletions(-) rename test/{prefer-class-fields.mjs => prefer-class-fields.js} (97%) rename test/snapshots/{prefer-class-fields.mjs.md => prefer-class-fields.js.md} (97%) rename test/snapshots/{prefer-class-fields.mjs.snap => prefer-class-fields.js.snap} (100%) 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.mjs b/test/prefer-class-fields.js similarity index 97% rename from test/prefer-class-fields.mjs rename to test/prefer-class-fields.js index dfe06842ba..6cd2246695 100644 --- a/test/prefer-class-fields.mjs +++ b/test/prefer-class-fields.js @@ -1,5 +1,5 @@ import outdent from 'outdent'; -import {getTester} from './utils/test.mjs'; +import {getTester} from './utils/test.js'; const {test} = getTester(import.meta); diff --git a/test/snapshots/prefer-class-fields.mjs.md b/test/snapshots/prefer-class-fields.js.md similarity index 97% rename from test/snapshots/prefer-class-fields.mjs.md rename to test/snapshots/prefer-class-fields.js.md index c58e5ec771..3e24c28ff8 100644 --- a/test/snapshots/prefer-class-fields.mjs.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -1,6 +1,6 @@ -# Snapshot report for `test/prefer-class-fields.mjs` +# Snapshot report for `test/prefer-class-fields.js` -The actual snapshot is saved in `prefer-class-fields.mjs.snap`. +The actual snapshot is saved in `prefer-class-fields.js.snap`. Generated by [AVA](https://avajs.dev). diff --git a/test/snapshots/prefer-class-fields.mjs.snap b/test/snapshots/prefer-class-fields.js.snap similarity index 100% rename from test/snapshots/prefer-class-fields.mjs.snap rename to test/snapshots/prefer-class-fields.js.snap From 4fee5030e4bfa1a2590109d28bd49ceaa7b95455 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Wed, 29 Jan 2025 01:14:27 +0100 Subject: [PATCH 24/54] chore: run fix:eslint docs (works only on node 22) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0935dc3e93..976e4b25d9 100644 --- a/readme.md +++ b/readme.md @@ -132,7 +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 constructor | βœ… | πŸ”§ | | +| [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. | βœ… | | πŸ’‘ | From f65e1fa259e9a2eac1d948c728c10924d5a0b518 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 30 Jan 2025 00:47:25 +0700 Subject: [PATCH 25/54] Update prefer-class-fields.md --- docs/rules/prefer-class-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index bdd3835e1c..7e40c75de5 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -11,7 +11,7 @@ This rule enforces the use of class field declarations for static values, instea > To avoid leaving empty constructors after autofixing, use the [`no-useless-constructor` rule](https://eslint.org/docs/latest/rules/no-useless-constructor). -## Fail +## Examples ```js // ❌ From 832217287bf0ec99286d2349a741568270eb0bfd Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Sat, 15 Feb 2025 18:39:48 +0100 Subject: [PATCH 26/54] chore: rewrite autofix with side-effects to suggestion --- docs/rules/prefer-class-fields.md | 2 +- readme.md | 2 +- rules/prefer-class-fields.js | 59 ++++++++++++++------- test/snapshots/prefer-class-fields.js.md | 12 +---- test/snapshots/prefer-class-fields.js.snap | Bin 699 -> 745 bytes 5 files changed, 42 insertions(+), 33 deletions(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index 7e40c75de5..a381a2f8d9 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -2,7 +2,7 @@ πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). -πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). +πŸ”§πŸ’‘ 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). diff --git a/readme.md b/readme.md index 976e4b25d9..6f6107c26a 100644 --- a/readme.md +++ b/readme.md @@ -132,7 +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-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/prefer-class-fields.js b/rules/prefer-class-fields.js index b0fa61826d..a2f992f5be 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -1,9 +1,12 @@ import getIndentString from './utils/get-indent-string.js'; -const MESSAGE_ID = 'prefer-class-fields/error'; +const MESSAGE_ID_ERROR = 'prefer-class-fields/error'; +const MESSAGE_ID_SUGGESTION = 'prefer-class-fields/suggestion'; const messages = { - [MESSAGE_ID]: + [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.', }; /** @@ -70,7 +73,7 @@ const findClassFieldNamed = (propertyName, classBody) => { @param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode @param {import('eslint').Rule.RuleFixer} fixer */ -const addOrReplaceClassFieldDeclaration = ( +const addClassFieldDeclaration = ( propertyName, propertyValue, classBody, @@ -78,18 +81,6 @@ const addOrReplaceClassFieldDeclaration = ( sourceCode, fixer, ) => { - const alreadyExistingDeclaration = findClassFieldNamed( - propertyName, - classBody, - ); - - if (alreadyExistingDeclaration) { - return fixer.replaceText( - alreadyExistingDeclaration, - `${propertyName} = ${propertyValue};`, - ); - } - const classBodyStartRange = [classBody.range[0], classBody.range[0] + 1]; const indent = getIndentString(constructor, sourceCode); return fixer.insertTextAfterRange( @@ -139,18 +130,46 @@ const create = context => { && node.expression.left.property.type === 'Identifier' && !node.expression.left.computed ) { + const propertyName = node.expression.left.property.name; + const propertyValue = node.expression.right.raw; + const alreadyExistingClassFieldDeclaration = findClassFieldNamed( + propertyName, + classBody, + ); + + if (alreadyExistingClassFieldDeclaration) { + return { + node, + messageId: MESSAGE_ID_SUGGESTION, + data: { + propertyName: node.expression.left.property.name, + className: classBody.parent.id.name, + }, + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ + * suggest(fixer) { + yield removeFieldAssignment(node, sourceCode, fixer); + yield fixer.replaceText( + alreadyExistingClassFieldDeclaration, + `${propertyName} = ${propertyValue};`, + ); + }, + }; + } + return { node, - messageId: MESSAGE_ID, + messageId: MESSAGE_ID_ERROR, /** @param {import('eslint').Rule.RuleFixer} fixer */ * fix(fixer) { yield removeFieldAssignment(node, sourceCode, fixer); - yield addOrReplaceClassFieldDeclaration( - node.expression.left.property.name, - node.expression.right.raw, + yield addClassFieldDeclaration( + propertyName, + propertyValue, classBody, constructor, sourceCode, @@ -174,7 +193,7 @@ const config = { recommended: true, }, fixable: 'code', - hasSuggestions: false, + hasSuggestions: true, messages, }, }; diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index 3e24c28ff8..7da81b903e 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -161,16 +161,6 @@ Generated by [AVA](https://avajs.dev). 6 | }␊ ` -> Output - - `␊ - 1 | class Foo {␊ - 2 | foo = 'foo';␊ - 3 | constructor() {␊ - 4 | }␊ - 5 | }␊ - ` - > Error 1/1 `␊ @@ -178,7 +168,7 @@ Generated by [AVA](https://avajs.dev). 2 | foo = 'test';␊ 3 | constructor() {␊ > 4 | this.foo = 'foo';␊ - | ^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + | ^^^^^^^^^^^^^^^^^ Encountered same-named class field declaration and \`this\` assignment in constructor. Replace the class field declaration with the value from \`this\` assignment.␊ 5 | }␊ 6 | }␊ ` diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index 5324ad249c94292c8952c738cd467c3f70960785..73c12af0b7ae693a9c26d2828738cdb963dba997 100644 GIT binary patch literal 745 zcmV{k)Dzd9I6-*;9)maF4R`|{06DdjtnKxOL{)st?q=pcGr!rLZ|?3h?Gqv3A!Be1HlsrFz?O_}@4zw0p(mOyW3USim$AkJ zn8Vx?l#IoYN%85-afl=@?JKowweT~p);5h7CO`{j>xNB$Hq4-&ux|ob;c`9Y#{}qv zJ987D3p1FT=#xYA@ev&dGIIM$al5Z~o3LzUOUZQT?gw(^@z=V?ji0shk@j*Tg~iV6 zPjlX#^aEwy#*d{Bz`_8+oS&(IKsyomj-jw_)^!fITl{B^R$ zXUb4%`=ikIy8>vk}S|~Gd=@vSQTd1>ap(pRpPvKM|bp)LMQk!aoLXZK5JtcxMt`?$} zp=5-x3tN$`Elt;aQ7ZRYMNXq5tClM{%Tgv}sfA3*EAhjvl8%Hy5 zsX}`DhNVDpGv}Htr6xHwl$l=lsLcW@k)s2GjPP~{jH4|-p-y4-oA4aRKCuzx5Ve-x bd2+ZoumA*H&PHj=C9wGmrAmo!Hw^#)aB5-H literal 699 zcmV;s0z~~mRzVOMae%q99uOS*;A1aD97)0`urcOv5F0!IBm^1IA*K=tO_vb@#z9CVi3ej8Mp*3H zLM8V(XB?Whnh7UQ6HZ!{IY~}h>ZoO7=dD)6*ME_nx<18&^V;gsX9t^Vczp zH_Fgy`=!zLqXKRA-1G}`V1SIPajk>t@iq=u%EmcnM hzu%01Y7~AiA1KA)XV;YVuFEBS{RWJ@5olly000ZFO%VV9 From ec0d6838e61756159786979baf18340fba7b74df Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Sat, 15 Feb 2025 18:46:22 +0100 Subject: [PATCH 27/54] chore: add EmptyStatement as whitelisted node preceding this assignment add tests to cover this case --- rules/prefer-class-fields.js | 4 +- test/prefer-class-fields.js | 9 +++++ test/snapshots/prefer-class-fields.js.md | 43 ++++++++++++++++++++- test/snapshots/prefer-class-fields.js.snap | Bin 745 -> 805 bytes 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index a2f992f5be..99c0559cf5 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -9,6 +9,8 @@ const messages = { 'Encountered same-named class field declaration and `this` assignment in constructor. Replace the class field declaration with the value from `this` assignment.', }; +const WHITELIST_NODES_PRECEDING_THIS_ASSIGNMENT = new Set(['EmptyStatement', 'ExpressionStatement']); + /** @param {import('eslint').Rule.Node} node @returns {node is import('estree').ExpressionStatement & {expression: import('estree').AssignmentExpression & {left: import('estree').MemberExpression & {object: import('estree').ThisExpression}}}} @@ -110,7 +112,7 @@ const create = context => { } const firstInvalidProperty = constructorBody.findIndex( - node => node.type !== 'ExpressionStatement', + node => !WHITELIST_NODES_PRECEDING_THIS_ASSIGNMENT.has(node.type), ); const validConstructorProperties = firstInvalidProperty === -1 diff --git a/test/prefer-class-fields.js b/test/prefer-class-fields.js index 6cd2246695..f48a2e8f57 100644 --- a/test/prefer-class-fields.js +++ b/test/prefer-class-fields.js @@ -80,6 +80,15 @@ test.snapshot({ } } `, + outdent` + class Foo { + constructor() { + ; + this.foo = 'foo'; + this.foo2 = 'foo2'; + } + } + `, outdent` class MyError extends Error { constructor(message) { diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index 7da81b903e..6910f05b02 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -112,7 +112,46 @@ Generated by [AVA](https://avajs.dev). 7 | }␊ ` -## invalid(4): class MyError extends Error { constructor(message) { super(message); this.name = "MyError"; } } +## invalid(4): class Foo { constructor() { ; this.foo = 'foo'; this.foo2 = 'foo2'; } } + +> Input + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | ;␊ + 4 | this.foo = 'foo';␊ + 5 | this.foo2 = 'foo2';␊ + 6 | }␊ + 7 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | foo = 'foo';␊ + 3 | foo2 = 'foo2';␊ + 4 | constructor() {␊ + 5 | ;␊ + 6 | }␊ + 7 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | constructor() {␊ + 3 | ;␊ + 4 | this.foo = 'foo';␊ + > 5 | this.foo2 = 'foo2';␊ + | ^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 6 | }␊ + 7 | }␊ + ` + +## invalid(5): class MyError extends Error { constructor(message) { super(message); this.name = "MyError"; } } > Input @@ -148,7 +187,7 @@ Generated by [AVA](https://avajs.dev). 6 | }␊ ` -## invalid(5): class Foo { foo = 'test'; constructor() { this.foo = 'foo'; } } +## invalid(6): class Foo { foo = 'test'; constructor() { this.foo = 'foo'; } } > Input diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index 73c12af0b7ae693a9c26d2828738cdb963dba997..68a8e52ef35fdbf244cbd778df2c9a3f927292a3 100644 GIT binary patch literal 805 zcmV+=1KRvSRzVODHMb%mgvy7Inggg? zd)$~hyXo#a1SJs?2qeU{Cr(fvfXCnscmvK{I3wiLPO`SwA3#-n%i5Xw&-{NoUVpOp zU1lDNwEdw9p#$PN)>>`-7MLy(0&X(~BQO~il6$6Pd~F>@AiIvJ+Kj;#tlEsN zZo&j6j-aG3x=f1KCzeGd*)=beE|vVtxL8^?9#;TrFkTE?0jR?m%8~dLfQG+Zj^(KU zH2pi13P20SFsYzR_R+;VwC~9v?Gq*Kjy`Q9awAzvVnTbzlk<$<7Ben=E#voOaG#@%Jcw>P=vrW*=@L$)!zi!;a@g|&?g0_PIiNkH5*)f{BiM|CkVHBr42bKY zsOm~}wmD}UYBy>jCl5kS>V-K8SL(r1UB}K##l)k3$WA#yLs0Ux_6;3ZLXm{UxQgX# zCW}v$q0;tKq3wGC+Vs6?r}joe;+e9T*oZib!;r;B3X6mvcz)t+Cc`J;8mvkba^ZF( z_(wyB>=(tCk2!r=&{mM$xYn?3na5q{Y?hu*hSdw(8VD6w(zE%YedLd}eEzWFx;{hcyBN5zL$+=xYs;w;U_S2p(( zMI1ZB(O%9+^zJVO=pTPDL&KClJDd{0>!AcNoMZ@q1#gzA|DHZ=z1|KhlFH{^dKGOL5_9h_MTez3g?h#4wl@LA>< zoV}vcKArUAI*s0r(NLhcn(|7|r6zj9&GV(TO-<%ei5x8uq=(miU@Wcugj$*ISK%S{ jU1B20E@~q^cVu_^z!VU$IqStE=fLJSdY@{k)Dzd9I6-*;9)maF4R`|{06DdjtnKxOL{)st?q=pcGr!rLZ|?3h?Gqv3A!Be1HlsrFz?O_}@4zw0p(mOyW3USim$AkJ zn8Vx?l#IoYN%85-afl=@?JKowweT~p);5h7CO`{j>xNB$Hq4-&ux|ob;c`9Y#{}qv zJ987D3p1FT=#xYA@ev&dGIIM$al5Z~o3LzUOUZQT?gw(^@z=V?ji0shk@j*Tg~iV6 zPjlX#^aEwy#*d{Bz`_8+oS&(IKsyomj-jw_)^!fITl{B^R$ zXUb4%`=ikIy8>vk}S|~Gd=@vSQTd1>ap(pRpPvKM|bp)LMQk!aoLXZK5JtcxMt`?$} zp=5-x3tN$`Elt;aQ7ZRYMNXq5tClM{%Tgv}sfA3*EAhjvl8%Hy5 zsX}`DhNVDpGv}Htr6xHwl$l=lsLcW@k)s2GjPP~{jH4|-p-y4-oA4aRKCuzx5Ve-x bd2+ZoumA*H&PHj=C9wGmrAmo!Hw^#)aB5-H From a0f95f4e4626be73e079484fbc746d8c8a41fa00 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Sat, 15 Feb 2025 18:54:53 +0100 Subject: [PATCH 28/54] chore: fixup issue raised by unnamed class expressions --- rules/prefer-class-fields.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 99c0559cf5..3a02278a6b 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -144,8 +144,9 @@ const create = context => { node, messageId: MESSAGE_ID_SUGGESTION, data: { - propertyName: node.expression.left.property.name, - className: classBody.parent.id.name, + propertyName, + // Class expression does not have name, e.g. const a = class {} + className: classBody.parent?.id?.name ?? '', }, /** @param {import('eslint').Rule.RuleFixer} fixer From d17f310a95f53971038cba989e49d31c89bd3124 Mon Sep 17 00:00:00 2001 From: FRSgit Date: Sat, 24 May 2025 01:11:45 +0200 Subject: [PATCH 29/54] chore: post cr changes --- rules/prefer-class-fields.js | 57 +++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 3a02278a6b..bef69e3378 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -38,15 +38,15 @@ const isThisAssignmentExpression = node => { @param {import('eslint').Rule.RuleFixer} fixer */ const removeFieldAssignment = (node, sourceCode, fixer) => { - const {line} = node.loc.start; + const { line } = node.loc.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}), + sourceCode.getIndexFromLoc({ line, column: 0 }), + sourceCode.getIndexFromLoc({ line: line + 1, column: 0 }), ]) : fixer.remove(node); }; @@ -95,13 +95,13 @@ const addClassFieldDeclaration = ( @type {import('eslint').Rule.RuleModule['create']} */ const create = context => { - const {sourceCode} = context; + const { sourceCode } = context; return { ClassBody(classBody) { - const constructor = classBody.body.find(x => x.kind === 'constructor'); + const constructor = classBody.body.find(node => node.kind === 'constructor' && node.type === 'MethodDefinition'); - if (!constructor || constructor.type !== 'MethodDefinition') { + if (!constructor) { return; } @@ -114,17 +114,17 @@ const create = context => { const firstInvalidProperty = constructorBody.findIndex( node => !WHITELIST_NODES_PRECEDING_THIS_ASSIGNMENT.has(node.type), ); - const validConstructorProperties + const lastValidPropertyIndex = firstInvalidProperty === -1 - ? constructorBody - : constructorBody.slice(0, firstInvalidProperty); + ? constructorBody.length - 1 + : firstInvalidProperty - 1; for ( - let index = validConstructorProperties.length - 1; + let index = lastValidPropertyIndex; index >= 0; index-- ) { - const node = validConstructorProperties[index]; + const node = constructorBody[index]; if ( isThisAssignmentExpression(node) && node.expression.right?.type === 'Literal' @@ -142,22 +142,25 @@ const create = context => { if (alreadyExistingClassFieldDeclaration) { return { node, - messageId: MESSAGE_ID_SUGGESTION, - data: { - propertyName, - // Class expression does not have name, e.g. const a = class {} - className: classBody.parent?.id?.name ?? '', - }, - /** - @param {import('eslint').Rule.RuleFixer} fixer - */ - * suggest(fixer) { - yield removeFieldAssignment(node, sourceCode, fixer); - yield fixer.replaceText( - alreadyExistingClassFieldDeclaration, - `${propertyName} = ${propertyValue};`, - ); - }, + messageId: MESSAGE_ID_ERROR, + suggest: [{ + messageId: MESSAGE_ID_SUGGESTION, + data: { + propertyName, + // Class expression does not have name, e.g. const a = class {} + className: classBody.parent?.id?.name ?? '', + }, + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ + * fix(fixer) { + yield removeFieldAssignment(node, sourceCode, fixer); + yield fixer.replaceText( + alreadyExistingClassFieldDeclaration, + `${propertyName} = ${propertyValue};`, + ); + }, + }], }; } From 6ee11bdab59046d6d6a49ee0b5a72007d6f0868e Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Sat, 24 May 2025 02:22:15 +0200 Subject: [PATCH 30/54] chore: lint fixes --- rules/prefer-class-fields.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index bef69e3378..baa1cce233 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -38,15 +38,15 @@ const isThisAssignmentExpression = node => { @param {import('eslint').Rule.RuleFixer} fixer */ const removeFieldAssignment = (node, sourceCode, fixer) => { - const { line } = node.loc.start; + const {line} = node.loc.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 }), + sourceCode.getIndexFromLoc({line, column: 0}), + sourceCode.getIndexFromLoc({line: line + 1, column: 0}), ]) : fixer.remove(node); }; @@ -83,7 +83,8 @@ const addClassFieldDeclaration = ( sourceCode, fixer, ) => { - const classBodyStartRange = [classBody.range[0], classBody.range[0] + 1]; + const classBodyRange = sourceCode.getRange(classBody); + const classBodyStartRange = [classBodyRange[0], classBodyRange[0] + 1]; const indent = getIndentString(constructor, sourceCode); return fixer.insertTextAfterRange( classBodyStartRange, From 2ad41e6e1a0d13166bd160e37c3228f50e2968b1 Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Sat, 24 May 2025 02:24:53 +0200 Subject: [PATCH 31/54] chore: lint fix --- rules/prefer-class-fields.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index baa1cce233..aa0afbc6ed 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -38,7 +38,7 @@ const isThisAssignmentExpression = node => { @param {import('eslint').Rule.RuleFixer} fixer */ const removeFieldAssignment = (node, sourceCode, fixer) => { - const {line} = node.loc.start; + const {line} = sourceCode.getLoc(node).start; const nodeText = sourceCode.getText(node); const lineText = sourceCode.lines[line - 1]; const isOnlyNodeOnLine = lineText.trim() === nodeText; @@ -96,7 +96,7 @@ const addClassFieldDeclaration = ( @type {import('eslint').Rule.RuleModule['create']} */ const create = context => { - const { sourceCode } = context; + const {sourceCode} = context; return { ClassBody(classBody) { From 4e8f8a0aa767740e73b4a692753c49d421b1f350 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 24 May 2025 14:57:17 +0200 Subject: [PATCH 32/54] chore: add eslint docs --- docs/rules/prefer-class-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index a381a2f8d9..33800913e5 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -1,6 +1,6 @@ # Prefer class field declarations over `this` assignments in constructors -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). +πŸ’Ό 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). From 35e57d30b4c46e37fd90c5e9ec2932beac31522d Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Sat, 24 May 2025 22:40:27 +0200 Subject: [PATCH 33/54] chore: update snapshots --- test/snapshots/prefer-class-fields.js.md | 10 +++++++++- test/snapshots/prefer-class-fields.js.snap | Bin 805 -> 827 bytes 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index 6910f05b02..069f13d9aa 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -207,7 +207,15 @@ Generated by [AVA](https://avajs.dev). 2 | foo = 'test';␊ 3 | constructor() {␊ > 4 | this.foo = 'foo';␊ - | ^^^^^^^^^^^^^^^^^ Encountered same-named class field declaration and \`this\` assignment in constructor. Replace the class field declaration with the value from \`this\` assignment.␊ + | ^^^^^^^^^^^^^^^^^ 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 | foo = 'foo';␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | }␊ ` diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index 68a8e52ef35fdbf244cbd778df2c9a3f927292a3..fa08d45b7c29cc2fa62096ba0661f6508187aae9 100644 GIT binary patch literal 827 zcmV-B1H}A6RzVh)` z9w(;GI`ytgP>GO0AR(?jaf0#yJO*#T8*t{r8L>|7#A|!)m^KQQqQtxFng7iEcC!9r z99Xt?NZ+3!Gns|MEt4FpmZ`7QRyRPiR7&BlZNmv@HlfUMHD)_2tNfnzOj!IWdwtNDC19Pz-#Uz zt;#|*w;hKsuHCK$oIDCRspsY-7^(Y1bs0Oag(4nZB0Hr74SvYu*ww1o}nG9CKO{g%WEHvHl zpJ>RC{U+4q^Ne)~Y0JrOTx*cFQi5hbljD@$ksqZ5Pn@*K3|O~QTxbKuWtz&FR4XD&tX)jrOy`wTLG8O-YLh(1 z&c{L+O-UHp2bzwuBq%K^bIfZyE3Z=c|BK_!{eb`VNU!#{w|{2I`NH2ikkuyw~7~36FuQB_Dw4ie|hbaukxV$FdVxdw_KgeOZs&C`~?sU*8l4e F002J!jyV7T literal 805 zcmV+=1KRvSRzVODHMb%mgvy7Inggg? zd)$~hyXo#a1SJs?2qeU{Cr(fvfXCnscmvK{I3wiLPO`SwA3#-n%i5Xw&-{NoUVpOp zU1lDNwEdw9p#$PN)>>`-7MLy(0&X(~BQO~il6$6Pd~F>@AiIvJ+Kj;#tlEsN zZo&j6j-aG3x=f1KCzeGd*)=beE|vVtxL8^?9#;TrFkTE?0jR?m%8~dLfQG+Zj^(KU zH2pi13P20SFsYzR_R+;VwC~9v?Gq*Kjy`Q9awAzvVnTbzlk<$<7Ben=E#voOaG#@%Jcw>P=vrW*=@L$)!zi!;a@g|&?g0_PIiNkH5*)f{BiM|CkVHBr42bKY zsOm~}wmD}UYBy>jCl5kS>V-K8SL(r1UB}K##l)k3$WA#yLs0Ux_6;3ZLXm{UxQgX# zCW}v$q0;tKq3wGC+Vs6?r}joe;+e9T*oZib!;r;B3X6mvcz)t+Cc`J;8mvkba^ZF( z_(wyB>=(tCk2!r=&{mM$xYn?3na5q{Y?hu*hSdw(8VD6w(zE%YedLd}eEzWFx;{hcyBN5zL$+=xYs;w;U_S2p(( zMI1ZB(O%9+^zJVO=pTPDL&KClJDd{0>!AcNoMZ@q1#gzA|DHZ=z1|KhlFH{^dKGOL5_9h_MTez3g?h#4wl@LA>< zoV}vcKArUAI*s0r(NLhcn(|7|r6zj9&GV(TO-<%ei5x8uq=(miU@Wcugj$*ISK%S{ jU1B20E@~q^cVu_^z!VU$IqStE=fLJSdY@ Date: Sun, 25 May 2025 15:44:26 +0200 Subject: [PATCH 34/54] chore: post cr changes with finding MethodDefinition constructor --- rules/prefer-class-fields.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index aa0afbc6ed..2dfafd0cf0 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -32,6 +32,12 @@ const isThisAssignmentExpression = node => { return true; }; +/** +@param {import('estree').MethodDefinition | import('estree').PropertyDefinition | import('estree').StaticBlock} node +@returns {node is import('estree').MethodDefinition} +*/ +const isMethodDefinitionConstructor = node => node.kind === 'constructor' && node.type === 'MethodDefinition'; + /** @param {import('eslint').Rule.Node} node @param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode @@ -100,15 +106,11 @@ const create = context => { return { ClassBody(classBody) { - const constructor = classBody.body.find(node => node.kind === 'constructor' && node.type === 'MethodDefinition'); - - if (!constructor) { - return; - } - - const constructorBody = constructor.value.body?.body; + // eslint-disable-next-line unicorn/no-array-callback-reference + const constructor = classBody.body.find(isMethodDefinitionConstructor); + const constructorBody = constructor?.value?.body?.body; - if (!constructorBody) { + if (!constructor || !constructorBody) { return; } From 2950c65d2ab0046e88934af9b48d0c843d2a6bef Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 25 May 2025 22:02:34 +0800 Subject: [PATCH 35/54] Fix snapshot --- test/snapshots/prefer-class-fields.js.snap | Bin 827 -> 827 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index fa08d45b7c29cc2fa62096ba0661f6508187aae9..4880b551d8a861e2f944b7939778779d17cecc9b 100644 GIT binary patch literal 827 zcmV-B1H}A6RzV4TQP+cV1|lI)t-3O5S=Q@mc7D?T*48T#n^2 z02=>kq6Oj2VDy+G+n}pbQlHJLk|0XzI4#;iD#4-SZ3LTf5Ryp8gdTA{ z6cwFSXNz;jp}JBHIe8p%Qp?RrxKaz2YC3jaD@{DQLUzgt8Uo8h?`t})gdz#^aTUwg zNEV+cL#6GvLfg+AwCQWpOs$QE#Hq5FSco`_{gB0a3X6odJU?+Zli`^G$^pC|0gn6{kk#+V(lqg%- z5_`p4itwAo8PvX+TKidSp~S+)x6n?wg&G;-eB&|${ev<+kMj?$cq7JJ6lZBPzOuO= zDB{=@M|*iPqIdr&K>zxOA)i*SD66v{_Y<;u7_z#0!Ro_T=U-u8BDDmZ|E1E~K_N&B zeTaqE#nD0lFeM#?O_&d2&1)F`q%`HD+7ykBq&AJ@Ty!fUOYL1u-bCk>Q$_8&Txydt z#?HrzjfTcX_JL-vIHxEr6{lF&W>#Ib@c$Rbod+TRYmr$UZ13RAlJmm$CLpG-jKLR~ zWANf9I_>JDAJ=L0?v*waDDIRlsU~{Do$XC4;@?=l;>XzWS}hbqnbno*I&4vsc~l}t z3k2!lvd_MywMM9w>8t`zu7^sv!w-h$!RD+Jk6f9`C4IDg{sIsT*8l4e F003W{g-!qf literal 827 zcmV-B1H}A6RzVh)` z9w(;GI`ytgP>GO0AR(?jaf0#yJO*#T8*t{r8L>|7#A|!)m^KQQqQtxFng7iEcC!9r z99Xt?NZ+3!Gns|MEt4FpmZ`7QRyRPiR7&BlZNmv@HlfUMHD)_2tNfnzOj!IWdwtNDC19Pz-#Uz zt;#|*w;hKsuHCK$oIDCRspsY-7^(Y1bs0Oag(4nZB0Hr74SvYu*ww1o}nG9CKO{g%WEHvHl zpJ>RC{U+4q^Ne)~Y0JrOTx*cFQi5hbljD@$ksqZ5Pn@*K3|O~QTxbKuWtz&FR4XD&tX)jrOy`wTLG8O-YLh(1 z&c{L+O-UHp2bzwuBq%K^bIfZyE3Z=c|BK_!{eb`VNU!#{w|{2I`NH2ikkuyw~7~36FuQB_Dw4ie|hbaukxV$FdVxdw_KgeOZs&C`~?sU*8l4e F002J!jyV7T From 37a216e41502f087cf8427fed2d84467fc5d4015 Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 25 May 2025 22:09:24 +0800 Subject: [PATCH 36/54] Simplify logic --- rules/prefer-class-fields.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 2dfafd0cf0..f9e4d47c7f 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -32,12 +32,6 @@ const isThisAssignmentExpression = node => { return true; }; -/** -@param {import('estree').MethodDefinition | import('estree').PropertyDefinition | import('estree').StaticBlock} node -@returns {node is import('estree').MethodDefinition} -*/ -const isMethodDefinitionConstructor = node => node.kind === 'constructor' && node.type === 'MethodDefinition'; - /** @param {import('eslint').Rule.Node} node @param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode @@ -106,14 +100,20 @@ const create = context => { return { ClassBody(classBody) { - // eslint-disable-next-line unicorn/no-array-callback-reference - const constructor = classBody.body.find(isMethodDefinitionConstructor); - const constructorBody = constructor?.value?.body?.body; + const constructor = classBody.body.find(node => + node.kind === 'constructor' + && !node.computed + && !node.static + && node.type === 'MethodDefinition' + && node.value.type === 'FunctionExpression' + ); - if (!constructor || !constructorBody) { + if (!constructor) { return; } + const constructorBody = constructor.value.body.body; + const firstInvalidProperty = constructorBody.findIndex( node => !WHITELIST_NODES_PRECEDING_THIS_ASSIGNMENT.has(node.type), ); From fb6b239f4baaa3a856f3a6d0f178008d679f3ac4 Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 25 May 2025 22:28:49 +0800 Subject: [PATCH 37/54] Fix logic --- rules/prefer-class-fields.js | 120 +++++++++------------ test/snapshots/prefer-class-fields.js.md | 2 +- test/snapshots/prefer-class-fields.js.snap | Bin 827 -> 826 bytes 3 files changed, 49 insertions(+), 73 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index f9e4d47c7f..5493f17c88 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -11,27 +11,6 @@ const messages = { const WHITELIST_NODES_PRECEDING_THIS_ASSIGNMENT = new Set(['EmptyStatement', 'ExpressionStatement']); -/** -@param {import('eslint').Rule.Node} node -@returns {node is import('estree').ExpressionStatement & {expression: import('estree').AssignmentExpression & {left: import('estree').MemberExpression & {object: import('estree').ThisExpression}}}} -*/ -const isThisAssignmentExpression = node => { - if ( - node.type !== 'ExpressionStatement' - || node.expression.type !== 'AssignmentExpression' - ) { - return false; - } - - const lhs = node.expression.left; - - if (!lhs.object || lhs.object.type !== 'ThisExpression') { - return false; - } - - return true; -}; - /** @param {import('eslint').Rule.Node} node @param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode @@ -51,22 +30,6 @@ const removeFieldAssignment = (node, sourceCode, fixer) => { : fixer.remove(node); }; -/** -@param {string} propertyName -@param {import('estree').ClassBody} classBody -*/ -const findClassFieldNamed = (propertyName, classBody) => { - for (const classBodyChild of classBody.body) { - if ( - classBodyChild.type === 'PropertyDefinition' - && classBodyChild.key.type === 'Identifier' - && classBodyChild.key.name === propertyName - ) { - return classBodyChild; - } - } -}; - /** @param {string} propertyName @param {string} propertyValue @@ -129,52 +92,63 @@ const create = context => { ) { const node = constructorBody[index]; if ( - isThisAssignmentExpression(node) - && node.expression.right?.type === 'Literal' + node.type === 'ExpressionStatement' + && node.expression.type === 'AssignmentExpression' && node.expression.operator === '=' - && node.expression.left.property.type === 'Identifier' + && node.expression.left.type === 'MemberExpression' + && node.expression.left.object.type === 'ThisExpression' && !node.expression.left.computed + && node.expression.left.property.type === 'Identifier' + && node.expression.right.type === 'Literal' ) { const propertyName = node.expression.left.property.name; const propertyValue = node.expression.right.raw; - const alreadyExistingClassFieldDeclaration = findClassFieldNamed( - propertyName, - classBody, + const existingProperty = classBody.body.find(node => + node.type === 'PropertyDefinition' + && !node.computed + && !node.static + && node.key.type === 'Identifier' + && node.key.name === propertyName ); - if (alreadyExistingClassFieldDeclaration) { - return { - node, - messageId: MESSAGE_ID_ERROR, - suggest: [{ - messageId: MESSAGE_ID_SUGGESTION, - data: { - propertyName, - // Class expression does not have name, e.g. const a = class {} - className: classBody.parent?.id?.name ?? '', - }, - /** - @param {import('eslint').Rule.RuleFixer} fixer - */ - * fix(fixer) { - yield removeFieldAssignment(node, sourceCode, fixer); - yield fixer.replaceText( - alreadyExistingClassFieldDeclaration, - `${propertyName} = ${propertyValue};`, - ); - }, - }], - }; - } - - return { + const problem = { node, messageId: MESSAGE_ID_ERROR, + }; + if (existingProperty) { + if (existingProperty.value) { + problem.suggest = [ + { + messageId: MESSAGE_ID_SUGGESTION, + data: { + propertyName, + // Class expression does not have name, e.g. const a = class {} + className: classBody.parent?.id?.name ?? '', + }, + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ + * fix(fixer) { + yield removeFieldAssignment(node, sourceCode, fixer); + fixer.replaceText(existingProperty.value, propertyValue); + } + } + ] + } else { + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ + problem.fix = function * (fixer) { + yield removeFieldAssignment(node, sourceCode, fixer); + yield fixer.insertTextAfter(existingProperty.key, ` = ${propertyValue}`); + } + } + } else { /** @param {import('eslint').Rule.RuleFixer} fixer */ - * fix(fixer) { + problem.fix = function * (fixer) { yield removeFieldAssignment(node, sourceCode, fixer); yield addClassFieldDeclaration( propertyName, @@ -184,8 +158,10 @@ const create = context => { sourceCode, fixer, ); - }, - }; + } + } + + return problem; } } }, diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index 069f13d9aa..f44cca1310 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -214,7 +214,7 @@ Generated by [AVA](https://avajs.dev). --------------------------------------------------------------------------------␊ 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 | foo = 'foo';␊ + 2 | foo = 'test';␊ 3 | constructor() {␊ 4 | }␊ 5 | }␊ diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index 4880b551d8a861e2f944b7939778779d17cecc9b..97a3a5e532607b8c00c4cd420b8ab7471453438d 100644 GIT binary patch delta 802 zcmV+-1Ks?)2D%1+K~_N^Q*L2!b7*gLAa*kf0|2r-Kk~wW`7(Bx7XLC+EtcZ^3gn!O zo+fK*XzD*{rSu<*2mk;800003&6m$^(?Af$lOTlRTO}S~R1R&0nks)VU+e>ynTO*2IZ8*m*xPjIF>xJhvAVPlrb~o?dyK&en2ZX^JySBixCAF4+m5K% zjKL->*o-Y~Kp*;!prk9>Op3SXmPI7lHLn$J6#S=ny)ajNY5-K>bUd&DP=iw_N8%d* zb$_`W%VPk4H2gPx17HLo|4$#H>wCl+r?K36qwmxknay?l}VnTb{lcS73#xs__ z7bbVwOmPZ>H_xLFJmP3052D)+x)xYyx`Y$yFbeD#IqdrZ_kjrEv}gyZ1c#2d5p2dm zNFp5*j*07`sOYRZTbwfv)sWfdT5e9lm0GZWRMWBZT500Z6|z%K&=6Q2cwf_T zB@{`RkE>X|MzZ)s87ghR721C0piN(!W@>FTBu3NcWXvG^b z-l8~5qw$r^eP0pBra0QmlM%i9M*;fRKMeV_dPP~C{kWfy)x(h0)gh}7UtN5KJ&Dv3 zaPgN)ZwG}SE%YH4UKd9T0l<`W5H?{xh&8W&Vfd5Ml#gmtG&+*nG?FvyRz#NCyO_L* z&MRk%+IP9sCS{DBj};pYjg9OB&0cX%QCccav98Umx@zJ7FOEA8LjKnxvpU$`!I>rJ zh3!p1OivkuFEYpA@FzO$>ZBjnY4q-u?kZ3$moBL$dcvLUO)KKxSia)N*zsB|6a$%) g`T{2zgAWGg!RD+Jk6gJL)bv-^zl$`54D1j909xpPr~m)} delta 803 zcmV+;1Kj+&2D=7-K~_N^Q*L2!b7*gLAa*kf0|1InYW;cb-K=hyfP9SI;|#Jh3yFdF zVlr6p>kLAKB84A|2mk;800003&6m$^(?Af$lOTlRTO}S~R1R$gO_e__Qmf`xB!p0b z3aLGSy0yoNsk58zu0v28A%Q?bTzldKVU+e>ynTO*28A?aG*xz($kGPJtSY28N(J@ zy1!hGQf$x+51;~7ie z3zIu-rZ|P+o9EF79&xmh2hnW@T?;HUUBZcU7zNft4*Pz5}}c^q<5%gsr+QVW)UYC3jaD@{DQLUzgt8Uo8h?`t}) zgdz#^aTUwgNEV+cL#6GvLfg+AwCQWpOs$QE#Hq5FSco`_{gB0a3X6odJU?+Zli`^G$^pC|0gn6{kk#+VFvy>=X*%EujTZ-_T#TnGTnOgfX1O0x zod2cL+d&~n3w?-%*TvC705ByTgiV+aV$ExR82+R*<)hjZjgF)?jpST(DC@mGISl4D&UA6H47ss6kA^&TUSsiTe;LMWq z!uBR0rmu{_7nx)5;wL)o>ZBjnY4q-uHWVoClrE_zdcvLUO)KKxSia)N*zsB|6hoPl h`T{2!!w-h$!RD+Jk6f9`C4IDg{sIsT*8l4e001>pcl-bV From ab1362d4fddf08da8d74a653d8178fca3501cf83 Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 25 May 2025 22:36:46 +0800 Subject: [PATCH 38/54] Rewrite tests --- test/prefer-class-fields.js | 71 +++-- test/snapshots/prefer-class-fields.js.md | 329 ++++++++++++++++----- test/snapshots/prefer-class-fields.js.snap | Bin 826 -> 878 bytes 3 files changed, 291 insertions(+), 109 deletions(-) diff --git a/test/prefer-class-fields.js b/test/prefer-class-fields.js index f48a2e8f57..351097e589 100644 --- a/test/prefer-class-fields.js +++ b/test/prefer-class-fields.js @@ -7,101 +7,110 @@ const MESSAGE_ID = 'prefer-class-fields/error'; test.snapshot({ valid: [ + '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 { - foo = 'foo'; - } - `, - outdent` - class MyError extends Error { - name = "MyError"; + constructor() { + if (something) { return; } + this.bar = 1; + } } `, + ], + invalid: [ outdent` class Foo { constructor() { - this.foo += 'foo'; + this.bar = 1; } } `, outdent` class Foo { constructor() { - this.foo ??= 'foo'; + ; + this.bar = 1; } } `, outdent` class Foo { constructor() { - this[foo] = 'foo'; + this.bar = 1; + this.baz = 2; } } `, outdent` class Foo { - something = 'a'; + bar; constructor() { - this[this.something] = 'foo'; + this.bar = 1; } } `, outdent` class Foo { + bar = 0; constructor() { - if (something) { return; } - this.elo = 'foo'; + this.bar = 1; } } `, - ], - invalid: [ outdent` class Foo { + [bar]; constructor() { - this.foo = 'foo'; + this.bar = 1; } } `, outdent` class Foo { + [bar] = 0; constructor() { - this.foo = 'foo'; - this.foo2 = 'foo2'; + this.bar = 1; } } `, outdent` class Foo { - constructor(argument) { - this.foo = 'foo'; - this.foo2 = argument + 'test'; - this.foo3 = 'foo3'; + static bar; + constructor() { + this.bar = 1; } } `, outdent` class Foo { + static bar = 0; constructor() { - ; - this.foo = 'foo'; - this.foo2 = 'foo2'; + this.bar = 1; } } `, outdent` - class MyError extends Error { - constructor(message) { - super(message); - this.name = "MyError"; + class Foo { + static [bar]; + constructor() { + this.bar = 1; } } `, outdent` class Foo { - foo = 'test'; + static [bar] = 1; constructor() { - this.foo = 'foo'; + this.bar = 1; } } `, diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index f44cca1310..c50c926e5a 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -4,14 +4,14 @@ The actual snapshot is saved in `prefer-class-fields.js.snap`. Generated by [AVA](https://avajs.dev). -## invalid(1): class Foo { constructor() { this.foo = 'foo'; } } +## invalid(1): class Foo { constructor() { this.bar = 1; } } > Input `␊ 1 | class Foo {␊ 2 | constructor() {␊ - 3 | this.foo = 'foo';␊ + 3 | this.bar = 1;␊ 4 | }␊ 5 | }␊ ` @@ -20,7 +20,7 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | foo = 'foo';␊ + 2 | bar = 1;␊ 3 | constructor() {␊ 4 | }␊ 5 | }␊ @@ -31,21 +31,21 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ 2 | constructor() {␊ - > 3 | this.foo = 'foo';␊ - | ^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + > 3 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ 4 | }␊ 5 | }␊ ` -## invalid(2): class Foo { constructor() { this.foo = 'foo'; this.foo2 = 'foo2'; } } +## invalid(2): class Foo { constructor() { ; this.bar = 1; } } > Input `␊ 1 | class Foo {␊ 2 | constructor() {␊ - 3 | this.foo = 'foo';␊ - 4 | this.foo2 = 'foo2';␊ + 3 | ;␊ + 4 | this.bar = 1;␊ 5 | }␊ 6 | }␊ ` @@ -54,8 +54,44 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | foo = 'foo';␊ - 3 | foo2 = 'foo2';␊ + 2 | bar = 1;␊ + 3 | constructor() {␊ + 4 | ;␊ + 5 | }␊ + 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 | bar = 1;␊ + 3 | baz = 2;␊ 4 | constructor() {␊ 5 | }␊ 6 | }␊ @@ -66,100 +102,126 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ 2 | constructor() {␊ - 3 | this.foo = 'foo';␊ - > 4 | this.foo2 = 'foo2';␊ - | ^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 3 | this.bar = 1;␊ + > 4 | this.baz = 2;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ 5 | }␊ 6 | }␊ ` -## invalid(3): class Foo { constructor(argument) { this.foo = 'foo'; this.foo2 = argument + 'test'; this.foo3 = 'foo3'; } } +## invalid(4): class Foo { bar; constructor() { this.bar = 1; } } > Input `␊ 1 | class Foo {␊ - 2 | constructor(argument) {␊ - 3 | this.foo = 'foo';␊ - 4 | this.foo2 = argument + 'test';␊ - 5 | this.foo3 = 'foo3';␊ - 6 | }␊ - 7 | }␊ + 2 | bar;␊ + 3 | constructor() {␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ ` > Output `␊ 1 | class Foo {␊ - 2 | foo = 'foo';␊ - 3 | foo3 = 'foo3';␊ - 4 | constructor(argument) {␊ - 5 | this.foo2 = argument + 'test';␊ - 6 | }␊ - 7 | }␊ + 2 | bar = 1;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | }␊ ` > Error 1/1 `␊ 1 | class Foo {␊ - 2 | constructor(argument) {␊ - 3 | this.foo = 'foo';␊ - 4 | this.foo2 = argument + 'test';␊ - > 5 | this.foo3 = 'foo3';␊ - | ^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ - 6 | }␊ - 7 | }␊ + 2 | bar;␊ + 3 | constructor() {␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ ` -## invalid(4): class Foo { constructor() { ; this.foo = 'foo'; this.foo2 = 'foo2'; } } +## invalid(5): class Foo { bar = 1; constructor() { this.bar = 1; } } > Input `␊ 1 | class Foo {␊ - 2 | constructor() {␊ - 3 | ;␊ - 4 | this.foo = 'foo';␊ - 5 | this.foo2 = 'foo2';␊ - 6 | }␊ - 7 | }␊ + 2 | bar = 1;␊ + 3 | constructor() {␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | class Foo {␊ + 2 | bar = 1;␊ + 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(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 | foo = 'foo';␊ - 3 | foo2 = 'foo2';␊ + 2 | bar = 1;␊ + 3 | [bar];␊ 4 | constructor() {␊ - 5 | ;␊ - 6 | }␊ - 7 | }␊ + 5 | }␊ + 6 | }␊ ` > Error 1/1 `␊ 1 | class Foo {␊ - 2 | constructor() {␊ - 3 | ;␊ - 4 | this.foo = 'foo';␊ - > 5 | this.foo2 = 'foo2';␊ - | ^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ - 6 | }␊ - 7 | }␊ + 2 | [bar];␊ + 3 | constructor() {␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 5 | }␊ + 6 | }␊ ` -## invalid(5): class MyError extends Error { constructor(message) { super(message); this.name = "MyError"; } } +## invalid(7): class Foo { [bar] = 1; constructor() { this.bar = 1; } } > Input `␊ - 1 | class MyError extends Error {␊ - 2 | constructor(message) {␊ - 3 | super(message);␊ - 4 | this.name = "MyError";␊ + 1 | class Foo {␊ + 2 | [bar] = 1;␊ + 3 | constructor() {␊ + 4 | this.bar = 1;␊ 5 | }␊ 6 | }␊ ` @@ -167,10 +229,10 @@ Generated by [AVA](https://avajs.dev). > Output `␊ - 1 | class MyError extends Error {␊ - 2 | name = "MyError";␊ - 3 | constructor(message) {␊ - 4 | super(message);␊ + 1 | class Foo {␊ + 2 | bar = 1;␊ + 3 | [bar] = 1;␊ + 4 | constructor() {␊ 5 | }␊ 6 | }␊ ` @@ -178,24 +240,35 @@ Generated by [AVA](https://avajs.dev). > Error 1/1 `␊ - 1 | class MyError extends Error {␊ - 2 | constructor(message) {␊ - 3 | super(message);␊ - > 4 | this.name = "MyError";␊ - | ^^^^^^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + 1 | class Foo {␊ + 2 | [bar] = 1;␊ + 3 | constructor() {␊ + > 4 | this.bar = 1;␊ + | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ 5 | }␊ 6 | }␊ ` -## invalid(6): class Foo { foo = 'test'; constructor() { this.foo = 'foo'; } } +## invalid(8): class Foo { static bar; constructor() { this.bar = 1; } } > Input `␊ 1 | class Foo {␊ - 2 | foo = 'test';␊ + 2 | static bar;␊ 3 | constructor() {␊ - 4 | this.foo = 'foo';␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | bar = 1;␊ + 3 | static bar;␊ + 4 | constructor() {␊ 5 | }␊ 6 | }␊ ` @@ -204,18 +277,118 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | foo = 'test';␊ + 2 | static bar;␊ 3 | constructor() {␊ - > 4 | this.foo = 'foo';␊ - | ^^^^^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ + > 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.␊ + ` + +## invalid(9): class Foo { static bar = 1; constructor() { this.bar = 1; } } + +> Input + + `␊ 1 | class Foo {␊ - 2 | foo = 'test';␊ + 2 | static bar = 1;␊ 3 | constructor() {␊ - 4 | }␊ - 5 | }␊ + 4 | this.bar = 1;␊ + 5 | }␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | class Foo {␊ + 2 | bar = 1;␊ + 3 | static bar = 1;␊ + 4 | constructor() {␊ + 5 | }␊ + 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(10): 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 | bar = 1;␊ + 3 | static [bar];␊ + 4 | constructor() {␊ + 5 | }␊ + 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(11): 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 | bar = 1;␊ + 3 | static [bar] = 1;␊ + 4 | constructor() {␊ + 5 | }␊ + 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 | }␊ ` diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index 97a3a5e532607b8c00c4cd420b8ab7471453438d..6da2bdc64dc1a620be62409c8b89f1d70df2ab99 100644 GIT binary patch literal 878 zcmV-!1CjheRzVE)xK#GL7r@$$(+kHp0Tr;^J5P*03G(iK{bGB*n@0GYz<(h@j8ogXaKW~ zw{;C*4)&m~QHK<%Ls#6YFIp*AQOY~=l%c3Iy-Vns%(~}`qii3;*~Z^y)D;#a3ZeOS zBmBU9MlH%(Q7xM~CYaQ42qVJwT<~g?!B!*C77)(ul3Ss!0NV|Z3@ncU7ev?w)QIC# zo|8$mml^XI=u>*YL_T0*I);h73KcdQ(OPmU+9Y&!Z!XX?FXO{N$shO4M^>5^lX8Ga zd6$5cBh>|o>f>dmr`T?0ZUxNDB=V8guNSZc(-MAsG{Vw=TqTP#E3u>uawkinEVI1} zRUqmQBI;`jqHrGyl0KYBx(WeZvmJC53CpLssj}wi>3FZL?+Bgu5$Hsz z9Vt7y9so1f4a^}H37~n|>Z6CIM*6=@5`(ikei6;`!g%^3PUJkbO9OtiM}Db9d9zG? zN?(L!*YJE-P)1GQq(U#bq(V)pG~$}E%5w0KRvltc5M?Tj^xPKZ<{z5k!D3#eJ92K| zR?vMmt_5dfzT<4H6$x8UWv3N%GB)KR4pR)?P)n$QRrM~m3rc@8|3F9bLxjCXt3Qzk zQU=kD0HQ0sAWCep0Z7l#9F3S(4##8@EeG8&p*1rVP(Kk+pW=WTJ(rh3wH!cowF9bF zz_i;QLjyJC{S4(9JXQ{>VnZD(Tvd9mej%-zd3RY^%s-dx#Rhl{^ z->RZYifI7r-@N`J$i68-c6uD_cKdVc<=&*F-?0Rmt`*zYVXay&s$(lLWdqo_ej~=d z#4~n^7O%T)!aBd$hqb15vmG?ur)r%YlAkGG+fk(T?`Z!JZ3l|9jlQS-2V(@GpDY>x E0G<|+u>b%7 literal 826 zcmV-A1I7G7RzVOX0v^dE}|00000000Bcm(Om~KoG{0AcW#uB_3c@4sC^+Dt}s}mgZI@giwJBsXc(Y zwa1C6vzzX&OHdmjfj~lBd*TG;0eB4FfH&aGg)>4FN4nVCbm%d09c!_=v<{|Agn)aD!3mg*3ducFGQPM3Cm`F7sMw6bCM?*D zEo?v^`i`KaE80wox965cB-u5u6>b##r+B?ESA1#!RN-_yumMnmQz%E`8vu2Gxg5)5 z05tqJeFI<>PN8q0OAgS*`?TxHAnh|H?Y2H`Byv4jN@7BL+moY=KgKhbz85BU+Dvf@ zgE!Bk4?N;%BM+k64!RatXu5X| zMzZ)s87ghR721C0piN(!W@>FTBuK!;sb0A*&BxU3`T-iPREs@s~<(2ZbOl z^dS~r7e@;Lz?5_lHeo)9HLqd#lhTxrYEv{ilG-$qGwfDGmfE|Ryot^$XNuZ)xzr|Q zjGd1a8x4((>;uhSaZXWMDo(Mk&8)g=;r}m=I}bws*CMkz*xtdJCFh0hO+ZXf8G|n} z$KdcMI_>JDAJ=L0?v?H;P%M`&sU~{Do$XC4;@?=l;>XzWS}ha8t`zu7^svgAWGg!RD+Jk6gJL)bv-^zl$`54D1j9 E0G?ls`v3p{ From 84a9c82123b3352af6a95141d1c141b3ff767bb1 Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 25 May 2025 22:52:32 +0800 Subject: [PATCH 39/54] Safer --- rules/prefer-class-fields.js | 150 ++++++++++----------- test/prefer-class-fields.js | 12 +- test/snapshots/prefer-class-fields.js.md | 138 ++++++++++++------- test/snapshots/prefer-class-fields.js.snap | Bin 878 -> 912 bytes 4 files changed, 164 insertions(+), 136 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 5493f17c88..63a0291158 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -46,12 +46,11 @@ const addClassFieldDeclaration = ( sourceCode, fixer, ) => { - const classBodyRange = sourceCode.getRange(classBody); - const classBodyStartRange = [classBodyRange[0], classBodyRange[0] + 1]; + const closingBrace = sourceCode.getLastToken(classBody); const indent = getIndentString(constructor, sourceCode); - return fixer.insertTextAfterRange( - classBodyStartRange, - `\n${indent}${propertyName} = ${propertyValue};`, + return fixer.insertTextBefore( + closingBrace, + `${indent}${propertyName} = ${propertyValue};\n`, ); }; @@ -75,95 +74,82 @@ const create = context => { return; } - const constructorBody = constructor.value.body.body; + 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 + && node.expression.left.property.type === 'Identifier' + && node.expression.right.type === 'Literal' + )) { + return + } - const firstInvalidProperty = constructorBody.findIndex( - node => !WHITELIST_NODES_PRECEDING_THIS_ASSIGNMENT.has(node.type), + const propertyName = node.expression.left.property.name; + const propertyValue = node.expression.right.raw; + const existingProperty = classBody.body.find(node => + node.type === 'PropertyDefinition' + && !node.computed + && !node.static + && node.key.type === 'Identifier' + && node.key.name === propertyName ); - const lastValidPropertyIndex - = firstInvalidProperty === -1 - ? constructorBody.length - 1 - : firstInvalidProperty - 1; - - for ( - let index = lastValidPropertyIndex; - index >= 0; - index-- - ) { - const node = constructorBody[index]; - 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 - && node.expression.left.property.type === 'Identifier' - && node.expression.right.type === 'Literal' - ) { - const propertyName = node.expression.left.property.name; - const propertyValue = node.expression.right.raw; - const existingProperty = classBody.body.find(node => - node.type === 'PropertyDefinition' - && !node.computed - && !node.static - && node.key.type === 'Identifier' - && node.key.name === propertyName - ); - const problem = { - node, - messageId: MESSAGE_ID_ERROR, - }; - - if (existingProperty) { - if (existingProperty.value) { - problem.suggest = [ - { - messageId: MESSAGE_ID_SUGGESTION, - data: { - propertyName, - // Class expression does not have name, e.g. const a = class {} - className: classBody.parent?.id?.name ?? '', - }, - /** - @param {import('eslint').Rule.RuleFixer} fixer - */ - * fix(fixer) { - yield removeFieldAssignment(node, sourceCode, fixer); - fixer.replaceText(existingProperty.value, propertyValue); - } - } - ] - } else { + const problem = { + node, + messageId: MESSAGE_ID_ERROR, + }; + + if (existingProperty) { + if (existingProperty.value) { + problem.suggest = [ + { + messageId: MESSAGE_ID_SUGGESTION, + data: { + propertyName, + // Class expression does not have name, e.g. const a = class {} + className: classBody.parent?.id?.name ?? '', + }, /** @param {import('eslint').Rule.RuleFixer} fixer */ - problem.fix = function * (fixer) { + * fix(fixer) { yield removeFieldAssignment(node, sourceCode, fixer); - yield fixer.insertTextAfter(existingProperty.key, ` = ${propertyValue}`); + fixer.replaceText(existingProperty.value, propertyValue); } } - } else { - /** - @param {import('eslint').Rule.RuleFixer} fixer - */ - problem.fix = function * (fixer) { - yield removeFieldAssignment(node, sourceCode, fixer); - yield addClassFieldDeclaration( - propertyName, - propertyValue, - classBody, - constructor, - sourceCode, - fixer, - ); - } + ] + } else { + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ + problem.fix = function * (fixer) { + yield removeFieldAssignment(node, sourceCode, fixer); + yield fixer.insertTextAfter(existingProperty.key, ` = ${propertyValue}`); } - - return problem; + } + } else { + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ + problem.fix = function * (fixer) { + yield removeFieldAssignment(node, sourceCode, fixer); + yield addClassFieldDeclaration( + propertyName, + propertyValue, + classBody, + constructor, + sourceCode, + fixer, + ); } } + + return problem; }, }; }; diff --git a/test/prefer-class-fields.js b/test/prefer-class-fields.js index 351097e589..367a4cc644 100644 --- a/test/prefer-class-fields.js +++ b/test/prefer-class-fields.js @@ -50,6 +50,14 @@ test.snapshot({ } } `, + outdent` + class Foo { + constructor() { + this.bar = 1; + this.bar = 2; + } + } + `, outdent` class Foo { bar; @@ -135,7 +143,6 @@ test.typescript({ code: outdent` class MyError extends Error { constructor(message: string) { - super(message); this.name = "MyError"; } } @@ -143,10 +150,9 @@ test.typescript({ errors: [{messageId: MESSAGE_ID}], output: outdent` class MyError extends Error { - name = "MyError"; constructor(message: string) { - super(message); } + name = "MyError"; } `, }, diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index c50c926e5a..8cda694297 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -20,9 +20,9 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | bar = 1;␊ - 3 | constructor() {␊ - 4 | }␊ + 2 | constructor() {␊ + 3 | }␊ + 4 | bar = 1;␊ 5 | }␊ ` @@ -54,10 +54,10 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | bar = 1;␊ - 3 | constructor() {␊ - 4 | ;␊ - 5 | }␊ + 2 | constructor() {␊ + 3 | ;␊ + 4 | }␊ + 5 | bar = 1;␊ 6 | }␊ ` @@ -90,26 +90,62 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | bar = 1;␊ - 3 | baz = 2;␊ - 4 | constructor() {␊ - 5 | }␊ + 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.baz = 2;␊ + 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(4): class Foo { bar; constructor() { this.bar = 1; } } +## invalid(5): class Foo { bar; constructor() { this.bar = 1; } } > Input @@ -144,13 +180,13 @@ Generated by [AVA](https://avajs.dev). 6 | }␊ ` -## invalid(5): class Foo { bar = 1; constructor() { this.bar = 1; } } +## invalid(6): class Foo { bar = 0; constructor() { this.bar = 1; } } > Input `␊ 1 | class Foo {␊ - 2 | bar = 1;␊ + 2 | bar = 0;␊ 3 | constructor() {␊ 4 | this.bar = 1;␊ 5 | }␊ @@ -161,7 +197,7 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | bar = 1;␊ + 2 | bar = 0;␊ 3 | constructor() {␊ > 4 | this.bar = 1;␊ | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ @@ -171,13 +207,13 @@ Generated by [AVA](https://avajs.dev). --------------------------------------------------------------------------------␊ 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;␊ + 2 | bar = 0;␊ 3 | constructor() {␊ 4 | }␊ 5 | }␊ ` -## invalid(6): class Foo { [bar]; constructor() { this.bar = 1; } } +## invalid(7): class Foo { [bar]; constructor() { this.bar = 1; } } > Input @@ -194,10 +230,10 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | bar = 1;␊ - 3 | [bar];␊ - 4 | constructor() {␊ - 5 | }␊ + 2 | [bar];␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ 6 | }␊ ` @@ -213,13 +249,13 @@ Generated by [AVA](https://avajs.dev). 6 | }␊ ` -## invalid(7): class Foo { [bar] = 1; constructor() { this.bar = 1; } } +## invalid(8): class Foo { [bar] = 0; constructor() { this.bar = 1; } } > Input `␊ 1 | class Foo {␊ - 2 | [bar] = 1;␊ + 2 | [bar] = 0;␊ 3 | constructor() {␊ 4 | this.bar = 1;␊ 5 | }␊ @@ -230,10 +266,10 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | bar = 1;␊ - 3 | [bar] = 1;␊ - 4 | constructor() {␊ - 5 | }␊ + 2 | [bar] = 0;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ 6 | }␊ ` @@ -241,7 +277,7 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | [bar] = 1;␊ + 2 | [bar] = 0;␊ 3 | constructor() {␊ > 4 | this.bar = 1;␊ | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ @@ -249,7 +285,7 @@ Generated by [AVA](https://avajs.dev). 6 | }␊ ` -## invalid(8): class Foo { static bar; constructor() { this.bar = 1; } } +## invalid(9): class Foo { static bar; constructor() { this.bar = 1; } } > Input @@ -266,10 +302,10 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | bar = 1;␊ - 3 | static bar;␊ - 4 | constructor() {␊ - 5 | }␊ + 2 | static bar;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ 6 | }␊ ` @@ -285,13 +321,13 @@ Generated by [AVA](https://avajs.dev). 6 | }␊ ` -## invalid(9): class Foo { static bar = 1; constructor() { this.bar = 1; } } +## invalid(10): class Foo { static bar = 0; constructor() { this.bar = 1; } } > Input `␊ 1 | class Foo {␊ - 2 | static bar = 1;␊ + 2 | static bar = 0;␊ 3 | constructor() {␊ 4 | this.bar = 1;␊ 5 | }␊ @@ -302,10 +338,10 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | bar = 1;␊ - 3 | static bar = 1;␊ - 4 | constructor() {␊ - 5 | }␊ + 2 | static bar = 0;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ 6 | }␊ ` @@ -313,7 +349,7 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | static bar = 1;␊ + 2 | static bar = 0;␊ 3 | constructor() {␊ > 4 | this.bar = 1;␊ | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ @@ -321,7 +357,7 @@ Generated by [AVA](https://avajs.dev). 6 | }␊ ` -## invalid(10): class Foo { static [bar]; constructor() { this.bar = 1; } } +## invalid(11): class Foo { static [bar]; constructor() { this.bar = 1; } } > Input @@ -338,10 +374,10 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | bar = 1;␊ - 3 | static [bar];␊ - 4 | constructor() {␊ - 5 | }␊ + 2 | static [bar];␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ 6 | }␊ ` @@ -357,7 +393,7 @@ Generated by [AVA](https://avajs.dev). 6 | }␊ ` -## invalid(11): class Foo { static [bar] = 1; constructor() { this.bar = 1; } } +## invalid(12): class Foo { static [bar] = 1; constructor() { this.bar = 1; } } > Input @@ -374,10 +410,10 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ - 2 | bar = 1;␊ - 3 | static [bar] = 1;␊ - 4 | constructor() {␊ - 5 | }␊ + 2 | static [bar] = 1;␊ + 3 | constructor() {␊ + 4 | }␊ + 5 | bar = 1;␊ 6 | }␊ ` diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index 6da2bdc64dc1a620be62409c8b89f1d70df2ab99..d6c767b767d4508a7b6e54b71fe7fbda0f142c6e 100644 GIT binary patch literal 912 zcmV;B18@96RzV+G?p7V{zSz@s!pWnJQ@Jy_)-`fEcXs5S0-(ZfFsK4hgk8voVk-dCt=oB|p#aRZ z?lu*GS=fc9LM>9J7QJEDJ>HRAL6Wx;OE!I#fND_8xa09dwf8}_(bqY7xg|k{(7wAK zyx=aQ24$V7hDj|QbgEf|5pFs*I1S2RvlVC)i0hgayGCserX6e<7!CuM6K-nIAeKkn zf~cP6GGh({byD@2DEUkjW0)w(P+_7LttO|UOF~ceX8nBTM0{vS{9zyBe%XSg%n3=^ zN2I(-K+2);k|67U%gm8A?q%kh&&+h9Inw#`3>Kj%;KxHPtYkD*@<>^VrG7yc&6<3` zg_x*ch^WuQIeX-)9zoYk8oJnjql?MYWZG~=%eLltHm8j0;F21hut|;T z!qkYZ$7WW5`?PKmjRLPyVWg)fueQI?P96;A)OsTq`i3h0C&VTHY+MMPjny(?YpKR* z4V{b)aS{g-*$>nTGO*I8fn-nSZ|F$Aix{ub;>YqpiXgh;Lv-;dh!T%jAEYO!k6KJA z2Qy^lmjh`N$$5P=R%``zCctNLK2Z)j)l%%2ahggR735l(TY@jI!T|vM*AUotzPNy-Qm7 zrF|!2`X!5_?i#Um9n`L6BOjaD_(;asPsG@#c*ai9=5@PESokl1V6F!orJ!sZb@#Dc mWo6z`q#!Nnb6eU=GTQzi+V&-Bi}W!R#Qg=NN>#6s8~^|umbx|o literal 878 zcmV-!1CjheRzVE)xK#GL7r@$$(+kHp0Tr;^J5P*03G(iK{bGB*n@0GYz<(h@j8ogXaKW~ zw{;C*4)&m~QHK<%Ls#6YFIp*AQOY~=l%c3Iy-Vns%(~}`qii3;*~Z^y)D;#a3ZeOS zBmBU9MlH%(Q7xM~CYaQ42qVJwT<~g?!B!*C77)(ul3Ss!0NV|Z3@ncU7ev?w)QIC# zo|8$mml^XI=u>*YL_T0*I);h73KcdQ(OPmU+9Y&!Z!XX?FXO{N$shO4M^>5^lX8Ga zd6$5cBh>|o>f>dmr`T?0ZUxNDB=V8guNSZc(-MAsG{Vw=TqTP#E3u>uawkinEVI1} zRUqmQBI;`jqHrGyl0KYBx(WeZvmJC53CpLssj}wi>3FZL?+Bgu5$Hsz z9Vt7y9so1f4a^}H37~n|>Z6CIM*6=@5`(ikei6;`!g%^3PUJkbO9OtiM}Db9d9zG? zN?(L!*YJE-P)1GQq(U#bq(V)pG~$}E%5w0KRvltc5M?Tj^xPKZ<{z5k!D3#eJ92K| zR?vMmt_5dfzT<4H6$x8UWv3N%GB)KR4pR)?P)n$QRrM~m3rc@8|3F9bLxjCXt3Qzk zQU=kD0HQ0sAWCep0Z7l#9F3S(4##8@EeG8&p*1rVP(Kk+pW=WTJ(rh3wH!cowF9bF zz_i;QLjyJC{S4(9JXQ{>VnZD(Tvd9mej%-zd3RY^%s-dx#Rhl{^ z->RZYifI7r-@N`J$i68-c6uD_cKdVc<=&*F-?0Rmt`*zYVXay&s$(lLWdqo_ej~=d z#4~n^7O%T)!aBd$hqb15vmG?ur)r%YlAkGG+fk(T?`Z!JZ3l|9jlQS-2V(@GpDY>x E0G<|+u>b%7 From 887aac6d182c923c307e761168136a05f7065800 Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 25 May 2025 22:59:05 +0800 Subject: [PATCH 40/54] Simplify --- rules/prefer-class-fields.js | 78 ++++++++++----------- test/snapshots/prefer-class-fields.js.md | 2 +- test/snapshots/prefer-class-fields.js.snap | Bin 912 -> 912 bytes 3 files changed, 38 insertions(+), 42 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 63a0291158..3481bd372a 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -77,7 +77,7 @@ const create = context => { const node = constructor.value.body.body.find((node) => node.type !== 'EmptyStatement'); if (!( - node.type === 'ExpressionStatement' + node?.type === 'ExpressionStatement' && node.expression.type === 'AssignmentExpression' && node.expression.operator === '=' && node.expression.left.type === 'MemberExpression' @@ -104,49 +104,45 @@ const create = context => { messageId: MESSAGE_ID_ERROR, }; - if (existingProperty) { - if (existingProperty.value) { - problem.suggest = [ - { - messageId: MESSAGE_ID_SUGGESTION, - data: { - propertyName, - // Class expression does not have name, e.g. const a = class {} - className: classBody.parent?.id?.name ?? '', - }, - /** - @param {import('eslint').Rule.RuleFixer} fixer - */ - * fix(fixer) { - yield removeFieldAssignment(node, sourceCode, fixer); - fixer.replaceText(existingProperty.value, propertyValue); - } + if (existingProperty?.value) { + problem.suggest = [ + { + messageId: MESSAGE_ID_SUGGESTION, + data: { + propertyName, + // Class expression does not have name, e.g. const a = class {} + className: classBody.parent?.id?.name ?? '', + }, + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ + * fix(fixer) { + yield removeFieldAssignment(node, sourceCode, fixer); + yield fixer.replaceText(existingProperty.value, propertyValue); } - ] - } else { - /** - @param {import('eslint').Rule.RuleFixer} fixer - */ - problem.fix = function * (fixer) { - yield removeFieldAssignment(node, sourceCode, fixer); - yield fixer.insertTextAfter(existingProperty.key, ` = ${propertyValue}`); } + ]; + return problem; + } + + /** + @param {import('eslint').Rule.RuleFixer} fixer + */ + problem.fix = function * (fixer) { + yield removeFieldAssignment(node, sourceCode, fixer); + if (existingProperty) { + yield fixer.insertTextAfter(existingProperty.key, ` = ${propertyValue}`); + return; } - } else { - /** - @param {import('eslint').Rule.RuleFixer} fixer - */ - problem.fix = function * (fixer) { - yield removeFieldAssignment(node, sourceCode, fixer); - yield addClassFieldDeclaration( - propertyName, - propertyValue, - classBody, - constructor, - sourceCode, - fixer, - ); - } + + yield addClassFieldDeclaration( + propertyName, + propertyValue, + classBody, + constructor, + sourceCode, + fixer, + ); } return problem; diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index 8cda694297..650057b4b6 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -207,7 +207,7 @@ Generated by [AVA](https://avajs.dev). --------------------------------------------------------------------------------␊ 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 = 0;␊ + 2 | bar = 1;␊ 3 | constructor() {␊ 4 | }␊ 5 | }␊ diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index d6c767b767d4508a7b6e54b71fe7fbda0f142c6e..a08ebbf50432759f999e875d093a159697209db4 100644 GIT binary patch delta 576 zcmV-G0>Ax`2apFcK~_N^Q*L2!b7*gLAa*kf0|4$D4>MVX=T@lVM#AN`a4{$|Q1-Q? z<&&o&Lu^#AWs8wAO#`vJ&XI!i4|_0ClA*#xEm}=ZMVEv~lY{{!e~vP9&1Ysh(H!Y~ zdIpP76!7Dr7FIHvDtV+V#Ztc@i)Kwe;6hB)FGSSm;ha5kRga--CJkNeztP3y>2i<} z(wC89%Bgjt7>locl(8kBvDpw~Wx`5n)}bxb(KT@~TLOL?quovW=#yTJ0#V~Eg!cxF z&R2xa+Zc4>vK^{Nf8J#un7Jr0un1F$pn0Bj4EQ3MgR?Sv9`*9vXnGUJvY*OC#+UNI zt5m4lwy9343$Sc!j%Rbqs17cv(FvQ>s4h&6*m}>*3UHs+EuvB2RVs}1)a2Fn7uv~# z!JJw&av}3k5Iq~0{IhW(bT(GYgsr6-r!{mkHpEFBNMt`ye=EqqN}mRjJ(<6uBl#|7 zyvBsh!+#?=qR)yEjFBpeJCSU0c~ksYAYwnDEo~l`yxfz$r)kSyQGC*+IJ$RU$QvruFAx`2apFcK~_N^Q*L2!b7*gLAa*kf0|4zc&=AqEEgg_Dhp6=f(-i&(zw!|i zA~iawaO#^Yd_K||~4`Y}p$xvaU7Of_yqDw+glY{{!f9_@Gn$OI1qB+v} z^b8iEDB#CKEv#fTRq{w#ilu%*7R{P`z=fEoUx=vB!#R88svbetOd7h_f1``Z)8!x| zq%R}IlvC?OF&16>UdEPu#%4o|l?f}QS%n?CoyaN1ya+6o?vSA-p$W zbiN{V-bSDkmF-YHfATK-z|8dm1B)<~2%6_f$AB-AIXEk$=TR@ujixtoEc>ZUWPB+P zyh??-ZJX+(x&X_z=6E)zjOyT$8lA97jq1YGh^@zFR)G7oZV`#6s8~^}3ix}(x From ef80c7f72f48a42828624608021b0dfc7536c11a Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 25 May 2025 23:15:13 +0800 Subject: [PATCH 41/54] Linting --- test/prefer-class-fields.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/prefer-class-fields.js b/test/prefer-class-fields.js index 367a4cc644..0fa64f8482 100644 --- a/test/prefer-class-fields.js +++ b/test/prefer-class-fields.js @@ -9,13 +9,13 @@ test.snapshot({ valid: [ 'class Foo {bar = 1}', 'class Foo {static bar = 1}', - // Not `=` assign + // Not `=` assign 'class Foo {constructor() {this.bar += 1}}', - // Computed + // Computed 'class Foo {constructor() {this[bar] = 1}}', - // Not `this` + // Not `this` 'class Foo {constructor() {notThis.bar = 1}}', - // Not `Literal` + // Not `Literal` 'class Foo {constructor() {notThis.bar = 1 + 2}}', outdent` class Foo { From 788978c8bd743b4356aada44e9ae0ebd1360de4a Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 25 May 2025 23:18:29 +0800 Subject: [PATCH 42/54] Linting --- rules/prefer-class-fields.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 3481bd372a..7ba232fe49 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -9,8 +9,6 @@ const messages = { 'Encountered same-named class field declaration and `this` assignment in constructor. Replace the class field declaration with the value from `this` assignment.', }; -const WHITELIST_NODES_PRECEDING_THIS_ASSIGNMENT = new Set(['EmptyStatement', 'ExpressionStatement']); - /** @param {import('eslint').Rule.Node} node @param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode @@ -67,14 +65,14 @@ const create = context => { && !node.computed && !node.static && node.type === 'MethodDefinition' - && node.value.type === 'FunctionExpression' + && node.value.type === 'FunctionExpression', ); if (!constructor) { return; } - const node = constructor.value.body.body.find((node) => node.type !== 'EmptyStatement'); + const node = constructor.value.body.body.find(node => node.type !== 'EmptyStatement'); if (!( node?.type === 'ExpressionStatement' @@ -86,7 +84,7 @@ const create = context => { && node.expression.left.property.type === 'Identifier' && node.expression.right.type === 'Literal' )) { - return + return; } const propertyName = node.expression.left.property.name; @@ -96,7 +94,7 @@ const create = context => { && !node.computed && !node.static && node.key.type === 'Identifier' - && node.key.name === propertyName + && node.key.name === propertyName, ); const problem = { @@ -119,8 +117,8 @@ const create = context => { * fix(fixer) { yield removeFieldAssignment(node, sourceCode, fixer); yield fixer.replaceText(existingProperty.value, propertyValue); - } - } + }, + }, ]; return problem; } @@ -143,7 +141,7 @@ const create = context => { sourceCode, fixer, ); - } + }; return problem; }, From dc28bcfa3e4646397d050c0c4a9b3fd200d4682b Mon Sep 17 00:00:00 2001 From: Jakub Freisler Date: Mon, 9 Jun 2025 23:02:47 +0200 Subject: [PATCH 43/54] chore: support private fields --- docs/rules/prefer-class-fields.md | 15 + rules/prefer-class-fields.js | 5 +- test/prefer-class-fields.js | 18 + test/snapshots/prefer-class-fields.js.md | 777 +++++++++++++++++++++ test/snapshots/prefer-class-fields.js.snap | Bin 912 -> 1244 bytes 5 files changed, 813 insertions(+), 2 deletions(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index 33800913e5..2f804bfb4a 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -56,3 +56,18 @@ class Foo { foo = 'bar'; } ``` + +```js +// ❌ +class Foo { + #foo = 'foo'; + constructor() { + this.#foo = 'bar'; + } +} + +// βœ… +class Foo { + #foo = 'bar'; +} +``` diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 7ba232fe49..1b6ee2700e 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -81,7 +81,7 @@ const create = context => { && node.expression.left.type === 'MemberExpression' && node.expression.left.object.type === 'ThisExpression' && !node.expression.left.computed - && node.expression.left.property.type === 'Identifier' + && ['Identifier', 'PrivateIdentifier'].includes(node.expression.left.property.type) && node.expression.right.type === 'Literal' )) { return; @@ -89,11 +89,12 @@ const create = context => { 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 === 'Identifier' + && node.key.type === propertyType && node.key.name === propertyName, ); diff --git a/test/prefer-class-fields.js b/test/prefer-class-fields.js index 0fa64f8482..c18efe6b15 100644 --- a/test/prefer-class-fields.js +++ b/test/prefer-class-fields.js @@ -9,6 +9,8 @@ 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 @@ -66,6 +68,14 @@ test.snapshot({ } } `, + outdent` + class Foo { + #bar; + constructor() { + this.#bar = 1; + } + } + `, outdent` class Foo { bar = 0; @@ -74,6 +84,14 @@ test.snapshot({ } } `, + outdent` + class Foo { + #bar = 0; + constructor() { + this.#bar = 1; + } + } + `, outdent` class Foo { [bar]; diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index 650057b4b6..c0ec3137e5 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -180,6 +180,783 @@ Generated by [AVA](https://avajs.dev). 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(3): 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(4): 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(5): 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(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; 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(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 = 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(10): 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(11): 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(12): 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(13): 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(14): 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(15): 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(6): class Foo { bar = 0; constructor() { this.bar = 1; } } > Input diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index a08ebbf50432759f999e875d093a159697209db4..b2df6d82c301b3b46ea9799484383f569084c419 100644 GIT binary patch literal 1244 zcmV<21S9)FRzV-+s#a>%R(uWQ;B|J!p7G4?IJKkaTdZi_o0;Fd zoqa#~X}uQ4-u>j)BhM%gct{_f528&H2L92?u?ygZBuU^>9K&Pq;wa&)?eUl$JqC|~ zH-n^HBMg?IQiTq5f+QjvNi*ijlV`q9IQjA3{+Takx}SXqW)AMVQ39yIR(8-5zyfT+ zoHTX`;B@!vIUz;~;7s@1P6=QUwxCm@A*s=jUX3=|yccp!2>Gpb$W9t16Ez#Ge!b0i zqP@;Wn|(fG9&SZ3L)iZIZuSG;FxSj?buvXU}UO_^92!9>-Bia^+++xArSNf=nY z#dJPPDn2|R_zwkwr^}WlWl2fO2Z59q7NqP1UQx_?cbOTw#)HgUPMJAvRY!V%J%VLe zQ1H_hwy@!-s>}(IjaVuQvZ~h1WwKBc^{+tGyXmYwW~;8D>r5WH*#AZsGp8#S5+Q{W zS;$$n_7r3KwI5_`C1q?;Vys43HP3bEzObWr)Qi~-kZ$AP?&f{;8DEVuqv}$S?+qe4 zzYFNR)S#oA?NmMTE~daNNx;mjiJ8|U(-QXVo_Z1IjhUIZg65Wxz%_jW#i{vIpysuP z8tsHvR1+S;%>;n+RSM48!Ab+maLTIM{9^n3NP{@9IBT=Vh3z~vn?H$(ECi(!jz6XE z+l>ZIw$rSftXzQA$cx(%r;PfLkQO}=krwrpQzMZ-a%N??NjE~`QQ%Fg9O*&8o7?}e zJ$b0JxHUL(+4(bM@N9gYo{e)uimK*tC<<$OI(`;Thc~jW86FR-;<`3l7S6|E&G{Ia z#)OtUJR;7Yn3{{ndFhPYt`T-8*Im{4;_*3z``c4L!Q`H+MO`&X)=EGGETydsJ{fFej7#9^j(EiR9)&*CHsWlhJ<}p>oRZaCQ zDt%P?PNXX;eN_5Jbji9El|Cwc*GgYDw5aq^>1#XdvYADtk4oQ2>C4&@mHtE*0`!%; zsPtuBiAsMON**4OT~PX8>TN7KeRTR(PG7dK==9O)7kBz<^NLO%oqiFguQ09X^qoHWzqr$vg$6o(boyiMIS#QK*mIoR{ltdK_<1)C{unklkEz-9_IQ^wOwQT)gYn<>~#anwvvC+G?p7V{zSz@s!pWnJQ@Jy_)-`fEcXs5S0-(ZfFsK4hgk8voVk-dCt=oB|p#aRZ z?lu*GS=fc9LM>9J7QJEDJ>HRAL6Wx;OE!I#fND_8xa09dwf8}_(bqY7xg|k{(7wAK zyx=aQ24$V7hDj|QbgEf|5pFs*I1S2RvlVC)i0hgayGCserX6e<7!CuM6K-nIAeKkn zf~cP6GGh({byD@2DEUkjdoWRwp~6HhT1`$xmxM_5X8nBTM0{vS{9zyBe%XSg%n3=^ zN2I(-K+2);k|67U%gm8Ajxux2XJ$Il9O-;|28&P>@Z+HtRx+9@d891GQokUJW=%fe zLQK>zMAYZuoIP??kD+TO4PETN(Z%HHa*z?ymyu%1sdb_li?4l@u_d3e*$`u8!b)k@ zp)J(WHE}Up0)88#-A()GlU|JiQR6Ix_Xdp4SA@>n7kz)GJ6l0BKfp(FV&X1vCWAIk$Ng6N75(Z!=6N<3nHke;AEYCTFh zm?0~_97vl;2Gn;1)Q4U`4d0X(LAC5dH6Mbi6EN+z$52CcaXkY%*<)oP7wcKLNXFF< z#MQ?bt|S}`6<9aBZ&Ig(WTl>24b<1FOjQRoxa!DBIV&g0DEo~l`yxfz$r)kSyQGC* z+IJ$RU$QvruF Date: Mon, 9 Jun 2025 23:11:49 +0200 Subject: [PATCH 44/54] chore: update snapshots --- test/snapshots/prefer-class-fields.js.md | 709 --------------------- test/snapshots/prefer-class-fields.js.snap | Bin 1244 -> 1012 bytes 2 files changed, 709 deletions(-) diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index c0ec3137e5..e2d8a2b233 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -496,712 +496,3 @@ Generated by [AVA](https://avajs.dev). 5 | }␊ 6 | }␊ ` - -## invalid(3): 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(4): 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(5): 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(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; 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(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 = 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(10): 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(11): 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(12): 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(13): 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(14): 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(15): 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(6): 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(7): 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(8): 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(9): 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(10): 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(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] = 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 | }␊ - ` diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index b2df6d82c301b3b46ea9799484383f569084c419..8049189be8d822a0730eff84c3c02997e31872dc 100644 GIT binary patch literal 1012 zcmVoV-@$$npG7PvDn%3&6~Bfg%_cjW%w%S>)ufoWhBE*EIp=rIoXx(> z=S*AMa$f9XYETnz&ly$~nT9@^8k+~rM2-W?whcR=*_J~|MWZ$u9fKX9MZ-zv5P?NV zEkF%whGU_!Q?#kGx3BAnqK6x&B4;A?&&cV>*^#?(fE4U{4~+w)VHaY)v*Q3$^{-=$ zMjT+e{%tJ|Fax_#i(?bzu!(P2WNvWRSNBI22Js zgDNsB*h%v8X)F<96G%;@TqYJ=Cel5aSdgK@U`n*APDP7^j_l32@yzo0&=~OteTchh z^O7>lCFPJI<+TDSZQ%?j>c7iO*Bo~;bIoODN@&@lftmFI zli(AR5F=A!YPP{oVnUjkgc3B57y~O}1N~F;m7(T9K#efq3@_j=+zbFXS6px|c1{{t zgh{1o^IY}*a799&IN9h0rkv-Z!A%V0Ac&8L{E6SM6bjgB*i22N=3&Xw?21J(!8$mo zgvTvZ!a6rJWa&L4OTs-|Hj##b7BTmvrv@!H{-F^({P0mnDh0>Y7UXLK)b5+ z;&CJq{p!V4Fg=x#Tf-OWWK9H9d52B^k55JS$C{o@-_EaZrTPA#(q#wPc zO{fv|jUnn?FQSHT72;8~+@i|=KB0MH(1&TlR8;3D)T0wVS0>|PJq#CWV0~x6dM^M= z!og6Hb+bj5|E)wj(!fKj%G9+elhtFITxG^dE-WW%l>K5T`y@r#$(do-Tcr8lWi(F= zzGhLTy7``YJ+5ubVtj0d<3o+H9}Ht3dNX!{cCVEdVgC0S-dK+~3P9O5Q{BgMnU#4o i2|$|ay)ErUjkezmZHJPy34II&aen~Vgie<5ApigZ0p1z_ literal 1244 zcmV<21S9)FRzV-+s#a>%R(uWQ;B|J!p7G4?IJKkaTdZi_o0;Fd zoqa#~X}uQ4-u>j)BhM%gct{_f528&H2L92?u?ygZBuU^>9K&Pq;wa&)?eUl$JqC|~ zH-n^HBMg?IQiTq5f+QjvNi*ijlV`q9IQjA3{+Takx}SXqW)AMVQ39yIR(8-5zyfT+ zoHTX`;B@!vIUz;~;7s@1P6=QUwxCm@A*s=jUX3=|yccp!2>Gpb$W9t16Ez#Ge!b0i zqP@;Wn|(fG9&SZ3L)iZIZuSG;FxSj?buvXU}UO_^92!9>-Bia^+++xArSNf=nY z#dJPPDn2|R_zwkwr^}WlWl2fO2Z59q7NqP1UQx_?cbOTw#)HgUPMJAvRY!V%J%VLe zQ1H_hwy@!-s>}(IjaVuQvZ~h1WwKBc^{+tGyXmYwW~;8D>r5WH*#AZsGp8#S5+Q{W zS;$$n_7r3KwI5_`C1q?;Vys43HP3bEzObWr)Qi~-kZ$AP?&f{;8DEVuqv}$S?+qe4 zzYFNR)S#oA?NmMTE~daNNx;mjiJ8|U(-QXVo_Z1IjhUIZg65Wxz%_jW#i{vIpysuP z8tsHvR1+S;%>;n+RSM48!Ab+maLTIM{9^n3NP{@9IBT=Vh3z~vn?H$(ECi(!jz6XE z+l>ZIw$rSftXzQA$cx(%r;PfLkQO}=krwrpQzMZ-a%N??NjE~`QQ%Fg9O*&8o7?}e zJ$b0JxHUL(+4(bM@N9gYo{e)uimK*tC<<$OI(`;Thc~jW86FR-;<`3l7S6|E&G{Ia z#)OtUJR;7Yn3{{ndFhPYt`T-8*Im{4;_*3z``c4L!Q`H+MO`&X)=EGGETydsJ{fFej7#9^j(EiR9)&*CHsWlhJ<}p>oRZaCQ zDt%P?PNXX;eN_5Jbji9El|Cwc*GgYDw5aq^>1#XdvYADtk4oQ2>C4&@mHtE*0`!%; zsPtuBiAsMON**4OT~PX8>TN7KeRTR(PG7dK==9O)7kBz<^NLO%oqiFguQ09X^qoHWzqr$vg$6o(boyiMIS#QK*mIoR{ltdK_<1)C{unklkEz-9_IQ^wOwQT)gYn<>~#anwvvC Date: Tue, 10 Jun 2025 00:02:24 +0200 Subject: [PATCH 45/54] chore: post rebase fixes --- rules/index.js | 1 + 1 file changed, 1 insertion(+) 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'; From fcb0a3aa33fba68007e6c37d7c0ecc8a11ebcbdd Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 12 Jun 2025 20:43:18 +0800 Subject: [PATCH 46/54] Remove unused message data --- rules/prefer-class-fields.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 1b6ee2700e..35786db3e0 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -107,11 +107,6 @@ const create = context => { problem.suggest = [ { messageId: MESSAGE_ID_SUGGESTION, - data: { - propertyName, - // Class expression does not have name, e.g. const a = class {} - className: classBody.parent?.id?.name ?? '', - }, /** @param {import('eslint').Rule.RuleFixer} fixer */ From 8b7a2c3f3a7eb6b0e223ef8fbc6d916473e08829 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 12 Jun 2025 20:51:39 +0800 Subject: [PATCH 47/54] Inline `addClassFieldDeclaration` to avoid ESLint warning --- rules/prefer-class-fields.js | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 35786db3e0..30bd01683d 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -28,30 +28,6 @@ const removeFieldAssignment = (node, sourceCode, fixer) => { : fixer.remove(node); }; -/** -@param {string} propertyName -@param {string} propertyValue -@param {import('estree').ClassBody} classBody -@param {import('estree').MethodDefinition} constructor -@param {import('eslint').Rule.RuleContext['sourceCode']} sourceCode -@param {import('eslint').Rule.RuleFixer} fixer -*/ -const addClassFieldDeclaration = ( - propertyName, - propertyValue, - classBody, - constructor, - sourceCode, - fixer, -) => { - const closingBrace = sourceCode.getLastToken(classBody); - const indent = getIndentString(constructor, sourceCode); - return fixer.insertTextBefore( - closingBrace, - `${indent}${propertyName} = ${propertyValue};\n`, - ); -}; - /** @type {import('eslint').Rule.RuleModule['create']} */ @@ -129,14 +105,9 @@ const create = context => { return; } - yield addClassFieldDeclaration( - propertyName, - propertyValue, - classBody, - constructor, - sourceCode, - fixer, - ); + const closingBrace = sourceCode.getLastToken(classBody); + const indent = getIndentString(constructor, sourceCode); + yield fixer.insertTextBefore(closingBrace, `${indent}${propertyName} = ${propertyValue};\n`); }; return problem; From bb7388c4666aae2c0eb76e3501e36bb8613397b3 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 12 Jun 2025 20:56:09 +0800 Subject: [PATCH 48/54] Use one fix function --- rules/prefer-class-fields.js | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 30bd01683d..c5d73bf2f4 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -79,37 +79,35 @@ const create = context => { messageId: MESSAGE_ID_ERROR, }; - if (existingProperty?.value) { - problem.suggest = [ - { - messageId: MESSAGE_ID_SUGGESTION, - /** - @param {import('eslint').Rule.RuleFixer} fixer - */ - * fix(fixer) { - yield removeFieldAssignment(node, sourceCode, fixer); - yield fixer.replaceText(existingProperty.value, propertyValue); - }, - }, - ]; - return problem; - } - /** @param {import('eslint').Rule.RuleFixer} fixer */ - problem.fix = function * (fixer) { + function * fix(fixer) { yield removeFieldAssignment(node, sourceCode, fixer); + if (existingProperty) { - yield fixer.insertTextAfter(existingProperty.key, ` = ${propertyValue}`); + yield existingProperty.value + ? fixer.replaceText(existingProperty.value, propertyValue) + : fixer.insertTextAfter(existingProperty.key, ` = ${propertyValue}`); return; } const closingBrace = sourceCode.getLastToken(classBody); const indent = getIndentString(constructor, sourceCode); yield fixer.insertTextBefore(closingBrace, `${indent}${propertyName} = ${propertyValue};\n`); - }; + } + + if (existingProperty?.value) { + problem.suggest = [ + { + messageId: MESSAGE_ID_SUGGESTION, + fix, + }, + ]; + return problem; + } + problem.fix = fix; return problem; }, }; From 6b6cf06ffa885197224704a0472c2f42b498ec03 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 12 Jun 2025 20:58:51 +0800 Subject: [PATCH 49/54] More tests for fix --- test/prefer-class-fields.js | 13 +++++ test/snapshots/prefer-class-fields.js.md | 63 +++++++++++++++++++++ test/snapshots/prefer-class-fields.js.snap | Bin 1012 -> 1120 bytes 3 files changed, 76 insertions(+) diff --git a/test/prefer-class-fields.js b/test/prefer-class-fields.js index c18efe6b15..b3ad617b7d 100644 --- a/test/prefer-class-fields.js +++ b/test/prefer-class-fields.js @@ -140,6 +140,19 @@ test.snapshot({ } } `, + outdent` + class Foo { + constructor() { + this.bar = 1; + }} + `, + outdent` + class Foo { + constructor() { + this.bar = 1; + } + static} + `, ], }); diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index e2d8a2b233..46fee6af83 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -496,3 +496,66 @@ Generated by [AVA](https://avajs.dev). 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 | }bar = 1;␊ + 4 | }␊ + ` + +> 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 | staticbar = 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 | static}␊ + ` diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index 8049189be8d822a0730eff84c3c02997e31872dc..835a4587f0603af4fdb897599c262dd7f9a1d19a 100644 GIT binary patch literal 1120 zcmV-m1fTmsRzV%3m_4iKnta61c>q$?@E;N=*He8 zrj8xfn^Hk@p|MU3G>^PIB z#e!*T^UjlJlpEZni{pm1Kukm5lihn1G?O?EoU(0r1e$F*oYgdLvpsv^5%99%WD11A zBxEO`4t2w^NYyFZ+*y96>x7dVcef4f7-&3$+Xr?IUP%FDVaa=F3LpndFzh=!1#qzO z@i5Xz0UT<4T2BFt!V=U|)FcIJ(z8~z##@pzNblYoFfnK5&`QB4w>piv~4y(5Yq;Mz~>HU@uSxbB%-MfH;m(vMSW#U|8OiLD6R5 zaKa4@7KmA+PDT_@V~R1GLH0n_Wn#i*A{W8LgbWo1O3|!36>So_qBrXLGbZ9gL*mmt zh`V9)k}@VFWfhU~T7i^~a9-f`#xfHa<8Ee7yUZL^>Labc9>OH#1pL%c3QGxfm0_eT z#Zs>zi+atl#|kk~zYtNM`m^`QMLmSBLviS0|BNmsPnV4hAw3zAi`lid6l3AB?`ACT zGB)aCtU%aAoN;I#W%RZ*yVif_cGYy_$O2|uZ8wNi;XYi4E-WRJp>rP(!$ zQ$}@gNQLgVNQLS`*NCM@dX|B!v}zKK0xwhHNe>KOUi*hN=b^~#O83c`#P5*q+BoUf z#*u)g8d4mB#_CqbeN-Jcz5-z?eFD(&7Q|&K)M}jC)oU|VZ{Wl7Us5;fA%D+!&of!9JnxTy9;(&T|lIO}qR@_O#3N^5P zAh14!fF)r;Uy*gLO_qNv(eX5P(^^1zEz3l8ojO+)SyD*K5;e+xBg#HYQMPq@*qJtI z{#{1v#Q0+tN7*e##&x}>EeqK(my8{1jQvE6eT-&o3(Z~^+JyP{8D3wT9L1n)5oPzD zTx4ZlO=6IydT&eHqDI>vMBAz)ZJ~Db6>*)PdHA;*5qt&UHH}hwS{O>fqAF-%Ru5$7 z5)wMDC{*`r1cLDjf$>}nhG63rnEj31chx1nC&>CY6j4p3sT7d=0!qpCL~`GvxTP5c m!Y!nFQ5A%iP*Xtahd%ER6)*dciol?D5cvxjnUabKCIA4UO9r+8 literal 1012 zcmVoV-@$$npG7PvDn%3&6~Bfg%_cjW%w%S>)ufoWhBE*EIp=rIoXx(> z=S*AMa$f9XYETnz&ly$~nT9@^8k+~rM2-W?whcR=*_J~|MWZ$u9fKX9MZ-zv5P?NV zEkF%whGU_!Q?#kGx3BAnqK6x&B4;A?&&cV>*^#?(fE4U{4~+w)VHaY)v*Q3$^{-=$ zMjT+e{%tJ|Fax_#i(?bzu!(P2WNvWRSNBI22Js zgDNsB*h%v8X)F<96G%;@TqYJ=Cel5aSdgK@U`n*APDP7^j_l32@yzo0&=~OteTchh z^O7>lCFPJI<+TDSZQ%?j>c7iO*Bo~;bIoODN@&@lftmFI zli(AR5F=A!YPP{oVnUjkgc3B57y~O}1N~F;m7(T9K#efq3@_j=+zbFXS6px|c1{{t zgh{1o^IY}*a799&IN9h0rkv-Z!A%V0Ac&8L{E6SM6bjgB*i22N=3&Xw?21J(!8$mo zgvTvZ!a6rJWa&L4OTs-|Hj##b7BTmvrv@!H{-F^({P0mnDh0>Y7UXLK)b5+ z;&CJq{p!V4Fg=x#Tf-OWWK9H9d52B^k55JS$C{o@-_EaZrTPA#(q#wPc zO{fv|jUnn?FQSHT72;8~+@i|=KB0MH(1&TlR8;3D)T0wVS0>|PJq#CWV0~x6dM^M= z!og6Hb+bj5|E)wj(!fKj%G9+elhtFITxG^dE-WW%l>K5T`y@r#$(do-Tcr8lWi(F= zzGhLTy7``YJ+5ubVtj0d<3o+H9}Ht3dNX!{cCVEdVgC0S-dK+~3P9O5Q{BgMnU#4o i2|$|ay)ErUjkezmZHJPy34II&aen~Vgie<5ApigZ0p1z_ From d8a2aa83e91e03f1db04e9df36e657e30ac216e5 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 12 Jun 2025 21:01:39 +0800 Subject: [PATCH 50/54] Fix logic --- rules/prefer-class-fields.js | 6 +++++- test/snapshots/prefer-class-fields.js.md | 10 ++++++---- test/snapshots/prefer-class-fields.js.snap | Bin 1120 -> 1118 bytes 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index c5d73bf2f4..604185d80a 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -94,7 +94,11 @@ const create = context => { const closingBrace = sourceCode.getLastToken(classBody); const indent = getIndentString(constructor, sourceCode); - yield fixer.insertTextBefore(closingBrace, `${indent}${propertyName} = ${propertyValue};\n`); + const characterBefore = sourceCode.getText()[sourceCode.getRange(closingBrace)[0] - 1]; + yield fixer.insertTextBefore( + closingBrace, + `${characterBefore === '\n' ? '' : '\n'}${indent}${propertyName} = ${propertyValue};\n`, + ); } if (existingProperty?.value) { diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index 46fee6af83..9e7f1406c7 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -513,8 +513,9 @@ Generated by [AVA](https://avajs.dev). `␊ 1 | class Foo {␊ 2 | constructor() {␊ - 3 | }bar = 1;␊ - 4 | }␊ + 3 | }␊ + 4 | bar = 1;␊ + 5 | }␊ ` > Error 1/1 @@ -545,8 +546,9 @@ Generated by [AVA](https://avajs.dev). 1 | class Foo {␊ 2 | constructor() {␊ 3 | }␊ - 4 | staticbar = 1;␊ - 5 | }␊ + 4 | static␊ + 5 | bar = 1;␊ + 6 | }␊ ` > Error 1/1 diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index 835a4587f0603af4fdb897599c262dd7f9a1d19a..d4f108b7a7db1c4cf3dadd9c105c860826d73a54 100644 GIT binary patch literal 1118 zcmV-k1flyuRzVxDUKydD$Xi zFaf!7s6*ZI9J1_HT<$%5Vw!}L>o<4y?e1$lJ9hQ$*>NcYkb~9Wp&5WYtinL(>WFlBLi@_@o7B+FbbDHZO&H;p>wvpL87wvqS_I;GR@tdihlAw=QwAlM zfyW8A3|Jv{je1#8Jgq6lTn4$3oX^C#&qO|kiE$MwER>=-eJWZcbVP5|_h(GRhk?Xr zyAb!o79?d%NXjQf%1aGW+QO3pum3JHkumOMX2xgckX9dQ{&f!~ATQvjhEiBgsjCbi zWhIun1zFT<1_D-yiTa6%`p}!bM=k0Sx(+9yi~TdYm^xi9GK6$xNIqfL+E$FoW8cZx zq|exBh_NDJ<4MM$C6v*5aWESKei}QooAl~a9*qL0%1j8)4H%uz2%T3FbY$N4)RlL_ z2WBh;%%GTtY=xcOqXq9P~a6RJn4?bE9?KT?mU#3TkSkKoBAEnSsSPP z+Bh1~R3*hBXsmv9+(y-5#P&7C^3ZCoh5jt6kA0H*h&5x2w!Hr(lKPD)89a`KYGkfR z*nFZ{ReA8(kVt=8Eh7U8T^gYG+VcgK{gDBC_g>~xE? z@GhfyV)8MIqwJPq%3m_4iKnta61c>q$?@E;N=*He8 zrj8xfn^Hk@p|MU3G>^PIB z#e!*T^UjlJlpEZni{pm1Kukm5lihn1G?O?EoU(0r1e$F*oYgdLvpsv^5%99%WD11A zBxEO`4t2w^NYyFZ+*y96>x7dVcef4f7-&3$+Xr?IUP%FDVaa=F3LpndFzh=!1#qzO z@i5Xz0UT<4T2BFt!V=U|)FcIJ(z8~z##@pzNblYoFfnK5&`QB4w>piv~4y(5Yq;Mz~>HU@uSxbB%-MfH;m(vMSW#U|8OiLD6R5 zaKa4@7KmA+PDT_@V~R1GLH0n_Wn#i*A{W8LgbWo1O3|!36>So_qBrXLGbZ9gL*mmt zh`V9)k}@VFWfhU~T7i^~a9-f`#xfHa<8Ee7yUZL^>Labc9>OH#1pL%c3QGxfm0_eT z#Zs>zi+atl#|kk~zYtNM`m^`QMLmSBLviS0|BNmsPnV4hAw3zAi`lid6l3AB?`ACT zGB)aCtU%aAoN;I#W%RZ*yVif_cGYy_$O2|uZ8wNi;XYi4E-WRJp>rP(!$ zQ$}@gNQLgVNQLS`*NCM@dX|B!v}zKK0xwhHNe>KOUi*hN=b^~#O83c`#P5*q+BoUf z#*u)g8d4mB#_CqbeN-Jcz5-z?eFD(&7Q|&K)M}jC)oU|VZ{Wl7Us5;fA%D+!&of!9JnxTy9;(&T|lIO}qR@_O#3N^5P zAh14!fF)r;Uy*gLO_qNv(eX5P(^^1zEz3l8ojO+)SyD*K5;e+xBg#HYQMPq@*qJtI z{#{1v#Q0+tN7*e##&x}>EeqK(my8{1jQvE6eT-&o3(Z~^+JyP{8D3wT9L1n)5oPzD zTx4ZlO=6IydT&eHqDI>vMBAz)ZJ~Db6>*)PdHA;*5qt&UHH}hwS{O>fqAF-%Ru5$7 z5)wMDC{*`r1cLDjf$>}nhG63rnEj31chx1nC&>CY6j4p3sT7d=0!qpCL~`GvxTP5c m!Y!nFQ5A%iP*Xtahd%ER6)*dciol?D5cvxjnUabKCIA4UO9r+8 From 5bd7ab47da409327c6bce84c57bad25573fac4d7 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 12 Jun 2025 21:12:59 +0800 Subject: [PATCH 51/54] Still incorrect --- rules/prefer-class-fields.js | 20 ++++++++++++++++---- test/snapshots/prefer-class-fields.js.md | 2 +- test/snapshots/prefer-class-fields.js.snap | Bin 1118 -> 1120 bytes 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/rules/prefer-class-fields.js b/rules/prefer-class-fields.js index 604185d80a..e180480c3d 100644 --- a/rules/prefer-class-fields.js +++ b/rules/prefer-class-fields.js @@ -94,11 +94,23 @@ const create = context => { 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]; - yield fixer.insertTextBefore( - closingBrace, - `${characterBefore === '\n' ? '' : '\n'}${indent}${propertyName} = ${propertyValue};\n`, - ); + 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) { diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index 9e7f1406c7..e65accef45 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -546,7 +546,7 @@ Generated by [AVA](https://avajs.dev). 1 | class Foo {␊ 2 | constructor() {␊ 3 | }␊ - 4 | static␊ + 4 | static;␊ 5 | bar = 1;␊ 6 | }␊ ` diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index d4f108b7a7db1c4cf3dadd9c105c860826d73a54..2d414edcea33c058618c9b3b6fc55c05e899c78a 100644 GIT binary patch literal 1120 zcmV-m1fTmsRzV-ib)KOn0a4 z>UI}rreq^qNbRUxLK_h*icfo2lNf%CQvYoOVd!&zD5HtRnIYru8mVI8U|YLXl^=|yX$%WYtyisCvcnSIWGp_QtE$^Q=o= zZc^YOY=64wec&pidCKai<_&7PCoPJnF~yk8AagR~GBNHlk&R$tT!sn*rD$HAiUtWS(HnOC84>ZJA@N&n zh`V9)k}@JBWebt=Qh}7F@T9=&-DM^)#;wd;aG4oY>Lc~P9>N4<1^kpz3JVE!m0qMQ z#ZtQA zGB)gEEJxUQoN?$L%IJbPm^A@6jjh>@d-X|=MxIl{Oz_VQ7@f}uomU~~gn8RhSKb*H zm=PZ^ePUwz!jUPVoGpoi*eA_QpAs~8kO4E{2DDGjS47R55Nbjlo)kG8z|9^2=d26P zsn$*d6EL9EZ62xKAFfE^9cQlRI7;V{p7=)W$wrVmk?@ncQ7#myvu$SPWM&MeEX^)k zoHD9|LrV07MM_i`x<)KL(z7&NrzrbC^3oDPG}ZXxcOZx&e1S|;)I*P1I4YK@OiKeHqn^pzowI~zSztp)_kR`dKEK#HE7ozNw6lMFThfOy~ z^Y1e1C&nMMILdB5GOmAX+Om)x%gNZG#@G+U*oSDw_R;J$(;&>h&+z)X!%+;%R#A5E z%0*V@)g%UKs`s|EEo!v=Mzn27(iUn*M-kWjnTLP75y4jgUbj$6F9<^^SX2cq%&LLx z%p##_MIm?HD)q~b7l@CiVm<_muRsWPbL~}?__`qDUsOc(mK~)41ac@dHxogio}apA m5Dz+1osG%Ej)04URQGs`=y=|NbOZ*siOL^>H41PSCIA5Gsve>M literal 1118 zcmV-k1flyuRzVxDUKydD$Xi zFaf!7s6*ZI9J1_HT<$%5Vw!}L>o<4y?e1$lJ9hQ$*>NcYkb~9Wp&5WYtinL(>WFlBLi@_@o7B+FbbDHZO&H;p>wvpL87wvqS_I;GR@tdihlAw=QwAlM zfyW8A3|Jv{je1#8Jgq6lTn4$3oX^C#&qO|kiE$MwER>=-eJWZcbVP5|_h(GRhk?Xr zyAb!o79?d%NXjQf%1aGW+QO3pum3JHkumOMX2xgckX9dQ{&f!~ATQvjhEiBgsjCbi zWhIun1zFT<1_D-yiTa6%`p}!bM=k0Sx(+9yi~TdYm^xi9GK6$xNIqfL+E$FoW8cZx zq|exBh_NDJ<4MM$C6v*5aWESKei}QooAl~a9*qL0%1j8)4H%uz2%T3FbY$N4)RlL_ z2WBh;%%GTtY=xcOqXq9P~a6RJn4?bE9?KT?mU#3TkSkKoBAEnSsSPP z+Bh1~R3*hBXsmv9+(y-5#P&7C^3ZCoh5jt6kA0H*h&5x2w!Hr(lKPD)89a`KYGkfR z*nFZ{ReA8(kVt=8Eh7U8T^gYG+VcgK{gDBC_g>~xE? z@GhfyV)8MIqwJPq Date: Thu, 12 Jun 2025 21:43:32 +0800 Subject: [PATCH 52/54] One more test --- test/prefer-class-fields.js | 8 +++++ test/snapshots/prefer-class-fields.js.md | 36 +++++++++++++++++++++ test/snapshots/prefer-class-fields.js.snap | Bin 1120 -> 1167 bytes 3 files changed, 44 insertions(+) diff --git a/test/prefer-class-fields.js b/test/prefer-class-fields.js index b3ad617b7d..2566d78f09 100644 --- a/test/prefer-class-fields.js +++ b/test/prefer-class-fields.js @@ -153,6 +153,14 @@ test.snapshot({ } static} `, + outdent` + class Foo { + constructor() { + this.bar = 1; + } + static// comment + } + `, ], }); diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index e65accef45..be7cf24ac7 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -561,3 +561,39 @@ Generated by [AVA](https://avajs.dev). 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 index 2d414edcea33c058618c9b3b6fc55c05e899c78a..b4d099c87b14483d3fae1b89700d387c1c1b9625 100644 GIT binary patch literal 1167 zcmV;A1aSL7RzVK@`VpMWng&+6xc#rV33=wbdG0M66#e_}OAD{g~`d zlC_)NIy;HQHaGeNK7u}j_aa`2C@Lx<3W|!aAxX2zp3Tng%x0@iaqk++{Qq;#Z_b>} z$)iHvw6#^|;bY1TZqoH}!>SU~(1)^zPl09<$ANRU4I7}@mcv;^<2D;Q3>&~phLg?{ z1{09YK@Dn#W05tdWOL`iV_hel++5z3fZU=s#>Yo`E? z)ju9U8YzI|^-pUlfKk|lT8f$^PfdE!TC4D;E;*cRAT%3!6w&;3lp+S|H73!o#@ie9wvl(PFS(k~N%fv_o6FC_w43we;bt+mUbVP5|-Orea4-JXW zb|LPD%}dIdkd#k|lotx5w1p=HyZ*b(1je|Nne#3)$CT={y@} z-Lr8rps0o%4nbjcPsbf}I<&~TCV4!RitBuT8l8_rA?G8~#ttoc|Bp!GH)crVG44Af z3wgp8WA&=ijmMTm^4)3?8JO?VfZA)%7j!INg>)g|;yd!v5)XXT$#wlQW7rI z!1|8BdKUtggoBr#s>|5T#A)7V9;it<{LiRw00t~KN&pOh0d%6=irK1xxxdurH) z7HR%jMsvmZZ5Bt_Ekwq3ySgn4`B+KDhZP_~Y; zds{BDGOs2vNK?JHrM;-p_8ZamNs_iuA3a4}`)3~h=|%)!0eH=#l%5xcQZT3r8kp4r z*_lH^7ZiouajVoXJDwvxo{0Gn488&(_?u&|n#9)yS^uCSsw@~qqCgf1bWI(ga4Qm0^I4FNKd1~77 hNi;t)7$L>FkzBH`alHYQFz#)_{s3v@?}m9O008oZFjN2l literal 1120 zcmV-m1fTmsRzV-ib)KOn0a4 z>UI}rreq^qNbRUxLK_h*icfo2lNf%CQvYoOVd!&zD5HtRnIYru8mVI8U|YLXl^=|yX$%WYtyisCvcnSIWGp_QtE$^Q=o= zZc^YOY=64wec&pidCKai<_&7PCoPJnF~yk8AagR~GBNHlk&R$tT!sn*rD$HAiUtWS(HnOC84>ZJA@N&n zh`V9)k}@JBWebt=Qh}7F@T9=&-DM^)#;wd;aG4oY>Lc~P9>N4<1^kpz3JVE!m0qMQ z#ZtQA zGB)gEEJxUQoN?$L%IJbPm^A@6jjh>@d-X|=MxIl{Oz_VQ7@f}uomU~~gn8RhSKb*H zm=PZ^ePUwz!jUPVoGpoi*eA_QpAs~8kO4E{2DDGjS47R55Nbjlo)kG8z|9^2=d26P zsn$*d6EL9EZ62xKAFfE^9cQlRI7;V{p7=)W$wrVmk?@ncQ7#myvu$SPWM&MeEX^)k zoHD9|LrV07MM_i`x<)KL(z7&NrzrbC^3oDPG}ZXxcOZx&e1S|;)I*P1I4YK@OiKeHqn^pzowI~zSztp)_kR`dKEK#HE7ozNw6lMFThfOy~ z^Y1e1C&nMMILdB5GOmAX+Om)x%gNZG#@G+U*oSDw_R;J$(;&>h&+z)X!%+;%R#A5E z%0*V@)g%UKs`s|EEo!v=Mzn27(iUn*M-kWjnTLP75y4jgUbj$6F9<^^SX2cq%&LLx z%p##_MIm?HD)q~b7l@CiVm<_muRsWPbL~}?__`qDUsOc(mK~)41ac@dHxogio}apA m5Dz+1osG%Ej)04URQGs`=y=|NbOZ*siOL^>H41PSCIA5Gsve>M From 570a881dd5bfa831e1a07118fc067c2f8f37e480 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 12 Jun 2025 21:44:45 +0800 Subject: [PATCH 53/54] Make sure we are using token instead of text --- test/prefer-class-fields.js | 2 +- test/snapshots/prefer-class-fields.js.md | 8 ++++---- test/snapshots/prefer-class-fields.js.snap | Bin 1167 -> 1164 bytes 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/prefer-class-fields.js b/test/prefer-class-fields.js index 2566d78f09..91c7238d8e 100644 --- a/test/prefer-class-fields.js +++ b/test/prefer-class-fields.js @@ -158,7 +158,7 @@ test.snapshot({ constructor() { this.bar = 1; } - static// comment + static// comment; } `, ], diff --git a/test/snapshots/prefer-class-fields.js.md b/test/snapshots/prefer-class-fields.js.md index be7cf24ac7..f5197c6671 100644 --- a/test/snapshots/prefer-class-fields.js.md +++ b/test/snapshots/prefer-class-fields.js.md @@ -562,7 +562,7 @@ Generated by [AVA](https://avajs.dev). 5 | static}␊ ` -## invalid(17): class Foo { constructor() { this.bar = 1; } static// comment } +## invalid(17): class Foo { constructor() { this.bar = 1; } static// comment; } > Input @@ -571,7 +571,7 @@ Generated by [AVA](https://avajs.dev). 2 | constructor() {␊ 3 | this.bar = 1;␊ 4 | }␊ - 5 | static// comment␊ + 5 | static// comment;␊ 6 | }␊ ` @@ -581,7 +581,7 @@ Generated by [AVA](https://avajs.dev). 1 | class Foo {␊ 2 | constructor() {␊ 3 | }␊ - 4 | static// comment␊ + 4 | static// comment;␊ 5 | ;bar = 1;␊ 6 | }␊ ` @@ -594,6 +594,6 @@ Generated by [AVA](https://avajs.dev). > 3 | this.bar = 1;␊ | ^^^^^^^^^^^^^ Prefer class field declaration over \`this\` assignment in constructor for static values.␊ 4 | }␊ - 5 | static// comment␊ + 5 | static// comment;␊ 6 | }␊ ` diff --git a/test/snapshots/prefer-class-fields.js.snap b/test/snapshots/prefer-class-fields.js.snap index b4d099c87b14483d3fae1b89700d387c1c1b9625..b5dca6df59a940d3bcdca0b2c14b9c391e5bb999 100644 GIT binary patch literal 1164 zcmV;71atdARzVCtjxZ&M@NV&yrdVADzmWged1G&9pVA#a-;DqbK3K*{AaaJ|B%Lews3h=V!WebGC zIOOtBgPP?zWXUVL+`ISCGzlkH7Pj~7?5RK7cJ%Dpb}j>ugVo@o8Gs>Jg}%_*8GwWJ zkNc2D2H;Tr(^>{#7*?T{p*AT{o1S!*s=OgNizH8LOV)gqfND^zc&5r*YA=Ioy^nj; zgtbrSgAbf#v`ATF)uKgh6HIE@gb{AJ4!Fyd!D4-(MIfGMm7EH7I9N_FWl(e( zc${#{fMsG=sh1VS)0$+=Wsn=p`Ap<}CWc~|$g5Ccp%h)yr=m$hTl9wg{fvnCFp&7A z4#fSi1xXnZlJXIe@=Sx2mhgmN*MFCp$QZXXGvzaLP^*qK{<;I>FeKooic(lgsjBoL zWhIt61zA*U`T{P*MEyiWz3GGb>$uP zff)$_(=R5bUye)#)9 z;r2=c<8VN$+T7K>KYWqYE6!~1K9tVgy~#~%%0iIYpYoHrP%V|Hw`Mapm>Yvh$8f6- zr;M85kqX`KkP0=0TO*DcyR$5ur%N_5DDW~Bu5`=d<+XoU+dLGRTWMc8oBAEnem0K# zXX8jjQI#AHL1Fbz$4zuPjM%!Scs#U<>uhfposT_|^AT%fla{>yMf z0%3EBdR67dV_hQsZncCA%ywu%@3rSMI+iaaU5H$KQ(jskh$fp~{2qv+NV^ZYr(0=T zGU?N#{OYA_LXW7gh^W_bM0H;)B%ldAAMp|N7pmuZGFYKj9I&`F;w8@ZTI!-XDL z-w;@DBw#5x=qj>KHOUH3C0aX8T(p)^Udt*`-Jr^~gq#%8a-v7sFGSe~CCau=4V!L~ z7M^7^R!rVzag^O+Y+N_0+j5YP#dLh=G4=y7_AZ{WE!2C>GzkmOGlF|v=O_VXw^4Sl zt3_7j)g%FFy7#uS7d_g3BicSH(kAuMRm8P^<`JH5#PAh?*CmwFDPbrDgQ}>3SuK*C z%SdQiQ^+5;D*dwKDdOXim=D3=D-wdgIrge4d|i+W4=Unn%eqno0tJ+ri>V;c$WKEv ziU$*^UQWovx`2;^QuTO+=y=kFbVLSMb}@xV6Kbdo4uauU>elj|v_FZl3FxOWYgYjY eOV+LAm6-aDn0njoOo=1kqU;YcH|EZLC;$NY)-Tfl literal 1167 zcmV;A1aSL7RzVK@`VpMWng&+6xc#rV33=wbdG0M66#e_}OAD{g~`d zlC_)NIy;HQHaGeNK7u}j_aa`2C@Lx<3W|!aAxX2zp3Tng%x0@iaqk++{Qq;#Z_b>} z$)iHvw6#^|;bY1TZqoH}!>SU~(1)^zPl09<$ANRU4I7}@mcv;^<2D;Q3>&~phLg?{ z1{09YK@Dn#W05tdWOL`iV_hel++5z3fZU=s#>Yo`E? z)ju9U8YzI|^-pUlfKk|lT8f$^PfdE!TC4D;E;*cRAT%3!6w&;3lp+S|H73!o#@ie9wvl(PFS(k~N%fv_o6FC_w43we;bt+mUbVP5|-Orea4-JXW zb|LPD%}dIdkd#k|lotx5w1p=HyZ*b(1je|Nne#3)$CT={y@} z-Lr8rps0o%4nbjcPsbf}I<&~TCV4!RitBuT8l8_rA?G8~#ttoc|Bp!GH)crVG44Af z3wgp8WA&=ijmMTm^4)3?8JO?VfZA)%7j!INg>)g|;yd!v5)XXT$#wlQW7rI z!1|8BdKUtggoBr#s>|5T#A)7V9;it<{LiRw00t~KN&pOh0d%6=irK1xxxdurH) z7HR%jMsvmZZ5Bt_Ekwq3ySgn4`B+KDhZP_~Y; zds{BDGOs2vNK?JHrM;-p_8ZamNs_iuA3a4}`)3~h=|%)!0eH=#l%5xcQZT3r8kp4r z*_lH^7ZiouajVoXJDwvxo{0Gn488&(_?u&|n#9)yS^uCSsw@~qqCgf1bWI(ga4Qm0^I4FNKd1~77 hNi;t)7$L>FkzBH`alHYQFz#)_{s3v@?}m9O008oZFjN2l From f2f5b0f0050a9d7f638ccca0d3db3fadf8de24fe Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 14 Jun 2025 01:58:23 +0200 Subject: [PATCH 54/54] Update prefer-class-fields.md --- docs/rules/prefer-class-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/prefer-class-fields.md b/docs/rules/prefer-class-fields.md index 2f804bfb4a..0e65dd26fc 100644 --- a/docs/rules/prefer-class-fields.md +++ b/docs/rules/prefer-class-fields.md @@ -7,7 +7,7 @@ -This rule enforces the use of class field declarations for static values, instead of assigning them in constructors using `this`. +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).