280 lines
6.2 KiB
D
280 lines
6.2 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 analysis.undocumented;
|
|
|
|
import analysis.base;
|
|
import dsymbol.scope_ : Scope;
|
|
import dparse.ast;
|
|
import dparse.lexer;
|
|
|
|
import std.regex : ctRegex, matchAll;
|
|
import std.stdio;
|
|
|
|
/**
|
|
* Checks for undocumented public declarations. Ignores some operator overloads,
|
|
* main functions, and functions whose name starts with "get" or "set".
|
|
*/
|
|
class UndocumentedDeclarationCheck : BaseAnalyzer
|
|
{
|
|
alias visit = BaseAnalyzer.visit;
|
|
|
|
this(string fileName, const(Scope)* sc)
|
|
{
|
|
super(fileName, sc);
|
|
}
|
|
|
|
override void visit(const Module mod)
|
|
{
|
|
push(tok!"public");
|
|
mod.accept(this);
|
|
}
|
|
|
|
override void visit(const Declaration dec)
|
|
{
|
|
if (dec.attributeDeclaration)
|
|
{
|
|
auto attr = dec.attributeDeclaration.attribute;
|
|
if (isProtection(attr.attribute.type))
|
|
set(attr.attribute.type);
|
|
else if (attr.attribute == tok!"override")
|
|
setOverride(true);
|
|
else if (attr.deprecated_ !is null)
|
|
setDeprecated(true);
|
|
else if (attr.atAttribute !is null && attr.atAttribute.identifier.text == "disable")
|
|
setDisabled(true);
|
|
}
|
|
|
|
immutable bool shouldPop = dec.attributeDeclaration is null;
|
|
immutable bool prevOverride = getOverride();
|
|
immutable bool prevDisabled = getDisabled();
|
|
immutable bool prevDeprecated = getDeprecated();
|
|
bool dis = false;
|
|
bool dep = false;
|
|
bool ovr = false;
|
|
bool pushed = false;
|
|
foreach (attribute; dec.attributes)
|
|
{
|
|
if (isProtection(attribute.attribute.type))
|
|
{
|
|
if (shouldPop)
|
|
{
|
|
pushed = true;
|
|
push(attribute.attribute.type);
|
|
}
|
|
else
|
|
set(attribute.attribute.type);
|
|
}
|
|
else if (attribute.attribute == tok!"override")
|
|
ovr = true;
|
|
else if (attribute.deprecated_ !is null)
|
|
dep = true;
|
|
else if (attribute.atAttribute !is null && attribute.atAttribute.identifier.text == "disable")
|
|
dis = true;
|
|
}
|
|
if (ovr)
|
|
setOverride(true);
|
|
if (dis)
|
|
setDisabled(true);
|
|
if (dep)
|
|
setDeprecated(true);
|
|
dec.accept(this);
|
|
if (shouldPop && pushed)
|
|
pop();
|
|
if (ovr)
|
|
setOverride(prevOverride);
|
|
if (dis)
|
|
setDisabled(prevDisabled);
|
|
if (dep)
|
|
setDeprecated(prevDeprecated);
|
|
}
|
|
|
|
override void visit(const VariableDeclaration variable)
|
|
{
|
|
if (!currentIsInteresting() || variable.comment !is null)
|
|
return;
|
|
if (variable.autoDeclaration !is null)
|
|
{
|
|
addMessage(variable.autoDeclaration.identifiers[0].line,
|
|
variable.autoDeclaration.identifiers[0].column,
|
|
variable.autoDeclaration.identifiers[0].text);
|
|
return;
|
|
}
|
|
foreach (dec; variable.declarators)
|
|
{
|
|
addMessage(dec.name.line, dec.name.column, dec.name.text);
|
|
return;
|
|
}
|
|
}
|
|
|
|
override void visit(const ConditionalDeclaration cond)
|
|
{
|
|
const VersionCondition ver = cond.compileCondition.versionCondition;
|
|
if (ver is null || (ver.token != tok!"unittest" && ver.token.text != "none"))
|
|
cond.accept(this);
|
|
else if (cond.falseDeclaration !is null)
|
|
visit(cond.falseDeclaration);
|
|
}
|
|
|
|
override void visit(const FunctionBody fb) {}
|
|
override void visit(const Unittest u) {}
|
|
override void visit(const TraitsExpression t) {}
|
|
|
|
mixin V!ClassDeclaration;
|
|
mixin V!InterfaceDeclaration;
|
|
mixin V!StructDeclaration;
|
|
mixin V!UnionDeclaration;
|
|
mixin V!TemplateDeclaration;
|
|
mixin V!FunctionDeclaration;
|
|
mixin V!Constructor;
|
|
|
|
private:
|
|
|
|
mixin template V(T)
|
|
{
|
|
override void visit(const T declaration)
|
|
{
|
|
import std.traits : hasMember;
|
|
if (currentIsInteresting())
|
|
{
|
|
if (declaration.comment is null)
|
|
{
|
|
static if (hasMember!(T, "name"))
|
|
{
|
|
static if (is (T == FunctionDeclaration))
|
|
{
|
|
import std.algorithm : canFind;
|
|
if (!(ignoredFunctionNames.canFind(declaration.name.text)
|
|
|| isGetterOrSetter(declaration.name.text)
|
|
|| isProperty(declaration)))
|
|
{
|
|
addMessage(declaration.name.line, declaration.name.column,
|
|
declaration.name.text);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (declaration.name.type != tok!"")
|
|
addMessage(declaration.name.line, declaration.name.column,
|
|
declaration.name.text);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
addMessage(declaration.line, declaration.column, null);
|
|
}
|
|
}
|
|
static if (!(is (T == TemplateDeclaration)
|
|
|| is(T == FunctionDeclaration)))
|
|
{
|
|
declaration.accept(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool isGetterOrSetter(string name)
|
|
{
|
|
return !matchAll(name, getSetRe).empty;
|
|
}
|
|
|
|
static bool isProperty(const FunctionDeclaration dec)
|
|
{
|
|
if (dec.memberFunctionAttributes is null)
|
|
return false;
|
|
foreach (attr; dec.memberFunctionAttributes)
|
|
{
|
|
if (attr.atAttribute && attr.atAttribute.identifier.text == "property")
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void addMessage(size_t line, size_t column, string name)
|
|
{
|
|
import std.string : format;
|
|
addErrorMessage(line, column, "dscanner.style.undocumented_declaration",
|
|
name is null ? "Public declaration is undocumented." :
|
|
format("Public declaration '%s' is undocumented.", name));
|
|
}
|
|
|
|
bool getOverride()
|
|
{
|
|
return stack[$ - 1].isOverride;
|
|
}
|
|
|
|
void setOverride(bool o = true)
|
|
{
|
|
stack[$ - 1].isOverride = o;
|
|
}
|
|
|
|
bool getDisabled()
|
|
{
|
|
return stack[$ - 1].isDisabled;
|
|
}
|
|
|
|
void setDisabled(bool d = true)
|
|
{
|
|
stack[$ - 1].isDisabled = d;
|
|
}
|
|
|
|
bool getDeprecated()
|
|
{
|
|
return stack[$ - 1].isDeprecated;
|
|
}
|
|
|
|
void setDeprecated(bool d = true)
|
|
{
|
|
stack[$ - 1].isDeprecated = d;
|
|
}
|
|
|
|
bool currentIsInteresting()
|
|
{
|
|
return stack[$ - 1].protection == tok!"public" && !stack[$ - 1].isOverride
|
|
&& !stack[$ - 1].isDisabled && !stack[$ - 1].isDeprecated;
|
|
}
|
|
|
|
void set(IdType p)
|
|
in { assert (isProtection(p)); }
|
|
body
|
|
{
|
|
stack[$ - 1].protection = p;
|
|
}
|
|
|
|
void push(IdType p)
|
|
in { assert (isProtection(p)); }
|
|
body
|
|
{
|
|
stack ~= ProtectionInfo(p, false);
|
|
}
|
|
|
|
void pop()
|
|
{
|
|
assert (stack.length > 1);
|
|
stack = stack[0 .. $ - 1];
|
|
}
|
|
|
|
static struct ProtectionInfo
|
|
{
|
|
IdType protection;
|
|
bool isOverride;
|
|
bool isDeprecated;
|
|
bool isDisabled;
|
|
}
|
|
|
|
ProtectionInfo[] stack;
|
|
}
|
|
|
|
// Ignore undocumented symbols with these names
|
|
private immutable string[] ignoredFunctionNames = [
|
|
"opCmp",
|
|
"opEquals",
|
|
"toString",
|
|
"toHash",
|
|
"main"
|
|
];
|
|
|
|
private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;
|