Skip to content

Commit 43ae0fc

Browse files
dimaboryJosh Goldberg
authored andcommitted
feat: add import-blacklist converter #277 (#280)
* feat: add [import-blacklist] converter #277 * chore: remove comment as being resolving * refactor: add notice, pass test coverage
1 parent 88b5a61 commit 43ae0fc

File tree

4 files changed

+141
-1
lines changed

4 files changed

+141
-1
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { RuleConverter } from "../converter";
2+
import { RequireAtLeastOne } from "../../utils";
3+
4+
type ESLintOptionPath = {
5+
name: string;
6+
importNames: string[];
7+
message?: string;
8+
};
9+
type ESLintSimpleOption = string[];
10+
type ESLintComplexOption = RequireAtLeastOne<{
11+
paths: (string | ESLintOptionPath)[];
12+
patterns: string[];
13+
}>;
14+
type ESLintOptions = ESLintSimpleOption | ESLintComplexOption;
15+
16+
const NOTICE_MATCH_PATTERNS =
17+
"ESLint and TSLint use different strategies to match patterns. TSLint uses standard regular expressions, but ESLint .gitignore spec.";
18+
19+
export const convertImportBlacklist: RuleConverter = tslintRule => {
20+
let ruleArguments: ESLintOptions = [];
21+
const notices = [];
22+
23+
if (tslintRule.ruleArguments.every(isString)) {
24+
ruleArguments = tslintRule.ruleArguments;
25+
} else {
26+
const objectOption = tslintRule.ruleArguments.reduce((rules, rule) => {
27+
if (!Array.isArray(rule)) {
28+
const eslintRule = isString(rule)
29+
? rule
30+
: {
31+
name: Object.keys(rule)[0],
32+
importNames: Object.values(rule)[0] as string[],
33+
};
34+
return { ...rules, paths: [...(rules.paths || []), eslintRule] };
35+
}
36+
37+
return { ...rules, patterns: [...(rules.patterns || []), ...rule] };
38+
}, {} as ESLintComplexOption);
39+
40+
if ("patterns" in objectOption && objectOption.patterns.length > 0) {
41+
notices.push(NOTICE_MATCH_PATTERNS);
42+
}
43+
44+
ruleArguments = [objectOption];
45+
}
46+
47+
return {
48+
rules: [
49+
{
50+
ruleArguments,
51+
...(notices.length > 0 && { notices }),
52+
ruleName: "no-restricted-imports",
53+
},
54+
],
55+
};
56+
};
57+
58+
function isString(value: string): boolean {
59+
return typeof value === "string";
60+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { convertImportBlacklist } from "../import-blacklist";
2+
3+
describe(convertImportBlacklist, () => {
4+
test.each([
5+
[[], []],
6+
[["rxjs"], ["rxjs"]],
7+
[
8+
["rxjs", "lodash"],
9+
["rxjs", "lodash"],
10+
],
11+
[
12+
[".*\\.temp$", ".*\\.tmp$"],
13+
[".*\\.temp$", ".*\\.tmp$"],
14+
],
15+
[
16+
["moment", "date-fns", ["eslint/*"]],
17+
[{ patterns: ["eslint/*"], paths: ["moment", "date-fns"] }],
18+
],
19+
[
20+
[{ lodash: ["pullAll", "pull"] }],
21+
[
22+
{
23+
paths: [
24+
{
25+
name: "lodash",
26+
importNames: ["pullAll", "pull"],
27+
},
28+
],
29+
},
30+
],
31+
],
32+
[
33+
[
34+
"rxjs",
35+
[".*\\.temp$", ".*\\.tmp$"],
36+
{ lodash: ["pullAll", "pull"] },
37+
{ dummy: ["default"] },
38+
],
39+
[
40+
{
41+
paths: [
42+
"rxjs",
43+
{
44+
name: "lodash",
45+
importNames: ["pullAll", "pull"],
46+
},
47+
{
48+
name: "dummy",
49+
importNames: ["default"],
50+
},
51+
],
52+
patterns: [".*\\.temp$", ".*\\.tmp$"],
53+
},
54+
],
55+
],
56+
] as any[][])("convert %j", (ruleArguments: any[], expected: any[]) => {
57+
const result = convertImportBlacklist({ ruleArguments });
58+
const hasPatterns = typeof expected[0] === "object" && "patterns" in expected[0];
59+
60+
expect(result).toEqual({
61+
rules: [
62+
{
63+
ruleArguments: expected,
64+
...(hasPatterns && {
65+
notices: [
66+
"ESLint and TSLint use different strategies to match patterns. TSLint uses standard regular expressions, but ESLint .gitignore spec.",
67+
],
68+
}),
69+
ruleName: "no-restricted-imports",
70+
},
71+
],
72+
});
73+
});
74+
});

src/rules/rulesConverters.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { convertEofline } from "./converters/eofline";
1818
import { convertFileNameCasing } from "./converters/file-name-casing";
1919
import { convertForin } from "./converters/forin";
2020
import { convertFunctionConstructor } from "./converters/function-constructor";
21+
import { convertImportBlacklist } from "./converters/import-blacklist";
2122
import { convertIncrementDecrement } from "./converters/increment-decrement";
2223
import { convertIndent } from "./converters/indent";
2324
import { convertInterfaceName } from "./converters/interface-name";
@@ -155,6 +156,7 @@ export const rulesConverters = new Map([
155156
["file-name-casing", convertFileNameCasing],
156157
["forin", convertForin],
157158
["function-constructor", convertFunctionConstructor],
159+
["import-blacklist", convertImportBlacklist],
158160
["increment-decrement", convertIncrementDecrement],
159161
["indent", convertIndent],
160162
["interface-name", convertInterfaceName],
@@ -275,7 +277,6 @@ export const rulesConverters = new Map([
275277

276278
// TSLint core rules:
277279
// ["ban", convertBan], // no-restricted-properties
278-
// ["import-blacklist", convertImportBlacklist], // no-restricted-imports
279280

280281
// tslint-microsoft-contrib rules:
281282
// ["max-func-body-length", convertMaxFuncBodyLength],

src/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ export type RemoveErrors<Items> = {
88

99
export type PromiseValue<T> = T extends Promise<infer R> ? R : never;
1010

11+
export type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
12+
{
13+
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
14+
}[Keys];
15+
1116
export const uniqueFromSources = <T>(...sources: (T | T[] | undefined)[]) => {
1217
const items: T[] = [];
1318

0 commit comments

Comments
 (0)