Skip to content

Commit

Permalink
feat: add no-import-assertions rule (#1209)
Browse files Browse the repository at this point in the history
  • Loading branch information
petamoriken authored Nov 20, 2023
1 parent fb02855 commit 0bebdcd
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 0 deletions.
19 changes: 19 additions & 0 deletions docs/rules/no_import_assertions.md
Original file line number Diff line number Diff line change
@@ -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" } });
```
2 changes: 2 additions & 0 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
151 changes: 151 additions & 0 deletions src/rules/no_import_assertions.rs
Original file line number Diff line number Diff line change
@@ -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,
},
],
};
}
}
7 changes: 7 additions & 0 deletions www/static/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 0bebdcd

Please sign in to comment.