303 lines
6.3 KiB
D
303 lines
6.3 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 analysis.has_public_example;
|
|
|
|
import analysis.base;
|
|
import dsymbol.scope_ : Scope;
|
|
import dparse.ast;
|
|
import dparse.lexer;
|
|
|
|
import std.algorithm;
|
|
import std.stdio;
|
|
|
|
/**
|
|
* Checks for public declarations without a documented unittests.
|
|
* For now, variable and enum declarations aren't checked.
|
|
*/
|
|
class HasPublicExampleCheck : BaseAnalyzer
|
|
{
|
|
alias visit = BaseAnalyzer.visit;
|
|
|
|
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
|
{
|
|
super(fileName, sc, skipTests);
|
|
}
|
|
|
|
override void visit(const Module mod)
|
|
{
|
|
// the last seen declaration is memorized
|
|
Declaration lastDecl;
|
|
|
|
// keep track of ddoced unittests after visiting lastDecl
|
|
bool hasNoDdocUnittest;
|
|
|
|
// on lastDecl reset we check for seen ddoced unittests since lastDecl was observed
|
|
void checkLastDecl()
|
|
{
|
|
if (lastDecl !is null && hasNoDdocUnittest)
|
|
triggerError(lastDecl);
|
|
lastDecl = null;
|
|
}
|
|
|
|
// check all public top-level declarations
|
|
foreach (decl; mod.declarations.filter!(decl => isPublic(decl.attributes)))
|
|
{
|
|
const bool hasDdocHeader = hasDdocHeader(decl);
|
|
|
|
// check the documentation of a unittest declaration
|
|
if (decl.unittest_ !is null)
|
|
{
|
|
if (hasDdocHeader)
|
|
hasNoDdocUnittest = false;
|
|
}
|
|
// add all declarations that could be publicly documented to the lastDecl "stack"
|
|
else if (hasDittableDecl(decl))
|
|
{
|
|
// ignore dittoed declarations
|
|
if (hasDittos(decl))
|
|
continue;
|
|
|
|
// new public symbol -> check the previous decl
|
|
checkLastDecl;
|
|
|
|
lastDecl = hasDdocHeader ? cast(Declaration) decl : null;
|
|
hasNoDdocUnittest = true;
|
|
}
|
|
else
|
|
// ran into variableDeclaration or something else -> reset & validate current lastDecl "stack"
|
|
checkLastDecl;
|
|
}
|
|
checkLastDecl;
|
|
}
|
|
|
|
private:
|
|
|
|
bool hasDitto(Decl)(const Decl decl)
|
|
{
|
|
import ddoc.comments : parseComment;
|
|
if (decl is null || decl.comment is null)
|
|
return false;
|
|
|
|
return parseComment(decl.comment, null).isDitto;
|
|
}
|
|
|
|
bool hasDittos(Decl)(const Decl decl)
|
|
{
|
|
foreach (property; possibleDeclarations)
|
|
if (mixin("hasDitto(decl." ~ property ~ ")"))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool hasDittableDecl(Decl)(const Decl decl)
|
|
{
|
|
foreach (property; possibleDeclarations)
|
|
if (mixin("decl." ~ property ~ " !is null"))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
import std.meta : AliasSeq;
|
|
alias possibleDeclarations = AliasSeq!(
|
|
"classDeclaration",
|
|
"enumDeclaration",
|
|
"functionDeclaration",
|
|
"interfaceDeclaration",
|
|
"structDeclaration",
|
|
"templateDeclaration",
|
|
"unionDeclaration",
|
|
//"variableDeclaration",
|
|
);
|
|
|
|
bool hasDdocHeader(const Declaration decl)
|
|
{
|
|
if (decl.declarations !is null)
|
|
return false;
|
|
|
|
// unittest can have ddoc headers as well, but don't have a name
|
|
if (decl.unittest_ !is null && decl.unittest_.comment.ptr !is null)
|
|
return true;
|
|
|
|
foreach (property; possibleDeclarations)
|
|
if (mixin("decl." ~ property ~ " !is null && decl." ~ property ~ ".comment.ptr !is null"))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool isPublic(const Attribute[] attrs)
|
|
{
|
|
import dparse.lexer : tok;
|
|
|
|
enum tokPrivate = tok!"private", tokProtected = tok!"protected", tokPackage = tok!"package";
|
|
|
|
if (attrs.map!`a.attribute`.any!(x => x == tokPrivate || x == tokProtected || x == tokPackage))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void triggerError(const Declaration decl)
|
|
{
|
|
foreach (property; possibleDeclarations)
|
|
if (auto fn = mixin("decl." ~ property))
|
|
addMessage(fn.name.line, fn.name.column, fn.name.text);
|
|
}
|
|
|
|
void addMessage(size_t line, size_t column, string name)
|
|
{
|
|
import std.string : format;
|
|
|
|
addErrorMessage(line, column, "dscanner.style.has_public_example", name is null
|
|
? "Public declaration has no documented example."
|
|
: format("Public declaration '%s' has no documented example.", name));
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.stdio : stderr;
|
|
import std.format : format;
|
|
import analysis.config : StaticAnalysisConfig, Check, disabledConfig;
|
|
import analysis.helpers : assertAnalyzerWarnings;
|
|
|
|
StaticAnalysisConfig sac = disabledConfig();
|
|
sac.has_public_example = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(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
|
|
assertAnalyzerWarnings(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
|
|
assertAnalyzerWarnings(q{
|
|
unittest {}
|
|
/// C
|
|
class C{} // [warn]: Public declaration 'C' has no documented example.
|
|
}, sac);
|
|
|
|
// test documented module header unittest
|
|
assertAnalyzerWarnings(q{
|
|
///
|
|
unittest {}
|
|
/// C
|
|
class C{} // [warn]: Public declaration 'C' has no documented example.
|
|
}, sac);
|
|
|
|
// test multiple unittest blocks
|
|
assertAnalyzerWarnings(q{
|
|
/// C
|
|
class C{} // [warn]: Public declaration 'C' has no documented example.
|
|
unittest {}
|
|
unittest {}
|
|
unittest {}
|
|
|
|
/// U
|
|
union U{}
|
|
unittest {}
|
|
///
|
|
unittest {}
|
|
unittest {}
|
|
}, sac);
|
|
|
|
/// check private
|
|
assertAnalyzerWarnings(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 and ditto-ed declarations
|
|
assertAnalyzerWarnings(q{
|
|
/// C
|
|
class C{}
|
|
private void foo(){}
|
|
///
|
|
unittest {}
|
|
|
|
/// I
|
|
interface I{}
|
|
/// ditto
|
|
void f(){}
|
|
///
|
|
unittest {}
|
|
}, sac);
|
|
|
|
stderr.writeln("Unittest for HasPublicExampleCheck passed.");
|
|
}
|
|
|