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
feat(eslint-plugin): [prefer-return-this-type] add a new rule #3228
Conversation
Thanks for the PR, @Zzzen! typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community. The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately. Thanks again! 🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint. As a thank you, your profile/company logo will be added to our main README which receives thousands of unique visitors per day. |
Codecov Report
@@ Coverage Diff @@
## master #3228 +/- ##
==========================================
+ Coverage 92.64% 93.51% +0.87%
==========================================
Files 326 147 -179
Lines 11253 7848 -3405
Branches 3171 2485 -686
==========================================
- Hits 10425 7339 -3086
+ Misses 368 162 -206
+ Partials 460 347 -113
Flags with carried forward coverage won't be shown. Click here to find out more. |
f2() { | ||
return this; | ||
} | ||
f3(): Foo | undefined { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not this | undefined
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👌
description: | ||
'Enforce that `this` is used when only `this` type is returned', | ||
category: 'Best Practices', | ||
recommended: 'error', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adding to the recommended set is a breaking change
recommended: 'error', | |
recommended: false, |
'ClassBody > MethodDefinition'(node: TSESTree.MethodDefinition): void { | ||
checkFunction(node.value, node.parent!.parent as ClassLikeDeclaration); | ||
}, | ||
'ClassBody > ClassProperty'(node: TSESTree.ClassProperty): void { | ||
if ( | ||
!( | ||
node.value?.type === AST_NODE_TYPES.FunctionExpression || | ||
node.value?.type === AST_NODE_TYPES.ArrowFunctionExpression | ||
) | ||
) { | ||
return; | ||
} | ||
|
||
checkFunction(node.value, node.parent!.parent as ClassLikeDeclaration); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one easier way to do this is to use a stack to track your state.
It saves you doing manual traversal of the tree
interface Stack { classLike: ClassLikeDeclaration, methodLike: FunctionLike | null }
const stack: Stack[] = [];
function getCurrentStack(): Stack | undefined {
return stack[stack.length - 1];
}
return {
'ClassDeclaration, ClassExpression:enter'(node: ClassLikeDeclaration) {
stack.push({ classLike: node, methodLike: null });
},
'ClassDeclaration, ClassExpression:exit'(node: ClassLikeDeclaration) {
stack.pop();
},
'ClassBody > MethodDefinition:enter, ClassBody > ClassProperty > :matches(FunctionExpression, ArrowFunctionExpression).value:enter'(node: FunctionLike ) {
const current = getCurrentStack();
if (current != null) {
current.methodLike = node;
}
},
'ClassBody > MethodDefinition, ClassBody > ClassProperty > :matches(FunctionExpression, ArrowFunctionExpression).value:exit'(node: FunctionLike ) {
const current = getCurrentStack();
if (current != null) {
current.methodLike = null;
}
},
ReturnStatement(node) {
const current = getCurrentStack();
if (current != null && current.methodLike != null) {
// check return and report
}
},
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I haven't seen this pattern before, will try it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more complicated than I have expected. We may have functions inside methods and need to track them too.
class Foo {
bar() {
const f = function() {
return this;
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a few quick comments - haven't reviewed SUPER deeply yet.
what about abstract
methods? Do we want to check those signatures?
what about interface
s? Do we want to check those signatures?
Co-authored-by: Brad Zacher <brad.zacher@gmail.com>
Showerthought: we don't need to check them because interface IAnimalFixed {
move(): IAnimalFixed;
}
interface IAnimalPolymophic {
move(): this;
}
class CatFixed implements IAnimalFixed {
// return type will be checked by this rule
move(): this {
return this;
}
meow(): this {
return this;
}
}
class CatPolymophic implements IAnimalPolymophic {
move(): this {
return this;
}
meow(): this {
return this;
}
}
function useInterface(fixed: IAnimalFixed, polymophic: IAnimalPolymophic) {
// return type: IAnimalFixed
fixed.move();
// return type: IAnimalFixed
polymophic.move();
}
function useImplementation(fixed: CatFixed, polymophic: CatPolymophic) {
// return type: CatFixed
fixed.move();
// return type: CatPolymophic
polymophic.move();
}
|
@@ -17,6 +17,7 @@ export = { | |||
'@typescript-eslint/no-unsafe-member-access': 'error', | |||
'@typescript-eslint/no-unsafe-return': 'error', | |||
'@typescript-eslint/prefer-regexp-exec': 'error', | |||
'@typescript-eslint/prefer-return-this-type': 'error', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you need to regenerate the configs
packages/eslint-plugin/README.md
Outdated
@@ -163,6 +163,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | |||
| [`@typescript-eslint/prefer-readonly-parameter-types`](./docs/rules/prefer-readonly-parameter-types.md) | Requires that function parameters are typed as readonly to prevent accidental mutation of inputs | | | :thought_balloon: | | |||
| [`@typescript-eslint/prefer-reduce-type-parameter`](./docs/rules/prefer-reduce-type-parameter.md) | Prefer using type parameter when calling `Array#reduce` instead of casting | | :wrench: | :thought_balloon: | | |||
| [`@typescript-eslint/prefer-regexp-exec`](./docs/rules/prefer-regexp-exec.md) | Enforce that `RegExp#exec` is used instead of `String#match` if no global flag is provided | :white_check_mark: | :wrench: | :thought_balloon: | | |||
| [`@typescript-eslint/prefer-return-this-type`](./docs/rules/prefer-return-this-type.md) | Enforce that `this` is used when only `this` type is returned | :heavy_check_mark: | :wrench: | :thought_balloon: | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you need to regen the docs to remove the recommended tick
Why is CI not running? |
Nx Cloud ReportCI ran the following commands for commit 52df4db. Click to see the status, the terminal output, and the build insights. 📂 See all runs for this branch Sent with 💌 from NxCloud. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM - this is enough coverage for a v1.
We can always add more cases later.
Thanks for your contribution!
close #889
The implementation is pretty clear, it only warns if:
this
typeTODO: