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

Commit 4b91110

Browse files
noamyogev84Josh Goldberg
authored and
Josh Goldberg
committed
#147 implement use-simple-attribute (#628)
* implement use-simple-attribute * fix typo in useSimpleAttributeRule reorder use-simple-attribute in tslint.json * add rationale to useSimpleAttributeRule * Merge branch master; rename to plural rule * Added description to README.md
1 parent 42bb696 commit 4b91110

File tree

6 files changed

+211
-0
lines changed

6 files changed

+211
-0
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,16 @@ We recommend you specify exact versions of lint libraries, including `tslint-mic
934934
</td>
935935
<td>6.0.0-beta</td>
936936
</tr>
937+
<tr>
938+
<td>
939+
<code>use-simple-attributes</code>
940+
</td>
941+
<td>
942+
Simpler attributes in JSX and TSX files helps keep code clean and readable.
943+
Separate complex expressions into their own line and use clear variable names to make your code more understandable.
944+
</td>
945+
<td>6.0.0</td>
946+
</tr>
937947
<tr>
938948
<td>
939949
<code>react-a11y-props</code>

recommended_ruleset.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ module.exports = {
9696
'triple-equals': [true, 'allow-null-check'],
9797
'use-isnan': true,
9898
'use-named-parameter': true,
99+
'use-simple-attributes': true,
99100

100101
/**
101102
* Code Clarity. The following rules should be turned on because they make the code
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { Utils } from '../utils/Utils';
2+
import { TestHelper } from './TestHelper';
3+
/**
4+
* Unit tests.
5+
*/
6+
describe('useSimpleAttributesRule', (): void => {
7+
const ruleName: string = 'use-simple-attributes';
8+
const binaryExpressionErrorMessage: string = 'Attribute contains a complex binary expression';
9+
const ternaryExpressionErrorMessage: string = 'Attribute contains a ternary expression';
10+
it('should fail if only attribute initializer is a complex binary expression', (): void => {
11+
const script: string = `
12+
import React = require('react');
13+
const element = <foo bar={"value1" + "value2" + "value3"}/>\`;
14+
`;
15+
16+
TestHelper.assertViolations(ruleName, script, [
17+
{
18+
failure: binaryExpressionErrorMessage,
19+
name: Utils.absolutePath('file.tsx'),
20+
ruleName: ruleName,
21+
startPosition: { character: 25, line: 3 }
22+
}
23+
]);
24+
});
25+
it('should fail if any attribute initializer is a complex binary expression', (): void => {
26+
const script: string = `
27+
import React = require('react');
28+
const element = <foo str="hello" bar={"value1" + "value2" + "value3"}/>\`;
29+
`;
30+
31+
TestHelper.assertViolations(ruleName, script, [
32+
{
33+
failure: binaryExpressionErrorMessage,
34+
name: Utils.absolutePath('file.tsx'),
35+
ruleName: ruleName,
36+
startPosition: { character: 25, line: 3 }
37+
}
38+
]);
39+
});
40+
it('should pass if any attribute initializer is a simple binary expression', (): void => {
41+
const script: string = `
42+
import React = require('react');
43+
const element = <foo str="hello" bar={"value1" + "value2"}/>\`;
44+
`;
45+
46+
TestHelper.assertNoViolation(ruleName, script);
47+
});
48+
it('should fail if only attribute initializer is a ternary expression', (): void => {
49+
const script: string = `
50+
import React = require('react');
51+
const someVar = 3;
52+
const element = <foo bar={someVar == 3 ? true : false}/>\`;
53+
`;
54+
55+
TestHelper.assertViolations(ruleName, script, [
56+
{
57+
failure: ternaryExpressionErrorMessage,
58+
name: Utils.absolutePath('file.tsx'),
59+
ruleName: ruleName,
60+
startPosition: { character: 25, line: 4 }
61+
}
62+
]);
63+
});
64+
it('should fail if any attribute initializer is a ternary expression', (): void => {
65+
const script: string = `
66+
import React = require('react');
67+
const someVar = 3;
68+
const element = <foo str="123" bar={someVar == 3 ? true : false}/>\`;
69+
`;
70+
71+
TestHelper.assertViolations(ruleName, script, [
72+
{
73+
failure: ternaryExpressionErrorMessage,
74+
name: Utils.absolutePath('file.tsx'),
75+
ruleName: ruleName,
76+
startPosition: { character: 25, line: 4 }
77+
}
78+
]);
79+
});
80+
it('should fail if any attribute initializer is a ternary expression or a complex binary expression', (): void => {
81+
const script: string = `
82+
import React = require('react');
83+
const someVar = 3;
84+
const element = <foo str="123" bar={someVar == 3 ? true : false} bin={"value1" + someVar + "value2"/>\`;
85+
`;
86+
87+
TestHelper.assertViolations(ruleName, script, [
88+
{
89+
failure: ternaryExpressionErrorMessage,
90+
name: Utils.absolutePath('file.tsx'),
91+
ruleName: ruleName,
92+
startPosition: { character: 25, line: 4 }
93+
},
94+
{
95+
failure: binaryExpressionErrorMessage,
96+
name: Utils.absolutePath('file.tsx'),
97+
ruleName: ruleName,
98+
startPosition: { character: 25, line: 4 }
99+
}
100+
]);
101+
});
102+
});

