use super::{Context, LintRule};
use crate::handler::{Handler, Traverse};
use crate::swc_util::find_lhs_ids;
use crate::tags::{self, Tags};
use crate::Program;
use deno_ast::view::AssignExpr;
use deno_ast::{BindingKind, SourceRanged};
use derive_more::Display;
#[derive(Debug)]
pub struct NoFuncAssign;
const CODE: &str = "no-func-assign";
#[derive(Display)]
enum NoFuncAssignMessage {
#[display(fmt = "Reassigning function declaration is not allowed")]
Unexpected,
}
#[derive(Display)]
enum NoFuncAssignHint {
#[display(
fmt = "Remove or rework the reassignment of the existing function"
)]
RemoveOrRework,
}
impl LintRule for NoFuncAssign {
fn tags(&self) -> Tags {
&[tags::RECOMMENDED]
}
fn code(&self) -> &'static str {
CODE
}
fn lint_program_with_ast_view<'view>(
&self,
context: &mut Context<'view>,
program: Program<'view>,
) {
NoFuncAssignVisitor.traverse(program, context);
}
}
struct NoFuncAssignVisitor;
impl Handler for NoFuncAssignVisitor {
fn assign_expr(&mut self, assign_expr: &AssignExpr, ctx: &mut Context) {
let ids = find_lhs_ids(&assign_expr.left);
for id in ids {
let var = ctx.scope().var(&id);
if let Some(var) = var {
if let BindingKind::Function = var.kind() {
ctx.add_diagnostic_with_hint(
assign_expr.range(),
CODE,
NoFuncAssignMessage::Unexpected,
NoFuncAssignHint::RemoveOrRework,
);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_func_assign_valid() {
assert_lint_ok! {
NoFuncAssign,
"function foo() { var foo = bar; }",
"function foo(foo) { foo = bar; }",
"function foo() { var foo; foo = bar; }",
"var foo = () => {}; foo = bar;",
"var foo = function() {}; foo = bar;",
"var foo = function() { foo = bar; };",
"import bar from 'bar'; function foo() { var foo = bar; }",
};
}
#[test]
fn no_func_assign_invalid() {
assert_lint_err! {
NoFuncAssign,
"function foo() {}; foo = bar;": [
{
col: 19,
message: NoFuncAssignMessage::Unexpected,
hint: NoFuncAssignHint::RemoveOrRework,
}
],
"function foo() { foo = bar; }": [
{
col: 17,
message: NoFuncAssignMessage::Unexpected,
hint: NoFuncAssignHint::RemoveOrRework,
}
],
"foo = bar; function foo() { };": [
{
col: 0,
message: NoFuncAssignMessage::Unexpected,
hint: NoFuncAssignHint::RemoveOrRework,
}
],
"[foo] = bar; function foo() { }": [
{
col: 0,
message: NoFuncAssignMessage::Unexpected,
hint: NoFuncAssignHint::RemoveOrRework,
}
],
"({x: foo = 0} = bar); function foo() { };": [
{
col: 1,
message: NoFuncAssignMessage::Unexpected,
hint: NoFuncAssignHint::RemoveOrRework,
}
],
"function foo() { [foo] = bar; }": [
{
col: 17,
message: NoFuncAssignMessage::Unexpected,
hint: NoFuncAssignHint::RemoveOrRework,
}
],
"(function() { ({x: foo = 0} = bar); function foo() { }; })();": [
{
col: 15,
message: NoFuncAssignMessage::Unexpected,
hint: NoFuncAssignHint::RemoveOrRework,
}
],
"var a = function foo() { foo = 123; };": [
{
col: 25,
message: NoFuncAssignMessage::Unexpected,
hint: NoFuncAssignHint::RemoveOrRework,
}
],
r#"
const a = "a";
const unused = "unused";
function asdf(b: number, c: string): number {
console.log(a, b);
debugger;
return 1;
}
asdf = "foobar";
"#: [
{
col: 0,
line: 11,
message: NoFuncAssignMessage::Unexpected,
hint: NoFuncAssignHint::RemoveOrRework,
}
],
r#"
function foo() {}
let a;
a = () => {
foo = 42;
};
"#: [
{
line: 5,
col: 2,
message: NoFuncAssignMessage::Unexpected,
hint: NoFuncAssignHint::RemoveOrRework,
}
],
};
}
}