Yuji Sugiura 2024-02-16 22:37:22 +09:00 committed by GitHub
parent 8110288bf4
commit 0b9e122de8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 342 additions and 0 deletions

View file

@ -203,6 +203,7 @@ mod unicorn {
pub mod no_new_buffer;
pub mod no_null;
pub mod no_object_as_default_parameter;
pub mod no_process_exit;
pub mod no_static_only_class;
pub mod no_thenable;
pub mod no_this_assignment;
@ -463,6 +464,7 @@ oxc_macros::declare_all_lint_rules! {
unicorn::no_new_buffer,
unicorn::no_null,
unicorn::no_object_as_default_parameter,
unicorn::no_process_exit,
unicorn::no_static_only_class,
unicorn::no_thenable,
unicorn::no_this_assignment,

View file

@ -0,0 +1,187 @@
use oxc_ast::AstKind;
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use crate::{ast_util::is_method_call, context::LintContext, rule::Rule, AstNode};
#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.")]
#[diagnostic(
severity(warning),
help("Only use `process.exit()` in CLI apps. Throw an error instead.")
)]
struct NoProcessExitDiagnostic(#[label] pub Span);
#[derive(Debug, Default, Clone)]
pub struct NoProcessExit;
declare_oxc_lint!(
/// ### What it does
/// Disallow `process.exit()`.
///
/// ### Why is this bad?
/// Only use `process.exit()` in CLI apps. Throw an error instead.
///
/// ### Example
/// ```javascript
/// // Bad
/// if (problem) process.exit(1);
///
/// // Good
/// if (problem) throw new Error("message");
///
/// #! /usr/bin/env node
/// if (problem) process.exit(1);
/// ```
NoProcessExit,
restriction,
);
impl Rule for NoProcessExit {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::CallExpression(expr) = node.kind() {
if is_method_call(expr, Some(&["process"]), Some(&["exit"]), None, None) {
if has_hashbang(ctx)
|| is_inside_process_event_handler(ctx, node)
|| is_worker_threads_imported(ctx)
{
return;
}
ctx.diagnostic(NoProcessExitDiagnostic(expr.span));
}
}
}
}
fn has_hashbang(ctx: &LintContext) -> bool {
let Some(root) = ctx.nodes().iter().next() else { return false };
let AstKind::Program(program) = root.kind() else { return false };
program.hashbang.is_some()
}
fn is_inside_process_event_handler(ctx: &LintContext, node: &AstNode) -> bool {
for parent in ctx.nodes().iter_parents(node.id()) {
if let AstKind::CallExpression(expr) = parent.kind() {
if is_method_call(expr, Some(&["process"]), Some(&["on", "once"]), Some(1), None) {
return true;
}
}
}
false
}
fn is_worker_threads_imported(ctx: &LintContext) -> bool {
ctx.semantic().module_record().import_entries.iter().any(|entry| {
matches!(entry.module_request.name().as_str(), "worker_threads" | "node:worker_threads")
})
}
#[test]
fn test() {
use crate::tester::Tester;
let pass = vec![
("#!/usr/bin/env node\n\nprocess.exit();"),
("Process.exit()"),
("const x = process.exit;"),
("x(process.exit)"),
(r#"process.on("SIGINT", function() { process.exit(1); })"#),
(r#"process.on("SIGKILL", function() { process.exit(1); })"#),
(r#"process.on("SIGINT", () => { process.exit(1); })"#),
(r#"process.on("SIGINT", () => process.exit(1))"#),
(r#"process.on("SIGINT", () => { if (true) { process.exit(1); } })"#),
(r#"process.once("SIGINT", function() { process.exit(1); })"#),
(r#"process.once("SIGKILL", function() { process.exit(1); })"#),
(r#"process.once("SIGINT", () => { process.exit(1); })"#),
(r#"process.once("SIGINT", () => process.exit(1))"#),
(r#"process.once("SIGINT", () => { if (true) { process.exit(1); } })"#),
(r"
const {workerData, parentPort} = require('worker_threads');
process.exit(1);
"),
(r"
const {workerData, parentPort} = require('node:worker_threads');
process.exit(1);
"),
(r"
import {workerData, parentPort} from 'worker_threads';
process.exit(1);
"),
(r"
import foo from 'worker_threads';
process.exit(1);
"),
(r"
import foo from 'node:worker_threads';
process.exit(1);
"),
// Not `CallExpression`
("new process.exit(1);"),
// Not `MemberExpression`
("exit(1);"),
// `callee.property` is not a `Identifier`
// (r#"process["exit"](1);"#),
// Computed
("process[exit](1);"),
// Not exit
("process.foo(1);"),
// Not `process`
("foo.exit(1);"),
// `callee.object.type` is not a `Identifier`
("lib.process.exit(1);"),
];
let fail = vec![
("process.exit();"),
("process.exit(1);"),
("x(process.exit(1));"),
(r#"process.on("SIGINT", function() {});process.exit();"#),
(r#"process.once("SIGINT", function() {}); process.exit(0)"#),
(r"
const mod = require('not_worker_threads');
process.exit(1);
"),
(r"
import mod from 'not_worker_threads';
process.exit(1);
"),
// Not `CallExpression`
(r"
const mod = new require('worker_threads');
process.exit(1);
"),
// Not `Literal` worker_threads
(r"
const mod = require(worker_threads);
process.exit(1);
"),
// Not `CallExpression`
(r#"new process.on("SIGINT", function() { process.exit(1); })"#),
(r#"new process.once("SIGINT", function() { process.exit(1); })"#),
// Not `MemberExpression`
(r#"on("SIGINT", function() { process.exit(1); })"#),
(r#"once("SIGINT", function() { process.exit(1); })"#),
// `callee.property` is not a `Identifier`
// (r#"process["on"]("SIGINT", function() { process.exit(1); })"#),
// (r#"process["once"]("SIGINT", function() { process.exit(1); })"#),
// Computed
(r#"process[on]("SIGINT", function() { process.exit(1); })"#),
(r#"process[once]("SIGINT", function() { process.exit(1); })"#),
// Not `on` / `once`
(r#"process.foo("SIGINT", function() { process.exit(1); })"#),
// Not `process`
(r#"foo.on("SIGINT", function() { process.exit(1); })"#),
(r#"foo.once("SIGINT", function() { process.exit(1); })"#),
// `callee.object.type` is not a `Identifier`
(r#"lib.process.on("SIGINT", function() { process.exit(1); })"#),
(r#"lib.process.once("SIGINT", function() { process.exit(1); })"#),
];
Tester::new(NoProcessExit::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,153 @@
---
source: crates/oxc_linter/src/tester.rs
expression: no_process_exit
---
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:1]
1 │ process.exit();
· ──────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:1]
1 │ process.exit(1);
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:3]
1 │ x(process.exit(1));
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:37]
1 │ process.on("SIGINT", function() {});process.exit();
· ──────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:40]
1 │ process.once("SIGINT", function() {}); process.exit(0)
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:3:13]
2 │ const mod = require('not_worker_threads');
3 │ process.exit(1);
· ───────────────
4 │
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:3:13]
2 │ import mod from 'not_worker_threads';
3 │ process.exit(1);
· ───────────────
4 │
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:3:13]
2 │ const mod = new require('worker_threads');
3 │ process.exit(1);
· ───────────────
4 │
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:3:13]
2 │ const mod = require(worker_threads);
3 │ process.exit(1);
· ───────────────
4 │
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:39]
1 │ new process.on("SIGINT", function() { process.exit(1); })
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:41]
1 │ new process.once("SIGINT", function() { process.exit(1); })
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:27]
1 │ on("SIGINT", function() { process.exit(1); })
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:29]
1 │ once("SIGINT", function() { process.exit(1); })
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:36]
1 │ process[on]("SIGINT", function() { process.exit(1); })
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:38]
1 │ process[once]("SIGINT", function() { process.exit(1); })
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:36]
1 │ process.foo("SIGINT", function() { process.exit(1); })
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:31]
1 │ foo.on("SIGINT", function() { process.exit(1); })
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:33]
1 │ foo.once("SIGINT", function() { process.exit(1); })
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:39]
1 │ lib.process.on("SIGINT", function() { process.exit(1); })
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.
⚠ eslint-plugin-unicorn(no-process-exit): Disallow `process.exit()`.
╭─[no_process_exit.tsx:1:41]
1 │ lib.process.once("SIGINT", function() { process.exit(1); })
· ───────────────
╰────
help: Only use `process.exit()` in CLI apps. Throw an error instead.