Merge upstream/master
This commit is contained in:
commit
35bd4f22af
11
README.md
11
README.md
|
@ -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
|
||||||
|
|
24
build.bat
24
build.bat
|
@ -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
|
||||||
|
|
2
dsymbol
2
dsymbol
|
@ -1 +1 @@
|
||||||
Subproject commit d22c9714a60ac05cb32db938e81a396cffb5ffa6
|
Subproject commit 6920a0489fbef44f105cdfb76d426a03ae14259a
|
9
dub.json
9
dub.json
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
bool inAggregate = false;
|
bool inAggregate;
|
||||||
bool skipTests;
|
bool skipTests;
|
||||||
|
|
||||||
template visitTemplate(T)
|
template visitTemplate(T)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.");
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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[]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue