Repository: denoland/deno_lint (rust)

Issue:
require-await doesn't consider `await using` expressions
### Lint Name

`require-await`

### Code Snippet

```ts
async function a() {
  await using b = c();
}
```

### Expected Result

No lint errors.

### Actual Result

```
Async function 'a' has no 'await' expression.
Remove 'async' keyword from the function or use 'await' expression inside.
```

### Version

deno 1.37.2 (release, x86_64-unknown-linux-gnu)
v8 11.8.172.13
typescript 5.2.2


Pull request:
fix(require_await): support 'using await'
Fixes #1206

Patch:
diff --git a/docs/rules/no_import_assertions.md b/docs/rules/no_import_assertions.md
new file mode 100644
index 0000000..af80cda
--- /dev/null
+++ b/docs/rules/no_import_assertions.md
@@ -0,0 +1,19 @@
+Disallows the `assert` keyword for import attributes
+
+ES import attributes (previously called import assetions) has been changed to
+use the `with` keyword. The old syntax using `assert` is still supported, but
+deprecated.
+
+### Invalid:
+
+```typescript
+import obj from "./obj.json" assert { type: "json" };
+import("./obj2.json", { assert: { type: "json" } });
+```
+
+### Valid:
+
+```typescript
+import obj from "./obj.json" with { type: "json" };
+import("./obj2.json", { with: { type: "json" } });
+```
diff --git a/docs/rules/require_await.md b/docs/rules/require_await.md
index 576a606..de7b80b 100644
--- a/docs/rules/require_await.md
+++ b/docs/rules/require_await.md
@@ -1,8 +1,9 @@
-Disallows async functions that have no await expression
+Disallows async functions that have no await expression or await using
+declaration
 
 In general, the primary reason to use async functions is to use await
-expressions inside. If an async function has no await expression, it is most
-likely an unintentional mistake.
+expressions or await using declarations inside. If an async function has
+neither, it is most likely an unintentional mistake.
 
 ### Invalid:
 
@@ -55,6 +56,10 @@ async function f4() {
   }
 }
 
+async function f5() {
+  using = createResource();
+}
+
 // empty functions are valid
 async function emptyFunction() {}
 const emptyArrowFunction = async () => {};
diff --git a/src/rules.rs b/src/rules.rs
index ac1c0ea..9385cb2 100644
--- a/src/rules.rs
+++ b/src/rules.rs
@@ -58,6 +58,7 @@ pub mod no_fallthrough;
 pub mod no_func_assign;
 pub mod no_global_assign;
 pub mod no_implicit_declare_namespace_export;
+pub mod no_import_assertions;
 pub mod no_import_assign;
 pub mod no_inferrable_types;
 pub mod no_inner_declarations;
@@ -277,6 +278,7 @@ fn get_all_rules_raw() -> Vec<&'static dyn LintRule> {
     &no_func_assign::NoFuncAssign,
     &no_global_assign::NoGlobalAssign,
     &no_implicit_declare_namespace_export::NoImplicitDeclareNamespaceExport,
+    &no_import_assertions::NoImportAssertions,
     &no_import_assign::NoImportAssign,
     &no_inferrable_types::NoInferrableTypes,
     &no_inner_declarations::NoInnerDeclarations,
diff --git a/src/rules/explicit_module_boundary_types.rs b/src/rules/explicit_module_boundary_types.rs
index a703714..48dfa71 100644
--- a/src/rules/explicit_module_boundary_types.rs
+++ b/src/rules/explicit_module_boundary_types.rs
@@ -62,7 +62,7 @@ impl Handler for ExplicitModuleBoundaryTypesHandler {
     use ast_view::Decl;
     match &export_decl.decl {
       Decl::Class(decl) => check_class(decl.class, ctx),
-      Decl::Fn(decl) => check_fn(decl.function, ctx),
+      Decl::Fn(decl) => check_fn(decl.function, ctx, false),
       Decl::Var(var) => check_var_decl(var, ctx),
       _ => {}
     }
@@ -76,22 +76,31 @@ impl Handler for ExplicitModuleBoundaryTypesHandler {
     use ast_view::DefaultDecl;
     match &export_default_decl.decl {
       DefaultDecl::Class(expr) => check_class(expr.class, ctx),
-      DefaultDecl::Fn(expr) => check_fn(expr.function, ctx),
+      DefaultDecl::Fn(expr) => check_fn(expr.function, ctx, false),
       _ => {}
     }
   }
+
+  fn export_default_expr(
+    &mut self,
+    export_default_expr: &ast_view::ExportDefaultExpr,
+    ctx: &mut Context,
+  ) {
+    check_expr(&export_default_expr.expr, ctx);
+  }
 }
 
 fn check_class(class: &ast_view::Class, ctx: &mut Context) {
   for member in &class.body {
     if let ast_view::ClassMember::Method(method) = member {
-      check_fn(method.function, ctx);
+      let is_setter = method.inner.kind == ast_view::MethodKind::Setter;
+      check_fn(method.function, ctx, is_setter);
     }
   }
 }
 
-fn check_fn(function: &ast_view::Function, ctx: &mut Context) {
-  if function.return_type.is_none() {
+fn check_fn(function: &ast_view::Function, ctx: &mut Context, is_setter: bool) {
+  if !is_setter && function.return_type.is_none() {
     ctx.add_diagnostic_with_hint(
       function.range(),
       CODE,
@@ -162,10 +171,19 @@ fn check_pat(pat: &ast_view::Pat, ctx: &mut Context) {
   };
 }
 
+fn check_expr(expr: &ast_view::Expr, ctx: &mut Context) {
+  match expr {
+    ast_view::Expr::Fn(func) => check_fn(func.function, ctx, false),
+    ast_view::Expr::Arrow(arrow) => check_arrow(arrow, ctx),
+    ast_view::Expr::Class(class) => check_class(class.class, ctx),
+    _ => {}
+  }
+}
+
 fn check_var_decl(var: &ast_view::VarDecl, ctx: &mut Context) {
   for declarator in &var.decls {
-    if let Some(ast_view::Expr::Arrow(arrow)) = &declarator.init {
-      check_arrow(arrow, ctx);
+    if let Some(expr) = &declarator.init {
+      check_expr(expr, ctx)
     }
   }
 }
@@ -186,6 +204,7 @@ mod tests {
       "class Test { method() { return; } }",
       "export function test(arg = 1) : number { return arg;}",
       "export function test(arg :number = 1) : number { return arg;}",
+      "export class Test { set method() { return true; } }",
     };
 
     assert_lint_ok! {
@@ -247,6 +266,36 @@ mod tests {
         message: ExplicitModuleBoundaryTypesMessage::MissingRetType,
         hint: ExplicitModuleBoundaryTypesHint::AddRetType,
       }],
+      r#"export default () => true;"#: [
+      {
+        col: 15,
+        message: ExplicitModuleBoundaryTypesMessage::MissingRetType,
+        hint: ExplicitModuleBoundaryTypesHint::AddRetType,
+      }],
+      r#"export default function() { return true; }"#: [
+      {
+        col: 15,
+        message: ExplicitModuleBoundaryTypesMessage::MissingRetType,
+        hint: ExplicitModuleBoundaryTypesHint::AddRetType,
+      }],
+      r#"export default function named() { return true; }"#: [
+      {
+        col: 15,
+        message: ExplicitModuleBoundaryTypesMessage::MissingRetType,
+        hint: ExplicitModuleBoundaryTypesHint::AddRetType,
+      }],
+      r#"export default class { method() { return; } }"#: [
+      {
+        col: 23,
+        message: ExplicitModuleBoundaryTypesMessage::MissingRetType,
+        hint: ExplicitModuleBoundaryTypesHint::AddRetType,
+      }],
+      r#"export default class Named { method() { return; } }"#: [
+      {
+        col: 29,
+        message: ExplicitModuleBoundaryTypesMessage::MissingRetType,
+        hint: ExplicitModuleBoundaryTypesHint::AddRetType,
+      }],
     }
   }
 }
