Skip to main content

forge_lint/sol/info/
redundant_base_constructor_call.rs

1use super::RedundantBaseConstructorCall;
2use crate::{
3    linter::{LateLintPass, LintContext, Suggestion},
4    sol::{Severity, SolLint},
5};
6use solar::{
7    interface::{BytePos, Span, diagnostics::Applicability},
8    sema::hir::{self, ItemId},
9};
10
11declare_forge_lint!(
12    REDUNDANT_BASE_CONSTRUCTOR_CALL,
13    Severity::Info,
14    "redundant-base-constructor-call",
15    "explicit empty base-constructor arguments are redundant"
16);
17
18impl<'hir> LateLintPass<'hir> for RedundantBaseConstructorCall {
19    fn check_contract(
20        &mut self,
21        ctx: &LintContext,
22        hir: &'hir hir::Hir<'hir>,
23        contract: &'hir hir::Contract<'hir>,
24    ) {
25        // `contract X is A(...), B(...)` clauses.
26        // Removing only the `()` is enough: `is A` is valid Solidity.
27        for m in contract.bases_args {
28            try_emit(ctx, hir, m, m.args.span);
29        }
30    }
31
32    fn check_function(
33        &mut self,
34        ctx: &LintContext,
35        hir: &'hir hir::Hir<'hir>,
36        func: &'hir hir::Function<'hir>,
37    ) {
38        // `constructor() A(...) {}` modifier-style base calls.
39        if !matches!(func.kind, hir::FunctionKind::Constructor) {
40            return;
41        }
42        for m in func.modifiers {
43            // Base-constructor invocations resolve to a contract; real modifiers resolve to
44            // functions.
45            if matches!(m.id, ItemId::Contract(_)) {
46                // The bare base name `A` (without parens) is not valid in a constructor's
47                // modifier list, so the whole `A()` must be removed. Extend the span to also
48                // swallow one leading whitespace char to avoid leaving a double space.
49                try_emit(ctx, hir, m, expand_to_leading_ws(ctx, m.span));
50            }
51        }
52    }
53}
54
55fn try_emit<'hir>(
56    ctx: &LintContext,
57    hir: &'hir hir::Hir<'hir>,
58    m: &'hir hir::Modifier<'hir>,
59    fix_span: Span,
60) {
61    let ItemId::Contract(base_id) = m.id else { return };
62
63    // `is A` (no parens written) — nothing to flag.
64    if m.args.is_dummy() {
65        return;
66    }
67    // `A(args...)` with real arguments — not redundant.
68    if !m.args.is_empty() {
69        return;
70    }
71
72    // Empty `()`. Redundant only when the base ctor takes no parameters
73    // (or the base declares no constructor at all).
74    let base = hir.contract(base_id);
75    let redundant = match base.ctor {
76        None => true,
77        Some(c) => hir.function(c).parameters.is_empty(),
78    };
79    if !redundant {
80        return;
81    }
82
83    // Only emit a machine-applicable fix if the args span really is just `()` (no comments,
84    // whitespace, etc. that we'd silently drop). Otherwise fall back to a plain diagnostic.
85    let safe_to_fix = ctx.span_to_snippet(m.args.span).map(|s| s.trim() == "()").unwrap_or(false);
86
87    if safe_to_fix {
88        ctx.emit_with_suggestion(
89            &REDUNDANT_BASE_CONSTRUCTOR_CALL,
90            m.args.span,
91            Suggestion::fix(String::new(), Applicability::MachineApplicable)
92                .with_span(fix_span)
93                .with_desc("remove redundant base-constructor call"),
94        );
95    } else {
96        ctx.emit(&REDUNDANT_BASE_CONSTRUCTOR_CALL, m.args.span);
97    }
98}
99
100/// Extends `span` to start one byte earlier when that byte is an ASCII space or tab.
101///
102/// Used so that removing a modifier-list base call like `A()` from
103/// `constructor() ... A() {}` doesn't leave a stray double space in the source.
104fn expand_to_leading_ws(ctx: &LintContext, span: Span) -> Span {
105    if span.is_dummy() || span.lo() == BytePos(0) {
106        return span;
107    }
108    let prev = Span::new(span.lo() - BytePos(1), span.lo());
109    match ctx.span_to_snippet(prev).as_deref() {
110        Some(" " | "\t") => span.with_lo(span.lo() - BytePos(1)),
111        _ => span,
112    }
113}