Skip to content

Commit ba75110

Browse files
authored
Merge pull request #470 from dlang-community/has_public_example
Add has_public_example check
2 parents 38c4d2d + e065d07 commit ba75110

File tree

4 files changed

+311
-0
lines changed

4 files changed

+311
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ Note that the "--skipTests" option is the equivalent of changing each
133133
* Useless initializers.
134134
* Allman brace style
135135
* Redundant visibility attributes
136+
* Public declarations without a documented unittest. By default disabled.
136137

137138
#### Wishlist
138139

src/analysis/config.d

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,7 @@ struct StaticAnalysisConfig
185185

186186
@INI("Check for redundant attributes")
187187
string redundant_attributes_check = Check.enabled;
188+
189+
@INI("Check public declarations without a documented unittest")
190+
string has_public_example = Check.disabled;
188191
}

src/analysis/has_public_example.d

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
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 analysis.has_public_example;
6+
7+
import analysis.base;
8+
import dsymbol.scope_ : Scope;
9+
import dparse.ast;
10+
import dparse.lexer;
11+
12+
import std.algorithm;
13+
import std.stdio;
14+
15+
/**
16+
* Checks for public declarations without a documented unittests.
17+
* For now, variable and enum declarations aren't checked.
18+
*/
19+
class HasPublicExampleCheck : BaseAnalyzer
20+
{
21+
alias visit = BaseAnalyzer.visit;
22+
23+
this(string fileName, const(Scope)* sc, bool skipTests = false)
24+
{
25+
super(fileName, sc, skipTests);
26+
}
27+
28+
override void visit(const Module mod)
29+
{
30+
// the last seen declaration is memorized
31+
Declaration lastDecl;
32+
33+
// keep track of ddoced unittests after visiting lastDecl
34+
bool hasNoDdocUnittest;
35+
36+
// on lastDecl reset we check for seen ddoced unittests since lastDecl was observed
37+
void checkLastDecl()
38+
{
39+
if (lastDecl !is null && hasNoDdocUnittest)
40+
triggerError(lastDecl);
41+
lastDecl = null;
42+
}
43+
44+
// check all public top-level declarations
45+
foreach (decl; mod.declarations.filter!(decl => isPublic(decl.attributes)))
46+
{
47+
const bool hasDdocHeader = hasDdocHeader(decl);
48+
49+
// check the documentation of a unittest declaration
50+
if (decl.unittest_ !is null)
51+
{
52+
if (hasDdocHeader)
53+
hasNoDdocUnittest = false;
54+
}
55+
// add all declarations that could be publicly documented to the lastDecl "stack"
56+
else if (hasDittableDecl(decl))
57+
{
58+
// ignore dittoed declarations
59+
if (hasDittos(decl))
60+
continue;
61+
62+
// new public symbol -> check the previous decl
63+
checkLastDecl;
64+
65+
lastDecl = hasDdocHeader ? cast(Declaration) decl : null;
66+
hasNoDdocUnittest = true;
67+
}
68+
else
69+
// ran into variableDeclaration or something else -> reset & validate current lastDecl "stack"
70+
checkLastDecl;
71+
}
72+
checkLastDecl;
73+
}
74+
75+
private:
76+
77+
bool hasDitto(Decl)(const Decl decl)
78+
{
79+
import ddoc.comments : parseComment;
80+
if (decl is null || decl.comment is null)
81+
return false;
82+
83+
return parseComment(decl.comment, null).isDitto;
84+
}
85+
86+
bool hasDittos(Decl)(const Decl decl)
87+
{
88+
foreach (property; possibleDeclarations)
89+
if (mixin("hasDitto(decl." ~ property ~ ")"))
90+
return true;
91+
return false;
92+
}
93+
94+
bool hasDittableDecl(Decl)(const Decl decl)
95+
{
96+
foreach (property; possibleDeclarations)
97+
if (mixin("decl." ~ property ~ " !is null"))
98+
return true;
99+
return false;
100+
}
101+
102+
import std.meta : AliasSeq;
103+
alias possibleDeclarations = AliasSeq!(
104+
"classDeclaration",
105+
"enumDeclaration",
106+
"functionDeclaration",
107+
"interfaceDeclaration",
108+
"structDeclaration",
109+
"templateDeclaration",
110+
"unionDeclaration",
111+
//"variableDeclaration",
112+
);
113+
114+
bool hasDdocHeader(const Declaration decl)
115+
{
116+
if (decl.declarations !is null)
117+
return false;
118+
119+
// unittest can have ddoc headers as well, but don't have a name
120+
if (decl.unittest_ !is null && decl.unittest_.comment.ptr !is null)
121+
return true;
122+
123+
foreach (property; possibleDeclarations)
124+
if (mixin("decl." ~ property ~ " !is null && decl." ~ property ~ ".comment.ptr !is null"))
125+
return true;
126+
127+
return false;
128+
}
129+
130+
bool isPublic(const Attribute[] attrs)
131+
{
132+
import dparse.lexer : tok;
133+
134+
enum tokPrivate = tok!"private", tokProtected = tok!"protected", tokPackage = tok!"package";
135+
136+
if (attrs.map!`a.attribute`.any!(x => x == tokPrivate || x == tokProtected || x == tokPackage))
137+
return false;
138+
139+
return true;
140+
}
141+
142+
void triggerError(const Declaration decl)
143+
{
144+
foreach (property; possibleDeclarations)
145+
if (auto fn = mixin("decl." ~ property))
146+
addMessage(fn.name.line, fn.name.column, fn.name.text);
147+
}
148+
149+
void addMessage(size_t line, size_t column, string name)
150+
{
151+
import std.string : format;
152+
153+
addErrorMessage(line, column, "dscanner.style.has_public_example", name is null
154+
? "Public declaration has no documented example."
155+
: format("Public declaration '%s' has no documented example.", name));
156+
}
157+
}
158+
159+
unittest
160+
{
161+
import std.stdio : stderr;
162+
import std.format : format;
163+
import analysis.config : StaticAnalysisConfig, Check, disabledConfig;
164+
import analysis.helpers : assertAnalyzerWarnings;
165+
166+
StaticAnalysisConfig sac = disabledConfig();
167+
sac.has_public_example = Check.enabled;
168+
169+
assertAnalyzerWarnings(q{
170+
/// C
171+
class C{}
172+
///
173+
unittest {}
174+
175+
/// I
176+
interface I{}
177+
///
178+
unittest {}
179+
180+
/// e
181+
enum e = 0;
182+
///
183+
unittest {}
184+
185+
/// f
186+
void f(){}
187+
///
188+
unittest {}
189+
190+
/// S
191+
struct S{}
192+
///
193+
unittest {}
194+
195+
/// T
196+
template T(){}
197+
///
198+
unittest {}
199+
200+
/// U
201+
union U{}
202+
///
203+
unittest {}
204+
}, sac);
205+
206+
// enums or variables don't need to have public unittest
207+
assertAnalyzerWarnings(q{
208+
/// C
209+
class C{} // [warn]: Public declaration 'C' has no documented example.
210+
unittest {}
211+
212+
/// I
213+
interface I{} // [warn]: Public declaration 'I' has no documented example.
214+
unittest {}
215+
216+
/// f
217+
void f(){} // [warn]: Public declaration 'f' has no documented example.
218+
unittest {}
219+
220+
/// S
221+
struct S{} // [warn]: Public declaration 'S' has no documented example.
222+
unittest {}
223+
224+
/// T
225+
template T(){} // [warn]: Public declaration 'T' has no documented example.
226+
unittest {}
227+
228+
/// U
229+
union U{} // [warn]: Public declaration 'U' has no documented example.
230+
unittest {}
231+
}, sac);
232+
233+
// test module header unittest
234+
assertAnalyzerWarnings(q{
235+
unittest {}
236+
/// C
237+
class C{} // [warn]: Public declaration 'C' has no documented example.
238+
}, sac);
239+
240+
// test documented module header unittest
241+
assertAnalyzerWarnings(q{
242+
///
243+
unittest {}
244+
/// C
245+
class C{} // [warn]: Public declaration 'C' has no documented example.
246+
}, sac);
247+
248+
// test multiple unittest blocks
249+
assertAnalyzerWarnings(q{
250+
/// C
251+
class C{} // [warn]: Public declaration 'C' has no documented example.
252+
unittest {}
253+
unittest {}
254+
unittest {}
255+
256+
/// U
257+
union U{}
258+
unittest {}
259+
///
260+
unittest {}
261+
unittest {}
262+
}, sac);
263+
264+
/// check private
265+
assertAnalyzerWarnings(q{
266+
/// C
267+
private class C{}
268+
269+
/// I
270+
protected interface I{}
271+
272+
/// e
273+
package enum e = 0;
274+
275+
/// f
276+
package(std) void f(){}
277+
278+
/// S
279+
extern(C) struct S{}
280+
///
281+
unittest {}
282+
}, sac);
283+
284+
/// check intermediate private declarations and ditto-ed declarations
285+
assertAnalyzerWarnings(q{
286+
/// C
287+
class C{}
288+
private void foo(){}
289+
///
290+
unittest {}
291+
292+
/// I
293+
interface I{}
294+
/// ditto
295+
void f(){}
296+
///
297+
unittest {}
298+
}, sac);
299+
300+
stderr.writeln("Unittest for HasPublicExampleCheck passed.");
301+
}
302+

src/analysis/run.d

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import analysis.vcall_in_ctor;
6868
import analysis.useless_initializer;
6969
import analysis.allman;
7070
import analysis.redundant_attributes;
71+
import analysis.has_public_example;
7172

7273
import dsymbol.string_interning : internString;
7374
import dsymbol.scope_;
@@ -399,6 +400,10 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a
399400
checks ~= new RedundantAttributesCheck(fileName, moduleScope,
400401
analysisConfig.redundant_attributes_check == Check.skipTests && !ut);
401402

403+
if (analysisConfig.has_public_example!= Check.disabled)
404+
checks ~= new HasPublicExampleCheck(fileName, moduleScope,
405+
analysisConfig.has_public_example == Check.skipTests && !ut);
406+
402407
version (none)
403408
if (analysisConfig.redundant_if_check != Check.disabled)
404409
checks ~= new IfStatementCheck(fileName, moduleScope,

0 commit comments

Comments
 (0)