Skip to content

Commit

Permalink
feat: 🎸 prefer-node-protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
IWANABETHATGUY committed Dec 3, 2023
1 parent e3c54b9 commit c7fca3c
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 3 deletions.
6 changes: 6 additions & 0 deletions crates/oxc_ast/src/ast_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub enum AstKind<'a> {
Hashbang(&'a Hashbang),

BlockStatement(&'a BlockStatement<'a>),
ImportDeclaration(&'a ImportDeclaration<'a>),
BreakStatement(&'a BreakStatement),
ContinueStatement(&'a ContinueStatement),
DebuggerStatement(&'a DebuggerStatement),
Expand Down Expand Up @@ -76,6 +77,7 @@ pub enum AstKind<'a> {
UpdateExpression(&'a UpdateExpression<'a>),
YieldExpression(&'a YieldExpression<'a>),
ImportExpression(&'a ImportExpression<'a>),
ExportNamedDeclaration(&'a ExportNamedDeclaration<'a>),
PrivateInExpression(&'a PrivateInExpression<'a>),

ObjectProperty(&'a ObjectProperty<'a>),
Expand Down Expand Up @@ -456,6 +458,8 @@ impl<'a> GetSpan for AstKind<'a> {
Self::TSTypeParameterInstantiation(x) => x.span,

Self::TSPropertySignature(x) => x.span,
Self::ImportDeclaration(x) => x.span,
Self::ExportNamedDeclaration(x) => x.span,
}
}
}
Expand Down Expand Up @@ -543,6 +547,8 @@ impl<'a> AstKind<'a> {
Self::UpdateExpression(_) => "UpdateExpression".into(),
Self::YieldExpression(_) => "YieldExpression".into(),
Self::ImportExpression(_) => "ImportExpression".into(),
Self::ImportDeclaration(_) => "ImportDeclaration".into(),
Self::ExportNamedDeclaration(_) => "ExportNamedDeclaration".into(),
Self::PrivateInExpression(_) => "PrivateInExpression".into(),

Self::ObjectProperty(_) => "ObjectProperty".into(),
Expand Down
15 changes: 13 additions & 2 deletions crates/oxc_ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,6 @@ pub trait Visit<'a>: Sized {
Expression::RegExpLiteral(lit) => self.visit_reg_expr_literal(lit),
Expression::StringLiteral(lit) => self.visit_string_literal(lit),
Expression::TemplateLiteral(lit) => self.visit_template_literal(lit),

Expression::Identifier(ident) => self.visit_identifier_reference(ident),
Expression::MetaProperty(meta) => self.visit_meta_property(meta),

Expand Down Expand Up @@ -732,10 +731,13 @@ pub trait Visit<'a>: Sized {
}

fn visit_import_expression(&mut self, expr: &ImportExpression<'a>) {
let kind = AstKind::ImportExpression(self.alloc(expr));
self.enter_node(kind);
self.visit_expression(&expr.source);
for arg in &expr.arguments {
self.visit_expression(arg);
}
self.leave_node(kind);
}

fn visit_logical_expression(&mut self, expr: &LogicalExpression<'a>) {
Expand Down Expand Up @@ -1282,12 +1284,15 @@ pub trait Visit<'a>: Sized {
}

fn visit_import_declaration(&mut self, decl: &ImportDeclaration<'a>) {
let kind = AstKind::ImportDeclaration(self.alloc(decl));
self.enter_node(kind);
if let Some(specifiers) = &decl.specifiers {
for specifier in specifiers {
self.visit_import_declaration_specifier(specifier);
}
}
// TODO: source
self.visit_string_literal(&decl.source);
self.leave_node(kind);
// TODO: assertions
}

Expand Down Expand Up @@ -1332,9 +1337,15 @@ pub trait Visit<'a>: Sized {
}

fn visit_export_named_declaration(&mut self, decl: &ExportNamedDeclaration<'a>) {
let kind = AstKind::ExportNamedDeclaration(self.alloc(decl));
self.enter_node(kind);
if let Some(decl) = &decl.declaration {
self.visit_declaration(decl);
}
if let Some(ref source) = decl.source {
self.visit_string_literal(source);
}
self.leave_node(kind);
}

fn visit_enum_member(&mut self, member: &TSEnumMember<'a>) {
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ mod unicorn {
pub mod prefer_logical_operator_over_ternary;
pub mod prefer_math_trunc;
pub mod prefer_native_coercion_functions;
pub mod prefer_node_protocol;
pub mod prefer_optional_catch_binding;
pub mod prefer_query_selector;
pub mod prefer_regexp_test;
Expand Down Expand Up @@ -327,6 +328,7 @@ oxc_macros::declare_all_lint_rules! {
jest::valid_expect,
jest::valid_title,
unicorn::catch_error_name,
unicorn::prefer_node_protocol,
unicorn::empty_brace_spaces,
unicorn::prefer_array_some,
unicorn::error_message,
Expand Down
134 changes: 134 additions & 0 deletions crates/oxc_linter/src/rules/unicorn/prefer_node_protocol.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use oxc_ast::{
ast::{Argument, CallExpression, Expression},
AstKind,
};
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::{self, Error},
};
use oxc_macros::declare_oxc_lint;
use oxc_span::{Atom, Span};

use crate::{context::LintContext, rule::Rule, utils::NODE_BUILTINS_MODULE, AstNode};

#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-unicorn(prefer-node-protocol):")]
#[diagnostic(severity(warning), help(""))]
struct PreferNodeProtocolDiagnostic(#[label] pub Span);

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

declare_oxc_lint!(
/// ### What it does
///
///
/// ### Why is this bad?
///
///
/// ### Example
/// ```javascript
/// ```
PreferNodeProtocol,
correctness
);

impl Rule for PreferNodeProtocol {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
node.kind();
let (string_lit_value_with_span) = match node.kind() {
AstKind::ImportExpression(import) => match import.source {
Expression::StringLiteral(ref str_lit) => {
Some((str_lit.value.clone(), str_lit.span))
}
_ => None,
},
AstKind::CallExpression(call) if !call.optional => get_static_require_arg(ctx, call),
AstKind::ImportDeclaration(import) => {
Some((import.source.value.clone(), import.source.span))
}
AstKind::ExportNamedDeclaration(export) => {
export.source.as_ref().map(|item| (item.value.clone(), item.span))
}
_ => None,
};
let Some((string_lit_value, span)) = string_lit_value_with_span else {
return;
};
let module_name = if let Some((prefix, postfix)) = string_lit_value.split_once("/") {
if !postfix.is_empty() {
prefix.to_string()
} else {
string_lit_value.to_string()
}
} else {
string_lit_value.to_string()
};
dbg!(&module_name);
if module_name.starts_with("node:") || !NODE_BUILTINS_MODULE.contains(&module_name) {
return;
}

ctx.diagnostic(PreferNodeProtocolDiagnostic(span));
}
}

fn get_static_require_arg<'a>(
ctx: &LintContext<'a>,
call: &CallExpression<'a>,
) -> Option<(Atom, Span)> {
let Expression::Identifier(ref id) = call.callee else { return None };
match call.arguments.as_slice() {
[Argument::Expression(Expression::StringLiteral(str))] if id.name == "require" => ctx
.semantic()
.scopes()
.root_unresolved_references()
.contains_key(&id.name)
.then(|| (str.value.clone(), str.span)),
_ => None,
}
}

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

let pass = vec![
r#"import unicorn from "unicorn";"#,
r#"import fs from "./fs";"#,
r#"import fs from "unknown-builtin-module";"#,
r#"import fs from "node:fs";"#,
r#"import "punycode/";"#,
r#"const fs = require("node:fs");"#,
r#"const fs = require("node:fs/promises");"#,
r#"const fs = require(fs);"#,
r#"const fs = notRequire("fs");"#,
r#"const fs = foo.require("fs");"#,
r#"const fs = require.resolve("fs");"#,
r#"const fs = require(`fs`);"#,
r#"const fs = require?.("fs");"#,
r#"const fs = require("fs", extra);"#,
r#"const fs = require();"#,
r#"const fs = require(...["fs"]);"#,
r#"const fs = require("unicorn");"#,
];

let fail = vec![
r#"import fs from "fs";"#,
r#"export {promises} from "fs";"#,
r#"import fs from "fs/promises";"#,
r#"export {default} from "fs/promises";"#,
r#"import {promises} from "fs";"#,
r#"export {default as promises} from "fs";"#,
r#"import {promises} from 'fs';"#,
r#"import "buffer";"#,
r#"import "child_process";"#,
r#"import "timers/promises";"#,
r#"const {promises} = require("fs")"#,
r#"const fs = require('fs/promises')"#,
r#"export fs from "fs";"#,
r#"await import('assert/strict')"#,
];

Tester::new_without_config(PreferNodeProtocol::NAME, pass, fail).test_and_snapshot();
}
102 changes: 102 additions & 0 deletions crates/oxc_linter/src/snapshots/prefer_node_protocol.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
source: crates/oxc_linter/src/tester.rs
expression: prefer_node_protocol
---
⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ import fs from "fs";
Β· ────
╰────
help:

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ export {promises} from "fs";
Β· ────
╰────
help:

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ import fs from "fs/promises";
Β· ─────────────
╰────
help:

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ export {default} from "fs/promises";
Β· ─────────────
╰────
help:

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ import {promises} from "fs";
Β· ────
╰────
help:

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ export {default as promises} from "fs";
Β· ────
╰────
help:

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ import {promises} from 'fs';
Β· ────
╰────
help:

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ import "buffer";
Β· ────────
╰────
help:

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ import "child_process";
Β· ───────────────
╰────
help:

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ import "timers/promises";
Β· ─────────────────
╰────
help:

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ const {promises} = require("fs")
Β· ────
╰────
help:

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ const fs = require('fs/promises')
Β· ─────────────
╰────
help:

Γ— Unexpected token
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ export fs from "fs";
Β· ──
╰────

⚠ eslint-plugin-unicorn(prefer-node-protocol):
╭─[prefer_node_protocol.tsx:1:1]
1 β”‚ await import('assert/strict')
Β· ───────────────
╰────
help:


3 changes: 2 additions & 1 deletion crates/oxc_linter/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod jest;
mod node;
mod react;
mod unicorn;

pub use self::{jest::*, react::*, unicorn::*};
pub use self::{jest::*, node::*, react::*, unicorn::*};
Loading

0 comments on commit c7fca3c

Please sign in to comment.