Skip to content

Commit

Permalink
feat(linter/jsdoc): Support settings.ignore(Private|Internal) (#3147)
Browse files Browse the repository at this point in the history
  • Loading branch information
leaysgur authored May 1, 2024
1 parent 8cdd5b0 commit d7a8345
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 26 deletions.
3 changes: 2 additions & 1 deletion crates/oxc_linter/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 28 additions & 1 deletion crates/oxc_linter/src/config/settings/jsdoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use rustc_hash::FxHashMap;
use serde::Deserialize;

/// <https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/settings.md>
#[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")]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/config/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use self::{
};
use serde::Deserialize;

mod jsdoc;
pub mod jsdoc;
mod jsx_a11y;
mod next;
mod react;
Expand Down
9 changes: 7 additions & 2 deletions crates/oxc_linter/src/rules/jsdoc/check_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down
14 changes: 12 additions & 2 deletions crates/oxc_linter/src/rules/jsdoc/check_property_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Span>> = FxHashMap::default();
for tag in jsdoc.tags() {
if tag.kind.parsed() != resolved_property_tag_name {
Expand Down
14 changes: 12 additions & 2 deletions crates/oxc_linter/src/rules/jsdoc/check_tag_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")]
Expand Down Expand Up @@ -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();

Expand Down
11 changes: 9 additions & 2 deletions crates/oxc_linter/src/rules/jsdoc/empty_tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")]
Expand Down Expand Up @@ -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;
Expand All @@ -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();

Expand Down
13 changes: 10 additions & 3 deletions crates/oxc_linter/src/rules/jsdoc/implements_on_classes.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -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();

Expand Down
13 changes: 10 additions & 3 deletions crates/oxc_linter/src/rules/jsdoc/no_defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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();

Expand Down
14 changes: 12 additions & 2 deletions crates/oxc_linter/src/rules/jsdoc/require_property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")]
Expand Down Expand Up @@ -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();
Expand Down
14 changes: 12 additions & 2 deletions crates/oxc_linter/src/rules/jsdoc/require_property_description.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")]
Expand Down Expand Up @@ -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;

Expand Down
14 changes: 12 additions & 2 deletions crates/oxc_linter/src/rules/jsdoc/require_property_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")]
Expand Down Expand Up @@ -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;

Expand Down
14 changes: 12 additions & 2 deletions crates/oxc_linter/src/rules/jsdoc/require_property_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")]
Expand Down Expand Up @@ -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;

Expand Down
35 changes: 34 additions & 1 deletion crates/oxc_linter/src/utils/jsdoc.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand Down Expand Up @@ -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
}

0 comments on commit d7a8345

Please sign in to comment.