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