1226 lines
28 KiB
D
1226 lines
28 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.properly_documented_public_functions;
|
|
|
|
import dparse.lexer;
|
|
import dparse.ast;
|
|
import dparse.formatter : astFmt = format;
|
|
import dscanner.analysis.base : BaseAnalyzer;
|
|
import dscanner.utils : safeAccess;
|
|
|
|
import std.format : format;
|
|
import std.range.primitives;
|
|
import std.stdio;
|
|
|
|
/**
|
|
* Requires each public function to contain the following ddoc sections
|
|
- PARAMS:
|
|
- if the function has at least one parameter
|
|
- every parameter must have a ddoc params entry (applies for template paramters too)
|
|
- Ddoc params entries without a parameter trigger warnings as well
|
|
- RETURNS: (except if it's void, only functions)
|
|
*/
|
|
final class ProperlyDocumentedPublicFunctions : BaseAnalyzer
|
|
{
|
|
enum string MISSING_PARAMS_KEY = "dscanner.style.doc_missing_params";
|
|
enum string MISSING_PARAMS_MESSAGE = "Parameter %s isn't documented in the `Params` section.";
|
|
enum string MISSING_TEMPLATE_PARAMS_MESSAGE
|
|
= "Template parameters %s isn't documented in the `Params` section.";
|
|
|
|
enum string NON_EXISTENT_PARAMS_KEY = "dscanner.style.doc_non_existing_params";
|
|
enum string NON_EXISTENT_PARAMS_MESSAGE = "Documented parameter %s isn't a function parameter.";
|
|
|
|
enum string MISSING_RETURNS_KEY = "dscanner.style.doc_missing_returns";
|
|
enum string MISSING_RETURNS_MESSAGE = "A public function needs to contain a `Returns` section.";
|
|
|
|
enum string MISSING_THROW_KEY = "dscanner.style.doc_missing_throw";
|
|
enum string MISSING_THROW_MESSAGE = "An instance of `%s` is thrown but not documented in the `Throws` section";
|
|
|
|
///
|
|
this(string fileName, bool skipTests = false)
|
|
{
|
|
super(fileName, null, skipTests);
|
|
}
|
|
|
|
override void visit(const Module mod)
|
|
{
|
|
islastSeenVisibilityLabelPublic = true;
|
|
mod.accept(this);
|
|
postCheckSeenDdocParams();
|
|
}
|
|
|
|
override void visit(const UnaryExpression decl)
|
|
{
|
|
const IdentifierOrTemplateInstance iot = safeAccess(decl)
|
|
.functionCallExpression.unaryExpression.primaryExpression
|
|
.identifierOrTemplateInstance;
|
|
|
|
// enforce(condition);
|
|
if (iot && iot.identifier.text == "enforce")
|
|
{
|
|
Type tt = new Type;
|
|
tt.type2 = new Type2;
|
|
tt.type2.typeIdentifierPart = new TypeIdentifierPart;
|
|
tt.type2.typeIdentifierPart.identifierOrTemplateInstance =
|
|
new IdentifierOrTemplateInstance;
|
|
tt.type2.typeIdentifierPart.identifierOrTemplateInstance.identifier =
|
|
Token(tok!"identifier", "Exception", 0, 0, 0);
|
|
thrown ~= tt;
|
|
}
|
|
else if (iot && iot.templateInstance && iot.templateInstance.identifier.text == "enforce")
|
|
{
|
|
// enforce!Type(condition);
|
|
if (const TemplateSingleArgument tsa = safeAccess(iot.templateInstance)
|
|
.templateArguments.templateSingleArgument)
|
|
{
|
|
Type tt = new Type;
|
|
tt.type2 = new Type2;
|
|
tt.type2.typeIdentifierPart = new TypeIdentifierPart;
|
|
tt.type2.typeIdentifierPart.identifierOrTemplateInstance =
|
|
new IdentifierOrTemplateInstance;
|
|
tt.type2.typeIdentifierPart.identifierOrTemplateInstance.identifier =
|
|
tsa.token;
|
|
thrown ~= tt;
|
|
}
|
|
// enforce!(Type)(condition);
|
|
else if (const TemplateArgumentList tal = safeAccess(iot.templateInstance)
|
|
.templateArguments.templateArgumentList)
|
|
{
|
|
if (tal.items.length && tal.items[0].type)
|
|
thrown ~= tal.items[0].type;
|
|
}
|
|
}
|
|
decl.accept(this);
|
|
}
|
|
|
|
override void visit(const Declaration decl)
|
|
{
|
|
import std.algorithm.searching : any;
|
|
import std.algorithm.iteration : map;
|
|
|
|
// skip private symbols
|
|
enum tokPrivate = tok!"private",
|
|
tokProtected = tok!"protected",
|
|
tokPackage = tok!"package",
|
|
tokPublic = tok!"public";
|
|
|
|
// Nested funcs for `Throws`
|
|
bool decNestedFunc;
|
|
if (decl.functionDeclaration)
|
|
{
|
|
nestedFuncs++;
|
|
decNestedFunc = true;
|
|
}
|
|
scope(exit)
|
|
{
|
|
if (decNestedFunc)
|
|
nestedFuncs--;
|
|
}
|
|
if (nestedFuncs > 1)
|
|
{
|
|
decl.accept(this);
|
|
return;
|
|
}
|
|
|
|
if (decl.attributes.length > 0)
|
|
{
|
|
const bool isPublic = !decl.attributes.map!`a.attribute`.any!(x => x == tokPrivate ||
|
|
x == tokProtected ||
|
|
x == tokPackage);
|
|
// recognize label blocks
|
|
if (!hasDeclaration(decl))
|
|
islastSeenVisibilityLabelPublic = isPublic;
|
|
|
|
if (!isPublic)
|
|
return;
|
|
}
|
|
|
|
if (islastSeenVisibilityLabelPublic || decl.attributes.map!`a.attribute`.any!(x => x == tokPublic))
|
|
{
|
|
// Don't complain about non-documented function declarations
|
|
if ((decl.functionDeclaration !is null && decl.functionDeclaration.comment.ptr !is null) ||
|
|
(decl.templateDeclaration !is null && decl.templateDeclaration.comment.ptr !is null) ||
|
|
decl.mixinTemplateDeclaration !is null ||
|
|
(decl.classDeclaration !is null && decl.classDeclaration.comment.ptr !is null) ||
|
|
(decl.structDeclaration !is null && decl.structDeclaration.comment.ptr !is null))
|
|
decl.accept(this);
|
|
}
|
|
}
|
|
|
|
override void visit(const TemplateDeclaration decl)
|
|
{
|
|
setLastDdocParams(decl.name.line, decl.name.column, decl.comment);
|
|
checkDdocParams(decl.name.line, decl.name.column, decl.templateParameters);
|
|
|
|
withinTemplate = true;
|
|
scope(exit) withinTemplate = false;
|
|
decl.accept(this);
|
|
}
|
|
|
|
override void visit(const MixinTemplateDeclaration decl)
|
|
{
|
|
decl.accept(this);
|
|
}
|
|
|
|
override void visit(const StructDeclaration decl)
|
|
{
|
|
setLastDdocParams(decl.name.line, decl.name.column, decl.comment);
|
|
checkDdocParams(decl.name.line, decl.name.column, decl.templateParameters);
|
|
decl.accept(this);
|
|
}
|
|
|
|
override void visit(const ClassDeclaration decl)
|
|
{
|
|
setLastDdocParams(decl.name.line, decl.name.column, decl.comment);
|
|
checkDdocParams(decl.name.line, decl.name.column, decl.templateParameters);
|
|
decl.accept(this);
|
|
}
|
|
|
|
override void visit(const FunctionDeclaration decl)
|
|
{
|
|
import std.algorithm.searching : all, any;
|
|
import std.array : Appender;
|
|
|
|
// ignore header declaration for now
|
|
if (decl.functionBody is null)
|
|
return;
|
|
|
|
if (nestedFuncs == 1)
|
|
thrown.length = 0;
|
|
// detect ThrowStatement only if not nothrow
|
|
if (!decl.attributes.any!(a => a.attribute.text == "nothrow"))
|
|
{
|
|
decl.accept(this);
|
|
if (nestedFuncs == 1 && !hasThrowSection(decl.comment))
|
|
foreach(t; thrown)
|
|
{
|
|
Appender!(char[]) app;
|
|
astFmt(&app, t);
|
|
addErrorMessage(decl.name.line, decl.name.column, MISSING_THROW_KEY,
|
|
MISSING_THROW_MESSAGE.format(app.data));
|
|
}
|
|
}
|
|
|
|
if (nestedFuncs == 1)
|
|
{
|
|
auto comment = setLastDdocParams(decl.name.line, decl.name.column, decl.comment);
|
|
checkDdocParams(decl.name.line, decl.name.column, decl.parameters, decl.templateParameters);
|
|
enum voidType = tok!"void";
|
|
if (decl.returnType is null || decl.returnType.type2.builtinType != voidType)
|
|
if (!(comment.isDitto || withinTemplate || comment.sections.any!(s => s.name == "Returns")))
|
|
addErrorMessage(decl.name.line, decl.name.column,
|
|
MISSING_RETURNS_KEY, MISSING_RETURNS_MESSAGE);
|
|
}
|
|
}
|
|
|
|
// remove thrown Type that are caught
|
|
override void visit(const TryStatement ts)
|
|
{
|
|
import std.algorithm.iteration : filter;
|
|
import std.algorithm.searching : canFind;
|
|
import std.array : array;
|
|
|
|
ts.accept(this);
|
|
|
|
if (ts.catches)
|
|
thrown = thrown.filter!(a => !ts.catches.catches
|
|
.canFind!(b => b.type == a))
|
|
.array;
|
|
}
|
|
|
|
override void visit(const ThrowStatement ts)
|
|
{
|
|
import std.algorithm.searching : canFind;
|
|
|
|
ts.accept(this);
|
|
if (ts.expression && ts.expression.items.length == 1)
|
|
if (const UnaryExpression ue = cast(UnaryExpression) ts.expression.items[0])
|
|
{
|
|
if (ue.newExpression && ue.newExpression.type &&
|
|
!thrown.canFind!(a => a == ue.newExpression.type))
|
|
{
|
|
thrown ~= ue.newExpression.type;
|
|
}
|
|
}
|
|
}
|
|
|
|
alias visit = BaseAnalyzer.visit;
|
|
|
|
private:
|
|
bool islastSeenVisibilityLabelPublic;
|
|
bool withinTemplate;
|
|
size_t nestedFuncs;
|
|
|
|
static struct Function
|
|
{
|
|
bool active;
|
|
size_t line, column;
|
|
const(string)[] ddocParams;
|
|
bool[string] params;
|
|
}
|
|
Function lastSeenFun;
|
|
|
|
const(Type)[] thrown;
|
|
|
|
// find invalid ddoc parameters (i.e. they don't occur in a function declaration)
|
|
void postCheckSeenDdocParams()
|
|
{
|
|
import std.format : format;
|
|
|
|
if (lastSeenFun.active)
|
|
foreach (p; lastSeenFun.ddocParams)
|
|
if (p !in lastSeenFun.params)
|
|
addErrorMessage(lastSeenFun.line, lastSeenFun.column, NON_EXISTENT_PARAMS_KEY,
|
|
NON_EXISTENT_PARAMS_MESSAGE.format(p));
|
|
|
|
lastSeenFun.active = false;
|
|
}
|
|
|
|
bool hasThrowSection(string commentText)
|
|
{
|
|
import std.algorithm.searching : canFind;
|
|
import ddoc.comments : parseComment;
|
|
|
|
const comment = parseComment(commentText, null);
|
|
return comment.isDitto || comment.sections.canFind!(s => s.name == "Throws");
|
|
}
|
|
|
|
auto setLastDdocParams(size_t line, size_t column, string commentText)
|
|
{
|
|
import ddoc.comments : parseComment;
|
|
import std.algorithm.searching : find;
|
|
import std.algorithm.iteration : map;
|
|
import std.array : array;
|
|
|
|
const comment = parseComment(commentText, null);
|
|
if (withinTemplate)
|
|
{
|
|
const paramSection = comment.sections.find!(s => s.name == "Params");
|
|
if (!paramSection.empty)
|
|
lastSeenFun.ddocParams ~= paramSection[0].mapping.map!(a => a[0]).array;
|
|
}
|
|
else if (!comment.isDitto)
|
|
{
|
|
// check old function for invalid ddoc params
|
|
if (lastSeenFun.active)
|
|
postCheckSeenDdocParams();
|
|
|
|
const paramSection = comment.sections.find!(s => s.name == "Params");
|
|
if (paramSection.empty)
|
|
{
|
|
lastSeenFun = Function(true, line, column, null);
|
|
}
|
|
else
|
|
{
|
|
auto ddocParams = paramSection[0].mapping.map!(a => a[0]).array;
|
|
lastSeenFun = Function(true, line, column, ddocParams);
|
|
}
|
|
}
|
|
|
|
return comment;
|
|
}
|
|
|
|
void checkDdocParams(size_t line, size_t column, const Parameters params,
|
|
const TemplateParameters templateParameters = null)
|
|
{
|
|
import std.array : array;
|
|
import std.algorithm.searching : canFind, countUntil;
|
|
import std.algorithm.iteration : map;
|
|
import std.algorithm.mutation : remove;
|
|
import std.range : indexed, iota;
|
|
|
|
// convert templateParameters into a string[] for faster access
|
|
const(TemplateParameter)[] templateList;
|
|
if (const tp = templateParameters)
|
|
if (const tpl = tp.templateParameterList)
|
|
templateList = tpl.items;
|
|
string[] tlList = templateList.map!(a => templateParamName(a)).array;
|
|
|
|
// make a copy of all parameters and remove the seen ones later during the loop
|
|
size_t[] unseenTemplates = templateList.length.iota.array;
|
|
|
|
if (lastSeenFun.active && params !is null)
|
|
foreach (p; params.parameters)
|
|
{
|
|
string templateName;
|
|
|
|
if (auto iot = safeAccess(p).type.type2
|
|
.typeIdentifierPart.identifierOrTemplateInstance.unwrap)
|
|
{
|
|
templateName = iot.identifier.text;
|
|
}
|
|
else if (auto iot = safeAccess(p).type.type2.type.type2
|
|
.typeIdentifierPart.identifierOrTemplateInstance.unwrap)
|
|
{
|
|
templateName = iot.identifier.text;
|
|
}
|
|
|
|
const idx = tlList.countUntil(templateName);
|
|
if (idx >= 0)
|
|
{
|
|
unseenTemplates = unseenTemplates.remove(idx);
|
|
tlList = tlList.remove(idx);
|
|
// documenting template parameter should be allowed
|
|
lastSeenFun.params[templateName] = true;
|
|
}
|
|
|
|
if (!lastSeenFun.ddocParams.canFind(p.name.text))
|
|
addErrorMessage(line, column, MISSING_PARAMS_KEY,
|
|
format(MISSING_PARAMS_MESSAGE, p.name.text));
|
|
else
|
|
lastSeenFun.params[p.name.text] = true;
|
|
}
|
|
|
|
// now check the remaining, not used template parameters
|
|
auto unseenTemplatesArr = templateList.indexed(unseenTemplates).array;
|
|
checkDdocParams(line, column, unseenTemplatesArr);
|
|
}
|
|
|
|
void checkDdocParams(size_t line, size_t column, const TemplateParameters templateParams)
|
|
{
|
|
if (lastSeenFun.active && templateParams !is null &&
|
|
templateParams.templateParameterList !is null)
|
|
checkDdocParams(line, column, templateParams.templateParameterList.items);
|
|
}
|
|
|
|
void checkDdocParams(size_t line, size_t column, const TemplateParameter[] templateParams)
|
|
{
|
|
import std.algorithm.searching : canFind;
|
|
foreach (p; templateParams)
|
|
{
|
|
const name = templateParamName(p);
|
|
assert(name, "Invalid template parameter name."); // this shouldn't happen
|
|
if (!lastSeenFun.ddocParams.canFind(name))
|
|
addErrorMessage(line, column, MISSING_PARAMS_KEY,
|
|
format(MISSING_TEMPLATE_PARAMS_MESSAGE, name));
|
|
else
|
|
lastSeenFun.params[name] = true;
|
|
}
|
|
}
|
|
|
|
static string templateParamName(const TemplateParameter p)
|
|
{
|
|
if (p.templateTypeParameter)
|
|
return p.templateTypeParameter.identifier.text;
|
|
if (p.templateValueParameter)
|
|
return p.templateValueParameter.identifier.text;
|
|
if (p.templateAliasParameter)
|
|
return p.templateAliasParameter.identifier.text;
|
|
if (p.templateTupleParameter)
|
|
return p.templateTupleParameter.identifier.text;
|
|
if (p.templateThisParameter)
|
|
return p.templateThisParameter.templateTypeParameter.identifier.text;
|
|
|
|
return null;
|
|
}
|
|
|
|
bool hasDeclaration(const Declaration decl)
|
|
{
|
|
import std.meta : AliasSeq;
|
|
alias properties = AliasSeq!(
|
|
"aliasDeclaration",
|
|
"aliasThisDeclaration",
|
|
"anonymousEnumDeclaration",
|
|
"attributeDeclaration",
|
|
"classDeclaration",
|
|
"conditionalDeclaration",
|
|
"constructor",
|
|
"debugSpecification",
|
|
"destructor",
|
|
"enumDeclaration",
|
|
"eponymousTemplateDeclaration",
|
|
"functionDeclaration",
|
|
"importDeclaration",
|
|
"interfaceDeclaration",
|
|
"invariant_",
|
|
"mixinDeclaration",
|
|
"mixinTemplateDeclaration",
|
|
"postblit",
|
|
"pragmaDeclaration",
|
|
"sharedStaticConstructor",
|
|
"sharedStaticDestructor",
|
|
"staticAssertDeclaration",
|
|
"staticConstructor",
|
|
"staticDestructor",
|
|
"structDeclaration",
|
|
"templateDeclaration",
|
|
"unionDeclaration",
|
|
"unittest_",
|
|
"variableDeclaration",
|
|
"versionSpecification",
|
|
);
|
|
if (decl.declarations !is null)
|
|
return false;
|
|
|
|
auto isNull = true;
|
|
foreach (property; properties)
|
|
if (mixin("decl." ~ property ~ " !is null"))
|
|
isNull = false;
|
|
|
|
return !isNull;
|
|
}
|
|
}
|
|
|
|
version(unittest)
|
|
{
|
|
import std.stdio : stderr;
|
|
import std.format : format;
|
|
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
|
|
import dscanner.analysis.helpers : assertAnalyzerWarnings;
|
|
}
|
|
|
|
// missing params
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Some text
|
|
*/
|
|
void foo(int k){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k")
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Some text
|
|
*/
|
|
void foo(int K)(){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("K")
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Some text
|
|
*/
|
|
struct Foo(Bar){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar")
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Some text
|
|
*/
|
|
class Foo(Bar){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar")
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Some text
|
|
*/
|
|
template Foo(Bar){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar")
|
|
), sac);
|
|
|
|
|
|
// test no parameters
|
|
assertAnalyzerWarnings(q{
|
|
/** Some text */
|
|
void foo(){}
|
|
}c, sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/** Some text */
|
|
struct Foo(){}
|
|
}c, sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/** Some text */
|
|
class Foo(){}
|
|
}c, sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/** Some text */
|
|
template Foo(){}
|
|
}c, sac);
|
|
|
|
}
|
|
|
|
// missing returns (only functions)
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Some text
|
|
*/
|
|
int foo(){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE,
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Some text
|
|
*/
|
|
auto foo(){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE,
|
|
), sac);
|
|
}
|
|
|
|
// ignore private
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Some text
|
|
*/
|
|
private void foo(int k){}
|
|
}c, sac);
|
|
|
|
// with block
|
|
assertAnalyzerWarnings(q{
|
|
private:
|
|
/**
|
|
Some text
|
|
*/
|
|
private void foo(int k){}
|
|
///
|
|
public int bar(){} // [warn]: %s
|
|
public:
|
|
///
|
|
int foobar(){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE,
|
|
ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE,
|
|
), sac);
|
|
|
|
// with block (template)
|
|
assertAnalyzerWarnings(q{
|
|
private:
|
|
/**
|
|
Some text
|
|
*/
|
|
private template foo(int k){}
|
|
///
|
|
public template bar(T){} // [warn]: %s
|
|
public:
|
|
///
|
|
template foobar(T){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"),
|
|
ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"),
|
|
), sac);
|
|
|
|
// with block (struct)
|
|
assertAnalyzerWarnings(q{
|
|
private:
|
|
/**
|
|
Some text
|
|
*/
|
|
private struct foo(int k){}
|
|
///
|
|
public struct bar(T){} // [warn]: %s
|
|
public:
|
|
///
|
|
struct foobar(T){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"),
|
|
ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"),
|
|
), sac);
|
|
}
|
|
|
|
// test parameter names
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
* Description.
|
|
*
|
|
* Params:
|
|
*
|
|
* Returns:
|
|
* A long description.
|
|
*/
|
|
int foo(int k){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k")
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Description.
|
|
|
|
Params:
|
|
val = A stupid parameter
|
|
k = A stupid parameter
|
|
|
|
Returns:
|
|
A long description.
|
|
*/
|
|
int foo(int k){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("val")
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Description.
|
|
|
|
Params:
|
|
|
|
Returns:
|
|
A long description.
|
|
*/
|
|
int foo(int k){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k")
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Description.
|
|
|
|
Params:
|
|
foo = A stupid parameter
|
|
bad = A stupid parameter (does not exist)
|
|
foobar = A stupid parameter
|
|
|
|
Returns:
|
|
A long description.
|
|
*/
|
|
int foo(int foo, int foobar){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("bad")
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Description.
|
|
|
|
Params:
|
|
foo = A stupid parameter
|
|
bad = A stupid parameter (does not exist)
|
|
foobar = A stupid parameter
|
|
|
|
Returns:
|
|
A long description.
|
|
*/
|
|
struct foo(int foo, int foobar){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("bad")
|
|
), sac);
|
|
|
|
// properly documented
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Description.
|
|
|
|
Params:
|
|
foo = A stupid parameter
|
|
bar = A stupid parameter
|
|
|
|
Returns:
|
|
A long description.
|
|
*/
|
|
int foo(int foo, int bar){}
|
|
}c, sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
Description.
|
|
|
|
Params:
|
|
foo = A stupid parameter
|
|
bar = A stupid parameter
|
|
|
|
Returns:
|
|
A long description.
|
|
*/
|
|
struct foo(int foo, int bar){}
|
|
}c, sac);
|
|
}
|
|
|
|
// support ditto
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
* Description.
|
|
*
|
|
* Params:
|
|
* k = A stupid parameter
|
|
*
|
|
* Returns:
|
|
* A long description.
|
|
*/
|
|
int foo(int k){}
|
|
|
|
/// ditto
|
|
int bar(int k){}
|
|
}c, sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
* Description.
|
|
*
|
|
* Params:
|
|
* k = A stupid parameter
|
|
* K = A stupid parameter
|
|
*
|
|
* Returns:
|
|
* A long description.
|
|
*/
|
|
int foo(int k){}
|
|
|
|
/// ditto
|
|
struct Bar(K){}
|
|
}c, sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
* Description.
|
|
*
|
|
* Params:
|
|
* k = A stupid parameter
|
|
* f = A stupid parameter
|
|
*
|
|
* Returns:
|
|
* A long description.
|
|
*/
|
|
int foo(int k){}
|
|
|
|
/// ditto
|
|
int bar(int f){}
|
|
}c, sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
* Description.
|
|
*
|
|
* Params:
|
|
* k = A stupid parameter
|
|
*
|
|
* Returns:
|
|
* A long description.
|
|
*/
|
|
int foo(int k){}
|
|
|
|
/// ditto
|
|
int bar(int bar){} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("bar")
|
|
), sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
* Description.
|
|
*
|
|
* Params:
|
|
* k = A stupid parameter
|
|
* bar = A stupid parameter
|
|
* f = A stupid parameter
|
|
*
|
|
* Returns:
|
|
* A long description.
|
|
* See_Also:
|
|
* $(REF takeExactly, std,range)
|
|
*/
|
|
int foo(int k){} // [warn]: %s
|
|
|
|
/// ditto
|
|
int bar(int bar){}
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("f")
|
|
), sac);
|
|
}
|
|
|
|
// check correct ddoc headers
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
Counts elements in the given
|
|
$(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
|
|
until the given predicate is true for one of the given $(D needles).
|
|
|
|
Params:
|
|
val = A stupid parameter
|
|
|
|
Returns: Awesome values.
|
|
+/
|
|
string bar(string val){}
|
|
}c, sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
Counts elements in the given
|
|
$(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
|
|
until the given predicate is true for one of the given $(D needles).
|
|
|
|
Params:
|
|
val = A stupid parameter
|
|
|
|
Returns: Awesome values.
|
|
+/
|
|
template bar(string val){}
|
|
}c, sac);
|
|
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/**
|
|
* Ddoc for the inner function appears here.
|
|
* This function is declared this way to allow for multiple variable-length
|
|
* template argument lists.
|
|
* ---
|
|
* abcde!("a", "b", "c")(100, x, y, z);
|
|
* ---
|
|
* Params:
|
|
* Args = foo
|
|
* U = bar
|
|
* T = barr
|
|
* varargs = foobar
|
|
* t = foo
|
|
* Returns: bar
|
|
*/
|
|
template abcde(Args ...) {
|
|
///
|
|
auto abcde(T, U...)(T t, U varargs) {
|
|
/// ....
|
|
}
|
|
}
|
|
}c, sac);
|
|
}
|
|
|
|
// Don't force the documentation of the template parameter if it's a used type in the parameter list
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
An awesome description.
|
|
|
|
Params:
|
|
r = an input range.
|
|
|
|
Returns: Awesome values.
|
|
+/
|
|
string bar(R)(R r){}
|
|
}c, sac);
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
An awesome description.
|
|
|
|
Params:
|
|
r = an input range.
|
|
|
|
Returns: Awesome values.
|
|
+/
|
|
string bar(P, R)(R r){}// [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("P")
|
|
), sac);
|
|
}
|
|
|
|
// https://github.com/dlang-community/D-Scanner/issues/601
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
void put(Range)(Range items) if (canPutConstRange!Range)
|
|
{
|
|
alias p = put!(Unqual!Range);
|
|
p(items);
|
|
}
|
|
}, sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
An awesome description.
|
|
|
|
Params:
|
|
items = things to put.
|
|
|
|
Returns: Awesome values.
|
|
+/
|
|
void put(Range)(const(Range) items) if (canPutConstRange!Range)
|
|
{}
|
|
}, sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
Throw but likely catched.
|
|
+/
|
|
void bar(){
|
|
try{throw new Exception("bla");throw new Error("bla");}
|
|
catch(Exception){} catch(Error){}}
|
|
}c, sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
Simple case
|
|
+/
|
|
void bar(){throw new Exception("bla");} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("Exception")
|
|
), sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
Supposed to be documented
|
|
|
|
Throws: Exception if...
|
|
+/
|
|
void bar(){throw new Exception("bla");}
|
|
}c.format(
|
|
), sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
rethrow
|
|
+/
|
|
void bar(){try throw new Exception("bla"); catch(Exception) throw new Error();} // [warn]: %s
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("Error")
|
|
), sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
trust nothrow before everything
|
|
+/
|
|
void bar() nothrow {try throw new Exception("bla"); catch(Exception) assert(0);}
|
|
}c, sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
case of throw in nested func
|
|
+/
|
|
void bar() // [warn]: %s
|
|
{
|
|
void foo(){throw new AssertError("bla");}
|
|
foo();
|
|
}
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError")
|
|
), sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
case of throw in nested func but caught
|
|
+/
|
|
void bar()
|
|
{
|
|
void foo(){throw new AssertError("bla");}
|
|
try foo();
|
|
catch (AssertError){}
|
|
}
|
|
}c, sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
case of double throw in nested func but only 1 caught
|
|
+/
|
|
void bar() // [warn]: %s
|
|
{
|
|
void foo(){throw new AssertError("bla");throw new Error("bla");}
|
|
try foo();
|
|
catch (Error){}
|
|
}
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError")
|
|
), sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
enforce
|
|
+/
|
|
void bar() // [warn]: %s
|
|
{
|
|
enforce(condition);
|
|
}
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("Exception")
|
|
), sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
enforce
|
|
+/
|
|
void bar() // [warn]: %s
|
|
{
|
|
enforce!AssertError(condition);
|
|
}
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError")
|
|
), sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
enforce
|
|
+/
|
|
void bar() // [warn]: %s
|
|
{
|
|
enforce!(AssertError)(condition);
|
|
}
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError")
|
|
), sac);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
enforce
|
|
+/
|
|
void foo() // [warn]: %s
|
|
{
|
|
void bar()
|
|
{
|
|
enforce!AssertError(condition);
|
|
}
|
|
bar();
|
|
}
|
|
|
|
}c.format(
|
|
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError")
|
|
), sac);
|
|
}
|
|
|
|
// https://github.com/dlang-community/D-Scanner/issues/583
|
|
unittest
|
|
{
|
|
StaticAnalysisConfig sac = disabledConfig;
|
|
sac.properly_documented_public_functions = Check.enabled;
|
|
|
|
assertAnalyzerWarnings(q{
|
|
/++
|
|
Implements the homonym function (also known as `accumulate`)
|
|
|
|
Returns:
|
|
the accumulated `result`
|
|
|
|
Params:
|
|
fun = one or more functions
|
|
+/
|
|
template reduce(fun...)
|
|
if (fun.length >= 1)
|
|
{
|
|
/++
|
|
No-seed version. The first element of `r` is used as the seed's value.
|
|
|
|
Params:
|
|
r = an iterable value as defined by `isIterable`
|
|
|
|
Returns:
|
|
the final result of the accumulator applied to the iterable
|
|
+/
|
|
auto reduce(R)(R r){}
|
|
}
|
|
}c.format(
|
|
), sac);
|
|
|
|
stderr.writeln("Unittest for ProperlyDocumentedPublicFunctions passed.");
|
|
}
|