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 class IfStatementCheck : BaseAnalyzer
14 {
15 	alias visit = BaseAnalyzer.visit;
16 	this(string fileName, const(Scope)* sc, bool skipTests = false)
17 	{
18 		super(fileName, sc, skipTests);
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,
33 					ifStatement.expression.line, 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, andAndExpression.column);
51 			redundancyCheck(andAndExpression.left, andAndExpression.line, andAndExpression.column);
52 			redundancyCheck(andAndExpression.right, andAndExpression.line,
53 					andAndExpression.column);
54 		}
55 		andAndExpression.accept(this);
56 	}
57 
58 	override void visit(const OrOrExpression orOrExpression)
59 	{
60 		// intentionally does nothing
61 	}
62 
63 private:
64 	invariant
65 	{
66 		assert(depth >= 0);
67 	}
68 
69 	void redundancyCheck(const ExpressionNode expression, size_t line, size_t column)
70 	{
71 		import std.string : format;
72 		import std.array : appender;
73 		import std.algorithm : sort;
74 
75 		if (expression is null)
76 			return;
77 		auto app = appender!string();
78 		dparse.formatter.format(app, expression);
79 		immutable size_t prevLocation = alreadyChecked(app.data, line, column);
80 		if (prevLocation != size_t.max)
81 		{
82 			addErrorMessage(line, column, KEY, "Expression %s is true: already checked on line %d.".format(
83 					expressions[prevLocation].formatted, expressions[prevLocation].line));
84 		}
85 		else
86 		{
87 			expressions ~= ExpressionInfo(app.data, line, column, depth);
88 			sort(expressions);
89 		}
90 	}
91 
92 	size_t alreadyChecked(string expressionText, size_t line, size_t column)
93 	{
94 		foreach (i, ref info; expressions)
95 		{
96 			if (info.line == line && info.column == column)
97 				continue;
98 			if (info.formatted == expressionText)
99 				return i;
100 		}
101 		return size_t.max;
102 	}
103 
104 	bool inIfExpresson;
105 	int depth;
106 	ExpressionInfo[] expressions;
107 	enum string KEY = "dscanner.if_statement";
108 }
109 
110 private struct ExpressionInfo
111 {
112 	int opCmp(ref const ExpressionInfo other) const nothrow
113 	{
114 		if (line < other.line || (line == other.line && column < other.column))
115 			return 1;
116 		if (line == other.line && column == other.column)
117 			return 0;
118 		return -1;
119 	}
120 
121 	string formatted;
122 	size_t line;
123 	size_t column;
124 	int depth;
125 }