1 //          Copyright Brian Schott (Hackerpilot) 2015.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 module dscanner.analysis.if_statements;
6 
7 import dparse.ast;
8 import dparse.lexer;
9 import dparse.formatter;
10 import dscanner.analysis.base;
11 import dsymbol.scope_ : Scope;
12 
13 final class IfStatementCheck : BaseAnalyzer
14 {
15 	alias visit = BaseAnalyzer.visit;
16 	mixin AnalyzerInfo!"redundant_if_check";
17 
18 	this(string fileName, const(Scope)* sc, bool skipTests = false)
19 	{
20 		super(fileName, sc, skipTests);
21 	}
22 
23 	override void visit(const IfStatement ifStatement)
24 	{
25 		import std..string : format;
26 		import std.algorithm : sort, countUntil;
27 		import std.array : appender;
28 
29 		++depth;
30 
31 		if (ifStatement.expression.items.length == 1
32 				&& (cast(AndAndExpression) ifStatement.expression.items[0]) is null)
33 		{
34 			redundancyCheck(ifStatement.expression,
35 					ifStatement.expression.line, ifStatement.expression.column);
36 		}
37 		inIfExpresson = true;
38 		ifStatement.expression.accept(this);
39 		inIfExpresson = false;
40 		ifStatement.thenStatement.accept(this);
41 		if (expressions.length)
42 			expressions = expressions[0 .. expressions.countUntil!(a => a.depth + 1 >= depth)];
43 		if (ifStatement.elseStatement)
44 			ifStatement.elseStatement.accept(this);
45 		--depth;
46 	}
47 
48 	override void visit(const AndAndExpression andAndExpression)
49 	{
50 		if (inIfExpresson)
51 		{
52 			redundancyCheck(andAndExpression, andAndExpression.line, andAndExpression.column);
53 			redundancyCheck(andAndExpression.left, andAndExpression.line, andAndExpression.column);
54 			redundancyCheck(andAndExpression.right, andAndExpression.line,
55 					andAndExpression.column);
56 		}
57 		andAndExpression.accept(this);
58 	}
59 
60 	override void visit(const OrOrExpression orOrExpression)
61 	{
62 		// intentionally does nothing
63 	}
64 
65 private:
66 	invariant
67 	{
68 		assert(depth >= 0);
69 	}
70 
71 	void redundancyCheck(const ExpressionNode expression, size_t line, size_t column)
72 	{
73 		import std..string : format;
74 		import std.array : appender;
75 		import std.algorithm : sort;
76 
77 		if (expression is null)
78 			return;
79 		auto app = appender!string();
80 		dparse.formatter.format(app, expression);
81 		immutable size_t prevLocation = alreadyChecked(app.data, line, column);
82 		if (prevLocation != size_t.max)
83 		{
84 			addErrorMessage(line, column, KEY, "Expression %s is true: already checked on line %d.".format(
85 					expressions[prevLocation].formatted, expressions[prevLocation].line));
86 		}
87 		else
88 		{
89 			expressions ~= ExpressionInfo(app.data, line, column, depth);
90 			sort(expressions);
91 		}
92 	}
93 
94 	size_t alreadyChecked(string expressionText, size_t line, size_t column)
95 	{
96 		foreach (i, ref info; expressions)
97 		{
98 			if (info.line == line && info.column == column)
99 				continue;
100 			if (info.formatted == expressionText)
101 				return i;
102 		}
103 		return size_t.max;
104 	}
105 
106 	bool inIfExpresson;
107 	int depth;
108 	ExpressionInfo[] expressions;
109 	enum string KEY = "dscanner.if_statement";
110 }
111 
112 private struct ExpressionInfo
113 {
114 	int opCmp(ref const ExpressionInfo other) const nothrow
115 	{
116 		if (line < other.line || (line == other.line && column < other.column))
117 			return 1;
118 		if (line == other.line && column == other.column)
119 			return 0;
120 		return -1;
121 	}
122 
123 	string formatted;
124 	size_t line;
125 	size_t column;
126 	int depth;
127 }