Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Commit

Permalink
[Issue #44] new rule: underscore-consistent-invocation rule
Browse files Browse the repository at this point in the history
closes #44
  • Loading branch information
HamletDRC committed Aug 1, 2016
1 parent 17f2c31 commit 346cc89
Show file tree
Hide file tree
Showing 5 changed files with 1,106 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Rule Name | Description | Since
`promise-must-complete` | When a Promise instance is created, then either the reject() or resolve() parameter must be called on it within all code branches in the scope. For more examples see the [feature request](https://github.com/Microsoft/tslint-microsoft-contrib/issues/34). | 1.0
`react-no-dangerous-html` | Do not use React's dangerouslySetInnerHTML API. This rule finds usages of the dangerouslySetInnerHTML API (but not any JSX references). For more info see the [react-no-dangerous-html Rule wiki page](https://github.com/Microsoft/tslint-microsoft-contrib/wiki/react-no-dangerous-html-Rule). | 0.0.2
`react-this-binding-issue` | Several errors can occur when using React and React.Component subclasses. When using React components you must be careful to correctly bind the 'this' reference on any methods that you pass off to child components as callbacks. For example, it is common to define a private method called 'onClick' and then specify `onClick={this.onClick}` as a JSX attribute. If you do this then the 'this' reference will be undefined when your private method is invoked. The React documentation suggests that you bind the 'this' reference on all of your methods within the constructor: `this.onClick = this.onClick.bind(this);`. This rule will create a violation if 1) a method reference is passed to a JSX attribute without being bound in the constructor. And 2) a method is bound multiple times in the constructor. Another issue that can occur is binding the 'this' reference to a function within the render() method. For example, many people will create an anonymous lambda within the JSX attribute to avoid the 'this' binding issue: `onClick={() => { this.onClick(); }}`. The problem with this is that a new instance of an anonymous function is created every time render() is invoked. When React compares virutal DOM properties within shouldComponentUpdate() then the onClick property will look like a new property and force a re-render. You should avoid this pattern because creating function instances within render methods breaks any logic within shouldComponentUpdate() methods. This rule creates violations if 1) an anonymous function is passed as a JSX attribute. And 2) if a function instantiated in local scope is passed as a JSX attribute. This rule can be configured via the "allow-anonymous-listeners" parameter. If you want to suppress violations for the anonymous listener scenarios then configure that rule like this: `"react-this-binding-issue": [ true, { 'allow-anonymous-listeners': true } ]` | 2.0.8, 2.0.9
`underscore-consistent-invocation` | Enforce a consistent usage of the _ functions. By default, invoking underscore functions should begin with wrapping a variable in an underscore instance: `_(list).map(...)`. An alternative is to prefer using the static methods on the _ variable: `_.map(list, ...)`. The rule accepts single parameter called 'style' which can be the value 'static' or 'instance': `[true, { "style": "static" }]`| 2.0.10
`use-isnan` | Deprecated - This rule is now part of the base TSLint product. Ensures that you use the isNaN() function to check for NaN references instead of a comparison to the NaN constant. Similar to the [use-isnan ESLint rule](http://eslint.org/docs/rules/use-isnan).| 1.0
`use-named-parameter` | Do not reference the arguments object by numerical index; instead, use a named parameter. This rule is similar to JSLint's [Use a named parameter](https://jslinterrors.com/use-a-named-parameter) rule. | 0.0.3
`valid-typeof` | Ensures that the results of typeof are compared against a valid string. This rule aims to prevent errors from likely typos by ensuring that when the result of a typeof operation is compared against a string, that the string is a valid value. Similar to the [valid-typeof ESLint rule](http://eslint.org/docs/rules/valid-typeof).| 1.0
Expand Down
5 changes: 3 additions & 2 deletions recommended_ruleset.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ module.exports = {
* Common Bugs and Correctness. The following rules should be turned on because they find
* common bug patterns in the code or enforce type safety.
*/
"chai-prefer-contains-to-index-of": true,
"chai-vague-errors": true,
"forin": true,
"jquery-deferred-must-complete": true,
"label-position": true,
Expand Down Expand Up @@ -75,6 +73,8 @@ module.exports = {
* Code Clarity. The following rules should be turned on because they make the code
* generally more clear to the reader.
*/
"chai-prefer-contains-to-index-of": true,
"chai-vague-errors": true,
"class-name": true,
"comment-format": true,
"export-name": true,
Expand Down Expand Up @@ -111,6 +111,7 @@ module.exports = {
"prefer-array-literal": true,
"prefer-const": true,
"typedef": [true, "callSignature", "indexSignature", "parameter", "propertySignature", "variableDeclarator", "memberVariableDeclarator"],
"underscore-consistent-invocation": true,
"variable-name": true,

/**
Expand Down
89 changes: 89 additions & 0 deletions src/underscoreConsistentInvocationRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as ts from 'typescript';
import * as Lint from 'tslint/lib/lint';

import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker';
import {SyntaxKind} from './utils/SyntaxKind';
import {AstUtils} from './utils/AstUtils';

const FAILURE_STATIC_FOUND: string = 'Static invocation of underscore function found. Prefer instance version instead: ';
const FAILURE_INSTANCE_FOUND: string = 'Underscore instance wrapping of variable found. Prefer underscore static functions instead: ';

const FUNCTION_NAMES: string[] = [
'each', 'forEach', 'map', 'collect',
'reduce', 'inject', 'foldl', 'reduceRight',
'foldr', 'find', 'detect', 'filter',
'select', 'where', 'findWhere', 'reject',
'every', 'all', 'some', 'any',
'contains', 'include', 'invoke', 'pluck',
'max', 'min', 'sortBy', 'groupBy',
'indexBy', 'countBy', 'shuffle', 'sample',
'toArray', 'size', 'partition', 'first',
'head', 'take', 'initial', 'last',
'rest', 'tail', 'drop', 'compact',
'flatten', 'without', 'union', 'intersection',
'difference', 'uniq', 'unique', 'object',
'zip', 'unzip', 'indexOf', 'findIndex',
'lastIndexOf', 'findLastIndex', 'sortedIndex', 'range'
];

/**
* Implementation of the underscore-consistent-invocation rule.
*/
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new UnderscoreConsistentInvocationRuleWalker(sourceFile, this.getOptions()));
}
}

class UnderscoreConsistentInvocationRuleWalker extends ErrorTolerantWalker {

private style: string = 'instance';

constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) {
super(sourceFile, options);
this.getOptions().forEach((opt: any) => {
if (typeof(opt) === 'object') {
if (opt.style === 'static') {
this.style = 'static';
}
}
});
}

protected visitCallExpression(node: ts.CallExpression): void {
const functionName: string = AstUtils.getFunctionName(node);

if (this.style === 'instance' && this.isStaticUnderscoreInvocation(node)) {
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), FAILURE_STATIC_FOUND + '_.' + functionName));
}
if (this.style === 'static' && this.isStaticUnderscoreInstanceInvocation(node)) {
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), FAILURE_INSTANCE_FOUND + node.expression.getText()));
}
super.visitCallExpression(node);
}

private isStaticUnderscoreInstanceInvocation(node: ts.CallExpression) {
if (node.expression.kind === SyntaxKind.current().PropertyAccessExpression) {
const propExpression: ts.PropertyAccessExpression = <ts.PropertyAccessExpression>node.expression;
if (propExpression.expression.kind === SyntaxKind.current().CallExpression) {
const call: ts.CallExpression = <ts.CallExpression>propExpression.expression;
const target: string = AstUtils.getFunctionTarget(call);
const functionName: string = AstUtils.getFunctionName(call);
if (target == null && functionName === '_' && call.arguments.length === 1) {
const underscoreFunctionName = AstUtils.getFunctionName(node);
return FUNCTION_NAMES.indexOf(underscoreFunctionName) > -1;
}
}
}
return false;
}

private isStaticUnderscoreInvocation(node: ts.CallExpression) {
const target: string = AstUtils.getFunctionTarget(node);
if (target !== '_') {
return false;
}
const functionName: string = AstUtils.getFunctionName(node);
return FUNCTION_NAMES.indexOf(functionName) > -1;
}
}
Loading

0 comments on commit 346cc89

Please sign in to comment.