Skip to content

Commit 691d3ed

Browse files
author
Josh Goldberg
authored
Eslint rule merging logic (#30)
* Always included typescript-eslint parser Fixes #12. * Added ruleArguments mergers support Starts off with ban-types and no-unnecessary-type-assertion.
1 parent 344a7c0 commit 691d3ed

File tree

9 files changed

+265
-7
lines changed

9 files changed

+265
-7
lines changed

src/convertConfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ProcessLogger } from "./logger";
33
import { reportConversionResults } from "./reportConversionResults";
44
import { convertRules, ConfigConversionResults } from "./rules/convertRules";
55
import { converters } from "./rules/converters";
6+
import { mergers } from "./rules/mergers";
67
import { TSLintToESLintSettings, TSLintToESLintResult, ResultStatus } from "./types";
78

89
export type CreateNewConfiguration = (
@@ -51,6 +52,7 @@ export const convertConfig = async ({
5152
...value,
5253
})),
5354
converters,
55+
mergers,
5456
);
5557

5658
await createNewConfiguration(convertedRules, originalConfiguration);

src/rules/convertRules.test.ts

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ describe("convertRules", () => {
1111
ruleSeverity: "off",
1212
};
1313
const converters = new Map();
14+
const mergers = new Map();
1415

1516
// Act
16-
const { missing } = convertRules([tslintRule], converters);
17+
const { missing } = convertRules([tslintRule], converters, mergers);
1718

1819
// Assert
1920
expect(missing).toEqual([]);
@@ -27,9 +28,10 @@ describe("convertRules", () => {
2728
ruleSeverity: "error",
2829
};
2930
const converters = new Map();
31+
const mergers = new Map();
3032

3133
// Act
32-
const { missing } = convertRules([tslintRule], converters);
34+
const { missing } = convertRules([tslintRule], converters, mergers);
3335

3436
// Assert
3537
expect(missing).toEqual([tslintRule]);
@@ -44,9 +46,10 @@ describe("convertRules", () => {
4446
};
4547
const conversionError = new ConversionError(new Error(), tslintRule);
4648
const converters = new Map([[tslintRule.ruleName, () => conversionError]]);
49+
const mergers = new Map();
4750

4851
// Act
49-
const { failed } = convertRules([tslintRule], converters);
52+
const { failed } = convertRules([tslintRule], converters, mergers);
5053

5154
// Assert
5255
expect(failed).toEqual([conversionError]);
@@ -67,9 +70,10 @@ describe("convertRules", () => {
6770
],
6871
};
6972
const converters = new Map([[tslintRule.ruleName, () => conversionResult]]);
73+
const mergers = new Map();
7074

7175
// Act
72-
const { converted } = convertRules([tslintRule], converters);
76+
const { converted } = convertRules([tslintRule], converters, mergers);
7377

7478
// Assert
7579
expect(converted).toEqual(
@@ -85,6 +89,81 @@ describe("convertRules", () => {
8589
);
8690
});
8791

92+
it("reports a failure when two outputs exist for a converted rule without a merger", () => {
93+
// Arrange
94+
const tslintRule: TSLintRuleOptions = {
95+
ruleArguments: [],
96+
ruleName: "tslint-rule-a",
97+
ruleSeverity: "error",
98+
};
99+
const conversionResult = {
100+
rules: [
101+
{
102+
ruleName: "eslint-rule-a",
103+
},
104+
{
105+
ruleName: "eslint-rule-a",
106+
},
107+
],
108+
};
109+
const converters = new Map([[tslintRule.ruleName, () => conversionResult]]);
110+
const mergers = new Map();
111+
112+
// Act
113+
const { failed } = convertRules([tslintRule], converters, mergers);
114+
115+
// Assert
116+
expect(failed).toEqual(
117+
jasmine.arrayContaining([
118+
jasmine.objectContaining({
119+
error: jasmine.objectContaining({
120+
message: `No merger for multiple output eslint-rule-a rule configurations.`,
121+
}),
122+
tslintRule,
123+
}),
124+
]),
125+
);
126+
});
127+
128+
it("merges rule arguments two outputs exist for a converted rule with a merger", () => {
129+
// Arrange
130+
const tslintRule: TSLintRuleOptions = {
131+
ruleArguments: [],
132+
ruleName: "tslint-rule-a",
133+
ruleSeverity: "error",
134+
};
135+
const conversionResult = {
136+
rules: [
137+
{
138+
ruleName: "eslint-rule-a",
139+
},
140+
{
141+
ruleName: "eslint-rule-a",
142+
},
143+
],
144+
};
145+
const mergedArguments = [{ merged: true }];
146+
const converters = new Map([[tslintRule.ruleName, () => conversionResult]]);
147+
const mergers = new Map([[conversionResult.rules[0].ruleName, () => mergedArguments]]);
148+
149+
// Act
150+
const { converted } = convertRules([tslintRule], converters, mergers);
151+
152+
// Assert
153+
expect(converted).toEqual(
154+
new Map([
155+
[
156+
"eslint-rule-a",
157+
{
158+
ruleArguments: mergedArguments,
159+
ruleName: "eslint-rule-a",
160+
ruleSeverity: "error",
161+
},
162+
],
163+
]),
164+
);
165+
});
166+
88167
it("marks a new package when a conversion has a new package", () => {
89168
// Arrange
90169
const tslintRule: TSLintRuleOptions = {
@@ -97,9 +176,10 @@ describe("convertRules", () => {
97176
rules: [],
98177
};
99178
const converters = new Map([[tslintRule.ruleName, () => conversionResult]]);
179+
const mergers = new Map();
100180

101181
// Act
102-
const { packages } = convertRules([tslintRule], converters);
182+
const { packages } = convertRules([tslintRule], converters, mergers);
103183

104184
// Assert
105185
expect(packages).toEqual(new Set(["extra-package"]));

src/rules/convertRules.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { convertRule } from "./convertRule";
33
import { convertRuleSeverity } from "./convertRuleSeverity";
44
import { TSLintRuleOptions, ESLintRuleOptions } from "./types";
55
import { RuleConverter } from "./converter";
6+
import { RuleMerger } from "./merger";
67

78
export type ConfigConversionResults = {
89
converted: Map<string, ESLintRuleOptions>;
@@ -14,6 +15,7 @@ export type ConfigConversionResults = {
1415
export const convertRules = (
1516
tslintRules: TSLintRuleOptions[],
1617
converters: Map<string, RuleConverter>,
18+
mergers: Map<string, RuleMerger>,
1719
): ConfigConversionResults => {
1820
const converted = new Map<string, ESLintRuleOptions>();
1921
const failed: ConversionError[] = [];
@@ -36,10 +38,36 @@ export const convertRules = (
3638
}
3739

3840
for (const changes of conversion.rules) {
39-
converted.set(changes.ruleName, {
41+
const existingConversion = converted.get(changes.ruleName);
42+
const newConversion = {
4043
...changes,
4144
ruleSeverity: convertRuleSeverity(tslintRule.ruleSeverity),
42-
});
45+
};
46+
47+
if (existingConversion === undefined) {
48+
converted.set(changes.ruleName, newConversion);
49+
continue;
50+
}
51+
52+
const merger = mergers.get(changes.ruleName);
53+
if (merger === undefined) {
54+
failed.push(
55+
new ConversionError(
56+
new Error(
57+
`No merger for multiple output ${changes.ruleName} rule configurations.`,
58+
),
59+
tslintRule,
60+
),
61+
);
62+
} else {
63+
converted.set(changes.ruleName, {
64+
...existingConversion,
65+
ruleArguments: merger(
66+
existingConversion.ruleArguments,
67+
newConversion.ruleArguments,
68+
),
69+
});
70+
}
4371
}
4472

4573
if (conversion.packages !== undefined) {

src/rules/merger.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Merges two ESLint rule result outputs to a single result.
3+
*/
4+
export type RuleMerger = (
5+
existingOptions: any[] | undefined,
6+
newOptions: any[] | undefined,
7+
) => any[];

src/rules/mergers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { mergeBanTypes } from "./mergers/ban-types";
2+
import { mergeNoUnnecessaryTypeAssertion } from "./mergers/no-unnecessary-type-assertion";
3+
4+
export const mergers = new Map([
5+
["@typescript-eslint/ban-types", mergeBanTypes],
6+
["@typescript-eslint/no-unnecessary-type-assertion", mergeNoUnnecessaryTypeAssertion],
7+
]);

src/rules/mergers/ban-types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { RuleMerger } from "../merger";
2+
3+
export const mergeBanTypes: RuleMerger = (existingOptions, newOptions) => {
4+
if (existingOptions === undefined && newOptions === undefined) {
5+
return [];
6+
}
7+
8+
return [
9+
{
10+
types: {
11+
...(existingOptions && existingOptions[0].types),
12+
...(newOptions && newOptions[0].types),
13+
},
14+
},
15+
];
16+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { RuleMerger } from "../merger";
2+
3+
export const mergeNoUnnecessaryTypeAssertion: RuleMerger = (existingOptions, newOptions) => {
4+
if (existingOptions === undefined && newOptions === undefined) {
5+
return [];
6+
}
7+
8+
let typesToIgnore: string[] | undefined;
9+
10+
for (const options of [existingOptions, newOptions]) {
11+
if (
12+
options === undefined ||
13+
options.length === 0 ||
14+
options[0].typesToIgnore === undefined
15+
) {
16+
continue;
17+
}
18+
19+
if (typesToIgnore === undefined) {
20+
typesToIgnore = [];
21+
}
22+
23+
typesToIgnore.push(...options[0].typesToIgnore);
24+
}
25+
26+
return [
27+
{
28+
...(typesToIgnore !== undefined && { typesToIgnore }),
29+
},
30+
];
31+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { mergeBanTypes } from "../ban-types";
2+
3+
describe(mergeBanTypes, () => {
4+
test("neither types existing", () => {
5+
const result = mergeBanTypes(undefined, undefined);
6+
7+
expect(result).toEqual([]);
8+
});
9+
10+
test("original type existing", () => {
11+
const result = mergeBanTypes([{ types: { a: "original" } }], undefined);
12+
13+
expect(result).toEqual([{ types: { a: "original" } }]);
14+
});
15+
16+
test("new type existing", () => {
17+
const result = mergeBanTypes(undefined, [{ types: { b: "new" } }]);
18+
19+
expect(result).toEqual([{ types: { b: "new" } }]);
20+
});
21+
22+
test("both types existing", () => {
23+
const result = mergeBanTypes([{ types: { a: "original" } }], [{ types: { b: "new" } }]);
24+
25+
expect(result).toEqual([{ types: { a: "original", b: "new" } }]);
26+
});
27+
});
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { mergeNoUnnecessaryTypeAssertion } from "../no-unnecessary-type-assertion";
2+
3+
describe(mergeNoUnnecessaryTypeAssertion, () => {
4+
test("neither options existing", () => {
5+
const result = mergeNoUnnecessaryTypeAssertion(undefined, undefined);
6+
7+
expect(result).toEqual([]);
8+
});
9+
10+
test("neither typesToIgnore existing", () => {
11+
const result = mergeNoUnnecessaryTypeAssertion([{}], [{}]);
12+
13+
expect(result).toEqual([{}]);
14+
});
15+
16+
test("original typesToIgnore existing but empty", () => {
17+
const result = mergeNoUnnecessaryTypeAssertion([{ typesToIgnore: [] }], undefined);
18+
19+
expect(result).toEqual([{ typesToIgnore: [] }]);
20+
});
21+
22+
test("original typesToIgnore existing", () => {
23+
const result = mergeNoUnnecessaryTypeAssertion(
24+
[{ typesToIgnore: ["original"] }],
25+
undefined,
26+
);
27+
28+
expect(result).toEqual([{ typesToIgnore: ["original"] }]);
29+
});
30+
31+
test("new typesToIgnore existing but empty", () => {
32+
const result = mergeNoUnnecessaryTypeAssertion(undefined, [{ typesToIgnore: [] }]);
33+
34+
expect(result).toEqual([{ typesToIgnore: [] }]);
35+
});
36+
37+
test("new typesToIgnore existing", () => {
38+
const result = mergeNoUnnecessaryTypeAssertion(undefined, [{ typesToIgnore: ["new"] }]);
39+
40+
expect(result).toEqual([{ typesToIgnore: ["new"] }]);
41+
});
42+
43+
test("both typesToIgnore existing but empty", () => {
44+
const result = mergeNoUnnecessaryTypeAssertion(
45+
[{ typesToIgnore: [] }],
46+
[{ typesToIgnore: [] }],
47+
);
48+
49+
expect(result).toEqual([{ typesToIgnore: [] }]);
50+
});
51+
52+
test("both typesToIgnore existing", () => {
53+
const result = mergeNoUnnecessaryTypeAssertion(
54+
[{ typesToIgnore: ["original"] }],
55+
[{ typesToIgnore: ["new"] }],
56+
);
57+
58+
expect(result).toEqual([{ typesToIgnore: ["original", "new"] }]);
59+
});
60+
});

0 commit comments

Comments
 (0)