Skip to content

Commit

Permalink
fix: detect rules with plugin alias name or no plugin name (#231)
Browse files Browse the repository at this point in the history
closes #226
  • Loading branch information
Sysix authored Nov 8, 2024
1 parent 497cc5a commit 883be80
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 9 deletions.
64 changes: 62 additions & 2 deletions src/build-from-oxlint-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,66 @@ describe('buildFromOxlintConfig', () => {
expect(rules[0].rules).not.toBeUndefined();
expect('import/no-unused-modules' in rules[0].rules!).toBe(false);
});

// look here: <https://github.com/oxc-project/oxc/blob/0b329516372a0353e9eb18e5bc0fbe63bce21fee/crates/oxc_linter/src/config/rules.rs#L285>
it('detects oxlint rules with plugin alias inside rules block', () => {
const configs = buildFromOxlintConfig({
rules: {
'eslint/eqeqeq': 'warn',
'typescript/no-unused-vars': 'warn',
'react_perf/jsx-no-new-array-as-prop': 'warn',
'nextjs/no-img-element': 'warn',
'jsx_a11y/alt-text': 'warn',
// 'react/rules-of-hooks': 'warn', -- ToDo oxc-project/eslint-plugin-oxlint#233
// 'deepscan/xxx': 'warn',
},
});

expect(configs.length).toBe(1);
expect(configs[0].rules).not.toBeUndefined();
expect('eqeqeq' in configs[0].rules!).toBe(true);
expect('@typescript-eslint/no-unused-vars' in configs[0].rules!).toBe(true);
expect('react-perf/jsx-no-new-array-as-prop' in configs[0].rules!).toBe(
true
);
expect('@next/next/no-img-element' in configs[0].rules!).toBe(true);
expect('jsx-a11y/alt-text' in configs[0].rules!).toBe(true);
// expect('react-hooks/rules-of-hooks' in rules[0].rules!).toBe(true);
});

it('detects rules without plugin name', () => {
const configs = buildFromOxlintConfig({
rules: {
'no-unused-vars': 'warn',
'jsx-no-new-array-as-prop': 'warn',
'no-img-element': 'warn',
'no-array-reduce': 'warn',
},
});

expect(configs.length).toBe(1);
expect(configs[0].rules).not.toBeUndefined();
expect('@typescript-eslint/no-unused-vars' in configs[0].rules!).toBe(true);
expect('react-perf/jsx-no-new-array-as-prop' in configs[0].rules!).toBe(
true
);
expect('@next/next/no-img-element' in configs[0].rules!).toBe(true);
expect('unicorn/no-array-reduce' in configs[0].rules!).toBe(true);
});

it('skips unknown oxlint rules', () => {
const rules = buildFromOxlintConfig({
rules: {
unknown: 'warn',
'typescript/no-img-element': 'warn', // valid rule, but wrong plugin-name
},
});

expect(rules.length).toBe(1);
expect(rules[0].rules).not.toBeUndefined();
expect('unknown' in rules[0].rules!).toBe(false);
expect('@next/next/no-img-element' in rules[0].rules!).toBe(false);
});
});

const createConfigFileAndBuildFromIt = (
Expand All @@ -146,14 +206,14 @@ describe('buildFromOxlintConfigFile', () => {
`{
"rules": {
// hello world
"no-await-loop": "error",
"no-await-in-loop": "error",
},
}`
);

expect(rules.length).toBe(1);
expect(rules[0].rules).not.toBeUndefined();
expect('no-await-loop' in rules[0].rules!).toBe(true);
expect('no-await-in-loop' in rules[0].rules!).toBe(true);
});

it('fails to find oxlint config', () => {
Expand Down
62 changes: 55 additions & 7 deletions src/build-from-oxlint-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@ import JSONCParser from 'jsonc-parser';
// only used for the scopes where the directory structure doesn't reflect the eslint scope
// such as `typescript` vs `@typescript-eslint` or others. Eslint as a scope is an exception,
// as eslint doesn't have a scope.
// There is a duplicate in scripts/constants.js, for clean builds we manage it in 2 files.
// In the future we can generate maybe this constant into src folder
const scopeMaps = {
// look here: <https://github.com/oxc-project/oxc/blob/0b329516372a0353e9eb18e5bc0fbe63bce21fee/crates/oxc_linter/src/config/rules.rs#L285>
const aliasPluginNames: Record<string, string> = {
eslint: '',
typescript: '@typescript-eslint',
nextjs: '@next/next',

// only in build-config
react_perf: 'react-perf',
jsx_a11y: 'jsx-a11y',
};

const allRulesObjects = Object.values(configByCategory).map(
(config) => config.rules
);
const allRules: string[] = allRulesObjects.flatMap((rulesObject) =>
Object.keys(rulesObject)
);

type OxlintConfigPlugins = string[];

type OxlintConfigCategories = Record<string, unknown>;
Expand Down Expand Up @@ -95,8 +105,8 @@ const handleCategoriesScope = (
// iterate to each rule to check if the rule can be appended, because the plugin is activated
for (const rule of Object.keys(possibleRules)) {
for (const plugin of plugins) {
// @ts-expect-error -- come on TS, we are checking if the plugin exists in the configByscopeMapsCategory
const pluginPrefix = plugin in scopeMaps ? scopeMaps[plugin] : plugin;
const pluginPrefix =
plugin in aliasPluginNames ? aliasPluginNames[plugin] : plugin;

// the rule has no prefix, so it is a eslint one
if (pluginPrefix === '' && !rule.includes('/')) {
Expand All @@ -110,6 +120,35 @@ const handleCategoriesScope = (
}
};

const getEsLintRuleName = (rule: string): string | undefined => {
// there is no plugin prefix, it can be all plugin
if (!rule.includes('/')) {
return allRules.find(
(search) => search.endsWith(`/${rule}`) || search === rule
);
}

// greedy works with `@next/next/no-img-element` as an example
const match = rule.match(/(^.*)\/(.*)/);

if (match === null) {
return undefined;
}

const pluginName = match[1];
const ruleName = match[2];

// map to the right eslint plugin
const esPluginName =
pluginName in aliasPluginNames ? aliasPluginNames[pluginName] : pluginName;

// extra check for eslint
const expectedRule =
esPluginName === '' ? ruleName : `${esPluginName}/${ruleName}`;

return allRules.find((rule) => rule == expectedRule);
};

/**
* checks if the oxlint rule is activated/deactivated and append/remove it.
*/
Expand All @@ -118,12 +157,21 @@ const handleRulesScope = (
rules: Record<string, 'off'>
): void => {
for (const rule in oxlintRules) {
const eslintName = getEsLintRuleName(rule);

if (eslintName === undefined) {
console.warn(
`eslint-plugin-oxlint: could not find matching eslint rule for "${rule}"`
);
continue;
}

// is this rules not turned off
if (isActiveValue(oxlintRules[rule])) {
rules[rule] = 'off';
rules[eslintName] = 'off';
} else if (rule in rules && isDeactivateValue(oxlintRules[rule])) {
// rules extended by categories or plugins can be disabled manually
delete rules[rule];
delete rules[eslintName];
}
}
};
Expand Down

0 comments on commit 883be80

Please sign in to comment.