diff --git a/src/configs/custom-rules.ts b/src/configs/custom-rules.ts index e6cd517..dc3cdf8 100644 --- a/src/configs/custom-rules.ts +++ b/src/configs/custom-rules.ts @@ -1,5 +1,6 @@ export const customRules = { rules: { + 'orthodox-getter-and-setter': true, 'blank-lines': [ true, { diff --git a/src/rules/orthodoxGetterAndSetterRule.ts b/src/rules/orthodoxGetterAndSetterRule.ts new file mode 100644 index 0000000..a250a56 --- /dev/null +++ b/src/rules/orthodoxGetterAndSetterRule.ts @@ -0,0 +1,75 @@ +import * as Lint from 'tslint'; +import * as tsutils from 'tsutils'; +import * as ts from 'typescript'; + + +/** + * Rule that enforces order getter property > setter property > private _property . + */ +export class Rule extends Lint.Rules.AbstractRule { + apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new Walker(sourceFile, this.getOptions())); + } +} + +class Walker extends Lint.RuleWalker { + visitGetAccessor(getter: ts.GetAccessorDeclaration) { + if (getter.parent && tsutils.isClassDeclaration(getter.parent)) { + const getterName = getter.name.getText(); + + const setter = getter.parent.members.find((member) => { + return tsutils.isSetAccessorDeclaration(member) && member.name.getText() === getterName; + }) as ts.SetAccessorDeclaration | undefined; + + if (setter && setter.pos < getter.pos) { + this.addFailureAtNode(getter, 'Getters must be declared before setters.'); + } + } + + super.visitGetAccessor(getter); + } + + visitSetAccessor(setter: ts.SetAccessorDeclaration) { + if (setter.parent && tsutils.isClassDeclaration(setter.parent)) { + const setterName = setter.name.getText(); + + const getter = setter.parent.members.find((member) => { + return tsutils.isGetAccessorDeclaration(member) && member.name.getText() === setterName; + }) as ts.GetAccessorDeclaration | undefined; + + if (getter && getter.pos > setter.pos) { + this.addFailureAtNode(setter, 'Setters must be declared after getters.'); + } + } + + super.visitSetAccessor(setter); + } + + visitPropertyDeclaration(property: ts.PropertyDeclaration) { + const propertyName = property.name.getText(); + const getterOrSetterName = propertyName.slice(1); + + if (propertyName.startsWith('_') && tsutils.hasModifier(property.modifiers, ts.SyntaxKind.PrivateKeyword)) { + const getter = property.parent.members.find((member) => { + return tsutils.isGetAccessorDeclaration(member) && member.name.getText() === getterOrSetterName; + }) as ts.GetAccessorDeclaration | undefined; + + const setter = property.parent.members.find((member) => { + return tsutils.isSetAccessorDeclaration(member) && member.name.getText() === getterOrSetterName; + }) as ts.SetAccessorDeclaration | undefined; + + if (!getter && !setter) { + this.addFailureAtNode(property, + 'Private property with leading underscore must be used only for getter or setter.'); + } + + if ((getter && property.pos < getter.pos) || + (setter && property && property.pos < setter.pos)) { + this.addFailureAtNode(property, + 'Private property for getter or setter must be declared after them.'); + } + } + + super.visitPropertyDeclaration(property); + } +} diff --git a/test/rules/orthodox-getter-and-setter/default/test.ts.lint b/test/rules/orthodox-getter-and-setter/default/test.ts.lint new file mode 100644 index 0000000..249d37e --- /dev/null +++ b/test/rules/orthodox-getter-and-setter/default/test.ts.lint @@ -0,0 +1,100 @@ +class Test1 { + get property() { + return this._property; + } + + set property(value) { + this._property = value; + } + + private _property: string; +} + +class Test2 { + set property(value) { + ~~~~~~~~~~~~~~~~~~~~~ + this._property = value; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + } +~~~~~ [Setters must be declared after getters.] + + get property() { + ~~~~~~~~~~~~~~~~ + return this._property; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + } +~~~~~ [Getters must be declared before setters.] + + private _property: string; +} + +class Test3 { + private _property: string; + ~~~~~~~~~~~~~~~~~~~~~~~~~~ [Private property for getter or setter must be declared after them.] + + get property() { + return this._property; + } + + set property(value) { + this._property = value; + } +} + +class Test4 { + private _property: string; + ~~~~~~~~~~~~~~~~~~~~~~~~~~ [Private property for getter or setter must be declared after them.] + + set property(value) { + this._property = value; + } +} + +class Test5 { + private _property: string; + ~~~~~~~~~~~~~~~~~~~~~~~~~~ [Private property for getter or setter must be declared after them.] + + get property() { + return this._property; + } +} + +class Test6 { + private _property: string; + ~~~~~~~~~~~~~~~~~~~~~~~~~~ [Private property with leading underscore must be used only for getter or setter.] +} + +class Test7 { + set property(value) { + this._property = value; + } + + private _property: string; +} + +class Test8 { + get property() { + return this._property; + } + + private _property: string; +} + +class Test9 { + private _property: string; + ~~~~~~~~~~~~~~~~~~~~~~~~~~ [Private property for getter or setter must be declared after them.] + + set property(value) { + ~~~~~~~~~~~~~~~~~~~~~ + this._property = value; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + } +~~~~~ [Setters must be declared after getters.] + + get property() { + ~~~~~~~~~~~~~~~~ + return this._property; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + } +~~~~~ [Getters must be declared before setters.] +} diff --git a/test/rules/orthodox-getter-and-setter/default/tslint.json b/test/rules/orthodox-getter-and-setter/default/tslint.json new file mode 100644 index 0000000..96a9755 --- /dev/null +++ b/test/rules/orthodox-getter-and-setter/default/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "orthodox-getter-and-setter": true + } +}