1 //          Copyright Brian Schott (Hackerpilot) 2014.
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 
6 module analysis.undocumented;
7 
8 import analysis.base;
9 import dsymbol.scope_ : Scope;
10 import dparse.ast;
11 import dparse.lexer;
12 
13 import std.regex : ctRegex, matchAll;
14 import std.stdio;
15 
16 /**
17  * Checks for undocumented public declarations. Ignores some operator overloads,
18  * main functions, and functions whose name starts with "get" or "set".
19  */
20 class UndocumentedDeclarationCheck : BaseAnalyzer
21 {
22 	alias visit = BaseAnalyzer.visit;
23 
24 	this(string fileName, const(Scope)* sc)
25 	{
26 		super(fileName, sc);
27 	}
28 
29 	override void visit(const Module mod)
30 	{
31 		push(tok!"public");
32 		mod.accept(this);
33 	}
34 
35 	override void visit(const Declaration dec)
36 	{
37 		if (dec.attributeDeclaration)
38 		{
39 			auto attr = dec.attributeDeclaration.attribute;
40 			if (isProtection(attr.attribute.type))
41 				set(attr.attribute.type);
42 			else if (attr.attribute == tok!"override")
43 				setOverride(true);
44 			else if (attr.deprecated_ !is null)
45 				setDeprecated(true);
46 			else if (attr.atAttribute !is null && attr.atAttribute.identifier.text == "disable")
47 				setDisabled(true);
48 		}
49 
50 		immutable bool shouldPop = dec.attributeDeclaration is null;
51 		immutable bool prevOverride = getOverride();
52 		immutable bool prevDisabled = getDisabled();
53 		immutable bool prevDeprecated = getDeprecated();
54 		bool dis = false;
55 		bool dep = false;
56 		bool ovr = false;
57 		bool pushed = false;
58 		foreach (attribute; dec.attributes)
59 		{
60 			if (isProtection(attribute.attribute.type))
61 			{
62 				if (shouldPop)
63 				{
64 					pushed = true;
65 					push(attribute.attribute.type);
66 				}
67 				else
68 					set(attribute.attribute.type);
69 			}
70 			else if (attribute.attribute == tok!"override")
71 				ovr = true;
72 			else if (attribute.deprecated_ !is null)
73 				dep = true;
74 			else if (attribute.atAttribute !is null
75 					&& attribute.atAttribute.identifier.text == "disable")
76 				dis = true;
77 		}
78 		if (ovr)
79 			setOverride(true);
80 		if (dis)
81 			setDisabled(true);
82 		if (dep)
83 			setDeprecated(true);
84 		dec.accept(this);
85 		if (shouldPop && pushed)
86 			pop();
87 		if (ovr)
88 			setOverride(prevOverride);
89 		if (dis)
90 			setDisabled(prevDisabled);
91 		if (dep)
92 			setDeprecated(prevDeprecated);
93 	}
94 
95 	override void visit(const VariableDeclaration variable)
96 	{
97 		if (!currentIsInteresting() || variable.comment.ptr !is null)
98 			return;
99 		if (variable.autoDeclaration !is null)
100 		{
101 			addMessage(variable.autoDeclaration.identifiers[0].line,
102 					variable.autoDeclaration.identifiers[0].column,
103 					variable.autoDeclaration.identifiers[0].text);
104 			return;
105 		}
106 		foreach (dec; variable.declarators)
107 		{
108 			if (dec.comment.ptr is null)
109 				addMessage(dec.name.line, dec.name.column, dec.name.text);
110 			return;
111 		}
112 	}
113 
114 	override void visit(const ConditionalDeclaration cond)
115 	{
116 		const VersionCondition ver = cond.compileCondition.versionCondition;
117 		if (ver is null || (ver.token != tok!"unittest" && ver.token.text != "none"))
118 			cond.accept(this);
119 		else if (cond.falseDeclarations.length > 0)
120 			foreach (f; cond.falseDeclarations)
121 				visit(f);
122 	}
123 
124 	override void visit(const FunctionBody fb)
125 	{
126 	}
127 
128 	override void visit(const Unittest u)
129 	{
130 	}
131 
132 	override void visit(const TraitsExpression t)
133 	{
134 	}
135 
136 	mixin V!ClassDeclaration;
137 	mixin V!InterfaceDeclaration;
138 	mixin V!StructDeclaration;
139 	mixin V!UnionDeclaration;
140 	mixin V!TemplateDeclaration;
141 	mixin V!FunctionDeclaration;
142 	mixin V!Constructor;
143 
144 private:
145 
146 	mixin template V(T)
147 	{
148 		override void visit(const T declaration)
149 		{
150 			import std.traits : hasMember;
151 
152 			if (currentIsInteresting())
153 			{
154 				if (declaration.comment.ptr is null)
155 				{
156 					static if (hasMember!(T, "name"))
157 					{
158 						static if (is(T == FunctionDeclaration))
159 						{
160 							import std.algorithm : canFind;
161 
162 							if (!(ignoredFunctionNames.canFind(declaration.name.text)
163 									|| isGetterOrSetter(declaration.name.text)
164 									|| isProperty(declaration)))
165 							{
166 								addMessage(declaration.name.line,
167 										declaration.name.column, declaration.name.text);
168 							}
169 						}
170 						else
171 						{
172 							if (declaration.name.type != tok!"")
173 								addMessage(declaration.name.line,
174 										declaration.name.column, declaration.name.text);
175 						}
176 					}
177 					else
178 					{
179 						addMessage(declaration.line, declaration.column, null);
180 					}
181 				}
182 				static if (!(is(T == TemplateDeclaration) || is(T == FunctionDeclaration)))
183 				{
184 					declaration.accept(this);
185 				}
186 			}
187 		}
188 	}
189 
190 	static bool isGetterOrSetter(string name)
191 	{
192 		return !matchAll(name, getSetRe).empty;
193 	}
194 
195 	static bool isProperty(const FunctionDeclaration dec)
196 	{
197 		if (dec.memberFunctionAttributes is null)
198 			return false;
199 		foreach (attr; dec.memberFunctionAttributes)
200 		{
201 			if (attr.atAttribute && attr.atAttribute.identifier.text == "property")
202 				return true;
203 		}
204 		return false;
205 	}
206 
207 	void addMessage(size_t line, size_t column, string name)
208 	{
209 		import std.string : format;
210 
211 		addErrorMessage(line, column, "dscanner.style.undocumented_declaration", name is null
212 				? "Public declaration is undocumented."
213 				: format("Public declaration '%s' is undocumented.", name));
214 	}
215 
216 	bool getOverride()
217 	{
218 		return stack[$ - 1].isOverride;
219 	}
220 
221 	void setOverride(bool o = true)
222 	{
223 		stack[$ - 1].isOverride = o;
224 	}
225 
226 	bool getDisabled()
227 	{
228 		return stack[$ - 1].isDisabled;
229 	}
230 
231 	void setDisabled(bool d = true)
232 	{
233 		stack[$ - 1].isDisabled = d;
234 	}
235 
236 	bool getDeprecated()
237 	{
238 		return stack[$ - 1].isDeprecated;
239 	}
240 
241 	void setDeprecated(bool d = true)
242 	{
243 		stack[$ - 1].isDeprecated = d;
244 	}
245 
246 	bool currentIsInteresting()
247 	{
248 		return stack[$ - 1].protection == tok!"public"
249 			&& !stack[$ - 1].isOverride && !stack[$ - 1].isDisabled && !stack[$ - 1].isDeprecated;
250 	}
251 
252 	void set(IdType p)
253 	in
254 	{
255 		assert(isProtection(p));
256 	}
257 	body
258 	{
259 		stack[$ - 1].protection = p;
260 	}
261 
262 	void push(IdType p)
263 	in
264 	{
265 		assert(isProtection(p));
266 	}
267 	body
268 	{
269 		stack ~= ProtectionInfo(p, false);
270 	}
271 
272 	void pop()
273 	{
274 		assert(stack.length > 1);
275 		stack = stack[0 .. $ - 1];
276 	}
277 
278 	static struct ProtectionInfo
279 	{
280 		IdType protection;
281 		bool isOverride;
282 		bool isDeprecated;
283 		bool isDisabled;
284 	}
285 
286 	ProtectionInfo[] stack;
287 }
288 
289 // Ignore undocumented symbols with these names
290 private immutable string[] ignoredFunctionNames = [
291 	"opCmp", "opEquals", "toString", "toHash", "main"
292 ];
293 
294 private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;