This repository has been archived by the owner on Jul 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 198
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* 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
- Loading branch information
1 parent
42bb696
commit 4b91110
Showing
6 changed files
with
211 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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 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 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,102 @@ | ||
import { Utils } from '../utils/Utils'; | ||
import { TestHelper } from './TestHelper'; | ||
/** | ||
* Unit tests. | ||
*/ | ||
describe('useSimpleAttributesRule', (): void => { | ||
const ruleName: string = 'use-simple-attributes'; | ||
const binaryExpressionErrorMessage: string = 'Attribute contains a complex binary expression'; | ||
const ternaryExpressionErrorMessage: string = 'Attribute contains a ternary expression'; | ||
it('should fail if only attribute initializer is a complex binary expression', (): void => { | ||
const script: string = ` | ||
import React = require('react'); | ||
const element = <foo bar={"value1" + "value2" + "value3"}/>\`; | ||
`; | ||
|
||
TestHelper.assertViolations(ruleName, script, [ | ||
{ | ||
failure: binaryExpressionErrorMessage, | ||
name: Utils.absolutePath('file.tsx'), | ||
ruleName: ruleName, | ||
startPosition: { character: 25, line: 3 } | ||
} | ||
]); | ||
}); | ||
it('should fail if any attribute initializer is a complex binary expression', (): void => { | ||
const script: string = ` | ||
import React = require('react'); | ||
const element = <foo str="hello" bar={"value1" + "value2" + "value3"}/>\`; | ||
`; | ||
|
||
TestHelper.assertViolations(ruleName, script, [ | ||
{ | ||
failure: binaryExpressionErrorMessage, | ||
name: Utils.absolutePath('file.tsx'), | ||
ruleName: ruleName, | ||
startPosition: { character: 25, line: 3 } | ||
} | ||
]); | ||
}); | ||
it('should pass if any attribute initializer is a simple binary expression', (): void => { | ||
const script: string = ` | ||
import React = require('react'); | ||
const element = <foo str="hello" bar={"value1" + "value2"}/>\`; | ||
`; | ||
|
||
TestHelper.assertNoViolation(ruleName, script); | ||
}); | ||
it('should fail if only attribute initializer is a ternary expression', (): void => { | ||
const script: string = ` | ||
import React = require('react'); | ||
const someVar = 3; | ||
const element = <foo bar={someVar == 3 ? true : false}/>\`; | ||
`; | ||
|
||
TestHelper.assertViolations(ruleName, script, [ | ||
{ | ||
failure: ternaryExpressionErrorMessage, | ||
name: Utils.absolutePath('file.tsx'), | ||
ruleName: ruleName, | ||
startPosition: { character: 25, line: 4 } | ||
} | ||
]); | ||
}); | ||
it('should fail if any attribute initializer is a ternary expression', (): void => { | ||
const script: string = ` | ||
import React = require('react'); | ||
const someVar = 3; | ||
const element = <foo str="123" bar={someVar == 3 ? true : false}/>\`; | ||
`; | ||
|
||
TestHelper.assertViolations(ruleName, script, [ | ||
{ | ||
failure: ternaryExpressionErrorMessage, | ||
name: Utils.absolutePath('file.tsx'), | ||
ruleName: ruleName, | ||
startPosition: { character: 25, line: 4 } | ||
} | ||
]); | ||
}); | ||
it('should fail if any attribute initializer is a ternary expression or a complex binary expression', (): void => { | ||
const script: string = ` | ||
import React = require('react'); | ||
const someVar = 3; | ||
const element = <foo str="123" bar={someVar == 3 ? true : false} bin={"value1" + someVar + "value2"/>\`; | ||
`; | ||
|
||
TestHelper.assertViolations(ruleName, script, [ | ||
{ | ||
failure: ternaryExpressionErrorMessage, | ||
name: Utils.absolutePath('file.tsx'), | ||
ruleName: ruleName, | ||
startPosition: { character: 25, line: 4 } | ||
}, | ||
{ | ||
failure: binaryExpressionErrorMessage, | ||
name: Utils.absolutePath('file.tsx'), | ||
ruleName: ruleName, | ||
startPosition: { character: 25, line: 4 } | ||
} | ||
]); | ||
}); | ||
}); |
This file contains 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,96 @@ | ||
import * as ts from 'typescript'; | ||
import * as Lint from 'tslint'; | ||
|
||
import { ExtendedMetadata } from './utils/ExtendedMetadata'; | ||
import { getJsxAttributesFromJsxElement } from './utils/JsxAttribute'; | ||
|
||
export class Rule extends Lint.Rules.AbstractRule { | ||
public static metadata: ExtendedMetadata = { | ||
ruleName: 'use-simple-attributes', | ||
type: 'functionality', | ||
description: 'Enforce usage of only simple attribute types.', | ||
rationale: | ||
'Simpler attributes in JSX and TSX files helps keep code clean and readable.\ | ||
Separate complex expressions into their own line and use clear variable names to make your code more understandable.', | ||
options: null, // tslint:disable-line:no-null-keyword | ||
optionsDescription: '', | ||
typescriptOnly: false, | ||
issueClass: 'Non-SDL', | ||
issueType: 'Error', | ||
severity: 'Important', | ||
level: 'Opportunity for Excellence', | ||
group: 'Correctness' | ||
}; | ||
|
||
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { | ||
return sourceFile.languageVariant === ts.LanguageVariant.JSX | ||
? this.applyWithWalker(new UseSimpleAttributesRuleWalker(sourceFile, this.getOptions())) | ||
: []; | ||
} | ||
} | ||
|
||
class UseSimpleAttributesRuleWalker extends Lint.RuleWalker { | ||
protected visitJsxSelfClosingElement(node: ts.JsxSelfClosingElement): void { | ||
this.checkJsxOpeningElement(node); | ||
super.visitJsxSelfClosingElement(node); | ||
} | ||
|
||
protected visitJsxElement(node: ts.JsxElement): void { | ||
this.checkJsxOpeningElement(node.openingElement); | ||
super.visitJsxElement(node); | ||
} | ||
|
||
private checkJsxOpeningElement(node: ts.JsxOpeningLikeElement) { | ||
const attributes = getJsxAttributesFromJsxElement(node); | ||
for (const key of Object.keys(attributes)) { | ||
const attribute = attributes[key]; | ||
|
||
// Handle Binary Expressions | ||
const binaryExpression = <ts.BinaryExpression>this.getNextNodeRecursive(attribute, ts.SyntaxKind.BinaryExpression); | ||
if (binaryExpression && !this.isSimpleBinaryExpression(binaryExpression)) { | ||
const binaryExpressionErrorMessage: string = 'Attribute contains a complex binary expression'; | ||
this.addFailureAt(node.getStart(), node.getWidth(), binaryExpressionErrorMessage); | ||
} | ||
|
||
// Handle Ternary Expression | ||
const ternaryExpression = <ts.ConditionalExpression>this.getNextNodeRecursive(attribute, ts.SyntaxKind.ConditionalExpression); | ||
if (ternaryExpression) { | ||
const ternaryExpressionErrorMessage: string = 'Attribute contains a ternary expression'; | ||
this.addFailureAt(node.getStart(), node.getWidth(), ternaryExpressionErrorMessage); | ||
} | ||
} | ||
} | ||
|
||
private isSimpleBinaryExpression(binaryExpression: ts.BinaryExpression): boolean { | ||
if (binaryExpression.kind !== ts.SyntaxKind.BinaryExpression) { | ||
return false; | ||
} | ||
|
||
// Both children of a Binary Expression should be primitives, constants or identifiers | ||
const allowedBinaryNodes: ts.SyntaxKind[] = [ | ||
ts.SyntaxKind.NumericLiteral, | ||
ts.SyntaxKind.StringLiteral, | ||
ts.SyntaxKind.TrueKeyword, | ||
ts.SyntaxKind.FalseKeyword, | ||
ts.SyntaxKind.Identifier | ||
]; | ||
|
||
const leftTerm = allowedBinaryNodes.find(kind => kind === binaryExpression.left.kind); | ||
const rightTerm = allowedBinaryNodes.find(kind => kind === binaryExpression.right.kind); | ||
return leftTerm ? (rightTerm ? true : false) : false; | ||
} | ||
|
||
private getNextNodeRecursive(node: ts.Node, kind: ts.SyntaxKind): ts.Node | undefined { | ||
if (!node) { | ||
return undefined; | ||
} | ||
const childNodes = node.getChildren(); | ||
let match = childNodes.find(cn => cn.kind === kind); | ||
if (!match) { | ||
for (const childNode of childNodes) { | ||
match = this.getNextNodeRecursive(childNode, kind); | ||
} | ||
} | ||
return match; | ||
} | ||
} |
This file contains 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 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