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