mirror of
https://github.com/dlang-community/D-Scanner.git
synced 2025-04-27 05:40:01 +03:00
317 lines
6.6 KiB
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.");
|
|
}
|