Merge upstream/master

This commit is contained in:
Sebastian Wilzbach 2017-09-14 01:55:08 +02:00
commit 35bd4f22af
28 changed files with 629 additions and 309 deletions

View File

@ -12,6 +12,11 @@ makefile has "ldc" and "gdc" targets if you'd prefer to compile with one of thes
compilers instead of DMD. To install, simply place the generated binary (in the compilers instead of DMD. To install, simply place the generated binary (in the
"bin" folder) somewhere on your $PATH. "bin" folder) somewhere on your $PATH.
### Testing
Testing does not work with DUB.
Under linux or OSX run the tests with `make test`.
Under Windows run the tests with `build.bat test`.
### Installing with DUB ### Installing with DUB
```sh ```sh
@ -313,7 +318,7 @@ It is possible to create a new section `analysis.config.ModuleFilters` in the `.
In this optional section a comma-separated list of inclusion and exclusion selectors can In this optional section a comma-separated list of inclusion and exclusion selectors can
be specified for every check on which selective filtering should be applied. be specified for every check on which selective filtering should be applied.
These given selectors match on the module name and partial matches (`std.` or `.foo.`) are possible. These given selectors match on the module name and partial matches (`std.` or `.foo.`) are possible.
Morover, every selectors must begin with either `+` (inclusion) or `-` (exclusion). Moreover, every selectors must begin with either `+` (inclusion) or `-` (exclusion).
Exclusion selectors take precedence over all inclusion operators. Exclusion selectors take precedence over all inclusion operators.
Of course, for every check a different selector set can given: Of course, for every check a different selector set can given:
@ -321,8 +326,6 @@ Of course, for every check a different selector set can given:
[analysis.config.ModuleFilters] [analysis.config.ModuleFilters]
final_attribute_check = "+std.foo,+std.bar" final_attribute_check = "+std.foo,+std.bar"
useless_initializer = "-std." useless_initializer = "-std."
;pseudo variable (workaround against an inifiled bug)
foo=""
``` ```
A few examples: A few examples:
@ -333,3 +336,5 @@ A few examples:
- `+.bar`: Includes all modules matching `.bar` (e.g. `foo.bar`, `a.b.c.barros`) - `+.bar`: Includes all modules matching `.bar` (e.g. `foo.bar`, `a.b.c.barros`)
- `-etc.`: Excludes all modules from `.etc` - `-etc.`: Excludes all modules from `.etc`
- `+std,-std.internal`: Includes entire `std`, except for the internal modules - `+std,-std.internal`: Includes entire `std`, except for the internal modules
- `-etc.`: Excludes all modules from `.etc`
- `+std,-std.internal`: Includes entire `std`, except for the internal modules

View File

@ -1,7 +1,8 @@
@echo off @echo off
setlocal enabledelayedexpansion setlocal enabledelayedexpansion
set DFLAGS=-O -release -inline set DFLAGS=-O -release -inline -version=StdLoggerDisableWarning
set TESTFLAGS=-g -w -version=StdLoggerDisableWarning
set CORE= set CORE=
set LIBDPARSE= set LIBDPARSE=
set STD= set STD=
@ -13,8 +14,6 @@ set LIBDDOC=
for %%x in (src\*.d) do set CORE=!CORE! %%x for %%x in (src\*.d) do set CORE=!CORE! %%x
for %%x in (src\analysis\*.d) do set ANALYSIS=!ANALYSIS! %%x for %%x in (src\analysis\*.d) do set ANALYSIS=!ANALYSIS! %%x
for %%x in (libdparse\experimental_allocator\src\std\experimental\allocator\*.d) do set STD=!STD! %%x
for %%x in (libdparse\experimental_allocator\src\std\experimental\allocator\building_blocks\*.d) do set STD=!STD! %%x
for %%x in (libdparse\src\dparse\*.d) do set LIBDPARSE=!LIBDPARSE! %%x for %%x in (libdparse\src\dparse\*.d) do set LIBDPARSE=!LIBDPARSE! %%x
for %%x in (libdparse\src\std\experimental\*.d) do set LIBDPARSE=!LIBDPARSE! %%x for %%x in (libdparse\src\std\experimental\*.d) do set LIBDPARSE=!LIBDPARSE! %%x
for %%x in (libddoc\src\ddoc\*.d) do set LIBDDOC=!LIBDDOC! %%x for %%x in (libddoc\src\ddoc\*.d) do set LIBDDOC=!LIBDDOC! %%x
@ -25,6 +24,21 @@ for %%x in (dsymbol\src\dsymbol\conversion\*.d) do set DSYMBOL=!DSYMBOL! %%x
for %%x in (containers\src\containers\*.d) do set CONTAINERS=!CONTAINERS! %%x for %%x in (containers\src\containers\*.d) do set CONTAINERS=!CONTAINERS! %%x
for %%x in (containers\src\containers\internal\*.d) do set CONTAINERS=!CONTAINERS! %%x for %%x in (containers\src\containers\internal\*.d) do set CONTAINERS=!CONTAINERS! %%x
@echo on if "%1" == "test" goto test_cmd
dmd %CORE% %STD% %LIBDPARSE% %LIBDDOC% %ANALYSIS% %INIFILED% %DSYMBOL% %CONTAINERS% %DFLAGS% -I"libdparse\src" -I"dsymbol\src" -I"containers\src" -I"libddoc\src" -ofdscanner.exe
@echo on
dmd %CORE% %STD% %LIBDPARSE% %LIBDDOC% %ANALYSIS% %INIFILED% %DSYMBOL% %CONTAINERS% %DFLAGS% -I"libdparse\src" -I"dsymbol\src" -I"containers\src" -I"libddoc\src" -ofbin\dscanner.exe
goto eof
:test_cmd
@echo on
set TESTNAME="bin\dscanner-unittest"
dmd %STD% %LIBDPARSE% %LIBDDOC% %INIFILED% %DSYMBOL% %CONTAINERS% -I"libdparse\src" -I"dsymbol\src" -I"containers\src" -I"libddoc\src" -lib %TESTFLAGS% -of%TESTNAME%.lib
if exist %TESTNAME%.lib dmd %CORE% %ANALYSIS% %TESTNAME%.lib -I"src" -I"inifiled\source" -I"libdparse\src" -I"dsymbol\src" -I"containers\src" -I"libddoc\src" -unittest %TESTFLAGS% -of%TESTNAME%.exe
if exist %TESTNAME%.exe %TESTNAME%.exe
if exist %TESTNAME%.obj del %TESTNAME%.obj
if exist %TESTNAME%.lib del %TESTNAME%.lib
if exist %TESTNAME%.exe del %TESTNAME%.exe
:eof

@ -1 +1 @@
Subproject commit d22c9714a60ac05cb32db938e81a396cffb5ffa6 Subproject commit 6920a0489fbef44f105cdfb76d426a03ae14259a

View File

@ -2,7 +2,9 @@
"name" : "dscanner", "name" : "dscanner",
"description" : "Swiss-army knife for D source code", "description" : "Swiss-army knife for D source code",
"copyright" : "© Brian Schott", "copyright" : "© Brian Schott",
"authors": ["Brian Schott"], "authors" : [
"Brian Schott"
],
"license" : "Boost Software License - Version 1.0", "license" : "Boost Software License - Version 1.0",
"targetType" : "autodetect", "targetType" : "autodetect",
"versions" : [ "versions" : [
@ -11,9 +13,10 @@
], ],
"dependencies" : { "dependencies" : {
"libdparse" : "~>0.7.2-alpha.1", "libdparse" : "~>0.7.2-alpha.1",
"dsymbol": "~>0.2.0", "dsymbol" : "~>0.2.6",
"inifiled": ">=0.0.6", "inifiled" : ">=1.0.2",
"emsi_containers" : "~>0.5.3", "emsi_containers" : "~>0.5.3",
"libddoc" : "~>0.2.0" "libddoc" : "~>0.2.0"
}, },
"targetPath" : "bin"
} }

View File

@ -56,7 +56,7 @@ public:
protected: protected:
bool inAggregate = false; bool inAggregate;
bool skipTests; bool skipTests;
template visitTemplate(T) template visitTemplate(T)

View File

@ -34,12 +34,12 @@ class DuplicateAttributeCheck : BaseAnalyzer
void checkAttributes(const Declaration node) void checkAttributes(const Declaration node)
{ {
bool hasProperty = false; bool hasProperty;
bool hasSafe = false; bool hasSafe;
bool hasTrusted = false; bool hasTrusted;
bool hasSystem = false; bool hasSystem;
bool hasPure = false; bool hasPure;
bool hasNoThrow = false; bool hasNoThrow;
// Check the attributes // Check the attributes
foreach (attribute; node.attributes) foreach (attribute; node.attributes)

View File

@ -24,7 +24,7 @@ class EnumArrayLiteralCheck : BaseAnalyzer
super(fileName, sc, skipTests); super(fileName, sc, skipTests);
} }
bool looking = false; bool looking;
mixin visitTemplate!ClassDeclaration; mixin visitTemplate!ClassDeclaration;
mixin visitTemplate!InterfaceDeclaration; mixin visitTemplate!InterfaceDeclaration;

View File

@ -30,6 +30,7 @@ private:
static immutable class_t = "templated functions declared within a class are never virtual"; static immutable class_t = "templated functions declared within a class are never virtual";
static immutable class_p = "private functions declared within a class are never virtual"; static immutable class_p = "private functions declared within a class are never virtual";
static immutable class_f = "functions declared within a final class are never virtual"; static immutable class_f = "functions declared within a final class are never virtual";
static immutable class_s = "static functions are never virtual";
static immutable interface_t = "templated functions declared within an interface are never virtual"; static immutable interface_t = "templated functions declared within an interface are never virtual";
static immutable struct_f = "functions declared within a struct are never virtual"; static immutable struct_f = "functions declared within a struct are never virtual";
static immutable union_f = "functions declared within an union are never virtual"; static immutable union_f = "functions declared within an union are never virtual";
@ -49,6 +50,8 @@ private:
bool[] _private; bool[] _private;
bool _finalAggregate; bool _finalAggregate;
bool _alwaysStatic;
bool _blockStatic;
Parent _parent = Parent.module_; Parent _parent = Parent.module_;
void addError(T)(T t, string msg) void addError(T)(T t, string msg)
@ -75,6 +78,7 @@ public:
const Parent saved = _parent; const Parent saved = _parent;
_parent = Parent.struct_; _parent = Parent.struct_;
_private.length += 1; _private.length += 1;
_alwaysStatic = false;
sd.accept(this); sd.accept(this);
_private.length -= 1; _private.length -= 1;
_parent = saved; _parent = saved;
@ -85,6 +89,7 @@ public:
const Parent saved = _parent; const Parent saved = _parent;
_parent = Parent.interface_; _parent = Parent.interface_;
_private.length += 1; _private.length += 1;
_alwaysStatic = false;
id.accept(this); id.accept(this);
_private.length -= 1; _private.length -= 1;
_parent = saved; _parent = saved;
@ -95,6 +100,7 @@ public:
const Parent saved = _parent; const Parent saved = _parent;
_parent = Parent.union_; _parent = Parent.union_;
_private.length += 1; _private.length += 1;
_alwaysStatic = false;
ud.accept(this); ud.accept(this);
_private.length -= 1; _private.length -= 1;
_parent = saved; _parent = saved;
@ -105,6 +111,7 @@ public:
const Parent saved = _parent; const Parent saved = _parent;
_parent = Parent.class_; _parent = Parent.class_;
_private.length += 1; _private.length += 1;
_alwaysStatic = false;
cd.accept(this); cd.accept(this);
_private.length -= 1; _private.length -= 1;
_parent = saved; _parent = saved;
@ -120,14 +127,34 @@ public:
// regular template are also mixable // regular template are also mixable
} }
override void visit(const(AttributeDeclaration) decl)
{
if (_parent == Parent.class_ && decl.attribute &&
decl.attribute.attribute == tok!"static")
_alwaysStatic = true;
}
override void visit(const(Declaration) d) override void visit(const(Declaration) d)
{ {
import std.algorithm.iteration : filter;
import std.algorithm.searching : canFind;
const Parent savedParent = _parent; const Parent savedParent = _parent;
bool undoBlockStatic;
if (_parent == Parent.class_ && d.attributes &&
d.attributes.canFind!(a => a.attribute == tok!"static"))
{
_blockStatic = true;
undoBlockStatic = true;
}
scope(exit) scope(exit)
{ {
d.accept(this); d.accept(this);
_parent = savedParent; _parent = savedParent;
if (undoBlockStatic)
_blockStatic = false;
} }
if (!d.attributeDeclaration && if (!d.attributeDeclaration &&
@ -138,30 +165,27 @@ public:
!d.functionDeclaration) !d.functionDeclaration)
return; return;
import std.algorithm.iteration : filter;
import std.algorithm.searching : find;
import std.range.primitives : empty;
if (d.attributeDeclaration && d.attributeDeclaration.attribute) if (d.attributeDeclaration && d.attributeDeclaration.attribute)
{ {
const tp = d.attributeDeclaration.attribute.attribute.type; const tp = d.attributeDeclaration.attribute.attribute.type;
_private[$-1] = isProtection(tp) & (tp == tok!"private"); _private[$-1] = isProtection(tp) & (tp == tok!"private");
} }
const bool isFinal = !d.attributes const bool isFinal = d.attributes
.find!(a => a.attribute.type == tok!"final") .canFind!(a => a.attribute.type == tok!"final");
.empty;
const bool isStaticOnce = d.attributes
.canFind!(a => a.attribute.type == tok!"static");
// determine if private // determine if private
const bool changeProtectionOnce = !d.attributes const bool changeProtectionOnce = d.attributes
.filter!(a => a.attribute.type.isProtection) .canFind!(a => a.attribute.type.isProtection);
.empty;
const bool isPrivateOnce = !d.attributes const bool isPrivateOnce = d.attributes
.find!(a => a.attribute.type == tok!"private") .canFind!(a => a.attribute.type == tok!"private");
.empty;
bool isPrivate; bool isPrivate;
if (isPrivateOnce) if (isPrivateOnce)
isPrivate = true; isPrivate = true;
else if (_private[$-1] && !changeProtectionOnce) else if (_private[$-1] && !changeProtectionOnce)
@ -194,6 +218,8 @@ public:
addError(fd, MESSAGE.class_t); addError(fd, MESSAGE.class_t);
if (isPrivate) if (isPrivate)
addError(fd, MESSAGE.class_p); addError(fd, MESSAGE.class_p);
else if (isStaticOnce || _alwaysStatic || _blockStatic)
addError(fd, MESSAGE.class_s);
else if (_finalAggregate) else if (_finalAggregate)
addError(fd, MESSAGE.class_f); addError(fd, MESSAGE.class_f);
break; break;
@ -349,5 +375,32 @@ public:
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p) FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
), sac); ), sac);
assertAnalyzerWarnings(q{
class Foo {final static void foo(){}} // [warn]: %s
}c.format(
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
), sac);
assertAnalyzerWarnings(q{
class Foo
{
void foo(){}
static: final void foo(){} // [warn]: %s
}
}c.format(
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
), sac);
assertAnalyzerWarnings(q{
class Foo
{
void foo(){}
static{ final void foo(){}} // [warn]: %s
void foo(){}
}
}c.format(
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
), sac);
stderr.writeln("Unittest for FinalAttributeChecker passed."); stderr.writeln("Unittest for FinalAttributeChecker passed.");
} }

View File

@ -59,8 +59,8 @@ class FunctionAttributeCheck : BaseAnalyzer
{ {
if (dec.parameters.parameters.length == 0) if (dec.parameters.parameters.length == 0)
{ {
bool foundConst = false; bool foundConst;
bool foundProperty = false; bool foundProperty;
foreach (attribute; dec.attributes) foreach (attribute; dec.attributes)
foundConst = foundConst || attribute.attribute.type == tok!"const" foundConst = foundConst || attribute.attribute.type == tok!"const"
|| attribute.attribute.type == tok!"immutable" || attribute.attribute.type == tok!"immutable"

View File

@ -50,6 +50,7 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config,
{ {
import analysis.run : parseModule; import analysis.run : parseModule;
import dparse.lexer : StringCache, Token; import dparse.lexer : StringCache, Token;
import std.ascii : newline;
StringCache cache = StringCache(StringCache.defaultBucketCount); StringCache cache = StringCache(StringCache.defaultBucketCount);
RollbackAllocator r; RollbackAllocator r;
@ -60,7 +61,7 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config,
// Run the code and get any warnings // Run the code and get any warnings
MessageSet rawWarnings = analyze("test", m, config, moduleCache, tokens); MessageSet rawWarnings = analyze("test", m, config, moduleCache, tokens);
string[] codeLines = code.split("\n"); string[] codeLines = code.split(newline);
// Get the warnings ordered by line // Get the warnings ordered by line
string[size_t] warnings; string[size_t] warnings;

View File

@ -28,6 +28,11 @@ class LabelVarNameCheck : BaseAnalyzer
mixin ScopedVisit!IfStatement; mixin ScopedVisit!IfStatement;
mixin ScopedVisit!TemplateDeclaration; mixin ScopedVisit!TemplateDeclaration;
mixin AggregateVisit!ClassDeclaration;
mixin AggregateVisit!StructDeclaration;
mixin AggregateVisit!InterfaceDeclaration;
mixin AggregateVisit!UnionDeclaration;
override void visit(const VariableDeclaration var) override void visit(const VariableDeclaration var)
{ {
foreach (dec; var.declarators) foreach (dec; var.declarators)
@ -63,6 +68,16 @@ private:
Thing[string][] stack; Thing[string][] stack;
template AggregateVisit(NodeType)
{
override void visit(const NodeType n)
{
pushAggregateName(n.name);
n.accept(this);
popAggregateName();
}
}
template ScopedVisit(NodeType) template ScopedVisit(NodeType)
{ {
override void visit(const NodeType n) override void visit(const NodeType n)
@ -81,15 +96,16 @@ private:
size_t i; size_t i;
foreach (s; retro(stack)) foreach (s; retro(stack))
{ {
const(Thing)* thing = name.text in s; string fqn = parentAggregateText ~ name.text;
const(Thing)* thing = fqn in s;
if (thing is null) if (thing is null)
currentScope[name.text] = Thing(name.text, name.line, name.column, !fromLabel /+, isConditional+/ ); currentScope[fqn] = Thing(fqn, name.line, name.column, !fromLabel /+, isConditional+/ );
else if (i != 0 || !isConditional) else if (i != 0 || !isConditional)
{ {
immutable thisKind = fromLabel ? "Label" : "Variable"; immutable thisKind = fromLabel ? "Label" : "Variable";
immutable otherKind = thing.isVar ? "variable" : "label"; immutable otherKind = thing.isVar ? "variable" : "label";
addErrorMessage(name.line, name.column, "dscanner.suspicious.label_var_same_name", addErrorMessage(name.line, name.column, "dscanner.suspicious.label_var_same_name",
thisKind ~ " \"" ~ name.text ~ "\" has the same name as a " thisKind ~ " \"" ~ fqn ~ "\" has the same name as a "
~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ "."); ~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ ".");
} }
++i; ++i;
@ -121,6 +137,32 @@ private:
} }
int conditionalDepth; int conditionalDepth;
void pushAggregateName(Token name)
{
parentAggregates ~= name;
updateAggregateText();
}
void popAggregateName()
{
parentAggregates.length -= 1;
updateAggregateText();
}
void updateAggregateText()
{
import std.algorithm : map;
import std.array : join;
if (parentAggregates.length)
parentAggregateText = parentAggregates.map!(a => a.text).join(".") ~ ".";
else
parentAggregateText = "";
}
Token[] parentAggregates;
string parentAggregateText;
} }
unittest unittest
@ -190,6 +232,45 @@ unittest
else version(BigEndian) { enum string NAME = "UTF-16BE"; } else version(BigEndian) { enum string NAME = "UTF-16BE"; }
} }
unittest
{
int a;
struct A {int a;}
}
unittest
{
int a;
struct A { struct A {int a;}}
}
unittest
{
int a;
class A { class A {int a;}}
}
unittest
{
int a;
interface A { interface A {int a;}}
}
unittest
{
interface A
{
int a;
int a; // [warn]: Variable "A.a" has the same name as a variable defined on line 89.
}
}
unittest
{
int aa;
struct a { int a; }
}
}c, sac); }c, sac);
stderr.writeln("Unittest for LabelVarNameCheck passed."); stderr.writeln("Unittest for LabelVarNameCheck passed.");
} }

View File

@ -44,6 +44,7 @@ private:
enum KEY = "dscanner.confusing.lambda_returns_lambda"; enum KEY = "dscanner.confusing.lambda_returns_lambda";
} }
version(Windows) {/*because of newline in code*/} else
unittest unittest
{ {
import analysis.helpers : assertAnalyzerWarnings; import analysis.helpers : assertAnalyzerWarnings;

View File

@ -125,7 +125,6 @@ private:
size_t length; size_t length;
const newLine = tok.line > prevLine; const newLine = tok.line > prevLine;
bool multiLine; bool multiLine;
if (tok.text is null) if (tok.text is null)
length += str(tok.type).length; length += str(tok.type).length;
else else

View File

@ -21,6 +21,7 @@ class ObjectConstCheck : BaseAnalyzer
{ {
alias visit = BaseAnalyzer.visit; alias visit = BaseAnalyzer.visit;
///
this(string fileName, const(Scope)* sc, bool skipTests = false) this(string fileName, const(Scope)* sc, bool skipTests = false)
{ {
super(fileName, sc, skipTests); super(fileName, sc, skipTests);
@ -31,24 +32,40 @@ class ObjectConstCheck : BaseAnalyzer
mixin visitTemplate!UnionDeclaration; mixin visitTemplate!UnionDeclaration;
mixin visitTemplate!StructDeclaration; mixin visitTemplate!StructDeclaration;
override void visit(const AttributeDeclaration d)
{
if (d.attribute.attribute == tok!"const" && inAggregate)
{
constColon = true;
}
d.accept(this);
}
override void visit(const Declaration d) override void visit(const Declaration d)
{ {
if (inAggregate && d.functionDeclaration !is null import std.algorithm : any;
&& isInteresting(d.functionDeclaration.name.text) && (!hasConst(d.attributes) bool setConstBlock;
&& !hasConst(d.functionDeclaration.memberFunctionAttributes))) if (inAggregate && d.attributes && d.attributes.any!(a => a.attribute == tok!"const"))
{
setConstBlock = true;
constBlock = true;
}
if (inAggregate && d.functionDeclaration !is null && !constColon && !constBlock
&& isInteresting(d.functionDeclaration.name.text)
&& !hasConst(d.functionDeclaration.memberFunctionAttributes))
{ {
addErrorMessage(d.functionDeclaration.name.line, addErrorMessage(d.functionDeclaration.name.line,
d.functionDeclaration.name.column, "dscanner.suspicious.object_const", d.functionDeclaration.name.column, "dscanner.suspicious.object_const",
"Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const."); "Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.");
} }
d.accept(this); d.accept(this);
}
private static bool hasConst(const Attribute[] attributes) if (!inAggregate)
{ constColon = false;
import std.algorithm : any; if (setConstBlock)
constBlock = false;
return attributes.any!(a => a.attribute == tok!"const");
} }
private static bool hasConst(const MemberFunctionAttribute[] attributes) private static bool hasConst(const MemberFunctionAttribute[] attributes)
@ -65,7 +82,8 @@ class ObjectConstCheck : BaseAnalyzer
|| name == "toString" || name == "opCast"; || name == "toString" || name == "opCast";
} }
private bool looking = false; private bool constBlock;
private bool constColon;
} }
@ -102,6 +120,16 @@ unittest
} }
} }
class Bat
{
const: override string toString() { return "foo"; } // ok
}
class Fox
{
const{ override string toString() { return "foo"; }} // ok
}
// Will warn, because none are const // Will warn, because none are const
class Dog class Dog
{ {

View File

@ -39,9 +39,9 @@ class OpEqualsWithoutToHashCheck : BaseAnalyzer
private void actualCheck(const Token name, const StructBody structBody) private void actualCheck(const Token name, const StructBody structBody)
{ {
bool hasOpEquals = false; bool hasOpEquals;
bool hasToHash = false; bool hasToHash;
bool hasOpCmp = false; bool hasOpCmp;
// Just return if missing children // Just return if missing children
if (!structBody || !structBody.declarations || name is Token.init) if (!structBody || !structBody.declarations || name is Token.init)

View File

@ -141,9 +141,10 @@ private:
long parseNumber(string te) long parseNumber(string te)
{ {
import std.conv : to; import std.conv : to;
import std.string : removechars; import std.regex : ctRegex, replaceAll;
string t = te.removechars("_uUlL"); enum re = ctRegex!("[_uUlL]", "");
string t = te.replaceAll(re, "");
if (t.length > 2) if (t.length > 2)
{ {
if (t[1] == 'x' || t[1] == 'X') if (t[1] == 'x' || t[1] == 'X')

View File

@ -11,6 +11,7 @@ import std.conv;
import std.algorithm; import std.algorithm;
import std.range; import std.range;
import std.array; import std.array;
import std.functional : toDelegate;
import dparse.lexer; import dparse.lexer;
import dparse.parser; import dparse.parser;
import dparse.ast; import dparse.ast;
@ -162,7 +163,7 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config,
bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool analyze(string[] fileNames, const StaticAnalysisConfig config,
ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true) ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
{ {
bool hasErrors = false; bool hasErrors;
foreach (fileName; fileNames) foreach (fileName; fileNames)
{ {
auto code = readFile(fileName); auto code = readFile(fileName);
@ -203,8 +204,9 @@ const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p,
tokens = getTokensForParser(code, config, &cache); tokens = getTokensForParser(code, config, &cache);
if (linesOfCode !is null) if (linesOfCode !is null)
(*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens); (*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens);
return dparse.parser.parseModule(tokens, fileName, p, report return dparse.parser.parseModule(tokens, fileName, p,
? &messageFunctionJSON : &messageFunction, errorCount, warningCount); report ? toDelegate(&messageFunctionJSON) : toDelegate(&messageFunction),
errorCount, warningCount);
} }
/** /**

View File

@ -70,17 +70,11 @@ final class StyleChecker : BaseAnalyzer
override void visit(const VariableDeclaration vd) override void visit(const VariableDeclaration vd)
{ {
import std.algorithm.iteration : filter;
varIsEnum = !vd.storageClasses.filter!(a => a.token == tok!"enum").empty;
vd.accept(this); vd.accept(this);
} }
override void visit(const Declarator dec) override void visit(const Declarator dec)
{ {
if (varIsEnum)
checkAggregateName("Variable", dec.name);
else
checkLowercaseName("Variable", dec.name); checkLowercaseName("Variable", dec.name);
} }
@ -143,8 +137,6 @@ final class StyleChecker : BaseAnalyzer
aggregateType ~ " name '" ~ name.text ~ "' does not match style guidelines."); aggregateType ~ " name '" ~ name.text ~ "' does not match style guidelines.");
} }
bool varIsEnum;
bool[] _winStyles = [false]; bool[] _winStyles = [false];
bool winStyle() bool winStyle()
@ -183,7 +175,10 @@ unittest
interface puma {} // [warn]: Interface name 'puma' does not match style guidelines. interface puma {} // [warn]: Interface name 'puma' does not match style guidelines.
struct dog {} // [warn]: Struct name 'dog' does not match style guidelines. struct dog {} // [warn]: Struct name 'dog' does not match style guidelines.
enum racoon { a } // [warn]: Enum name 'racoon' does not match style guidelines. enum racoon { a } // [warn]: Enum name 'racoon' does not match style guidelines.
enum bool Something = false; enum bool something = false;
enum bool someThing = false;
enum Cat { fritz, }
enum Cat = Cat.fritz;
}c, sac); }c, sac);
assertAnalyzerWarnings(q{ assertAnalyzerWarnings(q{

View File

@ -51,10 +51,10 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
immutable bool prevOverride = getOverride(); immutable bool prevOverride = getOverride();
immutable bool prevDisabled = getDisabled(); immutable bool prevDisabled = getDisabled();
immutable bool prevDeprecated = getDeprecated(); immutable bool prevDeprecated = getDeprecated();
bool dis = false; bool dis;
bool dep = false; bool dep;
bool ovr = false; bool ovr;
bool pushed = false; bool pushed;
foreach (attribute; dec.attributes) foreach (attribute; dec.attributes)
{ {
if (isProtection(attribute.attribute.type)) if (isProtection(attribute.attribute.type))

View File

@ -160,6 +160,7 @@ class UnmodifiedFinder : BaseAnalyzer
foreachStatement.low.accept(this); foreachStatement.low.accept(this);
interest--; interest--;
} }
if (foreachStatement.declarationOrStatement !is null)
foreachStatement.declarationOrStatement.accept(this); foreachStatement.declarationOrStatement.accept(this);
} }
@ -223,7 +224,7 @@ private:
castExpression.accept(this); castExpression.accept(this);
} }
bool foundCast = false; bool foundCast;
} }
if (initializer is null) if (initializer is null)

View File

@ -11,6 +11,7 @@ import std.container;
import std.regex : Regex, regex, matchAll; import std.regex : Regex, regex, matchAll;
import dsymbol.scope_ : Scope; import dsymbol.scope_ : Scope;
import std.algorithm.iteration : map; import std.algorithm.iteration : map;
import std.algorithm : all;
/** /**
* Checks for unused variables. * Checks for unused variables.
@ -89,18 +90,26 @@ class UnusedVariableCheck : BaseAnalyzer
} }
override void visit(const WhileStatement whileStatement) override void visit(const WhileStatement whileStatement)
{
if (whileStatement.expression !is null)
{ {
interestDepth++; interestDepth++;
whileStatement.expression.accept(this); whileStatement.expression.accept(this);
interestDepth--; interestDepth--;
}
if (whileStatement.declarationOrStatement !is null)
whileStatement.declarationOrStatement.accept(this); whileStatement.declarationOrStatement.accept(this);
} }
override void visit(const DoStatement doStatement) override void visit(const DoStatement doStatement)
{
if (doStatement.expression !is null)
{ {
interestDepth++; interestDepth++;
doStatement.expression.accept(this); doStatement.expression.accept(this);
interestDepth--; interestDepth--;
}
if (doStatement.statementNoCaseNoDefault !is null)
doStatement.statementNoCaseNoDefault.accept(this); doStatement.statementNoCaseNoDefault.accept(this);
} }
@ -120,6 +129,7 @@ class UnusedVariableCheck : BaseAnalyzer
forStatement.increment.accept(this); forStatement.increment.accept(this);
interestDepth--; interestDepth--;
} }
if (forStatement.declarationOrStatement !is null)
forStatement.declarationOrStatement.accept(this); forStatement.declarationOrStatement.accept(this);
} }
@ -131,6 +141,7 @@ class UnusedVariableCheck : BaseAnalyzer
ifStatement.expression.accept(this); ifStatement.expression.accept(this);
interestDepth--; interestDepth--;
} }
if (ifStatement.thenStatement !is null)
ifStatement.thenStatement.accept(this); ifStatement.thenStatement.accept(this);
if (ifStatement.elseStatement !is null) if (ifStatement.elseStatement !is null)
ifStatement.elseStatement.accept(this); ifStatement.elseStatement.accept(this);
@ -155,6 +166,7 @@ class UnusedVariableCheck : BaseAnalyzer
override void visit(const AssignExpression assignExp) override void visit(const AssignExpression assignExp)
{ {
if (assignExp.ternaryExpression !is null)
assignExp.ternaryExpression.accept(this); assignExp.ternaryExpression.accept(this);
if (assignExp.expression !is null) if (assignExp.expression !is null)
{ {
@ -372,7 +384,7 @@ private:
void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef) void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef)
{ {
if (inAggregateScope) if (inAggregateScope || name.all!(a => a == '_'))
return; return;
tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef)); tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef));
} }
@ -506,6 +518,15 @@ private:
void f(Bat** bat) {*bat = bats.ptr + 8;} void f(Bat** bat) {*bat = bats.ptr + 8;}
} }
// Issue 490
void test490()
{
auto cb1 = delegate(size_t _) {};
cb1(3);
auto cb2 = delegate(size_t a) {}; // [warn]: Parameter a is never used.
cb2(3);
}
}c, sac); }c, sac);
stderr.writeln("Unittest for UnusedVariableCheck passed."); stderr.writeln("Unittest for UnusedVariableCheck passed.");
} }

View File

@ -5,13 +5,19 @@
module analysis.useless_initializer; module analysis.useless_initializer;
import analysis.base; import analysis.base;
import containers.dynamicarray;
import containers.hashmap;
import dparse.ast; import dparse.ast;
import dparse.lexer; import dparse.lexer;
import std.algorithm;
import std.range : empty;
import std.stdio; import std.stdio;
/* /*
Limitations: Limitations:
- Stuff = Stuff.init doesnot work with type with * [] - Stuff s = Stuff.init does not work with type with postfixes`*` `[]`.
- Stuff s = Stuff.init is only detected for struct within the module.
- BasicType b = BasicType(v), default init used in BasicType ctor, e.g int(8).
*/ */
/** /**
@ -25,49 +31,142 @@ final class UselessInitializerChecker : BaseAnalyzer
private: private:
enum key = "dscanner.useless-initializer"; enum key = "dscanner.useless-initializer";
version(unittest)
enum msg = "X";
else
enum msg = `Variable %s initializer is useless because it does not differ from the default value`;
static immutable strDefs = [`""`, `""c`, `""w`, `""d`, "``", "``c", "``w", "``d", "q{}"]; version(unittest)
{
enum msg = "X";
}
else
{
enum msg = `Variable %s initializer is useless because it does not differ from the default value`;
}
static immutable intDefs = ["0", "0L", "0UL", "0uL", "0U", "0x0", "0b0"]; static immutable intDefs = ["0", "0L", "0UL", "0uL", "0U", "0x0", "0b0"];
HashMap!(string, bool) _structCanBeInit;
DynamicArray!(string) _structStack;
DynamicArray!(bool) _inStruct;
DynamicArray!(bool) _atDisabled;
bool _inTest;
public: public:
/// ///
this(string fileName, bool skipTests = false) this(string fileName, bool skipTests = false)
{ {
super(fileName, null, skipTests); super(fileName, null, skipTests);
_inStruct.insert(false);
}
override void visit(const(Unittest) test)
{
if (skipTests)
return;
_inTest = true;
test.accept(this);
_inTest = false;
}
override void visit(const(StructDeclaration) decl)
{
if (_inTest)
return;
assert(_inStruct.length > 1);
const string structName = _inStruct[$-2] ?
_structStack.back() ~ "." ~ decl.name.text :
decl.name.text;
_structStack.insert(structName);
_structCanBeInit[structName] = false;
_atDisabled.insert(false);
decl.accept(this);
_structStack.removeBack();
_atDisabled.removeBack();
}
override void visit(const(Declaration) decl)
{
_inStruct.insert(decl.structDeclaration !is null);
decl.accept(this);
if (_inStruct.length > 1 && _inStruct[$-2] && decl.constructor &&
((decl.constructor.parameters && decl.constructor.parameters.parameters.length == 0) ||
!decl.constructor.parameters))
{
_atDisabled[$-1] = decl.attributes
.canFind!(a => a.atAttribute !is null && a.atAttribute.identifier.text == "disable");
}
_inStruct.removeBack();
}
override void visit(const(Constructor) decl)
{
if (_inStruct.length > 1 && _inStruct[$-2] &&
((decl.parameters && decl.parameters.parameters.length == 0) || !decl.parameters))
{
const bool canBeInit = !_atDisabled[$-1];
_structCanBeInit[_structStack.back()] = canBeInit;
if (!canBeInit)
_structCanBeInit[_structStack.back()] = !decl.memberFunctionAttributes
.canFind!(a => a.atAttribute !is null && a.atAttribute.identifier.text == "disable");
}
decl.accept(this);
}
// issue 473, prevent to visit delegates that contain duck type checkers.
override void visit(const(TypeofExpression)) {}
// issue 473, prevent to check expressions in __traits(compiles, ...)
override void visit(const(TraitsExpression) e)
{
if (e.identifier.text == "compiles")
{
return;
}
else
{
e.accept(this);
}
} }
override void visit(const(VariableDeclaration) decl) override void visit(const(VariableDeclaration) decl)
{ {
if (!decl.type || !decl.type.type2 ||
// initializer has to appear clearly in generated ddoc
decl.comment !is null ||
// issue 474, manifest constants HAVE to be initialized.
decl.storageClasses.canFind!(a => a.token == tok!"enum"))
{
return;
}
foreach (declarator; decl.declarators) foreach (declarator; decl.declarators)
{ {
if (!decl.type || !decl.type.type2) if (!declarator.initializer ||
!declarator.initializer.nonVoidInitializer ||
declarator.comment !is null)
{
continue; continue;
if (!declarator.initializer || !declarator.initializer.nonVoidInitializer) }
continue;
import std.format : format;
version(unittest) version(unittest)
enum warn = q{addErrorMessage(declarator.name.line, declarator.name.column, {
key, msg);}; enum warn = q{addErrorMessage(declarator.name.line,
declarator.name.column, key, msg);};
}
else else
enum warn = q{addErrorMessage(declarator.name.line, declarator.name.column, {
key, msg.format(declarator.name.text));}; import std.format : format;
enum warn = q{addErrorMessage(declarator.name.line,
declarator.name.column, key, msg.format(declarator.name.text));};
}
// --- Info about the declaration type --- // // --- Info about the declaration type --- //
import std.algorithm : among, canFind; const bool isPtr = decl.type.typeSuffixes && decl.type.typeSuffixes
import std.algorithm.iteration : filter; .canFind!(a => a.star != tok!"");
import std.range : empty; const bool isArr = decl.type.typeSuffixes && decl.type.typeSuffixes
.canFind!(a => a.array);
const bool isPtr = decl.type.typeSuffixes && !decl.type.typeSuffixes
.filter!(a => a.star != tok!"").empty;
const bool isArr = decl.type.typeSuffixes && !decl.type.typeSuffixes
.filter!(a => a.array).empty;
bool isStr, isSzInt; bool isStr, isSzInt;
Token customType; Token customType;
@ -100,7 +199,8 @@ public:
case tok!"int", tok!"uint": case tok!"int", tok!"uint":
case tok!"long", tok!"ulong": case tok!"long", tok!"ulong":
case tok!"cent", tok!"ucent": case tok!"cent", tok!"ucent":
if (intDefs.canFind(value.text)) case tok!"bool":
if (intDefs.canFind(value.text) || value == tok!"false")
mixin(warn); mixin(warn);
goto default; goto default;
default: default:
@ -116,7 +216,7 @@ public:
if (intDefs.canFind(value.text)) if (intDefs.canFind(value.text))
mixin(warn); mixin(warn);
} }
else if (isPtr) else if (isPtr || isStr)
{ {
if (str(value.type) == "null") if (str(value.type) == "null")
mixin(warn); mixin(warn);
@ -127,24 +227,6 @@ public:
mixin(warn); mixin(warn);
else if (nvi.arrayInitializer && nvi.arrayInitializer.arrayMemberInitializations.length == 0) else if (nvi.arrayInitializer && nvi.arrayInitializer.arrayMemberInitializations.length == 0)
mixin(warn); mixin(warn);
else if (decl.type.type2.builtinType != tok!"")
{
switch(decl.type.type2.builtinType)
{
case tok!"char", tok!"wchar", tok!"dchar":
if (strDefs.canFind(value.text))
mixin(warn);
break;
default:
}
}
}
else if (isStr)
{
if (strDefs.canFind(value.text))
mixin(warn);
else if (nvi.arrayInitializer && nvi.arrayInitializer.arrayMemberInitializations.length == 0)
mixin(warn);
} }
} }
@ -154,8 +236,12 @@ public:
ue.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier == customType && ue.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier == customType &&
ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init") ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init")
{ {
if (customType.text in _structCanBeInit)
{
if (!_structCanBeInit[customType.text])
mixin(warn); mixin(warn);
} }
}
// 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init // 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init
else if (nvi.arrayInitializer && (isArr || isStr)) else if (nvi.arrayInitializer && (isArr || isStr))
@ -180,6 +266,7 @@ public:
// fails // fails
assertAnalyzerWarnings(q{ assertAnalyzerWarnings(q{
struct S {}
ubyte a = 0x0; // [warn]: X ubyte a = 0x0; // [warn]: X
int a = 0; // [warn]: X int a = 0; // [warn]: X
ulong a = 0; // [warn]: X ulong a = 0; // [warn]: X
@ -187,22 +274,24 @@ public:
Foo* a = null; // [warn]: X Foo* a = null; // [warn]: X
int[] a = null; // [warn]: X int[] a = null; // [warn]: X
int[] a = []; // [warn]: X int[] a = []; // [warn]: X
string a = ""; // [warn]: X string a = null; // [warn]: X
string a = ""c; // [warn]: X string a = null; // [warn]: X
wstring a = ""w; // [warn]: X wstring a = null; // [warn]: X
dstring a = ""d; // [warn]: X dstring a = null; // [warn]: X
string a = q{}; // [warn]: X
size_t a = 0; // [warn]: X size_t a = 0; // [warn]: X
ptrdiff_t a = 0; // [warn]: X ptrdiff_t a = 0; // [warn]: X
string a = []; // [warn]: X string a = []; // [warn]: X
char[] a = ""; // [warn]: X char[] a = null; // [warn]: X
int a = int.init; // [warn]: X int a = int.init; // [warn]: X
char a = char.init; // [warn]: X char a = char.init; // [warn]: X
S s = S.init; // [warn]: X S s = S.init; // [warn]: X
bool a = false; // [warn]: X
}, sac); }, sac);
// passes // passes
assertAnalyzerWarnings(q{ assertAnalyzerWarnings(q{
struct D {@disable this();}
struct E {this() @disable;}
ubyte a = 0xFE; ubyte a = 0xFE;
int a = 1; int a = 1;
ulong a = 1; ulong a = 1;
@ -216,11 +305,33 @@ public:
dstring a = "fgh"d; dstring a = "fgh"d;
string a = q{int a;}; string a = q{int a;};
size_t a = 1; size_t a = 1;
ptrdiff_t a = 1; ptrdiff_t a;
ubyte a;
int a;
ulong a;
int* a;
Foo* a;
int[] a;
string a;
wstring a;
dstring a;
string a = ['a']; string a = ['a'];
string a = "";
string a = ""c;
wstring a = ""w;
dstring a = ""d;
string a = q{};
char[] a = "ze"; char[] a = "ze";
S s = S(0,1); S s = S(0,1);
S s = s.call(); S s = s.call();
enum {a}
enum ubyte a = 0;
static assert(is(typeof((){T t = T.init;})));
void foo(){__traits(compiles, (){int a = 0;}).writeln;}
bool a;
D d = D.init;
E e = E.init;
NotKnown nk = NotKnown.init;
}, sac); }, sac);
stderr.writeln("Unittest for UselessInitializerChecker passed."); stderr.writeln("Unittest for UselessInitializerChecker passed.");

View File

@ -17,6 +17,7 @@ import std.conv;
import std.typecons; import std.typecons;
import containers.ttree; import containers.ttree;
import std.string; import std.string;
import std.functional : toDelegate;
/** /**
* Prints CTAGS information to the given file. * Prints CTAGS information to the given file.
@ -65,7 +66,7 @@ void printCtags(File output, string[] fileNames)
} }
auto tokens = getTokensForParser(bytes, config, &cache); auto tokens = getTokensForParser(bytes, config, &cache);
Module m = parseModule(tokens.array, fileName, &rba, &doNothing); Module m = parseModule(tokens.array, fileName, &rba, toDelegate(&doNothing));
auto printer = new CTagsPrinter(&tags); auto printer = new CTagsPrinter(&tags);
printer.fileName = fileName; printer.fileName = fileName;

View File

@ -17,6 +17,7 @@ import std.path;
import std.array; import std.array;
import std.conv; import std.conv;
import std.string; import std.string;
import std.functional : toDelegate;
// Prefix tags with their module name. Seems like correct behavior, but just // Prefix tags with their module name. Seems like correct behavior, but just
// in case, make it an option. // in case, make it an option.
@ -48,7 +49,7 @@ void printEtags(File output, bool tagAll, string[] fileNames)
auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size)); auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size));
f.rawRead(bytes); f.rawRead(bytes);
auto tokens = getTokensForParser(bytes, config, &cache); auto tokens = getTokensForParser(bytes, config, &cache);
Module m = parseModule(tokens.array, fileName, &rba, &doNothing); Module m = parseModule(tokens.array, fileName, &rba, toDelegate(&doNothing));
auto printer = new EtagsPrinter; auto printer = new EtagsPrinter;
printer.moduleName = m.moduleFullName(fileName); printer.moduleName = m.moduleFullName(fileName);

View File

@ -11,6 +11,7 @@ import dparse.parser;
import dparse.rollback_allocator; import dparse.rollback_allocator;
import std.stdio; import std.stdio;
import std.container.rbtree; import std.container.rbtree;
import std.functional : toDelegate;
import readers; import readers;
/** /**
@ -63,7 +64,7 @@ private void visitFile(bool usingStdin, string fileName, RedBlackTree!string imp
config.stringBehavior = StringBehavior.source; config.stringBehavior = StringBehavior.source;
auto visitor = new ImportPrinter; auto visitor = new ImportPrinter;
auto tokens = getTokensForParser(usingStdin ? readStdin() : readFile(fileName), config, cache); auto tokens = getTokensForParser(usingStdin ? readStdin() : readFile(fileName), config, cache);
auto mod = parseModule(tokens, fileName, &rba, &doNothing); auto mod = parseModule(tokens, fileName, &rba, toDelegate(&doNothing));
visitor.visit(mod); visitor.visit(mod);
importedModules.insert(visitor.imports[]); importedModules.insert(visitor.imports[]);
} }

View File

@ -15,6 +15,7 @@ import std.stdio;
import std.range; import std.range;
import std.experimental.lexer; import std.experimental.lexer;
import std.typecons : scoped; import std.typecons : scoped;
import std.functional : toDelegate;
import dparse.lexer; import dparse.lexer;
import dparse.parser; import dparse.parser;
import dparse.rollback_allocator; import dparse.rollback_allocator;
@ -283,7 +284,7 @@ else
config.stringBehavior = StringBehavior.source; config.stringBehavior = StringBehavior.source;
auto tokens = getTokensForParser(usingStdin ? readStdin() auto tokens = getTokensForParser(usingStdin ? readStdin()
: readFile(args[1]), config, &cache); : readFile(args[1]), config, &cache);
auto mod = parseModule(tokens, fileName, &rba, &doNothing); auto mod = parseModule(tokens, fileName, &rba, toDelegate(&doNothing));
if (ast) if (ast)
{ {

View File

@ -12,8 +12,25 @@ import dparse.ast;
import dparse.rollback_allocator; import dparse.rollback_allocator;
import std.stdio; import std.stdio;
import std.file : isFile; import std.file : isFile;
import std.functional : toDelegate;
void findDeclarationOf(File output, string symbolName, string[] fileNames) void findDeclarationOf(File output, string symbolName, string[] fileNames)
{
findDeclarationOf((string fileName, size_t line, size_t column)
{
output.writefln("%s(%d:%d)", fileName, line, column);
}, symbolName, fileNames);
}
/// Delegate that gets called every time a declaration gets found
alias OutputHandler = void delegate(string fileName, size_t line, size_t column);
/// Finds all declarations of a symbol in the given fileNames and calls a handler on every occurence.
/// Params:
/// output = Callback which gets called when a declaration is found
/// symbolName = Symbol name to search for
/// fileNames = An array of file names which might contain stdin to read from stdin
void findDeclarationOf(scope OutputHandler output, string symbolName, string[] fileNames)
{ {
import std.array : uninitializedArray, array; import std.array : uninitializedArray, array;
import std.conv : to; import std.conv : to;
@ -31,7 +48,7 @@ void findDeclarationOf(File output, string symbolName, string[] fileNames)
f.rawRead(bytes); f.rawRead(bytes);
auto tokens = getTokensForParser(bytes, config, &cache); auto tokens = getTokensForParser(bytes, config, &cache);
RollbackAllocator rba; RollbackAllocator rba;
Module m = parseModule(tokens.array, fileName, &rba, &doNothing); Module m = parseModule(tokens.array, fileName, &rba, toDelegate(&doNothing));
visitor.fileName = fileName; visitor.fileName = fileName;
visitor.visit(m); visitor.visit(m);
} }
@ -45,7 +62,7 @@ void doNothing(string, size_t, size_t, string, bool)
class FinderVisitor : ASTVisitor class FinderVisitor : ASTVisitor
{ {
this(File output, string symbolName) this(OutputHandler output, string symbolName)
{ {
this.output = output; this.output = output;
this.symbolName = symbolName; this.symbolName = symbolName;
@ -61,19 +78,19 @@ class FinderVisitor : ASTVisitor
override void visit(const EnumDeclaration dec) override void visit(const EnumDeclaration dec)
{ {
if (dec.name.text == symbolName) if (dec.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, dec.name.line, dec.name.column); output(fileName, dec.name.line, dec.name.column);
} }
override void visit(const AnonymousEnumMember member) override void visit(const AnonymousEnumMember member)
{ {
if (member.name.text == symbolName) if (member.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, member.name.line, member.name.column); output(fileName, member.name.line, member.name.column);
} }
override void visit(const EnumMember member) override void visit(const EnumMember member)
{ {
if (member.name.text == symbolName) if (member.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, member.name.line, member.name.column); output(fileName, member.name.line, member.name.column);
} }
override void visit(const AliasDeclaration dec) override void visit(const AliasDeclaration dec)
@ -83,13 +100,13 @@ class FinderVisitor : ASTVisitor
foreach (ident; dec.identifierList.identifiers) foreach (ident; dec.identifierList.identifiers)
{ {
if (ident.text == symbolName) if (ident.text == symbolName)
output.writefln("%s(%d:%d)", fileName, ident.line, ident.column); output(fileName, ident.line, ident.column);
} }
} }
foreach (initializer; dec.initializers) foreach (initializer; dec.initializers)
{ {
if (initializer.name.text == symbolName) if (initializer.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, initializer.name.line, output(fileName, initializer.name.line,
initializer.name.column); initializer.name.column);
} }
} }
@ -97,7 +114,7 @@ class FinderVisitor : ASTVisitor
override void visit(const Declarator dec) override void visit(const Declarator dec)
{ {
if (dec.name.text == symbolName) if (dec.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, dec.name.line, dec.name.column); output(fileName, dec.name.line, dec.name.column);
} }
override void visit(const AutoDeclaration ad) override void visit(const AutoDeclaration ad)
@ -105,7 +122,7 @@ class FinderVisitor : ASTVisitor
foreach (part; ad.parts) foreach (part; ad.parts)
{ {
if (part.identifier.text == symbolName) if (part.identifier.text == symbolName)
output.writefln("%s(%d:%d)", fileName, part.identifier.line, part.identifier.column); output(fileName, part.identifier.line, part.identifier.column);
} }
} }
@ -118,14 +135,14 @@ class FinderVisitor : ASTVisitor
override void visit(const T t) override void visit(const T t)
{ {
if (t.name.text == symbolName) if (t.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, t.name.line, t.name.column); output(fileName, t.name.line, t.name.column);
t.accept(this); t.accept(this);
} }
} }
alias visit = ASTVisitor.visit; alias visit = ASTVisitor.visit;
File output; OutputHandler output;
string symbolName; string symbolName;
string fileName; string fileName;
} }

17
test.sh
View File

@ -1,17 +0,0 @@
rm -f test
rm -f test.o
dmd\
src/*.d\
libdparse/src/std/*.d\
libdparse/src/std/d/*.d\
inifiled/source/*.d\
src/analysis/*.d\
-oftest\
-g -unittest\
-J.
./test
rm -f test test.o