diff --git a/apps/oxlint/src-js/package/config.generated.ts b/apps/oxlint/src-js/package/config.generated.ts index 66651ff114..d221c122c5 100644 --- a/apps/oxlint/src-js/package/config.generated.ts +++ b/apps/oxlint/src-js/package/config.generated.ts @@ -162,6 +162,10 @@ export type NoUnusedVarsFixMode = "off" | "suggestion" | "fix" | "safe-fix"; export type NoUseBeforeDefineConfigJson = Nofunc | NoUseBeforeDefineConfig; export type Nofunc = "nofunc"; export type Location = "start" | "anywhere"; +/** + * The rule takes a single option - an array of possible callback names - which may include object methods. The default callback names are `callback`, `cb`, `next`. + */ +export type CallbackReturn = string[]; /** * The rule takes a single string option: the name of the error parameter. * @@ -1220,7 +1224,7 @@ export interface DummyRuleMap { "no-void"?: RuleNoConfig | [AllowWarnDeny, NoVoid]; "no-warning-comments"?: RuleNoConfig | [AllowWarnDeny, NoWarningCommentsConfigJson]; "no-with"?: RuleNoConfig; - "node/callback-return"?: DummyRule; + "node/callback-return"?: RuleNoConfig | [AllowWarnDeny, CallbackReturn]; "node/global-require"?: RuleNoConfig; "node/handle-callback-err"?: RuleNoConfig | [AllowWarnDeny, HandleCallbackErrConfig]; "node/no-exports-assign"?: RuleNoConfig; diff --git a/crates/oxc_linter/src/config/rules.rs b/crates/oxc_linter/src/config/rules.rs index 79329b6665..79d9f14c14 100644 --- a/crates/oxc_linter/src/config/rules.rs +++ b/crates/oxc_linter/src/config/rules.rs @@ -357,18 +357,24 @@ impl JsonSchema for OxlintRules { schema: &'a Schema, r#gen: &'a SchemaGenerator, ) -> &'a Schema { - let mut current = schema; - while let Some(next) = r#gen.dereference(current) { - current = next; - } + let Some(current) = r#gen.dereference(schema) else { + return schema; + }; let Schema::Object(obj) = current else { - return current; + return schema; }; // We only need to dereference array schemas for rule config. // Reuse the schema for other cases. - if obj.array.is_none() { + // We only need to dereference array schemas with at least 2 entries. A single entry array schema is used for configs which accepts an array. + // 2 entries means that it is a tuple config, and `AllowWarnDeny` needs to be appended for each entry in the tuple. + if obj.array.as_ref().is_none_or(|array| { + array + .items + .as_ref() + .is_none_or(|items| !matches!(items, SingleOrVec::Vec(_))) + }) { return schema; } @@ -401,7 +407,7 @@ impl JsonSchema for OxlintRules { config_schema: Schema, r#gen: &mut SchemaGenerator, ) -> Schema { - let Schema::Object(mut obj) = config_schema else { + let Schema::Object(mut obj) = config_schema.clone() else { return SchemaObject { instance_type: Some(InstanceType::Array.into()), array: Some(Box::new(ArrayValidation { @@ -426,41 +432,35 @@ impl JsonSchema for OxlintRules { "Expected rule schema to be either an object, an array, or a reference, but not multiple" ); - if let Some(reference) = obj.reference { - return SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - items: Some(SingleOrVec::Vec(vec![ - r#gen.subschema_for::(), - Schema::Object(SchemaObject { - reference: Some(reference), - ..Default::default() - }), - ])), - min_items: Some(2), - max_items: Some(2), - ..Default::default() - })), - ..Default::default() - } - .into(); - } - if let Some(array) = obj.array { - let items = match array.items { - None => vec![r#gen.subschema_for::()], - Some(SingleOrVec::Single(config)) => { - vec![r#gen.subschema_for::(), *config] - } - Some(SingleOrVec::Vec(configs)) => { - let mut items = - Vec::with_capacity(configs.len().saturating_add(1)); - items.push(r#gen.subschema_for::()); - items.extend(configs); - items + debug_assert!( + array.additional_items.is_none(), + "Expected array schema to have no additionalItems for rule config" + ); + // We only need to handle the cases where multiple items exists, + // because single item array schema is used for rules which accepts an array as config, + // and we just need to append `AllowWarnDeny` for that case. + let Some(SingleOrVec::Vec(configs)) = array.items else { + return SchemaObject { + instance_type: Some(InstanceType::Array.into()), + array: Some(Box::new(ArrayValidation { + items: Some(SingleOrVec::Vec(vec![ + r#gen.subschema_for::(), + config_schema, + ])), + min_items: Some(2), + max_items: Some(2), + ..Default::default() + })), + ..Default::default() } + .into(); }; + let mut items = Vec::with_capacity(configs.len().saturating_add(1)); + items.push(r#gen.subschema_for::()); + items.extend(configs); + let config_length = items.len() as u32; return SchemaObject { @@ -496,7 +496,7 @@ impl JsonSchema for OxlintRules { array: Some(Box::new(ArrayValidation { items: Some(SingleOrVec::Vec(vec![ r#gen.subschema_for::(), - Schema::Object(obj), + config_schema, ])), min_items: Some(2), max_items: Some(2), diff --git a/crates/oxc_linter/src/utils/schemars.rs b/crates/oxc_linter/src/utils/schemars.rs index bf49db9c49..e88c809bbd 100644 --- a/crates/oxc_linter/src/utils/schemars.rs +++ b/crates/oxc_linter/src/utils/schemars.rs @@ -10,11 +10,10 @@ use crate::rules::RuleEnum; /// These should return the DummyRule struct/schema instead of the actual rule struct/schema, /// until we verify that the rule's schema is valid and can be generated by schemars. #[cfg(feature = "ruledocs")] -const NO_VERIFIED_VALID_SCHEMA: [&str; 10] = [ +const NO_VERIFIED_VALID_SCHEMA: [&str; 9] = [ "eslint/no-restricted-globals", // uses spread syntax like [AllowWarnDeny, ...Config] "eslint/no-restricted-imports", // uses spread syntax like [AllowWarnDeny, ...Config] "eslint/no-restricted-properties", // uses spread syntax like [AllowWarnDeny, ...Config] - "node/callback-return", // uses array syntax [AllowWarnDeny, string[]] "jest/prefer-lowercase-title", // has options from other rule #23186 "jest/valid-title", // has options from other rule #23186 "vitest/prefer-lowercase-title", // has options from other rule #23186 diff --git a/npm/oxlint/configuration_schema.json b/npm/oxlint/configuration_schema.json index 810a4eda81..63dcfea66b 100644 --- a/npm/oxlint/configuration_schema.json +++ b/npm/oxlint/configuration_schema.json @@ -1138,6 +1138,14 @@ }, "additionalProperties": false }, + "CallbackReturn": { + "description": "The rule takes a single option - an array of possible callback names - which may include object methods. The default callback names are `callback`, `cb`, `next`.", + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The rule takes a single option - an array of possible callback names - which may include object methods. The default callback names are `callback`, `cb`, `next`." + }, "CapitalizedCommentsOptions": { "description": "Configuration for the capitalized-comments rule.\n\nThe first element specifies whether comments should `\"always\"` or `\"never\"`\nbegin with a capital letter. The second element is an optional object\ncontaining additional options.", "type": "array", @@ -5517,7 +5525,24 @@ "$ref": "#/definitions/RuleNoConfig" }, "node/callback-return": { - "$ref": "#/definitions/DummyRule" + "anyOf": [ + { + "$ref": "#/definitions/RuleNoConfig" + }, + { + "type": "array", + "items": [ + { + "$ref": "#/definitions/AllowWarnDeny" + }, + { + "$ref": "#/definitions/CallbackReturn" + } + ], + "maxItems": 2, + "minItems": 2 + } + ] }, "node/global-require": { "$ref": "#/definitions/RuleNoConfig"