Merge master and update dependencies

This commit is contained in:
Hackerpilot 2015-09-18 02:56:30 -07:00
commit 380064de6a
18 changed files with 604 additions and 221 deletions

1
.gitignore vendored
View File

@ -25,6 +25,7 @@ dscanner-report.json
*.o *.o
*.obj *.obj
*.exe *.exe
obj
#debug build #debug build
dsc dsc

View File

@ -79,12 +79,13 @@ you do not want to use the one created by the "--defaultConfig" option.
* Confusing asm syntax. * Confusing asm syntax.
* Placement of const, immutable, or inout before a function return type instead of after the parameters. * Placement of const, immutable, or inout before a function return type instead of after the parameters.
* Functions in interface declarations redundantly marked 'abstract'. * Functions in interface declarations redundantly marked 'abstract'.
* Declaring a variable with the same name as a label.
* Variables that could have been declared const or immutable (experimental)
* Redundant parenthesis.
* Unused labels.
#### Wishlish #### Wishlish
* Assigning to foreach variables that are not "ref". https://github.com/Hackerpilot/Dscanner/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement
* Unused imports.
* Variables that are never modified and not declared immutable.
* Assignment in conditionals
### Reports ### Reports
The "--report" option writes a JSON report on the static analysis checks The "--report" option writes a JSON report on the static analysis checks

@ -1 +1 @@
Subproject commit d732a67e76f60fd037547c3ffe8776c6deda6bab Subproject commit 05b1f9f5906c4ac1f38964c7456482b3f11daa32

@ -1 +1 @@
Subproject commit 393da9696f81d0692f8022e2d5d40dc662d984f9 Subproject commit 1c5579db6c1d88a097d46f5bee5ae1e2cf8fff02

@ -1 +1 @@
Subproject commit 0dccfca0e2a132b3c862a62da1c323ccd24e622d Subproject commit 071e0cee083a0392d9226fdfba593dd86c21d412

View File

@ -1,27 +1,26 @@
.PHONY: all test .PHONY: all test
DMD = dmd DC ?= dmd
GDC = gdc DMD := $(DC)
LDC = ldc2 GDC := gdc
SRC = src/*.d\ LDC := ldc2
src/analysis/*.d\ OBJ_DIR := obj
libdparse/src/std/*.d\ SRC := \
libdparse/src/std/d/*.d\ $(shell find containers/experimental_allocator/src -name "*.d")\
inifiled/source/*.d\ $(shell find containers/src -name "*.d")\
$(shell find dsymbol/src -name "*.d")\ $(shell find dsymbol/src -name "*.d")\
containers/src/containers/ttree.d\ $(shell find inifiled/source/ -name "*.d")\
containers/src/containers/unrolledlist.d\ $(shell find libdparse/src/std/ -name "*.d")\
containers/src/containers/hashset.d\ $(shell find src/ -name "*.d")
containers/src/containers/internal/hash.d\ INCLUDE_PATHS = \
containers/src/containers/internal/node.d\ -Iinifiled/source -Isrc\
containers/src/containers/internal/storage_type.d\ -Ilibdparse/src\
containers/src/memory/allocators.d\ -Ilibdparse/experimental_allocator/src\
containers/src/memory/appender.d -Idsymbol/src -Icontainers/src
INCLUDE_PATHS = -Ilibdparse/src -Idsymbol/src -Icontainers/src
VERSIONS = VERSIONS =
DEBUG_VERSIONS = -version=std_parser_verbose DEBUG_VERSIONS = -version=std_parser_verbose
#DMD_FLAGS = -w -O -release -inline DMD_FLAGS = -w -O -inline -J. -od${OBJ_DIR}
DMD_FLAGS = -w DMD_TEST_FLAGS = -w -g -unittest -J.
all: dmdbuild all: dmdbuild
ldc: ldcbuild ldc: ldcbuild
@ -31,11 +30,11 @@ githash:
git log -1 --format="%H" > githash.txt git log -1 --format="%H" > githash.txt
debug: debug:
${DMD} -w -g -ofdsc ${VERSIONS} ${DEBUG_VERSIONS} ${INCLUDE_PATHS} ${SRC} -J. ${DC} -w -g -ofdsc ${VERSIONS} ${DEBUG_VERSIONS} ${INCLUDE_PATHS} ${SRC}
dmdbuild: githash dmdbuild: githash $(SRC)
mkdir -p bin mkdir -p bin
${DMD} ${DMD_FLAGS} -ofbin/dscanner ${VERSIONS} ${INCLUDE_PATHS} ${SRC} -J. ${DC} ${DMD_FLAGS} -ofbin/dscanner ${VERSIONS} ${INCLUDE_PATHS} ${SRC}
rm -f bin/dscanner.o rm -f bin/dscanner.o
gdcbuild: githash gdcbuild: githash
@ -50,8 +49,9 @@ test:
@./test.sh @./test.sh
clean: clean:
rm -rf dsc *.o rm -rf dsc
rm -rf bin rm -rf bin
rm -rf ${OBJ_DIR}
rm -f dscanner-report.json rm -f dscanner-report.json
report: all report: all

View File

@ -95,4 +95,7 @@ struct StaticAnalysisConfig
@INI("Checks for mismatched argument and parameter names") @INI("Checks for mismatched argument and parameter names")
bool mismatched_args_check; bool mismatched_args_check;
@INI("Checks for labels with the same name as variables")
bool label_var_same_name_check;
} }

View File

@ -59,8 +59,10 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, stri
ParseAllocator p = new ParseAllocator; ParseAllocator p = new ParseAllocator;
const(Module) m = parseModule(file, cast(ubyte[]) code, p, cache, false); const(Module) m = parseModule(file, cast(ubyte[]) code, p, cache, false);
ModuleCache moduleCache;
// Run the code and get any warnings // Run the code and get any warnings
MessageSet rawWarnings = analyze("test", m, config); MessageSet rawWarnings = analyze("test", m, config, moduleCache);
string[] codeLines = code.split("\n"); string[] codeLines = code.split("\n");
// Get the warnings ordered by line // Get the warnings ordered by line

View File

@ -0,0 +1,119 @@
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
module analysis.label_var_same_name_check;
import std.d.ast;
import std.d.lexer;
import analysis.base;
import analysis.helpers;
/**
* Checks for labels and variables that have the same name.
*/
class LabelVarNameCheck : BaseAnalyzer
{
this(string fileName, const(Scope)* sc)
{
super(fileName, sc);
}
override void visit(const Module mod)
{
pushScope();
mod.accept(this);
popScope();
}
override void visit(const BlockStatement block)
{
pushScope();
block.accept(this);
popScope();
}
override void visit(const StructBody structBody)
{
pushScope();
structBody.accept(this);
popScope();
}
override void visit(const VariableDeclaration var)
{
foreach (dec; var.declarators)
duplicateCheck(dec.name, false);
}
override void visit(const LabeledStatement labeledStatement)
{
duplicateCheck(labeledStatement.identifier, true);
if (labeledStatement.declarationOrStatement !is null)
labeledStatement.declarationOrStatement.accept(this);
}
alias visit = BaseAnalyzer.visit;
private:
Thing[string][] stack;
void duplicateCheck(const Token name, bool fromLabel)
{
import std.conv : to;
const(Thing)* thing = name.text in currentScope;
if (thing is null)
currentScope[name.text] = Thing(name.text, name.line, name.column, false);
else
{
immutable thisKind = fromLabel ? "Label" : "Variable";
immutable otherKind = thing.isVar ? "variable" : "label";
addErrorMessage(name.line, name.column, "dscanner.suspicious.label_var_same_name",
thisKind ~ " \"" ~ name.text ~ "\" has the same name as a "
~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ ".");
}
}
static struct Thing
{
string name;
size_t line;
size_t column;
bool isVar;
}
ref currentScope() @property
{
return stack[$-1];
}
void pushScope()
{
stack.length++;
}
void popScope()
{
stack.length--;
}
}
unittest
{
import analysis.config : StaticAnalysisConfig;
import std.stdio : stderr;
StaticAnalysisConfig sac;
sac.label_var_same_name_check = true;
assertAnalyzerWarnings(q{
unittest
{
blah:
int blah; // [warn]: Variable "blah" has the same name as a label defined on line 4.
}
int blah;
}c, sac);
stderr.writeln("Unittest for LabelVarNameCheck passed.");
}

