diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_node_protocol.rs b/crates/oxc_linter/src/rules/unicorn/prefer_node_protocol.rs index ef5d5bad0c481..c2783d7a15d52 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_node_protocol.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_node_protocol.rs @@ -1,5 +1,5 @@ use oxc_ast::{ - ast::{Expression, ModuleDeclaration}, + ast::{Expression, ModuleDeclaration, TSModuleReference}, AstKind, }; use oxc_diagnostics::OxcDiagnostic; @@ -43,6 +43,12 @@ impl Rule for PreferNodeProtocol { } _ => None, }, + AstKind::TSImportEqualsDeclaration(import) => match &import.module_reference { + TSModuleReference::ExternalModuleReference(external) => { + Some((external.expression.value.clone(), external.expression.span)) + } + _ => None, + }, AstKind::CallExpression(call) if !call.optional => { call.common_js_require().map(|s| (s.value.clone(), s.span)) } @@ -73,7 +79,19 @@ impl Rule for PreferNodeProtocol { return; } - ctx.diagnostic(prefer_node_protocol_diagnostic(span, &string_lit_value)); + ctx.diagnostic_with_fix( + prefer_node_protocol_diagnostic(span, &string_lit_value), + |fixer| { + // Smallest module name is 2 chars, plus 2 for quotes = 4. + debug_assert!( + span.size() >= 4, + "node stdlib module name should be at least 4 chars long" + ); + // We're replacing inside the string literal, shift to account for quotes. + let span = span.shrink_left(1).shrink_right(1); + fixer.replace(span, format!("node:{string_lit_value}")) + }, + ); } } @@ -86,7 +104,10 @@ fn test() { r#"import fs from "./fs";"#, r#"import fs from "unknown-builtin-module";"#, r#"import fs from "node:fs";"#, + r#"import * as fs from "node:fs";"#, r#"import "punycode / ";"#, + r#"import fs = require("node:fs");"#, + r#"import type fs = require("node:fs");"#, r#"const fs = require("node:fs");"#, r#"const fs = require("node:fs/promises");"#, r"const fs = require(fs);", @@ -103,6 +124,8 @@ fn test() { let fail = vec![ r#"import fs from "fs";"#, + r#"import * as fs from "fs";"#, + r#"import fs = require("fs");"#, r#"export {promises} from "fs";"#, r#"import fs from "fs/promises";"#, r#"export {default} from "fs/promises";"#, @@ -118,5 +141,15 @@ fn test() { r"await import('assert/strict')", ]; - Tester::new(PreferNodeProtocol::NAME, pass, fail).test_and_snapshot(); + let fix = vec![ + (r#"import fs from "fs";"#, r#"import fs from "node:fs";"#, None), + (r#"import * as fs from "fs";"#, r#"import * as fs from "node:fs";"#, None), + (r"import fs from 'fs';", r"import fs from 'node:fs';", None), + (r"const fs = require('fs');", r"const fs = require('node:fs');", None), + (r"import fs = require('fs');", r"import fs = require('node:fs');", None), + (r#"import "child_process";"#, r#"import "node:child_process";"#, None), + (r#"import fs from "fs/promises";"#, r#"import fs from "node:fs/promises";"#, None), + ]; + + Tester::new(PreferNodeProtocol::NAME, pass, fail).expect_fix(fix).test_and_snapshot(); } diff --git a/crates/oxc_linter/src/snapshots/prefer_node_protocol.snap b/crates/oxc_linter/src/snapshots/prefer_node_protocol.snap index 0d15de1d84b6d..bb946d03f5eac 100644 --- a/crates/oxc_linter/src/snapshots/prefer_node_protocol.snap +++ b/crates/oxc_linter/src/snapshots/prefer_node_protocol.snap @@ -8,6 +8,20 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Prefer `node:fs` over `fs`. + ⚠ eslint-plugin-unicorn(prefer-node-protocol): Prefer using the `node:` protocol when importing Node.js builtin modules. + ╭─[prefer_node_protocol.tsx:1:21] + 1 │ import * as fs from "fs"; + · ──── + ╰──── + help: Prefer `node:fs` over `fs`. + + ⚠ eslint-plugin-unicorn(prefer-node-protocol): Prefer using the `node:` protocol when importing Node.js builtin modules. + ╭─[prefer_node_protocol.tsx:1:21] + 1 │ import fs = require("fs"); + · ──── + ╰──── + help: Prefer `node:fs` over `fs`. + ⚠ eslint-plugin-unicorn(prefer-node-protocol): Prefer using the `node:` protocol when importing Node.js builtin modules. ╭─[prefer_node_protocol.tsx:1:24] 1 │ export {promises} from "fs";