Skip to content

Added quick utility script for making a new rule #1089

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 1, 2021
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*.log
*.map
coverage/
script/
src/**/*.stubs.*
src/**/*.test.*
src/**/*.ts
Expand Down
16 changes: 16 additions & 0 deletions docs/Creating a Rule Converter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Creating a Rule Converter

> If you're not familiar with this project's rule converters work, please read the [Architecture docs](./Architecture/README.md) first.

Adding a new rule converter to `tslint-to-eslint-config` is a relatively straightforward task.
For your convenience, a starter script is included that sets up the files:

```shell
node ./script/newConverter --eslint output-name --tslint input-name
```

If the lint rule includes arguments, add the `--sameArguments` flag above to have starter code generated for that as well.

```shell
node ./script/newConverter --eslint output-name --tslint input-name --sameArguments
```
1 change: 1 addition & 0 deletions docs/Development.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ Compile with `npm run tsc` and run tests with `npm run test`.
## Further Reading

- [Architecture](./Architecture/README.md): How the general app structure operates
- [Creating a Rule Converter](./Creating%20a%20Rule%20Converter.md): How to quickly add a missing converter for a TSLint rule
- [Dependencies](./Dependencies.md): How functions pass and receive static dependencies
- [Testing](./Testing.md): Unit tests
31 changes: 31 additions & 0 deletions script/newConverter/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const { Command } = require("commander");
const { upperFirst, camelCase } = require("lodash");

const { rewriteConvertersMap } = require("./rewriteConvertersMap");
const { writeConverter } = require("./writeConverter");
const { writeConverterTest } = require("./writeConverterTest");

(async () => {
const command = new Command()
.option("--eslint [eslint]", "name of the original ESLint rule")
.option("--sameArguments [sameArguments]", "whether to copy over ruleArguments")
.option("--tslint [tslint]", "name of the original TSLint rule");

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

for (const arg of ["eslint", "tslint"]) {
if (!args[arg]) {
throw new Error(`Missing --${arg} option.`);
}
}

const tslintPascalCase = upperFirst(camelCase(args.tslint));
const plugins = args.eslint.includes("/")
? `
plugins: ["${args.eslint.split("/")[0]}"],`
: "";

await rewriteConvertersMap({ args, tslintPascalCase });
await writeConverter({ args, plugins, tslintPascalCase });
await writeConverterTest({ args, plugins, tslintPascalCase });
})();
46 changes: 46 additions & 0 deletions script/newConverter/rewriteConvertersMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { promises: fs } = require("fs");
const { EOL } = require("node:os");
const path = require("path");

const filePath = "./src/converters/lintConfigs/rules/ruleConverters.ts";

module.exports.rewriteConvertersMap = async ({ args, tslintPascalCase }) => {
const lines = (await fs.readFile(filePath)).toString().split(/\r\n|\r|\n/);

/**
* Inserts a new line alphabetically into the file lines.
*
* @param {string} insertion Line to be added.
* @param {number} start Starting point to begin comparing at.
* @param {number} end Last line to compare at, and add just after as a fallback.
* @param {(line: string) => string} [mapLine] Transforms lines to be sorted.
* @remarks In theory this could use binary search, but... why bother?
*/
const insertAlphabetically = (insertion, start, end, mapLine = (line) => line) => {
const sorter = mapLine(insertion);

for (let i = start; i < lines.length; i += 1) {
if (mapLine(lines[i]) > sorter) {
lines.splice(i, 0, insertion);
return;
}
}

lines.splice(end, 0, insertion);
};

insertAlphabetically(
`import { convert${tslintPascalCase} } from "./ruleConverters/${args.tslint}";`,
0,
lines.indexOf(""),
(line) => line.split(" from ")[1],
);

insertAlphabetically(
` ["${args.tslint}", convert${tslintPascalCase}],`,
lines.indexOf("export const ruleConverters = new Map([") + 1,
lines.indexOf("]);"),
);

await fs.writeFile(filePath, lines.join(EOL));
};
30 changes: 30 additions & 0 deletions script/newConverter/writeConverter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const { promises: fs } = require("fs");

module.exports.writeConverter = async ({ args, plugins, tslintPascalCase }) => {
const [functionArguments, ruleArguments] = args.sameArguments
? [
"tslintRule",
`
...(tslintRule.ruleArguments.length !== 0 && {
ruleArguments: tslintRule.ruleArguments,
}),`,
]
: ["", ""];

await fs.writeFile(
`./src/converters/lintConfigs/rules/ruleConverters/${args.tslint}.ts`,
`
import { RuleConverter } from "../ruleConverter";

export const convert${tslintPascalCase}: RuleConverter = (${functionArguments}) => {
return {${plugins}
rules: [
{${ruleArguments}
ruleName: "${args.eslint}",
},
],
};
};
`.trimLeft(),
);
};
46 changes: 46 additions & 0 deletions script/newConverter/writeConverterTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { promises: fs } = require("fs");

module.exports.writeConverterTest = async ({ args, tslintPascalCase, plugins }) => {
const ruleArgumentsTest = args.sameArguments
? `

test("conversion with an argument", () => {
const result = convert${tslintPascalCase}({
ruleArguments: ["TODO"],
});

expect(result).toEqual({${plugins.replace("\n", "\n ")}
rules: [
{
ruleArguments: ["TODO"],
ruleName: "${args.eslint}",
},
],
});
});
`
: "";

await fs.writeFile(
`./src/converters/lintConfigs/rules/ruleConverters/tests/${args.tslint}.test.ts`,
`
import { convert${tslintPascalCase} } from "../${args.tslint}";

describe(convert${tslintPascalCase}, () => {
test("conversion without arguments", () => {
const result = convert${tslintPascalCase}({
ruleArguments: [],
});

expect(result).toEqual({${plugins.replace("\n", "\n ")}
rules: [
{
ruleName: "${args.eslint}",
},
],
});
});${ruleArgumentsTest}
});
`.trimLeft(),
);
};