View File

@ -30,8 +30,8 @@ final class MismatchedArgumentCheck : BaseAnalyzer
auto identVisitor = scoped!IdentVisitor; auto identVisitor = scoped!IdentVisitor;
identVisitor.visit(fce.unaryExpression); identVisitor.visit(fce.unaryExpression);
DSymbol* sym = resolveSymbol(sc, identVisitor.names); const(DSymbol)* sym = resolveSymbol(sc, identVisitor.names);
const istring[] params = sym is null ? [] : sym.parts[].map!(a => a.name).array(); const istring[] params = sym is null ? [] : sym.opSlice().map!(a => cast() a.name).array();
const ArgMismatch[] mismatches = compareArgsToParams(params, args); const ArgMismatch[] mismatches = compareArgsToParams(params, args);
foreach(size_t i, ref const mm; mismatches) foreach(size_t i, ref const mm; mismatches)
addErrorMessage(argVisitor.lines[i], argVisitor.columns[i], KEY, addErrorMessage(argVisitor.lines[i], argVisitor.columns[i], KEY,
@ -113,15 +113,15 @@ final class ArgVisitor : ASTVisitor
istring[] args; istring[] args;
} }
DSymbol* resolveSymbol(const Scope* sc, const istring[] symbolChain) const(DSymbol)* resolveSymbol(const Scope* sc, const istring[] symbolChain)
{ {
import std.array:empty; import std.array:empty;
DSymbol*[] s = sc.getSymbolsByName(symbolChain[0]); const(DSymbol)*[] s = sc.getSymbolsByName(symbolChain[0]);
if (s.empty) if (s.empty)
return null; return null;
DSymbol* sym = s[0]; const(DSymbol)* sym = s[0];
foreach (i; 1 .. symbolChain.length) foreach (i; 1 .. symbolChain.length)
{ {
if (sym.kind == CompletionKind.variableName || sym.kind == CompletionKind.memberVariableName if (sym.kind == CompletionKind.variableName || sym.kind == CompletionKind.memberVariableName

View File

@ -14,9 +14,13 @@ import std.array;
import std.d.lexer; import std.d.lexer;
import std.d.parser; import std.d.parser;
import std.d.ast; import std.d.ast;
import std.allocator : CAllocatorImpl;
import std.typecons : scoped; import std.typecons : scoped;
import std.experimental.allocator : CAllocatorImpl;
import std.experimental.allocator.mallocator : Mallocator;
import std.experimental.allocator.building_blocks.region : Region;
import std.experimental.allocator.building_blocks.allocator_list : AllocatorList;
import analysis.config; import analysis.config;
import analysis.base; import analysis.base;
import analysis.style; import analysis.style;
@ -46,22 +50,29 @@ import analysis.unmodified;
import analysis.if_statements; import analysis.if_statements;
import analysis.redundant_parens; import analysis.redundant_parens;
import analysis.mismatched_args; import analysis.mismatched_args;
import analysis.label_var_same_name_check;
import memory.allocators:BlockAllocator; import dsymbol.string_interning : internString;
import dsymbol.scope_; import dsymbol.scope_;
import dsymbol.semantic;
import dsymbol.conversion; import dsymbol.conversion;
import dsymbol.conversion.first;
import dsymbol.conversion.second;
import dsymbol.modulecache : ModuleCache;
bool first = true; bool first = true;
void messageFunction(string fileName, size_t line, size_t column, string message, private alias ASTAllocator = CAllocatorImpl!(
bool isError) AllocatorList!(n => Region!Mallocator(1024 * 128), Mallocator));
void messageFunction(string fileName, size_t line, size_t column, string message, bool isError)
{ {
writefln("%s(%d:%d)[%s]: %s", fileName, line, column, writefln("%s(%d:%d)[%s]: %s", fileName, line, column, isError ? "error" : "warn",
isError ? "error" : "warn", message); message);
} }
void messageFunctionJSON(string fileName, size_t line, size_t column, string message, bool) void messageFunctionJSON(string fileName, size_t line, size_t column, string message,
bool)
{ {
writeJSON("dscanner.syntax", fileName, line, column, message); writeJSON("dscanner.syntax", fileName, line, column, message);
} }
@ -81,13 +92,14 @@ void writeJSON(string key, string fileName, size_t line, size_t column, string m
write(" }"); write(" }");
} }
bool syntaxCheck(string[] fileNames) bool syntaxCheck(string[] fileNames, ref StringCache stringCache, ref ModuleCache moduleCache)
{ {
StaticAnalysisConfig config = defaultStaticAnalysisConfig(); StaticAnalysisConfig config = defaultStaticAnalysisConfig();
return analyze(fileNames, config, false); return analyze(fileNames, config, stringCache, moduleCache, false);
} }
void generateReport(string[] fileNames, const StaticAnalysisConfig config) void generateReport(string[] fileNames, const StaticAnalysisConfig config,
ref StringCache cache, ref ModuleCache moduleCache)
{ {
writeln("{"); writeln("{");
writeln(` "issues": [`); writeln(` "issues": [`);
@ -97,14 +109,14 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config)
foreach (fileName; fileNames) foreach (fileName; fileNames)
{ {
File f = File(fileName); File f = File(fileName);
if (f.size == 0) continue; if (f.size == 0)
continue;
auto code = uninitializedArray!(ubyte[])(to!size_t(f.size)); auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
f.rawRead(code); f.rawRead(code);
ParseAllocator p = new ParseAllocator; ParseAllocator p = new ParseAllocator;
StringCache cache = StringCache(StringCache.defaultBucketCount);
const Module m = parseModule(fileName, code, p, cache, true, &lineOfCodeCount); const Module m = parseModule(fileName, code, p, cache, true, &lineOfCodeCount);
stats.visit(m); stats.visit(m);
MessageSet results = analyze(fileName, m, config, true); MessageSet results = analyze(fileName, m, config, moduleCache, true);
foreach (result; results[]) foreach (result; results[])
{ {
writeJSON(result.key, result.fileName, result.line, result.column, result.message); writeJSON(result.key, result.fileName, result.line, result.column, result.message);
@ -123,26 +135,31 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config)
writeln("}"); writeln("}");
} }
/// For multiple files /**
/// Returns: true if there were errors * For multiple files
bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticAnalyze = true) *
* Returns: true if there were errors or if there were warnings and `staticAnalyze` was true.
*/
bool analyze(string[] fileNames, const StaticAnalysisConfig config,
ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
{ {
bool hasErrors = false; bool hasErrors = false;
foreach (fileName; fileNames) foreach (fileName; fileNames)
{ {
File f = File(fileName); File f = File(fileName);
if (f.size == 0) continue; if (f.size == 0)
continue;
auto code = uninitializedArray!(ubyte[])(to!size_t(f.size)); auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
f.rawRead(code); f.rawRead(code);
ParseAllocator p = new ParseAllocator; ParseAllocator p = new ParseAllocator;
StringCache cache = StringCache(StringCache.defaultBucketCount);
uint errorCount = 0; uint errorCount = 0;
uint warningCount = 0;
const Module m = parseModule(fileName, code, p, cache, false, null, const Module m = parseModule(fileName, code, p, cache, false, null,
&errorCount, null); &errorCount, &warningCount);
assert(m); assert(m);
if (errorCount > 0) if (errorCount > 0 || (staticAnalyze && warningCount > 0))
hasErrors = true; hasErrors = true;
MessageSet results = analyze(fileName, m, config, staticAnalyze); MessageSet results = analyze(fileName, m, config, moduleCache, staticAnalyze);
if (results is null) if (results is null)
continue; continue;
foreach (result; results[]) foreach (result; results[])
@ -157,6 +174,7 @@ const(Module) parseModule(string fileName, ubyte[] code, ParseAllocator p,
uint* errorCount = null, uint* warningCount = null) uint* errorCount = null, uint* warningCount = null)
{ {
import stats : isLineOfCode; import stats : isLineOfCode;
LexerConfig config; LexerConfig config;
config.fileName = fileName; config.fileName = fileName;
config.stringBehavior = StringBehavior.source; config.stringBehavior = StringBehavior.source;
@ -164,47 +182,81 @@ const(Module) parseModule(string fileName, ubyte[] code, ParseAllocator p,
if (linesOfCode !is null) if (linesOfCode !is null)
(*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens); (*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens);
return std.d.parser.parseModule(tokens, fileName, p, return std.d.parser.parseModule(tokens, fileName, p,
report ? &messageFunctionJSON : &messageFunction, report ? &messageFunctionJSON : &messageFunction, errorCount, warningCount);
errorCount, warningCount);
} }
MessageSet analyze(string fileName, const Module m, MessageSet analyze(string fileName, const Module m,
const StaticAnalysisConfig analysisConfig, bool staticAnalyze = true) const StaticAnalysisConfig analysisConfig, ref ModuleCache moduleCache, bool staticAnalyze = true)
{ {
if (!staticAnalyze) if (!staticAnalyze)
return null; return null;
auto allocator = scoped!(CAllocatorImpl!(BlockAllocator!(1024 * 16)))(); auto symbolAllocator = new ASTAllocator;
const(Scope)* moduleScope = generateAutocompleteTrees(m, allocator); auto first = scoped!FirstPass(m, internString(fileName), symbolAllocator,
symbolAllocator, true, &moduleCache, null);
first.run();
secondPass(first.rootSymbol, first.moduleScope, moduleCache);
typeid(SemanticSymbol).destroy(first.rootSymbol);
const(Scope)* moduleScope = first.moduleScope;
BaseAnalyzer[] checks; BaseAnalyzer[] checks;
if (analysisConfig.style_check) checks ~= new StyleChecker(fileName, moduleScope); if (analysisConfig.asm_style_check)
if (analysisConfig.enum_array_literal_check) checks ~= new EnumArrayLiteralCheck(fileName, moduleScope); checks ~= new AsmStyleCheck(fileName, moduleScope);
if (analysisConfig.exception_check) checks ~= new PokemonExceptionCheck(fileName, moduleScope); if (analysisConfig.backwards_range_check)
if (analysisConfig.delete_check) checks ~= new DeleteCheck(fileName, moduleScope); checks ~= new BackwardsRangeCheck(fileName, moduleScope);
if (analysisConfig.float_operator_check) checks ~= new FloatOperatorCheck(fileName, moduleScope); if (analysisConfig.builtin_property_names_check)
if (analysisConfig.number_style_check) checks ~= new NumberStyleCheck(fileName, moduleScope); checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope);
if (analysisConfig.object_const_check) checks ~= new ObjectConstCheck(fileName, moduleScope); if (analysisConfig.comma_expression_check)
if (analysisConfig.backwards_range_check) checks ~= new BackwardsRangeCheck(fileName, moduleScope); checks ~= new CommaExpressionCheck(fileName, moduleScope);
if (analysisConfig.if_else_same_check) checks ~= new IfElseSameCheck(fileName, moduleScope); if (analysisConfig.constructor_check)
if (analysisConfig.constructor_check) checks ~= new ConstructorCheck(fileName, moduleScope); checks ~= new ConstructorCheck(fileName, moduleScope);
if (analysisConfig.unused_label_check) checks ~= new UnusedLabelCheck(fileName, moduleScope); if (analysisConfig.could_be_immutable_check)
if (analysisConfig.unused_variable_check) checks ~= new UnusedVariableCheck(fileName, moduleScope); checks ~= new UnmodifiedFinder(fileName, moduleScope);
if (analysisConfig.duplicate_attribute) checks ~= new DuplicateAttributeCheck(fileName, moduleScope); if (analysisConfig.delete_check)
if (analysisConfig.opequals_tohash_check) checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope); checks ~= new DeleteCheck(fileName, moduleScope);
if (analysisConfig.length_subtraction_check) checks ~= new LengthSubtractionCheck(fileName, moduleScope); if (analysisConfig.duplicate_attribute)
if (analysisConfig.builtin_property_names_check) checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope); checks ~= new DuplicateAttributeCheck(fileName, moduleScope);
if (analysisConfig.asm_style_check) checks ~= new AsmStyleCheck(fileName, moduleScope); if (analysisConfig.enum_array_literal_check)
if (analysisConfig.logical_precedence_check) checks ~= new LogicPrecedenceCheck(fileName, moduleScope); checks ~= new EnumArrayLiteralCheck(fileName, moduleScope);
if (analysisConfig.undocumented_declaration_check) checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope); if (analysisConfig.exception_check)
if (analysisConfig.function_attribute_check) checks ~= new FunctionAttributeCheck(fileName, moduleScope); checks ~= new PokemonExceptionCheck(fileName, moduleScope);
if (analysisConfig.comma_expression_check) checks ~= new CommaExpressionCheck(fileName, moduleScope); if (analysisConfig.float_operator_check)
if (analysisConfig.local_import_check) checks ~= new LocalImportCheck(fileName, moduleScope); checks ~= new FloatOperatorCheck(fileName, moduleScope);
if (analysisConfig.could_be_immutable_check) checks ~= new UnmodifiedFinder(fileName, moduleScope); if (analysisConfig.function_attribute_check)
if (analysisConfig.redundant_parens_check) checks ~= new RedundantParenCheck(fileName, moduleScope); checks ~= new FunctionAttributeCheck(fileName, moduleScope);
if (analysisConfig.mismatched_args_check) checks ~= new MismatchedArgumentCheck(fileName, moduleScope); if (analysisConfig.if_else_same_check)
version(none) if (analysisConfig.redundant_if_check) checks ~= new IfStatementCheck(fileName, moduleScope); checks ~= new IfElseSameCheck(fileName, moduleScope);
if (analysisConfig.label_var_same_name_check)
checks ~= new LabelVarNameCheck(fileName, moduleScope);
if (analysisConfig.length_subtraction_check)
checks ~= new LengthSubtractionCheck(fileName, moduleScope);
if (analysisConfig.local_import_check)
checks ~= new LocalImportCheck(fileName, moduleScope);
if (analysisConfig.logical_precedence_check)
checks ~= new LogicPrecedenceCheck(fileName, moduleScope);
if (analysisConfig.mismatched_args_check)
checks ~= new MismatchedArgumentCheck(fileName, moduleScope);
if (analysisConfig.number_style_check)
checks ~= new NumberStyleCheck(fileName, moduleScope);
if (analysisConfig.object_const_check)
checks ~= new ObjectConstCheck(fileName, moduleScope);
if (analysisConfig.opequals_tohash_check)
checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope);
if (analysisConfig.redundant_parens_check)
checks ~= new RedundantParenCheck(fileName, moduleScope);
if (analysisConfig.style_check)
checks ~= new StyleChecker(fileName, moduleScope);
if (analysisConfig.undocumented_declaration_check)
checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope);
if (analysisConfig.unused_label_check)
checks ~= new UnusedLabelCheck(fileName, moduleScope);
if (analysisConfig.unused_variable_check)
checks ~= new UnusedVariableCheck(fileName, moduleScope);
version (none)
if (analysisConfig.redundant_if_check)
checks ~= new IfStatementCheck(fileName, moduleScope);
foreach (check; checks) foreach (check; checks)
{ {
@ -217,4 +269,3 @@ MessageSet analyze(string fileName, const Module m,
set.insert(message); set.insert(message);
return set; return set;
} }

View File

@ -9,6 +9,8 @@ import analysis.base;
import dsymbol.scope_ : Scope; import dsymbol.scope_ : Scope;
import std.d.ast; import std.d.ast;
import std.d.lexer; import std.d.lexer;
import std.regex : ctRegex, matchAll;
import std.stdio; import std.stdio;
/** /**
@ -38,13 +40,19 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
if (isProtection(attr.attribute.type)) if (isProtection(attr.attribute.type))
set(attr.attribute.type); set(attr.attribute.type);
else if (attr.attribute == tok!"override") else if (attr.attribute == tok!"override")
{
setOverride(true); setOverride(true);
} else if (attr.deprecated_ !is null)
setDeprecated(true);
else if (attr.atAttribute !is null && attr.atAttribute.identifier.text == "disable")
setDisabled(true);
} }
immutable bool shouldPop = dec.attributeDeclaration is null; immutable bool shouldPop = dec.attributeDeclaration is null;
immutable bool prevOverride = getOverride(); immutable bool prevOverride = getOverride();
immutable bool prevDisabled = getDisabled();
immutable bool prevDeprecated = getDeprecated();
bool dis = false;
bool dep = false;
bool ovr = false; bool ovr = false;
bool pushed = false; bool pushed = false;
foreach (attribute; dec.attributes) foreach (attribute; dec.attributes)
@ -61,14 +69,26 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
} }
else if (attribute.attribute == tok!"override") else if (attribute.attribute == tok!"override")
ovr = true; ovr = true;
else if (attribute.deprecated_ !is null)
dep = true;
else if (attribute.atAttribute !is null && attribute.atAttribute.identifier.text == "disable")
dis = true;
} }
if (ovr) if (ovr)
setOverride(true); setOverride(true);
if (dis)
setDisabled(true);
if (dep)
setDeprecated(true);
dec.accept(this); dec.accept(this);
if (shouldPop && pushed) if (shouldPop && pushed)
pop(); pop();
if (ovr) if (ovr)
setOverride(prevOverride); setOverride(prevOverride);
if (dis)
setDisabled(prevDisabled);
if (dep)
setDeprecated(prevDeprecated);
} }
override void visit(const VariableDeclaration variable) override void visit(const VariableDeclaration variable)
@ -92,7 +112,7 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
override void visit(const ConditionalDeclaration cond) override void visit(const ConditionalDeclaration cond)
{ {
const VersionCondition ver = cond.compileCondition.versionCondition; const VersionCondition ver = cond.compileCondition.versionCondition;
if (ver is null || ver.token != tok!"unittest" && ver.token.text != "none") if (ver is null || (ver.token != tok!"unittest" && ver.token.text != "none"))
cond.accept(this); cond.accept(this);
else if (cond.falseDeclaration !is null) else if (cond.falseDeclaration !is null)
visit(cond.falseDeclaration); visit(cond.falseDeclaration);
@ -100,6 +120,7 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
override void visit(const FunctionBody fb) {} override void visit(const FunctionBody fb) {}
override void visit(const Unittest u) {} override void visit(const Unittest u) {}
override void visit(const TraitsExpression t) {}
mixin V!ClassDeclaration; mixin V!ClassDeclaration;
mixin V!InterfaceDeclaration; mixin V!InterfaceDeclaration;
@ -156,8 +177,7 @@ private:
static bool isGetterOrSetter(string name) static bool isGetterOrSetter(string name)
{ {
import std.algorithm:startsWith; return !matchAll(name, getSetRe).empty;
return name.startsWith("get") || name.startsWith("set");
} }
static bool isProperty(const FunctionDeclaration dec) static bool isProperty(const FunctionDeclaration dec)
@ -190,9 +210,30 @@ private:
stack[$ - 1].isOverride = o; stack[$ - 1].isOverride = o;
} }
bool getDisabled()
{
return stack[$ - 1].isDisabled;
}
void setDisabled(bool d = true)
{
stack[$ - 1].isDisabled = d;
}
bool getDeprecated()
{
return stack[$ - 1].isDeprecated;
}
void setDeprecated(bool d = true)
{
stack[$ - 1].isDeprecated = d;
}
bool currentIsInteresting() bool currentIsInteresting()
{ {
return stack[$ - 1].protection == tok!"public" && !(stack[$ - 1].isOverride); return stack[$ - 1].protection == tok!"public" && !stack[$ - 1].isOverride
&& !stack[$ - 1].isDisabled && !stack[$ - 1].isDeprecated;
} }
void set(IdType p) void set(IdType p)
@ -219,6 +260,8 @@ private:
{ {
IdType protection; IdType protection;
bool isOverride; bool isOverride;
bool isDeprecated;
bool isDisabled;
} }
ProtectionInfo[] stack; ProtectionInfo[] stack;
@ -232,3 +275,5 @@ private immutable string[] ignoredFunctionNames = [
"toHash", "toHash",
"main" "main"
]; ];
private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;

View File

@ -159,6 +159,16 @@ class UnmodifiedFinder:BaseAnalyzer
foreachStatement.declarationOrStatement.accept(this); foreachStatement.declarationOrStatement.accept(this);
} }
override void visit(const TraitsExpression)
{
// issue #266: Ignore unmodified variables inside of `__traits` expressions
}
override void visit(const TypeofExpression)
{
// issue #270: Ignore unmodified variables inside of `typeof` expressions
}
private: private:
template PartsMightModify(T) template PartsMightModify(T)

View File

@ -316,6 +316,16 @@ class UnusedVariableCheck : BaseAnalyzer
variableUsed(primary.identifierChain.identifiers[0].text); variableUsed(primary.identifierChain.identifiers[0].text);
} }
override void visit(const TraitsExpression)
{
// issue #266: Ignore unused variables inside of `__traits` expressions
}
override void visit(const TypeofExpression)
{
// issue #270: Ignore unused variables inside of `typeof` expressions
}
private: private:
mixin template PartsUseVariables(NodeType) mixin template PartsUseVariables(NodeType)
@ -333,13 +343,11 @@ private:
{ {
if (inAggregateScope) if (inAggregateScope)
return; return;
// stderr.writeln("Adding ", name, " ", isParameter, " ", isRef);
tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef)); tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef));
} }
void variableUsed(string name) void variableUsed(string name)
{ {
// writeln("Marking ", name, " used");
size_t treeIndex = tree.length - 1; size_t treeIndex = tree.length - 1;
auto uu = UnUsed(name); auto uu = UnUsed(name);
while (true) while (true)

View File

@ -4,7 +4,6 @@
module analysis.unused_label; module analysis.unused_label;
import std.stdio;
import std.d.ast; import std.d.ast;
import std.d.lexer; import std.d.lexer;
import analysis.base; import analysis.base;
@ -20,39 +19,11 @@ class UnusedLabelCheck : BaseAnalyzer
super(fileName, sc); super(fileName, sc);
} }
static struct Label override void visit(const Module mod)
{ {
string name; pushScope();
size_t line; mod.accept(this);
size_t column; popScope();
bool used;
}
Label[string][] stack;
auto ref current() @property
{
return stack[$-1];
}
void pushScope()
{
stack.length++;
}
void popScope()
{
foreach (label; current.byValue())
{
assert(label.line != size_t.max && label.column != size_t.max);
if (!label.used)
{
addErrorMessage(label.line, label.column,
"dscanner.suspicious.unused_label",
"Label \"" ~ label.name ~ "\" is not used.");
}
}
stack.length--;
} }
override void visit(const FunctionBody functionBody) override void visit(const FunctionBody functionBody)
@ -100,15 +71,6 @@ class UnusedLabelCheck : BaseAnalyzer
labeledStatement.declarationOrStatement.accept(this); labeledStatement.declarationOrStatement.accept(this);
} }
void labelUsed(string name)
{
Label* entry = name in current;
if (entry is null)
current[name] = Label(name, size_t.max, size_t.max, true);
else
entry.used = true;
}
override void visit(const ContinueStatement contStatement) override void visit(const ContinueStatement contStatement)
{ {
if (contStatement.label.text.length) if (contStatement.label.text.length)
@ -124,11 +86,58 @@ class UnusedLabelCheck : BaseAnalyzer
if (gotoStatement.label.text.length) if (gotoStatement.label.text.length)
labelUsed(gotoStatement.label.text); labelUsed(gotoStatement.label.text);
} }
private:
static struct Label
{
string name;
size_t line;
size_t column;
bool used;
}
Label[string][] stack;
auto ref current() @property
{
return stack[$-1];
}
void pushScope()
{
stack.length++;
}
void popScope()
{
foreach (label; current.byValue())
{
assert(label.line != size_t.max && label.column != size_t.max);
if (!label.used)
{
addErrorMessage(label.line, label.column,
"dscanner.suspicious.unused_label",
"Label \"" ~ label.name ~ "\" is not used.");
}
}
stack.length--;
}
void labelUsed(string name)
{
Label* entry = name in current;
if (entry is null)
current[name] = Label(name, size_t.max, size_t.max, true);
else
entry.used = true;
}
} }
unittest unittest
{ {
import analysis.config : StaticAnalysisConfig; import analysis.config : StaticAnalysisConfig;
import std.stdio : stderr;
StaticAnalysisConfig sac; StaticAnalysisConfig sac;
sac.unused_label_check = true; sac.unused_label_check = true;

View File

@ -13,11 +13,13 @@ import std.range;
import std.stdio; import std.stdio;
import std.array; import std.array;
import std.conv; import std.conv;
import std.typecons;
/** /**
* Prints CTAGS information to the given file. * Prints CTAGS information to the given file.
* Includes metadata according to exuberant format used by Vim.
* Params: * Params:
* outpt = the file that CTAGS info is written to * output = the file that Exuberant TAGS info is written to
* fileNames = tags will be generated from these files * fileNames = tags will be generated from these files
*/ */
void printCtags(File output, string[] fileNames) void printCtags(File output, string[] fileNames)
@ -28,101 +30,141 @@ void printCtags(File output, string[] fileNames)
foreach (fileName; fileNames) foreach (fileName; fileNames)
{ {
File f = File(fileName); File f = File(fileName);
if (f.size == 0) continue; if (f.size == 0)
continue;
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, null, &doNothing); Module m = parseModule(tokens.array, fileName, null, &doNothing);
auto printer = new CTagsPrinter; auto printer = new CTagsPrinter;
printer.fileName = fileName; printer.fileName = fileName;
printer.visit(m); printer.visit(m);
tags ~= printer.tagLines; tags ~= printer.tagLines;
} }
output.write("!_TAG_FILE_FORMAT\t2\n" output.write(
~ "!_TAG_FILE_SORTED\t1\n" "!_TAG_FILE_FORMAT\t2\n" ~ "!_TAG_FILE_SORTED\t1\n" ~ "!_TAG_FILE_AUTHOR\tBrian Schott\n" ~ "!_TAG_PROGRAM_URL\thttps://github.com/Hackerpilot/Dscanner/\n");
~ "!_TAG_FILE_AUTHOR\tBrian Schott\n"
~ "!_TAG_PROGRAM_URL\thttps://github.com/Hackerpilot/Dscanner/\n");
tags.sort().copy(output.lockingTextWriter); tags.sort().copy(output.lockingTextWriter);
} }
private: private:
void doNothing(string, size_t, size_t, string, bool) {} /// States determining how an access modifier affects tags when traversing the
/// AST.
/// The assumption is that there are fewer AST nodes and patterns that affects
/// the whole scope.
/// Therefor the default was chosen to be Reset.
enum AccessState
{
Reset, /// when ascending the AST reset back to the previous access.
Keep /// when ascending the AST keep the new access.
}
class CTagsPrinter : ASTVisitor alias ContextType = Tuple!(string, "c", string, "access");
void doNothing(string, size_t, size_t, string, bool)
{
}
string paramsToString(Dec)(const Dec dec)
{
import std.d.formatter : Formatter;
auto app = appender!string();
auto formatter = new Formatter!(typeof(app))(app);
static if (is(Dec == FunctionDeclaration) || is(Dec == Constructor))
{
formatter.format(dec.parameters);
}
else static if (is(Dec == TemplateDeclaration))
{
formatter.format(dec.templateParameters);
}
return app.data;
}
final class CTagsPrinter
: ASTVisitor
{ {
override void visit(const ClassDeclaration dec) override void visit(const ClassDeclaration dec)
{ {
tagLines ~= "%s\t%s\t%d;\"\tc%s\n".format(dec.name.text, fileName, dec.name.line, context); tagLines ~= "%s\t%s\t%d;\"\tc\tline:%d%s%s\n".format(dec.name.text,
fileName, dec.name.line, dec.name.line, context.c, context.access);
auto c = context; auto c = context;
context = "\tclass:" ~ dec.name.text; context = ContextType("\tclass:" ~ dec.name.text, "\taccess:public");
dec.accept(this); dec.accept(this);
context = c; context = c;
} }
override void visit(const StructDeclaration dec) override void visit(const StructDeclaration dec)
{ {
tagLines ~= "%s\t%s\t%d;\"\ts%s\n".format(dec.name.text, fileName, dec.name.line, context); if (dec.name == tok!"")
{
dec.accept(this);
return;
}
tagLines ~= "%s\t%s\t%d;\"\ts\tline:%d%s%s\n".format(dec.name.text,
fileName, dec.name.line, dec.name.line, context.c, context.access);
auto c = context; auto c = context;
context = "\tstruct:" ~ dec.name.text; context = ContextType("\tstruct:" ~ dec.name.text, "\taccess:public");
dec.accept(this); dec.accept(this);
context = c; context = c;
} }
override void visit(const InterfaceDeclaration dec) override void visit(const InterfaceDeclaration dec)
{ {
tagLines ~= "%s\t%s\t%d;\"\ti%s\n".format(dec.name.text, fileName, dec.name.line, context); tagLines ~= "%s\t%s\t%d;\"\ti\tline:%d%s%s\n".format(dec.name.text,
fileName, dec.name.line, dec.name.line, context.c, context.access);
auto c = context; auto c = context;
context = "\tclass:" ~ dec.name.text; context = ContextType("\tclass:" ~ dec.name.text, context.access);
dec.accept(this); dec.accept(this);
context = c; context = c;
} }
override void visit(const TemplateDeclaration dec) override void visit(const TemplateDeclaration dec)
{ {
tagLines ~= "%s\t%s\t%d;\"\tT%s\n".format(dec.name.text, fileName, dec.name.line, context); auto params = paramsToString(dec);
tagLines ~= "%s\t%s\t%d;\"\tT\tline:%d%s%s\tsignature:%s\n".format(
dec.name.text, fileName, dec.name.line, dec.name.line, context.c,
context.access, params);
auto c = context; auto c = context;
context = "\ttemplate:" ~ dec.name.text; context = ContextType("\ttemplate:" ~ dec.name.text, context.access);
dec.accept(this); dec.accept(this);
context = c; context = c;
} }
override void visit(const FunctionDeclaration dec) override void visit(const FunctionDeclaration dec)
{ {
tagLines ~= "%s\t%s\t%d;\"\tf\tarity:%d%s\n".format(dec.name.text, fileName, auto params = paramsToString(dec);
dec.name.line, dec.parameters.parameters.length, context);
auto c = context; tagLines ~= "%s\t%s\t%d;\"\tf\tline:%d%s%s\tsignature:%s\n".format(
context = "\tfunction:" ~ dec.name.text; dec.name.text, fileName, dec.name.line, dec.name.line, context.c,
dec.accept(this); context.access, params);
context = c;
} }
override void visit(const Constructor dec) override void visit(const Constructor dec)
{ {
tagLines ~= "this\t%s\t%d;\"\tf\tarity:%d%s\n".format(fileName, auto params = paramsToString(dec);
dec.line, dec.parameters.parameters.length, context);
auto c = context; tagLines ~= "this\t%s\t%d;\"\tf\tline:%d%s\tsignature:%s%s\n".format(fileName,
context = "\tfunction: this"; dec.line, dec.line, context.c, params, context.access);
dec.accept(this);
context = c;
} }
override void visit(const Destructor dec) override void visit(const Destructor dec)
{ {
tagLines ~= "~this\t%s\t%d;\"\tf%s\n".format(fileName, dec.line, tagLines ~= "~this\t%s\t%d;\"\tf\tline:%d%s\n".format(fileName,
context); dec.line, dec.line, context.c);
auto c = context;
context = "\tfunction: this";
dec.accept(this);
context = c;
} }
override void visit(const EnumDeclaration dec) override void visit(const EnumDeclaration dec)
{ {
tagLines ~= "%s\t%s\t%d;\"\tg%s\n".format(dec.name.text, fileName, tagLines ~= "%s\t%s\t%d;\"\tg\tline:%d%s%s\n".format(dec.name.text,
dec.name.line, context); fileName, dec.name.line, dec.name.line, context.c, context.access);
auto c = context; auto c = context;
context = "\tenum:" ~ dec.name.text; context = ContextType("\tenum:" ~ dec.name.text, context.access);
dec.accept(this); dec.accept(this);
context = c; context = c;
} }
@ -134,32 +176,32 @@ class CTagsPrinter : ASTVisitor
dec.accept(this); dec.accept(this);
return; return;
} }
tagLines ~= "%s\t%s\t%d;\"\tu%s\n".format(dec.name.text, fileName, tagLines ~= "%s\t%s\t%d;\"\tu\tline:%d%s\n".format(dec.name.text,
dec.name.line, context); fileName, dec.name.line, dec.name.line, context.c);
auto c = context; auto c = context;
context = "\tunion:" ~ dec.name.text; context = ContextType("\tunion:" ~ dec.name.text, context.access);
dec.accept(this); dec.accept(this);
context = c; context = c;
} }
override void visit(const AnonymousEnumMember mem) override void visit(const AnonymousEnumMember mem)
{ {
tagLines ~= "%s\t%s\t%d;\"\te%s\n".format(mem.name.text, fileName, tagLines ~= "%s\t%s\t%d;\"\te\tline:%d%s\n".format(mem.name.text,
mem.name.line, context); fileName, mem.name.line, mem.name.line, context.c);
} }
override void visit(const EnumMember mem) override void visit(const EnumMember mem)
{ {
tagLines ~= "%s\t%s\t%d;\"\te%s\n".format(mem.name.text, fileName, tagLines ~= "%s\t%s\t%d;\"\te\tline:%d%s\n".format(mem.name.text,
mem.name.line, context); fileName, mem.name.line, mem.name.line, context.c);
} }
override void visit(const VariableDeclaration dec) override void visit(const VariableDeclaration dec)
{ {
foreach (d; dec.declarators) foreach (d; dec.declarators)
{ {
tagLines ~= "%s\t%s\t%d;\"\tv%s\n".format(d.name.text, fileName, tagLines ~= "%s\t%s\t%d;\"\tv\tline:%d%s%s\n".format(d.name.text,
d.name.line, context); fileName, d.name.line, d.name.line, context.c, context.access);
} }
dec.accept(this); dec.accept(this);
} }
@ -168,22 +210,117 @@ class CTagsPrinter : ASTVisitor
{ {
foreach (i; dec.identifiers) foreach (i; dec.identifiers)
{ {
tagLines ~= "%s\t%s\t%d;\"\tv%s\n".format(i.text, fileName, tagLines ~= "%s\t%s\t%d;\"\tv\tline:%d%s%s\n".format(i.text,
i.line, context); fileName, i.line, i.line, context.c, context.access);
} }
dec.accept(this); dec.accept(this);
} }
override void visit(const Invariant dec) override void visit(const Invariant dec)
{ {
tagLines ~= "invariant\t%s\t%d;\"\tv%s\n".format(fileName, dec.line, context); tagLines ~= "invariant\t%s\t%d;\"\tv\tline:%d%s%s\n".format(fileName,
dec.line, dec.line, context.c, context.access);
}
override void visit(const ModuleDeclaration dec)
{
context = ContextType("", "\taccess:public");
dec.accept(this);
}
override void visit(const Attribute attribute)
{
if (attribute.attribute != tok!"")
{
switch (attribute.attribute.type)
{
case tok!"export":
context.access = "\taccess:public";
break;
case tok!"public":
context.access = "\taccess:public";
break;
case tok!"package":
context.access = "\taccess:protected";
break;
case tok!"protected":
context.access = "\taccess:protected";
break;
case tok!"private":
context.access = "\taccess:private";
break;
default:
}
}
attribute.accept(this);
}
override void visit(const AttributeDeclaration dec)
{
accessSt = AccessState.Keep;
dec.accept(this);
}
override void visit(const Declaration dec)
{
auto c = context;
dec.accept(this);
final switch (accessSt) with (AccessState)
{
case Reset:
context = c;
break;
case Keep:
break;
}
accessSt = AccessState.Reset;
}
override void visit(const Unittest dec)
{
// skipping symbols inside a unit test to not clutter the ctags file
// with "temporary" symbols.
// TODO when phobos have a unittest library investigate how that could
// be used to describe the tests.
// Maybe with UDA's to give the unittest a "name".
}
override void visit(const AliasDeclaration dec)
{
// Old style alias
if (dec.identifierList)
{
foreach (i; dec.identifierList.identifiers)
{
tagLines ~= "%s\t%s\t%d;\"\ta\tline:%d%s%s\n".format(i.text,
fileName, i.line, i.line, context.c, context.access);
}
}
dec.accept(this);
}
override void visit(const AliasInitializer dec)
{
tagLines ~= "%s\t%s\t%d;\"\ta\tline:%d%s%s\n".format(dec.name.text,
fileName, dec.name.line, dec.name.line, context.c, context.access);
dec.accept(this);
}
override void visit(const AliasThisDeclaration dec)
{
auto name = dec.identifier;
tagLines ~= "%s\t%s\t%d;\"\ta\tline:%d%s%s\n".format(name.text,
fileName, name.line, name.line, context.c, context.access);
dec.accept(this);
} }
alias visit = ASTVisitor.visit; alias visit = ASTVisitor.visit;
string fileName; string fileName;
string[] tagLines; string[] tagLines;
int suppressDepth; ContextType context;
string context; AccessState accessSt;
} }

View File

@ -8,7 +8,7 @@ module dscanner_version;
/** /**
* Human-readable version number * Human-readable version number
*/ */
enum DSCANNER_VERSION = "v0.2.0-dev"; enum DSCANNER_VERSION = "v0.2.0-beta1";
version (Windows) {} version (Windows) {}
else else

View File

@ -1,4 +1,4 @@
// Copyright Brian Schott (Hackerpilot) 2012-2015. // Copyright Brian Schott (Hackerpilot) 2012.
// Distributed under the Boost Software License, Version 1.0. // Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at // (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt) // http://www.boost.org/LICENSE_1_0.txt)
@ -33,19 +33,10 @@ import inifiled;
import dsymbol.modulecache; import dsymbol.modulecache;
int main(string[] args)
{
version (unittest) version (unittest)
{ void main() {}
return 0;
}
else else
{ int main(string[] args)
return run(args);
}
}
int run(string[] args)
{ {
bool sloc; bool sloc;
bool highlight; bool highlight;
@ -65,9 +56,9 @@ int run(string[] args)
bool report; bool report;
string symbolName; string symbolName;
string configLocation; string configLocation;
string[] importPaths;
bool printVersion; bool printVersion;
bool explore; bool explore;
string[] importPaths;
try try
{ {
@ -127,9 +118,10 @@ int run(string[] args)
const(string[]) absImportPaths = importPaths.map!( const(string[]) absImportPaths = importPaths.map!(
a => a.absolutePath().buildNormalizedPath()).array(); a => a.absolutePath().buildNormalizedPath()).array();
ModuleCache.includeParameterSymbols = true; ModuleCache moduleCache;
if (absImportPaths.length) if (absImportPaths.length)
ModuleCache.addImportPaths(absImportPaths); moduleCache.addImportPaths(absImportPaths);
immutable optionCount = count!"a"([sloc, highlight, ctags, tokenCount, immutable optionCount = count!"a"([sloc, highlight, ctags, tokenCount,
syntaxCheck, ast, imports, outline, tokenDump, styleCheck, defaultConfig, syntaxCheck, ast, imports, outline, tokenDump, styleCheck, defaultConfig,
@ -199,13 +191,13 @@ int run(string[] args)
if (s.exists()) if (s.exists())
readINIFile(config, s); readINIFile(config, s);
if (report) if (report)
generateReport(expandArgs(args), config); generateReport(expandArgs(args), config, cache, moduleCache);
else else
analyze(expandArgs(args), config, true); return analyze(expandArgs(args), config, cache, moduleCache, true) ? 1 : 0;
} }
else if (syntaxCheck) else if (syntaxCheck)
{ {
return .syntaxCheck(expandArgs(args)); return .syntaxCheck(expandArgs(args), cache, moduleCache) ? 1 : 0;
} }
else else
{ {
@ -241,11 +233,11 @@ int run(string[] args)
} }
else if (imports) else if (imports)
{ {
const string[] fileNames = usingStdin ? ["stdin"] : args[1 .. $]; string[] fileNames = usingStdin ? ["stdin"] : args[1 .. $];
LexerConfig config; LexerConfig config;
config.stringBehavior = StringBehavior.source; config.stringBehavior = StringBehavior.source;
auto visitor = new ImportPrinter; auto visitor = new ImportPrinter;
foreach (name; fileNames) foreach (name; expandArgs(fileNames))
{ {
config.fileName = name; config.fileName = name;
auto tokens = getTokensForParser( auto tokens = getTokensForParser(
@ -284,6 +276,7 @@ int run(string[] args)
return 0; return 0;
} }
string[] expandArgs(string[] args) string[] expandArgs(string[] args)
{ {
// isFile can throw if it's a broken symlink. // isFile can throw if it's a broken symlink.
@ -377,11 +370,13 @@ options:
--syntaxCheck | -s [sourceFile] --syntaxCheck | -s [sourceFile]
Lexes and parses sourceFile, printing the line and column number of any Lexes and parses sourceFile, printing the line and column number of any
syntax errors to stdout. One error or warning is printed per line. syntax errors to stdout. One error or warning is printed per line.
If no files are specified, input is read from stdin. If no files are specified, input is read from stdin. %1$s will exit with
a status code of zero if no errors are found, 1 otherwise.
--styleCheck | -S [sourceFiles] --styleCheck | -S [sourceFiles]
Lexes and parses sourceFiles, printing the line and column number of any Lexes and parses sourceFiles, printing the line and column number of any
static analysis check failures stdout. static analysis check failures stdout. %1$s will exit with a status code
of zero if no warnings or errors are found, 1 otherwise.
--ctags | -c sourceFile --ctags | -c sourceFile
Generates ctags information from the given source code file. Note that Generates ctags information from the given source code file. Note that
@ -406,7 +401,9 @@ options:
current working directory if none are specified. current working directory if none are specified.
--report [sourceFiles sourceDirectories] --report [sourceFiles sourceDirectories]
Generate a static analysis report in JSON format. Implies --styleCheck. Generate a static analysis report in JSON format. Implies --styleCheck,
however the exit code will still be zero if errors or warnings are
found.
--config configFile --config configFile
Use the given configuration file instead of the default located in Use the given configuration file instead of the default located in