feat(linter/jsdoc): Support settings.ignore(Private|Internal) (#3147)

>
https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/settings.md#user-content-settings-allow-tags-private-or-internal-to-disable-rules-for-that-comment-block

...and fixed the issue that intended initial settings values are not
used in some cases.
This commit is contained in:
Yuji Sugiura 2024-05-01 20:48:28 +09:00 committed by GitHub
parent 8cdd5b0fd8
commit d7a8345e4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 173 additions and 26 deletions

View file

@ -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

View file

@ -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")]
@ -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]

View file

@ -4,7 +4,7 @@ use self::{
};
use serde::Deserialize;
mod jsdoc;
pub mod jsdoc;
mod jsx_a11y;
mod next;
mod react;

View file

@ -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();

View file

@ -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<Span>> = FxHashMap::default();
for tag in jsdoc.tags() {
if tag.kind.parsed() != resolved_property_tag_name {

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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
}