diff --git a/src/analysis/assert_without_msg.d b/src/analysis/assert_without_msg.d index 5a69a37..7e43e31 100644 --- a/src/analysis/assert_without_msg.d +++ b/src/analysis/assert_without_msg.d @@ -10,6 +10,7 @@ import dparse.lexer; import dparse.ast; import std.stdio; +import std.algorithm; /** * Check that all asserts have an explanatory message. @@ -31,8 +32,60 @@ class AssertWithoutMessageCheck : BaseAnalyzer addErrorMessage(expr.line, expr.column, KEY, MESSAGE); } + override void visit(const FunctionCallExpression expr) + { + if (!isStdExceptionImported) + return; + + if (expr.unaryExpression !is null && + expr.unaryExpression.primaryExpression !is null && + expr.unaryExpression.primaryExpression.identifierOrTemplateInstance !is null) + { + auto ident = expr.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier; + if (ident.text == "enforce" && expr.arguments !is null && expr.arguments.argumentList !is null && + expr.arguments.argumentList.items.length < 2) + addErrorMessage(ident.line, ident.column, KEY, MESSAGE); + } + } + + override void visit(const SingleImport sImport) + { + static immutable stdException = ["std", "exception"]; + if (sImport.identifierChain.identifiers.map!(a => a.text).equal(stdException)) + isStdExceptionImported = true; + } + + // revert the stack after new scopes + override void visit(const Declaration decl) + { + // be careful - ImportDeclarations don't introduce a new scope + if (decl.importDeclaration is null) + { + bool tmp = isStdExceptionImported; + scope(exit) isStdExceptionImported = tmp; + decl.accept(this); + } + else + decl.accept(this); + } + + mixin ScopedVisit!IfStatement; + mixin ScopedVisit!BlockStatement; + alias visit = BaseAnalyzer.visit; +private: + bool isStdExceptionImported; + + template ScopedVisit(NodeType) + { + override void visit(const NodeType n) + { + bool tmp = isStdExceptionImported; + scope(exit) isStdExceptionImported = tmp; + n.accept(this); + } + } } unittest @@ -63,6 +116,40 @@ unittest AssertWithoutMessageCheck.MESSAGE, ), sac); + // check for std.exception.enforce + assertAnalyzerWarnings(q{ + unittest { + enforce(0); // std.exception not imported yet - could be a user-defined symbol + import std.exception; + enforce(0, "foo bar"); + enforce(0); // [warn]: %s + } + }c.format( + AssertWithoutMessageCheck.MESSAGE, + ), sac); + + // check for std.exception.enforce + assertAnalyzerWarnings(q{ + unittest { + import exception; + class C { + import std.exception; + } + enforce(0); // std.exception not imported yet - could be a user-defined symbol + struct S { + import std.exception; + } + enforce(0); // std.exception not imported yet - could be a user-defined symbol + if (false) { + import std.exception; + } + enforce(0); // std.exception not imported yet - could be a user-defined symbol + { + import std.exception; + } + enforce(0); // std.exception not imported yet - could be a user-defined symbol + } + }c, sac); stderr.writeln("Unittest for AssertWithoutMessageCheck passed."); }