// 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."); }