D-Scanner/src/dscanner/analysis/static_if_else.d

170 lines
4.0 KiB
D

// Copyright Chris Wright (dhasenan) 2016.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
module dscanner.analysis.static_if_else;
import dparse.ast;
import dparse.lexer;
import dscanner.analysis.base;
import dscanner.utils : safeAccess;
/**
* Checks for potentially mistaken static if / else if.
*
* It's potentially valid to write:
* ---
* static if (foo) {
* } else if (bar) {
* }
* ---
*
* However, it's more likely that this is a mistake.
*/
final class StaticIfElse : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;
mixin AnalyzerInfo!"static_if_else_check";
this(string fileName, bool skipTests = false)
{
super(fileName, null, skipTests);
}
override void visit(const ConditionalStatement cc)
{
cc.accept(this);
if (cc.falseStatement is null)
{
return;
}
const(IfStatement) ifStmt = getIfStatement(cc);
if (!ifStmt)
{
return;
}
auto tokens = ifStmt.tokens[0 .. 1];
// extend one token to include `else` before this if
tokens = (tokens.ptr - 1)[0 .. 2];
addErrorMessage(tokens, KEY, "Mismatched static if. Use 'else static if' here.",
[
AutoFix.insertionBefore(tokens[$ - 1], "static "),
AutoFix.resolveLater("Wrap '{}' block around 'if'", [tokens[0].index, ifStmt.tokens[$ - 1].index, 0])
]);
}
const(IfStatement) getIfStatement(const ConditionalStatement cc)
{
return safeAccess(cc).falseStatement.statement.statementNoCaseNoDefault.ifStatement;
}
override AutoFix.CodeReplacement[] resolveAutoFix(
const Module mod,
scope const(Token)[] tokens,
const AutoFix.ResolveContext context,
const AutoFixFormatting formatting,
)
{
import dscanner.analysis.helpers : getLineIndentation;
import std.algorithm : countUntil;
auto beforeElse = tokens.countUntil!(a => a.index == context.params[0]);
auto lastToken = tokens.countUntil!(a => a.index == context.params[1]);
if (beforeElse == -1 || lastToken == -1)
throw new Exception("got different tokens than what was used to generate this autofix");
auto indentation = getLineIndentation(tokens, tokens[beforeElse].line, formatting);
string beforeIf = formatting.getWhitespaceBeforeOpeningBrace(indentation, false)
~ "{" ~ formatting.eol ~ indentation;
string afterIf = formatting.eol ~ indentation ~ "}";
return AutoFix.replacement([tokens[beforeElse].index + 4, tokens[beforeElse + 1].index], beforeIf, "")
.concat(AutoFix.indentLines(tokens[beforeElse + 1 .. lastToken + 1], formatting))
.concat(AutoFix.insertionAfter(tokens[lastToken], afterIf))
.expectReplacements;
}
enum KEY = "dscanner.suspicious.static_if_else";
}
unittest
{
import dscanner.analysis.config : Check, disabledConfig, StaticAnalysisConfig;
import dscanner.analysis.helpers : assertAnalyzerWarnings, assertAutoFix;
import std.stdio : stderr;
StaticAnalysisConfig sac = disabledConfig();
sac.static_if_else_check = Check.enabled;
assertAnalyzerWarnings(q{
void foo() {
static if (false)
auto a = 0;
else if (true) /+
^^^^^^^ [warn]: Mismatched static if. Use 'else static if' here. +/
auto b = 1;
}
}c, sac);
// Explicit braces, so no warning.
assertAnalyzerWarnings(q{
void foo() {
static if (false)
auto a = 0;
else {
if (true)
auto b = 1;
}
}
}c, sac);
assertAutoFix(q{
void foo() {
static if (false)
auto a = 0;
else if (true) // fix:0
auto b = 1;
}
void bar() {
static if (false)
auto a = 0;
else if (true) // fix:1
auto b = 1;
}
void baz() {
static if (false)
auto a = 0;
else if (true) { // fix:1
auto b = 1;
}
}
}c, q{
void foo() {
static if (false)
auto a = 0;
else static if (true) // fix:0
auto b = 1;
}
void bar() {
static if (false)
auto a = 0;
else {
if (true) // fix:1
auto b = 1;
}
}
void baz() {
static if (false)
auto a = 0;
else {
if (true) { // fix:1
auto b = 1;
}
}
}
}c, sac);
stderr.writeln("Unittest for StaticIfElse passed.");
}