Skip to content

Commit 81038d3

Browse files
imhoffdcwoolum
authored andcommitted
fix(rules): fix colliding rules (#23)
* fix(rules): fix colliding rules * fix(rules): fix colliding rules fixes #17 alternative to #21
1 parent fe87a47 commit 81038d3

5 files changed

+228
-206
lines changed

src/helpers/parametersRenamed.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import * as Lint from 'tslint';
2+
import * as ts from 'typescript';
3+
import * as tsutils from 'tsutils';
4+
5+
/**
6+
* Currently only supports a call exactly like this:
7+
*
8+
* ```
9+
* this.ctrl.foo({ prop1: <value>, prop2: <value> })
10+
* ```
11+
*
12+
* `this.ctrl` must refer to a provider by name in the constructor of a class
13+
* using Angular dependency injection
14+
*
15+
* @param methodName would be `foo` in example above
16+
* @param providerName the provider class name
17+
* @param parameterMap a map of properties to rename in the object literal above
18+
*/
19+
export function createParametersRenamedClass(methodName: string, providerName: string, parameterMap: Map<string, string>) {
20+
return class extends Lint.RuleWalker {
21+
visitCallExpression(node: ts.CallExpression) {
22+
debugger;
23+
if (node.arguments.length > 0) {
24+
const firstArgument = node.arguments[0];
25+
26+
if (isValidForRule(node, methodName, providerName) && tsutils.isObjectLiteralExpression(firstArgument)) {
27+
for (const prop of firstArgument.properties) {
28+
if (tsutils.isPropertyAssignment(prop)) {
29+
const propName = tsutils.getPropertyName(prop.name);
30+
const replacement = parameterMap.get(propName);
31+
32+
if (replacement) {
33+
this.addFailureAtNode(
34+
prop.name,
35+
`Property ${propName} has been renamed to ${replacement}.`,
36+
new Lint.Replacement(prop.name.getStart(), prop.name.getWidth(), replacement)
37+
);
38+
}
39+
}
40+
}
41+
}
42+
}
43+
44+
super.visitCallExpression(node);
45+
}
46+
};
47+
}
48+
49+
function isValidForRule(node: ts.CallExpression, methodName: string, providerName: string): boolean {
50+
const expression = node.expression;
51+
52+
if (
53+
tsutils.isPropertyAccessExpression(expression) &&
54+
expression.name.text === methodName &&
55+
tsutils.isPropertyAccessExpression(expression.expression) &&
56+
expression.expression.expression.kind === ts.SyntaxKind.ThisKeyword
57+
) {
58+
const classNode = findDeclarativeClass(node);
59+
const controllerName = expression.expression.name.text;
60+
61+
if (classNode) {
62+
const constructorNode = classNode.members.find(n => tsutils.isConstructorDeclaration(n));
63+
64+
if (constructorNode && tsutils.isConstructorDeclaration(constructorNode)) {
65+
const controllerParameter = constructorNode.parameters.find(
66+
p => tsutils.isTypeReferenceNode(p.type) && tsutils.isIdentifier(p.name) && p.name.text === controllerName
67+
);
68+
69+
if (
70+
controllerParameter &&
71+
tsutils.isTypeReferenceNode(controllerParameter.type) &&
72+
tsutils.isIdentifier(controllerParameter.type.typeName) &&
73+
controllerParameter.type.typeName.text === providerName
74+
) {
75+
return true;
76+
}
77+
}
78+
}
79+
}
80+
81+
return false;
82+
}
83+
84+
function findDeclarativeClass(node: ts.Node): ts.ClassDeclaration | undefined {
85+
if (!node) {
86+
return;
87+
}
88+
89+
if (tsutils.isClassDeclaration(node)) {
90+
return node;
91+
}
92+
93+
return findDeclarativeClass(node.parent);
94+
}
Lines changed: 5 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,12 @@
11
import * as Lint from 'tslint';
2-
import { IOptions, Replacement } from 'tslint';
32
import * as ts from 'typescript';
43

5-
export const ruleName = 'ion-action-sheet-method-create-parameters-renamed';
6-
7-
/**
8-
* This rule helps with the conversion of the ActionSheetController API.
9-
*/
10-
class ActionSheetMethodCreateParametersRenamedWalker extends Lint.RuleWalker {
11-
//actionControllerVariableName = undefined;
12-
foundPropertyArray = [];
13-
14-
// TODO: Not sure if we need to track the name of the ActionSheetController variable.
15-
// visitConstructorDeclaration(node: ts.ConstructorDeclaration) {
16-
// debugger;
17-
// for (let element of node.parameters) {
18-
// const typeName = (element.type as any).typeName.text;
19-
// if (typeName === 'ActionSheetController') {
20-
// this.actionControllerVariableName = (element.name as any).text;
21-
// this.tryAddFailure();
22-
// break;
23-
// }
24-
// }
25-
26-
// super.visitConstructorDeclaration(node);
27-
// }
28-
29-
visitCallExpression(node: ts.CallExpression) {
30-
const expression = node.expression as any;
31-
32-
if (expression.name && expression.name.text === 'create') {
33-
for (let argument of (node.arguments[0] as any).properties) {
34-
const name = argument.name.text;
35-
36-
switch (name) {
37-
case 'title':
38-
case 'subTitle':
39-
argument.parentVariableName = (node.expression as any).expression.text;
40-
this.foundPropertyArray.push(argument);
41-
this.tryAddFailure();
42-
break;
43-
}
44-
}
45-
}
46-
}
4+
import { createParametersRenamedClass } from './helpers/parametersRenamed';
475

48-
private tryAddFailure() {
49-
for (let i = this.foundPropertyArray.length - 1; i >= 0; i--) {
50-
let argument = this.foundPropertyArray[i];
51-
52-
const replacementParam = argument.name.text === 'title' ? 'header' : 'subHeader';
53-
54-
// TODO: Determine if this needs to be added in later.
55-
//if (this.actionControllerVariableName && this.actionControllerVariableName === argument.parentVariableName) {
56-
const errorMessage = `The ${argument.name.text} field has been replaced by ${replacementParam}.`;
57-
58-
const replacement = new Replacement(argument.name.getStart(), argument.name.getWidth(), replacementParam);
59-
60-
this.addFailure(this.createFailure(argument.name.getStart(), argument.name.getWidth(), errorMessage, [replacement]));
61-
this.foundPropertyArray.splice(i, 1);
6+
export const ruleName = 'ion-action-sheet-method-create-parameters-renamed';
627

63-
//}
64-
}
65-
}
66-
}
8+
const parameterMap = new Map([['title', 'header'], ['subTitle', 'subHeader']]);
9+
const Walker = createParametersRenamedClass('create', 'ActionSheetController', parameterMap);
6710

6811
export class Rule extends Lint.Rules.AbstractRule {
6912
public static metadata: Lint.IRuleMetadata = {
@@ -77,6 +20,6 @@ export class Rule extends Lint.Rules.AbstractRule {
7720
};
7821

7922
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
80-
return this.applyWithWalker(new ActionSheetMethodCreateParametersRenamedWalker(sourceFile, this.getOptions()));
23+
return this.applyWithWalker(new Walker(sourceFile, this.getOptions()));
8124
}
8225
}
Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,12 @@
11
import * as Lint from 'tslint';
2-
import { IOptions, Replacement } from 'tslint';
32
import * as ts from 'typescript';
43

5-
export const ruleName = 'ion-alert-method-create-parameters-renamed';
6-
7-
/**
8-
* This rule helps with the conversion of the AlertController API.
9-
*/
10-
class AlertMethodCreateParametersRenamedWalker extends Lint.RuleWalker {
11-
foundPropertyArray = [];
12-
13-
visitCallExpression(node: ts.CallExpression) {
14-
const expression = node.expression as any;
15-
16-
if (expression.name && expression.name.text === 'create') {
17-
for (let argument of (node.arguments[0] as any).properties) {
18-
const name = argument.name.text;
19-
20-
switch (name) {
21-
case 'title':
22-
case 'subTitle':
23-
argument.parentVariableName = (node.expression as any).expression.text;
24-
this.foundPropertyArray.push(argument);
25-
this.tryAddFailure();
26-
break;
27-
}
28-
}
29-
}
30-
}
4+
import { createParametersRenamedClass } from './helpers/parametersRenamed';
315

32-
private tryAddFailure() {
33-
for (let i = this.foundPropertyArray.length - 1; i >= 0; i--) {
34-
let argument = this.foundPropertyArray[i];
35-
36-
const replacementParam = argument.name.text === 'title' ? 'header' : 'subHeader';
37-
38-
const errorMessage = `The ${argument.name.text} field has been replaced by ${replacementParam}.`;
39-
40-
const replacement = new Replacement(argument.name.getStart(), argument.name.getWidth(), replacementParam);
6+
export const ruleName = 'ion-alert-method-create-parameters-renamed';
417

42-
this.addFailure(this.createFailure(argument.name.getStart(), argument.name.getWidth(), errorMessage, [replacement]));
43-
this.foundPropertyArray.splice(i, 1);
44-
}
45-
}
46-
}
8+
const parameterMap = new Map([['title', 'header'], ['subTitle', 'subHeader']]);
9+
const Walker = createParametersRenamedClass('create', 'AlertController', parameterMap);
4710

4811
export class Rule extends Lint.Rules.AbstractRule {
4912
public static metadata: Lint.IRuleMetadata = {
@@ -57,6 +20,6 @@ export class Rule extends Lint.Rules.AbstractRule {
5720
};
5821

5922
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
60-
return this.applyWithWalker(new AlertMethodCreateParametersRenamedWalker(sourceFile, this.getOptions()));
23+
return this.applyWithWalker(new Walker(sourceFile, this.getOptions()));
6124
}
6225
}

0 commit comments

Comments
 (0)