mirror of
https://github.com/swc-project/swc.git
synced 2024-12-23 21:54:36 +03:00
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:
parent
8a0ebebb08
commit
987213797f
@ -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`
|
||||
|
7
crates/swc/tests/errors/lints/no-console/.swcrc
Normal file
7
crates/swc/tests/errors/lints/no-console/.swcrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"jsc": {
|
||||
"lints": {
|
||||
"noConsole": ["error"]
|
||||
}
|
||||
}
|
||||
}
|
7
crates/swc/tests/errors/lints/no-console/1/input.js
Normal file
7
crates/swc/tests/errors/lints/no-console/1/input.js
Normal file
@ -0,0 +1,7 @@
|
||||
object.console.log("message");
|
||||
|
||||
(console) => {
|
||||
console.log("message");
|
||||
};
|
||||
|
||||
console.log("message");
|
@ -0,0 +1,6 @@
|
||||
error: Unexpected console statement
|
||||
|
||||
|
|
||||
7 | console.log("message");
|
||||
| ^^^^^^^
|
||||
|
@ -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,
|
||||
|
@ -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"}
|
||||
|
41
crates/swc_ecma_lints/src/config.rs
Normal file
41
crates/swc_ecma_lints/src/config.rs
Normal 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>,
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
pub mod config;
|
||||
pub mod rule;
|
||||
pub mod rules;
|
||||
|
@ -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
|
||||
|
68
crates/swc_ecma_lints/src/rules/no_console.rs
Normal file
68
crates/swc_ecma_lints/src/rules/no_console.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user