feat(rome_js_analyze): no new Symbol
MODIFY
crates/rome_js_analyze/src/analyzers/nursery.rs
crates/rome_js_analyze/src/analyzers/nursery.rs
//! Generated file, do not edit by hand, see `xtask/codegen`
use rome_analyze::declare_group;
+mod no_new_symbol;
mod no_unreachable;
-declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_unreachable :: NoUnreachable ,] } }
+declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_new_symbol :: NoNewSymbol , self :: no_unreachable :: NoUnreachable ,] } }

ADD
crates/rome_js_analyze/src/analyzers/nursery/no_new_symbol.rs
+use crate::{semantic_services::Semantic, JsRuleAction};
+use rome_analyze::{
+ context::RuleContext, declare_rule, ActionCategory, Rule, RuleCategory, RuleDiagnostic,
+};
+use rome_console::markup;
+use rome_diagnostics::Applicability;
+use rome_js_factory::make;
+use rome_js_syntax::{JsAnyExpression, JsCallExpression, JsNewExpression, JsNewExpressionFields};
+use rome_rowan::{AstNode, BatchMutationExt};
+
+declare_rule! {
+ /// Disallow `new` operators with the `Symbol` object
+ ///
+ /// ## Examples
+ ///
+ /// ### Invalid
+ ///
+ /// ```js,expect_diagnostic
+ /// var foo = new Symbol('foo');
+ /// ```
+ ///
+ /// ### Valid
+ ///
+ /// ```js
+ /// var bar = Symbol('bar');
+ /// function baz() {
+ /// function Symbol() { }
+ /// new Symbol();
+ /// }
+ /// ```
+ pub(crate) NoNewSymbol {
+ version: "0.10.0",
+ name: "noNewSymbol",
+ recommended: true,
+ }
+}
+
+impl Rule for NoNewSymbol {
+ const CATEGORY: RuleCategory = RuleCategory::Lint;
+
+ type Query = Semantic<JsNewExpression>;
+ type State = ();
+ type Signals = Option<Self::State>;
+
+ fn run(ctx: &RuleContext<Self>) -> Option<Self::State> {
+ let new_expression = ctx.query();
+ let callee = new_expression.callee().ok()?;
+
+ let ident = callee.as_js_identifier_expression()?;
+ let reference = ident.name().ok()?;
+
+ if reference.has_name("Symbol") {
+ let model = ctx.model();
+ let declaration = model.declaration(&reference);
+
+ if declaration.is_none() {
+ return Some(());
+ }
+ }
+
+ None
+ }
+
+ fn diagnostic(ctx: &RuleContext<Self>, _: &Self::State) -> Option<RuleDiagnostic> {
+ let node = ctx.query();
+
+ Some(RuleDiagnostic::new(
+ node.syntax().text_trimmed_range(),
+ markup! {
+ <Emphasis>"Symbol"</Emphasis>" cannot be called as a constructor."
+ },
+ ))
+ }
+
+ fn action(ctx: &RuleContext<Self>, _: &Self::State) -> Option<JsRuleAction> {
+ let node = ctx.query();
+ let mut mutation = ctx.root().begin();
+
+ let call_expression = convert_new_expression_to_call_expression(node)?;
+ mutation.replace_node_discard_trivia(
+ JsAnyExpression::JsNewExpression(node.clone()),
+ JsAnyExpression::JsCallExpression(call_expression),
+ );
+
+ Some(JsRuleAction {
+ category: ActionCategory::QuickFix,
+ applicability: Applicability::MaybeIncorrect,
+ message: markup! { "Remove "<Emphasis>"new"</Emphasis>"." }.to_owned(),
+ mutation,
+ })
+ }
+}
+
+fn convert_new_expression_to_call_expression(expr: &JsNewExpression) -> Option<JsCallExpression> {
+ let JsNewExpressionFields {
+ new_token,
+ callee,
+ arguments,
+ ..
+ } = expr.as_fields();
+
+ let new_token = new_token.ok()?;
+ let mut callee = callee.ok()?;
+ let arguments = arguments?;
+
+ if new_token.has_leading_comments() || new_token.has_trailing_comments() {
+ let symbol = callee.syntax().first_token()?;
+
+ let leading_trivia = new_token
+ .leading_trivia()
+ .pieces()
+ .chain(new_token.trailing_trivia().pieces())
+ .chain(symbol.leading_trivia().pieces())
+ .collect::<Vec<_>>();
+
+ let symbol = symbol.with_leading_trivia_pieces(leading_trivia);
+
+ callee = make::js_identifier_expression(make::js_reference_identifier(symbol)).into();
+ }
+
+ Some(make::js_call_expression(callee, arguments).build())
+}

