rslint_core/groups/errors/
no_this_before_super.rs

1use crate::rule_prelude::*;
2use ast::{ClassDecl, ClassElement};
3
4declare_lint! {
5    /**
6    Prevent the use of `this` / `super` before calling `super()`.
7
8    In the constructor of a derived class (`extends` a class), using `this` / `super` before the
9    `super()` call, will throw an error.
10
11    ## Incorrect Code Examples
12
13    ```js
14    class A extends B {
15        constructor() {
16            this.a = 0;
17            super();
18        }
19    }
20    ```
21
22    ```js
23    class A extends B {
24        constructor() {
25            this.foo();
26            super();
27        }
28    }
29    ```
30
31    ```js
32
33    class A extends B {
34        constructor() {
35            super.foo();
36            super();
37        }
38    }
39    ```
40
41    ```js
42    class A extends B {
43        constructor() {
44            super(this.foo());
45        }
46    }
47    ```
48
49    ## Correct Code Examples
50
51
52    ```js
53    class A {
54        constructor() {
55            this.a = 0; // OK, this class doesn't have an `extends` clause.
56        }
57    }
58    ```
59
60    ```js
61    class A extends B {
62        constructor() {
63            super();
64            this.a = 0; // OK, this is after `super()`.
65        }
66    }
67    ```
68
69    ```js
70    class A extends B {
71        foo() {
72            this.a = 0; // OK. this is not in a constructor.
73        }
74    }
75    ```
76    */
77    #[derive(Default)]
78    NoThisBeforeSuper,
79    errors,
80    tags(Recommended),
81    "no-this-before-super",
82}
83
84#[typetag::serde]
85impl CstRule for NoThisBeforeSuper {
86    fn check_node(&self, node: &SyntaxNode, ctx: &mut RuleCtx) -> Option<()> {
87        let class_decl = node.try_to::<ClassDecl>()?;
88        let constructor = class_decl.body()?.elements().find_map(|x| match x {
89            ClassElement::Constructor(c) => Some(c),
90            _ => None,
91        })?;
92
93        let mut super_call = None;
94        let mut this_expr = None;
95        for node in constructor.syntax().descendants_with_tokens() {
96            if node.kind() == SyntaxKind::SUPER_CALL {
97                super_call = Some(node.text_range());
98
99                if let Some(this) = node.as_node().and_then(|node| {
100                    node.descendants_with_tokens().skip(2).find(|n| {
101                        n.kind() == SyntaxKind::SUPER_KW || n.kind() == SyntaxKind::THIS_EXPR
102                    })
103                }) {
104                    this_expr = Some(this);
105                }
106
107                continue;
108            }
109
110            if (node.kind() == SyntaxKind::THIS_EXPR
111                || (node.kind() == SyntaxKind::SUPER_KW
112                    && node.parent()?.kind() != SyntaxKind::SUPER_CALL))
113                && super_call.is_none()
114            {
115                this_expr = Some(node);
116                // we don't `break` here, so we can still find the `super();` call,
117                // even if it's after the this expression
118            }
119        }
120
121        match (super_call, this_expr) {
122            (Some(super_call), Some(this)) => {
123                let use_name = match this.kind() {
124                    SyntaxKind::SUPER_KW => "super",
125                    SyntaxKind::THIS_EXPR => "this",
126                    _ => unreachable!(),
127                };
128                let err = ctx
129                    .err(
130                        self.name(),
131                        format!("`{}` is not allowed before calling `super()`", use_name),
132                    )
133                    .primary(this, format!("`{}` is used here...", use_name))
134                    .secondary(super_call, "...but `super` is called here")
135                    .footer_note(format!(
136                        "using `{}` before calling `super()` will result in a runtime error",
137                        use_name
138                    ));
139                ctx.add_err(err);
140
141                None
142            }
143            _ => None,
144        }
145    }
146}
147
148rule_tests! {
149    NoThisBeforeSuper::default(),
150    err: {
151        "class A extends B { constructor() { this.a = 0; super(); } }",
152        "class A extends B { constructor() { this.foo(); super(); } }",
153        "class A extends B { constructor() { super.foo(); super(); } }",
154        "class A extends B { constructor() { super(this.foo()); } }",
155    },
156    ok: {
157        "class A { constructor() { this.a = 0; } }",
158        "class A extends B { constructor() { super(); this.a = 0; } }",
159        "class A extends B { foo() { this.a = 0; } }",
160    }
161}