Skip to content

Commit

Permalink
feat(linter/import) implement no_unused_modules rule (#2720)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunqing committed Mar 18, 2024
1 parent 938deba commit 64db564
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 14 deletions.
108 changes: 94 additions & 14 deletions crates/oxc_linter/src/rules/import/no_unused_modules.rs
@@ -1,42 +1,122 @@
use oxc_diagnostics::{
miette::{self, Diagnostic},
miette::{self, diagnostic, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::{CompactStr, Span};
use oxc_span::Span;

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

#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-import(namespace): ")]
#[diagnostic(severity(warning), help(""))]
struct NoUnusedModulesDiagnostic(CompactStr, #[label] pub Span);
enum NoUnusedModulesDiagnostic {
#[error("eslint-plugin-import(no-unused-modules): No exports found")]
#[diagnostic(severity(warning))]
NoExportsFound(#[label] Span),
}

/// <https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/namespace.md>
/// <https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-unused-modules.md>
#[derive(Debug, Default, Clone)]
pub struct NoUnusedModules;
pub struct NoUnusedModules {
missing_exports: bool,
unused_exports: bool,
}

declare_oxc_lint!(
/// ### What it does
/// TODO
///
/// Reports:
/// * modules without any exports
/// * individual exports not being statically imported or requireed from other modules in the same project
/// * dynamic imports are supported if argument is a literal string
///
NoUnusedModules,
nursery
);

impl Rule for NoUnusedModules {
fn run_once(&self, _ctx: &LintContext<'_>) {}
fn from_configuration(value: serde_json::Value) -> Self {
Self {
missing_exports: value
.get("missingExports")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false),
unused_exports: value
.get("unusedExports")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false),
}
}

fn run_once(&self, ctx: &LintContext<'_>) {
let module_record = ctx.semantic().module_record();
if self.missing_exports && module_record.local_export_entries.is_empty() {
ctx.diagnostic(NoUnusedModulesDiagnostic::NoExportsFound(Span::new(0, 0)));
}
if self.unused_exports {
// TODO: implement unused exports
}
}
}

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

let missing_exports_options = json!({
"missingExports": true,
});

let pass = vec![
("export default function noOptions() {}", None),
("export default () => 1", Some(missing_exports_options.clone())),
("const a = 1; export { a }", Some(missing_exports_options.clone())),
("function a() { return true }; export { a }", Some(missing_exports_options.clone())),
("const a = 1; const b = 2; export { a, b }", Some(missing_exports_options.clone())),
("const a = 1; export default a", Some(missing_exports_options.clone())),
("export class Foo {}", Some(missing_exports_options.clone())),
("export const [foobar] = [];", Some(missing_exports_options.clone())),
("export const [foobar] = foobarFactory();", Some(missing_exports_options.clone())),
(
"export default function NewComponent () {
return 'I am new component'
}",
Some(missing_exports_options.clone()),
),
(
"export default function NewComponent () {
return 'I am new component'
}",
Some(missing_exports_options.clone()),
),
];

let fail = vec![
("const a = 1", Some(missing_exports_options.clone())),
("/* const a = 1 */", Some(missing_exports_options.clone())),
];

Tester::new(NoUnusedModules::NAME, pass, fail)
.change_rule_path("missing-exports.js")
.with_import_plugin(true)
.test_and_snapshot();

// TODO: support unused exports
// let unused_exports_options = json!({
// "unusedExports": true,
// "src": ["./no-unused-modules/**/*.js"],
// "ignoreExports": ["./no-unused-modules/*ignored*.js"],
// });

// let pass = vec![];
// let pass = vec![
// ("export default function noOptions() {}", None),
// ("export default () => 1", Some(unused_exports_options)),
// ];

// let fail = vec![];

// Tester::new(NoUnusedModules::NAME, pass, fail)
// .change_rule_path("index.js")
// .with_import_plugin(true)
// .test_and_snapshot();
// .change_rule_path("unused-exports.js")
// .with_import_plugin(true)
// .test_and_snapshot();
}
15 changes: 15 additions & 0 deletions crates/oxc_linter/src/snapshots/no_unused_modules.snap
@@ -0,0 +1,15 @@
---
source: crates/oxc_linter/src/tester.rs
expression: no_unused_modules
---
eslint-plugin-import(no-unused-modules): No exports found
╭─[missing-exports.js:1:1]
1const a = 1
· ▲
╰────

eslint-plugin-import(no-unused-modules): No exports found
╭─[missing-exports.js:1:1]
1/* const a = 1 */
· ▲
╰────

0 comments on commit 64db564

Please sign in to comment.