Skip to content

Commit

Permalink
feat: add OXLint (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
lvjiaxuan committed Feb 1, 2024
1 parent 82593e2 commit 921816e
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 37 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,44 @@ All rules inherit from [@antfu/eslint-config](https://github.com/antfu/eslint-co
# Features
1. Add my ESLint plugin.
2. Try to detect `tsconfig.json` if TypeScript is enabled, which means enabling type-aware rules.
3. Add my [eslint-plugin-oxlint](./packages/eslint-plugin-oxlint/README.md).

# Usage

As same as [antfu's](https://github.com/antfu/eslint-config).

## With [OXLint](https://github.com/oxc-project/oxc#-linter)

```js
// eslint.config.js
import lv from '@lvjiaxuan/eslint-config'

export default lv({
oxlint: true // equals to `{ deny: 'correctness' }`.
})
```

Options type, respects its original [options](https://oxc-project.github.io/docs/guide/usage/linter.html#useful-options):
<!-- eslint-skip -->
```ts
type OptionsOXLint = {
deny?: Categories | 'all'
allow?: (keyof OXLintRules)[]
// plugins: TODO
} | boolean
```
> [!Tip]
> 1. [Categories](https://github.com/oxc-project/oxc/blob/2beacd3f4d2707ab64ff98bf05462673e9993b71/crates/oxc_linter/src/rule.rs#L37) of OXLint.
> 2. [Rules](https://github.com/oxc-project/oxc/tree/main/crates/oxc_linter/src/rules) supported by OXLint.
Modify lint scritps:
```json
// package.json
{
"scripts": {
- "lint": "eslint ."
+ "lint": "oxlint . && eslint ." // `oxlint` installed, or use alternative `npx oxlint` .
}
}
```
9 changes: 8 additions & 1 deletion eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import lv from './src'

export default lv()
export default lv({
oxlint: true,
ignores: ['packages/eslint-plugin-oxlint/category-rules.json'],
}, {
rules: {
'no-console': 'off',
},
})
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,31 @@
"dist"
],
"scripts": {
"dev": "nr build --watch",
"view": "nr dev & nlx eslint-flat-config-viewer",
"dev": "pnpm run build --watch",
"view": "pnpm run dev & pnpm dlx eslint-flat-config-viewer",
"build": "tsup src/index.ts --format=esm,cjs --dts",
"lint": "eslint .",
"prepublishOnly": "nr build"
"lint": "oxlint . && eslint .",
"prepublishOnly": "pnpm run build"
},
"dependencies": {
"@antfu/eslint-config": "^2.6.3",
"@lvjiaxuan/eslint-plugin": "workspace:*",
"@lvjiaxuan/eslint-plugin-oxlint": "workspace:*",
"fs-extra": "^11.2.0"
},
"devDependencies": {
"@types/eslint": "^8.56.2",
"@types/fs-extra": "^11.0.4",
"@types/node": "^20.11.7",
"eslint": "npm:eslint-ts-patch@8.56.0-0",
"eslint-plugin-oxlint": "^0.2.3",
"eslint-ts-patch": "8.56.0-0",
"oxlint": "^0.2.6",
"tsup": "^8.0.1",
"typescript": "^5.3.3"
},
"lvr": {
"all": true,
"tag": 10
}
}
}
1 change: 1 addition & 0 deletions packages/eslint-plugin-oxlint/category-rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"correctness":{"deepscan/bad-array-method-on-arguments":"off","deepscan/bad-char-at-comparison":"off","deepscan/bad-comparison-sequence":"off","deepscan/bad-object-literal-comparison":"off","deepscan/bad-min-max-func":"off","deepscan/bad-replace-all-arg":"off","deepscan/missing-throw":"off","deepscan/number-arg-out-of-range":"off","deepscan/uninvoked-array-callback":"off","for-direction":"off","no-async-promise-executor":"off","no-caller":"off","no-class-assign":"off","no-compare-neg-zero":"off","no-cond-assign":"off","no-const-assign":"off","no-constant-binary-expression":"off","no-constant-condition":"off","no-control-regex":"off","no-debugger":"off","no-delete-var":"off","no-dupe-class-members":"off","no-dupe-else-if":"off","no-dupe-keys":"off","no-duplicate-case":"off","no-empty-character-class":"off","no-empty-pattern":"off","no-ex-assign":"off","no-extra-boolean-cast":"off","no-func-assign":"off","no-global-assign":"off","no-inner-declarations":"off","no-irregular-whitespace":"off","no-loss-of-precision":"off","no-new-symbol":"off","no-obj-calls":"off","no-self-assign":"off","no-setter-return":"off","no-shadow-restricted-names":"off","no-sparse-arrays":"off","no-unsafe-finally":"off","no-unsafe-negation":"off","no-unused-labels":"off","no-unused-private-class-members":"off","no-useless-catch":"off","no-useless-escape":"off","require-yield":"off","use-isnan":"off","valid-typeof":"off","typescript/no-extra-non-null-assertion":"off","typescript/no-misused-new":"off","typescript/no-namespace":"off","typescript/no-non-null-asserted-optional-chain":"off","typescript/no-this-alias":"off","typescript/no-unsafe-declaration-merging":"off","typescript/prefer-as-const":"off","typescript/triple-slash-reference":"off","jest/expect-expect":"off","jest/no-conditional-expect":"off","jest/no-disabled-tests":"off","jest/no-export":"off","jest/no-focused-tests":"off","jest/no-standalone-expect":"off","jest/valid-describe-callback":"off","jest/valid-expect":"off","jest/valid-title":"off","unicorn/no-document-cookie":"off","unicorn/no-empty-file":"off","unicorn/no-invalid-remove-event-listener":"off","unicorn/no-new-array":"off","unicorn/no-thenable":"off","unicorn/no-unnecessary-await":"off","unicorn/no-useless-fallback-in-spread":"off","unicorn/no-useless-length-check":"off","unicorn/no-useless-spread":"off","unicorn/prefer-set-size":"off","unicorn/prefer-string-starts-ends-with":"off","react/jsx-no-target-blank":"off","react/jsx-key":"off","react/jsx-no-duplicate-props":"off","react/jsx-no-useless-fragment":"off","react/jsx-no-undef":"off","react/no-children-prop":"off","react/no-direct-mutation-state":"off","react/no-find-dom-node":"off","react/no-render-return-value":"off","react/no-string-refs":"off","react/no-is-mounted":"off","react/require-render-return":"off","react_perf/jsx-no-jsx-as-prop":"off","react_perf/jsx-no-new-array-as-prop":"off","react_perf/jsx-no-new-function-as-prop":"off","react_perf/jsx-no-new-object-as-prop":"off","oxc/const-comparisons":"off","oxc/double-comparisons":"off","oxc/erasing-op":"off","oxc/only-used-in-recursion":"off","nextjs/google-font-display":"off","nextjs/google-font-preconnect":"off","nextjs/inline-script-id":"off","nextjs/next-script-for-ga":"off","nextjs/no-assign-module-variable":"off","nextjs/no-async-client-component":"off","nextjs/no-css-tags":"off","nextjs/no-head-element":"off","nextjs/no-head-import-in-document":"off","nextjs/no-img-element":"off","nextjs/no-script-component-in-head":"off","nextjs/no-sync-scripts":"off","nextjs/no-title-in-document-head":"off","nextjs/no-typos":"off","nextjs/no-document-import-in-page":"off"},"restriction":{"deepscan/bad-bitwise-operator":"off","no-bitwise":"off","no-console":"off","no-empty":"off","no-eval":"off","no-regex-spaces":"off","no-unsafe-optional-chaining":"off","no-var":"off","no-void":"off","typescript/no-explicit-any":"off","typescript/no-var-requires":"off","unicorn/no-abusive-eslint-disable":"off","unicorn/no-array-reduce":"off","unicorn/no-array-for-each":"off","unicorn/no-nested-ternary":"off","unicorn/prefer-modern-math-apis":"off","unicorn/prefer-number-properties":"off","react/button-has-type":"off","react/no-danger":"off","react/no-unknown-property":"off"},"pedantic":{"array-callback-return":"off","eqeqeq":"off","no-array-constructor":"off","no-case-declarations":"off","no-mixed-operators":"off","no-prototype-builtins":"off","no-redeclare":"off","no-return-await":"off","no-self-compare":"off","typescript/ban-types":"off","typescript/no-duplicate-enum-values":"off","unicorn/escape-case":"off","unicorn/explicit-length-check":"off","unicorn/new-for-builtins":"off","unicorn/no-hex-escape":"off","unicorn/no-instanceof-array":"off","unicorn/no-lonely-if":"off","unicorn/no-negated-condition":"off","unicorn/no-new-buffer":"off","unicorn/no-object-as-default-parameter":"off","unicorn/no-static-only-class":"off","unicorn/no-this-assignment":"off","unicorn/no-typeof-undefined":"off","unicorn/no-unreadable-iife":"off","unicorn/no-useless-promise-resolve-reject":"off","unicorn/no-useless-switch-case":"off","unicorn/prefer-array-flat":"off","unicorn/prefer-array-some":"off","unicorn/prefer-blob-reading-methods":"off","unicorn/prefer-code-point":"off","unicorn/prefer-date-now":"off","unicorn/prefer-dom-node-append":"off","unicorn/prefer-dom-node-dataset":"off","unicorn/prefer-dom-node-remove":"off","unicorn/prefer-event-target":"off","unicorn/prefer-math-trunc":"off","unicorn/prefer-native-coercion-functions":"off","unicorn/prefer-prototype-methods":"off","unicorn/prefer-query-selector":"off","unicorn/prefer-regexp-test":"off","unicorn/prefer-string-replace-all":"off","unicorn/prefer-string-slice":"off","unicorn/prefer-type-error":"off","unicorn/require-number-to-fixed-digits-argument":"off","react/no-unescaped-entities":"off"},"style":{"default-case-last":"off","typescript/adjacent-overload-signatures":"off","typescript/no-empty-interface":"off","jest/max-expects":"off","jest/no-alias-methods":"off","jest/no-confusing-set-timeout":"off","jest/no-deprecated-functions":"off","jest/no-done-callback":"off","jest/no-hooks":"off","jest/no-identical-title":"off","jest/no-interpolation-in-snapshots":"off","jest/no-jasmine-globals":"off","jest/no-mocks-import":"off","jest/no-restricted-jest-methods":"off","jest/no-restricted-matchers":"off","jest/no-test-prefixes":"off","jest/no-test-return-statement":"off","jest/prefer-called-with":"off","jest/prefer-todo":"off","unicorn/catch-error-name":"off","unicorn/prefer-node-protocol":"off","unicorn/empty-brace-spaces":"off","unicorn/error-message":"off","unicorn/filename-case":"off","unicorn/no-await-expression-member":"off","unicorn/no-console-spaces":"off","unicorn/no-null":"off","unicorn/no-unreadable-array-destructuring":"off","unicorn/no-zero-fractions":"off","unicorn/number-literal-case":"off","unicorn/numeric-separators-style":"off","unicorn/prefer-array-flat-map":"off","unicorn/prefer-dom-node-text-content":"off","unicorn/prefer-includes":"off","unicorn/prefer-logical-operator-over-ternary":"off","unicorn/prefer-modern-dom-apis":"off","unicorn/prefer-optional-catch-binding":"off","unicorn/prefer-reflect-apply":"off","unicorn/prefer-spread":"off","unicorn/prefer-string-trim-start-end":"off","unicorn/require-array-join-separator":"off","unicorn/switch-case-braces":"off","unicorn/text-encoding-identifier-case":"off","unicorn/throw-new-error":"off"},"suspicious":{"no-empty-static-block":"off","typescript/no-unnecessary-type-constraint":"off","jest/no-commented-out-tests":"off","unicorn/prefer-add-event-listener":"off","react/jsx-no-comment-textnodes":"off","react/react-in-jsx-scope":"off","oxc/approx-constant":"off","oxc/misrefactored-assign-op":"off"},"nursery":{"constructor-super":"off","getter-return":"off","no-fallthrough":"off","no-import-assign":"off","no-undef":"off","typescript/ban-ts-comment":"off","import/default":"off","import/no-named-as-default-member":"off","import/no-named-as-default":"off","import/named":"off","import/no-cycle":"off","import/no-self-import":"off","import/no-amd":"off","import/export":"off"},"perf":{"oxc/no-accumulating-spread":"off"}}
41 changes: 41 additions & 0 deletions packages/eslint-plugin-oxlint/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@lvjiaxuan/eslint-plugin-oxlint",
"type": "module",
"version": "1.7.0",
"description": "Turn off rules supported by oxlint, provide classified configs",
"author": "lvjiaxuan <471501748@qq.com> (https://github.com/lvjiaxuan)",
"keywords": [
"eslint-plugin"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./category-rules": "./category-rules.json"
},
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"g": "pnpm dlx jiti scripts/generate-rules.ts",
"build": "pnpm run g && tsup src/index.ts --format=esm,cjs --dts",
"postinstall": "pnpm run build",
"prepublishOnly": "pnpm run build"
},
"dependencies": {
"execa": "^8.0.1",
"local-pkg": "^0.5.0"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"devDependencies": {
"@antfu/eslint-define-config": "1.23.0-2",
"@type-challenges/utils": "^0.1.1"
}
}
49 changes: 49 additions & 0 deletions packages/eslint-plugin-oxlint/scripts/generate-rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { join } from 'node:path'
import { $ } from 'execa'
import fs from 'fs-extra'

async function main() {
const lines = (await $`pnpm dlx oxlint --rules`).stdout.split('\n')

const categoryRules = new Map<string, Set<string>>()

const categoryRegex = /^[A-Z]{1}[A-za-z]+(?=\s{1}\(\d+\):)/
const ruleRegex = /^•\s{1}(?<pluginName>[a-z_-]+):\s{1}(?<ruleName>[a-z-]+)/
let lastAddCategory: string = ''
lines.forEach((line) => {
const categoryRegexMatch = line.match(categoryRegex)

if (categoryRegexMatch) {
const category = categoryRegexMatch[0].toLowerCase()
categoryRules.set(category, new Set())
lastAddCategory = category
}
else {
const ruleMatch = line.match(ruleRegex)
if (ruleMatch) {
const { pluginName, ruleName } = ruleMatch.groups!
categoryRules
.get(lastAddCategory)
?.add(`${pluginName === 'eslint' ? '' : `${pluginName.toLowerCase()}/`}${ruleName.toLowerCase()}`)
}
}
})

// toJSON
const json: Record<string, Record<string, 'off'>> = {}
for (const [name, value] of categoryRules) {
json[name] = [...value].reduce((acc, i) => {
acc[i] = 'off' as const
return acc
}, {} as Record<string, 'off'>)
}

await fs.writeJSON(join(__dirname, '..', 'category-rules.json'), json)
}

try {
void main()
}
catch (e) {
console.error(e)
}
62 changes: 62 additions & 0 deletions packages/eslint-plugin-oxlint/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { FlatESLintConfigItem, RuleLevel } from '@antfu/eslint-define-config'
import type { MergeInsertions, UnionToIntersection } from '@type-challenges/utils'
import categoryRules from './../category-rules.json'

type CategoryRules<Rules extends typeof categoryRules = typeof categoryRules> = MergeInsertions<{
[K in keyof Rules]: {
[KK in keyof Rules[K]]: RuleLevel
}
}>

type Categories = keyof typeof categoryRules

const rules = categoryRules as CategoryRules

export type OXLintRules = MergeInsertions<UnionToIntersection<CategoryRules[Categories]>>

export type OptionsOXLint = {
deny?: Categories | 'all'
allow?: (keyof OXLintRules)[]
// plugins: TODO
} | boolean

// https://github.com/antfu/eslint-config/blob/3707078921b8d246b1d2980c5c4cfe7f39c67699/src/types.ts#L59
type FlatConfigItem = Omit<FlatESLintConfigItem<OXLintRules, false>, 'plugins'> & {
/**
* Custom name of each config item
*/
name?: string
/**
* An object containing a name-value mapping of plugin names to plugin objects. When `files` is specified, these plugins are only available to the matching files.
*
* @see [Using plugins in your configuration](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#using-plugins-in-your-configuration)
*/
plugins?: Record<string, any>
}

export async function oxlint(options: OptionsOXLint = true): Promise<FlatConfigItem[]> {
if (options === true) {
options = { deny: 'correctness' }
}
else {
return [{
name: 'lvjixuan:eslint-oxlint',
rules: {},
}]
}

let denyRules: Partial<OXLintRules> = {}

if (options.deny === 'all') {
for (const c in rules)
denyRules = { ...denyRules, ...rules[c as Categories] }
}
else if (options.deny) {
denyRules = rules[options.deny]
}

return [{
name: 'lvjixuan:eslint-oxlint',
rules: denyRules,
}]
}
6 changes: 3 additions & 3 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"scripts": {
"test": "vitest --globals",
"build": "tsup src/index.ts --format=esm,cjs --dts",
"prepare": "pnpm build",
"prepublishOnly": "nr build"
"postinstall": "pnpm run build",
"prepublishOnly": "pnpm run build"
},
"peerDependencies": {
"eslint": ">=8"
Expand All @@ -39,4 +39,4 @@
"publishConfig": {
"registry": "https://registry.npmjs.org/"
}
}
}

0 comments on commit 921816e

Please sign in to comment.