diff --git a/README.md b/README.md index e5154c2..3440568 100644 --- a/README.md +++ b/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 "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 ```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 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. -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. 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] final_attribute_check = "+std.foo,+std.bar" useless_initializer = "-std." -;pseudo variable (workaround against an inifiled bug) -foo="" ``` A few examples: @@ -333,3 +336,5 @@ A few examples: - `+.bar`: Includes all modules matching `.bar` (e.g. `foo.bar`, `a.b.c.barros`) - `-etc.`: Excludes all modules from `.etc` - `+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 diff --git a/build.bat b/build.bat index af36b70..04a988b 100644 --- a/build.bat +++ b/build.bat @@ -1,7 +1,8 @@ @echo off setlocal enabledelayedexpansion -set DFLAGS=-O -release -inline +set DFLAGS=-O -release -inline -version=StdLoggerDisableWarning +set TESTFLAGS=-g -w -version=StdLoggerDisableWarning set CORE= set LIBDPARSE= set STD= @@ -13,8 +14,6 @@ set LIBDDOC= for %%x in (src\*.d) do set CORE=!CORE! %%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\std\experimental\*.d) do set LIBDPARSE=!LIBDPARSE! %%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\internal\*.d) do set CONTAINERS=!CONTAINERS! %%x -@echo on -dmd %CORE% %STD% %LIBDPARSE% %LIBDDOC% %ANALYSIS% %INIFILED% %DSYMBOL% %CONTAINERS% %DFLAGS% -I"libdparse\src" -I"dsymbol\src" -I"containers\src" -I"libddoc\src" -ofdscanner.exe +if "%1" == "test" goto test_cmd +@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 diff --git a/dsymbol b/dsymbol index d22c971..6920a04 160000 --- a/dsymbol +++ b/dsymbol @@ -1 +1 @@ -Subproject commit d22c9714a60ac05cb32db938e81a396cffb5ffa6 +Subproject commit 6920a0489fbef44f105cdfb76d426a03ae14259a diff --git a/dub.json b/dub.json index 8947966..82f62e9 100644 --- a/dub.json +++ b/dub.json @@ -1,19 +1,22 @@ { - "name": "dscanner", - "description": "Swiss-army knife for D source code", - "copyright": "© Brian Schott", - "authors": ["Brian Schott"], - "license" : "Boost Software License - Version 1.0", - "targetType": "autodetect", - "versions": [ - "built_with_dub", - "StdLoggerDisableWarning" - ], - "dependencies": { - "libdparse": "~>0.7.2-alpha.1", - "dsymbol": "~>0.2.0", - "inifiled": ">=0.0.6", - "emsi_containers": "~>0.5.3", - "libddoc": "~>0.2.0" - }, + "name" : "dscanner", + "description" : "Swiss-army knife for D source code", + "copyright" : "© Brian Schott", + "authors" : [ + "Brian Schott" + ], + "license" : "Boost Software License - Version 1.0", + "targetType" : "autodetect", + "versions" : [ + "built_with_dub", + "StdLoggerDisableWarning" + ], + "dependencies" : { + "libdparse" : "~>0.7.2-alpha.1", + "dsymbol" : "~>0.2.6", + "inifiled" : ">=1.0.2", + "emsi_containers" : "~>0.5.3", + "libddoc" : "~>0.2.0" + }, + "targetPath" : "bin" } diff --git a/src/analysis/base.d b/src/analysis/base.d index 193b971..e0c9f80 100644 --- a/src/analysis/base.d +++ b/src/analysis/base.d @@ -56,7 +56,7 @@ public: protected: - bool inAggregate = false; + bool inAggregate; bool skipTests; template visitTemplate(T) diff --git a/src/analysis/duplicate_attribute.d b/src/analysis/duplicate_attribute.d index d10cd6b..887367b 100644 --- a/src/analysis/duplicate_attribute.d +++ b/src/analysis/duplicate_attribute.d @@ -34,12 +34,12 @@ class DuplicateAttributeCheck : BaseAnalyzer void checkAttributes(const Declaration node) { - bool hasProperty = false; - bool hasSafe = false; - bool hasTrusted = false; - bool hasSystem = false; - bool hasPure = false; - bool hasNoThrow = false; + bool hasProperty; + bool hasSafe; + bool hasTrusted; + bool hasSystem; + bool hasPure; + bool hasNoThrow; // Check the attributes foreach (attribute; node.attributes) diff --git a/src/analysis/enumarrayliteral.d b/src/analysis/enumarrayliteral.d index e31a660..99933fe 100644 --- a/src/analysis/enumarrayliteral.d +++ b/src/analysis/enumarrayliteral.d @@ -24,7 +24,7 @@ class EnumArrayLiteralCheck : BaseAnalyzer super(fileName, sc, skipTests); } - bool looking = false; + bool looking; mixin visitTemplate!ClassDeclaration; mixin visitTemplate!InterfaceDeclaration; diff --git a/src/analysis/final_attribute.d b/src/analysis/final_attribute.d index 684f230..15de025 100644 --- a/src/analysis/final_attribute.d +++ b/src/analysis/final_attribute.d @@ -30,6 +30,7 @@ private: 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_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 struct_f = "functions declared within a struct are never virtual"; static immutable union_f = "functions declared within an union are never virtual"; @@ -49,6 +50,8 @@ private: bool[] _private; bool _finalAggregate; + bool _alwaysStatic; + bool _blockStatic; Parent _parent = Parent.module_; void addError(T)(T t, string msg) @@ -75,6 +78,7 @@ public: const Parent saved = _parent; _parent = Parent.struct_; _private.length += 1; + _alwaysStatic = false; sd.accept(this); _private.length -= 1; _parent = saved; @@ -85,6 +89,7 @@ public: const Parent saved = _parent; _parent = Parent.interface_; _private.length += 1; + _alwaysStatic = false; id.accept(this); _private.length -= 1; _parent = saved; @@ -95,6 +100,7 @@ public: const Parent saved = _parent; _parent = Parent.union_; _private.length += 1; + _alwaysStatic = false; ud.accept(this); _private.length -= 1; _parent = saved; @@ -105,6 +111,7 @@ public: const Parent saved = _parent; _parent = Parent.class_; _private.length += 1; + _alwaysStatic = false; cd.accept(this); _private.length -= 1; _parent = saved; @@ -120,14 +127,34 @@ public: // 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) { + import std.algorithm.iteration : filter; + import std.algorithm.searching : canFind; + 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) { d.accept(this); _parent = savedParent; + if (undoBlockStatic) + _blockStatic = false; } if (!d.attributeDeclaration && @@ -138,30 +165,27 @@ public: !d.functionDeclaration) return; - import std.algorithm.iteration : filter; - import std.algorithm.searching : find; - import std.range.primitives : empty; - if (d.attributeDeclaration && d.attributeDeclaration.attribute) { const tp = d.attributeDeclaration.attribute.attribute.type; _private[$-1] = isProtection(tp) & (tp == tok!"private"); } - const bool isFinal = !d.attributes - .find!(a => a.attribute.type == tok!"final") - .empty; + const bool isFinal = d.attributes + .canFind!(a => a.attribute.type == tok!"final"); + + const bool isStaticOnce = d.attributes + .canFind!(a => a.attribute.type == tok!"static"); // determine if private - const bool changeProtectionOnce = !d.attributes - .filter!(a => a.attribute.type.isProtection) - .empty; + const bool changeProtectionOnce = d.attributes + .canFind!(a => a.attribute.type.isProtection); - const bool isPrivateOnce = !d.attributes - .find!(a => a.attribute.type == tok!"private") - .empty; + const bool isPrivateOnce = d.attributes + .canFind!(a => a.attribute.type == tok!"private"); bool isPrivate; + if (isPrivateOnce) isPrivate = true; else if (_private[$-1] && !changeProtectionOnce) @@ -194,6 +218,8 @@ public: addError(fd, MESSAGE.class_t); if (isPrivate) addError(fd, MESSAGE.class_p); + else if (isStaticOnce || _alwaysStatic || _blockStatic) + addError(fd, MESSAGE.class_s); else if (_finalAggregate) addError(fd, MESSAGE.class_f); break; @@ -349,5 +375,32 @@ public: FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p) ), 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."); } diff --git a/src/analysis/function_attributes.d b/src/analysis/function_attributes.d index 5dde5f2..871ff4b 100644 --- a/src/analysis/function_attributes.d +++ b/src/analysis/function_attributes.d @@ -59,8 +59,8 @@ class FunctionAttributeCheck : BaseAnalyzer { if (dec.parameters.parameters.length == 0) { - bool foundConst = false; - bool foundProperty = false; + bool foundConst; + bool foundProperty; foreach (attribute; dec.attributes) foundConst = foundConst || attribute.attribute.type == tok!"const" || attribute.attribute.type == tok!"immutable" diff --git a/src/analysis/helpers.d b/src/analysis/helpers.d index 5312ff9..390c7b2 100644 --- a/src/analysis/helpers.d +++ b/src/analysis/helpers.d @@ -50,6 +50,7 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, { import analysis.run : parseModule; import dparse.lexer : StringCache, Token; + import std.ascii : newline; StringCache cache = StringCache(StringCache.defaultBucketCount); RollbackAllocator r; @@ -60,7 +61,7 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, // Run the code and get any warnings MessageSet rawWarnings = analyze("test", m, config, moduleCache, tokens); - string[] codeLines = code.split("\n"); + string[] codeLines = code.split(newline); // Get the warnings ordered by line string[size_t] warnings; diff --git a/src/analysis/label_var_same_name_check.d b/src/analysis/label_var_same_name_check.d index eb4172b..13151ff 100644 --- a/src/analysis/label_var_same_name_check.d +++ b/src/analysis/label_var_same_name_check.d @@ -28,6 +28,11 @@ class LabelVarNameCheck : BaseAnalyzer mixin ScopedVisit!IfStatement; mixin ScopedVisit!TemplateDeclaration; + mixin AggregateVisit!ClassDeclaration; + mixin AggregateVisit!StructDeclaration; + mixin AggregateVisit!InterfaceDeclaration; + mixin AggregateVisit!UnionDeclaration; + override void visit(const VariableDeclaration var) { foreach (dec; var.declarators) @@ -63,6 +68,16 @@ private: Thing[string][] stack; + template AggregateVisit(NodeType) + { + override void visit(const NodeType n) + { + pushAggregateName(n.name); + n.accept(this); + popAggregateName(); + } + } + template ScopedVisit(NodeType) { override void visit(const NodeType n) @@ -81,15 +96,16 @@ private: size_t i; 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) - 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) { immutable thisKind = fromLabel ? "Label" : "Variable"; immutable otherKind = thing.isVar ? "variable" : "label"; addErrorMessage(name.line, name.column, "dscanner.suspicious.label_var_same_name", - thisKind ~ " \"" ~ name.text ~ "\" has the same name as a " + thisKind ~ " \"" ~ fqn ~ "\" has the same name as a " ~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ "."); } ++i; @@ -121,6 +137,32 @@ private: } 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 @@ -190,6 +232,45 @@ unittest 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); stderr.writeln("Unittest for LabelVarNameCheck passed."); } diff --git a/src/analysis/lambda_return_check.d b/src/analysis/lambda_return_check.d index 2ddf874..e85d05b 100644 --- a/src/analysis/lambda_return_check.d +++ b/src/analysis/lambda_return_check.d @@ -44,6 +44,7 @@ private: enum KEY = "dscanner.confusing.lambda_returns_lambda"; } +version(Windows) {/*because of newline in code*/} else unittest { import analysis.helpers : assertAnalyzerWarnings; diff --git a/src/analysis/line_length.d b/src/analysis/line_length.d index 542f330..d411c71 100644 --- a/src/analysis/line_length.d +++ b/src/analysis/line_length.d @@ -125,7 +125,6 @@ private: size_t length; const newLine = tok.line > prevLine; bool multiLine; - if (tok.text is null) length += str(tok.type).length; else diff --git a/src/analysis/objectconst.d b/src/analysis/objectconst.d index b7b4d4b..64f4184 100644 --- a/src/analysis/objectconst.d +++ b/src/analysis/objectconst.d @@ -21,6 +21,7 @@ class ObjectConstCheck : BaseAnalyzer { alias visit = BaseAnalyzer.visit; + /// this(string fileName, const(Scope)* sc, bool skipTests = false) { super(fileName, sc, skipTests); @@ -31,24 +32,40 @@ class ObjectConstCheck : BaseAnalyzer mixin visitTemplate!UnionDeclaration; 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) { - if (inAggregate && d.functionDeclaration !is null - && isInteresting(d.functionDeclaration.name.text) && (!hasConst(d.attributes) - && !hasConst(d.functionDeclaration.memberFunctionAttributes))) + import std.algorithm : any; + bool setConstBlock; + 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, d.functionDeclaration.name.column, "dscanner.suspicious.object_const", "Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const."); } + d.accept(this); - } - private static bool hasConst(const Attribute[] attributes) - { - import std.algorithm : any; - - return attributes.any!(a => a.attribute == tok!"const"); + if (!inAggregate) + constColon = false; + if (setConstBlock) + constBlock = false; } private static bool hasConst(const MemberFunctionAttribute[] attributes) @@ -65,7 +82,8 @@ class ObjectConstCheck : BaseAnalyzer || 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 class Dog { diff --git a/src/analysis/opequals_without_tohash.d b/src/analysis/opequals_without_tohash.d index 4c29f08..f0e2fa2 100644 --- a/src/analysis/opequals_without_tohash.d +++ b/src/analysis/opequals_without_tohash.d @@ -39,9 +39,9 @@ class OpEqualsWithoutToHashCheck : BaseAnalyzer private void actualCheck(const Token name, const StructBody structBody) { - bool hasOpEquals = false; - bool hasToHash = false; - bool hasOpCmp = false; + bool hasOpEquals; + bool hasToHash; + bool hasOpCmp; // Just return if missing children if (!structBody || !structBody.declarations || name is Token.init) diff --git a/src/analysis/range.d b/src/analysis/range.d index 32fd0ca..84ac157 100644 --- a/src/analysis/range.d +++ b/src/analysis/range.d @@ -141,9 +141,10 @@ private: long parseNumber(string te) { 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[1] == 'x' || t[1] == 'X') diff --git a/src/analysis/run.d b/src/analysis/run.d index 11649f1..3079e17 100644 --- a/src/analysis/run.d +++ b/src/analysis/run.d @@ -11,6 +11,7 @@ import std.conv; import std.algorithm; import std.range; import std.array; +import std.functional : toDelegate; import dparse.lexer; import dparse.parser; import dparse.ast; @@ -162,7 +163,7 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config, bool analyze(string[] fileNames, const StaticAnalysisConfig config, ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true) { - bool hasErrors = false; + bool hasErrors; foreach (fileName; fileNames) { auto code = readFile(fileName); @@ -203,8 +204,9 @@ const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p, tokens = getTokensForParser(code, config, &cache); if (linesOfCode !is null) (*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens); - return dparse.parser.parseModule(tokens, fileName, p, report - ? &messageFunctionJSON : &messageFunction, errorCount, warningCount); + return dparse.parser.parseModule(tokens, fileName, p, + report ? toDelegate(&messageFunctionJSON) : toDelegate(&messageFunction), + errorCount, warningCount); } /** diff --git a/src/analysis/style.d b/src/analysis/style.d index d699c88..3e76180 100644 --- a/src/analysis/style.d +++ b/src/analysis/style.d @@ -70,18 +70,12 @@ final class StyleChecker : BaseAnalyzer override void visit(const VariableDeclaration vd) { - import std.algorithm.iteration : filter; - - varIsEnum = !vd.storageClasses.filter!(a => a.token == tok!"enum").empty; vd.accept(this); } override void visit(const Declarator dec) { - if (varIsEnum) - checkAggregateName("Variable", dec.name); - else - checkLowercaseName("Variable", dec.name); + checkLowercaseName("Variable", dec.name); } override void visit(const FunctionDeclaration dec) @@ -143,8 +137,6 @@ final class StyleChecker : BaseAnalyzer aggregateType ~ " name '" ~ name.text ~ "' does not match style guidelines."); } - bool varIsEnum; - bool[] _winStyles = [false]; bool winStyle() @@ -183,7 +175,10 @@ unittest interface puma {} // [warn]: Interface name 'puma' 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 bool Something = false; + enum bool something = false; + enum bool someThing = false; + enum Cat { fritz, } + enum Cat = Cat.fritz; }c, sac); assertAnalyzerWarnings(q{ diff --git a/src/analysis/undocumented.d b/src/analysis/undocumented.d index 6bde256..013e518 100644 --- a/src/analysis/undocumented.d +++ b/src/analysis/undocumented.d @@ -51,10 +51,10 @@ class UndocumentedDeclarationCheck : BaseAnalyzer immutable bool prevOverride = getOverride(); immutable bool prevDisabled = getDisabled(); immutable bool prevDeprecated = getDeprecated(); - bool dis = false; - bool dep = false; - bool ovr = false; - bool pushed = false; + bool dis; + bool dep; + bool ovr; + bool pushed; foreach (attribute; dec.attributes) { if (isProtection(attribute.attribute.type)) diff --git a/src/analysis/unmodified.d b/src/analysis/unmodified.d index e661735..7acc649 100644 --- a/src/analysis/unmodified.d +++ b/src/analysis/unmodified.d @@ -160,7 +160,8 @@ class UnmodifiedFinder : BaseAnalyzer foreachStatement.low.accept(this); interest--; } - foreachStatement.declarationOrStatement.accept(this); + if (foreachStatement.declarationOrStatement !is null) + foreachStatement.declarationOrStatement.accept(this); } override void visit(const TraitsExpression) @@ -223,7 +224,7 @@ private: castExpression.accept(this); } - bool foundCast = false; + bool foundCast; } if (initializer is null) diff --git a/src/analysis/unused.d b/src/analysis/unused.d index 1c080a6..38b246e 100644 --- a/src/analysis/unused.d +++ b/src/analysis/unused.d @@ -11,6 +11,7 @@ import std.container; import std.regex : Regex, regex, matchAll; import dsymbol.scope_ : Scope; import std.algorithm.iteration : map; +import std.algorithm : all; /** * Checks for unused variables. @@ -90,18 +91,26 @@ class UnusedVariableCheck : BaseAnalyzer override void visit(const WhileStatement whileStatement) { - interestDepth++; - whileStatement.expression.accept(this); - interestDepth--; - whileStatement.declarationOrStatement.accept(this); + if (whileStatement.expression !is null) + { + interestDepth++; + whileStatement.expression.accept(this); + interestDepth--; + } + if (whileStatement.declarationOrStatement !is null) + whileStatement.declarationOrStatement.accept(this); } override void visit(const DoStatement doStatement) { - interestDepth++; - doStatement.expression.accept(this); - interestDepth--; - doStatement.statementNoCaseNoDefault.accept(this); + if (doStatement.expression !is null) + { + interestDepth++; + doStatement.expression.accept(this); + interestDepth--; + } + if (doStatement.statementNoCaseNoDefault !is null) + doStatement.statementNoCaseNoDefault.accept(this); } override void visit(const ForStatement forStatement) @@ -120,7 +129,8 @@ class UnusedVariableCheck : BaseAnalyzer forStatement.increment.accept(this); interestDepth--; } - forStatement.declarationOrStatement.accept(this); + if (forStatement.declarationOrStatement !is null) + forStatement.declarationOrStatement.accept(this); } override void visit(const IfStatement ifStatement) @@ -131,7 +141,8 @@ class UnusedVariableCheck : BaseAnalyzer ifStatement.expression.accept(this); interestDepth--; } - ifStatement.thenStatement.accept(this); + if (ifStatement.thenStatement !is null) + ifStatement.thenStatement.accept(this); if (ifStatement.elseStatement !is null) ifStatement.elseStatement.accept(this); } @@ -155,7 +166,8 @@ class UnusedVariableCheck : BaseAnalyzer override void visit(const AssignExpression assignExp) { - assignExp.ternaryExpression.accept(this); + if (assignExp.ternaryExpression !is null) + assignExp.ternaryExpression.accept(this); if (assignExp.expression !is null) { interestDepth++; @@ -372,7 +384,7 @@ private: void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef) { - if (inAggregateScope) + if (inAggregateScope || name.all!(a => a == '_')) return; tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef)); } @@ -506,6 +518,15 @@ private: 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); stderr.writeln("Unittest for UnusedVariableCheck passed."); } diff --git a/src/analysis/useless_initializer.d b/src/analysis/useless_initializer.d index 5deb565..6240a64 100644 --- a/src/analysis/useless_initializer.d +++ b/src/analysis/useless_initializer.d @@ -5,13 +5,19 @@ module analysis.useless_initializer; import analysis.base; +import containers.dynamicarray; +import containers.hashmap; import dparse.ast; import dparse.lexer; +import std.algorithm; +import std.range : empty; import std.stdio; /* 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). */ /** @@ -20,209 +26,314 @@ Limitations: */ final class UselessInitializerChecker : BaseAnalyzer { - alias visit = BaseAnalyzer.visit; + alias visit = BaseAnalyzer.visit; private: - 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`; + enum key = "dscanner.useless-initializer"; - static immutable strDefs = [`""`, `""c`, `""w`, `""d`, "``", "``c", "``w", "``d", "q{}"]; - static immutable intDefs = ["0", "0L", "0UL", "0uL", "0U", "0x0", "0b0"]; + 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"]; + + HashMap!(string, bool) _structCanBeInit; + DynamicArray!(string) _structStack; + DynamicArray!(bool) _inStruct; + DynamicArray!(bool) _atDisabled; + bool _inTest; public: - /// - this(string fileName, bool skipTests = false) - { - super(fileName, null, skipTests); - } + /// + this(string fileName, bool skipTests = false) + { + super(fileName, null, skipTests); + _inStruct.insert(false); + } - override void visit(const(VariableDeclaration) decl) - { - foreach (declarator; decl.declarators) - { - if (!decl.type || !decl.type.type2) - continue; - if (!declarator.initializer || !declarator.initializer.nonVoidInitializer) - continue; + override void visit(const(Unittest) test) + { + if (skipTests) + return; + _inTest = true; + test.accept(this); + _inTest = false; + } - import std.format : format; + override void visit(const(StructDeclaration) decl) + { + if (_inTest) + return; - version(unittest) - enum warn = q{addErrorMessage(declarator.name.line, declarator.name.column, - key, msg);}; - else - enum warn = q{addErrorMessage(declarator.name.line, declarator.name.column, - key, msg.format(declarator.name.text));}; + assert(_inStruct.length > 1); - // --- Info about the declaration type --- // - import std.algorithm : among, canFind; - import std.algorithm.iteration : filter; - import std.range : empty; + const string structName = _inStruct[$-2] ? + _structStack.back() ~ "." ~ decl.name.text : + decl.name.text; - 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; + _structStack.insert(structName); + _structCanBeInit[structName] = false; + _atDisabled.insert(false); + decl.accept(this); + _structStack.removeBack(); + _atDisabled.removeBack(); + } - bool isStr, isSzInt; - Token customType; + 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(); + } - if (decl.type.type2.symbol && decl.type.type2.symbol.identifierOrTemplateChain && - decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances.length == 1) - { - const IdentifierOrTemplateInstance idt = - decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances[0]; + 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); + } - customType = idt.identifier; - isStr = customType.text.among("string", "wstring", "dstring") != 0; - isSzInt = customType.text.among("size_t", "ptrdiff_t") != 0; - } + // issue 473, prevent to visit delegates that contain duck type checkers. + override void visit(const(TypeofExpression)) {} - // --- 'BasicType/Symbol AssignExpression' ---// - const NonVoidInitializer nvi = declarator.initializer.nonVoidInitializer; - const UnaryExpression ue = cast(UnaryExpression) nvi.assignExpression; - if (ue && ue.primaryExpression) - { - const Token value = ue.primaryExpression.primary; + // 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); + } + } - if (!isPtr && !isArr && !isStr && decl.type.type2.builtinType != tok!"") - { - switch(decl.type.type2.builtinType) - { - // check for common cases of default values - case tok!"byte", tok!"ubyte": - case tok!"short", tok!"ushort": - case tok!"int", tok!"uint": - case tok!"long", tok!"ulong": - case tok!"cent", tok!"ucent": - if (intDefs.canFind(value.text)) - mixin(warn); - goto default; - default: - // check for BasicType.init - if (ue.primaryExpression.basicType.type == decl.type.type2.builtinType && - ue.primaryExpression.primary.text == "init" && - !ue.primaryExpression.expression) - mixin(warn); - } - } - else if (isSzInt) - { - if (intDefs.canFind(value.text)) - mixin(warn); - } - else if (isPtr) - { - if (str(value.type) == "null") - mixin(warn); - } - else if (isArr) - { - if (str(value.type) == "null") - mixin(warn); - else if (nvi.arrayInitializer && nvi.arrayInitializer.arrayMemberInitializations.length == 0) - 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); - } - } + 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; + } - // Symbol s = Symbol.init - else if (ue && customType != tok!"" && ue.unaryExpression && ue.unaryExpression.primaryExpression && - ue.unaryExpression.primaryExpression.identifierOrTemplateInstance && - ue.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier == customType && - ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init") - { - mixin(warn); - } + foreach (declarator; decl.declarators) + { + if (!declarator.initializer || + !declarator.initializer.nonVoidInitializer || + declarator.comment !is null) + { + continue; + } - // 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init - else if (nvi.arrayInitializer && (isArr || isStr)) - { - if (nvi.arrayInitializer.arrayMemberInitializations.length == 0) - mixin(warn); - } - } + version(unittest) + { + enum warn = q{addErrorMessage(declarator.name.line, + declarator.name.column, key, msg);}; + } + else + { + import std.format : format; + enum warn = q{addErrorMessage(declarator.name.line, + declarator.name.column, key, msg.format(declarator.name.text));}; + } - decl.accept(this); - } + // --- Info about the declaration type --- // + const bool isPtr = decl.type.typeSuffixes && decl.type.typeSuffixes + .canFind!(a => a.star != tok!""); + const bool isArr = decl.type.typeSuffixes && decl.type.typeSuffixes + .canFind!(a => a.array); + + bool isStr, isSzInt; + Token customType; + + if (decl.type.type2.symbol && decl.type.type2.symbol.identifierOrTemplateChain && + decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances.length == 1) + { + const IdentifierOrTemplateInstance idt = + decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances[0]; + + customType = idt.identifier; + isStr = customType.text.among("string", "wstring", "dstring") != 0; + isSzInt = customType.text.among("size_t", "ptrdiff_t") != 0; + } + + // --- 'BasicType/Symbol AssignExpression' ---// + const NonVoidInitializer nvi = declarator.initializer.nonVoidInitializer; + const UnaryExpression ue = cast(UnaryExpression) nvi.assignExpression; + if (ue && ue.primaryExpression) + { + const Token value = ue.primaryExpression.primary; + + if (!isPtr && !isArr && !isStr && decl.type.type2.builtinType != tok!"") + { + switch(decl.type.type2.builtinType) + { + // check for common cases of default values + case tok!"byte", tok!"ubyte": + case tok!"short", tok!"ushort": + case tok!"int", tok!"uint": + case tok!"long", tok!"ulong": + case tok!"cent", tok!"ucent": + case tok!"bool": + if (intDefs.canFind(value.text) || value == tok!"false") + mixin(warn); + goto default; + default: + // check for BasicType.init + if (ue.primaryExpression.basicType.type == decl.type.type2.builtinType && + ue.primaryExpression.primary.text == "init" && + !ue.primaryExpression.expression) + mixin(warn); + } + } + else if (isSzInt) + { + if (intDefs.canFind(value.text)) + mixin(warn); + } + else if (isPtr || isStr) + { + if (str(value.type) == "null") + mixin(warn); + } + else if (isArr) + { + if (str(value.type) == "null") + mixin(warn); + else if (nvi.arrayInitializer && nvi.arrayInitializer.arrayMemberInitializations.length == 0) + mixin(warn); + } + } + + // Symbol s = Symbol.init + else if (ue && customType != tok!"" && ue.unaryExpression && ue.unaryExpression.primaryExpression && + ue.unaryExpression.primaryExpression.identifierOrTemplateInstance && + ue.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier == customType && + ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init") + { + if (customType.text in _structCanBeInit) + { + if (!_structCanBeInit[customType.text]) + mixin(warn); + } + } + + // 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init + else if (nvi.arrayInitializer && (isArr || isStr)) + { + if (nvi.arrayInitializer.arrayMemberInitializations.length == 0) + mixin(warn); + } + } + + decl.accept(this); + } } @system unittest { - import analysis.config : Check, disabledConfig, StaticAnalysisConfig; - import analysis.helpers: assertAnalyzerWarnings; - import std.stdio : stderr; + import analysis.config : Check, disabledConfig, StaticAnalysisConfig; + import analysis.helpers: assertAnalyzerWarnings; + import std.stdio : stderr; - StaticAnalysisConfig sac = disabledConfig; - sac.useless_initializer = Check.enabled; + StaticAnalysisConfig sac = disabledConfig; + sac.useless_initializer = Check.enabled; - // fails - assertAnalyzerWarnings(q{ - ubyte a = 0x0; // [warn]: X - int a = 0; // [warn]: X - ulong a = 0; // [warn]: X - int* a = null; // [warn]: X - Foo* a = null; // [warn]: X - int[] a = null; // [warn]: X - int[] a = []; // [warn]: X - string a = ""; // [warn]: X - string a = ""c; // [warn]: X - wstring a = ""w; // [warn]: X - dstring a = ""d; // [warn]: X - string a = q{}; // [warn]: X - size_t a = 0; // [warn]: X - ptrdiff_t a = 0; // [warn]: X - string a = []; // [warn]: X - char[] a = ""; // [warn]: X - int a = int.init; // [warn]: X - char a = char.init; // [warn]: X - S s = S.init; // [warn]: X - }, sac); + // fails + assertAnalyzerWarnings(q{ + struct S {} + ubyte a = 0x0; // [warn]: X + int a = 0; // [warn]: X + ulong a = 0; // [warn]: X + int* a = null; // [warn]: X + Foo* a = null; // [warn]: X + int[] a = null; // [warn]: X + int[] a = []; // [warn]: X + string a = null; // [warn]: X + string a = null; // [warn]: X + wstring a = null; // [warn]: X + dstring a = null; // [warn]: X + size_t a = 0; // [warn]: X + ptrdiff_t a = 0; // [warn]: X + string a = []; // [warn]: X + char[] a = null; // [warn]: X + int a = int.init; // [warn]: X + char a = char.init; // [warn]: X + S s = S.init; // [warn]: X + bool a = false; // [warn]: X + }, sac); - // passes - assertAnalyzerWarnings(q{ - ubyte a = 0xFE; - int a = 1; - ulong a = 1; - int* a = &a; - Foo* a = &a; - int[] a = &a; - int[] a = [0]; - string a = "sdf"; - string a = "sdg"c; - wstring a = "sdg"w; - dstring a = "fgh"d; - string a = q{int a;}; - size_t a = 1; - ptrdiff_t a = 1; - string a = ['a']; - char[] a = "ze"; - S s = S(0,1); - S s = s.call(); - }, sac); + // passes + assertAnalyzerWarnings(q{ + struct D {@disable this();} + struct E {this() @disable;} + ubyte a = 0xFE; + int a = 1; + ulong a = 1; + int* a = &a; + Foo* a = &a; + int[] a = &a; + int[] a = [0]; + string a = "sdf"; + string a = "sdg"c; + wstring a = "sdg"w; + dstring a = "fgh"d; + string a = q{int a;}; + size_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 = ""; + string a = ""c; + wstring a = ""w; + dstring a = ""d; + string a = q{}; + char[] a = "ze"; + S s = S(0,1); + 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); - stderr.writeln("Unittest for UselessInitializerChecker passed."); + stderr.writeln("Unittest for UselessInitializerChecker passed."); } diff --git a/src/ctags.d b/src/ctags.d index c4c76e8..939724a 100644 --- a/src/ctags.d +++ b/src/ctags.d @@ -17,6 +17,7 @@ import std.conv; import std.typecons; import containers.ttree; import std.string; +import std.functional : toDelegate; /** * Prints CTAGS information to the given file. @@ -65,7 +66,7 @@ void printCtags(File output, string[] fileNames) } 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); printer.fileName = fileName; diff --git a/src/etags.d b/src/etags.d index a89ce6c..a072632 100644 --- a/src/etags.d +++ b/src/etags.d @@ -17,6 +17,7 @@ import std.path; import std.array; import std.conv; import std.string; +import std.functional : toDelegate; // Prefix tags with their module name. Seems like correct behavior, but just // 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)); f.rawRead(bytes); 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; printer.moduleName = m.moduleFullName(fileName); diff --git a/src/imports.d b/src/imports.d index 392ebe4..6389608 100644 --- a/src/imports.d +++ b/src/imports.d @@ -11,6 +11,7 @@ import dparse.parser; import dparse.rollback_allocator; import std.stdio; import std.container.rbtree; +import std.functional : toDelegate; import readers; /** @@ -63,7 +64,7 @@ private void visitFile(bool usingStdin, string fileName, RedBlackTree!string imp config.stringBehavior = StringBehavior.source; auto visitor = new ImportPrinter; 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); importedModules.insert(visitor.imports[]); } diff --git a/src/main.d b/src/main.d index 6b0a9b0..8f87c4a 100644 --- a/src/main.d +++ b/src/main.d @@ -15,6 +15,7 @@ import std.stdio; import std.range; import std.experimental.lexer; import std.typecons : scoped; +import std.functional : toDelegate; import dparse.lexer; import dparse.parser; import dparse.rollback_allocator; @@ -283,7 +284,7 @@ else config.stringBehavior = StringBehavior.source; auto tokens = getTokensForParser(usingStdin ? readStdin() : readFile(args[1]), config, &cache); - auto mod = parseModule(tokens, fileName, &rba, &doNothing); + auto mod = parseModule(tokens, fileName, &rba, toDelegate(&doNothing)); if (ast) { diff --git a/src/symbol_finder.d b/src/symbol_finder.d index baf63da..7fabd01 100644 --- a/src/symbol_finder.d +++ b/src/symbol_finder.d @@ -12,8 +12,25 @@ import dparse.ast; import dparse.rollback_allocator; import std.stdio; import std.file : isFile; +import std.functional : toDelegate; 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.conv : to; @@ -31,7 +48,7 @@ void findDeclarationOf(File output, string symbolName, string[] fileNames) f.rawRead(bytes); auto tokens = getTokensForParser(bytes, config, &cache); RollbackAllocator rba; - Module m = parseModule(tokens.array, fileName, &rba, &doNothing); + Module m = parseModule(tokens.array, fileName, &rba, toDelegate(&doNothing)); visitor.fileName = fileName; visitor.visit(m); } @@ -45,7 +62,7 @@ void doNothing(string, size_t, size_t, string, bool) class FinderVisitor : ASTVisitor { - this(File output, string symbolName) + this(OutputHandler output, string symbolName) { this.output = output; this.symbolName = symbolName; @@ -61,19 +78,19 @@ class FinderVisitor : ASTVisitor override void visit(const EnumDeclaration dec) { 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) { 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) { 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) @@ -83,13 +100,13 @@ class FinderVisitor : ASTVisitor foreach (ident; dec.identifierList.identifiers) { if (ident.text == symbolName) - output.writefln("%s(%d:%d)", fileName, ident.line, ident.column); + output(fileName, ident.line, ident.column); } } foreach (initializer; dec.initializers) { if (initializer.name.text == symbolName) - output.writefln("%s(%d:%d)", fileName, initializer.name.line, + output(fileName, initializer.name.line, initializer.name.column); } } @@ -97,7 +114,7 @@ class FinderVisitor : ASTVisitor override void visit(const Declarator dec) { 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) @@ -105,7 +122,7 @@ class FinderVisitor : ASTVisitor foreach (part; ad.parts) { 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) { 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); } } alias visit = ASTVisitor.visit; - File output; + OutputHandler output; string symbolName; string fileName; } diff --git a/test.sh b/test.sh deleted file mode 100755 index 1648b6b..0000000 --- a/test.sh +++ /dev/null @@ -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