D-Scanner/src/dscanner/analysis/has_public_example.d

317 lines
6.6 KiB
D

// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
module dscanner.analysis.has_public_example;
import dscanner.analysis.base;
/**
* Checks for public declarations without a documented unittests.
* For now, variable and enum declarations aren't checked.
*/
extern (C++) class HasPublicExampleCheck(AST) : BaseAnalyzerDmd
{
alias visit = BaseAnalyzerDmd.visit;
mixin AnalyzerInfo!"has_public_example";
private enum KEY = "dscanner.style.has_public_example";
private enum DEFAULT_MSG = "Public declaration has no documented example.";
private enum MSG = "Public declaration '%s' has no documented example.";
private struct DeclarationInfo
{
bool ignore;
string name;
ulong lineNum;
ulong charNum;
}
private DeclarationInfo lastDecl = DeclarationInfo(true);
private bool isDocumented;
extern (D) this(string fileName, bool skipTests = false)
{
super(fileName, skipTests);
}
override void visit(AST.Module mod)
{
super.visit(mod);
checkLastDecl();
}
override void visit(AST.ConditionalStatement _) {}
override void visit(AST.ConditionalDeclaration _) {}
override void visit(AST.UnitTestDeclaration unitTestDecl)
{
if (unitTestDecl.comment() !is null)
isDocumented = true;
}
override void visit(AST.DeprecatedDeclaration _)
{
lastDecl = DeclarationInfo(true);
}
override void visit(AST.StorageClassDeclaration storageClassDecl)
{
if (!hasIgnorableStorageClass(storageClassDecl.stc))
super.visit(storageClassDecl);
else
lastDecl = DeclarationInfo(true);
}
private bool hasIgnorableStorageClass(ulong storageClass)
{
import dmd.astenums : STC;
return (storageClass & STC.deprecated_) || (storageClass & STC.manifest);
}
override void visit(AST.VisibilityDeclaration visibilityDecl)
{
import dmd.dsymbol : Visibility;
auto visibilityKind = visibilityDecl.visibility.kind;
bool isPrivate = visibilityKind == Visibility.Kind.private_
|| visibilityKind == Visibility.Kind.package_
|| visibilityKind == Visibility.Kind.protected_;
if (isPrivate)
checkLastDecl();
else
super.visit(visibilityDecl);
}
mixin VisitDeclaration!(AST.ClassDeclaration);
mixin VisitDeclaration!(AST.InterfaceDeclaration);
mixin VisitDeclaration!(AST.StructDeclaration);
mixin VisitDeclaration!(AST.UnionDeclaration);
mixin VisitDeclaration!(AST.FuncDeclaration);
mixin VisitDeclaration!(AST.TemplateDeclaration);
private template VisitDeclaration(NodeType)
{
override void visit(NodeType node)
{
import std.conv : to;
import std.string : strip, toLower;
static if (is(NodeType == AST.TemplateDeclaration))
{
if (shouldTemplateBeSkipped(node))
return;
}
bool isCommented = node.comment() !is null;
if (isCommented)
{
string comment = to!string(node.comment());
if (comment.strip().toLower() == "ditto")
return;
}
checkLastDecl();
if (isCommented)
{
string name = node.ident ? cast(string) node.ident.toString() : null;
lastDecl = DeclarationInfo(false, name, cast(ulong) node.loc.linnum, cast(ulong) node.loc.charnum);
}
isDocumented = false;
}
}
private bool shouldTemplateBeSkipped(AST.TemplateDeclaration templateDecl)
{
if (templateDecl.members is null)
return false;
foreach (member; *(templateDecl.members))
if (auto var = member.isVarDeclaration())
if (hasIgnorableStorageClass(var.storage_class))
return true;
return false;
}
private void checkLastDecl()
{
import std.format : format;
if (!lastDecl.ignore && !isDocumented)
{
string msg = lastDecl.name ? MSG.format(lastDecl.name) : DEFAULT_MSG;
addErrorMessage(lastDecl.lineNum, lastDecl.charNum, KEY, msg);
}
lastDecl = DeclarationInfo(true);
}
}
unittest
{
import std.stdio : stderr;
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
import dscanner.analysis.helpers : assertAnalyzerWarningsDMD;
StaticAnalysisConfig sac = disabledConfig();
sac.has_public_example = Check.enabled;
assertAnalyzerWarningsDMD(q{
/// C
class C{}
///
unittest {}
/// I
interface I{}
///
unittest {}
/// e
enum e = 0;
///
unittest {}
/// f
void f(){}
///
unittest {}
/// S
struct S{}
///
unittest {}
/// T
template T(){}
///
unittest {}
/// U
union U{}
///
unittest {}
}, sac);
// enums or variables don't need to have public unittest
assertAnalyzerWarningsDMD(q{
/// C
class C{} // [warn]: Public declaration 'C' has no documented example.
unittest {}
/// I
interface I{} // [warn]: Public declaration 'I' has no documented example.
unittest {}
/// f
void f(){} // [warn]: Public declaration 'f' has no documented example.
unittest {}
/// S
struct S{} // [warn]: Public declaration 'S' has no documented example.
unittest {}
/// T
template T(){} // [warn]: Public declaration 'T' has no documented example.
unittest {}
/// U
union U{} // [warn]: Public declaration 'U' has no documented example.
unittest {}
}, sac);
// test module header unittest
assertAnalyzerWarningsDMD(q{
unittest {}
/// C
class C{} // [warn]: Public declaration 'C' has no documented example.
}, sac);
// test documented module header unittest
assertAnalyzerWarningsDMD(q{
///
unittest {}
/// C
class C{} // [warn]: Public declaration 'C' has no documented example.
}, sac);
// test multiple unittest blocks
assertAnalyzerWarningsDMD(q{
/// C
class C{} // [warn]: Public declaration 'C' has no documented example.
unittest {}
unittest {}
unittest {}
/// U
union U{}
unittest {}
///
unittest {}
unittest {}
}, sac);
/// check private
assertAnalyzerWarningsDMD(q{
/// C
private class C{}
/// I
protected interface I{}
/// e
package enum e = 0;
/// f
package(std) void f(){}
/// S
extern(C) struct S{}
///
unittest {}
}, sac);
// check intermediate private declarations
// removed for issue #500
/*assertAnalyzerWarningsDMD(q{
/// C
class C{}
private void foo(){}
///
unittest {}
}, sac);*/
// check intermediate ditto-ed declarations
assertAnalyzerWarningsDMD(q{
/// I
interface I{}
/// ditto
void f(){}
///
unittest {}
}, sac);
// test reset on private symbols (#500)
assertAnalyzerWarningsDMD(q{
///
void dirName(C)(C[] path) {} // [warn]: Public declaration 'dirName' has no documented example.
private void _dirName(R)(R path) {}
///
unittest {}
}, sac);
// deprecated symbols shouldn't require a test
assertAnalyzerWarningsDMD(q{
///
deprecated void dirName(C)(C[] path) {}
}, sac);
stderr.writeln("Unittest for HasPublicExampleCheck passed.");
}