diff --git a/src/rules/no_import_assertions.rs b/src/rules/no_import_assertions.rs
new file mode 100644
index 0000000..4b2e835
--- /dev/null
+++ b/src/rules/no_import_assertions.rs
@@ -0,0 +1,151 @@
+// Copyright 2020-2023 the Deno authors. All rights reserved. MIT license.
+use super::{Context, LintRule};
+use crate::handler::{Handler, Traverse};
+use crate::Program;
+use deno_ast::swc::parser::token::{IdentLike, KnownIdent, Token, Word};
+use deno_ast::view as ast_view;
+use deno_ast::{SourceRanged, SourceRangedForSpanned};
+use if_chain::if_chain;
+
+#[derive(Debug)]
+pub struct NoImportAssertions;
+
+const CODE: &str = "no-import-assertions";
+const MESSAGE: &str =
+  "The `assert` keyword is deprecated for import attributes";
+const HINT: &str = "Instead use the `with` keyword";
+
+impl LintRule for NoImportAssertions {
+  fn tags(&self) -> &'static [&'static str] {
+    &["recommended"]
+  }
+
+  fn code(&self) -> &'static str {
+    CODE
+  }
+
+  fn lint_program_with_ast_view(
+    &self,
+    context: &mut Context,
+    program: Program<'_>,
+  ) {
+    NoImportAssertionsHandler.traverse(program, context);
+  }
+
+  #[cfg(feature = "docs")]
+  fn docs(&self) -> &'static str {
+    include_str!("../../docs/rules/no_import_assertions.md")
+  }
+}
+
+struct NoImportAssertionsHandler;
+
+impl Handler for NoImportAssertionsHandler {
+  fn import_decl(
+    &mut self,
+    import_decl: &ast_view::ImportDecl,
+    ctx: &mut Context,
+  ) {
+    if_chain! {
+      if let Some(with) = import_decl.with;
+      if let Some(prev_token_and_span) = with.start().previous_token_fast(ctx.program());
+      if let Token::Word(word) = &prev_token_and_span.token;
+      if let Word::Ident(ident_like) = word;
+      if let IdentLike::Known(known_ident) = ident_like;
+      if matches!(known_ident, KnownIdent::Assert);
+      then {
+        ctx.add_diagnostic_with_hint(
+          prev_token_and_span.span.range(),
+          CODE,
+          MESSAGE,
+          HINT,
+        );
+      }
+    }
+  }
+
+  fn call_expr(&mut self, call_expr: &ast_view::CallExpr, ctx: &mut Context) {
+    if_chain! {
+      if matches!(call_expr.callee, ast_view::Callee::Import(_));
+      if let Some(expr_or_spread) = call_expr.args.get(1);
+      if let ast_view::Expr::Object(object_lit) = expr_or_spread.expr;
+      then {
+        for prop_or_spread in object_lit.props.iter() {
+          if_chain! {
+            if let ast_view::PropOrSpread::Prop(prop) = prop_or_spread;
+            if let ast_view::Prop::KeyValue(key_value_prop) = prop;
+            then {
+              match key_value_prop.key {
+                ast_view::PropName::Ident(ident) => {
+                  if ident.sym().as_ref() == "assert" {
+                    ctx.add_diagnostic_with_hint(
+                      ident.range(),
+                      CODE,
+                      MESSAGE,
+                      HINT,
+                    );
+                  }
+                },
+                ast_view::PropName::Str(str) => {
+                  if str.value().as_ref() == "assert" {
+                    ctx.add_diagnostic_with_hint(
+                      str.range(),
+                      CODE,
+                      MESSAGE,
+                      HINT,
+                    );
+                  }
+                }
+                _ => (),
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+
+  #[test]
+  fn no_import_assertions_valid() {
+    assert_lint_ok! {
+      NoImportAssertions,
+      r#"import foo from './foo.js';"#,
+      r#"import foo from './foo.js' with { bar: 'bar' };"#,
+      r#"import('./foo.js');"#,
+      r#"import('./foo.js', { with: { bar: 'bar' } });"#,
+      r#"import('./foo.js', { "with": { bar: 'bar' } });"#,
+    };
+  }
+
+  #[test]
+  fn no_import_assertions_invalid() {
+    assert_lint_err! {
+      NoImportAssertions,
+      MESSAGE,
+      HINT,
+      r#"import foo from './foo.js' assert { bar: 'bar' };"#: [
+        {
+          line: 1,
+          col: 27,
+        },
+      ],
+      r#"import('./foo.js', { assert: { bar: 'bar' } });"#: [
+        {
+          line: 1,
+          col: 21,
+        },
+      ],
+      r#"import('./foo.js', { "assert": { bar: 'bar' } });"#: [
+        {
+          line: 1,
+          col: 21,
+        },
+      ],
+    };
+  }
+}
diff --git a/src/rules/no_var.rs b/src/rules/no_var.rs
index 9bac22e..4110f18 100644
--- a/src/rules/no_var.rs
+++ b/src/rules/no_var.rs
@@ -2,8 +2,8 @@
 use super::{Context, LintRule};
 use crate::handler::{Handler, Traverse};
 use crate::Program;
-use deno_ast::view::{VarDecl, VarDeclKind};
-use deno_ast::SourceRanged;
+use deno_ast::view::{NodeTrait, VarDecl, VarDeclKind};
+use deno_ast::SourceRangedForSpanned;
 
 #[derive(Debug)]
 pub struct NoVar;
@@ -39,7 +39,8 @@ struct NoVarHandler;
 impl Handler for NoVarHandler {
   fn var_decl(&mut self, var_decl: &VarDecl, ctx: &mut Context) {
     if var_decl.decl_kind() == VarDeclKind::Var {
-      ctx.add_diagnostic(var_decl.range(), CODE, MESSAGE);
+      let range = var_decl.tokens().first().unwrap().range();
+      ctx.add_diagnostic(range, CODE, MESSAGE);
     }
   }
 }
diff --git a/src/rules/require_await.rs b/src/rules/require_await.rs
index ebd2aad..1cca551 100644
--- a/src/rules/require_await.rs
+++ b/src/rules/require_await.rs
@@ -17,22 +17,34 @@ const CODE: &str = "require-await";
 
 #[derive(Display)]
 enum RequireAwaitMessage {
-  #[display(fmt = "Async function '{}' has no 'await' expression.", _0)]
+  #[display(
+    fmt = "Async function '{}' has no 'await' expression or 'await using' declaration.",
+    _0
+  )]
   Function(String),
-  #[display(fmt = "Async function has no 'await' expression.")]
+  #[display(
+    fmt = "Async function has no 'await' expression or 'await using' declaration."
+  )]
   AnonymousFunction,
-  #[display(fmt = "Async arrow function has no 'await' expression.")]
+  #[display(
+    fmt = "Async arrow function has no 'await' expression or 'await using' declaration."
+  )]
   ArrowFunction,
