Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions schemas/rules.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"no-implicit-declare-namespace-export",
"no-import-assertions",
"no-import-assign",
"no-import-prefix",
"no-inferrable-types",
"no-inner-declarations",
"no-invalid-regexp",
Expand Down
2 changes: 1 addition & 1 deletion schemas/tags.v1.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"enum": ["fresh", "jsr", "jsx", "react", "recommended"]
"enum": ["fresh", "jsr", "jsx", "react", "recommended", "workspace"]
}
2 changes: 2 additions & 0 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ 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_import_prefix;
pub mod no_inferrable_types;
pub mod no_inner_declarations;
pub mod no_invalid_regexp;
Expand Down Expand Up @@ -318,6 +319,7 @@ fn get_all_rules_raw() -> Vec<Box<dyn LintRule>> {
),
Box::new(no_import_assertions::NoImportAssertions),
Box::new(no_import_assign::NoImportAssign),
Box::new(no_import_prefix::NoImportPrefix),
Box::new(no_inferrable_types::NoInferrableTypes),
Box::new(no_inner_declarations::NoInnerDeclarations),
Box::new(no_invalid_regexp::NoInvalidRegexp),
Expand Down
132 changes: 132 additions & 0 deletions src/rules/no_import_prefix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use super::{Context, LintRule};
use crate::handler::{Handler, Traverse};
use crate::tags::{Tags, WORKSPACE};
use crate::Program;
use deno_ast::view::{CallExpr, Callee, Expr, ImportDecl, Lit};
use deno_ast::SourceRanged;

#[derive(Debug)]
pub struct NoImportPrefix;

const CODE: &str = "no-import-prefix";
const MESSAGE: &str =
"Inline 'npm:', 'jsr:' or 'http:' dependency not allowed";
const HINT: &str = "Add it as a dependency in a deno.json or package.json instead and reference it here via its bare specifier";

impl LintRule for NoImportPrefix {
fn tags(&self) -> Tags {
&[WORKSPACE]
}

fn code(&self) -> &'static str {
CODE
}

fn lint_program_with_ast_view(
&self,
context: &mut Context,
program: Program<'_>,
) {
NoImportPrefixHandler.traverse(program, context);
}
}

struct NoImportPrefixHandler;

impl Handler for NoImportPrefixHandler {
fn import_decl(&mut self, node: &ImportDecl, ctx: &mut Context) {
if is_non_bare(node.src.value()) {
ctx.add_diagnostic_with_hint(node.src.range(), CODE, MESSAGE, HINT);
}
}

fn call_expr(&mut self, node: &CallExpr, ctx: &mut Context) {
if let Callee::Import(_) = node.callee {
if let Some(arg) = node.args.first() {
if let Expr::Lit(Lit::Str(lit)) = arg.expr {
if is_non_bare(lit.value()) {
ctx.add_diagnostic_with_hint(arg.range(), CODE, MESSAGE, HINT);
}
}
}
}
}
}

fn is_non_bare(s: &str) -> bool {
s.starts_with("npm:")
|| s.starts_with("jsr:")
|| s.starts_with("http:")
|| s.starts_with("https:")
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn no_with_valid() {
assert_lint_ok! {
NoImportPrefix,
r#"import foo from "foo";"#,
r#"import foo from "@foo/bar";"#,
r#"import foo from "./foo";"#,
r#"import foo from "../foo";"#,
r#"import foo from "~/foo";"#,
r#"import("foo")"#,
r#"import("@foo/bar")"#,
r#"import("./foo")"#,
r#"import("../foo")"#,
r#"import("~/foo")"#,
}
}

#[test]
fn no_with_invalid() {
assert_lint_err! {
NoImportPrefix,
r#"import foo from "jsr:@foo/foo";"#: [{
col: 16,
message: MESSAGE,
hint: HINT
}],
r#"import foo from "npm:foo";"#: [{
col: 16,
message: MESSAGE,
hint: HINT
}],
r#"import foo from "http://example.com/foo";"#: [{
col: 16,
message: MESSAGE,
hint: HINT
}],
r#"import foo from "https://example.com/foo";"#: [{
col: 16,
message: MESSAGE,
hint: HINT
}],
r#"import("jsr:@foo/foo");"#: [{
col: 7,
message: MESSAGE,
hint: HINT
}],
r#"import("npm:foo");"#: [{
col: 7,
message: MESSAGE,
hint: HINT
}],
r#"import("http://example.com/foo");"#: [{
col: 7,
message: MESSAGE,
hint: HINT
}],
r#"import("https://example.com/foo");"#: [{
col: 7,
message: MESSAGE,
hint: HINT
}],
}
}
}
3 changes: 2 additions & 1 deletion src/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ pub const FRESH: Tag = Tag("fresh");
pub const JSR: Tag = Tag("jsr");
pub const REACT: Tag = Tag("react");
pub const JSX: Tag = Tag("jsx");
pub const WORKSPACE: Tag = Tag("workspace");

pub const ALL_TAGS: Tags = &[RECOMMENDED, FRESH, JSR, REACT, JSX];
pub const ALL_TAGS: Tags = &[RECOMMENDED, FRESH, JSR, REACT, JSX, WORKSPACE];
Loading