Skip to content

Commit 9470bb2

Browse files
committed
feat(linter): add schema for eslint/no-unused-vars (#23073)
Added the two files into the typo ignore list, because it would flag the docs for a regex pattern: ``` /* no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]*/ ``` See https://github.com/oxc-project/oxc/actions/runs/27093419108/job/79961070879
1 parent cdc0ac4 commit 9470bb2

8 files changed

Lines changed: 886 additions & 14 deletions

File tree

‎.typos.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ extend-exclude = [
77
"**/*.snap",
88
"**/*/CHANGELOG.md",
99
"apps/oxlint/conformance/snapshot.md",
10+
"apps/oxlint/src-js/package/config.generated.ts",
1011
"apps/oxfmt/test/**/fixtures/**",
1112
"crates/oxc_linter/fixtures",
1213
"crates/oxc_linter/src/rules/eslint/no_undefined.rs",
@@ -33,6 +34,7 @@ extend-exclude = [
3334
"tasks/transform_conformance/overrides",
3435
"tasks/transform_conformance/snapshots",
3536
"napi/minify/test/terser.test.ts",
37+
"npm/oxlint/configuration_schema.json",
3638
]
3739

3840
[default]

‎apps/oxlint/src-js/package/config.generated.ts‎

Lines changed: 265 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ export type NoMagicNumbersNumber = number | string;
120120
* Controls how hoisting is handled when checking for shadowing.
121121
*/
122122
export type HoistOption = "all" | "functions" | "functions-and-types" | "never" | "types";
123+
export type VarsOption = "all" | "local";
124+
export type ArgsOption = "after-used" | "all" | "none";
125+
export type IgnorePatternFor_String = null | string;
126+
export type CaughtErrorsJson = "all" | "none";
127+
export type NoUnusedVarsFixMode = "off" | "suggestion" | "fix" | "safe-fix";
123128
export type ShorthandType = "always" | "methods" | "properties" | "consistent" | "consistent-as-needed" | "never";
124129
/**
125130
* A forbidden prop, either as a plain prop name string or with options.
@@ -1006,7 +1011,7 @@ export interface DummyRuleMap {
10061011
"no-unused-expressions"?: AllowWarnDeny | [AllowWarnDeny] | [AllowWarnDeny, NoUnusedExpressionsConfig];
10071012
"no-unused-labels"?: RuleNoConfig;
10081013
"no-unused-private-class-members"?: RuleNoConfig;
1009-
"no-unused-vars"?: DummyRule;
1014+
"no-unused-vars"?: AllowWarnDeny | [AllowWarnDeny] | [AllowWarnDeny, VarsOption | NoUnusedVarsOptions];
10101015
"no-use-before-define"?: DummyRule;
10111016
"no-useless-assignment"?: RuleNoConfig;
10121017
"no-useless-backreference"?: RuleNoConfig;
@@ -3026,6 +3031,265 @@ export interface NoUnusedExpressionsConfig {
30263031
*/
30273032
enforceForJSX?: boolean;
30283033
}
3034+
export interface NoUnusedVarsOptions {
3035+
/**
3036+
* Controls how unused arguments are checked.
3037+
*/
3038+
args?: ArgsOption;
3039+
/**
3040+
* Specifies exceptions to this rule for unused arguments. Arguments whose
3041+
* names match this pattern will be ignored.
3042+
*
3043+
* By default, this pattern is `^_` unless options are configured with an
3044+
* object. In this case it will default to [`None`]. Note that this
3045+
* behavior deviates from both ESLint and TypeScript-ESLint, which never
3046+
* provide a default pattern.
3047+
*
3048+
* #### Example
3049+
*
3050+
* Examples of **correct** code for this option when the pattern is `^_`:
3051+
*
3052+
* ```javascript
3053+
* function foo(_a, b) {
3054+
* console.log(b);
3055+
* }
3056+
* foo(1, 2);
3057+
* ```
3058+
*/
3059+
argsIgnorePattern?: IgnorePatternFor_String;
3060+
/**
3061+
* Used for `catch` block validation.
3062+
*/
3063+
caughtErrors?: CaughtErrorsJson;
3064+
/**
3065+
* Specifies exceptions to this rule for errors caught within a `catch` block.
3066+
* Variables declared within a `catch` block whose names match this pattern
3067+
* will be ignored.
3068+
*
3069+
* #### Example
3070+
*
3071+
* Examples of **correct** code when the pattern is `^ignore`:
3072+
*
3073+
* ```javascript
3074+
* try {
3075+
* // ...
3076+
* } catch (ignoreErr) {
3077+
* console.error("Error caught in catch block");
3078+
* }
3079+
* ```
3080+
*/
3081+
caughtErrorsIgnorePattern?: IgnorePatternFor_String;
3082+
/**
3083+
* This option specifies exceptions within destructuring patterns that will
3084+
* not be checked for usage. Variables declared within array destructuring
3085+
* whose names match this pattern will be ignored.
3086+
*
3087+
* By default this pattern is unset.
3088+
*
3089+
* #### Example
3090+
*
3091+
* Examples of **correct** code for this option, when the pattern is `^_`:
3092+
* ```javascript
3093+
* const [a, _b, c] = ["a", "b", "c"];
3094+
* console.log(a + c);
3095+
*
3096+
* const { x: [_a, foo] } = bar;
3097+
* console.log(foo);
3098+
*
3099+
* let _m, n;
3100+
* foo.forEach(item => {
3101+
* [_m, n] = item;
3102+
* console.log(n);
3103+
* });
3104+
* ```
3105+
*/
3106+
destructuredArrayIgnorePattern?: IgnorePatternFor_String;
3107+
/**
3108+
* Controls which `no-unused-vars` auto-fixes are emitted.
3109+
*
3110+
* When omitted, both `imports` and `variables` default to `"suggestion"`,
3111+
* preserving the current behavior.
3112+
*
3113+
* NOTE: This option is experimental and may change based on feedback.
3114+
*/
3115+
fix?: NoUnusedVarsFixOptions;
3116+
/**
3117+
* The `ignoreClassWithStaticInitBlock` option is a boolean. Static
3118+
* initialization blocks allow you to initialize static variables and
3119+
* execute code during the evaluation of a class definition, meaning
3120+
* the static block code is executed without creating a new instance
3121+
* of the class. When set to `true`, this option ignores classes
3122+
* containing static initialization blocks.
3123+
*
3124+
* #### Example
3125+
*
3126+
* Examples of **incorrect** code for the `{ "ignoreClassWithStaticInitBlock": true }` option
3127+
*
3128+
* ```javascript
3129+
* /* no-unused-vars: ["error", { "ignoreClassWithStaticInitBlock": true }]* /
3130+
*
3131+
* class Foo {
3132+
* static myProperty = "some string";
3133+
* static mymethod() {
3134+
* return "some string";
3135+
* }
3136+
* }
3137+
*
3138+
* class Bar {
3139+
* static {
3140+
* let baz; // unused variable
3141+
* }
3142+
* }
3143+
* ```
3144+
*
3145+
* Examples of **correct** code for the `{ "ignoreClassWithStaticInitBlock": true }` option
3146+
*
3147+
* ```javascript
3148+
* /* no-unused-vars: ["error", { "ignoreClassWithStaticInitBlock": true }]* /
3149+
*
3150+
* class Foo {
3151+
* static {
3152+
* let bar = "some string";
3153+
*
3154+
* console.log(bar);
3155+
* }
3156+
* }
3157+
* ```
3158+
*/
3159+
ignoreClassWithStaticInitBlock?: boolean;
3160+
/**
3161+
* Using a Rest property it is possible to "omit" properties from an
3162+
* object, but by default the sibling properties are marked as "unused".
3163+
* With this option enabled the rest property's siblings are ignored.
3164+
*
3165+
*
3166+
* #### Example
3167+
* Examples of **correct** code when this option is set to `true`:
3168+
* ```js
3169+
* // 'foo' and 'bar' were ignored because they have a rest property sibling.
3170+
* var { foo, ...coords } = data;
3171+
*
3172+
* var bar;
3173+
* ({ bar, ...coords } = data);
3174+
* ```
3175+
*/
3176+
ignoreRestSiblings?: boolean;
3177+
/**
3178+
* When set to `true`, the rule will ignore variables declared with
3179+
* `using` or `await using` declarations, even if they are unused.
3180+
*
3181+
* This is useful when working with resources that need to be disposed
3182+
* via the explicit resource management proposal, where the primary
3183+
* purpose is the disposal side effect rather than using the resource.
3184+
*
3185+
* #### Example
3186+
*
3187+
* Examples of **correct** code for the `{ "ignoreUsingDeclarations": true }` option:
3188+
*
3189+
* ```javascript
3190+
* /* no-unused-vars: ["error", { "ignoreUsingDeclarations": true }]* /
3191+
*
3192+
* using resource = getResource();
3193+
* await using anotherResource = getAnotherResource();
3194+
* ```
3195+
*/
3196+
ignoreUsingDeclarations?: boolean;
3197+
/**
3198+
* The `reportUsedIgnorePattern` option is a boolean.
3199+
* Using this option will report variables that match any of the valid
3200+
* ignore pattern options (`varsIgnorePattern`, `argsIgnorePattern`,
3201+
* `caughtErrorsIgnorePattern`, or `destructuredArrayIgnorePattern`) if
3202+
* they have been used.
3203+
*
3204+
* #### Example
3205+
*
3206+
* Examples of **incorrect** code for the `{ "reportUsedIgnorePattern": true }` option:
3207+
*
3208+
* ```javascript
3209+
* /* no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]* /
3210+
*
3211+
* var firstVarIgnored = 1;
3212+
* var secondVar = 2;
3213+
* console.log(firstVarIgnored, secondVar);
3214+
* ```
3215+
*
3216+
* Examples of **correct** code for the `{ "reportUsedIgnorePattern": true }` option:
3217+
*
3218+
* ```javascript
3219+
* /* no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]* /
3220+
*
3221+
* var firstVar = 1;
3222+
* var secondVar = 2;
3223+
* console.log(firstVar, secondVar);
3224+
* ```
3225+
*/
3226+
reportUsedIgnorePattern?: boolean;
3227+
/**
3228+
* The `reportVarsOnlyUsedAsTypes` option is a boolean.
3229+
*
3230+
* If `true`, the rule will also report variables that are only used as types.
3231+
*
3232+
* #### Examples
3233+
*
3234+
* Examples of **incorrect** code for the `{ "reportVarsOnlyUsedAsTypes": true }` option:
3235+
*
3236+
* ```javascript
3237+
* /* no-unused-vars: ["error", { "reportVarsOnlyUsedAsTypes": true }] * /
3238+
*
3239+
* const myNumber: number = 4;
3240+
* export type MyNumber = typeof myNumber
3241+
* ```
3242+
*
3243+
* Examples of **correct** code for the `{ "reportVarsOnlyUsedAsTypes": true }` option:
3244+
*
3245+
* ```javascript
3246+
* export type MyNumber = number;
3247+
* ```
3248+
*
3249+
* Note: even with `{ "reportVarsOnlyUsedAsTypes": false }`, cases where the value is
3250+
* only used a type within itself will still be reported:
3251+
* ```javascript
3252+
* function foo(): typeof foo {}
3253+
* ```
3254+
*/
3255+
reportVarsOnlyUsedAsTypes?: boolean;
3256+
/**
3257+
* Controls how usage of a variable in the global scope is checked.
3258+
*/
3259+
vars?: VarsOption;
3260+
/**
3261+
* Specifies exceptions to this rule for unused variables. Variables whose
3262+
* names match this pattern will be ignored.
3263+
*
3264+
* By default, this pattern is `^_` unless options are configured with an
3265+
* object. In this case it will default to [`None`]. Note that this
3266+
* behavior deviates from both ESLint and TypeScript-ESLint, which never
3267+
* provide a default pattern.
3268+
*
3269+
* #### Example
3270+
*
3271+
* Examples of **correct** code for this option when the pattern is `^_`:
3272+
* ```javascript
3273+
* var _a = 10;
3274+
* var b = 10;
3275+
* console.log(b);
3276+
* ```
3277+
*/
3278+
varsIgnorePattern?: IgnorePatternFor_String;
3279+
}
3280+
/**
3281+
* Fine-grained auto-fix controls for `no-unused-vars`.
3282+
*/
3283+
export interface NoUnusedVarsFixOptions {
3284+
/**
3285+
* Controls auto-fixes for unused imports.
3286+
*/
3287+
imports?: NoUnusedVarsFixMode;
3288+
/**
3289+
* Controls auto-fixes for unused variables (including catch bindings).
3290+
*/
3291+
variables?: NoUnusedVarsFixMode;
3292+
}
30293293
export interface NoUselessComputedKey {
30303294
/**
30313295
* The `enforceForClassMembers` option controls whether the rule applies to

‎crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,23 @@ use oxc_diagnostics::OxcDiagnostic;
1919
use oxc_macros::declare_oxc_lint;
2020
use oxc_semantic::{AstNode, ScopeFlags, SymbolFlags};
2121
use oxc_span::{GetSpan, Span};
22+
use schemars::JsonSchema;
2223
use symbol::Symbol;
2324

2425
use crate::{
2526
context::{ContextHost, LintContext},
2627
rule::Rule,
28+
rules::eslint::no_unused_vars::options::VarsOption,
2729
};
2830

31+
#[derive(JsonSchema, Debug)]
32+
#[serde(untagged)]
33+
#[expect(unused)] // only for schemars generation, not actually used in code
34+
pub enum NoUnusedVarsConfig {
35+
Vars(VarsOption),
36+
Options(NoUnusedVarsOptions),
37+
}
38+
2939
#[derive(Debug, Default, Clone)]
3040
pub struct NoUnusedVars(Box<NoUnusedVarsOptions>);
3141

@@ -193,7 +203,7 @@ declare_oxc_lint!(
193203
eslint,
194204
correctness,
195205
fix = conditional_dangerous_fix_or_suggestion,
196-
config = NoUnusedVarsOptions,
206+
config = NoUnusedVarsConfig,
197207
version = "0.7.0",
198208
);
199209

‎crates/oxc_linter/src/rules/eslint/no_unused_vars/options.rs‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use serde_json::Value;
88

99
// See [ESLint - no-unused-vars config schema](https://github.com/eslint/eslint/blob/53b1ff047948e36682fade502c949f4e371e53cd/lib/rules/no-unused-vars.js#L61)
1010
#[derive(Debug, Clone, JsonSchema)]
11-
#[serde(rename_all = "camelCase", default)]
11+
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
1212
#[must_use]
1313
#[non_exhaustive]
1414
pub struct NoUnusedVarsOptions {
@@ -235,7 +235,7 @@ pub struct NoUnusedVarsOptions {
235235

236236
/// Fine-grained auto-fix controls for `no-unused-vars`.
237237
#[derive(Default, Debug, Clone, JsonSchema, Serialize)]
238-
#[serde(rename_all = "camelCase", default)]
238+
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
239239
#[must_use]
240240
#[non_exhaustive]
241241
pub struct NoUnusedVarsFixOptions {
@@ -271,9 +271,13 @@ impl NoUnusedVarsFixMode {
271271
// which represents the default ignore pattern for when no pattern is
272272
// explicitly provided.
273273
#[derive(Debug, Clone, Copy, JsonSchema)]
274+
#[serde(untagged)]
274275
pub enum IgnorePattern<R> {
275276
/// No ignore pattern was provided, use the default pattern. This
276277
/// means that the pattern is `^_`.
278+
#[serde(skip)]
279+
// skip serialization since this is only a marker for default behavior, not an actual pattern to be used.
280+
// The schema generation would produce two `null` options.
277281
Default,
278282
/// The ignore pattern is explicitly none.
279283
None,

‎crates/oxc_linter/src/utils/schemars.rs‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@ use crate::rules::RuleEnum;
1313
/// These should return the DummyRule struct/schema instead of the actual rule struct/schema,
1414
/// until we verify that the rule's schema is valid and can be generated by schemars.
1515
#[cfg(feature = "ruledocs")]
16-
const NO_VERIFIED_VALID_SCHEMA: [&str; 103] = [
16+
const NO_VERIFIED_VALID_SCHEMA: [&str; 102] = [
1717
"eslint/func-name-matching",
1818
"eslint/no-restricted-globals",
1919
"eslint/no-restricted-imports",
2020
"eslint/no-restricted-properties",
21-
"eslint/no-unused-vars",
2221
"eslint/no-use-before-define",
2322
"eslint/no-warning-comments",
2423
"eslint/operator-assignment",

0 commit comments

Comments
 (0)