Skip to content

Commit 09cd06a

Browse files
feat(eslint): add convertFormattingRules API (#49)
1 parent 627c3c6 commit 09cd06a

File tree

3 files changed

+100
-59
lines changed

3 files changed

+100
-59
lines changed

packages/eslint/index.ts

Lines changed: 96 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type * as TSSLint from '@tsslint/types';
22
import type * as ESLint from 'eslint';
33
import type * as ts from 'typescript';
4-
import type { ESLintRulesConfig } from './lib/types.js';
4+
import type { ESLintRulesConfig, O } from './lib/types.js';
55

66
export { create as createDisableNextLinePlugin } from './lib/plugins/disableNextLine.js';
77
export { create as createShowDocsActionPlugin } from './lib/plugins/showDocsAction.js';
@@ -12,14 +12,21 @@ const estrees = new WeakMap<ts.SourceFile, {
1212
eventQueue: any[];
1313
}>();
1414
const noop = () => { };
15+
const plugins: Record<string, Promise<{
16+
rules: Record<string, ESLint.Rule.RuleModule>;
17+
}>> = {};
18+
const loader = async (mod: string) => {
19+
try {
20+
return require(mod);
21+
} catch {
22+
return await import(mod);
23+
}
24+
};
1525

1626
/**
1727
* @deprecated Use `convertRules` instead.
1828
*/
19-
export function convertConfig(
20-
rulesConfig: ESLintRulesConfig,
21-
loader: (mod: string) => any = require
22-
) {
29+
export function convertConfig(rulesConfig: ESLintRulesConfig) {
2330
const rules: TSSLint.Rules = {};
2431
const plugins: Record<string, {
2532
rules: Record<string, ESLint.Rule.RuleModule>;
@@ -65,7 +72,7 @@ export function convertConfig(
6572
const ruleName = rule.slice(slashIndex + 1);
6673

6774
try {
68-
plugins[pluginName] ??= loader(pluginName);
75+
plugins[pluginName] ??= require(pluginName);
6976
} catch (e) {
7077
_rule = noop;
7178
console.log('\n\n', new Error(`Plugin "${pluginName}" does not exist.`));
@@ -99,20 +106,8 @@ export function convertConfig(
99106
return rules;
100107
}
101108

102-
export async function convertRules(
103-
rulesConfig: ESLintRulesConfig,
104-
loader = async (mod: string) => {
105-
try {
106-
return require(mod);
107-
} catch {
108-
return await import(mod);
109-
}
110-
}
111-
) {
109+
export async function convertRules(rulesConfig: ESLintRulesConfig) {
112110
const rules: TSSLint.Rules = {};
113-
const plugins: Record<string, Promise<{
114-
rules: Record<string, ESLint.Rule.RuleModule>;
115-
}>> = {};
116111
for (const [rule, severityOrOptions] of Object.entries(rulesConfig)) {
117112
let rawSeverity: 'error' | 'warn' | 'suggestion' | 'off' | 0 | 1 | 2;
118113
let options: any[];
@@ -142,45 +137,91 @@ export async function convertRules(
142137
rules[rule] = noop;
143138
continue;
144139
}
145-
rules[rule] = await loadRuleByKey(rule, options, tsSeverity);
140+
const ruleModule = await getRuleByKey(rule);
141+
if (!ruleModule) {
142+
rules[rule] = noop;
143+
continue;
144+
}
145+
rules[rule] = convertRule(ruleModule, options, tsSeverity);
146146
}
147147
return rules;
148+
}
149+
150+
export async function convertFormattingRules(config: {
151+
[K in keyof ESLintRulesConfig]?: ESLintRulesConfig[K] extends O<infer T> | undefined ? T : never;
152+
}) {
153+
const processes: TSSLint.FormattingProcess[] = [];
154+
for (const [rule, options] of Object.entries(config)) {
155+
const ruleModule = await getRuleByKey(rule);
156+
if (!ruleModule) {
157+
continue;
158+
}
159+
const tsslingRule = convertRule(ruleModule, options, 2 satisfies ts.DiagnosticCategory.Suggestion);
160+
processes.push(ctx => {
161+
const reporter: TSSLint.Reporter = {
162+
withDeprecated: () => reporter,
163+
withUnnecessary: () => reporter,
164+
withRefactor: () => reporter,
165+
withFix(_title, getChanges) {
166+
const changes = getChanges();
167+
for (const change of changes) {
168+
if (change.fileName !== ctx.sourceFile.fileName) {
169+
continue;
170+
}
171+
for (const textChange of change.textChanges) {
172+
ctx.replace(textChange.span.start, textChange.span.start + textChange.span.length, textChange.newText);
173+
}
174+
}
175+
return reporter;
176+
},
177+
};
178+
tsslingRule({
179+
...ctx,
180+
languageService: {} as any,
181+
languageServiceHost: {
182+
getCompilationSettings: () => ({}),
183+
} as any,
184+
reportError: () => reporter,
185+
reportWarning: () => reporter,
186+
reportSuggestion: () => reporter,
187+
});
188+
});
189+
}
190+
return processes;
191+
}
148192

149-
async function loadRuleByKey(rule: string, options: any[], tsSeverity?: ts.DiagnosticCategory) {
150-
let ruleModule: ESLint.Rule.RuleModule;
151-
const slashIndex = rule.indexOf('/');
152-
if (slashIndex !== -1) {
153-
const pluginName = rule.startsWith('@')
154-
? `${rule.slice(0, slashIndex)}/eslint-plugin`
155-
: `eslint-plugin-${rule.slice(0, slashIndex)}`;
156-
const ruleName = rule.slice(slashIndex + 1);
193+
async function getRuleByKey(rule: string) {
194+
const slashIndex = rule.indexOf('/');
195+
if (slashIndex !== -1) {
196+
const pluginName = rule.startsWith('@')
197+
? `${rule.slice(0, slashIndex)}/eslint-plugin`
198+
: `eslint-plugin-${rule.slice(0, slashIndex)}`;
199+
const ruleName = rule.slice(slashIndex + 1);
157200

158-
try {
159-
plugins[pluginName] ??= loader(pluginName);
160-
} catch (e) {
161-
console.log('\n\n', new Error(`Plugin "${pluginName}" does not exist.`));
162-
return noop;
163-
}
201+
try {
202+
plugins[pluginName] ??= loader(pluginName);
203+
} catch (e) {
204+
console.log('\n\n', new Error(`Plugin "${pluginName}" does not exist.`));
205+
return;
206+
}
164207

165-
let plugin = await plugins[pluginName];
166-
if ('default' in plugin) {
167-
// @ts-expect-error
168-
plugin = plugin.default;
169-
}
170-
ruleModule = plugin.rules[ruleName];
171-
if (!ruleModule) {
172-
console.log('\n\n', new Error(`Rule "${ruleName}" does not exist in plugin "${pluginName}".`));
173-
return noop;
174-
}
208+
let plugin = await plugins[pluginName];
209+
if ('default' in plugin) {
210+
// @ts-expect-error
211+
plugin = plugin.default;
175212
}
176-
else {
177-
try {
178-
ruleModule = require(`../../eslint/lib/rules/${rule}.js`);
179-
} catch {
180-
ruleModule = require(`./node_modules/eslint/lib/rules/${rule}.js`);
181-
}
213+
if (!plugin.rules[ruleName]) {
214+
console.log('\n\n', new Error(`Rule "${ruleName}" does not exist in plugin "${pluginName}".`));
215+
return;
216+
}
217+
return plugin.rules[ruleName];
218+
}
219+
else {
220+
try {
221+
return require(`../../eslint/lib/rules/${rule}.js`);
222+
} catch {
223+
return require(`./node_modules/eslint/lib/rules/${rule}.js`);
182224
}
183-
return convertRule(ruleModule, options, tsSeverity);
184225
}
185226
}
186227

@@ -216,7 +257,7 @@ export function convertRule(
216257
const { sourceCode, eventQueue } = getEstree(
217258
sourceFile,
218259
languageService,
219-
() => languageServiceHost.getCompilationSettings()
260+
languageServiceHost.getCompilationSettings()
220261
);
221262
const emitter = createEmitter();
222263

@@ -504,7 +545,7 @@ export function convertRule(
504545
function getEstree(
505546
sourceFile: ts.SourceFile,
506547
languageService: ts.LanguageService,
507-
getCompilationSettings: () => ts.CompilerOptions
548+
compilationSettings: ts.CompilerOptions
508549
) {
509550
if (!estrees.has(sourceFile)) {
510551
let program: ts.Program | undefined;
@@ -530,8 +571,8 @@ function getEstree(
530571
range: true,
531572
preserveNodeMaps: true,
532573
filePath: sourceFile.fileName,
533-
emitDecoratorMetadata: getCompilationSettings().emitDecoratorMetadata ?? false,
534-
experimentalDecorators: getCompilationSettings().experimentalDecorators ?? false,
574+
emitDecoratorMetadata: compilationSettings.emitDecoratorMetadata ?? false,
575+
experimentalDecorators: compilationSettings.experimentalDecorators ?? false,
535576
});
536577
const sourceCode = new SourceCode({
537578
text: sourceFile.text,

packages/eslint/lib/dtsGenerate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ export async function generate(
1717
let dts = '';
1818
let defId = 0;
1919

20-
line(`type S = 'error' | 'warn' | 'suggestion' | 'off' | 0 | 1 | 2;`);
21-
line(`type O<T extends any[]> = S | [S, ...options: T];`);
20+
line(`export type S = 'error' | 'warn' | 'suggestion' | 'off' | 0 | 1 | 2;`);
21+
line(`export type O<T extends any[]> = S | [S, ...options: T];`);
2222
line(``);
2323
line(`export interface ESLintRulesConfig {`);
2424
indentLevel++;

packages/eslint/lib/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
type S = 'error' | 'warn' | 'suggestion' | 'off' | 0 | 1 | 2;
2-
type O<T extends any[]> = S | [S, ...options: T];
1+
export type S = 'error' | 'warn' | 'suggestion' | 'off' | 0 | 1 | 2;
2+
export type O<T extends any[]> = S | [S, ...options: T];
33

44
export interface ESLintRulesConfig {
55
[key: string]: O<any[]>;

0 commit comments

Comments
 (0)