github-desktop/tslint-rules/reactReadonlyPropsAndStateRule.ts
2020-06-04 14:19:11 +02:00

87 lines
2.5 KiB
TypeScript

/**
* react-readonly-props-and-state
*
* This custom tslint rule is highly specific to GitHub Desktop and attempts
* to prevent props and state interfaces from being declared with mutable
* members.
*
* While it's technically possible to modify this.props there's never a good
* reason to do so and marking our interfaces as read only ensures that we
* get compiler support for that fact.
*/
import * as ts from 'typescript'
import * as Lint from 'tslint'
// The walker takes care of all the work.
class ReactReadonlyPropsAndStateWalker extends Lint.RuleWalker {
protected visitInterfaceDeclaration(node: ts.InterfaceDeclaration): void {
if (node.name.text.endsWith('Props')) {
this.ensureReadOnly(node.members)
}
if (node.name.text.endsWith('State')) {
this.ensureReadOnly(node.members)
}
super.visitInterfaceDeclaration(node)
}
private ensureReadOnly(members: ts.NodeArray<ts.TypeElement>) {
members.forEach(member => {
if (member.kind !== ts.SyntaxKind.PropertySignature) {
return
}
const propertySignature = member as ts.PropertySignature
if (!this.isReadOnly(propertySignature)) {
const start = propertySignature.getStart()
const width = propertySignature.getWidth()
const error = `Property and state signatures should be read-only`
this.addFailure(this.createFailure(start, width, error))
}
if (propertySignature.type) {
const typeString = propertySignature.type.getFullText()
if (/^\s*Array<.*>$/.test(typeString) || typeString.endsWith('[]')) {
this.addFailure(
this.createFailure(
propertySignature.type.getStart(),
propertySignature.type.getWidth(),
'Prop and State arrays should be read only (ReadOnlyArray)'
)
)
}
}
})
}
private isReadOnly(propertySignature: ts.PropertySignature): boolean {
const modifiers = propertySignature.modifiers
if (!modifiers) {
return false
}
if (modifiers.find(m => m.kind === ts.SyntaxKind.ReadonlyKeyword)) {
return true
}
return false
}
}
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithWalker(
new ReactReadonlyPropsAndStateWalker(sourceFile, this.getOptions())
)
} else {
return []
}
}
}