mirror of
https://github.com/swc-project/swc.git
synced 2024-11-24 02:06:08 +03:00
feat(es/lints): Implement no-use-before-define
rule (#3456)
This commit is contained in:
parent
8b438ea024
commit
205b76e78d
@ -420,6 +420,13 @@ impl Options {
|
||||
);
|
||||
|
||||
let pass = chain!(
|
||||
lint_to_fold(swc_ecma_lints::rules::all(LintParams {
|
||||
program: &program,
|
||||
lint_config: &lints,
|
||||
top_level_ctxt,
|
||||
es_version,
|
||||
source_map: cm.clone(),
|
||||
})),
|
||||
// Decorators may use type information
|
||||
Optional::new(
|
||||
decorators(decorators::Config {
|
||||
@ -444,13 +451,6 @@ impl Options {
|
||||
),
|
||||
syntax.typescript()
|
||||
),
|
||||
lint_to_fold(swc_ecma_lints::rules::all(LintParams {
|
||||
program: &program,
|
||||
lint_config: &lints,
|
||||
top_level_ctxt,
|
||||
es_version,
|
||||
source_map: cm.clone(),
|
||||
})),
|
||||
crate::plugin::plugins(experimental),
|
||||
custom_before_pass(&program),
|
||||
// handle jsx
|
||||
|
@ -46,6 +46,7 @@ fn issue_1532() {
|
||||
}
|
||||
|
||||
#[testing::fixture("tests/errors/**/input.js")]
|
||||
#[testing::fixture("tests/errors/**/input.ts")]
|
||||
fn fixture(input: PathBuf) {
|
||||
let _log = testing::init();
|
||||
let output_path = input.parent().unwrap().join("output.swc-stderr");
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"jsc": {
|
||||
"lints": {
|
||||
"noAlert": ["error"]
|
||||
}
|
||||
}
|
||||
"jsc": {
|
||||
"lints": {
|
||||
"noAlert": ["error"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"jsc": {
|
||||
"lints": {
|
||||
"noUseBeforeDefine": ["error", {
|
||||
"classes": true
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
new C();
|
||||
|
||||
class C {}
|
@ -0,0 +1,6 @@
|
||||
error: 'C' was used before it was defined
|
||||
|
||||
|
|
||||
1 | new C();
|
||||
| ^
|
||||
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true
|
||||
},
|
||||
"lints": {
|
||||
"noUseBeforeDefine": [
|
||||
"error",
|
||||
{
|
||||
"classes": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
// test case from node-swc/tests/issue-2546/input.ts
|
||||
|
||||
import { injectable } from 'inversify';
|
||||
|
||||
@injectable()
|
||||
export default abstract class MyClass {}
|
||||
|
||||
|
||||
// required to got error from test
|
||||
console.log(a);
|
||||
const a = 1;
|
@ -0,0 +1,6 @@
|
||||
error: 'a' was used before it was defined
|
||||
|
||||
|
|
||||
10 | console.log(a);
|
||||
| ^
|
||||
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"jsc": {
|
||||
"lints": {
|
||||
"noUseBeforeDefine": ["error", {
|
||||
"functions": true
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
foo();
|
||||
|
||||
function foo() {}
|
@ -0,0 +1,6 @@
|
||||
error: 'foo' was used before it was defined
|
||||
|
||||
|
|
||||
1 | foo();
|
||||
| ^^^
|
||||
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"jsc": {
|
||||
"lints": {
|
||||
"noUseBeforeDefine": [
|
||||
"error"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
alert(a);
|
||||
|
||||
console.log(a);
|
||||
|
||||
function f1() {
|
||||
alert(a);
|
||||
}
|
||||
|
||||
obj[a];
|
||||
|
||||
`${a}`;
|
||||
|
||||
[a];
|
||||
|
||||
var x = a + "";
|
||||
|
||||
var e = {
|
||||
[a]: "",
|
||||
};
|
||||
|
||||
const a = {};
|
||||
|
||||
// ----
|
||||
function f2() {
|
||||
alert(b);
|
||||
const b = {};
|
||||
}
|
||||
|
||||
// ----
|
||||
console.log(a1);
|
||||
|
||||
const { a1, a2 } = {};
|
||||
|
||||
console.log(a2);
|
||||
|
||||
// ----
|
||||
console.log(a3);
|
||||
|
||||
const { a3 = "a" } = {};
|
||||
|
||||
// ----
|
||||
console.log(a4);
|
||||
|
||||
const {
|
||||
n: { a4 },
|
||||
} = {};
|
||||
|
||||
// ----
|
||||
console.log(a5);
|
||||
|
||||
const { ...a5 } = {};
|
||||
|
||||
// ----
|
||||
console.log(a6);
|
||||
|
||||
const [a6] = {};
|
||||
|
||||
// ----
|
||||
console.log(a7);
|
||||
|
||||
const [_, ...a7] = {};
|
||||
|
||||
function b1(a) {
|
||||
alert(a);
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
error: 'a' was used before it was defined
|
||||
|
||||
|
|
||||
1 | alert(a);
|
||||
| ^
|
||||
|
||||
error: 'a' was used before it was defined
|
||||
|
||||
|
|
||||
3 | console.log(a);
|
||||
| ^
|
||||
|
||||
error: 'a' was used before it was defined
|
||||
|
||||
|
|
||||
9 | obj[a];
|
||||
| ^
|
||||
|
||||
error: 'a' was used before it was defined
|
||||
|
||||
|
|
||||
11 | `${a}`;
|
||||
| ^
|
||||
|
||||
error: 'a' was used before it was defined
|
||||
|
||||
|
|
||||
13 | [a];
|
||||
| ^
|
||||
|
||||
error: 'a' was used before it was defined
|
||||
|
||||
|
|
||||
15 | var x = a + "";
|
||||
| ^
|
||||
|
||||
error: 'a' was used before it was defined
|
||||
|
||||
|
|
||||
18 | [a]: "",
|
||||
| ^
|
||||
|
||||
error: 'b' was used before it was defined
|
||||
|
||||
|
|
||||
25 | alert(b);
|
||||
| ^
|
||||
|
||||
error: 'a1' was used before it was defined
|
||||
|
||||
|
|
||||
30 | console.log(a1);
|
||||
| ^^
|
||||
|
||||
error: 'a3' was used before it was defined
|
||||
|
||||
|
|
||||
37 | console.log(a3);
|
||||
| ^^
|
||||
|
||||
error: 'a4' was used before it was defined
|
||||
|
||||
|
|
||||
42 | console.log(a4);
|
||||
| ^^
|
||||
|
||||
error: 'a5' was used before it was defined
|
||||
|
||||
|
|
||||
49 | console.log(a5);
|
||||
| ^^
|
||||
|
||||
error: 'a6' was used before it was defined
|
||||
|
||||
|
|
||||
54 | console.log(a6);
|
||||
| ^^
|
||||
|
||||
error: 'a7' was used before it was defined
|
||||
|
||||
|
|
||||
59 | console.log(a7);
|
||||
| ^^
|
||||
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"jsc": {
|
||||
"lints": {
|
||||
"noUseBeforeDefine": ["error", {
|
||||
"variables": true
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
alert(a);
|
||||
|
||||
var a = 10;
|
@ -0,0 +1,6 @@
|
||||
error: 'a' was used before it was defined
|
||||
|
||||
|
|
||||
1 | alert(a);
|
||||
| ^
|
||||
|
@ -3,13 +3,11 @@ use std::fmt::Debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "non_critical_lints")]
|
||||
use crate::rules::non_critical_lints::dot_notation::DotNotationConfig;
|
||||
#[cfg(feature = "non_critical_lints")]
|
||||
use crate::rules::non_critical_lints::no_console::NoConsoleConfig;
|
||||
#[cfg(feature = "non_critical_lints")]
|
||||
use crate::rules::non_critical_lints::prefer_regex_literals::PreferRegexLiteralsConfig;
|
||||
#[cfg(feature = "non_critical_lints")]
|
||||
use crate::rules::non_critical_lints::quotes::QuotesConfig;
|
||||
use crate::rules::non_critical_lints::{
|
||||
dot_notation::DotNotationConfig, no_console::NoConsoleConfig,
|
||||
no_use_before_define::NoUseBeforeDefineConfig,
|
||||
prefer_regex_literals::PreferRegexLiteralsConfig, quotes::QuotesConfig,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@ -61,6 +59,10 @@ pub struct LintConfig {
|
||||
#[serde(default)]
|
||||
pub no_debugger: RuleConfig<()>,
|
||||
|
||||
#[cfg(feature = "non_critical_lints")]
|
||||
#[serde(default)]
|
||||
pub no_use_before_define: RuleConfig<NoUseBeforeDefineConfig>,
|
||||
|
||||
#[cfg(feature = "non_critical_lints")]
|
||||
#[serde(default)]
|
||||
pub dot_notation: RuleConfig<DotNotationConfig>,
|
||||
|
@ -18,6 +18,7 @@ pub(crate) mod non_critical_lints {
|
||||
pub mod no_alert;
|
||||
pub mod no_console;
|
||||
pub mod no_debugger;
|
||||
pub mod no_use_before_define;
|
||||
pub mod prefer_regex_literals;
|
||||
pub mod quotes;
|
||||
}
|
||||
@ -50,6 +51,10 @@ pub fn all(lint_params: LintParams) -> Vec<Box<dyn Rule>> {
|
||||
source_map,
|
||||
} = lint_params;
|
||||
|
||||
rules.extend(no_use_before_define::no_use_before_define(
|
||||
&lint_params.lint_config.no_use_before_define,
|
||||
));
|
||||
|
||||
rules.extend(no_console::no_console(
|
||||
&lint_config.no_console,
|
||||
top_level_ctxt,
|
||||
|
236
crates/swc_ecma_lints/src/rules/no_use_before_define.rs
Normal file
236
crates/swc_ecma_lints/src/rules/no_use_before_define.rs
Normal file
@ -0,0 +1,236 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use swc_common::{
|
||||
collections::{AHashMap, AHashSet},
|
||||
errors::HANDLER,
|
||||
Span, DUMMY_SP,
|
||||
};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
|
||||
|
||||
use crate::{
|
||||
config::{LintRuleReaction, RuleConfig},
|
||||
rule::{visitor_rule, Rule},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoUseBeforeDefineConfig {
|
||||
variables: Option<bool>,
|
||||
functions: Option<bool>,
|
||||
classes: Option<bool>,
|
||||
}
|
||||
|
||||
pub fn no_use_before_define(config: &RuleConfig<NoUseBeforeDefineConfig>) -> Option<Box<dyn Rule>> {
|
||||
match config.get_rule_reaction() {
|
||||
LintRuleReaction::Off => None,
|
||||
_ => Some(visitor_rule(NoUseBeforeDefine::new(config))),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
struct SpansScopeId {
|
||||
ident_id: Id,
|
||||
block_span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct NoUseBeforeDefine {
|
||||
expected_reaction: LintRuleReaction,
|
||||
// means variables defined with "var" keyword
|
||||
check_vars: bool,
|
||||
check_functions: bool,
|
||||
check_classes: bool,
|
||||
|
||||
scoped_indents: AHashMap<Span, AHashSet<Id>>,
|
||||
scope: Vec<Span>,
|
||||
scoped_spans: AHashMap<SpansScopeId, AHashSet<Span>>,
|
||||
}
|
||||
|
||||
impl NoUseBeforeDefine {
|
||||
fn new(config: &RuleConfig<NoUseBeforeDefineConfig>) -> Self {
|
||||
let rule_config = config.get_rule_config();
|
||||
|
||||
let root_scope = DUMMY_SP;
|
||||
|
||||
let mut scoped_indents: AHashMap<Span, AHashSet<Id>> = Default::default();
|
||||
|
||||
scoped_indents.insert(root_scope, Default::default());
|
||||
|
||||
Self {
|
||||
expected_reaction: *config.get_rule_reaction(),
|
||||
scoped_indents,
|
||||
scope: vec![root_scope],
|
||||
scoped_spans: Default::default(),
|
||||
check_vars: rule_config.variables.unwrap_or(false),
|
||||
check_functions: rule_config.functions.unwrap_or(false),
|
||||
check_classes: rule_config.classes.unwrap_or(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_ident_to_current_scope(&mut self, id: Id, span: Span) {
|
||||
let current_scope = self.scope.last().unwrap();
|
||||
|
||||
self.scoped_indents
|
||||
.get_mut(current_scope)
|
||||
.unwrap()
|
||||
.insert(id.clone());
|
||||
|
||||
let spans_block_id = SpansScopeId {
|
||||
ident_id: id,
|
||||
block_span: *current_scope,
|
||||
};
|
||||
|
||||
if let Some(spans) = self.scoped_spans.get_mut(&spans_block_id) {
|
||||
spans.insert(span);
|
||||
} else {
|
||||
let mut spans: AHashSet<Span> = Default::default();
|
||||
|
||||
spans.insert(span);
|
||||
|
||||
self.scoped_spans.insert(spans_block_id, spans);
|
||||
}
|
||||
}
|
||||
|
||||
fn has_access_before_define(&self, id: &Id) -> bool {
|
||||
let current_scope = self.scope.last().unwrap();
|
||||
|
||||
self.scoped_indents.get(current_scope).unwrap().contains(id)
|
||||
}
|
||||
|
||||
fn get_ident_spans_in_current_scope(&self, id: &Id) -> &AHashSet<Span> {
|
||||
let current_block = self.scope.last().unwrap();
|
||||
|
||||
let spans_block_id = SpansScopeId {
|
||||
ident_id: id.clone(),
|
||||
block_span: *current_block,
|
||||
};
|
||||
|
||||
self.scoped_spans.get(&spans_block_id).unwrap()
|
||||
}
|
||||
|
||||
fn emit_report(&self, es6_var_check: bool, span: Span, name: &str) {
|
||||
let message = format!("'{}' was used before it was defined", name);
|
||||
|
||||
let expected_reaction = if es6_var_check {
|
||||
LintRuleReaction::Error
|
||||
} else {
|
||||
self.expected_reaction
|
||||
};
|
||||
|
||||
HANDLER.with(|handler| match expected_reaction {
|
||||
LintRuleReaction::Error => {
|
||||
handler.struct_span_err(span, &message).emit();
|
||||
}
|
||||
LintRuleReaction::Warning => {
|
||||
handler.struct_span_warn(span, &message).emit();
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
|
||||
fn check_ident(&self, es6_var_check: bool, ident: &Ident) {
|
||||
let ident_id = ident.to_id();
|
||||
|
||||
if self.has_access_before_define(&ident_id) {
|
||||
let sym = &*ident.sym;
|
||||
let mut spans = self
|
||||
.get_ident_spans_in_current_scope(&ident_id)
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<Vec<Span>>();
|
||||
|
||||
spans.sort();
|
||||
|
||||
spans.into_iter().for_each(|span| {
|
||||
self.emit_report(es6_var_check, span, sym);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn check_pat(&self, es6_var_check: bool, pat: &Pat) {
|
||||
match pat {
|
||||
Pat::Ident(BindingIdent { id, .. }) => {
|
||||
self.check_ident(es6_var_check, id);
|
||||
}
|
||||
Pat::Array(ArrayPat { elems, .. }) => {
|
||||
elems.iter().for_each(|elem| {
|
||||
if let Some(elem) = elem {
|
||||
self.check_pat(es6_var_check, elem);
|
||||
}
|
||||
});
|
||||
}
|
||||
Pat::Object(ObjectPat { props, .. }) => {
|
||||
props.iter().for_each(|prop| match prop {
|
||||
ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
|
||||
self.check_ident(es6_var_check, key);
|
||||
}
|
||||
ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) => {
|
||||
self.check_pat(es6_var_check, value.as_ref());
|
||||
}
|
||||
ObjectPatProp::Rest(RestPat { arg, .. }) => {
|
||||
self.check_pat(es6_var_check, arg.as_ref());
|
||||
}
|
||||
});
|
||||
}
|
||||
Pat::Rest(RestPat { arg, .. }) => {
|
||||
self.check_pat(es6_var_check, arg.as_ref());
|
||||
}
|
||||
Pat::Assign(AssignPat { left, .. }) => {
|
||||
self.check_pat(es6_var_check, left.as_ref());
|
||||
}
|
||||
Pat::Invalid(_) => {}
|
||||
Pat::Expr(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for NoUseBeforeDefine {
|
||||
noop_visit_type!();
|
||||
|
||||
fn visit_ident(&mut self, ident: &Ident) {
|
||||
self.insert_ident_to_current_scope(ident.to_id(), ident.span);
|
||||
}
|
||||
|
||||
fn visit_block_stmt(&mut self, block: &BlockStmt) {
|
||||
self.scoped_indents.insert(block.span, Default::default());
|
||||
self.scope.push(block.span);
|
||||
|
||||
block.visit_children_with(self);
|
||||
|
||||
self.scoped_indents.remove(&block.span);
|
||||
self.scope.pop();
|
||||
}
|
||||
|
||||
fn visit_var_decl(&mut self, var_decl: &VarDecl) {
|
||||
let es6_var_check = !matches!(&var_decl.kind, VarDeclKind::Var);
|
||||
|
||||
var_decl.decls.iter().for_each(|declarator| {
|
||||
declarator.init.visit_with(self);
|
||||
|
||||
if let VarDeclKind::Var = var_decl.kind {
|
||||
if !self.check_vars {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.check_pat(es6_var_check, &declarator.name);
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_fn_decl(&mut self, function: &FnDecl) {
|
||||
if self.check_functions {
|
||||
self.check_ident(false, &function.ident);
|
||||
}
|
||||
|
||||
function.visit_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_class_decl(&mut self, class: &ClassDecl) {
|
||||
if self.check_classes {
|
||||
self.check_ident(false, &class.ident);
|
||||
}
|
||||
|
||||
class.visit_children_with(self);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user