src/useSimpleAttributesRule.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import * as ts from 'typescript';
2+
import * as Lint from 'tslint';
3+
4+
import { ExtendedMetadata } from './utils/ExtendedMetadata';
5+
import { getJsxAttributesFromJsxElement } from './utils/JsxAttribute';
6+
7+
export class Rule extends Lint.Rules.AbstractRule {
8+
public static metadata: ExtendedMetadata = {
9+
ruleName: 'use-simple-attributes',
10+
type: 'functionality',
11+
description: 'Enforce usage of only simple attribute types.',
12+
rationale:
13+
'Simpler attributes in JSX and TSX files helps keep code clean and readable.\
14+
Separate complex expressions into their own line and use clear variable names to make your code more understandable.',
15+
options: null, // tslint:disable-line:no-null-keyword
16+
optionsDescription: '',
17+
typescriptOnly: false,
18+
issueClass: 'Non-SDL',
19+
issueType: 'Error',
20+
severity: 'Important',
21+
level: 'Opportunity for Excellence',
22+
group: 'Correctness'
23+
};
24+
25+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
26+
return sourceFile.languageVariant === ts.LanguageVariant.JSX
27+
? this.applyWithWalker(new UseSimpleAttributesRuleWalker(sourceFile, this.getOptions()))
28+
: [];
29+
}
30+
}
31+
32+
class UseSimpleAttributesRuleWalker extends Lint.RuleWalker {
33+
protected visitJsxSelfClosingElement(node: ts.JsxSelfClosingElement): void {
34+
this.checkJsxOpeningElement(node);
35+
super.visitJsxSelfClosingElement(node);
36+
}
37+
38+
protected visitJsxElement(node: ts.JsxElement): void {
39+
this.checkJsxOpeningElement(node.openingElement);
40+
super.visitJsxElement(node);
41+
}
42+
43+
private checkJsxOpeningElement(node: ts.JsxOpeningLikeElement) {
44+
const attributes = getJsxAttributesFromJsxElement(node);
45+
for (const key of Object.keys(attributes)) {
46+
const attribute = attributes[key];
47+
48+
// Handle Binary Expressions
49+
const binaryExpression = <ts.BinaryExpression>this.getNextNodeRecursive(attribute, ts.SyntaxKind.BinaryExpression);
50+
if (binaryExpression && !this.isSimpleBinaryExpression(binaryExpression)) {
51+
const binaryExpressionErrorMessage: string = 'Attribute contains a complex binary expression';
52+
this.addFailureAt(node.getStart(), node.getWidth(), binaryExpressionErrorMessage);
53+
}
54+
55+
// Handle Ternary Expression
56+
const ternaryExpression = <ts.ConditionalExpression>this.getNextNodeRecursive(attribute, ts.SyntaxKind.ConditionalExpression);
57+
if (ternaryExpression) {
58+
const ternaryExpressionErrorMessage: string = 'Attribute contains a ternary expression';
59+
this.addFailureAt(node.getStart(), node.getWidth(), ternaryExpressionErrorMessage);
60+
}
61+
}
62+
}
63+
64+
private isSimpleBinaryExpression(binaryExpression: ts.BinaryExpression): boolean {
65+
if (binaryExpression.kind !== ts.SyntaxKind.BinaryExpression) {
66+
return false;
67+
}
68+
69+
// Both children of a Binary Expression should be primitives, constants or identifiers
70+
const allowedBinaryNodes: ts.SyntaxKind[] = [
71+
ts.SyntaxKind.NumericLiteral,
72+
ts.SyntaxKind.StringLiteral,
73+
ts.SyntaxKind.TrueKeyword,
74+
ts.SyntaxKind.FalseKeyword,
75+
ts.SyntaxKind.Identifier
76+
];
77+
78+
const leftTerm = allowedBinaryNodes.find(kind => kind === binaryExpression.left.kind);
79+
const rightTerm = allowedBinaryNodes.find(kind => kind === binaryExpression.right.kind);
80+
return leftTerm ? (rightTerm ? true : false) : false;
81+
}
82+
83+
private getNextNodeRecursive(node: ts.Node, kind: ts.SyntaxKind): ts.Node | undefined {
84+
if (!node) {
85+
return undefined;
86+
}
87+
const childNodes = node.getChildren();
88+
let match = childNodes.find(cn => cn.kind === kind);
89+
if (!match) {
90+
for (const childNode of childNodes) {
91+
match = this.getNextNodeRecursive(childNode, kind);
92+
}
93+
}
94+
return match;
95+
}
96+
}

tslint-warnings.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ unified-signatures,Warns for any two overloads that could be unified into one by
310310
use-default-type-parameter,Warns if an explicitly specified type argument is the default for that type parameter.,TSLINTLMNGTP,tslint,Non-SDL,Warning,Low,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,710,"CWE 710 - Coding Standards Violation"
311311
use-isnan,Enforces use of the `isNaN()` function to check for NaN references instead of a comparison to the `NaN` constant.,TSLINTPUV7LC,tslint,Non-SDL,Error,Critical,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,398,"CWE 398 - Indicator of Poor Code Quality"
312312
use-named-parameter,"Do not reference the arguments object by numerical index; instead, use a named parameter.",TSLINTKPEHQG,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,710,"CWE 710 - Coding Standards Violation"
313+
use-simple-attributes,Enforce usage of only simple attribute types.,TSLINT1PG0L9J,tslint,Non-SDL,Error,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
313314
valid-typeof,Ensures that the results of typeof are compared against a valid string.,TSLINT1IB59P1,tslint,Non-SDL,Error,Critical,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
314315
variable-name,Checks variable names for various errors.,TSLINT1CIV7K3,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,"398, 710","CWE 398 - Indicator of Poor Code Quality
315316
CWE 710 - Coding Standards Violation"

tslint.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
"underscore-consistent-invocation": true,
156156
"use-default-type-parameter": false,
157157
"use-named-parameter": true,
158+
"use-simple-attributes": true,
158159

159160
// tslint-microsoft-contrib rules disabled
160161
"missing-jsdoc": false,

0 commit comments

Comments
 (0)