commit
4109254e7b
|
@ -25,9 +25,13 @@ dscanner-report.json
|
|||
*.o
|
||||
*.obj
|
||||
*.exe
|
||||
obj
|
||||
|
||||
#debug build
|
||||
# debug build
|
||||
dsc
|
||||
|
||||
# Git hash
|
||||
githash.txt
|
||||
|
||||
# GDB history
|
||||
.gdb_history
|
||||
|
|
|
@ -5,3 +5,9 @@
|
|||
[submodule "inifiled"]
|
||||
path = inifiled
|
||||
url = https://github.com/burner/inifiled.git
|
||||
[submodule "containers"]
|
||||
path = containers
|
||||
url = https://github.com/economicmodeling/containers.git
|
||||
[submodule "dsymbol"]
|
||||
path = dsymbol
|
||||
url = https://github.com/Hackerpilot/dsymbol.git
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
language: d
|
||||
sudo: false
|
||||
language: d
|
||||
script:
|
||||
- git submodule update --init --recursive
|
||||
- make test
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# Overview
|
||||
DScanner is a tool for analyzing D source code
|
||||
# D-Scanner [](https://travis-ci.org/Hackerpilot/Dscanner/)
|
||||
D-Scanner is a tool for analyzing D source code
|
||||
|
||||
### Building and installing
|
||||
First make sure that you have all the source code. Run ```git submodule update --init --recursive```
|
||||
after cloning the project.
|
||||
|
||||
To build DScanner, run ```make``` (or the build.bat file on Windows).
|
||||
To build D-Scanner, run ```make``` (or the build.bat file on Windows).
|
||||
The build time can be rather long with the -inline flag on front-end versions
|
||||
older than 2.066, so you may wish to remove it from the build script. The
|
||||
makefile has "ldc" and "gdc" targets if you'd prefer to compile with one of these
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 05b1f9f5906c4ac1f38964c7456482b3f11daa32
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 20f7facf804c889d4d31c579ddfbf174d0fccbe5
|
2
inifiled
2
inifiled
|
@ -1 +1 @@
|
|||
Subproject commit 72393ec2e0711630ad93a15c66b0ea567b5b44df
|
||||
Subproject commit ebe9c5d0a3c1c8cb5a7df13e165e6b9bdf78866f
|
|
@ -1 +1 @@
|
|||
Subproject commit bd7c1c2dbb08bf160c4b646e0aede2af1ef6e0e4
|
||||
Subproject commit b5791923695c755aae9cf313555fd20c5b0993d9
|
45
makefile
45
makefile
|
@ -1,18 +1,26 @@
|
|||
.PHONY: all test
|
||||
|
||||
DMD = dmd
|
||||
GDC = gdc
|
||||
LDC = ldc2
|
||||
SRC = src/*.d\
|
||||
src/analysis/*.d\
|
||||
libdparse/src/std/*.d\
|
||||
libdparse/src/std/d/*.d\
|
||||
inifiled/source/*.d
|
||||
INCLUDE_PATHS = -Ilibdparse/src
|
||||
DC ?= dmd
|
||||
DMD := $(DC)
|
||||
GDC := gdc
|
||||
LDC := ldc2
|
||||
OBJ_DIR := obj
|
||||
SRC := \
|
||||
$(shell find containers/experimental_allocator/src -name "*.d")\
|
||||
$(shell find containers/src -name "*.d")\
|
||||
$(shell find dsymbol/src -name "*.d")\
|
||||
$(shell find inifiled/source/ -name "*.d")\
|
||||
$(shell find libdparse/src/std/ -name "*.d")\
|
||||
$(shell find src/ -name "*.d")
|
||||
INCLUDE_PATHS = \
|
||||
-Iinifiled/source -Isrc\
|
||||
-Ilibdparse/src\
|
||||
-Ilibdparse/experimental_allocator/src\
|
||||
-Idsymbol/src -Icontainers/src
|
||||
VERSIONS =
|
||||
DEBUG_VERSIONS = -version=std_parser_verbose
|
||||
DMD_FLAGS = -w -O -release -inline
|
||||
#DMD_FLAGS = -w
|
||||
DMD_FLAGS = -w -O -inline -J. -od${OBJ_DIR} -version=StdLoggerDisableWarning
|
||||
DMD_TEST_FLAGS = -w -g -unittest -J.
|
||||
|
||||
all: dmdbuild
|
||||
ldc: ldcbuild
|
||||
|
@ -22,11 +30,11 @@ githash:
|
|||
git log -1 --format="%H" > githash.txt
|
||||
|
||||
debug:
|
||||
${DMD} -w -g -ofdsc ${VERSIONS} ${DEBUG_VERSIONS} ${INCLUDE_PATHS} ${SRC} -J.
|
||||
${DC} -w -g -J. -ofdsc ${VERSIONS} ${DEBUG_VERSIONS} ${INCLUDE_PATHS} ${SRC}
|
||||
|
||||
dmdbuild: githash
|
||||
dmdbuild: githash $(SRC)
|
||||
mkdir -p bin
|
||||
${DMD} ${DMD_FLAGS} -ofbin/dscanner ${VERSIONS} ${INCLUDE_PATHS} ${SRC} -J.
|
||||
${DC} ${DMD_FLAGS} -ofbin/dscanner ${VERSIONS} ${INCLUDE_PATHS} ${SRC}
|
||||
rm -f bin/dscanner.o
|
||||
|
||||
gdcbuild: githash
|
||||
|
@ -37,12 +45,15 @@ ldcbuild: githash
|
|||
mkdir -p bin
|
||||
${LDC} -O5 -release -oq -of=bin/dscanner ${VERSIONS} ${INCLUDE_PATHS} ${SRC} -J.
|
||||
|
||||
test:
|
||||
@./test.sh
|
||||
test: githash
|
||||
${DC} -w -g -J. -unittest ${INCLUDE_PATHS} ${SRC} -ofbin/dscanner-unittest -version=StdLoggerDisableWarning
|
||||
./bin/dscanner-unittest
|
||||
rm -f bin/dscanner-unittest
|
||||
|
||||
clean:
|
||||
rm -rf dsc *.o
|
||||
rm -rf dsc
|
||||
rm -rf bin
|
||||
rm -rf ${OBJ_DIR}
|
||||
rm -f dscanner-report.json
|
||||
|
||||
report: all
|
||||
|
|
|
@ -10,6 +10,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
/**
|
||||
* Checks for confusing asm expressions.
|
||||
|
@ -19,9 +20,9 @@ class AsmStyleCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const AsmBrExp brExp)
|
||||
|
|
|
@ -4,6 +4,7 @@ import std.container;
|
|||
import std.string;
|
||||
import std.d.ast;
|
||||
import std.array;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
struct Message
|
||||
{
|
||||
|
@ -26,8 +27,9 @@ alias MessageSet = RedBlackTree!(Message, comparitor, true);
|
|||
abstract class BaseAnalyzer : ASTVisitor
|
||||
{
|
||||
public:
|
||||
this(string fileName)
|
||||
this(string fileName, const Scope* sc)
|
||||
{
|
||||
this.sc = sc;
|
||||
this.fileName = fileName;
|
||||
_messages = new MessageSet;
|
||||
}
|
||||
|
@ -61,6 +63,8 @@ protected:
|
|||
*/
|
||||
string fileName;
|
||||
|
||||
const(Scope)* sc;
|
||||
|
||||
MessageSet _messages;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_;
|
||||
|
||||
/**
|
||||
* The following code should be killed with fire:
|
||||
|
@ -29,9 +30,9 @@ class BuiltinPropertyNameCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const FunctionDeclaration fd)
|
||||
|
|
|
@ -8,6 +8,7 @@ module analysis.comma_expression;
|
|||
import std.d.ast;
|
||||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import dsymbol.scope_;
|
||||
|
||||
/**
|
||||
* Check for uses of the comma expression.
|
||||
|
@ -16,14 +17,14 @@ class CommaExpressionCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const Expression ex)
|
||||
{
|
||||
if (ex.items.length > 1)
|
||||
if (ex.items.length > 1 && interest > 0)
|
||||
{
|
||||
addErrorMessage(ex.line, ex.column, KEY,
|
||||
"Avoid using the comma expression.");
|
||||
|
@ -31,5 +32,19 @@ class CommaExpressionCheck : BaseAnalyzer
|
|||
ex.accept(this);
|
||||
}
|
||||
|
||||
override void visit(const AssignExpression ex)
|
||||
{
|
||||
++interest;
|
||||
ex.accept(this);
|
||||
--interest;
|
||||
}
|
||||
|
||||
invariant
|
||||
{
|
||||
assert(interest >= 0);
|
||||
}
|
||||
|
||||
int interest;
|
||||
|
||||
private enum KEY = "dscanner.suspicious.comma_expression";
|
||||
}
|
||||
|
|
|
@ -93,6 +93,9 @@ struct StaticAnalysisConfig
|
|||
@INI("Checks for redundant parenthesis")
|
||||
bool redundant_parens_check;
|
||||
|
||||
@INI("Checks for mismatched argument and parameter names")
|
||||
bool mismatched_args_check;
|
||||
|
||||
@INI("Checks for labels with the same name as variables")
|
||||
bool label_var_same_name_check;
|
||||
}
|
||||
|
|
|
@ -5,15 +5,16 @@ import std.d.lexer;
|
|||
import std.stdio;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
|
||||
class ConstructorCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const ClassDeclaration classDeclaration)
|
||||
|
|
|
@ -10,6 +10,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_;
|
||||
|
||||
/**
|
||||
* Checks for use of the deprecated 'delete' keyword
|
||||
|
@ -18,9 +19,9 @@ class DeleteCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const DeleteExpression d)
|
||||
|
|
|
@ -11,6 +11,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -21,9 +22,9 @@ class DuplicateAttributeCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const Declaration node)
|
||||
|
|
|
@ -9,6 +9,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import std.algorithm : canFind;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
void doNothing(string, size_t, size_t, string, bool) {}
|
||||
|
||||
|
@ -16,9 +17,9 @@ class EnumArrayLiteralCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
bool looking = false;
|
||||
|
|
|
@ -10,6 +10,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
/**
|
||||
* Checks for use of the deprecated floating point comparison operators.
|
||||
|
@ -20,9 +21,9 @@ class FloatOperatorCheck : BaseAnalyzer
|
|||
|
||||
enum string KEY = "dscanner.deprecated.floating_point_operators";
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const RelExpression r)
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
|
||||
module analysis.function_attributes;
|
||||
|
||||
import analysis.base;
|
||||
import std.d.ast;
|
||||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
|
||||
import std.stdio;
|
||||
import dsymbol.scope_;
|
||||
|
||||
/**
|
||||
* Prefer
|
||||
|
@ -25,9 +25,9 @@ class FunctionAttributeCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const InterfaceDeclaration dec)
|
||||
|
|
|
@ -59,8 +59,10 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, stri
|
|||
ParseAllocator p = new ParseAllocator;
|
||||
const(Module) m = parseModule(file, cast(ubyte[]) code, p, cache, false);
|
||||
|
||||
auto moduleCache = ModuleCache(p);
|
||||
|
||||
// Run the code and get any warnings
|
||||
MessageSet rawWarnings = analyze("test", m, config);
|
||||
MessageSet rawWarnings = analyze("test", m, config, moduleCache);
|
||||
string[] codeLines = code.split("\n");
|
||||
|
||||
// Get the warnings ordered by line
|
||||
|
|
|
@ -8,15 +8,14 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import std.d.formatter;
|
||||
import analysis.base;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
class IfStatementCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
import std.container.binaryheap : heapify;
|
||||
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const IfStatement ifStatement)
|
||||
|
|
|
@ -6,12 +6,11 @@
|
|||
module analysis.ifelsesame;
|
||||
|
||||
import std.stdio;
|
||||
|
||||
import std.d.ast;
|
||||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
/**
|
||||
* Checks for duplicated code in conditional and logical expressions.
|
||||
|
@ -25,9 +24,9 @@ class IfElseSameCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const IfStatement ifStatement)
|
||||
|
@ -41,7 +40,7 @@ class IfElseSameCheck : BaseAnalyzer
|
|||
|
||||
override void visit(const AssignExpression assignExpression)
|
||||
{
|
||||
const AssignExpression e = cast(const AssignExpression) assignExpression.assignExpression;
|
||||
auto e = cast(const AssignExpression) (cast(const Expression) assignExpression.expression).items[$ - 1];
|
||||
if (e !is null && assignExpression.operator == tok!"="
|
||||
&& e.ternaryExpression == assignExpression.ternaryExpression)
|
||||
{
|
||||
|
|
|
@ -15,9 +15,9 @@ import analysis.helpers;
|
|||
*/
|
||||
class LabelVarNameCheck : BaseAnalyzer
|
||||
{
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const Module mod)
|
||||
|
@ -44,35 +44,52 @@ class LabelVarNameCheck : BaseAnalyzer
|
|||
override void visit(const VariableDeclaration var)
|
||||
{
|
||||
foreach (dec; var.declarators)
|
||||
duplicateCheck(dec.name, false);
|
||||
duplicateCheck(dec.name, false, conditionalDepth > 0);
|
||||
}
|
||||
|
||||
override void visit(const LabeledStatement labeledStatement)
|
||||
{
|
||||
duplicateCheck(labeledStatement.identifier, true);
|
||||
duplicateCheck(labeledStatement.identifier, true, conditionalDepth > 0);
|
||||
if (labeledStatement.declarationOrStatement !is null)
|
||||
labeledStatement.declarationOrStatement.accept(this);
|
||||
}
|
||||
|
||||
override void visit(const ConditionalDeclaration condition)
|
||||
{
|
||||
if (condition.falseDeclaration)
|
||||
++conditionalDepth;
|
||||
condition.accept(this);
|
||||
if (condition.falseDeclaration)
|
||||
--conditionalDepth;
|
||||
}
|
||||
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
private:
|
||||
|
||||
Thing[string][] stack;
|
||||
|
||||
void duplicateCheck(const Token name, bool fromLabel)
|
||||
void duplicateCheck(const Token name, bool fromLabel, bool isConditional)
|
||||
{
|
||||
import std.conv : to;
|
||||
const(Thing)* thing = name.text in currentScope;
|
||||
if (thing is null)
|
||||
currentScope[name.text] = Thing(name.text, name.line, name.column, false);
|
||||
else
|
||||
import std.range : retro;
|
||||
|
||||
size_t i = 0;
|
||||
foreach (s; retro(stack))
|
||||
{
|
||||
immutable thisKind = fromLabel ? "Label" : "Variable";
|
||||
immutable otherKind = thing.isVar ? "variable" : "label";
|
||||
addErrorMessage(name.line, name.column, "dscanner.suspicious.label_var_same_name",
|
||||
thisKind ~ " \"" ~ name.text ~ "\" has the same name as a "
|
||||
~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ ".");
|
||||
const(Thing)* thing = name.text in s;
|
||||
if (thing is null)
|
||||
currentScope[name.text] = Thing(name.text, name.line, name.column,
|
||||
!fromLabel/+, isConditional+/);
|
||||
else if (i != 0 || !isConditional)
|
||||
{
|
||||
immutable thisKind = fromLabel ? "Label" : "Variable";
|
||||
immutable otherKind = thing.isVar ? "variable" : "label";
|
||||
addErrorMessage(name.line, name.column, "dscanner.suspicious.label_var_same_name",
|
||||
thisKind ~ " \"" ~ name.text ~ "\" has the same name as a "
|
||||
~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ ".");
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,6 +99,7 @@ private:
|
|||
size_t line;
|
||||
size_t column;
|
||||
bool isVar;
|
||||
//bool isConditional;
|
||||
}
|
||||
|
||||
ref currentScope() @property
|
||||
|
@ -98,6 +116,8 @@ private:
|
|||
{
|
||||
stack.length--;
|
||||
}
|
||||
|
||||
int conditionalDepth;
|
||||
}
|
||||
|
||||
unittest
|
||||
|
@ -114,6 +134,29 @@ blah:
|
|||
int blah; // [warn]: Variable "blah" has the same name as a label defined on line 4.
|
||||
}
|
||||
int blah;
|
||||
}c, sac);
|
||||
unittest
|
||||
{
|
||||
static if (stuff)
|
||||
int a;
|
||||
int a; // [warn]: Variable "a" has the same name as a variable defined on line 11.
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
static if (stuff)
|
||||
int a = 10;
|
||||
else
|
||||
int a = 20;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
static if (stuff)
|
||||
int a = 10;
|
||||
else
|
||||
int a = 20;
|
||||
int a; // [warn]: Variable "a" has the same name as a variable defined on line 28.
|
||||
}
|
||||
}c, sac);
|
||||
stderr.writeln("Unittest for LabelVarNameCheck passed.");
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -20,9 +21,9 @@ class LengthSubtractionCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const AddExpression addExpression)
|
||||
|
|
|
@ -10,6 +10,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_;
|
||||
|
||||
/**
|
||||
* Checks for local imports that import all symbols.
|
||||
|
@ -22,9 +23,9 @@ class LocalImportCheck : BaseAnalyzer
|
|||
/**
|
||||
* Construct with the given file name.
|
||||
*/
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
mixin visitThing!StructBody;
|
||||
|
|
|
@ -10,6 +10,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_;
|
||||
|
||||
/**
|
||||
* Checks for code with confusing && and || operator precedence
|
||||
|
@ -24,9 +25,9 @@ class LogicPrecedenceCheck : BaseAnalyzer
|
|||
|
||||
enum string KEY = "dscanner.confusing.logical_precedence";
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const OrOrExpression orOr)
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
module analysis.mismatched_args;
|
||||
|
||||
import analysis.base : BaseAnalyzer;
|
||||
import dsymbol.scope_;
|
||||
import dsymbol.symbol;
|
||||
import std.d.ast;
|
||||
import std.d.lexer : tok;
|
||||
import dsymbol.builtin.names;
|
||||
|
||||
/// Checks for mismatched argument and parameter names
|
||||
final class MismatchedArgumentCheck : BaseAnalyzer
|
||||
{
|
||||
///
|
||||
this(string fileName, const Scope* sc)
|
||||
{
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const FunctionCallExpression fce)
|
||||
{
|
||||
import std.typecons : scoped;
|
||||
import std.algorithm.iteration : each, map;
|
||||
import std.array : array;
|
||||
|
||||
if (fce.arguments is null)
|
||||
return;
|
||||
auto argVisitor = scoped!ArgVisitor;
|
||||
argVisitor.visit(fce.arguments);
|
||||
const istring[] args = argVisitor.args;
|
||||
|
||||
auto identVisitor = scoped!IdentVisitor;
|
||||
identVisitor.visit(fce.unaryExpression);
|
||||
|
||||
const(DSymbol)* sym = resolveSymbol(sc,
|
||||
identVisitor.names.length > 0 ? identVisitor.names : [CONSTRUCTOR_SYMBOL_NAME]);
|
||||
// The cast is a hack because .array() confuses the compiler's overload
|
||||
// resolution code.
|
||||
const(istring)[] params = sym is null ? [] : sym.argNames[].map!(a => cast() a).array();
|
||||
const ArgMismatch[] mismatches = compareArgsToParams(params, args);
|
||||
foreach (size_t i, ref const mm; mismatches)
|
||||
addErrorMessage(argVisitor.lines[i], argVisitor.columns[i], KEY,
|
||||
createWarningFromMismatch(mm));
|
||||
}
|
||||
|
||||
alias visit = ASTVisitor.visit;
|
||||
|
||||
private:
|
||||
|
||||
enum KEY = "dscanner.confusing.argument_parameter_mismatch";
|
||||
}
|
||||
|
||||
final class IdentVisitor : ASTVisitor
|
||||
{
|
||||
override void visit(const IdentifierOrTemplateInstance ioti)
|
||||
{
|
||||
import dsymbol.string_interning : internString;
|
||||
|
||||
if (ioti.identifier != tok!"")
|
||||
names ~= internString(ioti.identifier.text);
|
||||
else
|
||||
names ~= internString(ioti.templateInstance.identifier.text);
|
||||
}
|
||||
|
||||
override void visit(const Arguments)
|
||||
{
|
||||
}
|
||||
|
||||
override void visit(const IndexExpression ie)
|
||||
{
|
||||
if (ie.unaryExpression !is null)
|
||||
visit(ie.unaryExpression);
|
||||
}
|
||||
|
||||
alias visit = ASTVisitor.visit;
|
||||
|
||||
istring[] names;
|
||||
}
|
||||
|
||||
final class ArgVisitor : ASTVisitor
|
||||
{
|
||||
override void visit(const ArgumentList al)
|
||||
{
|
||||
foreach (a; al.items)
|
||||
{
|
||||
auto u = cast(UnaryExpression) a;
|
||||
if (u !is null)
|
||||
visit(u);
|
||||
else
|
||||
{
|
||||
args ~= istring.init;
|
||||
lines ~= size_t.max;
|
||||
columns ~= size_t.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override void visit(const UnaryExpression unary)
|
||||
{
|
||||
import dsymbol.string_interning : internString;
|
||||
|
||||
if (unary.primaryExpression is null)
|
||||
return;
|
||||
if (unary.primaryExpression.identifierOrTemplateInstance is null)
|
||||
return;
|
||||
if (unary.primaryExpression.identifierOrTemplateInstance.identifier == tok!"")
|
||||
return;
|
||||
immutable t = unary.primaryExpression.identifierOrTemplateInstance.identifier;
|
||||
lines ~= t.line;
|
||||
columns ~= t.column;
|
||||
args ~= internString(t.text);
|
||||
}
|
||||
|
||||
alias visit = ASTVisitor.visit;
|
||||
|
||||
size_t[] lines;
|
||||
size_t[] columns;
|
||||
istring[] args;
|
||||
}
|
||||
|
||||
const(DSymbol)* resolveSymbol(const Scope* sc, const istring[] symbolChain)
|
||||
{
|
||||
import std.array : empty;
|
||||
|
||||
const(DSymbol)*[] s = sc.getSymbolsByName(symbolChain[0]);
|
||||
if (s.empty)
|
||||
return null;
|
||||
|
||||
const(DSymbol)* sym = s[0];
|
||||
foreach (i; 1 .. symbolChain.length)
|
||||
{
|
||||
if (sym.kind == CompletionKind.variableName
|
||||
|| sym.kind == CompletionKind.memberVariableName
|
||||
|| sym.kind == CompletionKind.functionName)
|
||||
sym = sym.type;
|
||||
if (sym is null)
|
||||
return null;
|
||||
auto p = sym.getPartsByName(symbolChain[i]);
|
||||
if (p.empty)
|
||||
return null;
|
||||
sym = p[0];
|
||||
}
|
||||
return sym;
|
||||
}
|
||||
|
||||
struct ArgMismatch
|
||||
{
|
||||
size_t argIndex;
|
||||
size_t paramIndex;
|
||||
string name;
|
||||
}
|
||||
|
||||
immutable(ArgMismatch[]) compareArgsToParams(const istring[] params, const istring[] args) pure
|
||||
{
|
||||
import std.exception : assumeUnique;
|
||||
|
||||
if (args.length != params.length)
|
||||
return [];
|
||||
ArgMismatch[] retVal;
|
||||
foreach (i, arg; args)
|
||||
{
|
||||
if (arg is null || arg == params[i])
|
||||
continue;
|
||||
foreach (j, param; params)
|
||||
if (param == arg)
|
||||
retVal ~= ArgMismatch(i, j, arg);
|
||||
}
|
||||
return assumeUnique(retVal);
|
||||
}
|
||||
|
||||
string createWarningFromMismatch(const ArgMismatch mismatch) pure
|
||||
{
|
||||
import std.format : format;
|
||||
|
||||
return "Argument %d is named '%s', but this is the name of parameter %d".format(
|
||||
mismatch.argIndex + 1, mismatch.name, mismatch.paramIndex + 1);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
import dsymbol.string_interning : internString;
|
||||
import std.algorithm.iteration : map;
|
||||
import std.array : array;
|
||||
import std.conv : to;
|
||||
|
||||
{
|
||||
istring[] args = ["a", "b", "c"].map!internString().array();
|
||||
istring[] params = ["a", "b", "c"].map!internString().array();
|
||||
immutable res = compareArgsToParams(params, args);
|
||||
assert(res == []);
|
||||
}
|
||||
|
||||
{
|
||||
istring[] args = ["a", "c", "b"].map!internString().array();
|
||||
istring[] params = ["a", "b", "c"].map!internString().array();
|
||||
immutable res = compareArgsToParams(params, args);
|
||||
assert(res == [ArgMismatch(1, 2, "c"), ArgMismatch(2, 1, "b")], to!string(res));
|
||||
}
|
||||
|
||||
{
|
||||
istring[] args = ["a", "c", "b"].map!internString().array();
|
||||
istring[] params = ["alpha", "bravo", "c"].map!internString().array();
|
||||
immutable res = compareArgsToParams(params, args);
|
||||
assert(res == [ArgMismatch(1, 2, "c")]);
|
||||
}
|
||||
|
||||
{
|
||||
istring[] args = ["a", "b"].map!internString().array();
|
||||
istring[] params = [null, "b"].map!internString().array();
|
||||
immutable res = compareArgsToParams(params, args);
|
||||
assert(res == []);
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
/**
|
||||
* Checks for long and hard-to-read number literals
|
||||
|
@ -23,9 +24,9 @@ public:
|
|||
/**
|
||||
* Constructs the style checker with the given file name.
|
||||
*/
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const Token t)
|
||||
|
|
|
@ -11,6 +11,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
/**
|
||||
* Checks that opEquals, opCmp, toHash, and toString are either const,
|
||||
|
@ -20,9 +21,9 @@ class ObjectConstCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
mixin visitTemplate!ClassDeclaration;
|
||||
|
|
|
@ -10,6 +10,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
/**
|
||||
* Checks for when a class/struct has the method opEquals without toHash, or
|
||||
|
@ -19,9 +20,9 @@ class OpEqualsWithoutToHashCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const ClassDeclaration node)
|
||||
|
|
|
@ -10,6 +10,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -30,9 +31,9 @@ class PokemonExceptionCheck : BaseAnalyzer
|
|||
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const LastCatch lc)
|
||||
|
|
|
@ -10,6 +10,7 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
/**
|
||||
* Checks for .. expressions where the left side is larger than the right. This
|
||||
|
@ -26,9 +27,9 @@ class BackwardsRangeCheck : BaseAnalyzer
|
|||
* Params:
|
||||
* fileName = the name of the file being analyzed
|
||||
*/
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const ForeachStatement foreachStatement)
|
||||
|
@ -56,7 +57,7 @@ class BackwardsRangeCheck : BaseAnalyzer
|
|||
|
||||
override void visit(const AddExpression add)
|
||||
{
|
||||
auto s = state;
|
||||
immutable s = state;
|
||||
state = State.ignore;
|
||||
add.accept(this);
|
||||
state = s;
|
||||
|
|
|
@ -8,14 +8,15 @@ module analysis.redundant_parens;
|
|||
import std.d.ast;
|
||||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
class RedundantParenCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const IfStatement statement)
|
||||
|
|
|
@ -14,6 +14,12 @@ import std.array;
|
|||
import std.d.lexer;
|
||||
import std.d.parser;
|
||||
import std.d.ast;
|
||||
import std.typecons : scoped;
|
||||
|
||||
import std.experimental.allocator : CAllocatorImpl;
|
||||
import std.experimental.allocator.mallocator : Mallocator;
|
||||
import std.experimental.allocator.building_blocks.region : Region;
|
||||
import std.experimental.allocator.building_blocks.allocator_list : AllocatorList;
|
||||
|
||||
import analysis.config;
|
||||
import analysis.base;
|
||||
|
@ -43,18 +49,30 @@ import analysis.local_imports;
|
|||
import analysis.unmodified;
|
||||
import analysis.if_statements;
|
||||
import analysis.redundant_parens;
|
||||
import analysis.mismatched_args;
|
||||
import analysis.label_var_same_name_check;
|
||||
|
||||
import dsymbol.string_interning : internString;
|
||||
import dsymbol.scope_;
|
||||
import dsymbol.semantic;
|
||||
import dsymbol.conversion;
|
||||
import dsymbol.conversion.first;
|
||||
import dsymbol.conversion.second;
|
||||
import dsymbol.modulecache : ModuleCache;
|
||||
|
||||
bool first = true;
|
||||
|
||||
void messageFunction(string fileName, size_t line, size_t column, string message,
|
||||
bool isError)
|
||||
private alias ASTAllocator = CAllocatorImpl!(
|
||||
AllocatorList!(n => Region!Mallocator(1024 * 128), Mallocator));
|
||||
|
||||
void messageFunction(string fileName, size_t line, size_t column, string message, bool isError)
|
||||
{
|
||||
writefln("%s(%d:%d)[%s]: %s", fileName, line, column,
|
||||
isError ? "error" : "warn", message);
|
||||
writefln("%s(%d:%d)[%s]: %s", fileName, line, column, isError ? "error" : "warn",
|
||||
message);
|
||||
}
|
||||
|
||||
void messageFunctionJSON(string fileName, size_t line, size_t column, string message, bool)
|
||||
void messageFunctionJSON(string fileName, size_t line, size_t column, string message,
|
||||
bool)
|
||||
{
|
||||
writeJSON("dscanner.syntax", fileName, line, column, message);
|
||||
}
|
||||
|
@ -71,16 +89,17 @@ void writeJSON(string key, string fileName, size_t line, size_t column, string m
|
|||
writeln(` "line": `, line, `,`);
|
||||
writeln(` "column": `, column, `,`);
|
||||
writeln(` "message": "`, message.replace(`"`, `\"`), `"`);
|
||||
write( " }");
|
||||
write(" }");
|
||||
}
|
||||
|
||||
bool syntaxCheck(string[] fileNames)
|
||||
bool syntaxCheck(string[] fileNames, ref StringCache stringCache, ref ModuleCache moduleCache)
|
||||
{
|
||||
StaticAnalysisConfig config = defaultStaticAnalysisConfig();
|
||||
return analyze(fileNames, config, false);
|
||||
return analyze(fileNames, config, stringCache, moduleCache, false);
|
||||
}
|
||||
|
||||
void generateReport(string[] fileNames, const StaticAnalysisConfig config)
|
||||
void generateReport(string[] fileNames, const StaticAnalysisConfig config,
|
||||
ref StringCache cache, ref ModuleCache moduleCache)
|
||||
{
|
||||
writeln("{");
|
||||
writeln(` "issues": [`);
|
||||
|
@ -90,14 +109,14 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config)
|
|||
foreach (fileName; fileNames)
|
||||
{
|
||||
File f = File(fileName);
|
||||
if (f.size == 0) continue;
|
||||
if (f.size == 0)
|
||||
continue;
|
||||
auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
|
||||
f.rawRead(code);
|
||||
ParseAllocator p = new ParseAllocator;
|
||||
StringCache cache = StringCache(StringCache.defaultBucketCount);
|
||||
const Module m = parseModule(fileName, code, p, cache, true, &lineOfCodeCount);
|
||||
stats.visit(m);
|
||||
MessageSet results = analyze(fileName, m, config, true);
|
||||
MessageSet results = analyze(fileName, m, config, moduleCache, true);
|
||||
foreach (result; results[])
|
||||
{
|
||||
writeJSON(result.key, result.fileName, result.line, result.column, result.message);
|
||||
|
@ -121,25 +140,26 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config)
|
|||
*
|
||||
* Returns: true if there were errors or if there were warnings and `staticAnalyze` was true.
|
||||
*/
|
||||
bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticAnalyze = true)
|
||||
bool analyze(string[] fileNames, const StaticAnalysisConfig config,
|
||||
ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
|
||||
{
|
||||
bool hasErrors = false;
|
||||
foreach (fileName; fileNames)
|
||||
{
|
||||
File f = File(fileName);
|
||||
if (f.size == 0) continue;
|
||||
if (f.size == 0)
|
||||
continue;
|
||||
auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
|
||||
f.rawRead(code);
|
||||
ParseAllocator p = new ParseAllocator;
|
||||
StringCache cache = StringCache(StringCache.defaultBucketCount);
|
||||
uint errorCount = 0;
|
||||
uint warningCount = 0;
|
||||
const Module m = parseModule(fileName, code, p, cache, false, null,
|
||||
&errorCount, &warningCount);
|
||||
assert (m);
|
||||
assert(m);
|
||||
if (errorCount > 0 || (staticAnalyze && warningCount > 0))
|
||||
hasErrors = true;
|
||||
MessageSet results = analyze(fileName, m, config, staticAnalyze);
|
||||
MessageSet results = analyze(fileName, m, config, moduleCache, staticAnalyze);
|
||||
if (results is null)
|
||||
continue;
|
||||
foreach (result; results[])
|
||||
|
@ -154,6 +174,7 @@ const(Module) parseModule(string fileName, ubyte[] code, ParseAllocator p,
|
|||
uint* errorCount = null, uint* warningCount = null)
|
||||
{
|
||||
import stats : isLineOfCode;
|
||||
|
||||
LexerConfig config;
|
||||
config.fileName = fileName;
|
||||
config.stringBehavior = StringBehavior.source;
|
||||
|
@ -161,44 +182,81 @@ const(Module) parseModule(string fileName, ubyte[] code, ParseAllocator p,
|
|||
if (linesOfCode !is null)
|
||||
(*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens);
|
||||
return std.d.parser.parseModule(tokens, fileName, p,
|
||||
report ? &messageFunctionJSON : &messageFunction,
|
||||
errorCount, warningCount);
|
||||
report ? &messageFunctionJSON : &messageFunction, errorCount, warningCount);
|
||||
}
|
||||
|
||||
MessageSet analyze(string fileName, const Module m,
|
||||
const StaticAnalysisConfig analysisConfig, bool staticAnalyze = true)
|
||||
const StaticAnalysisConfig analysisConfig, ref ModuleCache moduleCache, bool staticAnalyze = true)
|
||||
{
|
||||
if (!staticAnalyze)
|
||||
return null;
|
||||
|
||||
auto symbolAllocator = new ASTAllocator;
|
||||
auto first = scoped!FirstPass(m, internString(fileName), symbolAllocator,
|
||||
symbolAllocator, true, &moduleCache, null);
|
||||
first.run();
|
||||
|
||||
secondPass(first.rootSymbol, first.moduleScope, moduleCache);
|
||||
typeid(SemanticSymbol).destroy(first.rootSymbol);
|
||||
const(Scope)* moduleScope = first.moduleScope;
|
||||
|
||||
BaseAnalyzer[] checks;
|
||||
|
||||
if (analysisConfig.style_check) checks ~= new StyleChecker(fileName);
|
||||
if (analysisConfig.enum_array_literal_check) checks ~= new EnumArrayLiteralCheck(fileName);
|
||||
if (analysisConfig.exception_check) checks ~= new PokemonExceptionCheck(fileName);
|
||||
if (analysisConfig.delete_check) checks ~= new DeleteCheck(fileName);
|
||||
if (analysisConfig.float_operator_check) checks ~= new FloatOperatorCheck(fileName);
|
||||
if (analysisConfig.number_style_check) checks ~= new NumberStyleCheck(fileName);
|
||||
if (analysisConfig.object_const_check) checks ~= new ObjectConstCheck(fileName);
|
||||
if (analysisConfig.backwards_range_check) checks ~= new BackwardsRangeCheck(fileName);
|
||||
if (analysisConfig.if_else_same_check) checks ~= new IfElseSameCheck(fileName);
|
||||
if (analysisConfig.constructor_check) checks ~= new ConstructorCheck(fileName);
|
||||
if (analysisConfig.unused_label_check) checks ~= new UnusedLabelCheck(fileName);
|
||||
if (analysisConfig.unused_variable_check) checks ~= new UnusedVariableCheck(fileName);
|
||||
if (analysisConfig.duplicate_attribute) checks ~= new DuplicateAttributeCheck(fileName);
|
||||
if (analysisConfig.opequals_tohash_check) checks ~= new OpEqualsWithoutToHashCheck(fileName);
|
||||
if (analysisConfig.length_subtraction_check) checks ~= new LengthSubtractionCheck(fileName);
|
||||
if (analysisConfig.builtin_property_names_check) checks ~= new BuiltinPropertyNameCheck(fileName);
|
||||
if (analysisConfig.asm_style_check) checks ~= new AsmStyleCheck(fileName);
|
||||
if (analysisConfig.logical_precedence_check) checks ~= new LogicPrecedenceCheck(fileName);
|
||||
if (analysisConfig.undocumented_declaration_check) checks ~= new UndocumentedDeclarationCheck(fileName);
|
||||
if (analysisConfig.function_attribute_check) checks ~= new FunctionAttributeCheck(fileName);
|
||||
if (analysisConfig.comma_expression_check) checks ~= new CommaExpressionCheck(fileName);
|
||||
if (analysisConfig.local_import_check) checks ~= new LocalImportCheck(fileName);
|
||||
if (analysisConfig.could_be_immutable_check) checks ~= new UnmodifiedFinder(fileName);
|
||||
if (analysisConfig.redundant_parens_check) checks ~= new RedundantParenCheck(fileName);
|
||||
if (analysisConfig.label_var_same_name_check) checks ~= new LabelVarNameCheck(fileName);
|
||||
version(none) if (analysisConfig.redundant_if_check) checks ~= new IfStatementCheck(fileName);
|
||||
if (analysisConfig.asm_style_check)
|
||||
checks ~= new AsmStyleCheck(fileName, moduleScope);
|
||||
if (analysisConfig.backwards_range_check)
|
||||
checks ~= new BackwardsRangeCheck(fileName, moduleScope);
|
||||
if (analysisConfig.builtin_property_names_check)
|
||||
checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope);
|
||||
if (analysisConfig.comma_expression_check)
|
||||
checks ~= new CommaExpressionCheck(fileName, moduleScope);
|
||||
if (analysisConfig.constructor_check)
|
||||
checks ~= new ConstructorCheck(fileName, moduleScope);
|
||||
if (analysisConfig.could_be_immutable_check)
|
||||
checks ~= new UnmodifiedFinder(fileName, moduleScope);
|
||||
if (analysisConfig.delete_check)
|
||||
checks ~= new DeleteCheck(fileName, moduleScope);
|
||||
if (analysisConfig.duplicate_attribute)
|
||||
checks ~= new DuplicateAttributeCheck(fileName, moduleScope);
|
||||
if (analysisConfig.enum_array_literal_check)
|
||||
checks ~= new EnumArrayLiteralCheck(fileName, moduleScope);
|
||||
if (analysisConfig.exception_check)
|
||||
checks ~= new PokemonExceptionCheck(fileName, moduleScope);
|
||||
if (analysisConfig.float_operator_check)
|
||||
checks ~= new FloatOperatorCheck(fileName, moduleScope);
|
||||
if (analysisConfig.function_attribute_check)
|
||||
checks ~= new FunctionAttributeCheck(fileName, moduleScope);
|
||||
if (analysisConfig.if_else_same_check)
|
||||
checks ~= new IfElseSameCheck(fileName, moduleScope);
|
||||
if (analysisConfig.label_var_same_name_check)
|
||||
checks ~= new LabelVarNameCheck(fileName, moduleScope);
|
||||
if (analysisConfig.length_subtraction_check)
|
||||
checks ~= new LengthSubtractionCheck(fileName, moduleScope);
|
||||
if (analysisConfig.local_import_check)
|
||||
checks ~= new LocalImportCheck(fileName, moduleScope);
|
||||
if (analysisConfig.logical_precedence_check)
|
||||
checks ~= new LogicPrecedenceCheck(fileName, moduleScope);
|
||||
if (analysisConfig.mismatched_args_check)
|
||||
checks ~= new MismatchedArgumentCheck(fileName, moduleScope);
|
||||
if (analysisConfig.number_style_check)
|
||||
checks ~= new NumberStyleCheck(fileName, moduleScope);
|
||||
if (analysisConfig.object_const_check)
|
||||
checks ~= new ObjectConstCheck(fileName, moduleScope);
|
||||
if (analysisConfig.opequals_tohash_check)
|
||||
checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope);
|
||||
if (analysisConfig.redundant_parens_check)
|
||||
checks ~= new RedundantParenCheck(fileName, moduleScope);
|
||||
if (analysisConfig.style_check)
|
||||
checks ~= new StyleChecker(fileName, moduleScope);
|
||||
if (analysisConfig.undocumented_declaration_check)
|
||||
checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope);
|
||||
if (analysisConfig.unused_label_check)
|
||||
checks ~= new UnusedLabelCheck(fileName, moduleScope);
|
||||
if (analysisConfig.unused_variable_check)
|
||||
checks ~= new UnusedVariableCheck(fileName, moduleScope);
|
||||
version (none)
|
||||
if (analysisConfig.redundant_if_check)
|
||||
checks ~= new IfStatementCheck(fileName, moduleScope);
|
||||
|
||||
foreach (check; checks)
|
||||
{
|
||||
|
@ -211,4 +269,3 @@ MessageSet analyze(string fileName, const Module m,
|
|||
set.insert(message);
|
||||
return set;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ class StatsCollector : BaseAnalyzer
|
|||
|
||||
this(string fileName)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, null);
|
||||
}
|
||||
|
||||
override void visit(const Statement statement)
|
||||
|
|
|
@ -13,8 +13,8 @@ import std.array;
|
|||
import std.conv;
|
||||
import std.format;
|
||||
import analysis.helpers;
|
||||
|
||||
import analysis.base;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
class StyleChecker : BaseAnalyzer
|
||||
{
|
||||
|
@ -25,9 +25,9 @@ class StyleChecker : BaseAnalyzer
|
|||
enum string moduleNameRegex = `^[\p{Ll}_\d]+$`;
|
||||
enum string KEY = "dscanner.style.phobos_naming_convention";
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const ModuleDeclaration dec)
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
|
||||
module analysis.undocumented;
|
||||
|
||||
import analysis.base;
|
||||
import dsymbol.scope_ : Scope;
|
||||
import std.d.ast;
|
||||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
|
||||
import std.regex : ctRegex, matchAll;
|
||||
import std.stdio;
|
||||
|
@ -20,9 +21,9 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
|
|||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const Module mod)
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
// http://www.boost.org/LICENSE_1_0.txt)
|
||||
module analysis.unmodified;
|
||||
|
||||
import analysis.base;
|
||||
import dsymbol.scope_ : Scope;
|
||||
import std.container;
|
||||
import std.d.ast;
|
||||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
|
||||
/**
|
||||
* Checks for variables that could have been declared const or immutable
|
||||
|
@ -17,9 +18,9 @@ class UnmodifiedFinder:BaseAnalyzer
|
|||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
///
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const Module mod)
|
||||
|
@ -41,7 +42,7 @@ class UnmodifiedFinder:BaseAnalyzer
|
|||
override void visit(const StructBody structBody)
|
||||
{
|
||||
pushScope();
|
||||
auto oldBlockStatementDepth = blockStatementDepth;
|
||||
immutable oldBlockStatementDepth = blockStatementDepth;
|
||||
blockStatementDepth = 0;
|
||||
structBody.accept(this);
|
||||
blockStatementDepth = oldBlockStatementDepth;
|
||||
|
@ -58,7 +59,7 @@ class UnmodifiedFinder:BaseAnalyzer
|
|||
if (initializedFromCast(d.initializer))
|
||||
continue;
|
||||
tree[$ - 1].insert(new VariableInfo(d.name.text, d.name.line,
|
||||
d.name.column));
|
||||
d.name.column, isValueTypeSimple(dec.type)));
|
||||
}
|
||||
}
|
||||
dec.accept(this);
|
||||
|
@ -88,9 +89,16 @@ class UnmodifiedFinder:BaseAnalyzer
|
|||
if (assignExpression.operator != tok!"")
|
||||
{
|
||||
interest++;
|
||||
guaranteeUse++;
|
||||
assignExpression.ternaryExpression.accept(this);
|
||||
guaranteeUse--;
|
||||
interest--;
|
||||
assignExpression.assignExpression.accept(this);
|
||||
|
||||
if (assignExpression.operator == tok!"~=")
|
||||
interest++;
|
||||
assignExpression.expression.accept(this);
|
||||
if (assignExpression.operator == tok!"~=")
|
||||
interest--;
|
||||
}
|
||||
else
|
||||
assignExpression.accept(this);
|
||||
|
@ -132,10 +140,13 @@ class UnmodifiedFinder:BaseAnalyzer
|
|||
override void visit(const UnaryExpression unary)
|
||||
{
|
||||
if (unary.prefix == tok!"++" || unary.prefix == tok!"--"
|
||||
|| unary.suffix == tok!"++" || unary.suffix == tok!"--")
|
||||
|| unary.suffix == tok!"++" || unary.suffix == tok!"--"
|
||||
|| unary.prefix == tok!"*" || unary.prefix == tok!"&")
|
||||
{
|
||||
interest++;
|
||||
guaranteeUse++;
|
||||
unary.accept(this);
|
||||
guaranteeUse--;
|
||||
interest--;
|
||||
}
|
||||
else
|
||||
|
@ -163,6 +174,13 @@ class UnmodifiedFinder:BaseAnalyzer
|
|||
// issue #270: Ignore unmodified variables inside of `typeof` expressions
|
||||
}
|
||||
|
||||
override void visit(const AsmStatement a)
|
||||
{
|
||||
inAsm = true;
|
||||
a.accept(this);
|
||||
inAsm = false;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template PartsMightModify(T)
|
||||
|
@ -177,10 +195,14 @@ private:
|
|||
|
||||
void variableMightBeModified(string name)
|
||||
{
|
||||
// import std.stdio : stderr;
|
||||
// stderr.writeln("Marking ", name, " as possibly modified");
|
||||
size_t index = tree.length - 1;
|
||||
auto vi = VariableInfo(name);
|
||||
if (guaranteeUse == 0)
|
||||
{
|
||||
auto r = tree[index].equalRange(&vi);
|
||||
if (!r.empty && r.front.isValueType && !inAsm)
|
||||
return;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
if (tree[index].removeKey(&vi) != 0 || index == 0)
|
||||
|
@ -245,6 +267,7 @@ private:
|
|||
string name;
|
||||
size_t line;
|
||||
size_t column;
|
||||
bool isValueType;
|
||||
}
|
||||
|
||||
void popScope()
|
||||
|
@ -269,8 +292,18 @@ private:
|
|||
|
||||
int interest;
|
||||
|
||||
int guaranteeUse;
|
||||
|
||||
int isImmutable;
|
||||
|
||||
bool inAsm;
|
||||
|
||||
RedBlackTree!(VariableInfo*, "a.name < b.name")[] tree;
|
||||
}
|
||||
|
||||
bool isValueTypeSimple(const Type type) pure nothrow @nogc
|
||||
{
|
||||
if (type.type2 is null)
|
||||
return false;
|
||||
return type.type2.builtinType != tok!"" && type.typeSuffixes.length == 0;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright Brian Schott (Hackerpilot) 2014.
|
||||
// Copyright Brian Schott (Hackerpilot) 2014-2015.
|
||||
// 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.unused;
|
||||
|
||||
import std.d.ast;
|
||||
|
@ -10,6 +9,7 @@ import std.d.lexer;
|
|||
import analysis.base;
|
||||
import std.container;
|
||||
import std.regex : Regex, regex, matchAll;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
/**
|
||||
* Checks for unused variables.
|
||||
|
@ -22,9 +22,9 @@ class UnusedVariableCheck : BaseAnalyzer
|
|||
* Params:
|
||||
* fileName = the name of the file being analyzed
|
||||
*/
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
re = regex("[\\p{Alphabetic}_][\\w_]*");
|
||||
}
|
||||
|
||||
|
@ -154,17 +154,17 @@ class UnusedVariableCheck : BaseAnalyzer
|
|||
override void visit(const AssignExpression assignExp)
|
||||
{
|
||||
assignExp.ternaryExpression.accept(this);
|
||||
if (assignExp.assignExpression !is null)
|
||||
if (assignExp.expression !is null)
|
||||
{
|
||||
interestDepth++;
|
||||
assignExp.assignExpression.accept(this);
|
||||
assignExp.expression.accept(this);
|
||||
interestDepth--;
|
||||
}
|
||||
}
|
||||
|
||||
override void visit(const TemplateDeclaration templateDeclaration)
|
||||
{
|
||||
auto inAgg = inAggregateScope;
|
||||
immutable inAgg = inAggregateScope;
|
||||
inAggregateScope = true;
|
||||
templateDeclaration.accept(this);
|
||||
inAggregateScope = inAgg;
|
||||
|
@ -404,4 +404,3 @@ private:
|
|||
|
||||
Regex!char re;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,14 +8,15 @@ import std.d.ast;
|
|||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.helpers;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
class UnusedLabelCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName)
|
||||
this(string fileName, const(Scope)* sc)
|
||||
{
|
||||
super(fileName);
|
||||
super(fileName, sc);
|
||||
}
|
||||
|
||||
override void visit(const Module mod)
|
||||
|
|
|
@ -113,13 +113,13 @@ class XMLPrinter : ASTVisitor
|
|||
|
||||
override void visit(const AssignExpression assignExpression)
|
||||
{
|
||||
if (assignExpression.assignExpression is null)
|
||||
output.writeln("<assignExpression>");
|
||||
if (assignExpression.expression is null)
|
||||
output.writeln("<expression>");
|
||||
else
|
||||
output.writeln("<assignExpression operator=\"",
|
||||
output.writeln("<expression operator=\"",
|
||||
xmlAttributeEscape(str(assignExpression.operator)), "\">");
|
||||
assignExpression.accept(this);
|
||||
output.writeln("</assignExpression>");
|
||||
output.writeln("</expression>");
|
||||
}
|
||||
|
||||
override void visit(const AtAttribute atAttribute)
|
||||
|
@ -911,15 +911,18 @@ class XMLPrinter : ASTVisitor
|
|||
"</prefix>");
|
||||
unaryExpression.unaryExpression.accept(this);
|
||||
}
|
||||
if (unaryExpression.suffix != tok!"")
|
||||
{
|
||||
assert(unaryExpression.suffix.text == "");
|
||||
unaryExpression.unaryExpression.accept(this);
|
||||
output.writeln("<suffix>", str(unaryExpression.suffix.type),
|
||||
"</suffix>");
|
||||
}
|
||||
else
|
||||
unaryExpression.accept(this);
|
||||
{
|
||||
if (unaryExpression.suffix != tok!"")
|
||||
{
|
||||
assert(unaryExpression.suffix.text == "");
|
||||
unaryExpression.unaryExpression.accept(this);
|
||||
output.writeln("<suffix>", str(unaryExpression.suffix.type),
|
||||
"</suffix>");
|
||||
}
|
||||
else
|
||||
unaryExpression.accept(this);
|
||||
}
|
||||
output.writeln("</unaryExpression>");
|
||||
}
|
||||
|
||||
|
|
22
src/main.d
22
src/main.d
|
@ -31,6 +31,8 @@ import dscanner_version;
|
|||
|
||||
import inifiled;
|
||||
|
||||
import dsymbol.modulecache;
|
||||
|
||||
version (unittest)
|
||||
void main() {}
|
||||
else
|
||||
|
@ -54,6 +56,7 @@ int main(string[] args)
|
|||
bool report;
|
||||
string symbolName;
|
||||
string configLocation;
|
||||
string[] importPaths;
|
||||
bool printVersion;
|
||||
bool explore;
|
||||
|
||||
|
@ -66,7 +69,7 @@ int main(string[] args)
|
|||
"ast|xml", &ast, "imports|i", &imports, "outline|o", &outline,
|
||||
"tokenDump", &tokenDump, "styleCheck|S", &styleCheck,
|
||||
"defaultConfig", &defaultConfig, "declaration|d", &symbolName,
|
||||
"config", &configLocation, "report", &report,
|
||||
"config", &configLocation, "report", &report, "I", &importPaths,
|
||||
"version", &printVersion, "muffinButton", &muffin, "explore", &explore);
|
||||
}
|
||||
catch (ConvException e)
|
||||
|
@ -112,7 +115,15 @@ int main(string[] args)
|
|||
return 0;
|
||||
}
|
||||
|
||||
auto optionCount = count!"a"([sloc, highlight, ctags, tokenCount,
|
||||
const(string[]) absImportPaths = importPaths.map!(
|
||||
a => a.absolutePath().buildNormalizedPath()).array();
|
||||
|
||||
auto moduleCache = ModuleCache(new dsymbol.modulecache.ASTAllocator);
|
||||
|
||||
if (absImportPaths.length)
|
||||
moduleCache.addImportPaths(absImportPaths);
|
||||
|
||||
immutable optionCount = count!"a"([sloc, highlight, ctags, tokenCount,
|
||||
syntaxCheck, ast, imports, outline, tokenDump, styleCheck, defaultConfig,
|
||||
report, symbolName !is null, etags, etagsAll]);
|
||||
if (optionCount > 1)
|
||||
|
@ -180,13 +191,13 @@ int main(string[] args)
|
|||
if (s.exists())
|
||||
readINIFile(config, s);
|
||||
if (report)
|
||||
generateReport(expandArgs(args), config);
|
||||
generateReport(expandArgs(args), config, cache, moduleCache);
|
||||
else
|
||||
return analyze(expandArgs(args), config, true) ? 1 : 0;
|
||||
return analyze(expandArgs(args), config, cache, moduleCache, true) ? 1 : 0;
|
||||
}
|
||||
else if (syntaxCheck)
|
||||
{
|
||||
return .syntaxCheck(expandArgs(args)) ? 1 : 0;
|
||||
return .syntaxCheck(expandArgs(args), cache, moduleCache) ? 1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -265,7 +276,6 @@ int main(string[] args)
|
|||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
string[] expandArgs(string[] args)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue