Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ec55570
add valid-title to the list of VITEST_COMPATIBLE_JEST_RULES
taearls Jul 5, 2025
2aae366
add support for vitest-valid-title rule
taearls Jul 5, 2025
2da1ff1
remove file that was accidentally copied
taearls Jul 5, 2025
3c7c67a
update snapshots
taearls Jul 5, 2025
771f562
update snapshots, remove unused import
taearls Jul 5, 2025
ee5e2d2
update snapshot
taearls Jul 5, 2025
57c0415
follow lint warning
taearls Jul 5, 2025
cda10b7
update diagnostic messages to fix grammar
taearls Jul 5, 2025
17601b8
update snapshot
taearls Jul 5, 2025
dd9f230
update diagnostics for better formatting
taearls Jul 5, 2025
c9a49cc
update snapshot
taearls Jul 5, 2025
696e3d6
fix lint errors
taearls Oct 4, 2025
0441fd3
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 4, 2025
8dcfd05
re-add vue rules that were mistakenly removed during rebase
taearls Oct 4, 2025
8bd89ee
refactor(linter): merge vitest/valid-title into jest/valid-title
taearls Oct 4, 2025
8331d20
chore: update snapshots for improved valid-title error messages
taearls Oct 4, 2025
898d52a
chore: remove unused vitest valid-title snapshot
taearls Oct 4, 2025
50498c9
fix: restore original jest valid-title error messages
taearls Oct 4, 2025
e594a7c
chore: revert oxlint snapshots to original error messages
taearls Oct 4, 2025
82558ab
fix(linter): use inline format args in valid-title rule
taearls Oct 4, 2025
224fa66
fix(linter): register missing Vue rules in declare_all_lint_rules macro
taearls Oct 11, 2025
5ca9715
chore(linter): regenerate rule runner implementations
taearls Oct 11, 2025
55d8332
chore(linter): regenerate rule runner implementations after rebase
taearls Oct 14, 2025
30e2c16
fix(linter): update VITEST_COMPATIBLE_JEST_RULES array size and regen…
taearls Oct 20, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ working directory:
"implementsReplacesDocs": false,
"exemptDestructuredRootsFromChecks": false,
"tagNamePreference": {}
},
"vitest": {
"typecheck": false
}
},
"env": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ working directory: fixtures
"implementsReplacesDocs": false,
"exemptDestructuredRootsFromChecks": false,
"tagNamePreference": {}
},
"vitest": {
"typecheck": false
}
},
"env": {
Expand Down
6 changes: 5 additions & 1 deletion crates/oxc_linter/src/config/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ pub mod jsdoc;
mod jsx_a11y;
mod next;
mod react;
pub mod vitest;

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use self::{
jsdoc::JSDocPluginSettings, jsx_a11y::JSXA11yPluginSettings, next::NextPluginSettings,
react::ReactPluginSettings,
react::ReactPluginSettings, vitest::VitestPluginSettings,
};

/// # Oxlint Plugin Settings
Expand Down Expand Up @@ -52,6 +53,9 @@ pub struct OxlintSettings {

#[serde(default)]
pub jsdoc: JSDocPluginSettings,

#[serde(default)]
pub vitest: VitestPluginSettings,
}

#[cfg(test)]
Expand Down
16 changes: 16 additions & 0 deletions crates/oxc_linter/src/config/settings/vitest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

/// Configure Vitest plugin rules.
///
/// See [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest)'s
/// configuration for a full reference.
#[derive(Debug, Clone, Deserialize, Serialize, Default, JsonSchema)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct VitestPluginSettings {
/// Whether to enable typecheck mode for Vitest rules.
/// When enabled, some rules will skip certain checks for describe blocks
/// to accommodate TypeScript type checking scenarios.
#[serde(default)]
pub typecheck: bool,
}
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1258,12 +1258,12 @@ oxc_macros::declare_all_lint_rules! {
vitest::prefer_to_be_object,
vitest::prefer_to_be_truthy,
vitest::require_local_test_context_for_concurrent_snapshots,
vue::define_props_destructuring,
vue::define_emits_declaration,
vue::define_props_declaration,
vue::define_props_destructuring,
vue::max_props,
vue::no_import_compiler_macros,
vue::no_export_in_script_setup,
vue::no_import_compiler_macros,
vue::no_multiple_slot_args,
vue::no_required_prop_with_default,
vue::prefer_import_from_vue,
Expand Down
89 changes: 84 additions & 5 deletions crates/oxc_linter/src/rules/jest/valid_title.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct ValidTitle(Box<ValidTitleConfig>);
pub struct ValidTitleConfig {
ignore_type_of_test_name: bool,
ignore_type_of_describe_name: bool,
allow_arguments: bool,
disallowed_words: Vec<CompactStr>,
ignore_space: bool,
must_not_match_patterns: FxHashMap<MatchKind, CompiledMatcherAndMessage>,
Expand All @@ -45,7 +46,7 @@ impl std::ops::Deref for ValidTitle {
declare_oxc_lint!(
/// ### What it does
///
/// Checks that the titles of Jest blocks are valid.
/// Checks that the titles of Jest and Vitest blocks are valid.
///
/// Titles must be:
/// - not empty,
Expand Down Expand Up @@ -84,6 +85,7 @@ declare_oxc_lint!(
/// ignoreSpaces?: boolean;
/// ignoreTypeOfTestName?: boolean;
/// ignoreTypeOfDescribeName?: boolean;
/// allowArguments?: boolean;
/// disallowedWords?: string[];
/// mustNotMatch?: Partial<Record<'describe' | 'test' | 'it', string>> | string;
/// mustMatch?: Partial<Record<'describe' | 'test' | 'it', string>> | string;
Expand All @@ -108,6 +110,7 @@ impl Rule for ValidTitle {

let ignore_type_of_test_name = get_as_bool("ignoreTypeOfTestName");
let ignore_type_of_describe_name = get_as_bool("ignoreTypeOfDescribeName");
let allow_arguments = get_as_bool("allowArguments");
let ignore_space = get_as_bool("ignoreSpaces");
let disallowed_words = config
.and_then(|v| v.get("disallowedWords"))
Expand All @@ -125,6 +128,7 @@ impl Rule for ValidTitle {
Self(Box::new(ValidTitleConfig {
ignore_type_of_test_name,
ignore_type_of_describe_name,
allow_arguments,
disallowed_words,
ignore_space,
must_not_match_patterns,
Expand Down Expand Up @@ -159,10 +163,29 @@ impl ValidTitle {
return;
}

// Check if extend keyword has been used (vitest feature)
if let Some(member) = jest_fn_call.members.first()
&& member.is_name_equal("extend")
{
return;
}

let Some(arg) = call_expr.arguments.first() else {
return;
};

// Handle typecheck settings - skip for describe when enabled (vitest feature)
if ctx.settings().vitest.typecheck
&& matches!(jest_fn_call.kind, JestFnKind::General(JestGeneralFnKind::Describe))
{
return;
}

// Handle allowArguments option (vitest feature)
if self.allow_arguments && matches!(arg, Argument::Identifier(_)) {
return;
}

let need_report_name = match jest_fn_call.kind {
JestFnKind::General(JestGeneralFnKind::Test) => !self.ignore_type_of_test_name,
JestFnKind::General(JestGeneralFnKind::Describe) => !self.ignore_type_of_describe_name,
Expand Down Expand Up @@ -284,15 +307,39 @@ fn compile_matcher_patterns(
fn compile_matcher_pattern(pattern: MatcherPattern) -> Option<CompiledMatcherAndMessage> {
match pattern {
MatcherPattern::String(pattern) => {
let reg_str = format!("(?u){}", pattern.as_str()?);
let pattern_str = pattern.as_str()?;

// Check for JS regex literal: /pattern/flags
if let Some(stripped) = pattern_str.strip_prefix('/')
&& let Some(end) = stripped.rfind('/')
{
let (pat, _flags) = stripped.split_at(end);
// For now, ignore flags and just use the pattern
let regex = Regex::new(pat).ok()?;
return Some((regex, None));
}

// Fallback: treat as a normal Rust regex with Unicode support
let reg_str = format!("(?u){pattern_str}");
let reg = Regex::new(&reg_str).ok()?;
Some((reg, None))
}
MatcherPattern::Vec(pattern) => {
let reg_str = pattern.first().and_then(|v| v.as_str()).map(|v| format!("(?u){v}"))?;
let reg = Regex::new(&reg_str).ok()?;
let pattern_str = pattern.first().and_then(|v| v.as_str())?;

// Check for JS regex literal: /pattern/flags
let regex = if let Some(stripped) = pattern_str.strip_prefix('/')
&& let Some(end) = stripped.rfind('/')
{
let (pat, _flags) = stripped.split_at(end);
Regex::new(pat).ok()?
} else {
let reg_str = format!("(?u){pattern_str}");
Regex::new(&reg_str).ok()?
};

let message = pattern.get(1).and_then(serde_json::Value::as_str).map(CompactStr::from);
Some((reg, message))
Some((regex, message))
}
}
}
Expand All @@ -306,6 +353,7 @@ fn validate_title(
) {
if title.is_empty() {
Message::EmptyTitle.diagnostic(ctx, span);
return;
}

if !valid_title.disallowed_words.is_empty() {
Expand Down Expand Up @@ -585,6 +633,35 @@ fn test() {
None,
),
("it(abc, function () {})", Some(serde_json::json!([{ "ignoreTypeOfTestName": true }]))),
// Vitest-specific tests with allowArguments option
("it(foo, () => {});", Some(serde_json::json!([{ "allowArguments": true }]))),
("describe(bar, () => {});", Some(serde_json::json!([{ "allowArguments": true }]))),
("test(baz, () => {});", Some(serde_json::json!([{ "allowArguments": true }]))),
// Vitest-specific tests with .extend()
(
"export const myTest = test.extend({
archive: []
})",
None,
),
("const localTest = test.extend({})", None),
(
"import { it } from 'vitest'

const test = it.extend({
fixture: [
async ({}, use) => {
setup()
await use()
teardown()
},
{ auto: true }
],
})

test('', () => {})",
None,
),
];

let fail = vec![
Expand Down Expand Up @@ -927,6 +1004,8 @@ fn test() {
None,
),
("it(abc, function () {})", None),
// Vitest-specific fail test with allowArguments: false
("test(bar, () => {});", Some(serde_json::json!([{ "allowArguments": false }]))),
];

let fix = vec![
Expand Down
7 changes: 7 additions & 0 deletions crates/oxc_linter/src/snapshots/jest_valid_title.snap
Original file line number Diff line number Diff line change
Expand Up @@ -574,3 +574,10 @@ source: crates/oxc_linter/src/tester.rs
· ───
╰────
help: "Replace your title with a string"

⚠ eslint-plugin-jest(valid-title): "Title must be a string"
╭─[valid_title.tsx:1:6]
1 │ test(bar, () => {});
· ───
╰────
help: "Replace your title with a string"
24 changes: 24 additions & 0 deletions crates/oxc_linter/src/snapshots/schema_json.snap
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ expression: json
"implementsReplacesDocs": false,
"exemptDestructuredRootsFromChecks": false,
"tagNamePreference": {}
},
"vitest": {
"typecheck": false
}
},
"allOf": [
Expand Down Expand Up @@ -538,6 +541,16 @@ expression: json
"$ref": "#/definitions/ReactPluginSettings"
}
]
},
"vitest": {
"default": {
"typecheck": false
},
"allOf": [
{
"$ref": "#/definitions/VitestPluginSettings"
}
]
}
}
},
Expand Down Expand Up @@ -598,6 +611,17 @@ expression: json
"type": "boolean"
}
]
},
"VitestPluginSettings": {
"description": "Configure Vitest plugin rules.\n\nSee [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest)'s\nconfiguration for a full reference.",
"type": "object",
"properties": {
"typecheck": {
"description": "Whether to enable typecheck mode for Vitest rules.\nWhen enabled, some rules will skip certain checks for describe blocks\nto accommodate TypeScript type checking scenarios.",
"default": false,
"type": "boolean"
}
}
}
}
}
24 changes: 24 additions & 0 deletions npm/oxlint/configuration_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@
"implementsReplacesDocs": false,
"exemptDestructuredRootsFromChecks": false,
"tagNamePreference": {}
},
"vitest": {
"typecheck": false
}
},
"allOf": [
Expand Down Expand Up @@ -534,6 +537,16 @@
"$ref": "#/definitions/ReactPluginSettings"
}
]
},
"vitest": {
"default": {
"typecheck": false
},
"allOf": [
{
"$ref": "#/definitions/VitestPluginSettings"
}
]
}
}
},
Expand Down Expand Up @@ -594,6 +607,17 @@
"type": "boolean"
}
]
},
"VitestPluginSettings": {
"description": "Configure Vitest plugin rules.\n\nSee [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest)'s\nconfiguration for a full reference.",
"type": "object",
"properties": {
"typecheck": {
"description": "Whether to enable typecheck mode for Vitest rules.\nWhen enabled, some rules will skip certain checks for describe blocks\nto accommodate TypeScript type checking scenarios.",
"default": false,
"type": "boolean"
}
}
}
}
}
26 changes: 26 additions & 0 deletions tasks/website/src/linter/snapshots/schema_markdown.snap
Original file line number Diff line number Diff line change
Expand Up @@ -540,3 +540,29 @@ Example:


##### settings.react.linkComponents[n]






### settings.vitest

type: `object`


Configure Vitest plugin rules.

See [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest)'s
configuration for a full reference.


#### settings.vitest.typecheck

type: `boolean`

default: `false`

Whether to enable typecheck mode for Vitest rules.
When enabled, some rules will skip certain checks for describe blocks
to accommodate TypeScript type checking scenarios.
Loading