diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index b7dc7cfca..534456451 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -381,6 +381,7 @@ mod jsdoc { pub mod require_property_description; pub mod require_property_name; pub mod require_property_type; + pub mod require_returns; pub mod require_yields; } @@ -732,6 +733,7 @@ oxc_macros::declare_all_lint_rules! { jsdoc::require_property_type, jsdoc::require_property_name, jsdoc::require_property_description, + jsdoc::require_returns, jsdoc::require_yields, tree_shaking::no_side_effects_in_initialization, } diff --git a/crates/oxc_linter/src/rules/jsdoc/require_returns.rs b/crates/oxc_linter/src/rules/jsdoc/require_returns.rs new file mode 100644 index 000000000..8bdb18df2 --- /dev/null +++ b/crates/oxc_linter/src/rules/jsdoc/require_returns.rs @@ -0,0 +1,2239 @@ +use oxc_ast::{ + ast::{BindingPatternKind, Expression, MethodDefinitionKind}, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_semantic::{JSDoc, JSDocTag}; +use oxc_span::Span; +use phf::phf_set; +use rustc_hash::FxHashMap; +use serde::Deserialize; + +use crate::{ + context::LintContext, + rule::Rule, + utils::{ + get_function_nearest_jsdoc_node, should_ignore_as_avoid, should_ignore_as_internal, + should_ignore_as_private, + }, +}; + +fn missing_returns_diagnostic(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn( + "eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function.", + ) + .with_help("Add `@returns` tag to the JSDoc comment.") + .with_labels([span0.into()]) +} +fn duplicate_returns_diagnostic(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("eslint-plugin-jsdoc(require-returns): Duplicate `@returns` tags.") + .with_help("Remove redundunt `@returns` tag.") + .with_labels([span0.into()]) +} + +#[derive(Debug, Default, Clone)] +pub struct RequireReturns(Box); + +declare_oxc_lint!( + /// ### What it does + /// Requires that return statements are documented. + /// Will also report if multiple `@returns` tags are present. + /// + /// ### Why is this bad? + /// The rule is intended to prevent the omission of `@returns` tag when necessary. + /// + /// ### Example + /// ```javascript + /// // Passing + /// /** @returns Foo. */ + /// function quux () { return foo; } + /// + /// // Failing + /// /** Foo. */ + /// function quux () { return foo; } + /// /** + /// * @returns Foo! + /// * @returns Foo? + /// */ + /// function quux () { return foo; } + /// ``` + RequireReturns, + correctness, +); + +#[derive(Debug, Clone, Deserialize)] +struct RequireReturnsConfig { + #[serde(default = "default_exempted_by", rename = "exemptedBy")] + exempted_by: Vec, + #[serde(default, rename = "checkConstructors")] + check_constructors: bool, + #[serde(default = "default_true", rename = "checkGetters")] + check_getters: bool, + #[serde(default, rename = "forceRequireReturn")] + force_require_return: bool, + #[serde(default, rename = "forceReturnsWithAsync")] + force_returns_with_async: bool, +} +impl Default for RequireReturnsConfig { + fn default() -> Self { + Self { + exempted_by: default_exempted_by(), + check_constructors: false, + check_getters: true, + force_require_return: false, + force_returns_with_async: false, + } + } +} +fn default_true() -> bool { + true +} +fn default_exempted_by() -> Vec { + vec!["inheritdoc".to_string()] +} + +impl Rule for RequireReturns { + fn from_configuration(value: serde_json::Value) -> Self { + value + .as_array() + .and_then(|arr| arr.first()) + .and_then(|value| serde_json::from_value(value.clone()).ok()) + .map_or_else(Self::default, |value| Self(Box::new(value))) + } + + fn run_once(&self, ctx: &LintContext) { + // Step 1. Collect functions to check + // In this rule, existence of `return` statement differs the behavior. + // Search `ReturnStatement` when visiting `Function` requires a lot of work. + // Instead, we collect all functions and their attributes first. + + // Value of map: (AstNode, Span, Attrs: (isAsync, hasReturnValue)) + let mut functions_to_check = FxHashMap::default(); + 'visit_node: for node in ctx.nodes().iter() { + match node.kind() { + AstKind::Function(func) => { + functions_to_check.insert(node.id(), (node, func.span, (func.r#async, false))); + } + AstKind::ArrowFunctionExpression(arrow_func) => { + functions_to_check.insert( + node.id(), + ( + node, + arrow_func.span, + if let Some(expr) = arrow_func.get_expression() { + is_promise_resolve_with_value(expr, ctx) + .map_or((arrow_func.r#async, true), |v| (true, v)) + } else { + (arrow_func.r#async, false) + }, + ), + ); + } + // Update function attrs entry with checking `return` value. + // If syntax is valid, parent function node should be found by looking up the tree. + // + // It may not be accurate if there are multiple `return` in a function like: + // ```js + // function foo(x) { + // if (x) return Promise.resolve(1); + // return 2; + // } + // ``` + // IMO: This is a fault of the original rule design... + AstKind::ReturnStatement(return_stmt) => { + let mut current_node = node; + while let Some(parent_node) = ctx.nodes().parent_node(current_node.id()) { + match parent_node.kind() { + AstKind::Function(_) | AstKind::ArrowFunctionExpression(_) => { + // Ignore `return;` + let Some(ref argument) = return_stmt.argument else { + continue 'visit_node; + }; + + functions_to_check.entry(parent_node.id()).and_modify(|e| { + // If `return somePromise` is found, treat this function as async + match is_promise_resolve_with_value(argument, ctx) { + Some(v) => e.2 = (true, v), + None => e.2 = (e.2 .0, true), + } + }); + continue 'visit_node; + } + _ => { + current_node = parent_node; + } + } + } + } + _ => continue, + } + } + + // Step 2. Check collected functions + for (node, func_span, (is_async, has_return_value)) in functions_to_check.values() { + let Some(func_def_node) = get_function_nearest_jsdoc_node(node, ctx) else { + continue; + }; + // If no JSDoc is found, skip + let Some(jsdocs) = ctx.jsdoc().get_all_by_node(func_def_node) else { + continue; + }; + + let config = &self.0; + let settings = &ctx.settings().jsdoc; + // If JSDoc is found but safely ignored, skip + if jsdocs + .iter() + .filter(|jsdoc| !should_ignore_as_custom_skip(jsdoc)) + .filter(|jsdoc| !should_ignore_as_avoid(jsdoc, settings, &config.exempted_by)) + .filter(|jsdoc| !should_ignore_as_private(jsdoc, settings)) + .filter(|jsdoc| !should_ignore_as_internal(jsdoc, settings)) + .count() + == 0 + { + continue; + } + + // If config disabled checking, skip + if let AstKind::MethodDefinition(method_def) = func_def_node.kind() { + match method_def.kind { + MethodDefinitionKind::Get => { + if !config.check_getters { + continue; + } + } + MethodDefinitionKind::Constructor => { + if !config.check_constructors { + continue; + } + } + _ => {} + } + } + + if !config.force_require_return && + // If sync, no return value, skip + (!has_return_value && !is_async) + { + continue; + } + if !config.force_require_return && !config.force_returns_with_async && + // If async, no resolve value, skip + (!has_return_value && *is_async) + { + continue; + } + + let jsdoc_tags = jsdocs.iter().flat_map(JSDoc::tags).collect::>(); + let resolved_returns_tag_name = settings.resolve_tag_name("returns"); + + if is_missing_returns_tag(&jsdoc_tags, &resolved_returns_tag_name) { + ctx.diagnostic(missing_returns_diagnostic(*func_span)); + continue; + } + + if let Some(span) = is_duplicated_returns_tag(&jsdoc_tags, &resolved_returns_tag_name) { + ctx.diagnostic(duplicate_returns_diagnostic(span)); + } + } + } +} + +const CUSTOM_SKIP_TAG_NAMES: phf::Set<&'static str> = phf_set! { + "abstract", "virtual", "class", "constructor", "type", "interface" +}; +fn should_ignore_as_custom_skip(jsdoc: &JSDoc) -> bool { + jsdoc.tags().iter().any(|tag| CUSTOM_SKIP_TAG_NAMES.contains(tag.kind.parsed())) +} + +fn is_missing_returns_tag(jsdoc_tags: &[&JSDocTag], resolved_returns_tag_name: &str) -> bool { + jsdoc_tags.iter().all(|tag| tag.kind.parsed() != resolved_returns_tag_name) +} + +fn is_duplicated_returns_tag( + jsdoc_tags: &Vec<&JSDocTag>, + resolved_returns_tag_name: &str, +) -> Option { + let mut returns_found = false; + for tag in jsdoc_tags { + if tag.kind.parsed() == resolved_returns_tag_name { + if returns_found { + return Some(tag.kind.span); + } + + returns_found = true; + } + } + + None +} + +/// - Some(true): `Promise` with value +/// - Some(false): `Promise` without value +/// - None: Not a `Promise` but some other expression +fn is_promise_resolve_with_value(expr: &Expression, ctx: &LintContext) -> Option { + // `return new Promise(...)` + if let Expression::NewExpression(new_expr) = expr { + if new_expr.callee.is_specific_id("Promise") { + return new_expr + .arguments + // Get `new Promise(HERE, ...)` + .first() + // Expect `new Promise(() => {})` or `new Promise(function() {})` + .and_then(|arg| match arg.as_expression() { + Some(Expression::FunctionExpression(func)) => func.params.items.first(), + Some(Expression::ArrowFunctionExpression(arrow_func)) => { + arrow_func.params.items.first() + } + _ => None, + }) + // Retrieve symbol_id of resolver, `new Promise((HERE, ...) => {})` + .and_then(|first_param| match &first_param.pattern.kind { + BindingPatternKind::BindingIdentifier(ident) => Some(ident), + _ => None, + }) + .and_then(|ident| { + // Find all usages of promise resolver + // It may not be accurate if there are multiple `resolve()` in a resolver like: + // ```js + // new Promise((resolve) => { + // if (x) return resolve(); + // resolve(x) + // }) + // ``` + // IMO: This is a fault of the original rule design... + for resolve_ref in ctx.symbols().get_resolved_references(ident.symbol_id.get()?) + { + // Check if `resolve` is called with value + match ctx.nodes().parent_node(resolve_ref.node_id())?.kind() { + // `resolve(foo)` + AstKind::CallExpression(call_expr) => { + if !call_expr.arguments.is_empty() { + return Some(true); + } + } + // `foo(resolve)` + AstKind::Argument(_) => { + return Some(true); + } + _ => continue, + } + } + None + }) + .or(Some(false)); + } + } + + // Tests do not cover `return Promise.xxx()`, but should be...? + + None +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ( + " + /** + * @returns Foo. + */ + function quux () { + + return foo; + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux (bar) { + bar.filter(baz => { + return baz.corge(); + }) + } + ", + None, + None, + ), + ( + " + /** + * @returns Array + */ + function quux (bar) { + return bar.filter(baz => { + return baz.corge(); + }) + } + ", + None, + None, + ), + ( + " + /** + * @returns Array + */ + const quux = (bar) => bar.filter(({ corge }) => corge()) + ", + None, + None, + ), + ( + " + /** + * @inheritdoc + */ + function quux (foo) { + } + ", + None, + None, + ), + ( + " + /** + * @override + */ + function quux (foo) { + } + ", + None, + None, + ), + ( + " + /** + * @constructor + */ + function quux (foo) { + return true; + } + ", + None, + None, + ), + ( + " + /** + * @override + */ + function quux (foo) { + + return foo; + } + ", + None, + None, + ), + ( + " + /** + * @class + */ + function quux (foo) { + return true; + } + ", + None, + None, + ), + ( + " + /** + * @constructor + */ + function quux (foo) { + + } + ", + None, + None, + ), + ( + " + /** + * @returns {object} + */ + function quux () { + + return {a: foo}; + } + ", + None, + None, + ), + ( + " + /** + * @returns {object} + */ + const quux = () => ({a: foo}); + ", + None, + None, + ), + ( + " + /** + * @returns {object} + */ + const quux = () => { + return {a: foo} + }; + ", + None, + None, + ), + ( + " + /** + * @returns {void} + */ + function quux () { + } + ", + None, + None, + ), + ( + " + /** + * @returns {void} + */ + const quux = () => { + + } + ", + None, + None, + ), + ( + " + /** + * @returns {undefined} + */ + function quux () { + } + ", + None, + None, + ), + ( + " + /** + * @returns {undefined} + */ + const quux = () => { + + } + ", + None, + None, + ), + ( + " + /** + * + */ + const quux = () => { + + } + ", + None, + None, + ), + ( + " + class Foo { + /** + * + */ + constructor () { + } + } + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + const language = { + /** + * @param {string} name + */ + set name(name) { + this._name = name; + } + } + ", + None, + None, + ), + ( + " + /** + * @returns {void} + */ + function quux () { + } + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** + * @returns {void} + */ + function quux () { + return undefined; + } + ", + None, + None, + ), + ( + " + /** + * @returns {void} + */ + function quux () { + return undefined; + } + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** + * @returns {void} + */ + function quux () { + return; + } + ", + None, + None, + ), + ( + " + /** + * @returns {void} + */ + function quux () { + return; + } + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** @type {RequestHandler} */ + function quux (req, res , next) { + return; + } + ", + None, + None, + ), + ( + " + /** + * @returns {Promise} + */ + async function quux () { + } + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** + * @returns {Promise} + */ + async function quux () { + } + ", + Some(serde_json::json!([ + { + "forceReturnsWithAsync": true, + }, + ])), + None, + ), + ( + " + /** + * + */ + async function quux () {} + ", + None, + None, + ), + ( + " + /** + * + */ + const quux = async function () {} + ", + None, + None, + ), + ( + " + /** + * + */ + const quux = async () => {} + ", + None, + None, + ), + ( + " + /** foo class */ + class foo { + /** foo constructor */ + constructor () { + // => + this.bar = true; + } + } + + export default foo; + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + } + ", + Some(serde_json::json!([ + { + "forceReturnsWithAsync": true, + }, + ])), + None, + ), + ( + " + /** + * @type {MyCallback} + */ + function quux () { + + } + ", + Some(serde_json::json!([ + { + "exemptedBy": [ + "type", + ], + }, + ])), + None, + ), + ( + " + /** + * @param {array} a + */ + async function foo(a) { + return; + } + ", + None, + None, + ), + ( + " + /** + * @function + */ + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** + * @callback + */ + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** + * @function + * @async + */ + ", + Some(serde_json::json!([ + { + "forceReturnsWithAsync": true, + }, + ])), + None, + ), + ( + " + /** + * @callback + * @async + */ + ", + Some(serde_json::json!([ + { + "forceReturnsWithAsync": true, + }, + ])), + None, + ), + ( + " + class foo { + get bar() { + return 0; + } + } + ", + Some(serde_json::json!([ + { + "checkGetters": false, + }, + ])), + None, + ), + ( + " + class foo { + /** @returns zero */ + get bar() { + return 0; + } + } + ", + Some(serde_json::json!([ + { + "checkGetters": true, + }, + ])), + None, + ), + ( + " + class foo { + /** @returns zero */ + get bar() { + return 0; + } + } + ", + Some(serde_json::json!([ + { + "checkGetters": false, + }, + ])), + None, + ), + ( + " + class TestClass { + /** + * + */ + constructor() { } + } + ", + None, + None, + ), + ( + " + class TestClass { + /** + * @returns A map. + */ + constructor() { + return new Map(); + } + } + ", + None, + None, + ), + ( + " + class TestClass { + /** + * + */ + constructor() { } + } + ", + Some(serde_json::json!([ + { + "checkConstructors": false, + }, + ])), + None, + ), + ( + " + class TestClass { + /** + * + */ + get Test() { } + } + ", + None, + None, + ), + ( + " + class TestClass { + /** + * @returns A number. + */ + get Test() { + return 0; + } + } + ", + None, + None, + ), + ( + " + class TestClass { + /** + * pass(getter but config.checkGetters is false) + */ + get Test() { + return 0; + } + } + ", + Some(serde_json::json!([ + { + "checkGetters": false, + }, + ])), + None, + ), + ( + " + /** + * + */ + function quux (foo) { + + return new Promise(function (resolve, reject) { + resolve(); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux (foo) { + + return new Promise(function (resolve, reject) { + setTimeout(() => { + resolve(); + }); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux (foo) { + + return new Promise(function (resolve, reject) { + foo(); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux (foo) { + + return new Promise(function (resolve, reject) { + abc((resolve) => { + resolve(true); + }); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux (foo) { + + return new Promise(function (resolve, reject) { + abc(function (resolve) { + resolve(true); + }); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + if (true) { + resolve(); + } + }); + return; + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise(); + } + ", + None, + None, + ), + ( + " + /** + * Description. + */ + async function foo() { + return new Promise(resolve => resolve()); + } + ", + None, + None, + ), + ( + " + /** + * Reads a test fixture. + * + * @returns {void}. + */ + export function readFixture(path: string): void; + ", + None, + None, + ), + ( + " + /** + * Reads a test fixture. + */ + export function readFixture(path: string): void; + ", + None, + None, + ), + ( + " + /** + * Reads a test fixture. + */ + export function readFixture(path: string); + ", + None, + None, + ), + ]; + + let fail = vec![ + ( + " + /** + * fail(no @returns) + */ + function quux (foo) { + + return foo; + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux (foo) { + someLabel: { + return foo; + } + } + ", + None, + None, + ), + ( + " + /** + * + */ + const foo = () => ({ + bar: 'baz' + }) + ", + None, + None, + ), + ( + " + /** + * + */ + const foo = bar=>({ bar }) + ", + None, + None, + ), + ( + " + /** + * + */ + const foo = bar => bar.baz() + ", + None, + None, + ), + ( + " + /** + * + */ + function quux (foo) { + + return foo; + } + ", + None, + Some(serde_json::json!({ "settings": { + "jsdoc": { + "tagNamePreference": { + "returns": "return", + }, + }, + } })), + ), + ( + " + /** + * fail(forceRequireReturn: true) + */ + async function quux() { + } + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** + * + */ + const quux = async function () {} + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** + * fail(forceRequireReturn: true) + */ + const quux = async () => {} + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** + * fail(forceRequireReturn: true) + */ + async function quux () {} + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** + * + */ + function quux () { + } + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + const language = { + /** + * @param {string} name + */ + get name() { + return this._name; + } + } + ", + None, + None, + ), + ( + " + /** + * fail(forceReturnsWithAsync: true) + */ + async function quux () { + } + ", + Some(serde_json::json!([ + { + "forceReturnsWithAsync": true, + }, + ])), + None, + ), + ( + " + /** + * @returns {undefined} + * @returns {void} + */ + function quux (foo) { + + return foo; + } + ", + None, + None, + ), + ( + " + /** + * @param foo + */ + function quux (foo) { + return 'bar'; + } + ", + Some(serde_json::json!([ + { + "exemptedBy": [ + "notPresent", + ], + }, + ])), + None, + ), + ( + " + /** + * @param {array} a + */ + async function foo(a) { + return; + } + ", + Some(serde_json::json!([ + { + "forceReturnsWithAsync": true, + }, + ])), + None, + ), + ( + " + /** + * @param {array} a + */ + async function foo(a) { + return Promise.all(a); + } + ", + Some(serde_json::json!([ + { + "forceReturnsWithAsync": true, + }, + ])), + None, + ), + ( + " + class foo { + /** gets bar */ + get bar() { + return 0; + } + } + ", + Some(serde_json::json!([ + { + "checkGetters": true, + }, + ])), + None, + ), + ( + " + class TestClass { + /** + * + */ + constructor() { + return new Map(); + } + } + ", + Some(serde_json::json!([ + { + "checkConstructors": true, + }, + ])), + None, + ), + ( + " + class TestClass { + /** + * + */ + get Test() { + return 0; + } + } + ", + Some(serde_json::json!([ + { + "checkGetters": true, + }, + ])), + None, + ), + ( + " + /** + * + */ + function quux (foo) { + + return new Promise(function (resolve, reject) { + resolve(foo); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux (foo) { + + return new Promise(function (resolve, reject) { + setTimeout(() => { + resolve(true); + }); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux (foo) { + + return new Promise(function (resolve, reject) { + foo(resolve); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + while(true) { + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + do { + resolve(true); + } + while(true) + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + if (true) { + resolve(true); + } + return; + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + if (resolve(true)) { + return; + } + return; + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + if (true) { + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + true ? resolve(true) : null; + return; + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + var a = {}; + return new Promise((resolve, reject) => { + with (a) { + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + var a = {}; + return new Promise((resolve, reject) => { + try { + resolve(true); + } catch (err) {} + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + var a = {}; + return new Promise((resolve, reject) => { + try { + } catch (err) { + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + var a = {}; + return new Promise((resolve, reject) => { + try { + } catch (err) { + } finally { + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + var a = {}; + return new Promise((resolve, reject) => { + switch (a) { + case 'abc': + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + if (true) { + resolve(); + } else { + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + for (let i = 0; i < 5 ; i++) { + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + for (const i of obj) { + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + for (const i in obj) { + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + if (true) { + return; + } else { + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + function a () { + resolve(true); + } + a(); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + return () => { + identifierForCoverage; + resolve(true); + }; + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + a || resolve(true); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + (r = resolve(true)); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + a + (resolve(true)); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + a, resolve(true); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + +resolve(); + [...resolve()]; + [...+resolve(true)]; + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise(function * (resolve, reject) { + yield resolve(true) + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise(async function (resolve, reject) { + await resolve(true) + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + someLabel: { + resolve(true); + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + var obj = { + [someKey]: 'val', + anotherKey: resolve(true) + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + var obj = { + [resolve(true)]: 'val', + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + `abc${resolve(true)}`; + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + tagTemp`abc${resolve(true)}`; + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + a.b[resolve(true)].c; + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + abc?.[resolve(true)].d?.e(resolve(true)); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + const [a = resolve(true)] = arr; + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + const {a = resolve(true)} = obj; + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + import(resolve(true)); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + class A { + method1 () { + resolve(); + } + @dec(function () { + resolve() + }) + method2 () { + resolve(true); + } + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + class A { + method1 () { + resolve(); + } + @dec(function () { + resolve(true) + }) + method2 () {} + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + const a = class { + [b] () { + resolve(); + } + method1 () { + resolve(true); + } + method2 () {} + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise((resolve, reject) => { + const a = class { + [b] () { + resolve(true); + } + } + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + export function quux () { + return new Promise((resolve, reject) => { + resolve(true); + }); + } + ", + None, + None, + ), + ( + " + /** + * + */ + function quux () { + return new Promise(); + } + ", + Some(serde_json::json!([ + { + "forceReturnsWithAsync": true, + }, + ])), + None, + ), + ( + " + /** + * + */ + async function quux () { + return new Promise(); + } + ", + Some(serde_json::json!([ + { + "forceReturnsWithAsync": true, + }, + ])), + None, + ), + ( + " + /** + * + */ + async function quux () { + return new Promise((resolve, reject) => {}); + } + ", + Some(serde_json::json!([ + { + "forceReturnsWithAsync": true, + }, + ])), + None, + ), + ( + " + /** + * Reads a test fixture. + */ + export function readFixture(path: string): void; + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** + * Reads a test fixture. + */ + export function readFixture(path: string); + ", + Some(serde_json::json!([ + { + "forceRequireReturn": true, + }, + ])), + None, + ), + ( + " + /** + * @param {array} a + */ + async function foo(a) { + return Promise.all(a); + } + ", + None, + None, + ), + ( + " + /** + * Description. + */ + export default async function demo() { + return true; + } + ", + None, + None, + ), + ]; + + Tester::new(RequireReturns::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/require_returns.snap b/crates/oxc_linter/src/snapshots/require_returns.snap new file mode 100644 index 000000000..42a2c3ab5 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/require_returns.snap @@ -0,0 +1,879 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: require_returns +--- + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux (foo) { + 6 │ │ + 7 │ │ return foo; + 8 │ ╰─▶ } + 9 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux (foo) { + 6 │ │ someLabel: { + 7 │ │ return foo; + 8 │ │ } + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:26] + 4 │ */ + 5 │ ╭─▶ const foo = () => ({ + 6 │ │ bar: 'baz' + 7 │ ╰─▶ }) + 8 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:26] + 4 │ */ + 5 │ const foo = bar=>({ bar }) + · ────────────── + 6 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:26] + 4 │ */ + 5 │ const foo = bar => bar.baz() + · ──────────────── + 6 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux (foo) { + 6 │ │ + 7 │ │ return foo; + 8 │ ╰─▶ } + 9 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ async function quux() { + 6 │ ╰─▶ } + 7 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:27] + 4 │ */ + 5 │ const quux = async function () {} + · ──────────────────── + 6 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:27] + 4 │ */ + 5 │ const quux = async () => {} + · ────────────── + 6 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:15] + 4 │ */ + 5 │ async function quux () {} + · ───────────────────────── + 6 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ ╰─▶ } + 7 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:6:20] + 5 │ */ + 6 │ ╭─▶ get name() { + 7 │ │ return this._name; + 8 │ ╰─▶ } + 9 │ } + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ async function quux () { + 6 │ ╰─▶ } + 7 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Duplicate `@returns` tags. + ╭─[require_returns.tsx:4:17] + 3 │ * @returns {undefined} + 4 │ * @returns {void} + · ──────── + 5 │ */ + ╰──── + help: Remove redundunt `@returns` tag. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux (foo) { + 6 │ │ return 'bar'; + 7 │ ╰─▶ } + 8 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:10] + 4 │ */ + 5 │ ╭─▶ async function foo(a) { + 6 │ │ return; + 7 │ ╰─▶ } + 8 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:10] + 4 │ */ + 5 │ ╭─▶ async function foo(a) { + 6 │ │ return Promise.all(a); + 7 │ ╰─▶ } + 8 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:4:19] + 3 │ /** gets bar */ + 4 │ ╭─▶ get bar() { + 5 │ │ return 0; + 6 │ ╰─▶ } + 7 │ } + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:6:25] + 5 │ */ + 6 │ ╭─▶ constructor() { + 7 │ │ return new Map(); + 8 │ ╰─▶ } + 9 │ } + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:6:20] + 5 │ */ + 6 │ ╭─▶ get Test() { + 7 │ │ return 0; + 8 │ ╰─▶ } + 9 │ } + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux (foo) { + 6 │ │ + 7 │ │ return new Promise(function (resolve, reject) { + 8 │ │ resolve(foo); + 9 │ │ }); + 10 │ ╰─▶ } + 11 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux (foo) { + 6 │ │ + 7 │ │ return new Promise(function (resolve, reject) { + 8 │ │ setTimeout(() => { + 9 │ │ resolve(true); + 10 │ │ }); + 11 │ │ }); + 12 │ ╰─▶ } + 13 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux (foo) { + 6 │ │ + 7 │ │ return new Promise(function (resolve, reject) { + 8 │ │ foo(resolve); + 9 │ │ }); + 10 │ ╰─▶ } + 11 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ while(true) { + 8 │ │ resolve(true); + 9 │ │ } + 10 │ │ }); + 11 │ ╰─▶ } + 12 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ do { + 8 │ │ resolve(true); + 9 │ │ } + 10 │ │ while(true) + 11 │ │ }); + 12 │ ╰─▶ } + 13 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ if (true) { + 8 │ │ resolve(true); + 9 │ │ } + 10 │ │ return; + 11 │ │ }); + 12 │ ╰─▶ } + 13 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ if (resolve(true)) { + 8 │ │ return; + 9 │ │ } + 10 │ │ return; + 11 │ │ }); + 12 │ ╰─▶ } + 13 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ if (true) { + 8 │ │ resolve(true); + 9 │ │ } + 10 │ │ }); + 11 │ ╰─▶ } + 12 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ true ? resolve(true) : null; + 8 │ │ return; + 9 │ │ }); + 10 │ ╰─▶ } + 11 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ var a = {}; + 7 │ │ return new Promise((resolve, reject) => { + 8 │ │ with (a) { + 9 │ │ resolve(true); + 10 │ │ } + 11 │ │ }); + 12 │ ╰─▶ } + 13 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ var a = {}; + 7 │ │ return new Promise((resolve, reject) => { + 8 │ │ try { + 9 │ │ resolve(true); + 10 │ │ } catch (err) {} + 11 │ │ }); + 12 │ ╰─▶ } + 13 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ var a = {}; + 7 │ │ return new Promise((resolve, reject) => { + 8 │ │ try { + 9 │ │ } catch (err) { + 10 │ │ resolve(true); + 11 │ │ } + 12 │ │ }); + 13 │ ╰─▶ } + 14 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ var a = {}; + 7 │ │ return new Promise((resolve, reject) => { + 8 │ │ try { + 9 │ │ } catch (err) { + 10 │ │ } finally { + 11 │ │ resolve(true); + 12 │ │ } + 13 │ │ }); + 14 │ ╰─▶ } + 15 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ var a = {}; + 7 │ │ return new Promise((resolve, reject) => { + 8 │ │ switch (a) { + 9 │ │ case 'abc': + 10 │ │ resolve(true); + 11 │ │ } + 12 │ │ }); + 13 │ ╰─▶ } + 14 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ if (true) { + 8 │ │ resolve(); + 9 │ │ } else { + 10 │ │ resolve(true); + 11 │ │ } + 12 │ │ }); + 13 │ ╰─▶ } + 14 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ for (let i = 0; i < 5 ; i++) { + 8 │ │ resolve(true); + 9 │ │ } + 10 │ │ }); + 11 │ ╰─▶ } + 12 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ for (const i of obj) { + 8 │ │ resolve(true); + 9 │ │ } + 10 │ │ }); + 11 │ ╰─▶ } + 12 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ for (const i in obj) { + 8 │ │ resolve(true); + 9 │ │ } + 10 │ │ }); + 11 │ ╰─▶ } + 12 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ if (true) { + 8 │ │ return; + 9 │ │ } else { + 10 │ │ resolve(true); + 11 │ │ } + 12 │ │ }); + 13 │ ╰─▶ } + 14 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ function a () { + 8 │ │ resolve(true); + 9 │ │ } + 10 │ │ a(); + 11 │ │ }); + 12 │ ╰─▶ } + 13 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ return () => { + 8 │ │ identifierForCoverage; + 9 │ │ resolve(true); + 10 │ │ }; + 11 │ │ }); + 12 │ ╰─▶ } + 13 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ a || resolve(true); + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ (r = resolve(true)); + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ a + (resolve(true)); + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ a, resolve(true); + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ +resolve(); + 8 │ │ [...resolve()]; + 9 │ │ [...+resolve(true)]; + 10 │ │ }); + 11 │ ╰─▶ } + 12 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise(function * (resolve, reject) { + 7 │ │ yield resolve(true) + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise(async function (resolve, reject) { + 7 │ │ await resolve(true) + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ someLabel: { + 8 │ │ resolve(true); + 9 │ │ } + 10 │ │ }); + 11 │ ╰─▶ } + 12 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ var obj = { + 8 │ │ [someKey]: 'val', + 9 │ │ anotherKey: resolve(true) + 10 │ │ } + 11 │ │ }); + 12 │ ╰─▶ } + 13 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ var obj = { + 8 │ │ [resolve(true)]: 'val', + 9 │ │ } + 10 │ │ }); + 11 │ ╰─▶ } + 12 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ `abc${resolve(true)}`; + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ tagTemp`abc${resolve(true)}`; + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ a.b[resolve(true)].c; + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ abc?.[resolve(true)].d?.e(resolve(true)); + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ const [a = resolve(true)] = arr; + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ const {a = resolve(true)} = obj; + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ import(resolve(true)); + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ class A { + 8 │ │ method1 () { + 9 │ │ resolve(); + 10 │ │ } + 11 │ │ @dec(function () { + 12 │ │ resolve() + 13 │ │ }) + 14 │ │ method2 () { + 15 │ │ resolve(true); + 16 │ │ } + 17 │ │ } + 18 │ │ }); + 19 │ ╰─▶ } + 20 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ class A { + 8 │ │ method1 () { + 9 │ │ resolve(); + 10 │ │ } + 11 │ │ @dec(function () { + 12 │ │ resolve(true) + 13 │ │ }) + 14 │ │ method2 () {} + 15 │ │ } + 16 │ │ }); + 17 │ ╰─▶ } + 18 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ const a = class { + 8 │ │ [b] () { + 9 │ │ resolve(); + 10 │ │ } + 11 │ │ method1 () { + 12 │ │ resolve(true); + 13 │ │ } + 14 │ │ method2 () {} + 15 │ │ } + 16 │ │ }); + 17 │ ╰─▶ } + 18 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ const a = class { + 8 │ │ [b] () { + 9 │ │ resolve(true); + 10 │ │ } + 11 │ │ } + 12 │ │ }); + 13 │ ╰─▶ } + 14 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:21] + 4 │ */ + 5 │ ╭─▶ export function quux () { + 6 │ │ return new Promise((resolve, reject) => { + 7 │ │ resolve(true); + 8 │ │ }); + 9 │ ╰─▶ } + 10 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ function quux () { + 6 │ │ return new Promise(); + 7 │ ╰─▶ } + 8 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ async function quux () { + 6 │ │ return new Promise(); + 7 │ ╰─▶ } + 8 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:14] + 4 │ */ + 5 │ ╭─▶ async function quux () { + 6 │ │ return new Promise((resolve, reject) => {}); + 7 │ ╰─▶ } + 8 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:17] + 4 │ */ + 5 │ export function readFixture(path: string): void; + · ───────────────────────────────────────── + 6 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:17] + 4 │ */ + 5 │ export function readFixture(path: string); + · ─────────────────────────────────── + 6 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:10] + 4 │ */ + 5 │ ╭─▶ async function foo(a) { + 6 │ │ return Promise.all(a); + 7 │ ╰─▶ } + 8 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. + + ⚠ eslint-plugin-jsdoc(require-returns): Missing JSDoc `@returns` declaration for function. + ╭─[require_returns.tsx:5:25] + 4 │ */ + 5 │ ╭─▶ export default async function demo() { + 6 │ │ return true; + 7 │ ╰─▶ } + 8 │ + ╰──── + help: Add `@returns` tag to the JSDoc comment. diff --git a/crates/oxc_linter/src/utils/jsdoc.rs b/crates/oxc_linter/src/utils/jsdoc.rs index ee916f879..4b14cbe28 100644 --- a/crates/oxc_linter/src/utils/jsdoc.rs +++ b/crates/oxc_linter/src/utils/jsdoc.rs @@ -35,7 +35,18 @@ pub fn get_function_nearest_jsdoc_node<'a, 'b>( | AstKind::PropertyDefinition(_) // /** This JSDoc should NOT found for `ArrowFunctionExpression` callback */ // function outer() { inner(() => {}) } - | AstKind::CallExpression(_) => return None, + | AstKind::CallExpression(_) + // /** This JSDoc should NOT found for `ArrowFunctionExpression` callback */ + // new Promise(() => {}) + | AstKind::NewExpression(_) => { + // /** This JSDoc should NOT found for `VariableDeclaration` */ + // export const foo = () => {} + let parent_node = ctx.nodes().parent_node(current_node.id())?; + match parent_node.kind() { + AstKind::ExportDefaultDeclaration(_) | AstKind::ExportNamedDeclaration(_) => return Some(parent_node), + _ => return None + } + }, _ => current_node = ctx.nodes().parent_node(current_node.id())?, } } diff --git a/crates/oxc_semantic/src/jsdoc/builder.rs b/crates/oxc_semantic/src/jsdoc/builder.rs index d3ba8145a..9f12c48ab 100644 --- a/crates/oxc_semantic/src/jsdoc/builder.rs +++ b/crates/oxc_semantic/src/jsdoc/builder.rs @@ -223,7 +223,6 @@ fn should_attach_jsdoc(kind: &AstKind) -> bool { | AstKind::ExportDefaultDeclaration(_) | AstKind::ExportNamedDeclaration(_) | AstKind::ImportDeclaration(_) - | AstKind::ModuleDeclaration(_) // Maybe JSX, TS related kinds should be added? )