-
Notifications
You must be signed in to change notification settings - Fork 76
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
Add new rule noAccessStateInSetstate #190
Changes from 3 commits
0caf7df
8ddc00f
5e19d46
a81a966
0ce297b
e2b08c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/** | ||
* @license | ||
* Copyright 2018 Palantir Technologies, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import * as Lint from "tslint"; | ||
import { isCallExpression, isClassDeclaration, isPropertyAccessExpression } from "tsutils"; | ||
import * as ts from "typescript"; | ||
|
||
export class Rule extends Lint.Rules.AbstractRule { | ||
/* tslint:disable:object-literal-sort-keys */ | ||
public static metadata: Lint.IRuleMetadata = { | ||
ruleName: "no-access-state-in-setstate", | ||
description: "Reports usage of this.state within setState", | ||
rationale: Lint.Utils.dedent | ||
`Usage of this.state might result in errors when two state calls are \ | ||
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. you don't need the trailing also the leading backtick should go right after |
||
called in batch and thus referencing old state and not the current state.`, | ||
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. It would be good to reference these react docs in this rationale. you can use markdown 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. Added a link to the docs. Is the phrasing alright? (English being not my first language and all) |
||
options: null, | ||
optionsDescription: "", | ||
type: "functionality", | ||
typescriptOnly: false, | ||
}; | ||
/* tslint:enable:object-literal-sort-keys */ | ||
|
||
public static FAILURE_STRING = "Avoid using this.state in first argument of setState."; | ||
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. how about we say this instead:
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. Good idea! Unfortunately the only way I came up to achieve this was to duplicate the callback function which checks the 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. I have some ideas for cleaning this up a bit, I can push an additional commit after this merges 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. Maybe you can share your ideas now, since I will refactor this again a bit. Or you just push them, as soon as I provide you guys with the new version! |
||
|
||
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { | ||
return this.applyWithFunction(sourceFile, walk); | ||
} | ||
} | ||
|
||
function walk(ctx: Lint.WalkContext<void>): void { | ||
return ts.forEachChild(ctx.sourceFile, callbackForEachChild); | ||
|
||
function callbackForEachChild(node: ts.Node): void { | ||
if (!isClassDeclaration(node)) { | ||
return; | ||
} | ||
|
||
ts.forEachChild(node, callbackForEachChildInClass); | ||
} | ||
|
||
function callbackForEachChildInClass(node: ts.Node): void { | ||
if (!isCallExpression(node)) { | ||
return ts.forEachChild(node, callbackForEachChildInClass); | ||
} | ||
|
||
const callExpressionArguments = node.arguments; | ||
|
||
if (!isPropertyAccessExpression(node.expression) || callExpressionArguments.length === 0) { | ||
return; | ||
} | ||
|
||
const propertyAccessExpression = node.expression; | ||
|
||
const isThisPropertyAccess = propertyAccessExpression.expression.kind === ts.SyntaxKind.ThisKeyword; | ||
const isSetStateCall = propertyAccessExpression.name.text === "setState"; | ||
|
||
if (!isThisPropertyAccess || !isSetStateCall) { | ||
return; | ||
} | ||
|
||
ts.forEachChild(node.arguments[0], callbackForEachChildInSetStateArgument); | ||
} | ||
|
||
function callbackForEachChildInSetStateArgument(node: ts.Node): void { | ||
if (!isPropertyAccessExpression(node) || !isPropertyAccessExpression(node.expression)) { | ||
return ts.forEachChild(node, callbackForEachChildInSetStateArgument); | ||
} | ||
|
||
if ( | ||
node.expression.expression.kind !== ts.SyntaxKind.ThisKeyword || | ||
node.expression.name.text !== "state" | ||
) { | ||
return ts.forEachChild(node, callbackForEachChildInSetStateArgument); | ||
} | ||
|
||
ctx.addFailureAtNode(node, Rule.FAILURE_STRING); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
class SomeReactComponent extends React.Component { | ||
|
||
someClassFunction() { | ||
|
||
this.fooBar({ | ||
foo: this.state.foo | ||
}); | ||
|
||
this.setState({ | ||
foo: "foo", | ||
bar: this.barz | ||
}); | ||
|
||
this.setState( | ||
{ | ||
foo: "foo" | ||
}, | ||
() => this.fooBar(this.state.foo); | ||
); | ||
|
||
this.setState(prevState => ({ | ||
foo: !prevState.foo | ||
})); | ||
|
||
this.setState((prevState, currentProps) => ({ | ||
foo: !prevState.foo, | ||
bar: currentProps.bar | ||
})); | ||
|
||
this.setState({ | ||
foo: window.history.length | ||
}); | ||
|
||
this.setState({ | ||
foo: !this.props.bar | ||
}); | ||
|
||
this.setState({ | ||
foo: !this.state.foo | ||
~~~~~~~~~~~~~~ [0] | ||
}); | ||
|
||
this.setState({ | ||
foo: this.fooBar(this.state.foo) | ||
~~~~~~~~~~~~~~ [0] | ||
}); | ||
|
||
this.setState((prevState, currentProps) => ({ | ||
foo: !this.state.foo, | ||
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. it would of course be much nicer to have the marker only on the occurrence(s) of 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. I sat down again with my code and now I'm pretty sure I found a still very easy and even more reliable way to not only find accesses of this.state but also can now display the error message exactly at the right node. 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. Or even better push it right away. Looking forward to your review! |
||
~~~~~~~~~~~~~~ [0] | ||
bar: currentProps.bar | ||
})); | ||
|
||
this.setState((prevState, currentProps) => { | ||
this.fooBar(this.state.foo); | ||
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. For this case the error message might read a bit strange, since the code is using the callback. 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. Very good idea, will do that! 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. Okay, unfortunately it might be a little difficult. In my now updated code, you'd have to copy the So for a quick solution I simply made the failure text a bit more generic. |
||
~~~~~~~~~~~~~~ [0] | ||
return { | ||
bar: !prevState.bar | ||
}; | ||
}); | ||
} | ||
} | ||
|
||
[0]: Avoid using this.state in first argument of setState. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"rules": { | ||
"no-access-state-in-setstate": true | ||
} | ||
} |
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 did not get syntax highlighted / formatted correctly -- check out the markdown preview