Skip to content

Commit

Permalink
feat(rules): add ion-col-attributes-renamed rule (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
imhoffd authored and cwoolum committed Jul 6, 2018
1 parent 098bd7f commit 2cb436f
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 4 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,14 @@ We are looking for contributors to help build these rules out! See [`CONTRIBUTIN
<th>
<a href="https://github.com/ionic-team/ionic/blob/master/angular/BREAKING.md#grid">Grid</a>
</th>
<td></td>
<td>:white_large_square:</td>
<td>:wrench:</td>
<td>:white_check_mark:</td>
<td>
<code>ion-grid-attributes-renamed</code>
<code>ion-col-attributes-renamed</code>
</td>
<td>
<a href="https://github.com/dwieeb">@dwieeb</a>
</td>
<td></td>
</tr>
<tr>
<th>
Expand Down
48 changes: 48 additions & 0 deletions src/ionColAttributesRenamedRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { NgWalker } from 'codelyzer/angular/ngWalker';
import * as Lint from 'tslint';
import * as ts from 'typescript';

import { createAttributesRenamedTemplateVisitorClass } from './helpers/attributesRenamed';

export const ruleName = 'ion-col-attributes-renamed';

const formatOldAttr = (prefix: string, breakpoint: string | undefined, value: string) =>
`${prefix}${typeof breakpoint === 'undefined' ? '' : `-${breakpoint}`}-${value}`;
const formatNewAttr = (prefix: string, breakpoint: string | undefined, value: string) =>
`${prefix}${typeof breakpoint === 'undefined' ? '' : `-${breakpoint}`}="${value}"`;

const attrPrefixMap = new Map([['col', 'size'], ['offset', 'offset'], ['push', 'push'], ['pull', 'pull']]);
const breakpoints = [undefined, 'xs', 'sm', 'md', 'lg', 'xl'];
const values = Array(12)
.fill(undefined)
.map((v, i) => String(i + 1));

const replacementPairs: [string, string][] = [].concat(
...[...attrPrefixMap.entries()].map(([oldPrefix, newPrefix]) =>
[].concat(...breakpoints.map(b => values.map(v => [formatOldAttr(oldPrefix, b, v), formatNewAttr(newPrefix, b, v)])))
)
);

const replacementMap = new Map(replacementPairs);

const IonGridAttributesRenamedTemplateVisitor = createAttributesRenamedTemplateVisitorClass('ion-col', replacementMap);

export class Rule extends Lint.Rules.AbstractRule {
public static metadata: Lint.IRuleMetadata = {
ruleName: ruleName,
type: 'functionality',
description: 'Attributes of ion-col have been renamed.',
options: null,
optionsDescription: 'Not configurable.',
typescriptOnly: false,
hasFix: true
};

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(
new NgWalker(sourceFile, this.getOptions(), {
templateVisitorCtrl: IonGridAttributesRenamedTemplateVisitor
})
);
}
}
146 changes: 146 additions & 0 deletions test/ionColAttributesRenamed.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { expect } from 'chai';
import { Replacement, Utils } from 'tslint';
import { ruleName } from '../src/ionColAttributesRenamedRule';
import { assertAnnotated, assertFailure, assertFailures, assertMultipleAnnotated, assertSuccess } from './testHelper';

describe(ruleName, () => {
describe('success', () => {
it('should work with proper size attribute', () => {
let source = `
@Component({
template: \`<ion-col size="3"></ion-col>\`
})
class Bar {}
`;
assertSuccess(ruleName, source);
});

it('should work with proper offset attribute', () => {
let source = `
@Component({
template: \`<ion-col offset="3"></ion-col>\`
})
class Bar {}
`;
assertSuccess(ruleName, source);
});
});

describe('failure', () => {
it('should fail when col-3 is used', () => {
let source = `
@Component({
template: \`
<ion-col col-3>
~~~~~
</ion-col>
\`
})
class Bar {}
`;

assertAnnotated({
ruleName,
message: 'The col-3 attribute of ion-col has been renamed. Use size="3" instead.',
source
});
});

it('should fail when col-xs-3 is used', () => {
let source = `
@Component({
template: \`
<ion-col col-xs-3>
~~~~~~~~
</ion-col>
\`
})
class Bar {}
`;

assertAnnotated({
ruleName,
message: 'The col-xs-3 attribute of ion-col has been renamed. Use size-xs="3" instead.',
source
});
});
});

describe('replacements', () => {
it('should replace col-3 with size="3"', () => {
let source = `
@Component({
template: \`
<ion-col col-3></ion-col>
\`
})
class Bar {}
`;

const fail = {
message: 'The col-3 attribute of ion-col has been renamed. Use size="3" instead.',
startPosition: {
line: 3,
character: 21
},
endPosition: {
line: 3,
character: 26
}
};

const failures = assertFailure(ruleName, source, fail);
const fixes = failures.map(f => f.getFix());
const res = Replacement.applyAll(source, Utils.flatMap(fixes, Utils.arrayify));

let expected = `
@Component({
template: \`
<ion-col size="3"></ion-col>
\`
})
class Bar {}
`;

expect(res).to.eq(expected);
});

it('should replace col-xs-3 with size-xs="3"', () => {
let source = `
@Component({
template: \`
<ion-col col-xs-3></ion-col>
\`
})
class Bar {}
`;

const fail = {
message: 'The col-xs-3 attribute of ion-col has been renamed. Use size-xs="3" instead.',
startPosition: {
line: 3,
character: 21
},
endPosition: {
line: 3,
character: 29
}
};

const failures = assertFailure(ruleName, source, fail);
const fixes = failures.map(f => f.getFix());
const res = Replacement.applyAll(source, Utils.flatMap(fixes, Utils.arrayify));

let expected = `
@Component({
template: \`
<ion-col size-xs="3"></ion-col>
\`
})
class Bar {}
`;

expect(res).to.eq(expected);
});
});
});

0 comments on commit 2cb436f

Please sign in to comment.