1 // Distributed under the Boost Software License, Version 1.0.
2 //	  (See accompanying file LICENSE_1_0.txt or copy at
3 //			http://www.boost.org/LICENSE_1_0.txt)
4 
5 module dscanner.analysis.if_constraints_indent;
6 
7 import dparse.lexer;
8 import dparse.ast;
9 import dscanner.analysis.base : BaseAnalyzer, Message;
10 import dsymbol.scope_ : Scope;
11 
12 import std.algorithm.iteration : filter;
13 import std.range;
14 
15 /**
16 Checks whether all if constraints have the same indention as their declaration.
17 */
18 class IfConstraintsIndentCheck : BaseAnalyzer
19 {
20 	///
21 	this(string fileName, const(Token)[] tokens, bool skipTests = false)
22 	{
23 		super(fileName, null, skipTests);
24 
25 		// libdparse columns start at 1
26 		foreach (t; tokens)
27 		{
28 			while (firstSymbolAtLine.length < t.line - 1)
29 				firstSymbolAtLine ~= Pos(1);
30 
31 			if (firstSymbolAtLine.length < t.line)
32 				firstSymbolAtLine ~= Pos(t.column, t.type == tok!"if");
33 		}
34 	}
35 
36 	override void visit(const FunctionDeclaration decl)
37 	{
38 		if (decl.constraint !is null)
39 			checkConstraintSpace(decl.constraint, decl.name);
40 	}
41 
42 	override void visit(const InterfaceDeclaration decl)
43 	{
44 		if (decl.constraint !is null)
45 			checkConstraintSpace(decl.constraint, decl.name);
46 	}
47 
48 
49 	override void visit(const ClassDeclaration decl)
50 	{
51 		if (decl.constraint !is null)
52 			checkConstraintSpace(decl.constraint, decl.name);
53 	}
54 
55 	override void visit(const TemplateDeclaration decl)
56 	{
57 		if (decl.constraint !is null)
58 			checkConstraintSpace(decl.constraint, decl.name);
59 	}
60 
61 	override void visit(const UnionDeclaration decl)
62 	{
63 		if (decl.constraint !is null)
64 			checkConstraintSpace(decl.constraint, decl.name);
65 	}
66 
67 	override void visit(const StructDeclaration decl)
68 	{
69 		if (decl.constraint !is null)
70 			checkConstraintSpace(decl.constraint, decl.name);
71 	}
72 
73 	override void visit(const Constructor decl)
74 	{
75 		if (decl.constraint !is null)
76 			checkConstraintSpace(decl.constraint, decl.line);
77 	}
78 
79 	alias visit = ASTVisitor.visit;
80 
81 private:
82 
83 	enum string KEY = "dscanner.style.if_constraints_indent";
84 	enum string MESSAGE = "If constraints should have the same indentation as the function";
85 
86 	Pos[] firstSymbolAtLine;
87 	static struct Pos
88 	{
89 		size_t column;
90 		bool isIf;
91 	}
92 
93 	/**
94 	Check indentation of constraints
95 	*/
96 	void checkConstraintSpace(const Constraint constraint, const Token token)
97 	{
98 		checkConstraintSpace(constraint, token.line);
99 	}
100 
101 	void checkConstraintSpace(const Constraint constraint, size_t line)
102 	{
103 		// dscanner lines start at 1
104 		auto pDecl = firstSymbolAtLine[line - 1];
105 
106 		// search for constraint if (might not be on the same line as the expression)
107 		auto r = firstSymbolAtLine[line .. constraint.expression.line].retro.filter!(s => s.isIf);
108 
109 		// no hit = constraint is on the same line
110 		if (r.empty)
111 			addErrorMessage(constraint.expression.line, constraint.expression.column, KEY, MESSAGE);
112 		else if (pDecl.column != r.front.column)
113 			addErrorMessage(constraint.expression.line, constraint.expression.column, KEY, MESSAGE);
114 	}
115 }
116 
117 unittest
118 {
119 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
120 	import dscanner.analysis.helpers : assertAnalyzerWarnings;
121 	import std.format : format;
122 	import std.stdio : stderr;
123 
124 	StaticAnalysisConfig sac = disabledConfig();
125 	sac.if_constraints_indent = Check.enabled;
126 
127 	assertAnalyzerWarnings(q{
128 void foo(R)(R r)
129 if (R == int)
130 {}
131 
132 void foo(R)(R r)
133 	if (R == int) // [warn]: %s
134 {}
135 	}}.format(
136 		IfConstraintsIndentCheck.MESSAGE,
137 	), sac);
138 
139 	assertAnalyzerWarnings(q{
140 	void foo(R)(R r)
141 	if (R == int)
142 	{}
143 
144 	void foo(R)(R r)
145 if (R == int) // [warn]: %s
146 	{}
147 
148 	void foo(R)(R r)
149 		if (R == int) // [warn]: %s
150 	{}
151 	}}.format(
152 		IfConstraintsIndentCheck.MESSAGE,
153 		IfConstraintsIndentCheck.MESSAGE,
154 	), sac);
155 
156 	assertAnalyzerWarnings(q{
157 	struct Foo(R)
158 	if (R == int)
159 	{}
160 
161 	struct Foo(R)
162 if (R == int) // [warn]: %s
163 	{}
164 
165 	struct Foo(R)
166 		if (R == int) // [warn]: %s
167 	{}
168 	}}.format(
169 		IfConstraintsIndentCheck.MESSAGE,
170 		IfConstraintsIndentCheck.MESSAGE,
171 	), sac);
172 
173 	// test example from Phobos
174 	assertAnalyzerWarnings(q{
175 Num abs(Num)(Num x) @safe pure nothrow
176 if (is(typeof(Num.init >= 0)) && is(typeof(-Num.init)) &&
177     !(is(Num* : const(ifloat*)) || is(Num* : const(idouble*))
178     || is(Num* : const(ireal*))))
179 {
180     static if (isFloatingPoint!(Num))
181         return fabs(x);
182     else
183         return x >= 0 ? x : -x;
184 }
185 	}, sac);
186 
187 	// weird constraint formatting
188 	assertAnalyzerWarnings(q{
189 	struct Foo(R)
190 	if
191 	(R == int)
192 	{}
193 
194 	struct Foo(R)
195 	if
196 		(R == int)
197 	{}
198 
199 	struct Foo(R)
200 if
201 	(R == int) // [warn]: %s
202 	{}
203 
204 	struct Foo(R)
205 	if (
206 	R == int)
207 	{}
208 
209 	struct Foo(R)
210 	if (
211 		R == int
212 	)
213 	{}
214 
215 	struct Foo(R)
216 		if (
217 		R == int // [warn]: %s
218 	) {}
219 	}}.format(
220 		IfConstraintsIndentCheck.MESSAGE,
221 		IfConstraintsIndentCheck.MESSAGE,
222 	), sac);
223 
224 	// constraint on the same line
225 	assertAnalyzerWarnings(q{
226 	struct CRC(uint N, ulong P) if (N == 32 || N == 64) // [warn]: %s
227 	{}
228 	}}.format(
229 		IfConstraintsIndentCheck.MESSAGE,
230 	), sac);
231 
232 	stderr.writeln("Unittest for IfConstraintsIndentCheck passed.");
233 }