Skip to content

Commit aad56b9

Browse files
author
Josh Goldberg
authored
Added quick utility script for making a new rule (#1089)
* Added quick utility script for making a new rule * Simple import sort: will pull into separate branch * Enabled eslint-plugin-simple-import-sort internally * Added eslint-plugin-simple-import-sort Enforces that exports and imports are sorted alphabetically, with a fixer for ESLint's `--fix`. Precursor to #1089 to make it easier to insert lines into `ruleConverters.ts` automatically. * Reset package-lock.json * Added back simple-import-sort * Reset .eslintrc.js * Remove test bed files * Also add to ruleConverters.ts, alphabetically
1 parent cf4f326 commit aad56b9

7 files changed

+171
-0
lines changed

Diff for: .npmignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
*.log
33
*.map
44
coverage/
5+
script/
56
src/**/*.stubs.*
67
src/**/*.test.*
78
src/**/*.ts

Diff for: docs/Creating a Rule Converter.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Creating a Rule Converter
2+
3+
> If you're not familiar with this project's rule converters work, please read the [Architecture docs](./Architecture/README.md) first.
4+
5+
Adding a new rule converter to `tslint-to-eslint-config` is a relatively straightforward task.
6+
For your convenience, a starter script is included that sets up the files:
7+
8+
```shell
9+
node ./script/newConverter --eslint output-name --tslint input-name
10+
```
11+
12+
If the lint rule includes arguments, add the `--sameArguments` flag above to have starter code generated for that as well.
13+
14+
```shell
15+
node ./script/newConverter --eslint output-name --tslint input-name --sameArguments
16+
```

Diff for: docs/Development.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ Compile with `npm run tsc` and run tests with `npm run test`.
2222
## Further Reading
2323

2424
- [Architecture](./Architecture/README.md): How the general app structure operates
25+
- [Creating a Rule Converter](./Creating%20a%20Rule%20Converter.md): How to quickly add a missing converter for a TSLint rule
2526
- [Dependencies](./Dependencies.md): How functions pass and receive static dependencies
2627
- [Testing](./Testing.md): Unit tests

Diff for: script/newConverter/index.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const { Command } = require("commander");
2+
const { upperFirst, camelCase } = require("lodash");
3+
4+
const { rewriteConvertersMap } = require("./rewriteConvertersMap");
5+
const { writeConverter } = require("./writeConverter");
6+
const { writeConverterTest } = require("./writeConverterTest");
7+
8+
(async () => {
9+
const command = new Command()
10+
.option("--eslint [eslint]", "name of the original ESLint rule")
11+
.option("--sameArguments [sameArguments]", "whether to copy over ruleArguments")
12+
.option("--tslint [tslint]", "name of the original TSLint rule");
13+
14+
const args = command.parse(process.argv).opts();
15+
16+
for (const arg of ["eslint", "tslint"]) {
17+
if (!args[arg]) {
18+
throw new Error(`Missing --${arg} option.`);
19+
}
20+
}
21+
22+
const tslintPascalCase = upperFirst(camelCase(args.tslint));
23+
const plugins = args.eslint.includes("/")
24+
? `
25+
plugins: ["${args.eslint.split("/")[0]}"],`
26+
: "";
27+
28+
await rewriteConvertersMap({ args, tslintPascalCase });
29+
await writeConverter({ args, plugins, tslintPascalCase });
30+
await writeConverterTest({ args, plugins, tslintPascalCase });
31+
})();

Diff for: script/newConverter/rewriteConvertersMap.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const { promises: fs } = require("fs");
2+
const { EOL } = require("node:os");
3+
const path = require("path");
4+
5+
const filePath = "./src/converters/lintConfigs/rules/ruleConverters.ts";
6+
7+
module.exports.rewriteConvertersMap = async ({ args, tslintPascalCase }) => {
8+
const lines = (await fs.readFile(filePath)).toString().split(/\r\n|\r|\n/);
9+
10+
/**
11+
* Inserts a new line alphabetically into the file lines.
12+
*
13+
* @param {string} insertion Line to be added.
14+
* @param {number} start Starting point to begin comparing at.
15+
* @param {number} end Last line to compare at, and add just after as a fallback.
16+
* @param {(line: string) => string} [mapLine] Transforms lines to be sorted.
17+
* @remarks In theory this could use binary search, but... why bother?
18+
*/
19+
const insertAlphabetically = (insertion, start, end, mapLine = (line) => line) => {
20+
const sorter = mapLine(insertion);
21+
22+
for (let i = start; i < lines.length; i += 1) {
23+
if (mapLine(lines[i]) > sorter) {
24+
lines.splice(i, 0, insertion);
25+
return;
26+
}
27+
}
28+
29+
lines.splice(end, 0, insertion);
30+
};
31+
32+
insertAlphabetically(
33+
`import { convert${tslintPascalCase} } from "./ruleConverters/${args.tslint}";`,
34+
0,
35+
lines.indexOf(""),
36+
(line) => line.split(" from ")[1],
37+
);
38+
39+
insertAlphabetically(
40+
` ["${args.tslint}", convert${tslintPascalCase}],`,
41+
lines.indexOf("export const ruleConverters = new Map([") + 1,
42+
lines.indexOf("]);"),
43+
);
44+
45+
await fs.writeFile(filePath, lines.join(EOL));
46+
};

Diff for: script/newConverter/writeConverter.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const { promises: fs } = require("fs");
2+
3+
module.exports.writeConverter = async ({ args, plugins, tslintPascalCase }) => {
4+
const [functionArguments, ruleArguments] = args.sameArguments
5+
? [
6+
"tslintRule",
7+
`
8+
...(tslintRule.ruleArguments.length !== 0 && {
9+
ruleArguments: tslintRule.ruleArguments,
10+
}),`,
11+
]
12+
: ["", ""];
13+
14+
await fs.writeFile(
15+
`./src/converters/lintConfigs/rules/ruleConverters/${args.tslint}.ts`,
16+
`
17+
import { RuleConverter } from "../ruleConverter";
18+
19+
export const convert${tslintPascalCase}: RuleConverter = (${functionArguments}) => {
20+
return {${plugins}
21+
rules: [
22+
{${ruleArguments}
23+
ruleName: "${args.eslint}",
24+
},
25+
],
26+
};
27+
};
28+
`.trimLeft(),
29+
);
30+
};

Diff for: script/newConverter/writeConverterTest.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const { promises: fs } = require("fs");
2+
3+
module.exports.writeConverterTest = async ({ args, tslintPascalCase, plugins }) => {
4+
const ruleArgumentsTest = args.sameArguments
5+
? `
6+
7+
test("conversion with an argument", () => {
8+
const result = convert${tslintPascalCase}({
9+
ruleArguments: ["TODO"],
10+
});
11+
12+
expect(result).toEqual({${plugins.replace("\n", "\n ")}
13+
rules: [
14+
{
15+
ruleArguments: ["TODO"],
16+
ruleName: "${args.eslint}",
17+
},
18+
],
19+
});
20+
});
21+
`
22+
: "";
23+
24+
await fs.writeFile(
25+
`./src/converters/lintConfigs/rules/ruleConverters/tests/${args.tslint}.test.ts`,
26+
`
27+
import { convert${tslintPascalCase} } from "../${args.tslint}";
28+
29+
describe(convert${tslintPascalCase}, () => {
30+
test("conversion without arguments", () => {
31+
const result = convert${tslintPascalCase}({
32+
ruleArguments: [],
33+
});
34+
35+
expect(result).toEqual({${plugins.replace("\n", "\n ")}
36+
rules: [
37+
{
38+
ruleName: "${args.eslint}",
39+
},
40+
],
41+
});
42+
});${ruleArgumentsTest}
43+
});
44+
`.trimLeft(),
45+
);
46+
};

0 commit comments

Comments
 (0)