Skip to content

feat(rule): add special case to no-input-rename rule #561

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions src/noInputRenameRule.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as Lint from 'tslint';
import * as ts from 'typescript';
import { sprintf } from 'sprintf-js';
import { DirectiveMetadata } from './angular/metadata';
import { NgWalker } from './angular/ngWalker';

export class Rule extends Lint.Rules.AbstractRule {
Expand All @@ -15,29 +16,31 @@ export class Rule extends Lint.Rules.AbstractRule {
typescriptOnly: true,
};

static FAILURE_STRING: string = 'In the class "%s", the directive ' +
'input property "%s" should not be renamed.' +
'Please, consider the following use "@Input() %s: string"';
static FAILURE_STRING: string = 'In the class "%s", the directive input property "%s" should not be renamed.';

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(
new InputMetadataWalker(sourceFile,
this.getOptions()));
return this.applyWithWalker(new InputMetadataWalker(sourceFile, this.getOptions()));
}
}

export class InputMetadataWalker extends NgWalker {
private directiveSelector: DirectiveMetadata['selector'][];

visitNgDirective(metadata: DirectiveMetadata): void {
this.directiveSelector =
(metadata.selector || '').replace(/[\[\]\s]/g, '').split(',');
}

visitNgInput(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
let className = (<any>property).parent.name.text;
let memberName = (<any>property.name).text;
if (args.length !== 0 && memberName !== args[0]) {
let failureConfig: string[] = [className, memberName, memberName];
failureConfig.unshift(Rule.FAILURE_STRING);
this.addFailure(
this.createFailure(
property.getStart(),
property.getWidth(),
sprintf.apply(this, failureConfig)));
const className = (property.parent as any).name.text;
const memberName = (property.name as any).text;

if (args.length === 0 ||
(this.directiveSelector && this.directiveSelector.indexOf(memberName) !== -1)) {
return;
}

const failureConfig = [Rule.FAILURE_STRING, className, memberName];
this.addFailureAtNode(property, sprintf.apply(this, failureConfig));
}
}
126 changes: 100 additions & 26 deletions test/noInputRenameRule.spec.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,111 @@
import { assertSuccess, assertAnnotated } from './testHelper';

describe('no-input-rename', () => {
describe('invalid directive input property', () => {
it('should fail, when a directive input property is renamed', () => {
let source = `
class ButtonComponent {
@Input('labelAttribute') label: string;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}`;
assertAnnotated({
ruleName: 'no-input-rename',
message: 'In the class "ButtonComponent", the directive input property "label" should not be renamed.' +
'Please, consider the following use "@Input() label: string"',
source
const ruleName = 'no-input-rename';

const getMessage = (className: string, propertyName: string): string => {
return `In the class "${className}", the directive input property "${propertyName}" should not be renamed.`;
};

describe(ruleName, () => {
describe('failure', () => {
describe('Component', () => {
it('should fail when a input property is renamed', () => {
const source = `
@Component
class TestComponent {
@Input('labelAttribute') label: string;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;

assertAnnotated({
ruleName,
message: getMessage('TestComponent', 'label'),
source
});
});

it('should fail when input property is fake renamed', () => {
const source = `
@Component
class TestComponent {
@Input('label') label: string;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;

assertAnnotated({
ruleName,
message: getMessage('TestComponent', 'label'),
source
});
});
});

describe('Directive', () => {
it('should fail when a input property is renamed', () => {
const source = `
@Directive
class TestDirective {
@Input('labelText') label: string;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;

assertAnnotated({
ruleName,
message: getMessage('TestDirective', 'label'),
source
});
});

it(`should fail when input property is renamed and it's different from directive's selector`, () => {
const source = `
@Directive({
selector: '[label], label2'
})
class TestDirective {
@Input('label') labelText: string;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
`;

assertAnnotated({
ruleName,
message: getMessage('TestDirective', `labelText`),
source
});
});
});
});

describe('valid directive input property', () => {
it('should succeed, when a directive input property is properly used', () => {
let source = `
class ButtonComponent {
@Input() label: string;
}`;
assertSuccess('no-input-rename', source);
describe('success', () => {
describe('Component', () => {
it('should succeed when a input property is not renamed', () => {
const source = `
@Component
class TestComponent {
@Input() label: string;
}
`;

assertSuccess(ruleName, source);
});
});

it('should succeed, when a directive input property rename is the same as the name of the property', () => {
let source = `
class ButtonComponent {
@Input('label') label: string;
}`;
assertSuccess('no-input-rename', source);
describe('Directive', () => {
it('should succeed when the directive name is also an input property', () => {
const source = `
@Directive({
selector: '[label], label2'
})
class TestDirective {
@Input('labelText') label: string;
}
`;

assertSuccess(ruleName, source);
});
});
});
});