use oxc_ast::{
    AstKind,
    ast::{BindingPattern, Expression, MethodDefinitionKind},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::Deserialize;

use crate::{
    context::LintContext,
    rule::{DefaultRuleConfig, Rule},
    utils::{
        get_function_nearest_jsdoc_node, is_duplicated_special_tag, is_missing_special_tag,
        should_ignore_as_avoid, should_ignore_as_custom_skip, should_ignore_as_internal,
        should_ignore_as_private,
    },
};

fn missing_returns_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Missing JSDoc `@returns` declaration for function.")
        .with_help("Add a `@returns` tag to the JSDoc comment.")
        .with_label(span)
}

fn duplicate_returns_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Duplicate `@returns` tags.")
        .with_help("Remove the redundant `@returns` tag.")
        .with_label(span)
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct RequireReturns(Box<RequireReturnsConfig>);

#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
struct RequireReturnsConfig {
    /// Tags that exempt functions from requiring `@returns`.
    exempted_by: Vec<String>,
    /// Whether to check constructor methods.
    check_constructors: bool,
    /// Whether to check getter methods.
    check_getters: bool,
    /// Whether to require a `@returns` tag even if the function doesn't return a value.
    force_require_return: bool,
    /// Whether to require a `@returns` tag for async functions.
    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,
        }
    }
}

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.
    ///
    /// ### Examples
    ///
    /// Examples of **incorrect** code for this rule:
    /// ```javascript
    /// /** Foo. */
    /// function quux () { return foo; }
    ///
    /// /**
    ///  * @returns Foo!
    ///  * @returns Foo?
    ///  */
    /// function quux () { return foo; }
    /// ```
    ///
    /// Examples of **correct** code for this rule:
    /// ```javascript
    /// /** @returns Foo. */
    /// function quux () { return foo; }
    /// ```
    RequireReturns,
    jsdoc,
    pedantic,
    pending,
    config = RequireReturnsConfig,
    version = "0.4.0",
    short_description = "Requires that return statements are documented with `@returns`.",
);

impl Rule for RequireReturns {
    fn from_configuration(value: serde_json::Value) -> Result<Self, serde_json::error::Error> {
        serde_json::from_value::<DefaultRuleConfig<Self>>(value).map(DefaultRuleConfig::into_inner)
    }

    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() {
            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 !matches!(current_node.kind(), AstKind::Program(_)) {
                        let parent_node = ctx.nodes().parent_node(current_node.id());
                        match parent_node.kind() {
                            AstKind::Function(_) | AstKind::ArrowFunctionExpression(_) => {
                                // Ignore `return;`
                                let Some(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;
                            }
                        }
                    }
                }
                _ => {}
            }
        }

        // 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(jsdoc) = ctx.jsdoc().get_one_by_node(ctx.nodes(), func_def_node) else {
                continue;
            };

            let config = &self.0;
            let settings = &ctx.settings().jsdoc;
            // If JSDoc is found but safely ignored, skip
            if should_ignore_as_custom_skip(&jsdoc)
                || should_ignore_as_avoid(&jsdoc, settings, &config.exempted_by)
                || should_ignore_as_private(&jsdoc, settings)
                || should_ignore_as_internal(&jsdoc, settings)
            {
                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 resolved_returns_tag_name = settings.resolve_tag_name("returns");

            if is_missing_special_tag(jsdoc.tags(), resolved_returns_tag_name) {
                ctx.diagnostic(missing_returns_diagnostic(*func_span));
                continue;
            }

            if let Some(span) = is_duplicated_special_tag(jsdoc.tags(), resolved_returns_tag_name) {
                ctx.diagnostic(duplicate_returns_diagnostic(span));
            }
        }
    }
}

fn default_exempted_by() -> Vec<String> {
    vec!["inheritdoc".to_string()]
}

/// - 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<bool> {
    // `return new Promise(...)`
    if let Expression::NewExpression(new_expr) = expr
        && 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 {
                BindingPattern::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.scoping().get_resolved_references(ident.symbol_id()) {
                    // Check if `resolve` is called with value
                    if let AstKind::CallExpression(call_expr) =
                        ctx.nodes().parent_kind(resolve_ref.node_id())
                        && !call_expr.arguments.is_empty()
                    {
                        return Some(true);
                    }
                }
                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,
        ),
        (
            "
			          /**
			           * @param md
			           */
			          const component = (md) => {
			            md.renderer.rules.fence = (...args) => {
			              const [tokens, index] = args;
			              return tokens[index];
			            };
			          };
			      ",
            None,
            None,
        ),
        (
            "
                      /**
                       * Random float in [min, max).
                       * @param {number} min - Minimum float value.
                       * @param {number} max - Maximum float value.
                       * @returns {number} Random float in [min, max).
                       */
                      function randomRange(min, max) {
                        return min + Math.random() * (max - min);
                      }
                  ",
            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 to get the order index of a property name based on compiled regex patterns.
                       * @callback OrderIndexGetter
                       * @param {string} propName Property name to check.
                       * @returns {number} Order index based on matching pattern, or length of patterns if no match.
                       */

                      /**
                       * Create a function that returns the order index of a property name based on
                       * compiled regex patterns. Returns pattern array length if no match found.
                       * @param {RegExp[]} compiledOrder - Compiled regex patterns in priority order.
                       * @returns {OrderIndexGetter} Function that takes a property name and returns its order index.
                       */
                      const createOrderIndexGetter = (compiledOrder) => (propName) => {
                        for (let i = 0; i < compiledOrder.length; i += 1) {
                          if (compiledOrder[i].test(propName)) {
                            return i;
                          }
                        }
                        return compiledOrder.length;
                      };
                  ",
            None,
            None,
        ),
        (
            "
                      /**
                       * Earlier doc.
                       * @returns {number} earlier
                       */

                      /**
                       * Function doc.
                       * @returns {number} actual
                       */
                      function quux () {
                        return 1;
                      }",
            None,
            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,
        ),
        (
            "
			          /**
                       * Has single quote ' in comment.
			           * @returns Foo.
			           */
			          function quux () {
			            return foo;
			          }
			      ",
            None,
            None,
        ),
    ];

    let fail = vec![
        (
            "
			          /**
			           * fail(no @returns)
			           */
			          function quux (foo) {

			            return foo;
			          }
			      ",
            None,
            None,
        ),
        (
            "
                      /**
                       * @callback Getter
                       * @returns {number} callback return
                       */

                      /**
                       * Function doc missing returns.
                       */
                      function quux () {
                        return 1;
                      }",
            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, RequireReturns::PLUGIN, pass, fail).test_and_snapshot();
}
