Merge pull request #482 from wilzbach/phobo-master
Merge master into phobos merged-on-behalf-of: Sebastian Wilzbach <sebi.wilzbach@gmail.com>
This commit is contained in:
commit
18f0a0c1a3
|
@ -0,0 +1,91 @@
|
||||||
|
; Configure which static analysis checks are enabled
|
||||||
|
[analysis.config.StaticAnalysisConfig]
|
||||||
|
; Check variable, class, struct, interface, union, and function names against t
|
||||||
|
; he Phobos style guide
|
||||||
|
style_check="disabled"
|
||||||
|
; Check for array literals that cause unnecessary allocation
|
||||||
|
enum_array_literal_check="enabled"
|
||||||
|
; Check for poor exception handling practices
|
||||||
|
exception_check="enabled"
|
||||||
|
; Check for use of the deprecated 'delete' keyword
|
||||||
|
delete_check="enabled"
|
||||||
|
; Check for use of the deprecated floating point operators
|
||||||
|
float_operator_check="enabled"
|
||||||
|
; Check number literals for readability
|
||||||
|
number_style_check="enabled"
|
||||||
|
; Checks that opEquals, opCmp, toHash, and toString are either const, immutable
|
||||||
|
; , or inout.
|
||||||
|
object_const_check="enabled"
|
||||||
|
; Checks for .. expressions where the left side is larger than the right.
|
||||||
|
backwards_range_check="enabled"
|
||||||
|
; Checks for if statements whose 'then' block is the same as the 'else' block
|
||||||
|
if_else_same_check="enabled"
|
||||||
|
; Checks for some problems with constructors
|
||||||
|
constructor_check="enabled"
|
||||||
|
; Checks for unused variables and function parameters
|
||||||
|
unused_variable_check="disabled"
|
||||||
|
; Checks for unused labels
|
||||||
|
unused_label_check="enabled"
|
||||||
|
; Checks for duplicate attributes
|
||||||
|
duplicate_attribute="enabled"
|
||||||
|
; Checks that opEquals and toHash are both defined or neither are defined
|
||||||
|
opequals_tohash_check="disabled"
|
||||||
|
; Checks for subtraction from .length properties
|
||||||
|
length_subtraction_check="disabled"
|
||||||
|
; Checks for methods or properties whose names conflict with built-in propertie
|
||||||
|
; s
|
||||||
|
builtin_property_names_check="enabled"
|
||||||
|
; Checks for confusing code in inline asm statements
|
||||||
|
asm_style_check="enabled"
|
||||||
|
; Checks for confusing logical operator precedence
|
||||||
|
logical_precedence_check="enabled"
|
||||||
|
; Checks for undocumented public declarations
|
||||||
|
undocumented_declaration_check="disabled"
|
||||||
|
; Checks for poor placement of function attributes
|
||||||
|
function_attribute_check="disabled"
|
||||||
|
; Checks for use of the comma operator
|
||||||
|
comma_expression_check="enabled"
|
||||||
|
; Checks for local imports that are too broad
|
||||||
|
local_import_check="enabled"
|
||||||
|
; Checks for variables that could be declared immutable
|
||||||
|
could_be_immutable_check="disabled"
|
||||||
|
; Checks for redundant expressions in if statements
|
||||||
|
redundant_if_check="enabled"
|
||||||
|
; Checks for redundant parenthesis
|
||||||
|
redundant_parens_check="enabled"
|
||||||
|
; Checks for mismatched argument and parameter names
|
||||||
|
mismatched_args_check="enabled"
|
||||||
|
; Checks for labels with the same name as variables
|
||||||
|
label_var_same_name_check="enabled"
|
||||||
|
; Checks for lines longer than 120 characters
|
||||||
|
long_line_check="disabled"
|
||||||
|
; Checks for assignment to auto-ref function parameters
|
||||||
|
auto_ref_assignment_check="enabled"
|
||||||
|
; Checks for incorrect infinite range definitions
|
||||||
|
incorrect_infinite_range_check="enabled"
|
||||||
|
; Checks for asserts that are always true
|
||||||
|
useless_assert_check="enabled"
|
||||||
|
; Check for uses of the old-style alias syntax
|
||||||
|
alias_syntax_check="enabled"
|
||||||
|
; Checks for else if that should be else static if
|
||||||
|
static_if_else_check="enabled"
|
||||||
|
; Check for unclear lambda syntax
|
||||||
|
lambda_return_check="enabled"
|
||||||
|
; Check for auto function without return statement
|
||||||
|
auto_function_check="enabled"
|
||||||
|
; Check for sortedness of imports
|
||||||
|
imports_sortedness="disabled"
|
||||||
|
; Check for explicitly annotated unittests
|
||||||
|
explicitly_annotated_unittests="disabled"
|
||||||
|
; Check for properly documented public functions (Returns, Params)
|
||||||
|
properly_documented_public_functions="disabled"
|
||||||
|
; Check for useless usage of the final attribute
|
||||||
|
final_attribute_check="enabled"
|
||||||
|
; Check for virtual calls in the class constructors
|
||||||
|
vcall_in_ctor="enabled"
|
||||||
|
; Check for useless user defined initializers
|
||||||
|
useless_initializer="enabled"
|
||||||
|
; Check allman brace style
|
||||||
|
allman_braces_check="disabled"
|
||||||
|
; Check for redundant attributes
|
||||||
|
redundant_attributes_check="enabled"
|
|
@ -10,4 +10,5 @@ elif [[ $DC == ldc2 ]]; then
|
||||||
else
|
else
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
make test
|
make test
|
||||||
|
make lint
|
||||||
fi
|
fi
|
||||||
|
|
172
README.md
172
README.md
|
@ -21,11 +21,13 @@ compilers instead of DMD. To install, simply place the generated binary (in the
|
||||||
# Usage
|
# Usage
|
||||||
The following examples assume that we are analyzing a simple file called helloworld.d
|
The following examples assume that we are analyzing a simple file called helloworld.d
|
||||||
|
|
||||||
import std.stdio;
|
```d
|
||||||
void main(string[] args)
|
import std.stdio;
|
||||||
{
|
void main(string[] args)
|
||||||
writeln("Hello World");
|
{
|
||||||
}
|
writeln("Hello World");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Token Count
|
### Token Count
|
||||||
The "--tokenCount" or "-t" option prints the number of tokens in the given file
|
The "--tokenCount" or "-t" option prints the number of tokens in the given file
|
||||||
|
@ -131,6 +133,7 @@ Note that the "--skipTests" option is the equivalent of changing each
|
||||||
* Useless initializers.
|
* Useless initializers.
|
||||||
* Allman brace style
|
* Allman brace style
|
||||||
* Redundant visibility attributes
|
* Redundant visibility attributes
|
||||||
|
* Public declarations without a documented unittest. By default disabled.
|
||||||
|
|
||||||
#### Wishlist
|
#### Wishlist
|
||||||
|
|
||||||
|
@ -214,83 +217,88 @@ If a `dscanner.ini` file is locate in the working directory or any of it's paren
|
||||||
The "--ast" or "--xml" options will dump the complete abstract syntax tree of
|
The "--ast" or "--xml" options will dump the complete abstract syntax tree of
|
||||||
the given source file to standard output in XML format.
|
the given source file to standard output in XML format.
|
||||||
|
|
||||||
$ dscanner --ast helloworld.d
|
```sh
|
||||||
<module>
|
$ dscanner --ast helloworld.d
|
||||||
<declaration>
|
```
|
||||||
<importDeclaration>
|
|
||||||
<singleImport>
|
```xml
|
||||||
<identifierChain>
|
<module>
|
||||||
<identifier>std</identifier>
|
<declaration>
|
||||||
<identifier>stdio</identifier>
|
<importDeclaration>
|
||||||
</identifierChain>
|
<singleImport>
|
||||||
</singleImport>
|
<identifierChain>
|
||||||
</importDeclaration>
|
<identifier>std</identifier>
|
||||||
</declaration>
|
<identifier>stdio</identifier>
|
||||||
<declaration>
|
</identifierChain>
|
||||||
<functionDeclaration line="3">
|
</singleImport>
|
||||||
<name>main</name>
|
</importDeclaration>
|
||||||
<type pretty="void">
|
</declaration>
|
||||||
<type2>
|
<declaration>
|
||||||
void
|
<functionDeclaration line="3">
|
||||||
</type2>
|
<name>main</name>
|
||||||
</type>
|
<type pretty="void">
|
||||||
<parameters>
|
<type2>
|
||||||
<parameter>
|
void
|
||||||
<name>args</name>
|
</type2>
|
||||||
<type pretty="string[]">
|
</type>
|
||||||
<type2>
|
<parameters>
|
||||||
<symbol>
|
<parameter>
|
||||||
<identifierOrTemplateChain>
|
<name>args</name>
|
||||||
<identifierOrTemplateInstance>
|
<type pretty="string[]">
|
||||||
<identifier>string</identifier>
|
<type2>
|
||||||
</identifierOrTemplateInstance>
|
<symbol>
|
||||||
</identifierOrTemplateChain>
|
<identifierOrTemplateChain>
|
||||||
</symbol>
|
<identifierOrTemplateInstance>
|
||||||
</type2>
|
<identifier>string</identifier>
|
||||||
<typeSuffix type="[]"/>
|
</identifierOrTemplateInstance>
|
||||||
</type>
|
</identifierOrTemplateChain>
|
||||||
<identifier>args</identifier>
|
</symbol>
|
||||||
</parameter>
|
</type2>
|
||||||
</parameters>
|
<typeSuffix type="[]"/>
|
||||||
<functionBody>
|
</type>
|
||||||
<blockStatement>
|
<identifier>args</identifier>
|
||||||
<declarationsAndStatements>
|
</parameter>
|
||||||
<declarationOrStatement>
|
</parameters>
|
||||||
<statement>
|
<functionBody>
|
||||||
<statementNoCaseNoDefault>
|
<blockStatement>
|
||||||
<expressionStatement>
|
<declarationsAndStatements>
|
||||||
<expression>
|
<declarationOrStatement>
|
||||||
<assignExpression>
|
<statement>
|
||||||
<functionCallExpression>
|
<statementNoCaseNoDefault>
|
||||||
<unaryExpression>
|
<expressionStatement>
|
||||||
<primaryExpression>
|
<expression>
|
||||||
<identifierOrTemplateInstance>
|
<assignExpression>
|
||||||
<identifier>writeln</identifier>
|
<functionCallExpression>
|
||||||
</identifierOrTemplateInstance>
|
<unaryExpression>
|
||||||
</primaryExpression>
|
<primaryExpression>
|
||||||
</unaryExpression>
|
<identifierOrTemplateInstance>
|
||||||
<arguments>
|
<identifier>writeln</identifier>
|
||||||
<argumentList>
|
</identifierOrTemplateInstance>
|
||||||
<assignExpression>
|
</primaryExpression>
|
||||||
<primaryExpression>
|
</unaryExpression>
|
||||||
<stringLiteral>Hello World</stringLiteral>
|
<arguments>
|
||||||
</primaryExpression>
|
<argumentList>
|
||||||
</assignExpression>
|
<assignExpression>
|
||||||
</argumentList>
|
<primaryExpression>
|
||||||
</arguments>
|
<stringLiteral>Hello World</stringLiteral>
|
||||||
</functionCallExpression>
|
</primaryExpression>
|
||||||
</assignExpression>
|
</assignExpression>
|
||||||
</expression>
|
</argumentList>
|
||||||
</expressionStatement>
|
</arguments>
|
||||||
</statementNoCaseNoDefault>
|
</functionCallExpression>
|
||||||
</statement>
|
</assignExpression>
|
||||||
</declarationOrStatement>
|
</expression>
|
||||||
</declarationsAndStatements>
|
</expressionStatement>
|
||||||
</blockStatement>
|
</statementNoCaseNoDefault>
|
||||||
</functionBody>
|
</statement>
|
||||||
</functionDeclaration>
|
</declarationOrStatement>
|
||||||
</declaration>
|
</declarationsAndStatements>
|
||||||
</module>
|
</blockStatement>
|
||||||
|
</functionBody>
|
||||||
|
</functionDeclaration>
|
||||||
|
</declaration>
|
||||||
|
</module>
|
||||||
|
```
|
||||||
|
|
||||||
For more readable output, pipe the command through [xmllint](http://xmlsoft.org/xmllint.html)
|
For more readable output, pipe the command through [xmllint](http://xmlsoft.org/xmllint.html)
|
||||||
using its formatting switch.
|
using its formatting switch.
|
||||||
|
|
3
makefile
3
makefile
|
@ -51,6 +51,9 @@ test: githash
|
||||||
./bin/dscanner-unittest
|
./bin/dscanner-unittest
|
||||||
rm -f bin/dscanner-unittest
|
rm -f bin/dscanner-unittest
|
||||||
|
|
||||||
|
lint: dmdbuild
|
||||||
|
./bin/dscanner --config .dscanner.ini --styleCheck src
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf dsc
|
rm -rf dsc
|
||||||
rm -rf bin
|
rm -rf bin
|
||||||
|
|
|
@ -33,8 +33,8 @@ class AllManCheck : BaseAnalyzer
|
||||||
super(fileName, null, skipTests);
|
super(fileName, null, skipTests);
|
||||||
foreach (i; 1 .. tokens.length - 1)
|
foreach (i; 1 .. tokens.length - 1)
|
||||||
{
|
{
|
||||||
auto curLine = tokens[i].line;
|
const curLine = tokens[i].line;
|
||||||
auto prevTokenLine = tokens[i-1].line;
|
const prevTokenLine = tokens[i-1].line;
|
||||||
if (tokens[i].type == tok!"{" && curLine == prevTokenLine)
|
if (tokens[i].type == tok!"{" && curLine == prevTokenLine)
|
||||||
{
|
{
|
||||||
// ignore struct initialization
|
// ignore struct initialization
|
||||||
|
|
|
@ -93,10 +93,10 @@ private:
|
||||||
{
|
{
|
||||||
import std.algorithm : canFind;
|
import std.algorithm : canFind;
|
||||||
|
|
||||||
return builtinProperties.canFind(name);
|
return BuiltinProperties.canFind(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum string[] builtinProperties = ["init", "sizeof", "mangleof", "alignof", "stringof"];
|
enum string[] BuiltinProperties = ["init", "sizeof", "mangleof", "alignof", "stringof"];
|
||||||
int depth;
|
int depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -186,6 +186,9 @@ struct StaticAnalysisConfig
|
||||||
@INI("Check for redundant attributes")
|
@INI("Check for redundant attributes")
|
||||||
string redundant_attributes_check = Check.enabled;
|
string redundant_attributes_check = Check.enabled;
|
||||||
|
|
||||||
|
@INI("Check public declarations without a documented unittest")
|
||||||
|
string has_public_example = Check.disabled;
|
||||||
|
|
||||||
@INI("Module-specific filters")
|
@INI("Module-specific filters")
|
||||||
ModuleFilters filters;
|
ModuleFilters filters;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ class FunctionAttributeCheck : BaseAnalyzer
|
||||||
|
|
||||||
override void visit(const InterfaceDeclaration dec)
|
override void visit(const InterfaceDeclaration dec)
|
||||||
{
|
{
|
||||||
auto t = inInterface;
|
const t = inInterface;
|
||||||
inInterface = true;
|
inInterface = true;
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
inInterface = t;
|
inInterface = t;
|
||||||
|
@ -40,7 +40,7 @@ class FunctionAttributeCheck : BaseAnalyzer
|
||||||
|
|
||||||
override void visit(const ClassDeclaration dec)
|
override void visit(const ClassDeclaration dec)
|
||||||
{
|
{
|
||||||
auto t = inInterface;
|
const t = inInterface;
|
||||||
inInterface = false;
|
inInterface = false;
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
inInterface = t;
|
inInterface = t;
|
||||||
|
|
|
@ -0,0 +1,302 @@
|
||||||
|
// 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.");
|
||||||
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ class ImportSortednessCheck : BaseAnalyzer
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
int level = 0;
|
int level;
|
||||||
string[][int] imports;
|
string[][int] imports;
|
||||||
|
|
||||||
template ScopedVisit(NodeType)
|
template ScopedVisit(NodeType)
|
||||||
|
|
|
@ -78,7 +78,7 @@ private:
|
||||||
import std.conv : to;
|
import std.conv : to;
|
||||||
import std.range : retro;
|
import std.range : retro;
|
||||||
|
|
||||||
size_t i = 0;
|
size_t i;
|
||||||
foreach (s; retro(stack))
|
foreach (s; retro(stack))
|
||||||
{
|
{
|
||||||
const(Thing)* thing = name.text in s;
|
const(Thing)* thing = name.text in s;
|
||||||
|
|
|
@ -26,7 +26,7 @@ class LineLengthCheck : BaseAnalyzer
|
||||||
|
|
||||||
override void visit(const Module)
|
override void visit(const Module)
|
||||||
{
|
{
|
||||||
size_t endColumn = 0;
|
size_t endColumn;
|
||||||
lastErrorLine = ulong.max;
|
lastErrorLine = ulong.max;
|
||||||
foreach (i, token; tokens)
|
foreach (i, token; tokens)
|
||||||
{
|
{
|
||||||
|
@ -89,7 +89,6 @@ private:
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
import std.stdio;
|
|
||||||
assert(new LineLengthCheck(null, null).checkMultiLineToken(Token(tok!"stringLiteral", " ", 0, 0, 0)) == 8);
|
assert(new LineLengthCheck(null, null).checkMultiLineToken(Token(tok!"stringLiteral", " ", 0, 0, 0)) == 8);
|
||||||
assert(new LineLengthCheck(null, null).checkMultiLineToken(Token(tok!"stringLiteral", " \na", 0, 0, 0)) == 2);
|
assert(new LineLengthCheck(null, null).checkMultiLineToken(Token(tok!"stringLiteral", " \na", 0, 0, 0)) == 2);
|
||||||
assert(new LineLengthCheck(null, null).checkMultiLineToken(Token(tok!"stringLiteral", " \n ", 0, 0, 0)) == 5);
|
assert(new LineLengthCheck(null, null).checkMultiLineToken(Token(tok!"stringLiteral", " \n ", 0, 0, 0)) == 5);
|
||||||
|
@ -123,8 +122,8 @@ private:
|
||||||
{
|
{
|
||||||
import std.utf : byDchar;
|
import std.utf : byDchar;
|
||||||
|
|
||||||
size_t length = 0;
|
size_t length;
|
||||||
bool newLine = tok.line > prevLine;
|
const newLine = tok.line > prevLine;
|
||||||
bool multiLine;
|
bool multiLine;
|
||||||
|
|
||||||
if (tok.text is null)
|
if (tok.text is null)
|
||||||
|
|
|
@ -71,7 +71,7 @@ private:
|
||||||
{
|
{
|
||||||
override void visit(const T thing)
|
override void visit(const T thing)
|
||||||
{
|
{
|
||||||
auto b = interesting;
|
const b = interesting;
|
||||||
interesting = true;
|
interesting = true;
|
||||||
thing.accept(this);
|
thing.accept(this);
|
||||||
interesting = b;
|
interesting = b;
|
||||||
|
|
|
@ -67,7 +67,7 @@ class PokemonExceptionCheck : BaseAnalyzer
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto identOrTemplate = type2.symbol.identifierOrTemplateChain
|
const identOrTemplate = type2.symbol.identifierOrTemplateChain
|
||||||
.identifiersOrTemplateInstances[0];
|
.identifiersOrTemplateInstances[0];
|
||||||
if (identOrTemplate.templateInstance !is null)
|
if (identOrTemplate.templateInstance !is null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -43,7 +43,7 @@ class RedundantAttributesCheck : BaseAnalyzer
|
||||||
// new scope: private { }
|
// new scope: private { }
|
||||||
if (decl.declarations.length > 0)
|
if (decl.declarations.length > 0)
|
||||||
{
|
{
|
||||||
auto prev = currentAttributes[];
|
const prev = currentAttributes[];
|
||||||
// append to current scope and reset once block is left
|
// append to current scope and reset once block is left
|
||||||
foreach (attr; attributes)
|
foreach (attr; attributes)
|
||||||
addAttribute(attr);
|
addAttribute(attr);
|
||||||
|
@ -113,7 +113,7 @@ private:
|
||||||
void removeOverwrite(const Attribute attr)
|
void removeOverwrite(const Attribute attr)
|
||||||
{
|
{
|
||||||
import std.array : array;
|
import std.array : array;
|
||||||
auto group = getAttributeGroup(attr);
|
const group = getAttributeGroup(attr);
|
||||||
if (currentAttributes.filter!(a => getAttributeGroup(a) == group
|
if (currentAttributes.filter!(a => getAttributeGroup(a) == group
|
||||||
&& !isIdenticalAttribute(a, attr)).walkLength > 0)
|
&& !isIdenticalAttribute(a, attr)).walkLength > 0)
|
||||||
{
|
{
|
||||||
|
@ -172,7 +172,6 @@ private:
|
||||||
stack.length--;
|
stack.length--;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
enum string KEY = "dscanner.suspicious.redundant_attributes";
|
enum string KEY = "dscanner.suspicious.redundant_attributes";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ import analysis.vcall_in_ctor;
|
||||||
import analysis.useless_initializer;
|
import analysis.useless_initializer;
|
||||||
import analysis.allman;
|
import analysis.allman;
|
||||||
import analysis.redundant_attributes;
|
import analysis.redundant_attributes;
|
||||||
|
import analysis.has_public_example;
|
||||||
|
|
||||||
import dsymbol.string_interning : internString;
|
import dsymbol.string_interning : internString;
|
||||||
import dsymbol.scope_;
|
import dsymbol.scope_;
|
||||||
|
@ -168,8 +169,8 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config,
|
||||||
if (code.length == 0)
|
if (code.length == 0)
|
||||||
continue;
|
continue;
|
||||||
RollbackAllocator r;
|
RollbackAllocator r;
|
||||||
uint errorCount = 0;
|
uint errorCount;
|
||||||
uint warningCount = 0;
|
uint warningCount;
|
||||||
const(Token)[] tokens;
|
const(Token)[] tokens;
|
||||||
const Module m = parseModule(fileName, code, &r, cache, false, tokens,
|
const Module m = parseModule(fileName, code, &r, cache, false, tokens,
|
||||||
null, &errorCount, &warningCount);
|
null, &errorCount, &warningCount);
|
||||||
|
@ -476,6 +477,10 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a
|
||||||
checks ~= new RedundantAttributesCheck(fileName, moduleScope,
|
checks ~= new RedundantAttributesCheck(fileName, moduleScope,
|
||||||
analysisConfig.redundant_attributes_check == Check.skipTests && !ut);
|
analysisConfig.redundant_attributes_check == Check.skipTests && !ut);
|
||||||
|
|
||||||
|
if (analysisConfig.has_public_example!= Check.disabled)
|
||||||
|
checks ~= new HasPublicExampleCheck(fileName, moduleScope,
|
||||||
|
analysisConfig.has_public_example == Check.skipTests && !ut);
|
||||||
|
|
||||||
version (none)
|
version (none)
|
||||||
if (moduleName.shouldRun!"redundant_if_check"(analysisConfig))
|
if (moduleName.shouldRun!"redundant_if_check"(analysisConfig))
|
||||||
checks ~= new IfStatementCheck(fileName, moduleScope,
|
checks ~= new IfStatementCheck(fileName, moduleScope,
|
||||||
|
|
|
@ -97,7 +97,7 @@ final class EtagsPrinter : ASTVisitor
|
||||||
override void visit(const ModuleDeclaration dec)
|
override void visit(const ModuleDeclaration dec)
|
||||||
{
|
{
|
||||||
auto tok0 = dec.moduleName.identifiers[0];
|
auto tok0 = dec.moduleName.identifiers[0];
|
||||||
auto was = context;
|
const was = context;
|
||||||
context = "";
|
context = "";
|
||||||
maketag(moduleName, tok0.index, tok0.line);
|
maketag(moduleName, tok0.index, tok0.line);
|
||||||
context = was;
|
context = was;
|
||||||
|
@ -115,7 +115,7 @@ final class EtagsPrinter : ASTVisitor
|
||||||
|
|
||||||
// visibility needs to be restored to what it was when changed by
|
// visibility needs to be restored to what it was when changed by
|
||||||
// attribute.
|
// attribute.
|
||||||
auto was = visibility;
|
const was = visibility;
|
||||||
foreach (attr; dec.attributes)
|
foreach (attr; dec.attributes)
|
||||||
{
|
{
|
||||||
updateVisibility(attr.attribute.type);
|
updateVisibility(attr.attribute.type);
|
||||||
|
@ -217,7 +217,7 @@ final class EtagsPrinter : ASTVisitor
|
||||||
|
|
||||||
override void visit(const Unittest dec)
|
override void visit(const Unittest dec)
|
||||||
{
|
{
|
||||||
bool was = inUnittest;
|
const was = inUnittest;
|
||||||
inUnittest = true;
|
inUnittest = true;
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
inUnittest = was;
|
inUnittest = was;
|
||||||
|
@ -287,7 +287,7 @@ private:
|
||||||
void acceptInContext(const ASTNode dec, string name)
|
void acceptInContext(const ASTNode dec, string name)
|
||||||
{
|
{
|
||||||
// nest context before journeying on
|
// nest context before journeying on
|
||||||
auto c = context;
|
const c = context;
|
||||||
context ~= name ~ ".";
|
context ~= name ~ ".";
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
context = c;
|
context = c;
|
||||||
|
|
Loading…
Reference in New Issue