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

287 lines
7.1 KiB
D

// Copyright Brian Schott (Hackerpilot) 2014.
// 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.undocumented;
import dscanner.analysis.base;
import dmd.astenums : STC;
import std.format : format;
import std.regex : ctRegex, matchAll;
/**
* Checks for undocumented public declarations. Ignores some operator overloads,
* main functions, and functions whose name starts with "get" or "set".
*/
extern (C++) class UndocumentedDeclarationCheck(AST) : BaseAnalyzerDmd
{
alias visit = BaseAnalyzerDmd.visit;
mixin AnalyzerInfo!"undocumented_declaration_check";
private enum KEY = "dscanner.style.undocumented_declaration";
private enum DEFAULT_MSG = "Public declaration is undocumented.";
private enum MSG = "Public declaration '%s' is undocumented.";
private immutable string[] ignoredFunctionNames = [
"opCmp", "opEquals", "toString", "toHash", "main"
];
private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;
extern (D) this(string fileName, bool skipTests = false)
{
super(fileName, skipTests);
}
override void visit(AST.VisibilityDeclaration visibilityDecl)
{
import dmd.dsymbol : Visibility;
if (visibilityDecl.visibility.kind == Visibility.Kind.public_)
super.visit(visibilityDecl);
}
override void visit(AST.StorageClassDeclaration storageClassDecl)
{
if (!hasIgnorableStorageClass(storageClassDecl.stc))
super.visit(storageClassDecl);
}
override void visit(AST.DeprecatedDeclaration _) {}
override void visit(AST.FuncDeclaration funcDecl)
{
if (funcDecl.comment() !is null || funcDecl.ident is null)
return;
string funcName = cast(string) funcDecl.ident.toString();
bool canBeUndocumented = hasIgnorableStorageClass(funcDecl.storage_class) || isIgnorableFunctionName(funcName);
if (!canBeUndocumented)
{
addErrorMessage(funcDecl.loc.linnum, funcDecl.loc.charnum, KEY, MSG.format(funcName));
super.visit(funcDecl);
}
}
private extern (D) bool isIgnorableFunctionName(string funcName)
{
import std.algorithm : canFind;
return ignoredFunctionNames.canFind(funcName) || !matchAll(funcName, getSetRe).empty;
}
override void visit(AST.CtorDeclaration ctorDecl)
{
if (ctorDecl.comment() !is null)
return;
addErrorMessage(ctorDecl.loc.linnum, ctorDecl.loc.charnum, KEY, DEFAULT_MSG);
}
override void visit(AST.TemplateDeclaration templateDecl)
{
if (templateDecl.comment() !is null || templateDecl.ident is null)
return;
if (!templateDecl.isDeprecated())
{
string templateName = cast(string) templateDecl.ident.toString();
addErrorMessage(templateDecl.loc.linnum, templateDecl.loc.charnum, KEY, MSG.format(templateName));
}
}
mixin VisitDeclaration!(AST.ClassDeclaration);
mixin VisitDeclaration!(AST.InterfaceDeclaration);
mixin VisitDeclaration!(AST.StructDeclaration);
mixin VisitDeclaration!(AST.UnionDeclaration);
mixin VisitDeclaration!(AST.EnumDeclaration);
mixin VisitDeclaration!(AST.EnumMember);
mixin VisitDeclaration!(AST.VarDeclaration);
private template VisitDeclaration(NodeType)
{
override void visit(NodeType decl)
{
if (decl.comment() !is null || decl.ident is null)
{
super.visit(decl);
return;
}
bool canBeUndocumented;
static if (__traits(hasMember, NodeType, "storage_class"))
canBeUndocumented = hasIgnorableStorageClass(decl.storage_class);
if (!canBeUndocumented)
{
string declName = cast(string) decl.ident.toString();
addErrorMessage(decl.loc.linnum, decl.loc.charnum, KEY, MSG.format(declName));
super.visit(decl);
}
}
}
private bool hasIgnorableStorageClass(ulong storageClass)
{
return (storageClass & STC.deprecated_) || (storageClass & STC.override_)
|| (storageClass & STC.disable) || (storageClass & STC.property);
}
override void visit(AST.UnitTestDeclaration _) {}
override void visit(AST.TraitsExp _) {}
override void visit(AST.ConditionalDeclaration conditionalDecl)
{
auto versionCond = conditionalDecl.condition.isVersionCondition();
if (versionCond is null)
super.visit(conditionalDecl);
if (isIgnorableVersion(versionCond) && conditionalDecl.elsedecl)
{
foreach (decl; *(conditionalDecl.elsedecl))
super.visit(decl);
}
}
override void visit(AST.ConditionalStatement conditionalStatement)
{
auto versionCond = conditionalStatement.condition.isVersionCondition();
if (versionCond is null)
super.visit(conditionalStatement);
if (isIgnorableVersion(versionCond) && conditionalStatement.elsebody)
{
super.visit(conditionalStatement.elsebody);
}
}
private bool isIgnorableVersion(AST.VersionCondition versionCond)
{
if (versionCond is null || versionCond.ident is null)
return false;
string versionStr = cast(string) versionCond.ident.toString();
return versionStr == "unittest" || versionStr == "none";
}
}
unittest
{
import std.stdio : stderr;
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
import dscanner.analysis.helpers : assertAnalyzerWarningsDMD;
StaticAnalysisConfig sac = disabledConfig();
sac.undocumented_declaration_check = Check.enabled;
assertAnalyzerWarningsDMD(q{
private int x;
int y; // [warn]: Public declaration 'y' is undocumented.
public int z; // [warn]: Public declaration 'z' is undocumented.
///
class C
{
int h; // [warn]: Public declaration 'h' is undocumented.
public:
int g; // [warn]: Public declaration 'g' is undocumented.
void f() {} // [warn]: Public declaration 'f' is undocumented.
private:
int a;
int b;
}
}c, sac);
assertAnalyzerWarningsDMD(q{
deprecated int y;
///
class C
{
private int b;
}
}c, sac);
assertAnalyzerWarningsDMD(q{
class C{} // [warn]: Public declaration 'C' is undocumented.
interface I{} // [warn]: Public declaration 'I' is undocumented.
enum e = 0; // [warn]: Public declaration 'e' is undocumented.
void f(){} // [warn]: Public declaration 'f' is undocumented.
struct S{} // [warn]: Public declaration 'S' is undocumented.
template T(){} // [warn]: Public declaration 'T' is undocumented.
union U{} // [warn]: Public declaration 'U' is undocumented.
}, sac);
assertAnalyzerWarningsDMD(q{
/// C
class C{}
/// I
interface I{}
/// e
enum e = 0;
/// f
void f(){}
/// S
struct S{}
/// T
template T(){}
/// U
union U{}
}, sac);
// https://github.com/dlang-community/D-Scanner/issues/760
assertAnalyzerWarningsDMD(q{
deprecated("This has been deprecated") auto func(){}
deprecated auto func(){}
deprecated auto func()(){}
}, sac);
assertAnalyzerWarningsDMD(q{
class C{} /// a
interface I{} /// b
enum e = 0; /// c
void f(){} /// d
struct S{} /// e
template T(){} /// f
union U{} /// g
}, sac);
assertAnalyzerWarningsDMD(q{
int x; // [warn]: Public declaration 'x' is undocumented.
int y; ///
///
class C
{
private int a;
int b; ///
int c; // [warn]: Public declaration 'c' is undocumented.
protected int d;
}
///
class D
{
///
void fun()
{
class Inner
{
int z;
}
int a1, a2, a3;
}
}
}c, sac);
stderr.writeln("Unittest for UndocumentedDeclarationCheck passed.");
}