-
-
Notifications
You must be signed in to change notification settings - Fork 337
/
no_unresolved.rs
141 lines (129 loc) · 4.95 KB
/
no_unresolved.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use std::path::{Component, Path};
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_resolver::NODEJS_BUILTINS;
use oxc_span::SourceType;
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,
suspicious
);
impl Rule for NoUnresolved {
fn run_once(&self, ctx: &LintContext<'_>) {
let module_record = ctx.semantic().module_record();
for (specifier, requested_modules) in &module_record.requested_modules {
if module_record.loaded_modules.contains_key(specifier) {
continue;
}
let specifier_path = Path::new(specifier.as_str());
// skip if the extension is not supported
if specifier_path.extension().is_some()
&& SourceType::from_path(specifier_path).is_err()
{
continue;
}
// skip node.js builtin modules
if specifier.starts_with("node:")
|| (specifier_path
.components()
.next()
.is_some_and(|c| matches!(c, Component::Normal(_)))
&& NODEJS_BUILTINS.binary_search(&specifier.as_str()).is_ok())
{
continue;
}
for requested_module in requested_modules {
// ignore type-only imports and exports
if requested_module.is_type() {
continue;
}
ctx.diagnostic(NoUnresolvedDiagnostic(requested_module.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';",
r"import fs from 'fs';",
r"import fs from 'node: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")"#,
// TODO: commonjs: false
// 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)",
// Unsupported extensions
r#"import "./test.png""#,
// ignore type-only imports and exports
r"import type { m } from 'mod'",
r"export type * from 'mod'",
];
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.ts")
.with_import_plugin(true)
.test_and_snapshot();
}