diff --git a/crates/oxc_linter/src/config/mod.rs b/crates/oxc_linter/src/config/mod.rs index 2923a493bad08..521122b3d0837 100644 --- a/crates/oxc_linter/src/config/mod.rs +++ b/crates/oxc_linter/src/config/mod.rs @@ -17,7 +17,8 @@ use self::errors::{ FailedToParseJsonc, }; pub use self::{ - env::ESLintEnv, globals::ESLintGlobals, rules::ESLintRules, settings::ESLintSettings, + env::ESLintEnv, globals::ESLintGlobals, rules::ESLintRules, + settings::jsdoc::JSDocPluginSettings, settings::ESLintSettings, }; /// ESLint Config diff --git a/crates/oxc_linter/src/config/settings/jsdoc.rs b/crates/oxc_linter/src/config/settings/jsdoc.rs index b31fe54cd3a4c..1a358484c2717 100644 --- a/crates/oxc_linter/src/config/settings/jsdoc.rs +++ b/crates/oxc_linter/src/config/settings/jsdoc.rs @@ -2,7 +2,7 @@ use rustc_hash::FxHashMap; use serde::Deserialize; /// -#[derive(Debug, Deserialize, Default)] +#[derive(Debug, Deserialize)] pub struct JSDocPluginSettings { /// For all rules but NOT apply to `check-access` and `empty-tags` rule #[serde(default, rename = "ignorePrivate")] @@ -70,6 +70,23 @@ pub struct JSDocPluginSettings { // }[] } +// `Default` attribute does not call custom `default = "path"` function! +impl Default for JSDocPluginSettings { + fn default() -> Self { + Self { + ignore_private: false, + ignore_internal: false, + // Exists only for these defaults + ignore_replaces_docs: true, + override_replaces_docs: true, + arguments_extends_replaces_docs: false, + implements_replaces_docs: false, + exempt_destructured_roots_from_checks: false, + tag_name_preference: FxHashMap::default(), + } + } +} + impl JSDocPluginSettings { /// Only for `check-tag-names` rule /// Return `Some(reason)` if blocked @@ -187,6 +204,16 @@ mod test { assert!(settings.override_replaces_docs); assert!(!settings.arguments_extends_replaces_docs); assert!(!settings.implements_replaces_docs); + + let settings = JSDocPluginSettings::default(); + + assert!(!settings.ignore_private); + assert!(!settings.ignore_internal); + assert_eq!(settings.tag_name_preference.len(), 0); + assert!(settings.ignore_replaces_docs); + assert!(settings.override_replaces_docs); + assert!(!settings.arguments_extends_replaces_docs); + assert!(!settings.implements_replaces_docs); } #[test] diff --git a/crates/oxc_linter/src/config/settings/mod.rs b/crates/oxc_linter/src/config/settings/mod.rs index a8928765a3510..eaca63662faaf 100644 --- a/crates/oxc_linter/src/config/settings/mod.rs +++ b/crates/oxc_linter/src/config/settings/mod.rs @@ -4,7 +4,7 @@ use self::{ }; use serde::Deserialize; -mod jsdoc; +pub mod jsdoc; mod jsx_a11y; mod next; mod react; diff --git a/crates/oxc_linter/src/rules/jsdoc/check_access.rs b/crates/oxc_linter/src/rules/jsdoc/check_access.rs index 636f0f56c34f5..d87def7857ee7 100644 --- a/crates/oxc_linter/src/rules/jsdoc/check_access.rs +++ b/crates/oxc_linter/src/rules/jsdoc/check_access.rs @@ -7,7 +7,7 @@ use oxc_span::Span; use phf::phf_set; use rustc_hash::FxHashSet; -use crate::{context::LintContext, rule::Rule}; +use crate::{context::LintContext, rule::Rule, utils::should_ignore_as_internal}; #[derive(Debug, Error, Diagnostic)] enum CheckAccessDiagnostic { @@ -75,7 +75,12 @@ impl Rule for CheckAccess { access_related_tag_names.insert(settings.resolve_tag_name(level)); } - for jsdoc in ctx.semantic().jsdoc().iter_all() { + for jsdoc in ctx + .semantic() + .jsdoc() + .iter_all() + .filter(|jsdoc| !should_ignore_as_internal(jsdoc, settings)) + { let mut access_related_tags_count = 0; for tag in jsdoc.tags() { let tag_name = tag.kind.parsed(); diff --git a/crates/oxc_linter/src/rules/jsdoc/check_property_names.rs b/crates/oxc_linter/src/rules/jsdoc/check_property_names.rs index 97c0486c473d6..f8737417db9b5 100644 --- a/crates/oxc_linter/src/rules/jsdoc/check_property_names.rs +++ b/crates/oxc_linter/src/rules/jsdoc/check_property_names.rs @@ -7,7 +7,11 @@ use oxc_macros::declare_oxc_lint; use oxc_span::Span; use rustc_hash::{FxHashMap, FxHashSet}; -use crate::{context::LintContext, rule::Rule}; +use crate::{ + context::LintContext, + rule::Rule, + utils::{should_ignore_as_internal, should_ignore_as_private}, +}; #[derive(Debug, Error, Diagnostic)] enum CheckPropertyNamesDiagnostic { @@ -63,7 +67,13 @@ impl Rule for CheckPropertyNames { let settings = &ctx.settings().jsdoc; let resolved_property_tag_name = settings.resolve_tag_name("property"); - for jsdoc in ctx.semantic().jsdoc().iter_all() { + for jsdoc in ctx + .semantic() + .jsdoc() + .iter_all() + .filter(|jsdoc| !should_ignore_as_internal(jsdoc, settings)) + .filter(|jsdoc| !should_ignore_as_private(jsdoc, settings)) + { let mut seen: FxHashMap<&str, FxHashSet> = FxHashMap::default(); for tag in jsdoc.tags() { if tag.kind.parsed() != resolved_property_tag_name { diff --git a/crates/oxc_linter/src/rules/jsdoc/check_tag_names.rs b/crates/oxc_linter/src/rules/jsdoc/check_tag_names.rs index a58841a1428ee..6f31fbf313aa6 100644 --- a/crates/oxc_linter/src/rules/jsdoc/check_tag_names.rs +++ b/crates/oxc_linter/src/rules/jsdoc/check_tag_names.rs @@ -7,7 +7,11 @@ use oxc_span::Span; use phf::phf_set; use serde::Deserialize; -use crate::{context::LintContext, rule::Rule}; +use crate::{ + context::LintContext, + rule::Rule, + utils::{should_ignore_as_internal, should_ignore_as_private}, +}; #[derive(Debug, Error, Diagnostic)] #[error("eslint-plugin-jsdoc(check-tag-names): Invalid tag name found.")] @@ -212,7 +216,13 @@ impl Rule for CheckTagNames { let is_declare = false; let is_ambient = is_dts || is_declare; - for jsdoc in ctx.semantic().jsdoc().iter_all() { + for jsdoc in ctx + .semantic() + .jsdoc() + .iter_all() + .filter(|jsdoc| !should_ignore_as_internal(jsdoc, settings)) + .filter(|jsdoc| !should_ignore_as_private(jsdoc, settings)) + { for tag in jsdoc.tags() { let tag_name = tag.kind.parsed(); diff --git a/crates/oxc_linter/src/rules/jsdoc/empty_tags.rs b/crates/oxc_linter/src/rules/jsdoc/empty_tags.rs index adb7ff9fddaef..d7d9d564ff1aa 100644 --- a/crates/oxc_linter/src/rules/jsdoc/empty_tags.rs +++ b/crates/oxc_linter/src/rules/jsdoc/empty_tags.rs @@ -7,7 +7,7 @@ use oxc_span::Span; use phf::phf_set; use serde::Deserialize; -use crate::{context::LintContext, rule::Rule}; +use crate::{context::LintContext, rule::Rule, utils::should_ignore_as_private}; #[derive(Debug, Error, Diagnostic)] #[error("eslint-plugin-jsdoc(empty-tags): Expects the void tags to be empty of any content.")] @@ -95,6 +95,8 @@ impl Rule for EmptyTags { } fn run_once(&self, ctx: &LintContext) { + let settings = &ctx.settings().jsdoc; + let is_empty_tag_kind = |tag_name: &str| { if EMPTY_TAGS.contains(tag_name) { return true; @@ -105,7 +107,12 @@ impl Rule for EmptyTags { false }; - for jsdoc in ctx.semantic().jsdoc().iter_all() { + for jsdoc in ctx + .semantic() + .jsdoc() + .iter_all() + .filter(|jsdoc| !should_ignore_as_private(jsdoc, settings)) + { for tag in jsdoc.tags() { let tag_name = tag.kind.parsed(); diff --git a/crates/oxc_linter/src/rules/jsdoc/implements_on_classes.rs b/crates/oxc_linter/src/rules/jsdoc/implements_on_classes.rs index 91d9c7cd627e3..288287dba4b7c 100644 --- a/crates/oxc_linter/src/rules/jsdoc/implements_on_classes.rs +++ b/crates/oxc_linter/src/rules/jsdoc/implements_on_classes.rs @@ -1,6 +1,9 @@ use crate::{ - ast_util::is_function_node, context::LintContext, rule::Rule, - utils::get_function_nearest_jsdoc_node, AstNode, + ast_util::is_function_node, + context::LintContext, + rule::Rule, + utils::{get_function_nearest_jsdoc_node, should_ignore_as_internal, should_ignore_as_private}, + AstNode, }; use oxc_ast::AstKind; use oxc_diagnostics::{ @@ -93,7 +96,11 @@ impl Rule for ImplementsOnClasses { let resolved_constructor_tag_name = settings.resolve_tag_name("constructor"); let (mut implements_found, mut class_or_ctor_found) = (None, false); - for jsdoc in &jsdocs { + for jsdoc in jsdocs + .iter() + .filter(|jsdoc| !should_ignore_as_internal(jsdoc, settings)) + .filter(|jsdoc| !should_ignore_as_private(jsdoc, settings)) + { for tag in jsdoc.tags() { let tag_name = tag.kind.parsed(); diff --git a/crates/oxc_linter/src/rules/jsdoc/no_defaults.rs b/crates/oxc_linter/src/rules/jsdoc/no_defaults.rs index 733d4f9f0f614..20bc206a54c76 100644 --- a/crates/oxc_linter/src/rules/jsdoc/no_defaults.rs +++ b/crates/oxc_linter/src/rules/jsdoc/no_defaults.rs @@ -7,8 +7,11 @@ use oxc_span::Span; use serde::Deserialize; use crate::{ - ast_util::is_function_node, context::LintContext, rule::Rule, - utils::get_function_nearest_jsdoc_node, AstNode, + ast_util::is_function_node, + context::LintContext, + rule::Rule, + utils::{get_function_nearest_jsdoc_node, should_ignore_as_internal, should_ignore_as_private}, + AstNode, }; #[derive(Debug, Error, Diagnostic)] @@ -74,7 +77,11 @@ impl Rule for NoDefaults { let resolved_param_tag_name = settings.resolve_tag_name("param"); let config = &self.0; - for jsdoc in jsdocs { + for jsdoc in jsdocs + .iter() + .filter(|jsdoc| !should_ignore_as_internal(jsdoc, settings)) + .filter(|jsdoc| !should_ignore_as_private(jsdoc, settings)) + { for tag in jsdoc.tags() { let tag_name = tag.kind.parsed(); diff --git a/crates/oxc_linter/src/rules/jsdoc/require_property.rs b/crates/oxc_linter/src/rules/jsdoc/require_property.rs index dab89c6bf4629..97bef7450e2e5 100644 --- a/crates/oxc_linter/src/rules/jsdoc/require_property.rs +++ b/crates/oxc_linter/src/rules/jsdoc/require_property.rs @@ -5,7 +5,11 @@ use oxc_diagnostics::{ use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule}; +use crate::{ + context::LintContext, + rule::Rule, + utils::{should_ignore_as_internal, should_ignore_as_private}, +}; #[derive(Debug, Error, Diagnostic)] #[error("eslint-plugin-jsdoc(require-property): The `@typedef` and `@namespace` tags must include a `@property` tag with the type Object.")] @@ -58,7 +62,13 @@ impl Rule for RequireProperty { let resolved_typedef_tag_name = settings.resolve_tag_name("typedef"); let resolved_namespace_tag_name = settings.resolve_tag_name("namespace"); - for jsdoc in ctx.semantic().jsdoc().iter_all() { + for jsdoc in ctx + .semantic() + .jsdoc() + .iter_all() + .filter(|jsdoc| !should_ignore_as_internal(jsdoc, settings)) + .filter(|jsdoc| !should_ignore_as_private(jsdoc, settings)) + { let mut should_report = None; for tag in jsdoc.tags() { let tag_name = tag.kind.parsed(); diff --git a/crates/oxc_linter/src/rules/jsdoc/require_property_description.rs b/crates/oxc_linter/src/rules/jsdoc/require_property_description.rs index d22144be3283b..3c09cccb1aa0a 100644 --- a/crates/oxc_linter/src/rules/jsdoc/require_property_description.rs +++ b/crates/oxc_linter/src/rules/jsdoc/require_property_description.rs @@ -5,7 +5,11 @@ use oxc_diagnostics::{ use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule}; +use crate::{ + context::LintContext, + rule::Rule, + utils::{should_ignore_as_internal, should_ignore_as_private}, +}; #[derive(Debug, Error, Diagnostic)] #[error("eslint-plugin-jsdoc(require-property-description): Missing description in @property tag.")] @@ -45,7 +49,13 @@ impl Rule for RequirePropertyDescription { let settings = &ctx.settings().jsdoc; let resolved_property_tag_name = settings.resolve_tag_name("property"); - for jsdoc in ctx.semantic().jsdoc().iter_all() { + for jsdoc in ctx + .semantic() + .jsdoc() + .iter_all() + .filter(|jsdoc| !should_ignore_as_internal(jsdoc, settings)) + .filter(|jsdoc| !should_ignore_as_private(jsdoc, settings)) + { for tag in jsdoc.tags() { let tag_name = tag.kind; diff --git a/crates/oxc_linter/src/rules/jsdoc/require_property_name.rs b/crates/oxc_linter/src/rules/jsdoc/require_property_name.rs index c7e829b48d92c..853a4b14385ed 100644 --- a/crates/oxc_linter/src/rules/jsdoc/require_property_name.rs +++ b/crates/oxc_linter/src/rules/jsdoc/require_property_name.rs @@ -5,7 +5,11 @@ use oxc_diagnostics::{ use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule}; +use crate::{ + context::LintContext, + rule::Rule, + utils::{should_ignore_as_internal, should_ignore_as_private}, +}; #[derive(Debug, Error, Diagnostic)] #[error("eslint-plugin-jsdoc(require-property-name): Missing name in @property tag.")] @@ -45,7 +49,13 @@ impl Rule for RequirePropertyName { let settings = &ctx.settings().jsdoc; let resolved_property_tag_name = settings.resolve_tag_name("property"); - for jsdoc in ctx.semantic().jsdoc().iter_all() { + for jsdoc in ctx + .semantic() + .jsdoc() + .iter_all() + .filter(|jsdoc| !should_ignore_as_internal(jsdoc, settings)) + .filter(|jsdoc| !should_ignore_as_private(jsdoc, settings)) + { for tag in jsdoc.tags() { let tag_name = tag.kind; diff --git a/crates/oxc_linter/src/rules/jsdoc/require_property_type.rs b/crates/oxc_linter/src/rules/jsdoc/require_property_type.rs index 80e163fcf36ac..5412a79a335f8 100644 --- a/crates/oxc_linter/src/rules/jsdoc/require_property_type.rs +++ b/crates/oxc_linter/src/rules/jsdoc/require_property_type.rs @@ -5,7 +5,11 @@ use oxc_diagnostics::{ use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule}; +use crate::{ + context::LintContext, + rule::Rule, + utils::{should_ignore_as_internal, should_ignore_as_private}, +}; #[derive(Debug, Error, Diagnostic)] #[error("eslint-plugin-jsdoc(require-property-type): Missing type in @property tag.")] @@ -45,7 +49,13 @@ impl Rule for RequirePropertyType { let settings = &ctx.settings().jsdoc; let resolved_property_tag_name = settings.resolve_tag_name("property"); - for jsdoc in ctx.semantic().jsdoc().iter_all() { + for jsdoc in ctx + .semantic() + .jsdoc() + .iter_all() + .filter(|jsdoc| !should_ignore_as_internal(jsdoc, settings)) + .filter(|jsdoc| !should_ignore_as_private(jsdoc, settings)) + { for tag in jsdoc.tags() { let tag_name = tag.kind; diff --git a/crates/oxc_linter/src/utils/jsdoc.rs b/crates/oxc_linter/src/utils/jsdoc.rs index 33f6d1168851a..08ce096d97f0f 100644 --- a/crates/oxc_linter/src/utils/jsdoc.rs +++ b/crates/oxc_linter/src/utils/jsdoc.rs @@ -1,5 +1,6 @@ -use crate::{context::LintContext, AstNode}; +use crate::{config::JSDocPluginSettings, context::LintContext, AstNode}; use oxc_ast::AstKind; +use oxc_semantic::JSDoc; /// JSDoc is often attached on the parent node of a function. /// @@ -36,3 +37,35 @@ pub fn get_function_nearest_jsdoc_node<'a, 'b>( Some(current_node) } + +pub fn should_ignore_as_internal(jsdoc: &JSDoc, settings: &JSDocPluginSettings) -> bool { + if settings.ignore_internal { + let resolved_internal_tag_name = settings.resolve_tag_name("internal"); + + for tag in jsdoc.tags() { + if tag.kind.parsed() == resolved_internal_tag_name { + return true; + } + } + } + + false +} + +pub fn should_ignore_as_private(jsdoc: &JSDoc, settings: &JSDocPluginSettings) -> bool { + if settings.ignore_private { + let resolved_private_tag_name = settings.resolve_tag_name("private"); + let resolved_access_tag_name = settings.resolve_tag_name("access"); + + for tag in jsdoc.tags() { + let tag_name = tag.kind.parsed(); + if tag_name == resolved_private_tag_name + || tag_name == resolved_access_tag_name && tag.comment().parsed() == "private" + { + return true; + } + } + } + + false +}