Skip to content

Commit 9cbfa34

Browse files
author
Josh Goldberg
authored
Added architecture and testing documentation (#84)
* Added architecture and testing documentation * Documented overall conversion results
1 parent 7a1ddfe commit 9cbfa34

File tree

8 files changed

+153
-14
lines changed

8 files changed

+153
-14
lines changed

docs/Architecture.md

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,47 @@
22

33
## CLI Architecture
44

5-
- CLI usage starts in `bin/tslint-to-eslint-config`, which immediately calls `src/cli/main.ts`.
6-
- CLI settings are parsed and read in `src/cli/runCli.ts`.
7-
- Application logic is run by `src/conversion/convertConfig.ts`.
5+
1. CLI usage starts in `bin/tslint-to-eslint-config`, which immediately calls `src/cli/main.ts`.
6+
2. CLI settings are parsed and read in `src/cli/runCli.ts`.
7+
3. Application logic is run by `src/conversion/convertConfig.ts`.
88

9-
## Dependencies
9+
## Configuration Conversion
1010

11-
Methods are created using a variant of [Dependency Injection](http://en.wikipedia.org/wiki/Dependency_Injection).
12-
Any method with dependencies that should be stubbed out during tests, such as file system bindings, logging, or other methods,
13-
takes in a first argument of name `dependencies`.
14-
Its dependencies object is manually created in `src/cli/main.ts` and bound to the method with `bind`.
11+
Within `src/conversion/convertConfig.ts`, the following steps occur:
12+
13+
1. Existing configurations are read from disk
14+
2. TSLint rules are converted into their ESLint configurations
15+
3. ESLint configurations are simplified based on extended ESLint presets
16+
4. The simplified configuration is written to the output config file
17+
5. A summary of the results is printed to the user's console
18+
19+
### Conversion Results
20+
21+
The overall configuration generated by steps 2-3 and printed in 4-5 contains the following information:
22+
23+
### Rule Converters
24+
25+
Each TSLint rule should output at least one ESLint rule as the equivalent.
26+
"Converters" for TSLint rules are located in `src/rules/converters/`, and keyed under their names by the map in `src/rules/converters.ts`.
27+
28+
Each converter for a TSLint rule takes an arguments object for the rule, and returns an array of objects containing:
29+
30+
- `rules`: At least one equivalent ESLint rule and options
31+
- `notices`: Any extra info that should be printed after conversion
32+
- `plugins`: Any plugins that should now be installed if not already
33+
34+
The `rules` output is an array of objects containing:
35+
36+
- `ruleName`: Equivalent ESLint rule name that should be enabled
37+
- `ruleArguments`: Any arguments for that ESLint rule
38+
39+
Multiple objects must be supported because some general-use TSLint rules can only be represented by two or more ESLint rules.
40+
For example, TSLint's `no-banned-terms` is represented by ESLint's `no-caller` and `no-eval`.
41+
42+
### Rule Mergers
43+
44+
It's possible that one ESLint rule will be output by multiple converters.
45+
"Mergers" for those ESLint rules should take in two configurations to the same rule and output the equivalent single configuration.
46+
These are located in `src/rules/mergers/`, and keyed under their names by the map in `src/rules/mergers.ts`.
47+
48+
For example, `@typescript-eslint/ban-types` spreads both arguments' `types` members into one large `types` object.

docs/Dependencies.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Dependencies
2+
3+
Functions are created using a variant of [Dependency Injection](http://en.wikipedia.org/wiki/Dependency_Injection).
4+
Any method with dependencies that should be stubbed out during tests, such as file system bindings, logging, or other functions,
5+
takes in a first argument of name `dependencies`.
6+
Its dependencies object is manually created in `src/cli/main.ts` and bound to the method with `bind`.
7+
8+
## When to Use Dependencies
9+
10+
Most functions don't need a `dependencies` object.
11+
Only add one if something should be stubbed out during tests.
12+
13+
## How to Use Dependencies
14+
15+
Suppose your method `myMethod`, should take in a `fileSystem`, a `string`, and a `number`:
16+
17+
1. Create a `MyMethodDependencies` type in `myMethod.ts`:
18+
19+
```ts
20+
// ~~~/myMethod.ts
21+
export type MyMethodDependencies = {
22+
fileSystem: FileSystem;
23+
};
24+
```
25+
26+
2. Add `dependencies: MyMethodDependencies` as the first argument to `myMethod`:
27+
28+
```ts
29+
// ~~~/myMethod.ts
30+
export const myMethod = async (
31+
dependencies: MyMethodDependencies,
32+
argumentOne: string,
33+
argumentTwo: number,
34+
) => {
35+
// ...
36+
};
37+
```
38+
39+
3. In `src/cli/main.ts`, create a `myMethodDependencies: MyMethodDependencies`:
40+
41+
```ts
42+
// src/cli/main.ts
43+
const myMethodDependencies: MyMethodDependencies = {
44+
fileSystem,
45+
};
46+
```
47+
48+
4. In `src/cli/main.ts`, include `myMethod: bind(mymethod, myMethodDependencies)` in any dependencies object that requires `myMethod`:
49+
50+
```ts
51+
// src/cli/main.ts
52+
const otherMethodDependencies: OtherMethodDependencies = {
53+
myMethod: bind(myMethod, myMethodDependencies),
54+
};
55+
```
56+
57+
5. In the types of any dependencies that include `myMethod`, add `myMethod: SansDependencies<typeof myMethod>` to require the result of `bind`ing `myMethod`:
58+
59+
```ts
60+
// ~~~/otherMethod.ts
61+
export type OtherMethodDependencies = {
62+
myMethod: SansDependencies<typeof myMethod>;
63+
};
64+
```

docs/Development.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ npm i
1919

2020
Compile with `npm run tsc` and run tests with `npm run test:unit`.
2121

22-
## More Reading
22+
## Further Reading
2323

24-
- [Architecture](./Architecture.md)
25-
- [Testing](./Testing.md)
24+
- [Architecture](./Architecture.md): How the general app structure operates
25+
- [Dependencies](./Dependencies.md): How functions pass and receive static dependencies
26+
- [Testing](./Testing.md): Unit and end-to-end tests

docs/Testing.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,23 @@ Tests should include comments above each section of the "AAA" testing format:
1818
- `src/cli/main.ts`
1919
- `src/adapters/*.ts`
2020
- `src/**/*.stubs.ts`
21+
22+
See [Dependencies](./Dependencies.md) for how static dependencies are stubbed out in functions.
23+
That system is how functions can receive stubs and spies during unit tests.
24+
25+
## End-to-End Tests
26+
27+
```shell
28+
npm run test:end-to-end
29+
```
30+
31+
End-to-end tests that execute the `bin/tslint-to-eslint` command and validate outputs are generated from the directories in `test/tests/`.
32+
Each directory there contains:
33+
34+
- `.eslintrc.json`: `.gitignore`d output from the most recent test run
35+
- `expected.json`: Expected output ESLint configuration
36+
- `stderr.txt`: Any output written to the process `stderr`
37+
- `stdout.txt`: Any output written to the process `stdout`
38+
- `tslint.json`: Original TSLint configuration file to convert
39+
40+
Within each directory, a test suite will execute `bin/tslint-to-eslint` and validate the outputs match what's on disk.

src/conversion/convertConfig.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,43 @@ export type ConvertConfigDependencies = {
1414
writeConversionResults: SansDependencies<typeof writeConversionResults>;
1515
};
1616

17+
/**
18+
* Root-level driver to convert a TSLint configuration to ESLint.
19+
* @see `Architecture.md` for documentation.
20+
*/
1721
export const convertConfig = async (
1822
dependencies: ConvertConfigDependencies,
1923
settings: TSLintToESLintSettings,
2024
): Promise<ResultWithStatus> => {
25+
// 1. Existing configurations are read
2126
const originalConfigurations = await dependencies.findOriginalConfigurations(settings);
2227
if (originalConfigurations.status !== ResultStatus.Succeeded) {
2328
return originalConfigurations;
2429
}
2530

31+
// 2. TSLint rules are converted into their ESLint configurations
2632
const ruleConversionResults = dependencies.convertRules(
2733
originalConfigurations.data.tslint.rules,
2834
);
29-
const mergedConfiguration = {
35+
36+
// 3. ESLint configurations are simplified based on extended ESLint presets
37+
const simplifiedConfiguration = {
3038
...ruleConversionResults,
3139
...(await dependencies.simplifyPackageRules(
3240
originalConfigurations.data.eslint,
3341
ruleConversionResults,
3442
)),
3543
};
3644

45+
// 4. The simplified configuration is written to the output config file
3746
await dependencies.writeConversionResults(
3847
settings.config,
39-
mergedConfiguration,
48+
simplifiedConfiguration,
4049
originalConfigurations.data,
4150
);
42-
dependencies.reportConversionResults(mergedConfiguration);
51+
52+
// 5. A summary of the results is printed to the user's console
53+
dependencies.reportConversionResults(simplifiedConfiguration);
4354

4455
return {
4556
status: ResultStatus.Succeeded,

src/rules/converter.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ export type ConversionResult = {
3737
* An ESLint rule equivalent to a previously enabled TSLint rule.
3838
*/
3939
export type ConvertedRuleChanges = {
40+
/**
41+
* Any arguments for that ESLint rule.
42+
*/
4043
ruleArguments?: any[];
44+
45+
/**
46+
* Equivalent ESLint rule name that should be enabled.
47+
*/
4148
ruleName: string;
4249
};

src/rules/converters/no-banned-terms.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { RuleConverter } from "../converter";
22

33
export const convertNoBannedTerms: RuleConverter = () => {
44
return {
5+
// This is mentioned in Architecture.md as a TSLint rule with two ESLint equivalents
56
rules: [
67
{
78
ruleName: "no-caller",

src/rules/mergers/ban-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const mergeBanTypes: RuleMerger = (existingOptions, newOptions) => {
55
return [];
66
}
77

8+
// This is mentioned in Architecture.md as an ESLint rule with a merger
89
return [
910
{
1011
types: {

0 commit comments

Comments
 (0)