diff --git a/README.md b/README.md index 571f1f74d..b4a03e83d 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,7 @@ Below you can find a recommended configuration which is based on the [Angular St "use-host-property-decorator": true, "no-attribute-parameter-decorator": true, "no-input-rename": true, + "no-on-prefix-output-name": true, "no-output-rename": true, "no-forward-ref": true, "use-life-cycle-interface": true, @@ -292,6 +293,8 @@ module.exports = { [leosvelperez](https://github.com/leosvelperez) |[rtfpessoa](https://github.com/rtfpessoa) |[scttcper](https://github.com/scttcper) |[laco0416](https://github.com/laco0416) |[tmair](https://github.com/tmair) |[cexbrayat](https://github.com/cexbrayat) | :---: |:---: |:---: |:---: |:---: |:---: | [leosvelperez](https://github.com/leosvelperez) |[rtfpessoa](https://github.com/rtfpessoa) |[scttcper](https://github.com/scttcper) |[laco0416](https://github.com/laco0416) |[tmair](https://github.com/tmair) |[cexbrayat](https://github.com/cexbrayat) | +[eromano](https://github.com/eromano) | | | | | | +[eromano](https://github.com/eromano) | | | | || ## License diff --git a/docs/src/worker.ts b/docs/src/worker.ts index 61cec9838..7ef10cd8c 100644 --- a/docs/src/worker.ts +++ b/docs/src/worker.ts @@ -10,6 +10,7 @@ const rulesConfig = { 'use-host-property-decorator': true, 'no-input-rename': true, 'no-output-rename': true, + 'no-on-prefix-output-name': true, 'use-life-cycle-interface': true, 'use-pipe-transform-interface': true, 'component-class-suffix': true, diff --git a/package.json b/package.json index 275747ef3..e8e9c65c3 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "contributors": [ "Minko Gechev ", "Preslav Semov ", - "William Koza " + "William Koza ", + "Eugenio Romano " ], "repository": { "type": "git", diff --git a/src/index.ts b/src/index.ts index 5ba35a8ad..bf23fa263 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ export { Rule as ImportDestructuringSpacingRule } from './importDestructuringSpa export { Rule as NoAttributeParameterDecoratorRule } from './noAttributeParameterDecoratorRule'; export { Rule as NoForwardRefRule } from './noForwardRefRule'; export { Rule as NoInputRenameRule } from './noInputRenameRule'; +export { Rule as NoOutputOnPrefixNameRule } from './noOutputOnPrefixNameRule'; export { Rule as NoOutputRenameRule } from './noOutputRenameRule'; export { Rule as NoUnusedCssRule } from './noUnusedCssRule'; export { Rule as PipeImpureRule } from './pipeImpureRule'; diff --git a/src/noOutputOnPrefixNameRule.ts b/src/noOutputOnPrefixNameRule.ts new file mode 100644 index 000000000..ceb35181d --- /dev/null +++ b/src/noOutputOnPrefixNameRule.ts @@ -0,0 +1,44 @@ +import * as Lint from 'tslint'; +import * as ts from 'typescript'; +import { sprintf } from 'sprintf-js'; +import { NgWalker } from './angular/ngWalker'; + +export class Rule extends Lint.Rules.AbstractRule { + public static metadata: Lint.IRuleMetadata = { + ruleName: 'no-on-prefix-output-name', + type: 'maintainability', + description: `Name events without the prefix on`, + descriptionDetails: `See more at https://angular.io/guide/styleguide#dont-prefix-output-properties`, + rationale: `Angular allows for an alternative syntax on-*. If the event itself was prefixed with on + this would result in an on-onEvent binding expression`, + options: null, + optionsDescription: `Not configurable.`, + typescriptOnly: true, + }; + + static FAILURE_STRING: string = 'In the class "%s", the output ' + + 'property "%s" should not be prefixed with on'; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker( + new OutputWalker(sourceFile, + this.getOptions())); + } +} + +export class OutputWalker extends NgWalker { + visitNgOutput(property: ts.PropertyDeclaration, output: ts.Decorator, args: string[]) { + let className = (property).parent.name.text; + let memberName = (property.name).text; + + if (memberName && memberName.startsWith('on')) { + let failureConfig: string[] = [className, memberName]; + failureConfig.unshift(Rule.FAILURE_STRING); + this.addFailure( + this.createFailure( + property.getStart(), + property.getWidth(), + sprintf.apply(this, failureConfig))); + } + } +} diff --git a/test/noOutputOnPrefixNameRule.spec.ts b/test/noOutputOnPrefixNameRule.spec.ts new file mode 100644 index 000000000..e1f2a1bcd --- /dev/null +++ b/test/noOutputOnPrefixNameRule.spec.ts @@ -0,0 +1,27 @@ +import { assertSuccess, assertAnnotated } from './testHelper'; + +describe('no-on-prefix-output-name', () => { + describe('invalid directive output property', () => { + it(`should fail, when a directive output property is named with on prefix`, () => { + let source = ` + class ButtonComponent { + @Output() onChange = new EventEmitter(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + }`; + assertAnnotated({ + ruleName: 'no-on-prefix-output-name', + source + }); + }); + }); + + describe('valid directive output property', () => { + it('should succeed, when a directive output property is properly named', () => { + let source = ` + class ButtonComponent { + @Output() change = new EventEmitter(); + }`; + assertSuccess('no-on-prefix-output-name', source); + }); + }); +});