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 && attribute.atAttribute.identifier.text == "disable")
75 				dis = true;
76 		}
77 		if (ovr)
78 			setOverride(true);
79 		if (dis)
80 			setDisabled(true);
81 		if (dep)
82 			setDeprecated(true);
83 		dec.accept(this);
84 		if (shouldPop && pushed)
85 			pop();
86 		if (ovr)
87 			setOverride(prevOverride);
88 		if (dis)
89 			setDisabled(prevDisabled);
90 		if (dep)
91 			setDeprecated(prevDeprecated);
92 	}
93 
94 	override void visit(const VariableDeclaration variable)
95 	{
96 		if (!currentIsInteresting() || variable.comment !is null)
97 			return;
98 		if (variable.autoDeclaration !is null)
99 		{
100 			addMessage(variable.autoDeclaration.identifiers[0].line,
101 				variable.autoDeclaration.identifiers[0].column,
102 				variable.autoDeclaration.identifiers[0].text);
103 			return;
104 		}
105 		foreach (dec; variable.declarators)
106 		{
107 			addMessage(dec.name.line, dec.name.column, dec.name.text);
108 			return;
109 		}
110 	}
111 
112 	override void visit(const ConditionalDeclaration cond)
113 	{
114 		const VersionCondition ver = cond.compileCondition.versionCondition;
115 		if (ver is null || (ver.token != tok!"unittest" && ver.token.text != "none"))
116 			cond.accept(this);
117 		else if (cond.falseDeclarations.length > 0)
118 			foreach (f; cond.falseDeclarations)
119 				visit(f);
120 	}
121 
122 	override void visit(const FunctionBody fb) {}
123 	override void visit(const Unittest u) {}
124 	override void visit(const TraitsExpression t) {}
125 
126 	mixin V!ClassDeclaration;
127 	mixin V!InterfaceDeclaration;
128 	mixin V!StructDeclaration;
129 	mixin V!UnionDeclaration;
130 	mixin V!TemplateDeclaration;
131 	mixin V!FunctionDeclaration;
132 	mixin V!Constructor;
133 
134 private:
135 
136 	mixin template V(T)
137 	{
138 		override void visit(const T declaration)
139 		{
140 			import std.traits : hasMember;
141 			if (currentIsInteresting())
142 			{
143 				if (declaration.comment is null)
144 				{
145 					static if (hasMember!(T, "name"))
146 					{
147 						static if (is (T == FunctionDeclaration))
148 						{
149 							import std.algorithm : canFind;
150 							if (!(ignoredFunctionNames.canFind(declaration.name.text)
151 								|| isGetterOrSetter(declaration.name.text)
152 								|| isProperty(declaration)))
153 							{
154 								addMessage(declaration.name.line, declaration.name.column,
155 									declaration.name.text);
156 							}
157 						}
158 						else
159 						{
160 							if (declaration.name.type != tok!"")
161 								addMessage(declaration.name.line, declaration.name.column,
162 									declaration.name.text);
163 						}
164 					}
165 					else
166 					{
167 						addMessage(declaration.line, declaration.column, null);
168 					}
169 				}
170 				static if (!(is (T == TemplateDeclaration)
171 					|| is(T == FunctionDeclaration)))
172 				{
173 					declaration.accept(this);
174 				}
175 			}
176 		}
177 	}
178 
179 	static bool isGetterOrSetter(string name)
180 	{
181 		return !matchAll(name, getSetRe).empty;
182 	}
183 
184 	static bool isProperty(const FunctionDeclaration dec)
185 	{
186 		if (dec.memberFunctionAttributes is null)
187 			return false;
188 		foreach (attr; dec.memberFunctionAttributes)
189 		{
190 			if (attr.atAttribute && attr.atAttribute.identifier.text == "property")
191 				return true;
192 		}
193 		return false;
194 	}
195 
196 	void addMessage(size_t line, size_t column, string name)
197 	{
198 		import std.string : format;
199 		addErrorMessage(line, column, "dscanner.style.undocumented_declaration",
200 			name is null ? "Public declaration is undocumented." :
201 				format("Public declaration '%s' is undocumented.", name));
202 	}
203 
204 	bool getOverride()
205 	{
206 		return stack[$ - 1].isOverride;
207 	}
208 
209 	void setOverride(bool o = true)
210 	{
211 		stack[$ - 1].isOverride = o;
212 	}
213 
214 	bool getDisabled()
215 	{
216 		return stack[$ - 1].isDisabled;
217 	}
218 
219 	void setDisabled(bool d = true)
220 	{
221 		stack[$ - 1].isDisabled = d;
222 	}
223 
224 	bool getDeprecated()
225 	{
226 		return stack[$ - 1].isDeprecated;
227 	}
228 
229 	void setDeprecated(bool d = true)
230 	{
231 		stack[$ - 1].isDeprecated = d;
232 	}
233 
234 	bool currentIsInteresting()
235 	{
236 		return stack[$ - 1].protection == tok!"public" && !stack[$ - 1].isOverride
237 			&& !stack[$ - 1].isDisabled && !stack[$ - 1].isDeprecated;
238 	}
239 
240 	void set(IdType p)
241 	in { assert (isProtection(p)); }
242 	body
243 	{
244 		stack[$ - 1].protection = p;
245 	}
246 
247 	void push(IdType p)
248 	in { assert (isProtection(p)); }
249 	body
250 	{
251 		stack ~= ProtectionInfo(p, false);
252 	}
253 
254 	void pop()
255 	{
256 		assert (stack.length > 1);
257 		stack = stack[0 .. $ - 1];
258 	}
259 
260 	static struct ProtectionInfo
261 	{
262 		IdType protection;
263 		bool isOverride;
264 		bool isDeprecated;
265 		bool isDisabled;
266 	}
267 
268 	ProtectionInfo[] stack;
269 }
270 
271 // Ignore undocumented symbols with these names
272 private immutable string[] ignoredFunctionNames = [
273 	"opCmp",
274 	"opEquals",
275 	"toString",
276 	"toHash",
277 	"main"
278 ];
279 
280 private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;