ADD
crates/rome_js_analyze/tests/specs/nursery/noNewSymbol.js
+// valid
+var bar = Symbol('bar');
+function baz() {
+ function Symbol() { }
+ new Symbol();
+}
+// invalid
+var foo = new Symbol('foo');
+var foo2 = new Symbol();
+var lorem = new Symbol() // comment
+var s = /* prefix_cmt */ new /* suffix_cmt */ Symbol() // comment
\ No newline at end of file

MODIFY
crates/rome_js_syntax/src/expr_ext.rs
crates/rome_js_syntax/src/expr_ext.rs
@@ -26,8 +26,23 @@ impl JsReferenceIdentifier {
/// assert!(!js_reference_identifier(ident("x")).is_undefined());
/// ```
pub fn is_undefined(&self) -> bool {
+ self.has_name("undefined")
+ }
+
+ /// Returns `true` if this identifier has the given name.
+ ///
+ /// ## Examples
+ ///
+ /// ```
+ /// use rome_js_factory::make::{js_reference_identifier, ident};
+ ///
+ /// assert!(js_reference_identifier(ident("foo")).has_name("foo"));
+ /// assert!(!js_reference_identifier(ident("bar")).has_name("foo"));
+ /// ```
+ pub fn has_name(&self, name: &str) -> bool {
self.value_token()
- .map_or(false, |name| name.text_trimmed() == "undefined")
+ .map(|token| token.text_trimmed() == name)
+ .unwrap_or_default()
}
}

MODIFY
crates/rome_service/src/configuration/linter/rules.rs
crates/rome_service/src/configuration/linter/rules.rs
@@ -387,17 +387,24 @@ pub struct Nursery {
#[allow(dead_code)]
#[doc = r" A list of rules that belong to this group"]
struct NurserySchema {
+ no_new_symbol: Option<RuleConfiguration>,
no_unreachable: Option<RuleConfiguration>,
no_unused_variables: Option<RuleConfiguration>,
use_camel_case: Option<RuleConfiguration>,
}
impl Nursery {
const CATEGORY_NAME: &'static str = "nursery";
- pub(crate) const CATEGORY_RULES: [&'static str; 3] =
- ["noUnreachable", "noUnusedVariables", "useCamelCase"];
- const RECOMMENDED_RULES: [&'static str; 1] = ["noUnusedVariables"];
- const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 1] =
- [RuleFilter::Rule("nursery", Self::CATEGORY_RULES[1])];
+ pub(crate) const CATEGORY_RULES: [&'static str; 4] = [
+ "noNewSymbol",
+ "noUnreachable",
+ "noUnusedVariables",
+ "useCamelCase",
+ ];
+ const RECOMMENDED_RULES: [&'static str; 2] = ["noNewSymbol", "noUnusedVariables"];
+ const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 2] = [
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[0]),
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[2]),
+ ];
pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) }
pub(crate) fn get_enabled_rules(&self) -> IndexSet<RuleFilter> {
IndexSet::from_iter(self.rules.iter().filter_map(|(key, conf)| {
@@ -423,7 +430,7 @@ impl Nursery {
pub(crate) fn is_recommended_rule(rule_name: &str) -> bool {
Self::RECOMMENDED_RULES.contains(&rule_name)
}
- pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 1] {
+ pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 2] {
Self::RECOMMENDED_RULES_AS_FILTERS
}
}

MODIFY
editors/vscode/configuration_schema.json
editors/vscode/configuration_schema.json
"description": "A list of rules that belong to this group",
"type": "object",
"properties": {
+ "noNewSymbol": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/RuleConfiguration"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
"noUnreachable": {
"anyOf": [
{

MODIFY
npm/backend-jsonrpc/src/workspace.ts
npm/backend-jsonrpc/src/workspace.ts
@@ -152,6 +152,7 @@ export interface Correctness {
* A list of rules that belong to this group
*/
export interface Nursery {
+ noNewSymbol?: RuleConfiguration;
noUnreachable?: RuleConfiguration;
noUnusedVariables?: RuleConfiguration;
/**

MODIFY
website/src/docs/lint/rules/index.md
website/src/docs/lint/rules/index.md
@@ -254,6 +254,14 @@ initializer and update expressions are not needed
<section>
<h2>Nursery</h2>
<div class="rule">
+<h3 data-toc-exclude id="noNewSymbol">
+ <a href="/docs/lint/rules/noNewSymbol">noNewSymbol (since v0.10.0)</a>
+ <a class="header-anchor" href="#noNewSymbol"></a>
+ <span class="recommended">recommended</span>
+</h3>
+Disallow <code>new</code> operators with the <code>Symbol</code> object
+</div>
+<div class="rule">
<h3 data-toc-exclude id="noUnreachable">
<a href="/docs/lint/rules/noUnreachable">noUnreachable (since v0.7.0)</a>
<a class="header-anchor" href="#noUnreachable"></a>

feat(rome_js_analyze): no new Symbol (#3201)