feat(es/lints): Implement "no-console" rule (#3269)

swc_ecma_lints:
 - Add types for general configuration.
 - Add `no-console`rule.

swc:
 - Expose the lint config via `jsc.lints`.
This commit is contained in:
Artur 2022-01-26 14:39:19 +03:00 committed by GitHub
parent 8a0ebebb08
commit 987213797f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 165 additions and 12 deletions

View File

@ -25,11 +25,11 @@ pub use swc_common::chain;
use swc_common::{
collections::{AHashMap, AHashSet},
errors::Handler,
FileName, Mark, SourceMap,
FileName, Mark, SourceMap, SyntaxContext,
};
use swc_ecma_ast::{EsVersion, Expr, Program};
use swc_ecma_ext_transforms::jest;
use swc_ecma_lints::rules::lint_to_fold;
use swc_ecma_lints::{config::LintConfig, rules::lint_to_fold};
use swc_ecma_loader::resolvers::{
lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver,
};
@ -277,6 +277,7 @@ impl Options {
paths,
minify: mut js_minify,
experimental,
lints,
..
} = config.jsc;
@ -368,6 +369,8 @@ impl Options {
.global_mark
.unwrap_or_else(|| Mark::fresh(Mark::root()));
let top_level_ctxt = SyntaxContext::empty().apply_mark(top_level_mark);
let pass = chain!(
const_modules,
optimization,
@ -427,7 +430,7 @@ impl Options {
),
syntax.typescript()
),
lint_to_fold(swc_ecma_lints::rules::all()),
lint_to_fold(swc_ecma_lints::rules::all(&lints, top_level_ctxt)),
crate::plugin::plugins(experimental),
custom_before_pass(&program),
// handle jsx
@ -961,6 +964,9 @@ pub struct JscConfig {
#[serde(default)]
pub experimental: JscExperimental,
#[serde(default)]
pub lints: LintConfig,
}
/// `jsc.experimental` in `.swcrc`

View File

@ -0,0 +1,7 @@
{
"jsc": {
"lints": {
"noConsole": ["error"]
}
}
}

View File

@ -0,0 +1,7 @@
object.console.log("message");
(console) => {
console.log("message");
};
console.log("message");

View File

@ -0,0 +1,6 @@
error: Unexpected console statement
|
7 | console.log("message");
| ^^^^^^^

View File

@ -144,6 +144,7 @@ fn shopify_2_same_opt() {
paths: Default::default(),
minify: None,
experimental: Default::default(),
lints: Default::default(),
assumptions: Default::default(),
},
module: None,

View File

@ -12,6 +12,7 @@ version = "0.10.0"
auto_impl = "0.5.0"
parking_lot = "0.11"
rayon = "1.5.1"
serde = {version = "1.0.133", features = ["derive"]}
swc_atoms = {version = "0.2.9", path = "../swc_atoms"}
swc_common = {version = "0.17.0", path = "../swc_common"}
swc_ecma_ast = {version = "0.65.0", path = "../swc_ecma_ast"}

View File

@ -0,0 +1,41 @@
use crate::rules::no_console::NoConsoleConfig;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LintRuleReaction {
Off,
Warning,
Error,
}
impl Default for LintRuleReaction {
fn default() -> Self {
Self::Off
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RuleConfig<T: Debug + Clone + Serialize + Default>(
#[serde(default)] LintRuleReaction,
#[serde(default)] T,
);
impl<T: Debug + Clone + Serialize + Default> RuleConfig<T> {
pub(crate) fn get_rule_reaction(&self) -> &LintRuleReaction {
&self.0
}
pub(crate) fn get_rule_config(&self) -> &T {
&self.1
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct LintConfig {
#[serde(default)]
pub no_console: RuleConfig<NoConsoleConfig>,
}

View File

@ -1,2 +1,3 @@
pub mod config;
pub mod rule;
pub mod rules;

View File

@ -1,17 +1,26 @@
use crate::rule::Rule;
use crate::{config::LintConfig, rule::Rule};
use swc_common::SyntaxContext;
use swc_ecma_ast::*;
use swc_ecma_visit::{noop_fold_type, Fold};
mod const_assign;
mod duplicate_bindings;
mod duplicate_exports;
pub mod no_console;
pub fn all() -> Vec<Box<dyn Rule>> {
vec![
pub fn all(lint_config: &LintConfig, top_level_ctxt: SyntaxContext) -> Vec<Box<dyn Rule>> {
let mut rules = vec![
const_assign::const_assign(),
duplicate_bindings::duplicate_bindings(),
duplicate_exports::duplicate_exports(),
]
];
rules.extend(no_console::no_console(
&lint_config.no_console,
top_level_ctxt,
));
rules
}
pub fn lint_to_fold<R>(r: R) -> impl Fold

View File

@ -0,0 +1,68 @@
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
};
use serde::{Deserialize, Serialize};
use swc_common::{collections::AHashSet, errors::HANDLER, SyntaxContext};
use swc_ecma_ast::*;
use swc_ecma_visit::{noop_visit_type, Visit};
const MESSAGE: &str = "Unexpected console statement";
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct NoConsoleConfig {
// not used for now
allow: Option<AHashSet<String>>,
}
pub fn no_console(
config: &RuleConfig<NoConsoleConfig>,
top_level_ctxt: SyntaxContext,
) -> Option<Box<dyn Rule>> {
let rule_reaction = config.get_rule_reaction();
match rule_reaction {
LintRuleReaction::Off => None,
_ => Some(visitor_rule(NoConsole::new(
rule_reaction.clone(),
top_level_ctxt,
))),
}
}
#[derive(Debug, Default)]
struct NoConsole {
expected_reaction: LintRuleReaction,
top_level_ctxt: SyntaxContext,
}
impl NoConsole {
fn new(expected_reaction: LintRuleReaction, top_level_ctxt: SyntaxContext) -> Self {
Self {
expected_reaction,
top_level_ctxt,
}
}
fn check(&mut self, id: &Ident) {
if &*id.sym == "console" && id.span.ctxt == self.top_level_ctxt {
HANDLER.with(|handler| match self.expected_reaction {
LintRuleReaction::Error => {
handler.struct_span_err(id.span, MESSAGE).emit();
}
LintRuleReaction::Warning => {
handler.struct_span_warn(id.span, MESSAGE).emit();
}
_ => {}
});
}
}
}
impl Visit for NoConsole {
noop_visit_type!();
fn visit_ident(&mut self, id: &Ident) {
self.check(id);
}
}

View File

@ -1,10 +1,10 @@
use std::path::PathBuf;
use swc_common::input::SourceFileInput;
use swc_common::{input::SourceFileInput, Mark, SyntaxContext};
use swc_ecma_ast::EsVersion;
use swc_ecma_lints::rules::all;
use swc_ecma_lints::{config::LintConfig, rules::all};
use swc_ecma_parser::{lexer::Lexer, Parser, Syntax};
use swc_ecma_transforms_base::resolver::resolver;
use swc_ecma_transforms_base::resolver::resolver_with_mark;
use swc_ecma_utils::HANDLER;
use swc_ecma_visit::VisitMutWith;
@ -37,9 +37,15 @@ fn pass(input: PathBuf) {
let mut parser = Parser::new_from(lexer);
let mut m = parser.parse_module().unwrap();
m.visit_mut_with(&mut resolver());
let top_level_mark = Mark::fresh(Mark::root());
let rules = all();
m.visit_mut_with(&mut resolver_with_mark(top_level_mark));
let top_level_ctxt = SyntaxContext::empty().apply_mark(top_level_mark);
let mut config = LintConfig::default();
let rules = all(&config, top_level_ctxt);
HANDLER.set(handler, || {
for mut rule in rules {