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 analysis.if_statements;
6 
7 import dparse.ast;
8 import dparse.lexer;
9 import dparse.formatter;
10 import analysis.base;
11 import dsymbol.scope_ : Scope;
12 
13 class IfStatementCheck : BaseAnalyzer
14 {
15 	alias visit = BaseAnalyzer.visit;
16 	this(string fileName, const(Scope)* sc)
17 	{
18 		super(fileName, sc);
19 	}
20 
21 	override void visit(const IfStatement ifStatement)
22 	{
23 		import std.string : format;
24 		import std.algorithm : sort, countUntil;
25 		import std.array : appender;
26 
27 		++depth;
28 
29 		if (ifStatement.expression.items.length == 1
30 			&& (cast(AndAndExpression) ifStatement.expression.items[0]) is null)
31 		{
32 			redundancyCheck(ifStatement.expression, ifStatement.expression.line,
33 				ifStatement.expression.column);
34 		}
35 		inIfExpresson = true;
36 		ifStatement.expression.accept(this);
37 		inIfExpresson = false;
38 		ifStatement.thenStatement.accept(this);
39 		if (expressions.length)
40 			expressions = expressions[0 .. expressions.countUntil!(a => a.depth + 1 >= depth)];
41 		if (ifStatement.elseStatement)
42 			ifStatement.elseStatement.accept(this);
43 		--depth;
44 	}
45 
46 	override void visit(const AndAndExpression andAndExpression)
47 	{
48 		if (inIfExpresson)
49 		{
50 			redundancyCheck(andAndExpression, andAndExpression.line,
51 				andAndExpression.column);
52 			redundancyCheck(andAndExpression.left, andAndExpression.line,
53 				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,
72 		size_t column)
73 	{
74 		import std.string : format;
75 		import std.array : appender;
76 		import std.algorithm : sort;
77 
78 		if (expression is null)
79 			return;
80 		auto app = appender!string();
81 		dparse.formatter.format(app, expression);
82 		immutable size_t prevLocation = alreadyChecked(app.data, line, column);
83 		if (prevLocation != size_t.max)
84 		{
85 			addErrorMessage(line, column, KEY,
86 				"Expression %s is true: already checked on line %d.".format(
87 				expressions[prevLocation].formatted,
88 				expressions[prevLocation].line));
89 		}
90 		else
91 		{
92 			expressions ~= ExpressionInfo(app.data, line, column, depth);
93 			sort(expressions);
94 		}
95 	}
96 
97 	size_t alreadyChecked(string expressionText, size_t line, size_t column)
98 	{
99 		foreach (i, ref info; expressions)
100 		{
101 			if (info.line == line && info.column == column)
102 				continue;
103 			if (info.formatted == expressionText)
104 				return i;
105 		}
106 		return size_t.max;
107 	}
108 
109 	bool inIfExpresson;
110 	int depth;
111 	ExpressionInfo[] expressions;
112 	enum string KEY = "dscanner.if_statement";
113 }
114 
115 private struct ExpressionInfo
116 {
117 	int opCmp(ref const ExpressionInfo other) const nothrow
118 	{
119 		if (line < other.line || (line == other.line && column < other.column))
120 			return 1;
121 		if (line == other.line && column == other.column)
122 			return 0;
123 		return -1;
124 	}
125 
126 	string formatted;
127 	size_t line;
128 	size_t column;
129 	int depth;
130 }