eslint-plugin-oxlint
Advanced tools
Comparing version 0.11.0 to 0.11.1
{ | ||
"name": "eslint-plugin-oxlint", | ||
"version": "0.11.0", | ||
"version": "0.11.1", | ||
"description": "Turn off all rules already supported by oxlint", | ||
"type": "module", | ||
"packageManager": "pnpm@9.12.2", | ||
"types": "./dist/index.d.ts", | ||
"packageManager": "pnpm@9.12.3", | ||
"exports": { | ||
@@ -15,12 +16,12 @@ ".": { | ||
"./rules-by-category": { | ||
"types": "./dist/rules-by-category.d.ts", | ||
"import": "./dist/rules-by-category.mjs", | ||
"require": "./dist/rules-by-category.cjs", | ||
"default": "./dist/rules-by-category.mjs" | ||
"types": "./dist/generated/rules-by-category.d.ts", | ||
"import": "./dist/generated/rules-by-category.mjs", | ||
"require": "./dist/generated/rules-by-category.cjs", | ||
"default": "./dist/generated/rules-by-category.mjs" | ||
}, | ||
"./rules-by-scope": { | ||
"types": "./dist/rules-by-scope.d.ts", | ||
"import": "./dist/rules-by-scope.mjs", | ||
"require": "./dist/rules-by-scope.cjs", | ||
"default": "./dist/rules-by-scope.mjs" | ||
"types": "./dist/generated/rules-by-scope.d.ts", | ||
"import": "./dist/generated/rules-by-scope.mjs", | ||
"require": "./dist/generated/rules-by-scope.cjs", | ||
"default": "./dist/generated/rules-by-scope.mjs" | ||
} | ||
@@ -67,3 +68,3 @@ }, | ||
"memfs": "^4.14.0", | ||
"oxlint": "^0.11.0", | ||
"oxlint": "^0.11.1", | ||
"prettier": "^3.3.3", | ||
@@ -70,0 +71,0 @@ "scule": "^1.3.0", |
@@ -56,6 +56,6 @@ # eslint-plugin-oxlint | ||
// eslint.config.js | ||
import { buildFromOxlintConfigFile } from 'eslint-plugin-oxlint'; | ||
import oxlint from 'eslint-plugin-oxlint'; | ||
export default [ | ||
..., // other plugins | ||
...buildFromOxlintConfigFile('./oxlint.json'), | ||
...oxlint.buildFromOxlintConfigFile('./oxlint.json'), | ||
]; | ||
@@ -68,6 +68,6 @@ ``` | ||
// eslint.config.js | ||
import { buildFromOxlintConfig } from 'eslint-plugin-oxlint'; | ||
import oxlint from 'eslint-plugin-oxlint'; | ||
export default [ | ||
..., // other plugins | ||
...buildFromOxlintConfig({ | ||
...oxlint.buildFromOxlintConfig({ | ||
categories: { | ||
@@ -74,0 +74,0 @@ correctness: 'warn' |
@@ -9,3 +9,3 @@ import { describe, expect, it } from 'vitest'; | ||
import type { Linter } from 'eslint'; | ||
import { typescriptRulesExtendEslintRules } from '../scripts/constants.js'; | ||
import { typescriptRulesExtendEslintRules } from './constants.js'; | ||
@@ -123,4 +123,67 @@ describe('buildFromOxlintConfig', () => { | ||
}); | ||
expect('import/no-unused-modules' in rules).toBe(false); | ||
expect(rules.length).toBe(1); | ||
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', | ||
// '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 configs[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); | ||
}); | ||
}); | ||
@@ -148,3 +211,3 @@ | ||
// hello world | ||
"no-await-loop": "error", | ||
"no-await-in-loop": "error", | ||
}, | ||
@@ -156,3 +219,3 @@ }` | ||
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); | ||
}); | ||
@@ -159,0 +222,0 @@ |
import fs from 'node:fs'; | ||
import configByCategory from './configs-by-category.js'; | ||
import configByCategory from './generated/configs-by-category.js'; | ||
import type { Linter } from 'eslint'; | ||
import JSONCParser from 'jsonc-parser'; | ||
import { | ||
aliasPluginNames, | ||
reactHookRulesInsideReactScope, | ||
} from './constants.js'; | ||
// these are the mappings from the scope in the rules.rs to the eslint scope | ||
// 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 = { | ||
eslint: '', | ||
typescript: '@typescript-eslint', | ||
}; | ||
const allRulesObjects = Object.values(configByCategory).map( | ||
(config) => config.rules | ||
); | ||
const allRules: string[] = allRulesObjects.flatMap((rulesObject) => | ||
Object.keys(rulesObject) | ||
); | ||
@@ -37,2 +37,8 @@ type OxlintConfigPlugins = string[]; | ||
/** | ||
* Detects it the value is an object | ||
*/ | ||
const isObject = (value: unknown): boolean => | ||
typeof value === 'object' && value !== null && !Array.isArray(value); | ||
/** | ||
* tries to read the oxlint config file and returning its JSON content. | ||
@@ -51,3 +57,3 @@ * if the file is not found or could not be parsed, undefined is returned. | ||
if (typeof configContent !== 'object' || Array.isArray(configContent)) { | ||
if (!isObject(configContent)) { | ||
throw new TypeError('not an valid config file'); | ||
@@ -93,4 +99,4 @@ } | ||
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; | ||
@@ -109,2 +115,39 @@ // the rule has no prefix, so it is a eslint one | ||
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 | ||
let esPluginName = | ||
pluginName in aliasPluginNames ? aliasPluginNames[pluginName] : pluginName; | ||
// special case for eslint-plugin-react-hooks | ||
if ( | ||
esPluginName === 'react' && | ||
reactHookRulesInsideReactScope.includes(ruleName) | ||
) { | ||
esPluginName = 'react-hooks'; | ||
} | ||
// extra check for eslint | ||
const expectedRule = | ||
esPluginName === '' ? ruleName : `${esPluginName}/${ruleName}`; | ||
return allRules.find((rule) => rule == expectedRule); | ||
}; | ||
/** | ||
@@ -118,8 +161,17 @@ * checks if the oxlint rule is activated/deactivated and append/remove it. | ||
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]; | ||
} | ||
@@ -129,3 +181,8 @@ } | ||
const isOffValue = (value: unknown) => value === 'off' || value === 0; | ||
/** | ||
* checks if value is validSet, or if validSet is an array, check if value is first value of it | ||
*/ | ||
const isValueInSet = (value: unknown, validSet: unknown[]) => | ||
validSet.includes(value) || | ||
(Array.isArray(value) && validSet.includes(value[0])); | ||
@@ -135,15 +192,9 @@ /** | ||
*/ | ||
const isDeactivateValue = (value: unknown): boolean => { | ||
return isOffValue(value) || (Array.isArray(value) && isOffValue(value[0])); | ||
}; | ||
const isDeactivateValue = (value: unknown) => isValueInSet(value, ['off', 0]); | ||
const isOnValue = (value: unknown) => | ||
value === 'error' || value === 'warn' || value === 1 || value === 2; | ||
/** | ||
* check if the value is "error", "warn", 1, 2, ["error", ...], ["warn", ...], [1, ...], or [2, ...] | ||
*/ | ||
const isActiveValue = (value: unknown): boolean => { | ||
return isOnValue(value) || (Array.isArray(value) && isOnValue(value[0])); | ||
}; | ||
const isActiveValue = (value: unknown) => | ||
isValueInSet(value, ['error', 'warn', 1, 2]); | ||
@@ -169,5 +220,3 @@ /** | ||
): OxlintConfigCategories | undefined => { | ||
return 'categories' in config && | ||
typeof config.categories === 'object' && | ||
config.categories !== null | ||
return 'categories' in config && isObject(config.categories) | ||
? (config.categories as OxlintConfigCategories) | ||
@@ -184,5 +233,3 @@ : undefined; | ||
): OxlintConfigRules | undefined => { | ||
return 'rules' in config && | ||
typeof config.rules === 'object' && | ||
config.rules !== null | ||
return 'rules' in config && isObject(config.rules) | ||
? (config.rules as OxlintConfigRules) | ||
@@ -189,0 +236,0 @@ : undefined; |
@@ -1,7 +0,3 @@ | ||
import * as ruleMapsByScope from './rules-by-scope.js'; | ||
import * as ruleMapsByCategory from './rules-by-category.js'; | ||
import configByScope from './configs-by-scope.js'; | ||
import configByCategory from './configs-by-category.js'; | ||
export { | ||
import configs from './configs.js'; | ||
import { | ||
buildFromOxlintConfig, | ||
@@ -11,37 +7,6 @@ buildFromOxlintConfigFile, | ||
type UnionToIntersection<U> = ( | ||
U extends unknown ? (x: U) => void : never | ||
) extends (x: infer I) => void | ||
? I | ||
: never; | ||
type RulesGroups = keyof typeof ruleMapsByScope; | ||
type AllRules = (typeof ruleMapsByScope)[RulesGroups]; | ||
const allRules: UnionToIntersection<AllRules> = Object.assign( | ||
{}, | ||
...Object.values(ruleMapsByScope) | ||
); | ||
export default { | ||
configs: { | ||
recommended: { | ||
plugins: ['oxlint'], | ||
rules: ruleMapsByCategory.correctnessRules, | ||
}, | ||
all: { | ||
plugins: ['oxlint'], | ||
rules: allRules, | ||
}, | ||
'flat/all': { | ||
name: 'oxlint/all', | ||
rules: allRules, | ||
}, | ||
'flat/recommended': { | ||
name: 'oxlint/recommended', | ||
rules: ruleMapsByCategory.correctnessRules, | ||
}, | ||
...configByScope, | ||
...configByCategory, | ||
}, | ||
configs, | ||
buildFromOxlintConfig, | ||
buildFromOxlintConfigFile, | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
528431
39
10683
1