-  #[display(fmt = "Async method '{}' has no 'await' expression.", _0)]
+  #[display(
+    fmt = "Async method '{}' has no 'await' expression or 'await using' declaration.",
+    _0
+  )]
   Method(String),
-  #[display(fmt = "Async method has no 'await' expression.")]
+  #[display(
+    fmt = "Async method has no 'await' expression or 'await using' declaration."
+  )]
   AnonymousMethod,
 }
 
 #[derive(Display)]
 enum RequireAwaitHint {
   #[display(
-    fmt = "Remove 'async' keyword from the function or use 'await' expression inside."
+    fmt = "Remove 'async' keyword from the function or use 'await' expression or 'await using' declaration inside."
   )]
   RemoveOrUse,
 }
@@ -290,18 +302,30 @@ impl Handler for FunctionHandler {
       info.has_await = true;
     }
   }
+
   fn for_of_stmt(
     &mut self,
     for_of_stmt: &deno_ast::view::ForOfStmt,
     _ctx: &mut Context,
   ) {
-    for_of_stmt.tokens_fast(_ctx.program());
     if for_of_stmt.is_await() {
       if let Some(info) = self.function_info.as_mut() {
         info.has_await = true;
       }
     }
   }
+
+  fn using_decl(
+    &mut self,
+    using_decl: &deno_ast::view::UsingDecl,
+    _ctx: &mut Context,
+  ) {
+    if using_decl.is_await() {
+      if let Some(info) = self.function_info.as_mut() {
+        info.has_await = true;
+      }
+    }
+  }
 }
 
 fn process_function<'a, N>(
@@ -368,6 +392,9 @@ mod tests {
       // for-await-of
       "async function foo() { for await (x of xs); }",
 
+      // using-await
+      "async function foo() { await using foo = doSomething(); }",
+
       // global await
       "await foo()",
       r#"
diff --git a/www/static/docs.json b/www/static/docs.json
index 8e6b451..bf41a50 100644
--- a/www/static/docs.json
+++ b/www/static/docs.json
@@ -339,6 +339,13 @@
     "docs": "Disallows the use of implicit exports in [\"ambient\" namespaces].\n\nTypeScript implicitly export all members of an [\"ambient\" namespaces], except\nwhether a named export is present.\n\n[\"ambient\" namespaces]: https://www.typescriptlang.org/docs/handbook/namespaces.html#ambient-namespaces\n\n### Invalid:\n\n```ts\n// foo.ts or foo.d.ts\ndeclare namespace ns {\n  interface ImplicitlyExported {}\n  export type Exported = true;\n}\n```\n\n### Valid:\n\n```ts\n// foo.ts or foo.d.ts\ndeclare namespace ns {\n  interface NonExported {}\n  export {};\n}\n\ndeclare namespace ns {\n  interface Exported {}\n  export { Exported };\n}\n\ndeclare namespace ns {\n  export interface Exported {}\n}\n```\n",
     "tags": []
   },
