Skip to content

Commit

Permalink
feat(linter): eslint-plugin-import/no_unresolved
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Feb 23, 2024
1 parent 2714a32 commit 2f791d1
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod import {
pub mod no_named_as_default;
pub mod no_named_as_default_member;
pub mod no_self_import;
pub mod no_unresolved;
pub mod no_unused_modules;
}

Expand Down Expand Up @@ -557,6 +558,7 @@ oxc_macros::declare_all_lint_rules! {
import::no_named_as_default,
import::no_named_as_default_member,
import::no_self_import,
import::no_unresolved,
import::no_unused_modules,
jsx_a11y::alt_text,
jsx_a11y::anchor_has_content,
Expand Down
108 changes: 108 additions & 0 deletions crates/oxc_linter/src/rules/import/no_unresolved.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule};

#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-import(no-unresolved): Ensure imports point to a file/module that can be resolved")]
#[diagnostic(severity(warning))]
struct NoUnresolvedDiagnostic(#[label] pub Span);

/// <https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-unresolved.md>
#[derive(Debug, Default, Clone)]
pub struct NoUnresolved;

declare_oxc_lint!(
/// ### What it does
///
/// Ensures an imported module can be resolved to a module on the local filesystem.
NoUnresolved,
nursery
);

impl Rule for NoUnresolved {
fn run_once(&self, ctx: &LintContext<'_>) {
let module_record = ctx.semantic().module_record();

for (specifier, spans) in &module_record.requested_modules {
if !module_record.loaded_modules.contains_key(specifier) {
for span in spans {
ctx.diagnostic(NoUnresolvedDiagnostic(*span));
}
}
}
}
}

#[test]
fn test() {
use crate::tester::Tester;

let pass = vec![
// TODO: handle malformed file?
// r#"import "./malformed.js""#,
r#"import foo from "./bar";"#,
r#"import bar from './bar.js';"#,
r#"import {someThing} from './test-module';"#,
// TODO: exclude nodejs builtin modules
// r#"import fs from 'fs';"#,
r#"import('fs');"#,
r#"import('fs');"#,
r#"import * as foo from "a""#,
r#"export { foo } from "./bar""#,
r#"export * from "./bar""#,
r#"let foo; export { foo }"#,
r#"export * as bar from "./bar""#,
// parser: parsers.BABEL_OLD
// r#"export bar from "./bar""#,
r#"import foo from "./jsx/MyUnCoolComponent.jsx""#,
r#"var foo = require("./bar")"#,
r#"require("./bar")"#,
r#"require("./does-not-exist")"#,
r#"require("./does-not-exist")"#,
r#"require(["./bar"], function (bar) {})"#,
r#"define(["./bar"], function (bar) {})"#,
r#"require(["./does-not-exist"], function (bar) {})"#,
r#"define(["require", "exports", "module"], function (r, e, m) { })"#,
r#"require(["./does-not-exist"])"#,
r#"define(["./does-not-exist"], function (bar) {})"#,
r#"require("./does-not-exist", "another arg")"#,
r#"proxyquire("./does-not-exist")"#,
r#"(function() {})("./does-not-exist")"#,
r#"define([0, foo], function (bar) {})"#,
r#"require(0)"#,
r#"require(foo)"#,
];

let fail = vec![
r#"import reallyfake from "./reallyfake/module""#,
r#"import bar from './baz';"#,
r#"import bar from './baz';"#,
r#"import bar from './empty-folder';"#,
r#"import { DEEP } from 'in-alternate-root';"#,
// TODO: dynamic import
// r#"import('in-alternate-root').then(function({DEEP}) {});"#,
r#"export { foo } from "./does-not-exist""#,
r#"export * from "./does-not-exist""#,
// TODO: dynamic import
// r#"import('in-alternate-root').then(function({DEEP}) {});"#,
r#"export * as bar from "./does-not-exist""#,
r#"export bar from "./does-not-exist""#,
r#"var bar = require("./baz")"#,
// TODO: require expression
// r#"require("./baz")"#,
// TODO: amd
// r#"require(["./baz"], function (bar) {})"#,
// r#"define(["./baz"], function (bar) {})"#,
// r#"define(["./baz", "./bar", "./does-not-exist"], function (bar) {})"#,
];

Tester::new(NoUnresolved::NAME, pass, fail)
.change_rule_path("index.js")
.with_import_plugin(true)
.test_and_snapshot();
}
65 changes: 65 additions & 0 deletions crates/oxc_linter/src/snapshots/no_unresolved.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
source: crates/oxc_linter/src/tester.rs
expression: no_unresolved
---

eslint-plugin-import(no-unresolved): Ensure imports point to a file/module that can be resolved
╭─[index.js:1:24]
1import reallyfake from "./reallyfake/module"
· ─────────────────────
╰────

eslint-plugin-import(no-unresolved): Ensure imports point to a file/module that can be resolved
╭─[index.js:1:17]
1import bar from './baz';
· ───────
╰────

eslint-plugin-import(no-unresolved): Ensure imports point to a file/module that can be resolved
╭─[index.js:1:17]
1import bar from './baz';
· ───────
╰────

eslint-plugin-import(no-unresolved): Ensure imports point to a file/module that can be resolved
╭─[index.js:1:17]
1import bar from './empty-folder';
· ────────────────
╰────

eslint-plugin-import(no-unresolved): Ensure imports point to a file/module that can be resolved
╭─[index.js:1:22]
1import { DEEP } from 'in-alternate-root';
· ───────────────────
╰────

eslint-plugin-import(no-unresolved): Ensure imports point to a file/module that can be resolved
╭─[index.js:1:21]
1export { foo } from "./does-not-exist"
· ──────────────────
╰────

eslint-plugin-import(no-unresolved): Ensure imports point to a file/module that can be resolved
╭─[index.js:1:15]
1export * from "./does-not-exist"
· ──────────────────
╰────

eslint-plugin-import(no-unresolved): Ensure imports point to a file/module that can be resolved
╭─[index.js:1:22]
1export * as bar from "./does-not-exist"
· ──────────────────
╰────

× Unexpected token
╭─[index.js:1:8]
1export bar from "./does-not-exist"
· ───
╰────

eslint-plugin-import(no-unresolved): Ensure imports point to a file/module that can be resolved
╭─[index.js:1:19]
1var bar = require("./baz")
· ───────
╰────

0 comments on commit 2f791d1

Please sign in to comment.