feat(es/lints): Implement symbol-description rule (#4161)

This commit is contained in:
Artur 2022-03-26 09:49:57 +03:00 committed by GitHub
parent 6debb423d5
commit 10851ece98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 185 additions and 1 deletions

View File

@ -0,0 +1,12 @@
{
"jsc": {
"lints": {
"symbol-description": [
"error",
{
"enforceStringDescription": false
}
]
}
}
}

View File

@ -0,0 +1,3 @@
Symbol()
Symbol({})
Symbol('name')

View File

@ -0,0 +1,6 @@
x Expected Symbol to have a description
,----
1 | Symbol()
: ^^^^^^^^
`----

View File

@ -0,0 +1,9 @@
{
"jsc": {
"lints": {
"symbol-description": [
"error"
]
}
}
}

View File

@ -0,0 +1,4 @@
Symbol()
Symbol({})
foo(() => Symbol())
Symbol('name')

View File

@ -0,0 +1,18 @@
x Expected Symbol to have a description
,----
1 | Symbol()
: ^^^^^^^^
`----
x Symbol description should be a string
,----
2 | Symbol({})
: ^^^^^^^^^^
`----
x Expected Symbol to have a description
,----
3 | foo(() => Symbol())
: ^^^^^^^^
`----

View File

@ -9,7 +9,8 @@ use crate::rules::non_critical_lints::{
no_param_reassign::NoParamReassignConfig, no_restricted_syntax::NoRestrictedSyntaxConfig,
no_use_before_define::NoUseBeforeDefineConfig,
prefer_regex_literals::PreferRegexLiteralsConfig, quotes::QuotesConfig, radix::RadixConfig,
use_is_nan::UseIsNanConfig, valid_typeof::ValidTypeofConfig, yoda::YodaConfig,
symbol_description::SymbolDescriptionConfig, use_is_nan::UseIsNanConfig,
valid_typeof::ValidTypeofConfig, yoda::YodaConfig,
};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
@ -155,4 +156,8 @@ pub struct LintConfig {
#[cfg(feature = "non_critical_lints")]
#[serde(default, alias = "noParamReassign")]
pub no_param_reassign: RuleConfig<NoParamReassignConfig>,
#[cfg(feature = "non_critical_lints")]
#[serde(default, alias = "symbolDescription")]
pub symbol_description: RuleConfig<SymbolDescriptionConfig>,
}

View File

@ -33,6 +33,7 @@ pub(crate) mod non_critical_lints {
pub mod prefer_regex_literals;
pub mod quotes;
pub mod radix;
pub mod symbol_description;
pub mod use_is_nan;
pub mod valid_typeof;
pub mod yoda;
@ -145,6 +146,12 @@ pub fn all(lint_params: LintParams) -> Vec<Box<dyn Rule>> {
rules.extend(no_param_reassign::no_param_reassign(
&lint_config.no_param_reassign,
));
rules.extend(symbol_description::symbol_description(
program,
top_level_ctxt,
&lint_config.symbol_description,
));
}
rules

View File

@ -0,0 +1,120 @@
use serde::{Deserialize, Serialize};
use swc_common::{collections::AHashSet, errors::HANDLER, Span, SyntaxContext};
use swc_ecma_ast::*;
use swc_ecma_utils::{collect_decls_with_ctxt, ident::IdentLike};
use swc_ecma_visit::{Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
rules::utils::{extract_arg_val, ArgValue},
};
const SYMBOL_EXPECTED_MESSAGE: &str = "Expected Symbol to have a description";
const SYMBOL_STRING_DESCRIPTION_EXPECTED_MESSAGE: &str = "Symbol description should be a string";
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SymbolDescriptionConfig {
enforce_string_description: Option<bool>,
}
pub fn symbol_description(
program: &Program,
top_level_ctxt: SyntaxContext,
config: &RuleConfig<SymbolDescriptionConfig>,
) -> Option<Box<dyn Rule>> {
match config.get_rule_reaction() {
LintRuleReaction::Off => None,
_ => Some(visitor_rule(SymbolDescription::new(
collect_decls_with_ctxt(program, top_level_ctxt),
top_level_ctxt,
config,
))),
}
}
#[derive(Debug, Default)]
struct SymbolDescription {
expected_reaction: LintRuleReaction,
top_level_ctxt: SyntaxContext,
top_level_declared_vars: AHashSet<Id>,
enforce_string_description: bool,
}
impl SymbolDescription {
fn new(
top_level_declared_vars: AHashSet<Id>,
top_level_ctxt: SyntaxContext,
config: &RuleConfig<SymbolDescriptionConfig>,
) -> Self {
let rule_config = config.get_rule_config();
Self {
expected_reaction: config.get_rule_reaction(),
top_level_ctxt,
top_level_declared_vars,
enforce_string_description: rule_config.enforce_string_description.unwrap_or(true),
}
}
fn is_symbol_call(&self, ident: &Ident) -> bool {
if self.top_level_declared_vars.contains(&ident.to_id()) {
return false;
}
if ident.span.ctxt != self.top_level_ctxt {
return false;
}
&*ident.sym == "Symbol"
}
fn check(&self, span: Span, first_arg: Option<&ExprOrSpread>) {
if let Some(ExprOrSpread { expr, .. }) = first_arg {
if self.enforce_string_description {
match extract_arg_val(
&self.top_level_ctxt,
&self.top_level_declared_vars,
expr,
true,
) {
ArgValue::Str(_) => {}
_ => {
self.emit_report(span, SYMBOL_STRING_DESCRIPTION_EXPECTED_MESSAGE);
}
}
}
return;
}
self.emit_report(span, SYMBOL_EXPECTED_MESSAGE);
}
fn emit_report(&self, span: Span, message: &str) {
HANDLER.with(|handler| match self.expected_reaction {
LintRuleReaction::Error => {
handler.struct_span_err(span, message).emit();
}
LintRuleReaction::Warning => {
handler.struct_span_warn(span, message).emit();
}
_ => {}
});
}
}
impl Visit for SymbolDescription {
fn visit_call_expr(&mut self, call_expr: &CallExpr) {
if let Callee::Expr(expr) = &call_expr.callee {
if let Expr::Ident(ident) = expr.as_ref() {
if self.is_symbol_call(ident) {
self.check(call_expr.span, call_expr.args.first());
}
}
}
call_expr.visit_children_with(self);
}
}