-
-
Notifications
You must be signed in to change notification settings - Fork 412
Add no-accessor-recursion
rule
#2525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
sindresorhus
merged 59 commits into
sindresorhus:main
from
axetroy:no_accessor_recursion
Jan 24, 2025
Merged
Changes from 23 commits
Commits
Show all changes
59 commits
Select commit
Hold shift + click to select a range
33155ab
Add `no-accessor-recursion` rule
axetroy fc62f17
update docs
axetroy e67c4d0
ignore no-accessor-recursion
axetroy 664998d
update docs
axetroy eb7cfa8
update test case
axetroy 3335bbe
add more test
axetroy 2bc1ffb
add test
axetroy a8e0195
support nest scope
axetroy c3adada
add test
axetroy 2ce59e2
support detect nest scope in array function
axetroy a519d44
add more test case
axetroy 08a09e8
Update no-accessor-recursion.md
sindresorhus 35bc0ce
Update no-accessor-recursion.js
sindresorhus 6d3a3a1
update description
axetroy 1e45d9b
update test
axetroy 54d8ab7
add more test
axetroy cd67a7c
rename mjs to js
axetroy cccc72c
Merge branch 'main' into no_accessor_recursion
sindresorhus c936b88
Update no-accessor-recursion.js
sindresorhus 91eace3
update
axetroy 354c0c6
fix lint
axetroy b8b7390
remove snapshot
axetroy 610f2b0
update jsdoc
axetroy 28ab834
fix computed accessor
axetroy 97d2285
update problem report
axetroy 1e12f24
simplify the selector
axetroy b619216
simplify the condition branch with early return
axetroy 823cf60
add more test
axetroy 72e47c8
move some logic to the top for performance
axetroy baa6a7d
fix setter checker when node in the right of AssignmentExpression
axetroy d79aa76
simplify the node selector
axetroy d96eead
simplify the code to reduce complexity
axetroy eff4ae3
fix some edge case
axetroy 675722f
simplify the code
axetroy d0e7629
simplify the code
axetroy 684baf2
simplify the code, remove the selector
axetroy 090a434
Improve `getClosestFunctionScope`
fisker 818e194
Add `isAccessor` and check missing `computed`
fisker f699a4c
feat: support private field
axetroy 4dc5489
add static method test case
axetroy 7b516a2
update jsdoc
axetroy 9351864
fix false positive for private field
axetroy 2b66ed8
support detect static method
axetroy be11037
Merge branch 'main' into no_accessor_recursion
axetroy 8a73ee2
support destructuring assignment within getter
axetroy 58b2058
add more test case
axetroy 9b355f4
simplify the code
axetroy 8ff5bbd
add more test case
axetroy ef9b2cb
add test for destructuring assignment with computed property
axetroy 689c49a
early return for performance
axetroy 3e3f740
The params of isSameKey should be camelCase
axetroy a17cef9
Handle more cases
fisker c6e2dfd
More specific kind
fisker 75542e0
Tweak report location
fisker abd9d62
Add test for private destructuring
fisker aeb880e
Update test
fisker 810c31f
Add test for class static block
axetroy bf1ac9d
Another case
fisker f7b7013
Merge branch 'main' into no_accessor_recursion
axetroy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# Disallow recursive access to `this` within getters and setters | ||
|
||
💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). | ||
|
||
<!-- end auto-generated rule header --> | ||
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` --> | ||
|
||
This rule prevents recursive access to `this` within getter and setter methods in objects and classes, avoiding infinite recursion and stack overflow errors. | ||
|
||
## Examples | ||
|
||
```js | ||
// ❌ | ||
const foo = { | ||
get bar() { | ||
return this.bar; | ||
} | ||
}; | ||
|
||
// ✅ | ||
const foo = { | ||
get bar() { | ||
return this.baz; | ||
} | ||
}; | ||
``` | ||
|
||
```js | ||
// ❌ | ||
class Foo { | ||
get bar() { | ||
return this.bar; | ||
} | ||
} | ||
|
||
// ✅ | ||
class Foo { | ||
get bar() { | ||
return this.baz; | ||
} | ||
} | ||
``` | ||
|
||
```js | ||
// ❌ | ||
const foo = { | ||
set bar(value) { | ||
this.bar = value; | ||
} | ||
}; | ||
|
||
// ✅ | ||
const foo = { | ||
set bar(value) { | ||
this._bar = value; | ||
} | ||
}; | ||
``` | ||
|
||
```js | ||
// ❌ | ||
class Foo { | ||
set bar(value) { | ||
this.bar = value; | ||
} | ||
} | ||
|
||
// ✅ | ||
class Foo { | ||
set bar(value) { | ||
this._bar = value; | ||
} | ||
} | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
const MESSAGE_ID_ERROR = 'no-accessor-recursion/error'; | ||
const messages = { | ||
[MESSAGE_ID_ERROR]: 'Disallow recursive access to `this` within getters and setters.', | ||
}; | ||
|
||
/** @param {import('eslint').Scope.Scope} scope */ | ||
const isArrowFunctionScope = scope => scope.type === 'function' && scope.block.type === 'ArrowFunctionExpression'; | ||
|
||
/** @param {import('eslint').Rule.RuleContext} context */ | ||
const create = context => { | ||
const functionExpressionStack = []; | ||
|
||
return { | ||
// Getter for object literal | ||
'Property[value.type="FunctionExpression"], [kind="get"]'(node) { | ||
functionExpressionStack.push(node); | ||
}, | ||
'Property[value.type="FunctionExpression"], [kind="get"]:exit'() { | ||
functionExpressionStack.pop(); | ||
}, | ||
// Setter for object literal | ||
'Property[value.type="FunctionExpression"], [kind="set"]'(node) { | ||
functionExpressionStack.push(node); | ||
}, | ||
'Property[value.type="FunctionExpression"], [kind="set"]:exit'() { | ||
functionExpressionStack.pop(); | ||
}, | ||
// Getter for class | ||
'MethodDefinition[kind="get"]'(node) { | ||
functionExpressionStack.push(node); | ||
}, | ||
'MethodDefinition[kind="get"]:exit'() { | ||
functionExpressionStack.pop(); | ||
}, | ||
// Setter for class | ||
'MethodDefinition[kind="set"]'(node) { | ||
functionExpressionStack.push(node); | ||
}, | ||
'MethodDefinition[kind="set"]:exit'() { | ||
functionExpressionStack.pop(); | ||
}, | ||
ThisExpression(node) { | ||
if (functionExpressionStack.length > 0) { | ||
fisker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** @type {import('estree').Property | import('estree').MethodDefinition} */ | ||
const property = functionExpressionStack.at(-1); | ||
|
||
let scope = context.sourceCode.getScope(node); | ||
|
||
while (scope.type !== 'function' || isArrowFunctionScope(scope)) { | ||
scope = scope.upper; | ||
} | ||
|
||
// Check if `this` is in the current function expression scope | ||
if (scope.block === property.value) { | ||
/** @type {import('estree').MemberExpression} */ | ||
const {parent} = node; | ||
|
||
const isThisAccessed = () => parent.type === 'MemberExpression' && parent.property.type === 'Identifier' && parent.property.name === property.key.name; | ||
fisker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
switch (property.kind) { | ||
case 'get': { | ||
if (isThisAccessed()) { | ||
context.report({node: parent, messageId: MESSAGE_ID_ERROR}); | ||
} | ||
|
||
break; | ||
} | ||
|
||
case 'set': { | ||
if (isThisAccessed() && parent.parent.type === 'AssignmentExpression') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need make sure the memberExpression is on left
|
||
context.report({node: parent.parent, messageId: MESSAGE_ID_ERROR}); | ||
} | ||
|
||
break; | ||
} | ||
|
||
default: | ||
} | ||
} | ||
} | ||
}, | ||
}; | ||
}; | ||
|
||
/** @type {import('eslint').Rule.RuleModule} */ | ||
const config = { | ||
create, | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'Disallow recursive access to `this` within getters and setters.', | ||
recommended: true, | ||
}, | ||
defaultOptions: [], | ||
messages, | ||
}, | ||
}; | ||
|
||
export default config; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import outdent from 'outdent'; | ||
import {getTester} from './utils/test.js'; | ||
|
||
const {test} = getTester(import.meta); | ||
|
||
test.snapshot({ | ||
valid: [ | ||
'function foo () { this.bar }', | ||
'function foo () { this.foo }', | ||
'function foo (value) { this.bar = value }', | ||
'this.foo = foo', | ||
outdent` | ||
{ | ||
this.foo = foo; | ||
} | ||
`, | ||
'this.foo = function () { this.foo }', | ||
'const foo = () => this.foo', | ||
outdent` | ||
const foo = { | ||
bar() { | ||
this.bar = void 0; | ||
return this.bar; | ||
} | ||
}; | ||
`, | ||
outdent` | ||
class Foo { | ||
foo() { | ||
this.foo = void 0; | ||
return this.foo; | ||
} | ||
} | ||
`, | ||
// Deep property setter | ||
outdent` | ||
class Foo { | ||
set bar(value) { | ||
this.bar.baz = value; | ||
} | ||
} | ||
`, | ||
// Define this to alias | ||
outdent` | ||
class Foo { | ||
get bar() { | ||
const self = this; | ||
return self.bar; | ||
} | ||
} | ||
`, | ||
outdent` | ||
class Foo { | ||
set bar(value) { | ||
const self = this; | ||
return self.bar = value; | ||
} | ||
} | ||
`, | ||
outdent` | ||
const foo = { | ||
get bar() { | ||
return this._bar; | ||
} | ||
}; | ||
`, | ||
// Access this in function scope | ||
outdent` | ||
const foo = { | ||
get bar() { | ||
function baz() { | ||
return this.bar; | ||
} | ||
} | ||
}; | ||
`, | ||
// Nest getter | ||
outdent` | ||
const foo = { | ||
get bar() { | ||
const qux = { | ||
get quux () { | ||
return this.bar; | ||
} | ||
} | ||
} | ||
}; | ||
`, | ||
], | ||
invalid: [ | ||
// Getter | ||
outdent` | ||
const foo = { | ||
get bar() { | ||
return this.bar; | ||
} | ||
}; | ||
`, | ||
outdent` | ||
class Foo { | ||
get bar() { | ||
return this.bar; | ||
} | ||
} | ||
`, | ||
// Setter | ||
outdent` | ||
const foo = { | ||
set bar(value) { | ||
this.bar = value; | ||
} | ||
}; | ||
`, | ||
outdent` | ||
class Foo { | ||
set bar(value) { | ||
this.bar = value; | ||
} | ||
} | ||
`, | ||
// Deep property getter | ||
outdent` | ||
const foo = { | ||
get bar() { | ||
return this.bar.baz; | ||
} | ||
}; | ||
`, | ||
// Access this in nest block | ||
outdent` | ||
const foo = { | ||
get bar() { | ||
if (true) { | ||
return this.bar; | ||
} | ||
} | ||
}; | ||
`, | ||
// Access this in arrow function scope | ||
outdent` | ||
const foo = { | ||
get bar() { | ||
const baz = () => { | ||
return this.bar; | ||
} | ||
} | ||
}; | ||
`, | ||
outdent` | ||
const foo = { | ||
get bar() { | ||
const baz = () => { | ||
return () => { | ||
return this.bar; | ||
} | ||
} | ||
} | ||
}; | ||
`, | ||
], | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.