From a1c680c4e6c07f58101840ff285f27456778b768 Mon Sep 17 00:00:00 2001 From: benmelz Date: Fri, 29 May 2026 09:53:03 -0400 Subject: [PATCH 1/3] add oxlint precommit hook --- README.md | 1 + config/default.yml | 7 ++ lib/overcommit/hook/pre_commit/ox_lint.rb | 35 ++++++++ .../hook/pre_commit/ox_lint_spec.rb | 89 +++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 lib/overcommit/hook/pre_commit/ox_lint.rb create mode 100644 spec/overcommit/hook/pre_commit/ox_lint_spec.rb diff --git a/README.md b/README.md index a0d46726..1e003544 100644 --- a/README.md +++ b/README.md @@ -539,6 +539,7 @@ issue](https://github.com/sds/overcommit/issues/238) for more details. * [Mdl](lib/overcommit/hook/pre_commit/mdl.rb) * [`*`MergeConflicts](lib/overcommit/hook/pre_commit/merge_conflicts.rb) * [NginxTest](lib/overcommit/hook/pre_commit/nginx_test.rb) +* [OxLint](lib/overcommit/hook/pre_commit/ox_lint.rb) * [PhpCs](lib/overcommit/hook/pre_commit/php_cs.rb) * [PhpCsFixer](lib/overcommit/hook/pre_commit/php_cs_fixer.rb) * [PhpLint](lib/overcommit/hook/pre_commit/php_lint.rb) diff --git a/config/default.yml b/config/default.yml index c13055fd..880492ea 100644 --- a/config/default.yml +++ b/config/default.yml @@ -565,6 +565,13 @@ PreCommit: flags: ['-t'] include: '**/nginx.conf' + OxLint: + enabled: false + description: 'Analyze with oxlint' + required_executable: 'oxlint' + flags: ['--format=unix'] + install_command: 'npm install -g oxlint' + Pep257: # Deprecated – use Pydocstyle instead. enabled: false description: 'Analyze docstrings with pep257' diff --git a/lib/overcommit/hook/pre_commit/ox_lint.rb b/lib/overcommit/hook/pre_commit/ox_lint.rb new file mode 100644 index 00000000..73848487 --- /dev/null +++ b/lib/overcommit/hook/pre_commit/ox_lint.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Overcommit::Hook::PreCommit + # Runs `oxlint` against any modified JavaScript files. + # + # Protip: if you have an npm script set up to run oxlint, you can configure + # this hook to run oxlint via your npm script by using the `command` option in + # your .overcommit.yml file. This can be useful if you have some oxlint + # configuration built into your npm script that you don't want to repeat + # somewhere else. Example: + # + # OxLint: + # required_executable: 'npm' + # enabled: true + # command: ['npm', 'run', 'lint', '--', '--format=unix'] + # + # Note: This hook supports only unix format. + # + # @see https://oxc.rs + class OxLint < Base + def run + oxlint_regex = /^(?:file:\/\/)?(?[^:]+):(?\d+):\d+:.*?(?Error|Warning)/ + result = execute(command, args: applicable_files) + output = result.stdout.chomp + messages = output.split("\n").grep(oxlint_regex) + + return [:fail, result.stderr] if messages.empty? && !result.success? + return :pass if result.success? && output.empty? + + # example message: + # file://test.js:5:1: `debugger` statement is not allowed [Error/eslint(no-debugger)] + extract_messages(messages, oxlint_regex, lambda { |type| type.downcase.to_sym }) + end + end +end diff --git a/spec/overcommit/hook/pre_commit/ox_lint_spec.rb b/spec/overcommit/hook/pre_commit/ox_lint_spec.rb new file mode 100644 index 00000000..4a03225e --- /dev/null +++ b/spec/overcommit/hook/pre_commit/ox_lint_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Overcommit::Hook::PreCommit::OxLint do + let(:config) { Overcommit::ConfigurationLoader.default_configuration } + let(:context) { double('context') } + subject { described_class.new(config, context) } + + before do + subject.stub(:applicable_files).and_return(%w[file1.js file2.js]) + end + + context 'when oxlint is unable to run' do + let(:result) { double('result') } + + before do + result.stub(:stderr).and_return('SyntaxError: Use of const in strict mode.') + result.stub(:stdout).and_return('') + + result.stub(:success?).and_return(false) + subject.stub(:execute).and_return(result) + end + + it { should fail_hook } + end + + context 'when oxlint exits successfully' do + let(:result) { double('result') } + + before do + result.stub(:success?).and_return(true) + subject.stub(:execute).and_return(result) + end + + context 'with no output' do + before do + result.stub(:stdout).and_return('') + end + + it { should pass } + end + + context 'and it reports a warning' do + before do + result.stub(:stdout).and_return([ + 'file://test.js:5:1: `debugger` statement is not allowed [Warning/eslint(no-debugger)]', + '', + '1 problem' + ].join("\n")) + end + + it { should warn } + end + + context 'and it doesnt count false positives error messages' do + before do + result.stub(:stdout).and_return([ + '$ yarn oxlint --quiet --format=unix /app/project/Error.ts', + '$ /app/project/node_modules/.bin/oxlint --quiet --format=compact /app/project/Error.ts', + '', + ].join("\n")) + end + + it { should pass } + end + end + + context 'when oxlint exits unsucessfully' do + let(:result) { double('result') } + + before do + result.stub(:success?).and_return(false) + subject.stub(:execute).and_return(result) + end + + context 'and it reports an error' do + before do + result.stub(:stdout).and_return([ + 'file://test.js:5:1: `debugger` statement is not allowed [Error/eslint(no-debugger)]', + '', + '1 problem' + ].join("\n")) + end + + it { should fail_hook } + end + end +end From 89993ec62b3b76a88d4e95fa715ab0f8109e7192 Mon Sep 17 00:00:00 2001 From: benmelz Date: Fri, 29 May 2026 10:10:07 -0400 Subject: [PATCH 2/3] add oxfmt precommit hook --- README.md | 1 + config/default.yml | 7 ++ lib/overcommit/hook/pre_commit/ox_fmt.rb | 35 +++++++ .../overcommit/hook/pre_commit/ox_fmt_spec.rb | 95 +++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 lib/overcommit/hook/pre_commit/ox_fmt.rb create mode 100644 spec/overcommit/hook/pre_commit/ox_fmt_spec.rb diff --git a/README.md b/README.md index 1e003544..b57d6237 100644 --- a/README.md +++ b/README.md @@ -539,6 +539,7 @@ issue](https://github.com/sds/overcommit/issues/238) for more details. * [Mdl](lib/overcommit/hook/pre_commit/mdl.rb) * [`*`MergeConflicts](lib/overcommit/hook/pre_commit/merge_conflicts.rb) * [NginxTest](lib/overcommit/hook/pre_commit/nginx_test.rb) +* [OxFmt](lib/overcommit/hook/pre_commit/ox_fmt.rb) * [OxLint](lib/overcommit/hook/pre_commit/ox_lint.rb) * [PhpCs](lib/overcommit/hook/pre_commit/php_cs.rb) * [PhpCsFixer](lib/overcommit/hook/pre_commit/php_cs_fixer.rb) diff --git a/config/default.yml b/config/default.yml index 880492ea..21ab267f 100644 --- a/config/default.yml +++ b/config/default.yml @@ -565,6 +565,13 @@ PreCommit: flags: ['-t'] include: '**/nginx.conf' + OxFmt: + enabled: false + description: 'Analyze with oxfmt' + required_executable: 'oxfmt' + flags: ['--check'] + install_command: 'npm install -g oxfmt' + OxLint: enabled: false description: 'Analyze with oxlint' diff --git a/lib/overcommit/hook/pre_commit/ox_fmt.rb b/lib/overcommit/hook/pre_commit/ox_fmt.rb new file mode 100644 index 00000000..d3772376 --- /dev/null +++ b/lib/overcommit/hook/pre_commit/ox_fmt.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Overcommit::Hook::PreCommit + # Runs `oxfmt` against any modified files. + # + # Protip: if you have an npm script set up to run oxfmt, you can configure + # this hook to run oxfmt via your npm script by using the `command` option in + # your .overcommit.yml file. This can be useful if you have some oxfmt + # configuration built into your npm script that you don't want to repeat + # somewhere else. Example: + # + # oxfmt: + # required_executable: 'npm' + # enabled: true + # command: ['npm', 'run', 'fmt', '--', '--check'] + # + # Note: This hook only supports check mode. + # + # @see https://oxc.rs + class OxFmt < Base + def run + oxfmt_regex = /^(?.+) \(\d+ms\)/ + result = execute(command, args: applicable_files) + output = result.stdout.chomp + messages = output.split("\n").grep(oxfmt_regex) + + return [:fail, result.stderr] if messages.empty? && !result.success? + return :pass if result.success? && output.empty? + + # example message: + # test.js (5ms) + extract_messages(messages, oxfmt_regex) + end + end +end diff --git a/spec/overcommit/hook/pre_commit/ox_fmt_spec.rb b/spec/overcommit/hook/pre_commit/ox_fmt_spec.rb new file mode 100644 index 00000000..75c61876 --- /dev/null +++ b/spec/overcommit/hook/pre_commit/ox_fmt_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Overcommit::Hook::PreCommit::OxFmt do + let(:config) { Overcommit::ConfigurationLoader.default_configuration } + let(:context) { double('context') } + subject { described_class.new(config, context) } + + before do + subject.stub(:applicable_files).and_return(%w[file1.js file2.js]) + end + + context 'when oxfmt is unable to run' do + let(:result) { double('result') } + + before do + result.stub(:stderr).and_return('SyntaxError: Use of const in strict mode.') + result.stub(:stdout).and_return('') + + result.stub(:success?).and_return(false) + subject.stub(:execute).and_return(result) + end + + it { should fail_hook } + end + + context 'when oxfmt exits successfully' do + let(:result) { double('result') } + + before do + result.stub(:success?).and_return(true) + subject.stub(:execute).and_return(result) + end + + context 'with no output' do + before do + result.stub(:stdout).and_return('') + end + + it { should pass } + end + + context 'and it reports an error' do + before do + result.stub(:stdout).and_return([ + 'Checking formatting...', + '', + 'README.md (66ms)', + '', + 'Format issues found in above 1 files. Run without `--check` to fix.', + 'Finished in 66ms on 1 files using 8 threads.' + ].join("\n")) + end + + it { should fail_hook } + end + + context 'and it doesnt count false positives error messages' do + before do + result.stub(:stdout).and_return([ + '$ yarn oxfmt --check /app/project/Error.ts', + '$ /app/project/node_modules/.bin/oxfmt --check /app/project/Error.ts', + '', + ].join("\n")) + end + + it { should pass } + end + end + + context 'when oxfmt exits unsucessfully' do + let(:result) { double('result') } + + before do + result.stub(:success?).and_return(false) + subject.stub(:execute).and_return(result) + end + + context 'and it reports an error' do + before do + result.stub(:stdout).and_return([ + 'Checking formatting...', + '', + 'README.md (66ms)', + '', + 'Format issues found in above 1 files. Run without `--check` to fix.', + 'Finished in 66ms on 1 files using 8 threads.' + ].join("\n")) + end + + it { should fail_hook } + end + end +end From 2ee36103ff959ecf005f85cb55bf309feb395c6e Mon Sep 17 00:00:00 2001 From: benmelz Date: Fri, 29 May 2026 10:15:56 -0400 Subject: [PATCH 3/3] fix linting offense --- lib/overcommit/hook/pre_commit/ox_lint.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/overcommit/hook/pre_commit/ox_lint.rb b/lib/overcommit/hook/pre_commit/ox_lint.rb index 73848487..93f552f6 100644 --- a/lib/overcommit/hook/pre_commit/ox_lint.rb +++ b/lib/overcommit/hook/pre_commit/ox_lint.rb @@ -19,7 +19,7 @@ module Overcommit::Hook::PreCommit # @see https://oxc.rs class OxLint < Base def run - oxlint_regex = /^(?:file:\/\/)?(?[^:]+):(?\d+):\d+:.*?(?Error|Warning)/ + oxlint_regex = %r{^(?:file://)?(?[^:]+):(?\d+):\d+:.*?(?Error|Warning)} result = execute(command, args: applicable_files) output = result.stdout.chomp messages = output.split("\n").grep(oxlint_regex)