Skip to content

Commit

Permalink
feat(datetime): add check for import change
Browse files Browse the repository at this point in the history
  • Loading branch information
mhartington committed Jun 27, 2018
1 parent 16316a7 commit 1a9928b
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .angulardoc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"repoId": "a74b340a-2476-460e-8399-b773fa83fda6",
"lastSync": 0
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { Rule as AlertTitleAndSubtitleAreNowHeaderAndSubheader } from './ionAler
export { Rule as IonButtonAttributesAreRenamedRule } from './ionButtonAttributesAreRenamedRule';
export { Rule as IonButtonIsNowAnElementRule } from './ionButtonIsNowAnElementRule';
export { Rule as IonChipMarkupChangedRule } from './ionChipMarkupHasChangedRule';
export { Rule as ionDatetimeCapitalizationChangedRule } from './ionDatetimeCapitalizationChangedRule';
export { Rule as IonNavbarIsNowIonToolbarRule } from './ionNavbarIsNowIonToolbarRule';
export { Rule as IonTabBadgeStyleIsNowBadgeStyle } from './ionTabBadgeStyleIsNowBadgeStyleRule';
export { Rule as IonTabIconIsNowIcon } from './ionTabIconIsNowIconRule';
Expand Down
103 changes: 103 additions & 0 deletions src/ionDatetimeCapitalizationChangedRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import * as Lint from 'tslint';
import * as ts from 'typescript';

export const ruleName = 'ion-datetime-capitalization-changed';
export class Rule extends Lint.Rules.AbstractRule {
public static metadata: Lint.IRuleMetadata = {
ruleName,
type: 'functionality',
description: 'Updates the Datetime import',
rationale: 'Datetime exported symbol has changed',
options: null,
optionsDescription: 'Not configurable.',
typescriptOnly: true
};

static RuleFailure = 'outdated import path';

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

class UpdateImportsWalker extends Lint.RuleWalker {
visitImportDeclaration(node: ts.ImportDeclaration): void {
if (ts.isStringLiteral(node.moduleSpecifier) && node.importClause) {
const specifier = node.moduleSpecifier;
const path = (specifier as ts.StringLiteral).text;
const start = specifier.getStart() + 1;
const end = specifier.text.length;
const replacementStart = start;
const replacementEnd = specifier.text.length;
let replacement = null;

// Try to find updated symbol names.
ImportReplacements.forEach(r => (r.path === path ? this._migrateExportedSymbols(r, node) : void 0));

// Try to migrate entire import path updates.
if (ImportMap.has(path)) {
replacement = ImportMap.get(path);
}
if (replacement !== null) {
return this.addFailureAt(start, end, Rule.RuleFailure, this.createReplacement(replacementStart, replacementEnd, replacement));
}
}
}

private _migrateExportedSymbols(re: ImportReplacement, node: ts.ImportDeclaration) {
const importClause = node.importClause as ts.ImportClause;
const bindings = importClause.namedBindings as ts.NamedImports | null;
if (!bindings || bindings.kind !== ts.SyntaxKind.NamedImports) {
return;
}

bindings.elements.forEach((e: ts.ImportSpecifier | null) => {
if (!e || e.kind !== ts.SyntaxKind.ImportSpecifier) {
return;
}

let toReplace = e.name;
// We don't want to introduce type errors so we alias the old new symbol.
let replacement = `${re.newSymbol} as ${re.symbol}`;
if (e.propertyName) {
toReplace = e.propertyName;
replacement = re.newSymbol;
}

if (toReplace.getText() !== re.symbol) {
return;
}

return this.addFailureAt(
toReplace.getStart(),
toReplace.getWidth(),
'imported symbol no longer exists',
this.createReplacement(toReplace.getStart(), toReplace.getWidth(), replacement)
);
});
}
}

const ImportMap = new Map([['ionic-angular', '@ionic/angular']]);

interface ImportReplacement {
path: string;
symbol: string;
newPath: string;
newSymbol: string;
}

const ImportReplacements = [
{
path: 'ionic-angular',
symbol: 'DateTime',
newPath: '@ionic/angular',
newSymbol: 'Datetime'
},
{
path: '@ionic/angular',
symbol: 'DateTime',
newPath: '@ionic/angular',
newSymbol: 'Datetime'
}
];
55 changes: 55 additions & 0 deletions test/ionDatetimeCapitalizationChanged.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ruleName } from '../src/ionDatetimeCapitalizationChangedRule';
import { assertAnnotated, assertSuccess, assertMultipleAnnotated } from './testHelper';

describe(ruleName, () => {
describe('success', () => {
it('should work when the import and path are correct', () => {
let source = `
import {Datetime} from '@ionic/angular';
`;
assertSuccess(ruleName, source);
});
});
describe('failure', () => {
it('should fail when symbol is right, but path is wrong', () => {
let source = `
import {Datetime} from '@ionic/angular';
`;
assertAnnotated({
ruleName,
source,
message: 'outdated import path'
});
});
it('should fail when symbol is wrong, but path is right', () => {
let source = `
import {DateTime} from '@ionic/angular';
`;
assertAnnotated({
ruleName,
source,
message: 'imported symbol no longer exists'
});
});
it('should fail when symbol and path are wrong', () => {
let source = `
import {DateTime} from 'ionic-angular';
`;
assertAnnotated({
ruleName,
source,
message: 'imported symbol no longer exists'
});
});
it('should fail when symbol and path are wrong', () => {
let source = `
import {DateTime} from 'ionic-angular';
`;
assertAnnotated({
ruleName,
source,
message: 'outdated import path'
});
});
});
});

0 comments on commit 1a9928b

Please sign in to comment.