feat(linter): add oxc/no-rest-spread-properties rule (#3432)

People open this rule may have some specific purpose, I am deliberating
about whether to add a custom message.
This commit is contained in:
Wang Wenzhe 2024-05-28 23:04:17 +08:00 committed by GitHub
parent 0d2c977c11
commit e275659cdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 205 additions and 0 deletions

View file

@ -351,6 +351,7 @@ mod oxc {
pub mod no_async_await;
pub mod no_barrel_file;
pub mod no_const_enum;
pub mod no_rest_spread_properties;
pub mod number_arg_out_of_range;
pub mod only_used_in_recursion;
pub mod uninvoked_array_callback;
@ -715,6 +716,7 @@ oxc_macros::declare_all_lint_rules! {
oxc::const_comparisons,
oxc::double_comparisons,
oxc::erasing_op,
oxc::no_rest_spread_properties,
oxc::misrefactored_assign_op,
oxc::missing_throw,
oxc::no_accumulating_spread,

View file

@ -0,0 +1,164 @@
use oxc_ast::{ast::AssignmentTarget, AstKind};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use crate::{context::LintContext, rule::Rule, AstNode};
fn no_rest_spread_properties_diagnostic(span0: Span, x1: &str, x2: &str) -> OxcDiagnostic {
OxcDiagnostic::warn(format!("oxc(no-rest-spread-properties): {x1} are not allowed. {x2}"))
.with_labels([span0.into()])
}
#[derive(Debug, Default, Clone)]
pub struct NoRestSpreadProperties(Box<NoRestSpreadPropertiesOptions>);
#[derive(Debug, Default, Clone)]
pub struct NoRestSpreadPropertiesOptions {
object_spread_message: String,
object_rest_message: String,
}
impl std::ops::Deref for NoRestSpreadProperties {
type Target = NoRestSpreadPropertiesOptions;
fn deref(&self) -> &Self::Target {
&self.0
}
}
declare_oxc_lint!(
/// ### What it does
///
/// Disallow [Object Rest/Spread Properties](https://github.com/tc39/proposal-object-rest-spread#readme).
///
/// ### Example
///
/// ```javascript
/// let { x, ...y } = z;
/// let z = { x, ...y };
/// ```
///
/// ### Options
///
/// ```json
/// {
/// "rules": {
/// "no-rest-spread-properties": [
/// "error",
/// {
/// "objectSpreadMessage": "Object spread properties are not allowed.",
/// "objectRestMessage": "Object rest properties are not allowed."
/// }
/// ]
/// }
/// }
/// ```
///
/// - `objectSpreadMessage`: A message to display when object spread properties are found.
/// - `objectRestMessage`: A message to display when object rest properties are found.
///
NoRestSpreadProperties,
restriction,
);
impl Rule for NoRestSpreadProperties {
fn from_configuration(value: serde_json::Value) -> Self {
let config = value.get(0);
let object_spread_message = config
.and_then(|v| v.get("objectSpreadMessage"))
.and_then(serde_json::Value::as_str)
.unwrap_or_default();
let object_rest_message = config
.and_then(|v| v.get("objectRestMessage"))
.and_then(serde_json::Value::as_str)
.unwrap_or_default();
Self(Box::new(NoRestSpreadPropertiesOptions {
object_spread_message: object_spread_message.to_string(),
object_rest_message: object_rest_message.to_string(),
}))
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
match node.kind() {
AstKind::SpreadElement(spread_element) => {
if ctx
.nodes()
.parent_kind(node.id())
.is_some_and(|parent| matches!(parent, AstKind::ObjectExpression(_)))
{
ctx.diagnostic(no_rest_spread_properties_diagnostic(
spread_element.span,
"object spread property",
self.object_spread_message.as_str(),
));
}
}
AstKind::BindingRestElement(rest_element) => {
if ctx
.nodes()
.parent_kind(node.id())
.is_some_and(|parent| matches!(parent, AstKind::ObjectPattern(_)))
{
ctx.diagnostic(no_rest_spread_properties_diagnostic(
rest_element.span,
"object rest property",
self.object_rest_message.as_str(),
));
}
}
AstKind::AssignmentTarget(assign_target) => {
let AssignmentTarget::ObjectAssignmentTarget(object_assign) = assign_target else {
return;
};
let Some(rest) = &object_assign.rest else {
return;
};
ctx.diagnostic(no_rest_spread_properties_diagnostic(
rest.span,
"object rest property",
self.object_rest_message.as_str(),
));
}
_ => {}
}
}
}
#[test]
fn test() {
use crate::tester::Tester;
// test case are copied from eslint-plugin-es:
// https://github.com/mysticatea/eslint-plugin-es/blob/v1.4.1/tests/lib/rules/no-rest-spread-properties.js
let pass = vec![
("[...a]", None),
("[...a] = foo", None),
("({a: [...b]})", None),
("({a: [...b]} = obj)", None),
("function f(...a) {}", None),
("f(...a)", None),
];
let fail = vec![
("({...a})", None),
("({...a} = obj)", None),
("for ({...a} in foo) {}", None),
("function f({...a}) {}", None),
(
"({...a})",
Some(serde_json::json!([{
"objectSpreadMessage": "Our codebase does not allow object spread properties."
}])),
),
(
"({...a} = obj)",
Some(serde_json::json!([{
"objectRestMessage": "Our codebase does not allow object rest properties."
}])),
),
];
Tester::new(NoRestSpreadProperties::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,39 @@
---
source: crates/oxc_linter/src/tester.rs
expression: no_rest_spread_properties
---
⚠ oxc(no-rest-spread-properties): object spread property are not allowed.
╭─[no_rest_spread_properties.tsx:1:3]
1 │ ({...a})
· ────
╰────
⚠ oxc(no-rest-spread-properties): object rest property are not allowed.
╭─[no_rest_spread_properties.tsx:1:3]
1 │ ({...a} = obj)
· ────
╰────
⚠ oxc(no-rest-spread-properties): object rest property are not allowed.
╭─[no_rest_spread_properties.tsx:1:7]
1 │ for ({...a} in foo) {}
· ────
╰────
⚠ oxc(no-rest-spread-properties): object rest property are not allowed.
╭─[no_rest_spread_properties.tsx:1:13]
1 │ function f({...a}) {}
· ────
╰────
⚠ oxc(no-rest-spread-properties): object spread property are not allowed. Our codebase does not allow object spread properties.
╭─[no_rest_spread_properties.tsx:1:3]
1 │ ({...a})
· ────
╰────
⚠ oxc(no-rest-spread-properties): object rest property are not allowed. Our codebase does not allow object rest properties.
╭─[no_rest_spread_properties.tsx:1:3]
1 │ ({...a} = obj)
· ────
╰────