Skip to content

Commit 032af77

Browse files
authored
Add oxc pre-commit hooks (#879)
Specifically the linter/formatter of [the oxc project](https://oxc.rs). Both impl/tests were copied and tweaked from the eslint hook since they're so similar. Since side-effects are not supported, the formatter hook just does a "check" to indicate the dev forgot to run it (better than nothing).
1 parent 92b22f0 commit 032af77

6 files changed

Lines changed: 270 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,8 @@ issue](https://github.com/sds/overcommit/issues/238) for more details.
539539
* [Mdl](lib/overcommit/hook/pre_commit/mdl.rb)
540540
* [`*`MergeConflicts](lib/overcommit/hook/pre_commit/merge_conflicts.rb)
541541
* [NginxTest](lib/overcommit/hook/pre_commit/nginx_test.rb)
542+
* [OxFmt](lib/overcommit/hook/pre_commit/ox_fmt.rb)
543+
* [OxLint](lib/overcommit/hook/pre_commit/ox_lint.rb)
542544
* [PhpCs](lib/overcommit/hook/pre_commit/php_cs.rb)
543545
* [PhpCsFixer](lib/overcommit/hook/pre_commit/php_cs_fixer.rb)
544546
* [PhpLint](lib/overcommit/hook/pre_commit/php_lint.rb)

config/default.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,20 @@ PreCommit:
565565
flags: ['-t']
566566
include: '**/nginx.conf'
567567

568+
OxFmt:
569+
enabled: false
570+
description: 'Analyze with oxfmt'
571+
required_executable: 'oxfmt'
572+
flags: ['--check']
573+
install_command: 'npm install -g oxfmt'
574+
575+
OxLint:
576+
enabled: false
577+
description: 'Analyze with oxlint'
578+
required_executable: 'oxlint'
579+
flags: ['--format=unix']
580+
install_command: 'npm install -g oxlint'
581+
568582
Pep257: # Deprecated – use Pydocstyle instead.
569583
enabled: false
570584
description: 'Analyze docstrings with pep257'
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# frozen_string_literal: true
2+
3+
module Overcommit::Hook::PreCommit
4+
# Runs `oxfmt` against any modified files.
5+
#
6+
# Protip: if you have an npm script set up to run oxfmt, you can configure
7+
# this hook to run oxfmt via your npm script by using the `command` option in
8+
# your .overcommit.yml file. This can be useful if you have some oxfmt
9+
# configuration built into your npm script that you don't want to repeat
10+
# somewhere else. Example:
11+
#
12+
# oxfmt:
13+
# required_executable: 'npm'
14+
# enabled: true
15+
# command: ['npm', 'run', 'fmt', '--', '--check']
16+
#
17+
# Note: This hook only supports check mode.
18+
#
19+
# @see https://oxc.rs
20+
class OxFmt < Base
21+
def run
22+
oxfmt_regex = /^(?<file>.+) \(\d+ms\)/
23+
result = execute(command, args: applicable_files)
24+
output = result.stdout.chomp
25+
messages = output.split("\n").grep(oxfmt_regex)
26+
27+
return [:fail, result.stderr] if messages.empty? && !result.success?
28+
return :pass if result.success? && output.empty?
29+
30+
# example message:
31+
# test.js (5ms)
32+
extract_messages(messages, oxfmt_regex)
33+
end
34+
end
35+
end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# frozen_string_literal: true
2+
3+
module Overcommit::Hook::PreCommit
4+
# Runs `oxlint` against any modified JavaScript files.
5+
#
6+
# Protip: if you have an npm script set up to run oxlint, you can configure
7+
# this hook to run oxlint via your npm script by using the `command` option in
8+
# your .overcommit.yml file. This can be useful if you have some oxlint
9+
# configuration built into your npm script that you don't want to repeat
10+
# somewhere else. Example:
11+
#
12+
# OxLint:
13+
# required_executable: 'npm'
14+
# enabled: true
15+
# command: ['npm', 'run', 'lint', '--', '--format=unix']
16+
#
17+
# Note: This hook supports only unix format.
18+
#
19+
# @see https://oxc.rs
20+
class OxLint < Base
21+
def run
22+
oxlint_regex = %r{^(?:file://)?(?<file>[^:]+):(?<line>\d+):\d+:.*?(?<type>Error|Warning)}
23+
result = execute(command, args: applicable_files)
24+
output = result.stdout.chomp
25+
messages = output.split("\n").grep(oxlint_regex)
26+
27+
return [:fail, result.stderr] if messages.empty? && !result.success?
28+
return :pass if result.success? && output.empty?
29+
30+
# example message:
31+
# file://test.js:5:1: `debugger` statement is not allowed [Error/eslint(no-debugger)]
32+
extract_messages(messages, oxlint_regex, lambda { |type| type.downcase.to_sym })
33+
end
34+
end
35+
end
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe Overcommit::Hook::PreCommit::OxFmt do
6+
let(:config) { Overcommit::ConfigurationLoader.default_configuration }
7+
let(:context) { double('context') }
8+
subject { described_class.new(config, context) }
9+
10+
before do
11+
subject.stub(:applicable_files).and_return(%w[file1.js file2.js])
12+
end
13+
14+
context 'when oxfmt is unable to run' do
15+
let(:result) { double('result') }
16+
17+
before do
18+
result.stub(:stderr).and_return('SyntaxError: Use of const in strict mode.')
19+
result.stub(:stdout).and_return('')
20+
21+
result.stub(:success?).and_return(false)
22+
subject.stub(:execute).and_return(result)
23+
end
24+
25+
it { should fail_hook }
26+
end
27+
28+
context 'when oxfmt exits successfully' do
29+
let(:result) { double('result') }
30+
31+
before do
32+
result.stub(:success?).and_return(true)
33+
subject.stub(:execute).and_return(result)
34+
end
35+
36+
context 'with no output' do
37+
before do
38+
result.stub(:stdout).and_return('')
39+
end
40+
41+
it { should pass }
42+
end
43+
44+
context 'and it reports an error' do
45+
before do
46+
result.stub(:stdout).and_return([
47+
'Checking formatting...',
48+
'',
49+
'README.md (66ms)',
50+
'',
51+
'Format issues found in above 1 files. Run without `--check` to fix.',
52+
'Finished in 66ms on 1 files using 8 threads.'
53+
].join("\n"))
54+
end
55+
56+
it { should fail_hook }
57+
end
58+
59+
context 'and it doesnt count false positives error messages' do
60+
before do
61+
result.stub(:stdout).and_return([
62+
'$ yarn oxfmt --check /app/project/Error.ts',
63+
'$ /app/project/node_modules/.bin/oxfmt --check /app/project/Error.ts',
64+
'',
65+
].join("\n"))
66+
end
67+
68+
it { should pass }
69+
end
70+
end
71+
72+
context 'when oxfmt exits unsucessfully' do
73+
let(:result) { double('result') }
74+
75+
before do
76+
result.stub(:success?).and_return(false)
77+
subject.stub(:execute).and_return(result)
78+
end
79+
80+
context 'and it reports an error' do
81+
before do
82+
result.stub(:stdout).and_return([
83+
'Checking formatting...',
84+
'',
85+
'README.md (66ms)',
86+
'',
87+
'Format issues found in above 1 files. Run without `--check` to fix.',
88+
'Finished in 66ms on 1 files using 8 threads.'
89+
].join("\n"))
90+
end
91+
92+
it { should fail_hook }
93+
end
94+
end
95+
end
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe Overcommit::Hook::PreCommit::OxLint do
6+
let(:config) { Overcommit::ConfigurationLoader.default_configuration }
7+
let(:context) { double('context') }
8+
subject { described_class.new(config, context) }
9+
10+
before do
11+
subject.stub(:applicable_files).and_return(%w[file1.js file2.js])
12+
end
13+
14+
context 'when oxlint is unable to run' do
15+
let(:result) { double('result') }
16+
17+
before do
18+
result.stub(:stderr).and_return('SyntaxError: Use of const in strict mode.')
19+
result.stub(:stdout).and_return('')
20+
21+
result.stub(:success?).and_return(false)
22+
subject.stub(:execute).and_return(result)
23+
end
24+
25+
it { should fail_hook }
26+
end
27+
28+
context 'when oxlint exits successfully' do
29+
let(:result) { double('result') }
30+
31+
before do
32+
result.stub(:success?).and_return(true)
33+
subject.stub(:execute).and_return(result)
34+
end
35+
36+
context 'with no output' do
37+
before do
38+
result.stub(:stdout).and_return('')
39+
end
40+
41+
it { should pass }
42+
end
43+
44+
context 'and it reports a warning' do
45+
before do
46+
result.stub(:stdout).and_return([
47+
'file://test.js:5:1: `debugger` statement is not allowed [Warning/eslint(no-debugger)]',
48+
'',
49+
'1 problem'
50+
].join("\n"))
51+
end
52+
53+
it { should warn }
54+
end
55+
56+
context 'and it doesnt count false positives error messages' do
57+
before do
58+
result.stub(:stdout).and_return([
59+
'$ yarn oxlint --quiet --format=unix /app/project/Error.ts',
60+
'$ /app/project/node_modules/.bin/oxlint --quiet --format=compact /app/project/Error.ts',
61+
'',
62+
].join("\n"))
63+
end
64+
65+
it { should pass }
66+
end
67+
end
68+
69+
context 'when oxlint exits unsucessfully' do
70+
let(:result) { double('result') }
71+
72+
before do
73+
result.stub(:success?).and_return(false)
74+
subject.stub(:execute).and_return(result)
75+
end
76+
77+
context 'and it reports an error' do
78+
before do
79+
result.stub(:stdout).and_return([
80+
'file://test.js:5:1: `debugger` statement is not allowed [Error/eslint(no-debugger)]',
81+
'',
82+
'1 problem'
83+
].join("\n"))
84+
end
85+
86+
it { should fail_hook }
87+
end
88+
end
89+
end

0 commit comments

Comments
 (0)