Implement #232
This commit is contained in:
parent
6b7cb5941f
commit
dbea951e5d
|
@ -83,4 +83,7 @@ struct StaticAnalysisConfig
|
||||||
|
|
||||||
@INI("Checks for variables that could be declared immutable")
|
@INI("Checks for variables that could be declared immutable")
|
||||||
bool could_be_immutable_check = false; // disabled by default for now
|
bool could_be_immutable_check = false; // disabled by default for now
|
||||||
|
|
||||||
|
@INI("Checks for redundant expressions in if statements")
|
||||||
|
bool redundant_if_check;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
// Copyright Brian Schott (Hackerpilot) 2015.
|
||||||
|
// 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 analysis.if_statements;
|
||||||
|
|
||||||
|
import std.d.ast;
|
||||||
|
import std.d.lexer;
|
||||||
|
import std.d.formatter;
|
||||||
|
import analysis.base;
|
||||||
|
|
||||||
|
class IfStatementCheck : BaseAnalyzer
|
||||||
|
{
|
||||||
|
alias visit = BaseAnalyzer.visit;
|
||||||
|
this(string fileName)
|
||||||
|
{
|
||||||
|
import std.container.binaryheap : heapify;
|
||||||
|
|
||||||
|
super(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const IfStatement ifStatement)
|
||||||
|
{
|
||||||
|
import std.format : format;
|
||||||
|
import std.algorithm : sort, countUntil;
|
||||||
|
import std.array : appender;
|
||||||
|
|
||||||
|
++depth;
|
||||||
|
|
||||||
|
if (ifStatement.expression.items.length == 1
|
||||||
|
&& (cast(AndAndExpression) ifStatement.expression.items[0]) is null)
|
||||||
|
{
|
||||||
|
redundancyCheck(ifStatement.expression, ifStatement.expression.line,
|
||||||
|
ifStatement.expression.column);
|
||||||
|
}
|
||||||
|
inIfExpresson = true;
|
||||||
|
ifStatement.expression.accept(this);
|
||||||
|
inIfExpresson = false;
|
||||||
|
ifStatement.thenStatement.accept(this);
|
||||||
|
if (expressions.length)
|
||||||
|
expressions = expressions[0 .. expressions.countUntil!(a => a.depth + 1 >= depth)];
|
||||||
|
if (ifStatement.elseStatement)
|
||||||
|
ifStatement.elseStatement.accept(this);
|
||||||
|
--depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const AndAndExpression andAndExpression)
|
||||||
|
{
|
||||||
|
if (inIfExpresson)
|
||||||
|
{
|
||||||
|
redundancyCheck(andAndExpression, andAndExpression.line,
|
||||||
|
andAndExpression.column);
|
||||||
|
redundancyCheck(andAndExpression.left, andAndExpression.line,
|
||||||
|
andAndExpression.column);
|
||||||
|
redundancyCheck(andAndExpression.right, andAndExpression.line,
|
||||||
|
andAndExpression.column);
|
||||||
|
}
|
||||||
|
andAndExpression.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const OrOrExpression orOrExpression)
|
||||||
|
{
|
||||||
|
// intentionally does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
invariant
|
||||||
|
{
|
||||||
|
assert(depth >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void redundancyCheck(const ExpressionNode expression, size_t line,
|
||||||
|
size_t column)
|
||||||
|
{
|
||||||
|
import std.format : format;
|
||||||
|
import std.array : appender;
|
||||||
|
import std.algorithm : sort;
|
||||||
|
|
||||||
|
if (expression is null)
|
||||||
|
return;
|
||||||
|
auto app = appender!string();
|
||||||
|
std.d.formatter.format(app, expression);
|
||||||
|
immutable size_t prevLocation = alreadyChecked(app.data, line, column);
|
||||||
|
if (prevLocation != size_t.max)
|
||||||
|
{
|
||||||
|
addErrorMessage(line, column, KEY,
|
||||||
|
"Expression %s is true: already checked on line %d.".format(
|
||||||
|
expressions[prevLocation].formatted,
|
||||||
|
expressions[prevLocation].line));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
expressions ~= ExpressionInfo(app.data, line, column, depth);
|
||||||
|
sort(expressions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t alreadyChecked(string expressionText, size_t line, size_t column)
|
||||||
|
{
|
||||||
|
foreach (i, ref info; expressions)
|
||||||
|
{
|
||||||
|
if (info.line == line && info.column == column)
|
||||||
|
continue;
|
||||||
|
if (info.formatted == expressionText)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return size_t.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inIfExpresson;
|
||||||
|
int depth;
|
||||||
|
ExpressionInfo[] expressions;
|
||||||
|
private enum KEY = "dscanner.if_statement";
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ExpressionInfo
|
||||||
|
{
|
||||||
|
int opCmp(ref const ExpressionInfo other) const nothrow
|
||||||
|
{
|
||||||
|
if (line < other.line || (line == other.line && column < other.column))
|
||||||
|
return 1;
|
||||||
|
if (line == other.line && column == other.column)
|
||||||
|
return 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
string formatted;
|
||||||
|
size_t line;
|
||||||
|
size_t column;
|
||||||
|
int depth;
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import analysis.comma_expression;
|
||||||
import analysis.function_attributes;
|
import analysis.function_attributes;
|
||||||
import analysis.local_imports;
|
import analysis.local_imports;
|
||||||
import analysis.unmodified;
|
import analysis.unmodified;
|
||||||
|
import analysis.if_statements;
|
||||||
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
||||||
|
@ -112,8 +113,8 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config)
|
||||||
writeln("}");
|
writeln("}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// For multiple files
|
/// For multiple files
|
||||||
// Returns: true if there were errors
|
/// Returns: true if there were errors
|
||||||
bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticAnalyze = true)
|
bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticAnalyze = true)
|
||||||
{
|
{
|
||||||
bool hasErrors = false;
|
bool hasErrors = false;
|
||||||
|
@ -187,6 +188,7 @@ MessageSet analyze(string fileName, const Module m,
|
||||||
if (analysisConfig.comma_expression_check) checks ~= new CommaExpressionCheck(fileName);
|
if (analysisConfig.comma_expression_check) checks ~= new CommaExpressionCheck(fileName);
|
||||||
if (analysisConfig.local_import_check) checks ~= new LocalImportCheck(fileName);
|
if (analysisConfig.local_import_check) checks ~= new LocalImportCheck(fileName);
|
||||||
if (analysisConfig.could_be_immutable_check) checks ~= new UnmodifiedFinder(fileName);
|
if (analysisConfig.could_be_immutable_check) checks ~= new UnmodifiedFinder(fileName);
|
||||||
|
if (analysisConfig.redundant_if_check) checks ~= new IfStatementCheck(fileName);
|
||||||
|
|
||||||
foreach (check; checks)
|
foreach (check; checks)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue