Rust製のLinter「Oxlint」が速すぎる
Rust製のLinterである「Oxlint」が2023/12/12に利用可能となったので、ドキュメントをまとめてみました
特徴
- ESLintの50倍から100倍速く、CPUコア数に応じてスケールする
- Node.jsが不要で、コマンドだけで実行可能
- デフォルトで他のESLintプラグインのルールを統合
- .eslintignore をサポート
- ESLintコメントの無効化をサポート
インストール
oxlintはNode.jsを必須としていないので、ダウンロードせずコマンドだけでも利用可能です
インストールする時
npm i -D oxlint
コマンドの時
npx oxlint@latest
Lintルール
Oxlintはまだプラグインシステムを提供していませんが、TypeScript、React、Jest、Unicorn、JSX-a11y、Importなどの一般的なプラグインからのルールを積極的に統合しています
eslint
, typescript
, eslint-plugin-react
, eslint-plugin-jest
, eslint-plugin-unicorn
, eslint-plugin-jsx-a11y
から、217個のルール(執筆時点)が統合されています
統合されているルールは誤った、冗長な、または紛らわしいコード—を識別するもので、多くの人に受け入れられやすい構成だと思います
ルールの一覧は以下のコマンドで確認できます
npx oxlint@latest --rules
ルール一覧(執筆時点)
• deepscan: bad-array-method-on-arguments
• deepscan: bad-char-at-comparison
• deepscan: bad-comparison-sequence
• deepscan: bad-min-max-func
• deepscan: bad-replace-all-arg
• deepscan: missing-throw
• deepscan: number-arg-out-of-range
• deepscan: uninvoked-array-callback
• eslint: for-direction
• eslint: no-async-promise-executor
• eslint: no-caller
• eslint: no-class-assign
• eslint: no-compare-neg-zero
• eslint: no-cond-assign
• eslint: no-const-assign
• eslint: no-constant-binary-expression
• eslint: no-constant-condition
• eslint: no-control-regex
• eslint: no-debugger
• eslint: no-delete-var
• eslint: no-dupe-class-members
• eslint: no-dupe-else-if
• eslint: no-dupe-keys
• eslint: no-duplicate-case
• eslint: no-empty-character-class
• eslint: no-empty-pattern
• eslint: no-ex-assign
• eslint: no-extra-boolean-cast
• eslint: no-func-assign
• eslint: no-global-assign
• eslint: no-inner-declarations
• eslint: no-loss-of-precision
• eslint: no-new-symbol
• eslint: no-obj-calls
• eslint: no-self-assign
• eslint: no-setter-return
• eslint: no-shadow-restricted-names
• eslint: no-sparse-arrays
• eslint: no-unsafe-finally
• eslint: no-unsafe-negation
• eslint: no-unused-labels
• eslint: no-useless-catch
• eslint: no-useless-escape
• eslint: require-yield
• eslint: use-isnan
• eslint: valid-typeof
• typescript: no-extra-non-null-assertion
• typescript: no-misused-new
• typescript: no-namespace
• typescript: no-non-null-asserted-optional-chain
• typescript: no-this-alias
• typescript: no-unsafe-declaration-merging
• typescript: prefer-as-const
• jest: expect-expect
• jest: no-conditional-expect
• jest: no-disabled-tests
• jest: no-export
• jest: no-focused-tests
• jest: no-standalone-expect
• jest: valid-describe-callback
• jest: valid-expect
• jest: valid-title
• unicorn: no-document-cookie
• unicorn: no-empty-file
• unicorn: no-invalid-remove-event-listener
• unicorn: no-thenable
• unicorn: no-unnecessary-await
• unicorn: no-useless-fallback-in-spread
• unicorn: no-useless-length-check
• unicorn: no-useless-spread
• unicorn: prefer-set-size
• unicorn: prefer-string-starts-ends-with
• react: jsx-key
• react: jsx-no-duplicate-props
• react: jsx-no-useless-fragment
• react: no-children-prop
• react: no-find-dom-node
• react: no-render-return-value
• react: no-string-refs
• react: no-is-mounted
• jsx_a11y: alt-text
• jsx_a11y: anchor-has-content
• jsx_a11y: anchor-is-valid
• jsx_a11y: aria-props
• jsx_a11y: heading-has-content
• jsx_a11y: html-has-lang
• jsx_a11y: iframe-has-title
• jsx_a11y: img-redundant-alt
• jsx_a11y: no-access-key
• jsx_a11y: no-aria-hidden-on-focusable
• jsx_a11y: no-autofocus
• jsx_a11y: scope
• jsx_a11y: tab-index-no-positive
• jsx_a11y: no-distracting-elements
• oxc: const-comparisons
• oxc: double-comparisons
Restriction (15):
• deepscan: bad-bitwise-operator
• eslint: no-bitwise
• eslint: no-console
• eslint: no-empty
• eslint: no-eval
• eslint: no-regex-spaces
• eslint: no-unsafe-optional-chaining
• typescript: no-explicit-any
• typescript: no-var-requires
• unicorn: no-abusive-eslint-disable
• unicorn: no-array-reduce
• unicorn: no-array-for-each
• unicorn: no-nested-ternary
• unicorn: prefer-number-properties
• react: no-dangerously-set-inner-html
Pedantic (44):
• eslint: array-callback-return
• eslint: eqeqeq
• eslint: no-array-constructor
• eslint: no-case-declarations
• eslint: no-mixed-operators
• eslint: no-prototype-builtins
• eslint: no-return-await
• eslint: no-self-compare
• typescript: ban-types
• typescript: no-duplicate-enum-values
• unicorn: escape-case
• unicorn: explicit-length-check
• unicorn: new-for-builtins
• unicorn: no-hex-escape
• unicorn: no-instanceof-array
• unicorn: no-lonely-if
• unicorn: no-negated-condition
• unicorn: no-new-buffer
• unicorn: no-object-as-default-parameter
• unicorn: no-static-only-class
• unicorn: no-this-assignment
• unicorn: no-typeof-undefined
• unicorn: no-unreadable-iife
• unicorn: no-useless-promise-resolve-reject
• unicorn: no-useless-switch-case
• unicorn: prefer-array-flat
• unicorn: prefer-array-some
• unicorn: prefer-blob-reading-methods
• unicorn: prefer-code-point
• unicorn: prefer-date-now
• unicorn: prefer-dom-node-append
• unicorn: prefer-dom-node-dataset
• unicorn: prefer-dom-node-remove
• unicorn: prefer-event-target
• unicorn: prefer-math-trunc
• unicorn: prefer-native-coercion-functions
• unicorn: prefer-prototype-methods
• unicorn: prefer-query-selector
• unicorn: prefer-regexp-test
• unicorn: prefer-string-replace-all
• unicorn: prefer-string-slice
• unicorn: prefer-type-error
• unicorn: require-number-to-fixed-digits-argument
• react: no-unescaped-entities
Style (40):
• eslint: default-case-last
• typescript: adjacent-overload-signatures
• typescript: no-empty-interface
• jest: max-expects
• jest: no-alias-methods
• jest: no-confusing-set-timeout
• jest: no-deprecated-functions
• jest: no-done-callback
• jest: no-hooks
• jest: no-identical-title
• jest: no-interpolation-in-snapshots
• jest: no-jasmine-globals
• jest: no-mocks-import
• jest: no-test-prefixes
• jest: prefer-todo
• unicorn: catch-error-name
• unicorn: prefer-node-protocol
• unicorn: empty-brace-spaces
• unicorn: error-message
• unicorn: filename-case
• unicorn: no-await-expression-member
• unicorn: no-console-spaces
• unicorn: no-null
• unicorn: no-unreadable-array-destructuring
• unicorn: no-zero-fractions
• unicorn: number-literal-case
• unicorn: numeric-separators-style
• unicorn: prefer-array-flat-map
• unicorn: prefer-dom-node-text-content
• unicorn: prefer-includes
• unicorn: prefer-logical-operator-over-ternary
• unicorn: prefer-modern-dom-apis
• unicorn: prefer-optional-catch-binding
• unicorn: prefer-reflect-apply
• unicorn: prefer-spread
• unicorn: prefer-string-trim-start-end
• unicorn: require-array-join-separator
• unicorn: switch-case-braces
• unicorn: text-encoding-identifier-case
• unicorn: throw-new-error
Suspicious (7):
• eslint: no-empty-static-block
• typescript: no-unnecessary-type-constraint
• jest: no-commented-out-tests
• unicorn: no-new-array
• unicorn: prefer-add-event-listener
• react: jsx-no-comment-text-nodes
• react: react-in-jsx-scope
Nursery (14):
• eslint: constructor-super
• eslint: getter-return
• eslint: no-fallthrough
• eslint: no-import-assign
• eslint: no-redeclare
• eslint: no-undef
• typescript: ban-ts-comment
• typescript: consistent-type-exports
• import: default
• import: named
• import: no-cycle
• import: no-self-import
• import: no-amd
• import: export
Perf (1):
• oxc: no-accumulating-spread
Total: 217
実行
実行すると以下のような結果が表示されます
様々なESLintのpluginのルールを統合しているので、どのルールに引っかかったかも併せて表示されます
65ファイルが19msということで、かなり高速に実行されていることが分かります(高スペPCだと5msでした)
ESLintの時
Oxlintの時
ベンチマークもOxlintが用意しているみたいです
その他
vscode拡張機能追加
vscodeの拡張機能も追加します
他のメンバーにも拡張機能をレコメンドするために、.vscode/extensions.json
を作成します
{
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"streetsidesoftware.code-spell-checker",
"voorjaar.windicss-intellisense",
"oxc.oxc-vscode"
]
}
husky, lintstagedに組み込む
husky, lintstagedの導入は以下を参考にしてください
husky, lintstagedの導入と、pre-commit時にlintstagedが出来ている状態で以下を追加します
{
"scripts": {
"lint": "npx oxlint@latest",
"lint:fix": "npx oxlint@latest --fix",
}
...
}
{
"*.{ts,tsx,js,jsx}": "npm run lint:fix"
}
このようにコミット、プッシュ時にoxlintが発火して、エラーが出るとGitコマンドを失敗させることが出来ます
コミット
プッシュ
GitHub Actionsに組み込む
push時にOxlintのチェックをさせる、GitHub Actionsを追加します
name: lint
on: push
jobs:
lint:
name: Lint JS
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npx --yes oxlint@0.0.0 # change to the latest release
最後に
現在、ESLintのconfigファイルを読み取って実行できるよう進行中!!
とのことで、既存の .eslintrc
などの設定ファイルを読み込んで、Oxlintが使えるとなる未来が来そうですね!
huskyや、CIなどでLintするタイミングは何度もあるので、Lint時間が短縮されるのはとても助かると思います!
これからの開発に期待大ですね!
Discussion