Skip to content

Commit

Permalink
feat(linter): eslint-plugin-import/no-self-import
Browse files Browse the repository at this point in the history
 closes #440 #441
  • Loading branch information
Boshen committed Sep 9, 2023
1 parent 4e5f63a commit 5e1ddbc
Show file tree
Hide file tree
Showing 14 changed files with 253 additions and 52 deletions.
4 changes: 3 additions & 1 deletion crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
/// <https://github.com/import-js/eslint-plugin-import>
mod import {
pub mod named;
pub mod no_self_import;
}

mod deepscan {
Expand Down Expand Up @@ -202,5 +203,6 @@ oxc_macros::declare_all_lint_rules! {
jest::no_interpolation_in_snapshots,
unicorn::no_instanceof_array,
unicorn::no_unnecessary_await,
import::named
import::named,
import::no_self_import
}
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/import/named.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ fn test() {
];

Tester::new_without_config(Named::NAME, pass, fail)
.with_rule_path_extension("js")
.change_rule_path("index.js")
.with_import_plugin(true)
.test_and_snapshot();
}
116 changes: 116 additions & 0 deletions crates/oxc_linter/src/rules/import/no_self_import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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-self-import): module importing itself is not allowed")]
#[diagnostic(severity(warning))]
struct NoSelfImportDiagnostic(#[label] pub Span);

#[derive(Debug, Default, Clone)]
pub struct NoSelfImport;

declare_oxc_lint!(
/// ### What it does
///
/// Forbid a module from importing itself. This can sometimes happen during refactoring.
///
/// ### Example
///
/// ```javascript
/// // foo.js
/// import foo from './foo.js'
/// const foo = require('./foo')
/// ```
NoSelfImport,
correctness
);

impl Rule for NoSelfImport {
fn run_once(&self, ctx: &LintContext<'_>) {
let module_record = ctx.semantic().module_record();
let resolved_absolute_path = &module_record.resolved_absolute_path;
for (request, spans) in &module_record.requested_modules {
let Some(remote_module_record_ref) = module_record.loaded_modules.get(request) else {
continue;
};
if remote_module_record_ref.value().resolved_absolute_path == *resolved_absolute_path {
for span in spans {
ctx.diagnostic(NoSelfImportDiagnostic(*span));
}
}
}
}
}

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

let mut tester = Tester::new_without_config::<String>(NoSelfImport::NAME, vec![], vec![])
.with_import_plugin(true);

{
let pass = vec![
"import _ from 'lodash'",
"import find from 'lodash.find'",
"import foo from './foo'",
"import foo from '../foo'",
"import foo from 'foo'",
"import foo from './'",
"import foo from '@scope/foo'",
"var _ = require('lodash')",
"var find = require('lodash.find')",
"var foo = require('./foo')",
"var foo = require('../foo')",
"var foo = require('foo')",
"var foo = require('./')",
"var foo = require('@scope/foo')",
"var bar = require('./bar/index')",
];

let fail = vec![
"import bar from './no-self-import'",
"var bar = require('./no-self-import')",
"var bar = require('./no-self-import.js')",
];

tester = tester.change_rule_path("no-self-import.js").update_expect_pass_fail(pass, fail);
tester.test();
}

{
let pass = vec!["var bar = require('./bar')"];
let fail = vec![];

tester = tester.change_rule_path("bar/index.js").update_expect_pass_fail(pass, fail);
tester.test();
}

{
let pass = vec![];
let fail = vec![
"var bar = require('.')",
"var bar = require('./')",
"var bar = require('././././')",
];

tester = tester.change_rule_path("index.js").update_expect_pass_fail(pass, fail);
tester.test();
}

{
let pass = vec![];
let fail = vec!["var bar = require('../no-self-import-folder')"];

tester = tester
.change_rule_path("no-self-import-folder/index.js")
.update_expect_pass_fail(pass, fail);
tester.test_and_snapshot();
}
}
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ impl Runtime {
let semantic_builder = SemanticBuilder::new(source_text, source_type)
.with_trivias(ret.trivias)
.with_check_syntax_error(check_syntax_errors)
.build_module_record(program);
.build_module_record(path.to_path_buf(), program);
let module_record = semantic_builder.module_record();

if self.linter.options().import_plugin {
Expand Down
52 changes: 26 additions & 26 deletions crates/oxc_linter/src/snapshots/named.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,181 +3,181 @@ source: crates/oxc_linter/src/tester.rs
expression: named
---
eslint-plugin-import(named): named import "somethingElse" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { somethingElse } from './test-module'
· ─────────────
╰────
help: does "./test-module" have the export "somethingElse"?

eslint-plugin-import(named): named import "baz" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { baz } from './bar'
· ───
╰────
help: does "./bar" have the export "baz"?

eslint-plugin-import(named): named import "baz" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { baz, bop } from './bar'
· ───
╰────
help: does "./bar" have the export "baz"?

eslint-plugin-import(named): named import "bop" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { baz, bop } from './bar'
· ───
╰────
help: does "./bar" have the export "bop"?

eslint-plugin-import(named): named import "c" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import {a, b, c} from './named-exports'
· ─
╰────
help: does "./named-exports" have the export "c"?

eslint-plugin-import(named): named import "a" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { a } from './default-export'
· ─
╰────
help: does "./default-export" have the export "a"?

eslint-plugin-import(named): named import "ActionTypes1" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { ActionTypes1 } from './qc'
· ────────────
╰────
help: does "./qc" have the export "ActionTypes1"?

eslint-plugin-import(named): named import "a" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import {a, b, c, d, e} from './re-export'
· ─
╰────
help: does "./re-export" have the export "a"?

eslint-plugin-import(named): named import "b" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import {a, b, c, d, e} from './re-export'
· ─
╰────
help: does "./re-export" have the export "b"?

eslint-plugin-import(named): named import "d" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import {a, b, c, d, e} from './re-export'
· ─
╰────
help: does "./re-export" have the export "d"?

eslint-plugin-import(named): named import "e" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import {a, b, c, d, e} from './re-export'
· ─
╰────
help: does "./re-export" have the export "e"?

eslint-plugin-import(named): named import "a" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { a } from './re-export-names'
· ─
╰────
help: does "./re-export-names" have the export "a"?

eslint-plugin-import(named): named import "bar" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1export { bar } from './bar'
· ───
╰────
help: does "./bar" have the export "bar"?

× Unexpected token
╭─[named.js:1:1]
╭─[index.js:1:1]
1export bar2, { bar } from './bar'
· ────
╰────

eslint-plugin-import(named): named import "baz" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1const { baz } = require('./bar')
· ───
╰────
help: does "./bar" have the export "baz"?

eslint-plugin-import(named): named import "baz" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1let { baz } = require('./bar')
· ───
╰────
help: does "./bar" have the export "baz"?

eslint-plugin-import(named): named import "bar" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1const { baz: bar, bop } = require('./bar'), { a } = require('./re-export-names')
· ───
╰────
help: does "./bar" have the export "bar"?

eslint-plugin-import(named): named import "bop" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1const { baz: bar, bop } = require('./bar'), { a } = require('./re-export-names')
· ───
╰────
help: does "./bar" have the export "bop"?

eslint-plugin-import(named): named import "a" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1const { baz: bar, bop } = require('./bar'), { a } = require('./re-export-names')
· ─
╰────
help: does "./re-export-names" have the export "a"?

eslint-plugin-import(named): named import "defExport" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1const { default: defExport } = require('./named-exports')
· ─────────
╰────
help: does "./named-exports" have the export "defExport"?

eslint-plugin-import(named): named import "baz" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { baz } from 'es6-module'
· ───
╰────
help: does "es6-module" have the export "baz"?

eslint-plugin-import(named): named import "bap" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { foo, bar, bap } from './re-export-default'
· ───
╰────
help: does "./re-export-default" have the export "bap"?

eslint-plugin-import(named): named import "default" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { default as barDefault } from './re-export'
· ───────
╰────
help: does "./re-export" have the export "default"?

eslint-plugin-import(named): named import "bar" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { bar } from './export-all'
· ───
╰────
help: does "./export-all" have the export "bar"?

eslint-plugin-import(named): named import "NotExported" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { NotExported } from './typescript-export-assign-object'
· ───────────
╰────
help: does "./typescript-export-assign-object" have the export "NotExported"?

eslint-plugin-import(named): named import "FooBar" not found
╭─[named.js:1:1]
╭─[index.js:1:1]
1import { FooBar } from './typescript-export-assign-object'
· ──────
╰────
Expand Down
Loading

0 comments on commit 5e1ddbc

Please sign in to comment.