Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions apps/oxlint/fixtures/disable_vitest_rules/.oxlintrc-vitest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"plugins": ["vitest"],
"categories": {
"correctness": "off"
},
"rules": {
"vitest/no-restricted-vi-methods": [
"error",
{
"advanceTimersByTime": null,
"spyOn": "Don't use spies"
}
]
}
}
24 changes: 24 additions & 0 deletions apps/oxlint/fixtures/disable_vitest_rules/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// File should have a total of 1 error from the vitest rule, and 1 warning about an unnecessary disable.
import { vi } from 'vitest';

vi.useFakeTimers();

it('calls the callback after 1 second via advanceTimersByTime', () => {
vi.advanceTimersByTime(1000);
})

test('plays video', () => {
// oxlint-disable vitest/no-restricted-vi-methods
vi.spyOn(audio, 'play'); // this is disabled by the block above, should be no error.
// oxlint-enable vitest/no-restricted-vi-methods

// oxlint-disable-next-line vitest/no-restricted-vi-methods
const spy = vi.spyOn(video, 'play'); // this is disabled by the line above, should be no error.

// This one should trigger a warning about an unnecessary disable:
// oxlint-disable-next-line vitest/no-restricted-vi-methods
video.play();

// Next line should not have an error, as we disable the rule.
vi.spyOn(audio, 'play'); // oxlint-disable-line vitest/no-restricted-vi-methods
})
10 changes: 10 additions & 0 deletions apps/oxlint/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,16 @@ mod test {
.test_and_snapshot_multiple(&[args_1, args_2]);
}

#[test]
// Test to ensure that a vitest rule based on the jest rule is
// handled correctly when it has a different name.
// e.g. `vitest/no-restricted-vi-methods` vs `jest/no-restricted-jest-methods`
fn test_disable_vitest_rules() {
let args =
&["-c", ".oxlintrc-vitest.json", "--report-unused-disable-directives", "test.js"];
Tester::new().with_cwd("fixtures/disable_vitest_rules".into()).test_and_snapshot(args);
}

#[test]
fn test_two_rules_with_same_rule_name_from_different_plugins() {
// Issue: <https://github.com/oxc-project/oxc/issues/8485>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
source: apps/oxlint/src/tester.rs
---
##########
arguments: -c .oxlintrc-vitest.json --report-unused-disable-directives test.js
working directory: fixtures/disable_vitest_rules
----------

x ]8;;https://oxc.rs/docs/guide/usage/linter/rules/jest/no-restricted-jest-methods.html\eslint-plugin-jest(no-restricted-jest-methods)]8;;\: Use of `advanceTimersByTime` is not allowed
,-[test.js:7:6]
6 | it('calls the callback after 1 second via advanceTimersByTime', () => {
7 | vi.advanceTimersByTime(1000);
: ^^^^^^^^^^^^^^^^^^^
8 | })
`----

! Unused eslint-disable directive (no problems were reported).
,-[test.js:19:5]
18 | // This one should trigger a warning about an unnecessary disable:
19 | // oxlint-disable-next-line vitest/no-restricted-vi-methods
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
20 | video.play();
`----

Found 1 warning and 1 error.
Finished in <variable>ms on 1 file with 1 rules using 1 threads.
----------
CLI result: LintFoundErrors
----------
5 changes: 5 additions & 0 deletions crates/oxc_linter/src/config/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ fn transform_rule_and_plugin_name<'a>(
rule_name: &'a str,
plugin_name: &'a str,
) -> (&'a str, &'a str) {
// Special case: vitest/no-restricted-vi-methods is implemented by jest/no-restricted-jest-methods
if plugin_name == "vitest" && rule_name == "no-restricted-vi-methods" {
return ("no-restricted-jest-methods", "jest");
}

let plugin_name = match plugin_name {
"vitest" if is_jest_rule_adapted_to_vitest(rule_name) => "jest",
"unicorn" if rule_name == "no-negated-condition" => "eslint",
Expand Down
21 changes: 16 additions & 5 deletions crates/oxc_linter/src/disable_directives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,21 @@ impl DisableDirectives {
// Check if this rule should be disabled
let rule_matches = match &interval.val {
DisabledRule::All { .. } => true,
// Our rule name currently does not contain the prefix.
// For example, this will match `@typescript-eslint/no-var-requires` given
// our rule_name is `no-var-requires`.
DisabledRule::Single { rule_name: name, .. } => name.contains(rule_name),
// `rule_name` does not contain the prefix.
// - `vitest/foobar` will be just `foobar`.
// - `@typescript-eslint/no-var-requires` will be just `no-var-requires`
//
// This enables matching rules across different plugins that share the same
// rule name, such as jest<->vitest rules and eslint<->typescript rules.
DisabledRule::Single { rule_name: name, .. } => {
if name.contains(rule_name) {
return true;
}

// Special-case mapping: `vitest/no-restricted-vi-methods` is implemented by `jest/no-restricted-jest-methods`.
return name == "vitest/no-restricted-vi-methods"
&& rule_name == "no-restricted-jest-methods";
}
};

if !rule_matches {
Expand Down Expand Up @@ -360,7 +371,7 @@ impl DisableDirectivesBuilder {
if let Some(text) =
text.strip_prefix("eslint-disable").or_else(|| text.strip_prefix("oxlint-disable"))
{
rule_name_start += 14; // eslint-disable is 14 bytes
rule_name_start += 14; // eslint-disable and oxlint-disable are each 14 bytes
// `eslint-disable`
if text.trim().is_empty() {
if self.disable_all_start.is_none() {
Expand Down
10 changes: 7 additions & 3 deletions crates/oxc_linter/src/rules/jest/no_restricted_jest_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ pub struct NoRestrictedJestMethods(Box<NoRestrictedJestMethodsConfig>);
#[derive(Debug, Default, Clone, JsonSchema, Deserialize)]
#[serde(rename_all = "camelCase", default)]
pub struct NoRestrictedJestMethodsConfig {
/// A mapping of restricted Jest method names to custom messages.
/// A mapping of restricted Jest method names to custom messages - or
/// `null`, for a generic message.
restricted_jest_methods: FxHashMap<String, String>,
}

Expand All @@ -45,10 +46,13 @@ declare_oxc_lint!(
///
/// ### Why is this bad?
///
/// Certain Jest methods may be deprecated, discouraged in specific
/// Certain Jest or Vitest methods may be deprecated, discouraged in specific
/// contexts, or incompatible with your testing environment. Restricting
/// them helps maintain consistent and reliable test practices.
///
/// By default, no methods are restricted by this rule.
/// You must configure the rule for it to disable anything.
///
/// ### Examples
///
/// Examples of **incorrect** code for this rule:
Expand All @@ -75,7 +79,7 @@ declare_oxc_lint!(
/// ```json
/// {
/// "rules": {
/// "vitest/no-restricted-vi-methods": "error"
/// "vitest/no-restricted-vi-methods": ["error", { "badFunction": "Don't use `badFunction`, it is bad." }]
/// }
/// }
/// ```
Expand Down
Loading