+  {
+    "code": "no-import-assertions",
+    "docs": "Disallows the `assert` keyword for import attributes\n\nES import attributes (previously called import assetions) has been changed to\nuse the `with` keyword. The old syntax using `assert` is still supported, but\ndeprecated.\n\n### Invalid:\n\n```typescript\nimport obj from \"./obj.json\" assert { type: \"json\" };\nimport(\"./obj2.json\", { assert: { type: \"json\" } });\n```\n\n### Valid:\n\n```typescript\nimport obj from \"./obj.json\" with { type: \"json\" };\nimport(\"./obj2.json\", { with: { type: \"json\" } });\n```\n",
+    "tags": [
+      "recommended"
+    ]
+  },
   {
     "code": "no-import-assign",
     "docs": "Disallows reassignment of imported module bindings\n\nES module import bindings should be treated as read-only since modifying them\nduring code execution will likely result in runtime errors. It also makes for\npoor code readability and difficult maintenance.\n\n### Invalid:\n\n```typescript\nimport defaultMod, { namedMod } from \"./mod.js\";\nimport * as modNameSpace from \"./mod2.js\";\n\ndefaultMod = 0;\nnamedMod = true;\nmodNameSpace.someExportedMember = \"hello\";\nmodNameSpace = {};\n```\n\n### Valid:\n\n```typescript\nimport defaultMod, { namedMod } from \"./mod.js\";\nimport * as modNameSpace from \"./mod2.js\";\n\n// properties of bound imports may be set\ndefaultMod.prop = 1;\nnamedMod.prop = true;\nmodNameSpace.someExportedMember.prop = \"hello\";\n```\n",
@@ -596,7 +603,7 @@
   },
   {
     "code": "require-await",
-    "docs": "Disallows async functions that have no await expression\n\nIn general, the primary reason to use async functions is to use await\nexpressions inside. If an async function has no await expression, it is most\nlikely an unintentional mistake.\n\n### Invalid:\n\n```typescript\nasync function f1() {\n  doSomething();\n}\n\nconst f2 = async () => {\n  doSomething();\n};\n\nconst f3 = async () => doSomething();\n\nconst obj = {\n  async method() {\n    doSomething();\n  },\n};\n\nclass MyClass {\n  async method() {\n    doSomething();\n  }\n}\n```\n\n### Valid:\n\n```typescript\nawait asyncFunction();\n\nfunction normalFunction() {\n  doSomething();\n}\n\nasync function f1() {\n  await asyncFunction();\n}\n\nconst f2 = async () => {\n  await asyncFunction();\n};\n\nconst f3 = async () => await asyncFunction();\n\nasync function f4() {\n  for await (const num of asyncIterable) {\n    console.log(num);\n  }\n}\n\n// empty functions are valid\nasync function emptyFunction() {}\nconst emptyArrowFunction = async () => {};\n\n// generators are also valid\nasync function* gen() {\n  console.log(42);\n}\n```\n",
+    "docs": "Disallows async functions that have no await expression or await using\ndeclaration\n\nIn general, the primary reason to use async functions is to use await\nexpressions or await using declarations inside. If an async function has\nneither, it is most likely an unintentional mistake.\n\n### Invalid:\n\n```typescript\nasync function f1() {\n  doSomething();\n}\n\nconst f2 = async () => {\n  doSomething();\n};\n\nconst f3 = async () => doSomething();\n\nconst obj = {\n  async method() {\n    doSomething();\n  },\n};\n\nclass MyClass {\n  async method() {\n    doSomething();\n  }\n}\n```\n\n### Valid:\n\n```typescript\nawait asyncFunction();\n\nfunction normalFunction() {\n  doSomething();\n}\n\nasync function f1() {\n  await asyncFunction();\n}\n\nconst f2 = async () => {\n  await asyncFunction();\n};\n\nconst f3 = async () => await asyncFunction();\n\nasync function f4() {\n  for await (const num of asyncIterable) {\n    console.log(num);\n  }\n}\n\nasync function f5() {\n  using = createResource();\n}\n\n// empty functions are valid\nasync function emptyFunction() {}\nconst emptyArrowFunction = async () => {};\n\n// generators are also valid\nasync function* gen() {\n  console.log(42);\n}\n```\n",
     "tags": [
       "recommended"
     ]


Test patch:
diff --git a/examples/dlint/testdata/issue1145_no_trailing_newline.out b/examples/dlint/testdata/issue1145_no_trailing_newline.out
index 6fda401..45fe04a 100644
--- a/examples/dlint/testdata/issue1145_no_trailing_newline.out
+++ b/examples/dlint/testdata/issue1145_no_trailing_newline.out
@@ -15,7 +15,7 @@ no-var
    ,-[issue1145_no_trailing_newline.ts:3:1]
  3 | ---*/
  4 | var base
-   : ^^^^^^^^
+   : ^^^
    `----
   help: https://lint.deno.land/#no-var