Skip to content

Commit 44e5025

Browse files
feat(config): implement importTSLintRules (#78)
1 parent 9fb1306 commit 44e5025

File tree

9 files changed

+545
-16
lines changed

9 files changed

+545
-16
lines changed

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,16 +190,37 @@ import { defineConfig, importESLintRules } from '@tsslint/config';
190190
export default defineConfig({
191191
rules: {
192192
...await importESLintRules({
193-
'no-unused-vars': 'error',
194-
'@typescript-eslint/no-explicit-any': 'warn',
193+
'no-unused-vars': true,
194+
'@typescript-eslint/no-explicit-any': true,
195195
}),
196196
},
197197
});
198198
```
199199

200+
`importESLintRules` will automatically resolve and load rules from ESLint plugins (e.g., `@typescript-eslint/eslint-plugin`) by searching your `node_modules`. Plugin rules are identified by their prefix (e.g., `@typescript-eslint/`).
201+
200202
#### TSLint
201203
Convert TSLint rules via `@tsslint/compat-tslint`.
202204

205+
```bash
206+
npm install @tsslint/compat-tslint --save-dev
207+
```
208+
209+
```ts
210+
import { defineConfig, importTSLintRules } from '@tsslint/config';
211+
212+
export default defineConfig({
213+
rules: {
214+
...await importTSLintRules({
215+
'no-console': true,
216+
'member-ordering': [true, { order: 'fields-first' }],
217+
}),
218+
},
219+
});
220+
```
221+
222+
`importTSLintRules` will automatically read `rulesDirectory` from your `tslint.json` to support third-party TSLint plugins.
223+
203224
#### TSL
204225
Convert TSL rules via `@tsslint/compat-tsl`.
205226

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { convertRule } from '@tsslint/compat-tslint';
2-
import { defineConfig } from '@tsslint/config';
1+
import { defineConfig, importTSLintRules } from '@tsslint/config';
32

43
export default defineConfig({
5-
rules: {
6-
'strict-boolean-expressions': convertRule((await import('tslint/lib/rules/strictBooleanExpressionsRule.js')).Rule),
7-
},
4+
rules: await importTSLintRules({
5+
'strict-boolean-expressions': true,
6+
}),
87
});

packages/compat-tslint/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ export function convertRule(
1616
ruleSeverity: 'warning',
1717
disabledIntervals: [],
1818
}) as IRule | ITypedRule;
19-
return ({ file, report, ...ctx }) => {
19+
return ({ typescript: ts, file, report, ...ctx }) => {
20+
if (Rule.metadata?.typescriptOnly) {
21+
const scriptKind = (file as any).scriptKind;
22+
if (scriptKind === ts.ScriptKind.JS || scriptKind === ts.ScriptKind.JSX) {
23+
return;
24+
}
25+
}
26+
2027
const failures = 'applyWithProgram' in rule
2128
? rule.applyWithProgram(file, ctx.program)
2229
: rule.apply(file);

packages/config/bin/tsslint-config-update.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ if (fs.existsSync(dtsGeneratePath)) {
2626

2727
try {
2828
const { generateESlintTypes } = require('../lib/eslint-gen');
29+
const { generateTSLintTypes } = require('../lib/tslint-gen');
30+
2931
generateESlintTypes(nodeModulesDirs).then(({ dts, stats }) => {
3032
fs.writeFileSync(path.resolve(__dirname, '..', 'lib', 'eslint-types.d.ts'), dts);
3133

@@ -51,8 +53,39 @@ try {
5153
*
5254
* ${statsTable}
5355
*
54-
* ---
5556
* If you have added new ESLint plugins, please run \`npx tsslint-config-update\` to update this list.
57+
*/`;
58+
indexContent = indexContent.slice(0, jsDocStart) + newJsDoc + indexContent.slice(jsDocEnd);
59+
fs.writeFileSync(indexPath, indexContent);
60+
}
61+
}
62+
});
63+
generateTSLintTypes(nodeModulesDirs).then(({ dts, stats }) => {
64+
fs.writeFileSync(path.resolve(__dirname, '..', 'lib', 'tslint-types.d.ts'), dts);
65+
66+
const indexPath = path.resolve(__dirname, '..', 'lib', 'tslint.d.ts');
67+
if (fs.existsSync(indexPath)) {
68+
let indexContent = fs.readFileSync(indexPath, 'utf8');
69+
const fnIndex = indexContent.indexOf('export declare function importTSLintRules');
70+
const jsDocEnd = indexContent.lastIndexOf('*/', fnIndex) + 2;
71+
const jsDocStart = indexContent.lastIndexOf('/**', jsDocEnd);
72+
73+
if (jsDocStart !== -1 && jsDocEnd !== -1 && jsDocStart < fnIndex) {
74+
const statsTable = [
75+
'| Dir | Rules |',
76+
'| :--- | :--- |',
77+
...Object.entries(stats)
78+
.filter(([_, count]) => count > 0)
79+
.sort((a, b) => b[1] - a[1])
80+
.map(([name, count]) => `| <span>${name}</span> | ${count} |`),
81+
].join('\n * ');
82+
83+
const newJsDoc = `/**
84+
* Converts a TSLint rules configuration to TSSLint rules.
85+
*
86+
* ${statsTable}
87+
*
88+
* If you have added new TSLint plugins, please run \`npx tsslint-config-update\` to update this list.
5689
*/`;
5790
indexContent = indexContent.slice(0, jsDocStart) + newJsDoc + indexContent.slice(jsDocEnd);
5891
fs.writeFileSync(indexPath, indexContent);

packages/config/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from './lib/eslint.js';
33
export { create as createCategoryPlugin } from './lib/plugins/category.js';
44
export { create as createDiagnosticsPlugin } from './lib/plugins/diagnostics.js';
55
export { create as createIgnorePlugin } from './lib/plugins/ignore.js';
6+
export * from './lib/tslint.js';
67

78
import type { Config, Plugin, Rule } from '@tsslint/types';
89

packages/config/lib/eslint.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ const loader = async (moduleName: string) => {
3030
return mod as any;
3131
};
3232

33+
type Severity = boolean | 'error' | 'warn';
34+
3335
/**
3436
* Converts an ESLint rules configuration to TSSLint rules.
3537
*
36-
* ---
3738
* ⚠️ **Type definitions not generated**
3839
*
3940
* Please add `@tsslint/config` to `pnpm.onlyBuiltDependencies` in your `package.json` to allow the postinstall script to run.
@@ -51,9 +52,8 @@ const loader = async (moduleName: string) => {
5152
* If the type definitions become outdated, please run `npx tsslint-config-update` to update them.
5253
*/
5354
export async function importESLintRules(
54-
config: { [K in keyof ESLintRulesConfig]: boolean | ESLintRulesConfig[K] },
55+
config: { [K in keyof ESLintRulesConfig]: Severity | [Severity, ESLintRulesConfig[K]] },
5556
context: Partial<ESLint.Rule.RuleContext> = {},
56-
category: ts.DiagnosticCategory = 3 satisfies ts.DiagnosticCategory.Message,
5757
) {
5858
let convertRule: typeof import('@tsslint/compat-eslint').convertRule;
5959
try {
@@ -65,11 +65,10 @@ export async function importESLintRules(
6565

6666
const rules: TSSLint.Rules = {};
6767
for (const [rule, severityOrOptions] of Object.entries(config)) {
68-
let severity: boolean;
68+
let severity: Severity;
6969
let options: any[];
7070
if (Array.isArray(severityOrOptions)) {
71-
severity = true;
72-
options = severityOrOptions;
71+
[severity, ...options] = severityOrOptions;
7372
}
7473
else {
7574
severity = severityOrOptions;
@@ -87,7 +86,11 @@ export async function importESLintRules(
8786
ruleModule,
8887
options,
8988
{ id: rule, ...context },
90-
category,
89+
severity === 'error'
90+
? 1 satisfies ts.DiagnosticCategory.Error
91+
: severity === 'warn'
92+
? 0 satisfies ts.DiagnosticCategory.Warning
93+
: 3 satisfies ts.DiagnosticCategory.Message,
9194
);
9295
}
9396
return rules;

0 commit comments

Comments
 (0)