From bd82ab0eac5f7f110ebf5f6181e7476a9518d59f Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Fri, 24 May 2024 14:14:17 +0900 Subject: [PATCH 1/2] feat(linter/jsdoc): Implement require-returns-description --- crates/oxc_linter/src/rules.rs | 2 + .../jsdoc/require_returns_description.rs | 258 ++++++++++++++++++ .../require_returns_description.snap | 30 ++ 3 files changed, 290 insertions(+) create mode 100644 crates/oxc_linter/src/rules/jsdoc/require_returns_description.rs create mode 100644 crates/oxc_linter/src/snapshots/require_returns_description.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 02d4adc967c28..0f454274fcd62 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -383,6 +383,7 @@ mod jsdoc { pub mod require_property_name; pub mod require_property_type; pub mod require_returns; + pub mod require_returns_description; pub mod require_yields; } @@ -736,6 +737,7 @@ oxc_macros::declare_all_lint_rules! { jsdoc::require_property_name, jsdoc::require_property_description, jsdoc::require_returns, + jsdoc::require_returns_description, jsdoc::require_yields, tree_shaking::no_side_effects_in_initialization, } diff --git a/crates/oxc_linter/src/rules/jsdoc/require_returns_description.rs b/crates/oxc_linter/src/rules/jsdoc/require_returns_description.rs new file mode 100644 index 0000000000000..9522b1f5d2f5b --- /dev/null +++ b/crates/oxc_linter/src/rules/jsdoc/require_returns_description.rs @@ -0,0 +1,258 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + context::LintContext, + rule::Rule, + utils::{get_function_nearest_jsdoc_node, should_ignore_as_internal, should_ignore_as_private}, + AstNode, +}; + +fn missing_description_diagnostic(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn( + "eslint-plugin-jsdoc(require-returns-description): Missing JSDoc `@returns` description.", + ) + .with_help("Add description comment to `@returns` tag.") + .with_labels([span0.into()]) +} + +#[derive(Debug, Default, Clone)] +pub struct RequireReturnsDescription; + +declare_oxc_lint!( + /// ### What it does + /// Requires that the `@returns` tag has a description value. + /// The error will not be reported if the return value is `void `or `undefined` or if it is `Promise` or `Promise`. + /// + /// ### Why is this bad? + /// A `@returns` tag should have a description value. + /// + /// ### Example + /// ```javascript + /// // Passing + /// /** @returns Foo. */ + /// function quux (foo) {} + /// + /// // Failing + /// /** @returns */ + /// function quux (foo) {} + /// ``` + RequireReturnsDescription, + pedantic, +); + +impl Rule for RequireReturnsDescription { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::Function(_) | AstKind::ArrowFunctionExpression(_) => {} + _ => return, + } + + // If no JSDoc is found, skip + let Some(jsdocs) = get_function_nearest_jsdoc_node(node, ctx) + .and_then(|node| ctx.jsdoc().get_all_by_node(node)) + else { + return; + }; + + let settings = &ctx.settings().jsdoc; + let resolved_returns_tag_name = settings.resolve_tag_name("returns"); + 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() { + if tag.kind.parsed() != resolved_returns_tag_name { + continue; + } + + let (type_part, comment_part) = tag.type_comment(); + + // If returns type is marked as nothing, skip + if let Some(type_part) = type_part { + if matches!( + type_part.parsed(), + "void" | "undefined" | "Promise" | "Promise" + ) { + continue; + } + } + + // If description exists, skip + if !comment_part.parsed().is_empty() { + continue; + } + + ctx.diagnostic(missing_description_diagnostic(tag.kind.span)); + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ( + " + /** + * + */ + function quux () { + + } + ", + None, + None, + ), + ( + " + /** + * @returns Foo. + */ + function quux () { + + } + ", + None, + None, + ), + ( + " + /** + * @returns Foo. + */ + function quux () { + + } + ", + Some(serde_json::json!([ + { + "contexts": [ + "any", + ], + }, + ])), + None, + ), + ( + " + /** + * @returns {undefined} + */ + function quux () { + + } + ", + None, + None, + ), + ( + " + /** + * @returns {void} + */ + function quux () { + + } + ", + None, + None, + ), + ( + " + /** + * @returns {Promise} + */ + function quux () { + + } + ", + None, + None, + ), + ( + " + /** + * @returns {Promise} + */ + function quux () { + + } + ", + None, + None, + ), + ( + " + /** + * @function + * @returns + */ + ", + None, + None, + ), + ( + " + /** + * @callback + * @returns + */ + ", + None, + None, + ), + ]; + + let fail = vec![ + ( + " + /** + * @returns + */ + function quux (foo) { + + } + ", + None, + None, + ), + ( + " + /** + * @returns {string} + */ + function quux (foo) { + + } + ", + None, + None, + ), + ( + " + /** + * @return + */ + function quux (foo) { + + } + ", + None, + Some(serde_json::json!({ "settings": { + "jsdoc": { + "tagNamePreference": { + "returns": "return", + }, + }, + } })), + ), + ]; + + Tester::new(RequireReturnsDescription::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/require_returns_description.snap b/crates/oxc_linter/src/snapshots/require_returns_description.snap new file mode 100644 index 0000000000000..61fc841a6f49e --- /dev/null +++ b/crates/oxc_linter/src/snapshots/require_returns_description.snap @@ -0,0 +1,30 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: require_returns_description +--- + ⚠ eslint-plugin-jsdoc(require-returns-description): Missing JSDoc `@returns` description. + ╭─[require_returns_description.tsx:3:17] + 2 │ /** + 3 │ * @returns + · ──────── + 4 │ */ + ╰──── + help: Add description comment to `@returns` tag. + + ⚠ eslint-plugin-jsdoc(require-returns-description): Missing JSDoc `@returns` description. + ╭─[require_returns_description.tsx:3:17] + 2 │ /** + 3 │ * @returns {string} + · ──────── + 4 │ */ + ╰──── + help: Add description comment to `@returns` tag. + + ⚠ eslint-plugin-jsdoc(require-returns-description): Missing JSDoc `@returns` description. + ╭─[require_returns_description.tsx:3:17] + 2 │ /** + 3 │ * @return + · ─────── + 4 │ */ + ╰──── + help: Add description comment to `@returns` tag. From cf81209642cc330e469d254eb3d5fb3e3a356fab Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Fri, 24 May 2024 14:17:55 +0900 Subject: [PATCH 2/2] Use utils --- .../src/rules/jsdoc/require_returns_description.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/oxc_linter/src/rules/jsdoc/require_returns_description.rs b/crates/oxc_linter/src/rules/jsdoc/require_returns_description.rs index 9522b1f5d2f5b..e1c109d58e808 100644 --- a/crates/oxc_linter/src/rules/jsdoc/require_returns_description.rs +++ b/crates/oxc_linter/src/rules/jsdoc/require_returns_description.rs @@ -1,9 +1,9 @@ -use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; use crate::{ + ast_util::is_function_node, context::LintContext, rule::Rule, utils::{get_function_nearest_jsdoc_node, should_ignore_as_internal, should_ignore_as_private}, @@ -45,9 +45,8 @@ declare_oxc_lint!( impl Rule for RequireReturnsDescription { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - match node.kind() { - AstKind::Function(_) | AstKind::ArrowFunctionExpression(_) => {} - _ => return, + if !is_function_node(node) { + return; } // If no JSDoc is found, skip