Skip to content

Commit 84b22ce

Browse files
author
Josh Goldberg
authored
Added obsolete rule support to convertRules.ts (#1148)
1 parent 037a446 commit 84b22ce

21 files changed

+203
-33
lines changed

docs/Architecture/Linters.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Those are run by `src/converters/lintConfigs/rules/convertRules.ts`, which takes
1818

1919
1. The raw TSLint rule is converted to a standardized format.
2020
2. The appropriate converter is run for the rule.
21-
3. If the rule is missing or the conversion failed, this is marked.
21+
3. If the rule is missing or obsolete, or the conversion failed, this is marked.
2222
4. For each output rule equivalent given by the conversion:
2323
* The output rule name is added to the TSLint rule's equivalency set.
2424
* The TSLint rule's config severity is mapped to its ESLint equivalent.
@@ -34,7 +34,7 @@ Each TSLint rule should output at least one ESLint rule as the equivalent.
3434

3535
Each converter for a TSLint rule takes an arguments object for the rule, and returns an array of objects containing:
3636

37-
- `rules`: At least one equivalent ESLint rule and options
37+
- `rules`: At least one equivalent ESLint rule and options, _or_ none if obsolete
3838
- `notices`: Any extra info that should be printed after conversion
3939
- `plugins`: Any plugins that should now be installed if not already
4040

docs/Creating a Rule Converter.md

+6
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ If the lint rule includes arguments, add the `--sameArguments` flag above to hav
1414
```shell
1515
node ./script/newConverter --eslint output-name --tslint input-name --sameArguments
1616
```
17+
18+
If the original TSLint rule is obsolete and does not have an ESLint equivalent, you can omit `--eslint`:
19+
20+
```shell
21+
node ./script/newConverter --tslint input-name
22+
```

script/newConverter/index.js

+10-7
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ const { writeConverterTest } = require("./writeConverterTest");
1313

1414
const args = command.parse(process.argv).opts();
1515

16-
for (const arg of ["eslint", "tslint"]) {
17-
if (!args[arg]) {
18-
throw new Error(`Missing --${arg} option.`);
19-
}
16+
if (!args.tslint) {
17+
throw new Error(`Missing --tslint option.`);
18+
}
19+
20+
if (args.sameArguments && !args.eslint) {
21+
throw new Error(`Cannot use --sameArguments without --eslint.`);
2022
}
2123

2224
const tslintPascalCase = upperFirst(camelCase(args.tslint)).replace("A11Y", "A11y");
23-
const plugins = args.eslint.includes("/")
24-
? `
25+
const plugins =
26+
args.eslint && args.eslint.includes("/")
27+
? `
2528
plugins: ["${args.eslint.split("/")[0]}"],`
26-
: "";
29+
: "";
2730

2831
await rewriteConvertersMap({ args, tslintPascalCase });
2932
await writeConverter({ args, plugins, tslintPascalCase });

script/newConverter/writeConverter.js

+12-8
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,23 @@ module.exports.writeConverter = async ({ args, plugins, tslintPascalCase }) => {
1111
]
1212
: ["", ""];
1313

14+
const body = args.eslint
15+
? `{${plugins}
16+
rules: [
17+
{${ruleArguments}
18+
ruleName: "${args.eslint}",
19+
},
20+
],
21+
}`
22+
: `({})`;
23+
1424
await fs.writeFile(
1525
`./src/converters/lintConfigs/rules/ruleConverters/${args.tslint}.ts`,
1626
`
17-
import { RuleConverter } from "../ruleConverter";
27+
import { RuleConverter } from "../ruleConverter";
1828
1929
export const convert${tslintPascalCase}: RuleConverter = (${functionArguments}) => {
20-
return {${plugins}
21-
rules: [
22-
{${ruleArguments}
23-
ruleName: "${args.eslint}",
24-
},
25-
],
26-
};
30+
return ${body};
2731
};
2832
`.trimLeft(),
2933
);

script/newConverter/writeConverterTest.js

+11-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ module.exports.writeConverterTest = async ({ args, tslintPascalCase, plugins })
2121
`
2222
: "";
2323

24+
const body = args.eslint
25+
? `
26+
rules: [
27+
{
28+
ruleName: "${args.eslint}",
29+
},
30+
],
31+
`
32+
: "";
33+
2434
await fs.writeFile(
2535
`./src/converters/lintConfigs/rules/ruleConverters/tests/${args.tslint}.test.ts`,
2636
`
@@ -32,13 +42,7 @@ describe(convert${tslintPascalCase}, () => {
3242
ruleArguments: [],
3343
});
3444
35-
expect(result).toEqual({${plugins.replace("\n", "\n ")}
36-
rules: [
37-
{
38-
ruleName: "${args.eslint}",
39-
},
40-
],
41-
});
45+
expect(result).toEqual({${plugins.replace("\n", "\n ")}${body}});
4246
});${ruleArgumentsTest}
4347
});
4448
`.trimLeft(),

src/converters/comments/convertFileComments.test.ts

+34
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,40 @@ describe("convertFileComments", () => {
5555
expect(dependencies.fileSystem.writeFile).not.toHaveBeenCalled();
5656
});
5757

58+
it("ignores comment contents when an input rule is obsolete", async () => {
59+
// Arrange
60+
const dependencies = {
61+
...createStubDependencies(`
62+
// tslint:disable
63+
export const a = true;
64+
65+
// tslint:disable:obsolete
66+
export const b = true;
67+
`),
68+
converters: new Map([["obsolete", () => ({})]]),
69+
};
70+
71+
// Act
72+
await convertFileComments(
73+
dependencies,
74+
stubFileName,
75+
new Map<string, string[]>(),
76+
new Map<string, string[]>(),
77+
);
78+
79+
// Assert
80+
expect(dependencies.fileSystem.writeFile).toHaveBeenCalledWith(
81+
stubFileName,
82+
`
83+
/* eslint-disable */
84+
export const a = true;
85+
86+
/* eslint-disable */
87+
export const b = true;
88+
`,
89+
);
90+
});
91+
5892
it("parses TSLint directives to their matching ESLint directives", async () => {
5993
// Arrange
6094
const dependencies = createStubDependencies(`

src/converters/comments/replaceFileComments.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const replaceFileComments = (
2929
return undefined;
3030
}
3131

32-
const equivalents = converted.rules.map((conversion) => conversion.ruleName);
32+
const equivalents = converted.rules?.map((conversion) => conversion.ruleName) ?? [];
3333

3434
ruleCommentsCache.set(ruleName, equivalents);
3535

src/converters/lintConfigs/configConversionResults.stubs.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const createEmptyConfigConversionResults = (
88
extensionRules: new Map(),
99
failed: [],
1010
missing: [],
11+
obsolete: new Set(),
1112
plugins: new Set(),
1213
ruleEquivalents: new Map(),
1314
...overrides,

src/converters/lintConfigs/convertLintConfig.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export type ConvertLintConfigDependencies = {
1717

1818
/**
1919
* Root-level driver to convert a TSLint configuration to ESLint.
20-
* @see `/docs/Architecture/Linting.md` for documentation.
20+
* @see `/docs/Architecture/Linters.md` for documentation.
2121
*/
2222
export const convertLintConfig = async (
2323
dependencies: ConvertLintConfigDependencies,

src/converters/lintConfigs/reporting/reportConfigConversionResults.test.ts

+38
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,44 @@ describe("reportConfigConversionResults", () => {
225225
);
226226
});
227227

228+
it("logs obsolete conversions when there is one obsolete conversion", async () => {
229+
// Arrange
230+
const logger = createStubLogger();
231+
const conversionResults = createEmptyConfigConversionResults({
232+
extends: basicExtends,
233+
obsolete: new Set(["obsolete"]),
234+
});
235+
236+
// Act
237+
await reportConfigConversionResults({ logger }, ".eslintrc.js", conversionResults);
238+
239+
// Assert
240+
expectEqualWrites(
241+
logger.stdout.write,
242+
`🦖 1 rule is obsolete and does not have an ESLint equivalent. 🦖`,
243+
` Check ${logger.debugFileName} for details.`,
244+
);
245+
});
246+
247+
it("logs obsolete conversions when there are multiple obsolete conversions", async () => {
248+
// Arrange
249+
const logger = createStubLogger();
250+
const conversionResults = createEmptyConfigConversionResults({
251+
extends: basicExtends,
252+
obsolete: new Set(["obsolete-a", "obsolete-b"]),
253+
});
254+
255+
// Act
256+
await reportConfigConversionResults({ logger }, ".eslintrc.js", conversionResults);
257+
258+
// Assert
259+
expectEqualWrites(
260+
logger.stdout.write,
261+
`🦖 2 rules are obsolete and do not have ESLint equivalents. 🦖`,
262+
` Check ${logger.debugFileName} for details.`,
263+
);
264+
});
265+
228266
it("logs a Prettier recommendation when extends doesn't include eslint-config-prettier", async () => {
229267
// Arrange
230268
const logger = createStubLogger();

src/converters/lintConfigs/reporting/reportConfigConversionResults.ts

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Logger } from "../../../adapters/logger";
55
import {
66
logFailedConversions,
77
logMissingConversionTarget,
8+
logObsoleteRules,
89
logSuccessfulConversions,
910
} from "../../../reporting";
1011
import { ESLintRuleOptions, TSLintRuleOptions } from "../rules/types";
@@ -50,6 +51,10 @@ export const reportConfigConversionResults = async (
5051
);
5152
}
5253

54+
if (ruleConversionResults.obsolete.size !== 0) {
55+
logObsoleteRules(Array.from(ruleConversionResults.obsolete), dependencies.logger);
56+
}
57+
5358
if (!ruleConversionResults.extends.join("").includes("prettier")) {
5459
logPrettierExtension(dependencies.logger);
5560
}

src/converters/lintConfigs/rules/convertRules.test.ts

+16
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ describe("convertRules", () => {
7171
expect(failed).toEqual([conversionError]);
7272
});
7373

74+
it("marks a converted rule as obsolete when it has no output rules", () => {
75+
// Arrange
76+
const { tslintRule, converters, mergers } = setupConversionEnvironment();
77+
converters.set(tslintRule.ruleName, () => ({}));
78+
79+
// Act
80+
const { obsolete } = convertRules(
81+
{ ruleConverters: converters, ruleMergers: mergers },
82+
{ [tslintRule.ruleName]: tslintRule },
83+
new Map<string, string[]>(),
84+
);
85+
86+
// Assert
87+
expect(Array.from(obsolete)).toEqual([tslintRule.ruleName]);
88+
});
89+
7490
it("marks a converted rule name as converted when a conversion has rules", () => {
7591
// Arrange
7692
const conversionResult = {

src/converters/lintConfigs/rules/convertRules.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ export type RuleConversionResults = {
2121
failed: ErrorSummary[];
2222
missing: TSLintRuleOptions[];
2323
plugins: Set<string>;
24+
obsolete: Set<string>;
2425
ruleEquivalents: Map<string, string[]>;
2526
};
2627

2728
/**
2829
* Converts raw TSLint rules to their ESLint equivalents.
29-
* @see `/docs/Architecture/Linting.md` for documentation.
30+
* @see `/docs/Architecture/Linters.md` for documentation.
3031
*/
3132
export const convertRules = (
3233
dependencies: ConvertRulesDependencies,
@@ -36,6 +37,7 @@ export const convertRules = (
3637
const converted = new Map<string, ESLintRuleOptions>();
3738
const failed: ConversionError[] = [];
3839
const missing: TSLintRuleOptions[] = [];
40+
const obsolete = new Set<string>();
3941
const plugins = new Set<string>();
4042

4143
if (rawTslintRules !== undefined) {
@@ -48,7 +50,7 @@ export const convertRules = (
4850
// 2. The appropriate converter is run for the rule.
4951
const conversion = convertRule(tslintRule, dependencies.ruleConverters);
5052

51-
// 3. If the rule is missing or the conversion failed, this is marked.
53+
// 3. If the rule is missing or obsolete, or the conversion failed, this is marked.
5254
if (conversion === undefined) {
5355
if (tslintRule.ruleSeverity !== "off") {
5456
missing.push(tslintRule);
@@ -62,6 +64,11 @@ export const convertRules = (
6264
continue;
6365
}
6466

67+
if (!conversion.rules) {
68+
obsolete.add(tslintRule.ruleName);
69+
continue;
70+
}
71+
6572
const equivalents = new Set<string>();
6673

6774
// 4. For each output rule equivalent given by the conversion:
@@ -119,5 +126,5 @@ export const convertRules = (
119126
}
120127
}
121128

122-
return { converted, failed, missing, plugins, ruleEquivalents };
129+
return { converted, failed, missing, obsolete, plugins, ruleEquivalents };
123130
};
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { ConversionError } from "../../../errors/conversionError";
22

3-
export const createStubConverter = (result: ConversionError | string[]) => {
3+
export const createStubConverter = (result?: ConversionError | string[]) => {
44
return () => {
55
return result instanceof ConversionError
66
? result
77
: {
8-
rules: result.map((ruleName) => ({ ruleName })),
8+
rules: result?.map((ruleName) => ({ ruleName })),
99
};
1010
};
1111
};

src/converters/lintConfigs/rules/ruleConverter.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ export type ConversionResult = {
2828
plugins?: string[];
2929

3030
/**
31-
* At least one equivalent ESLint rule and options.
31+
* At least one equivalent ESLint rule and options, if not obsolete.
3232
*/
33-
rules: ConvertedRuleChanges[];
33+
rules?: ConvertedRuleChanges[];
3434
};
3535

3636
/**

src/converters/lintConfigs/rules/ruleConverters.ts

+4
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ import { convertFileNameCasing } from "./ruleConverters/file-name-casing";
134134
import { convertForin } from "./ruleConverters/forin";
135135
import { convertFunctionConstructor } from "./ruleConverters/function-constructor";
136136
import { convertImportBlacklist } from "./ruleConverters/import-blacklist";
137+
import { convertImportDestructuringSpacing } from "./ruleConverters/import-destructuring-spacing";
137138
import { convertIncrementDecrement } from "./ruleConverters/increment-decrement";
138139
import { convertIndent } from "./ruleConverters/indent";
139140
import { convertInterfaceName } from "./ruleConverters/interface-name";
@@ -244,6 +245,7 @@ import { convertPreferConditionalExpression } from "./ruleConverters/prefer-cond
244245
import { convertPreferConst } from "./ruleConverters/prefer-const";
245246
import { convertPreferForOf } from "./ruleConverters/prefer-for-of";
246247
import { convertPreferFunctionOverMethod } from "./ruleConverters/prefer-function-over-method";
248+
import { convertPreferInlineDecorator } from "./ruleConverters/prefer-inline-decorator";
247249
import { convertPreferObjectSpread } from "./ruleConverters/prefer-object-spread";
248250
import { convertPreferReadonly } from "./ruleConverters/prefer-readonly";
249251
import { convertPreferSwitch } from "./ruleConverters/prefer-switch";
@@ -309,6 +311,7 @@ export const ruleConverters = new Map([
309311
["forin", convertForin],
310312
["function-constructor", convertFunctionConstructor],
311313
["import-blacklist", convertImportBlacklist],
314+
["import-destructuring-spacing", convertImportDestructuringSpacing],
312315
["increment-decrement", convertIncrementDecrement],
313316
["indent", convertIndent],
314317
["interface-name", convertInterfaceName],
@@ -478,6 +481,7 @@ export const ruleConverters = new Map([
478481
["prefer-for-of", convertPreferForOf],
479482
["prefer-function-over-method", convertPreferFunctionOverMethod],
480483
["prefer-immediate-return", convertPreferImmediateReturn],
484+
["prefer-inline-decorator", convertPreferInlineDecorator],
481485
["prefer-object-spread", convertPreferObjectSpread],
482486
["prefer-on-push-component-change-detection", convertPreferOnPushComponentChangeDetection],
483487
["prefer-output-readonly", convertPreferOutputReadonly],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { RuleConverter } from "../ruleConverter";
2+
3+
export const convertImportDestructuringSpacing: RuleConverter = () => {
4+
return {};
5+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { RuleConverter } from "../ruleConverter";
2+
3+
export const convertPreferInlineDecorator: RuleConverter = () => {
4+
return {};
5+
};

0 commit comments

Comments
 (0)