Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add no-import-assertions rule #1209

Merged
merged 3 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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