Merge branch 'master' of https://github.com/Hackerpilot/Dscanner
This commit is contained in:
commit
ba70f0e9dd
|
@ -1 +1 @@
|
||||||
Subproject commit 32f6d638e38888e1bb11cf43e93fe2d11132a98f
|
Subproject commit bd7c1c2dbb08bf160c4b646e0aede2af1ef6e0e4
|
|
@ -92,4 +92,7 @@ struct StaticAnalysisConfig
|
||||||
|
|
||||||
@INI("Checks for redundant parenthesis")
|
@INI("Checks for redundant parenthesis")
|
||||||
bool redundant_parens_check;
|
bool redundant_parens_check;
|
||||||
|
|
||||||
|
@INI("Checks for labels with the same name as variables")
|
||||||
|
bool label_var_same_name_check;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
{
|
||||||
|
super(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}
|
|
@ -43,6 +43,7 @@ import analysis.local_imports;
|
||||||
import analysis.unmodified;
|
import analysis.unmodified;
|
||||||
import analysis.if_statements;
|
import analysis.if_statements;
|
||||||
import analysis.redundant_parens;
|
import analysis.redundant_parens;
|
||||||
|
import analysis.label_var_same_name_check;
|
||||||
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
||||||
|
@ -115,8 +116,11 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config)
|
||||||
writeln("}");
|
writeln("}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For multiple files
|
/**
|
||||||
/// Returns: true if there were errors
|
* For multiple files
|
||||||
|
*
|
||||||
|
* Returns: true if there were errors or if there were warnings and `staticAnalyze` was true.
|
||||||
|
*/
|
||||||
bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticAnalyze = true)
|
bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticAnalyze = true)
|
||||||
{
|
{
|
||||||
bool hasErrors = false;
|
bool hasErrors = false;
|
||||||
|
@ -129,10 +133,11 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticA
|
||||||
ParseAllocator p = new ParseAllocator;
|
ParseAllocator p = new ParseAllocator;
|
||||||
StringCache cache = StringCache(StringCache.defaultBucketCount);
|
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, staticAnalyze);
|
||||||
if (results is null)
|
if (results is null)
|
||||||
|
@ -192,6 +197,7 @@ MessageSet analyze(string fileName, const Module m,
|
||||||
if (analysisConfig.local_import_check) checks ~= new LocalImportCheck(fileName);
|
if (analysisConfig.local_import_check) checks ~= new LocalImportCheck(fileName);
|
||||||
if (analysisConfig.could_be_immutable_check) checks ~= new UnmodifiedFinder(fileName);
|
if (analysisConfig.could_be_immutable_check) checks ~= new UnmodifiedFinder(fileName);
|
||||||
if (analysisConfig.redundant_parens_check) checks ~= new RedundantParenCheck(fileName);
|
if (analysisConfig.redundant_parens_check) checks ~= new RedundantParenCheck(fileName);
|
||||||
|
if (analysisConfig.label_var_same_name_check) checks ~= new LabelVarNameCheck(fileName);
|
||||||
version(none) if (analysisConfig.redundant_if_check) checks ~= new IfStatementCheck(fileName);
|
version(none) if (analysisConfig.redundant_if_check) checks ~= new IfStatementCheck(fileName);
|
||||||
|
|
||||||
foreach (check; checks)
|
foreach (check; checks)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import std.d.ast;
|
||||||
import std.d.lexer;
|
import std.d.lexer;
|
||||||
import analysis.base;
|
import analysis.base;
|
||||||
|
|
||||||
|
import std.regex : ctRegex, matchAll;
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,13 +39,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 +68,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 +111,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 +119,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 +176,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 +209,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 +259,8 @@ private:
|
||||||
{
|
{
|
||||||
IdType protection;
|
IdType protection;
|
||||||
bool isOverride;
|
bool isOverride;
|
||||||
|
bool isDeprecated;
|
||||||
|
bool isDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProtectionInfo[] stack;
|
ProtectionInfo[] stack;
|
||||||
|
@ -232,3 +274,5 @@ private immutable string[] ignoredFunctionNames = [
|
||||||
"toHash",
|
"toHash",
|
||||||
"main"
|
"main"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;
|
||||||
|
|
|
@ -153,6 +153,11 @@ class UnmodifiedFinder:BaseAnalyzer
|
||||||
foreachStatement.declarationOrStatement.accept(this);
|
foreachStatement.declarationOrStatement.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override void visit(const TraitsExpression)
|
||||||
|
{
|
||||||
|
// Issue #266. Ignore everything inside of __traits expressions.
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
template PartsMightModify(T)
|
template PartsMightModify(T)
|
||||||
|
|
|
@ -277,7 +277,6 @@ class UnusedVariableCheck : BaseAnalyzer
|
||||||
import std.array : array;
|
import std.array : array;
|
||||||
if (parameter.name != tok!"")
|
if (parameter.name != tok!"")
|
||||||
{
|
{
|
||||||
// stderr.writeln("Adding parameter ", parameter.name.text);
|
|
||||||
immutable bool isRef = canFind(parameter.parameterAttributes, cast(IdType) tok!"ref")
|
immutable bool isRef = canFind(parameter.parameterAttributes, cast(IdType) tok!"ref")
|
||||||
|| canFind(parameter.parameterAttributes, cast(IdType) tok!"in")
|
|| canFind(parameter.parameterAttributes, cast(IdType) tok!"in")
|
||||||
|| canFind(parameter.parameterAttributes, cast(IdType) tok!"out");
|
|| canFind(parameter.parameterAttributes, cast(IdType) tok!"out");
|
||||||
|
@ -317,6 +316,11 @@ class UnusedVariableCheck : BaseAnalyzer
|
||||||
variableUsed(primary.identifierChain.identifiers[0].text);
|
variableUsed(primary.identifierChain.identifiers[0].text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override void visit(const TraitsExpression)
|
||||||
|
{
|
||||||
|
// Issue #266. Ignore everything inside of __traits expressions.
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
mixin template PartsUseVariables(NodeType)
|
mixin template PartsUseVariables(NodeType)
|
||||||
|
@ -334,13 +338,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)
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -19,39 +18,11 @@ class UnusedLabelCheck : BaseAnalyzer
|
||||||
super(fileName);
|
super(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
@ -99,15 +70,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)
|
||||||
|
@ -123,11 +85,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;
|
||||||
|
|
64
src/ctags.d
64
src/ctags.d
|
@ -53,8 +53,8 @@ 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\n".format(dec.name.text, fileName, dec.name.line, dec.name.line, context);
|
||||||
auto c = context;
|
const c = context;
|
||||||
context = "\tclass:" ~ dec.name.text;
|
context = "\tclass:" ~ dec.name.text;
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
context = c;
|
context = c;
|
||||||
|
@ -62,8 +62,8 @@ class CTagsPrinter : ASTVisitor
|
||||||
|
|
||||||
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);
|
tagLines ~= "%s\t%s\t%d;\"\ts\tline:%d%s\n".format(dec.name.text, fileName, dec.name.line, dec.name.line, context);
|
||||||
auto c = context;
|
const c = context;
|
||||||
context = "\tstruct:" ~ dec.name.text;
|
context = "\tstruct:" ~ dec.name.text;
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
context = c;
|
context = c;
|
||||||
|
@ -71,8 +71,8 @@ class CTagsPrinter : ASTVisitor
|
||||||
|
|
||||||
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\n".format(dec.name.text, fileName, dec.name.line, dec.name.line, context);
|
||||||
auto c = context;
|
const c = context;
|
||||||
context = "\tclass:" ~ dec.name.text;
|
context = "\tclass:" ~ dec.name.text;
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
context = c;
|
context = c;
|
||||||
|
@ -80,8 +80,8 @@ class CTagsPrinter : ASTVisitor
|
||||||
|
|
||||||
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);
|
tagLines ~= "%s\t%s\t%d;\"\tT\tline:%d%s\n".format(dec.name.text, fileName, dec.name.line, dec.name.line, context);
|
||||||
auto c = context;
|
const c = context;
|
||||||
context = "\ttemplate:" ~ dec.name.text;
|
context = "\ttemplate:" ~ dec.name.text;
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
context = c;
|
context = c;
|
||||||
|
@ -89,9 +89,9 @@ class CTagsPrinter : ASTVisitor
|
||||||
|
|
||||||
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,
|
tagLines ~= "%s\t%s\t%d;\"\tf\tarity:%d\tline:%d%s\n".format(dec.name.text, fileName,
|
||||||
dec.name.line, dec.parameters.parameters.length, context);
|
dec.name.line, dec.parameters.parameters.length, dec.name.line, context);
|
||||||
auto c = context;
|
const c = context;
|
||||||
context = "\tfunction:" ~ dec.name.text;
|
context = "\tfunction:" ~ dec.name.text;
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
context = c;
|
context = c;
|
||||||
|
@ -99,9 +99,9 @@ class CTagsPrinter : ASTVisitor
|
||||||
|
|
||||||
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,
|
tagLines ~= "this\t%s\t%d;\"\tf\tarity:%d\tline:%d%s\n".format(fileName,
|
||||||
dec.line, dec.parameters.parameters.length, context);
|
dec.line, dec.parameters.parameters.length, dec.line, context);
|
||||||
auto c = context;
|
const c = context;
|
||||||
context = "\tfunction: this";
|
context = "\tfunction: this";
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
context = c;
|
context = c;
|
||||||
|
@ -109,9 +109,9 @@ class CTagsPrinter : ASTVisitor
|
||||||
|
|
||||||
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, dec.line,
|
||||||
context);
|
dec.line, context);
|
||||||
auto c = context;
|
const c = context;
|
||||||
context = "\tfunction: this";
|
context = "\tfunction: this";
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
context = c;
|
context = c;
|
||||||
|
@ -119,9 +119,9 @@ class CTagsPrinter : ASTVisitor
|
||||||
|
|
||||||
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\n".format(dec.name.text, fileName,
|
||||||
dec.name.line, context);
|
dec.name.line, dec.name.line, context);
|
||||||
auto c = context;
|
const c = context;
|
||||||
context = "\tenum:" ~ dec.name.text;
|
context = "\tenum:" ~ dec.name.text;
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
context = c;
|
context = c;
|
||||||
|
@ -134,9 +134,9 @@ 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, fileName,
|
||||||
dec.name.line, context);
|
dec.name.line, dec.name.line, context);
|
||||||
auto c = context;
|
const c = context;
|
||||||
context = "\tunion:" ~ dec.name.text;
|
context = "\tunion:" ~ dec.name.text;
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
context = c;
|
context = c;
|
||||||
|
@ -144,22 +144,22 @@ class CTagsPrinter : ASTVisitor
|
||||||
|
|
||||||
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, fileName,
|
||||||
mem.name.line, context);
|
mem.name.line, mem.name.line, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
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, fileName,
|
||||||
mem.name.line, context);
|
mem.name.line, mem.name.line, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
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\n".format(d.name.text, fileName,
|
||||||
d.name.line, context);
|
d.name.line, d.name.line, context);
|
||||||
}
|
}
|
||||||
dec.accept(this);
|
dec.accept(this);
|
||||||
}
|
}
|
||||||
|
@ -168,15 +168,15 @@ 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\n".format(i.text, fileName,
|
||||||
i.line, context);
|
i.line, i.line, context);
|
||||||
}
|
}
|
||||||
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\n".format(fileName, dec.line, dec.line, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
alias visit = ASTVisitor.visit;
|
alias visit = ASTVisitor.visit;
|
||||||
|
|
|
@ -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
|
||||||
|
|
24
src/main.d
24
src/main.d
|
@ -34,17 +34,8 @@ import inifiled;
|
||||||
int main(string[] args)
|
int main(string[] args)
|
||||||
{
|
{
|
||||||
version (unittest)
|
version (unittest)
|
||||||
{
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return run(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int run(string[] args)
|
|
||||||
{
|
|
||||||
bool sloc;
|
bool sloc;
|
||||||
bool highlight;
|
bool highlight;
|
||||||
bool ctags;
|
bool ctags;
|
||||||
|
@ -191,11 +182,11 @@ int run(string[] args)
|
||||||
if (report)
|
if (report)
|
||||||
generateReport(expandArgs(args), config);
|
generateReport(expandArgs(args), config);
|
||||||
else
|
else
|
||||||
analyze(expandArgs(args), config, true);
|
return analyze(expandArgs(args), config, true) ? 1 : 0;
|
||||||
}
|
}
|
||||||
else if (syntaxCheck)
|
else if (syntaxCheck)
|
||||||
{
|
{
|
||||||
return .syntaxCheck(expandArgs(args));
|
return .syntaxCheck(expandArgs(args)) ? 1 : 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -274,6 +265,7 @@ int run(string[] args)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
string[] expandArgs(string[] args)
|
string[] expandArgs(string[] args)
|
||||||
{
|
{
|
||||||
|
@ -368,11 +360,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
|
||||||
|
@ -397,7 +391,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
|
||||||
|
|
Loading…
Reference in New Issue