Merge master and update dependencies
This commit is contained in:
commit
380064de6a
|
@ -25,6 +25,7 @@ dscanner-report.json
|
|||
*.o
|
||||
*.obj
|
||||
*.exe
|
||||
obj
|
||||
|
||||
#debug build
|
||||
dsc
|
||||
|
|
|
@ -79,12 +79,13 @@ you do not want to use the one created by the "--defaultConfig" option.
|
|||
* Confusing asm syntax.
|
||||
* Placement of const, immutable, or inout before a function return type instead of after the parameters.
|
||||
* 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
|
||||
* Assigning to foreach variables that are not "ref".
|
||||
* Unused imports.
|
||||
* Variables that are never modified and not declared immutable.
|
||||
* Assignment in conditionals
|
||||
https://github.com/Hackerpilot/Dscanner/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement
|
||||
|
||||
### Reports
|
||||
The "--report" option writes a JSON report on the static analysis checks
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit d732a67e76f60fd037547c3ffe8776c6deda6bab
|
||||
Subproject commit 05b1f9f5906c4ac1f38964c7456482b3f11daa32
|
2
dsymbol
2
dsymbol
|
@ -1 +1 @@
|
|||
Subproject commit 393da9696f81d0692f8022e2d5d40dc662d984f9
|
||||
Subproject commit 1c5579db6c1d88a097d46f5bee5ae1e2cf8fff02
|
|
@ -1 +1 @@
|
|||
Subproject commit 0dccfca0e2a132b3c862a62da1c323ccd24e622d
|
||||
Subproject commit 071e0cee083a0392d9226fdfba593dd86c21d412
|
46
makefile
46
makefile
|
@ -1,27 +1,26 @@
|
|||
.PHONY: all test
|
||||
|
||||
DMD = dmd
|
||||
GDC = gdc
|
||||
LDC = ldc2
|
||||
SRC = src/*.d\
|
||||
src/analysis/*.d\
|
||||
libdparse/src/std/*.d\
|
||||
libdparse/src/std/d/*.d\
|
||||
inifiled/source/*.d\
|
||||
DC ?= dmd
|
||||
DMD := $(DC)
|
||||
GDC := gdc
|
||||
LDC := ldc2
|
||||
OBJ_DIR := obj
|
||||
SRC := \
|
||||
$(shell find containers/experimental_allocator/src -name "*.d")\
|
||||
$(shell find containers/src -name "*.d")\
|
||||
$(shell find dsymbol/src -name "*.d")\
|
||||
containers/src/containers/ttree.d\
|
||||
containers/src/containers/unrolledlist.d\
|
||||
containers/src/containers/hashset.d\
|
||||
containers/src/containers/internal/hash.d\
|
||||
containers/src/containers/internal/node.d\
|
||||
containers/src/containers/internal/storage_type.d\
|
||||
containers/src/memory/allocators.d\
|
||||
containers/src/memory/appender.d
|
||||
INCLUDE_PATHS = -Ilibdparse/src -Idsymbol/src -Icontainers/src
|
||||
$(shell find inifiled/source/ -name "*.d")\
|
||||
$(shell find libdparse/src/std/ -name "*.d")\
|
||||
$(shell find src/ -name "*.d")
|
||||
INCLUDE_PATHS = \
|
||||
-Iinifiled/source -Isrc\
|
||||
-Ilibdparse/src\
|
||||
-Ilibdparse/experimental_allocator/src\
|
||||
-Idsymbol/src -Icontainers/src
|
||||
VERSIONS =
|
||||
DEBUG_VERSIONS = -version=std_parser_verbose
|
||||
#DMD_FLAGS = -w -O -release -inline
|
||||
DMD_FLAGS = -w
|
||||
DMD_FLAGS = -w -O -inline -J. -od${OBJ_DIR}
|
||||
DMD_TEST_FLAGS = -w -g -unittest -J.
|
||||
|
||||
all: dmdbuild
|
||||
ldc: ldcbuild
|
||||
|
@ -31,11 +30,11 @@ githash:
|
|||
git log -1 --format="%H" > githash.txt
|
||||
|
||||
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
|
||||
${DMD} ${DMD_FLAGS} -ofbin/dscanner ${VERSIONS} ${INCLUDE_PATHS} ${SRC} -J.
|
||||
${DC} ${DMD_FLAGS} -ofbin/dscanner ${VERSIONS} ${INCLUDE_PATHS} ${SRC}
|
||||
rm -f bin/dscanner.o
|
||||
|
||||
gdcbuild: githash
|
||||
|
@ -50,8 +49,9 @@ test:
|
|||
@./test.sh
|
||||
|
||||
clean:
|
||||
rm -rf dsc *.o
|
||||
rm -rf dsc
|
||||
rm -rf bin
|
||||
rm -rf ${OBJ_DIR}
|
||||
rm -f dscanner-report.json
|
||||
|
||||
report: all
|
||||
|
|
|
@ -95,4 +95,7 @@ struct StaticAnalysisConfig
|
|||
|
||||
@INI("Checks for mismatched argument and parameter names")
|
||||
bool mismatched_args_check;
|
||||
|
||||
@INI("Checks for labels with the same name as variables")
|
||||
bool label_var_same_name_check;
|
||||
}
|
||||
|
|
|
@ -59,8 +59,10 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, stri
|
|||
ParseAllocator p = new ParseAllocator;
|
||||
const(Module) m = parseModule(file, cast(ubyte[]) code, p, cache, false);
|
||||
|
||||
ModuleCache moduleCache;
|
||||
|
||||
// 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");
|
||||
|
||||
// Get the warnings ordered by line
|
||||
|
|
|
@ -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.");
|
||||
}
|
|
@ -30,8 +30,8 @@ final class MismatchedArgumentCheck : BaseAnalyzer
|
|||
auto identVisitor = scoped!IdentVisitor;
|
||||
identVisitor.visit(fce.unaryExpression);
|
||||
|
||||
DSymbol* sym = resolveSymbol(sc, identVisitor.names);
|
||||
const istring[] params = sym is null ? [] : sym.parts[].map!(a => a.name).array();
|
||||
const(DSymbol)* sym = resolveSymbol(sc, identVisitor.names);
|
||||
const istring[] params = sym is null ? [] : sym.opSlice().map!(a => cast() a.name).array();
|
||||
const ArgMismatch[] mismatches = compareArgsToParams(params, args);
|
||||
foreach(size_t i, ref const mm; mismatches)
|
||||
addErrorMessage(argVisitor.lines[i], argVisitor.columns[i], KEY,
|
||||
|
@ -113,15 +113,15 @@ final class ArgVisitor : ASTVisitor
|
|||
istring[] args;
|
||||
}
|
||||
|
||||
DSymbol* resolveSymbol(const Scope* sc, const istring[] symbolChain)
|
||||
const(DSymbol)* resolveSymbol(const Scope* sc, const istring[] symbolChain)
|
||||
{
|
||||
import std.array:empty;
|
||||
|
||||
DSymbol*[] s = sc.getSymbolsByName(symbolChain[0]);
|
||||
const(DSymbol)*[] s = sc.getSymbolsByName(symbolChain[0]);
|
||||
if (s.empty)
|
||||
return null;
|
||||
|
||||
DSymbol* sym = s[0];
|
||||
const(DSymbol)* sym = s[0];
|
||||
foreach (i; 1 .. symbolChain.length)
|
||||
{
|
||||
if (sym.kind == CompletionKind.variableName || sym.kind == CompletionKind.memberVariableName
|
||||
|
|
|
@ -14,8 +14,12 @@ import std.array;
|
|||
import std.d.lexer;
|
||||
import std.d.parser;
|
||||
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.base;
|
||||
|
@ -46,22 +50,29 @@ import analysis.unmodified;
|
|||
import analysis.if_statements;
|
||||
import analysis.redundant_parens;
|
||||
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.semantic;
|
||||
import dsymbol.conversion;
|
||||
import dsymbol.conversion.first;
|
||||
import dsymbol.conversion.second;
|
||||
import dsymbol.modulecache : ModuleCache;
|
||||
|
||||
bool first = true;
|
||||
|
||||
void messageFunction(string fileName, size_t line, size_t column, string message,
|
||||
bool isError)
|
||||
private alias ASTAllocator = CAllocatorImpl!(
|
||||
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,
|
||||
isError ? "error" : "warn", message);
|
||||
writefln("%s(%d:%d)[%s]: %s", fileName, line, column, isError ? "error" : "warn",
|
||||
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);
|
||||
}
|
||||
|
@ -78,16 +89,17 @@ void writeJSON(string key, string fileName, size_t line, size_t column, string m
|
|||
writeln(` "line": `, line, `,`);
|
||||
writeln(` "column": `, column, `,`);
|
||||
writeln(` "message": "`, message.replace(`"`, `\"`), `"`);
|
||||
write( " }");
|
||||
write(" }");
|
||||
}
|
||||
|
||||
bool syntaxCheck(string[] fileNames)
|
||||
bool syntaxCheck(string[] fileNames, ref StringCache stringCache, ref ModuleCache moduleCache)
|
||||
{
|
||||
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(` "issues": [`);
|
||||
|
@ -97,14 +109,14 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config)
|
|||
foreach (fileName; fileNames)
|
||||
{
|
||||
File f = File(fileName);
|
||||
if (f.size == 0) continue;
|
||||
if (f.size == 0)
|
||||
continue;
|
||||
auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
|
||||
f.rawRead(code);
|
||||
ParseAllocator p = new ParseAllocator;
|
||||
StringCache cache = StringCache(StringCache.defaultBucketCount);
|
||||
const Module m = parseModule(fileName, code, p, cache, true, &lineOfCodeCount);
|
||||
stats.visit(m);
|
||||
MessageSet results = analyze(fileName, m, config, true);
|
||||
MessageSet results = analyze(fileName, m, config, moduleCache, true);
|
||||
foreach (result; results[])
|
||||
{
|
||||
writeJSON(result.key, result.fileName, result.line, result.column, result.message);
|
||||
|
@ -123,26 +135,31 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config)
|
|||
writeln("}");
|
||||
}
|
||||
|
||||
/// For multiple files
|
||||
/// Returns: true if there were errors
|
||||
bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticAnalyze = true)
|
||||
/**
|
||||
* 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,
|
||||
ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
|
||||
{
|
||||
bool hasErrors = false;
|
||||
foreach (fileName; fileNames)
|
||||
{
|
||||
File f = File(fileName);
|
||||
if (f.size == 0) continue;
|
||||
if (f.size == 0)
|
||||
continue;
|
||||
auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
|
||||
f.rawRead(code);
|
||||
ParseAllocator p = new ParseAllocator;
|
||||
StringCache cache = StringCache(StringCache.defaultBucketCount);
|
||||
uint errorCount = 0;
|
||||
uint warningCount = 0;
|
||||
const Module m = parseModule(fileName, code, p, cache, false, null,
|
||||
&errorCount, null);
|
||||
assert (m);
|
||||
if (errorCount > 0)
|
||||
&errorCount, &warningCount);
|
||||
assert(m);
|
||||
if (errorCount > 0 || (staticAnalyze && warningCount > 0))
|
||||
hasErrors = true;
|
||||
MessageSet results = analyze(fileName, m, config, staticAnalyze);
|
||||
MessageSet results = analyze(fileName, m, config, moduleCache, staticAnalyze);
|
||||
if (results is null)
|
||||
continue;
|
||||
foreach (result; results[])
|
||||
|
@ -157,6 +174,7 @@ const(Module) parseModule(string fileName, ubyte[] code, ParseAllocator p,
|
|||
uint* errorCount = null, uint* warningCount = null)
|
||||
{
|
||||
import stats : isLineOfCode;
|
||||
|
||||
LexerConfig config;
|
||||
config.fileName = fileName;
|
||||
config.stringBehavior = StringBehavior.source;
|
||||
|
@ -164,47 +182,81 @@ const(Module) parseModule(string fileName, ubyte[] code, ParseAllocator p,
|
|||
if (linesOfCode !is null)
|
||||
(*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens);
|
||||
return std.d.parser.parseModule(tokens, fileName, p,
|
||||
report ? &messageFunctionJSON : &messageFunction,
|
||||
errorCount, warningCount);
|
||||
report ? &messageFunctionJSON : &messageFunction, errorCount, warningCount);
|
||||
}
|
||||
|
||||
MessageSet analyze(string fileName, const Module m,
|
||||
const StaticAnalysisConfig analysisConfig, bool staticAnalyze = true)
|
||||
const StaticAnalysisConfig analysisConfig, ref ModuleCache moduleCache, bool staticAnalyze = true)
|
||||
{
|
||||
if (!staticAnalyze)
|
||||
return null;
|
||||
|
||||
auto allocator = scoped!(CAllocatorImpl!(BlockAllocator!(1024 * 16)))();
|
||||
const(Scope)* moduleScope = generateAutocompleteTrees(m, allocator);
|
||||
auto symbolAllocator = new ASTAllocator;
|
||||
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;
|
||||
|
||||
if (analysisConfig.style_check) checks ~= new StyleChecker(fileName, moduleScope);
|
||||
if (analysisConfig.enum_array_literal_check) checks ~= new EnumArrayLiteralCheck(fileName, moduleScope);
|
||||
if (analysisConfig.exception_check) checks ~= new PokemonExceptionCheck(fileName, moduleScope);
|
||||
if (analysisConfig.delete_check) checks ~= new DeleteCheck(fileName, moduleScope);
|
||||
if (analysisConfig.float_operator_check) checks ~= new FloatOperatorCheck(fileName, moduleScope);
|
||||
if (analysisConfig.number_style_check) checks ~= new NumberStyleCheck(fileName, moduleScope);
|
||||
if (analysisConfig.object_const_check) checks ~= new ObjectConstCheck(fileName, moduleScope);
|
||||
if (analysisConfig.backwards_range_check) checks ~= new BackwardsRangeCheck(fileName, moduleScope);
|
||||
if (analysisConfig.if_else_same_check) checks ~= new IfElseSameCheck(fileName, moduleScope);
|
||||
if (analysisConfig.constructor_check) checks ~= new ConstructorCheck(fileName, moduleScope);
|
||||
if (analysisConfig.unused_label_check) checks ~= new UnusedLabelCheck(fileName, moduleScope);
|
||||
if (analysisConfig.unused_variable_check) checks ~= new UnusedVariableCheck(fileName, moduleScope);
|
||||
if (analysisConfig.duplicate_attribute) checks ~= new DuplicateAttributeCheck(fileName, moduleScope);
|
||||
if (analysisConfig.opequals_tohash_check) checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope);
|
||||
if (analysisConfig.length_subtraction_check) checks ~= new LengthSubtractionCheck(fileName, moduleScope);
|
||||
if (analysisConfig.builtin_property_names_check) checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope);
|
||||
if (analysisConfig.asm_style_check) checks ~= new AsmStyleCheck(fileName, moduleScope);
|
||||
if (analysisConfig.logical_precedence_check) checks ~= new LogicPrecedenceCheck(fileName, moduleScope);
|
||||
if (analysisConfig.undocumented_declaration_check) checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope);
|
||||
if (analysisConfig.function_attribute_check) checks ~= new FunctionAttributeCheck(fileName, moduleScope);
|
||||
if (analysisConfig.comma_expression_check) checks ~= new CommaExpressionCheck(fileName, moduleScope);
|
||||
if (analysisConfig.local_import_check) checks ~= new LocalImportCheck(fileName, moduleScope);
|
||||
if (analysisConfig.could_be_immutable_check) checks ~= new UnmodifiedFinder(fileName, moduleScope);
|
||||
if (analysisConfig.redundant_parens_check) checks ~= new RedundantParenCheck(fileName, moduleScope);
|
||||
if (analysisConfig.mismatched_args_check) checks ~= new MismatchedArgumentCheck(fileName, moduleScope);
|
||||
version(none) if (analysisConfig.redundant_if_check) checks ~= new IfStatementCheck(fileName, moduleScope);
|
||||
if (analysisConfig.asm_style_check)
|
||||
checks ~= new AsmStyleCheck(fileName, moduleScope);
|
||||
if (analysisConfig.backwards_range_check)
|
||||
checks ~= new BackwardsRangeCheck(fileName, moduleScope);
|
||||
if (analysisConfig.builtin_property_names_check)
|
||||
checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope);
|
||||
if (analysisConfig.comma_expression_check)
|
||||
checks ~= new CommaExpressionCheck(fileName, moduleScope);
|
||||
if (analysisConfig.constructor_check)
|
||||
checks ~= new ConstructorCheck(fileName, moduleScope);
|
||||
if (analysisConfig.could_be_immutable_check)
|
||||
checks ~= new UnmodifiedFinder(fileName, moduleScope);
|
||||
if (analysisConfig.delete_check)
|
||||
checks ~= new DeleteCheck(fileName, moduleScope);
|
||||
if (analysisConfig.duplicate_attribute)
|
||||
checks ~= new DuplicateAttributeCheck(fileName, moduleScope);
|
||||
if (analysisConfig.enum_array_literal_check)
|
||||
checks ~= new EnumArrayLiteralCheck(fileName, moduleScope);
|
||||
if (analysisConfig.exception_check)
|
||||
checks ~= new PokemonExceptionCheck(fileName, moduleScope);
|
||||
if (analysisConfig.float_operator_check)
|
||||
checks ~= new FloatOperatorCheck(fileName, moduleScope);
|
||||
if (analysisConfig.function_attribute_check)
|
||||
checks ~= new FunctionAttributeCheck(fileName, moduleScope);
|
||||
if (analysisConfig.if_else_same_check)
|
||||
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)
|
||||
{
|
||||
|
@ -217,4 +269,3 @@ MessageSet analyze(string fileName, const Module m,
|
|||
set.insert(message);
|
||||
return set;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ import analysis.base;
|
|||
import dsymbol.scope_ : Scope;
|
||||
import std.d.ast;
|
||||
import std.d.lexer;
|
||||
|
||||
import std.regex : ctRegex, matchAll;
|
||||
import std.stdio;
|
||||
|
||||
/**
|
||||
|
@ -38,13 +40,19 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
|
|||
if (isProtection(attr.attribute.type))
|
||||
set(attr.attribute.type);
|
||||
else if (attr.attribute == tok!"override")
|
||||
{
|
||||
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 prevOverride = getOverride();
|
||||
immutable bool prevDisabled = getDisabled();
|
||||
immutable bool prevDeprecated = getDeprecated();
|
||||
bool dis = false;
|
||||
bool dep = false;
|
||||
bool ovr = false;
|
||||
bool pushed = false;
|
||||
foreach (attribute; dec.attributes)
|
||||
|
@ -61,14 +69,26 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
|
|||
}
|
||||
else if (attribute.attribute == tok!"override")
|
||||
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)
|
||||
setOverride(true);
|
||||
if (dis)
|
||||
setDisabled(true);
|
||||
if (dep)
|
||||
setDeprecated(true);
|
||||
dec.accept(this);
|
||||
if (shouldPop && pushed)
|
||||
pop();
|
||||
if (ovr)
|
||||
setOverride(prevOverride);
|
||||
if (dis)
|
||||
setDisabled(prevDisabled);
|
||||
if (dep)
|
||||
setDeprecated(prevDeprecated);
|
||||
}
|
||||
|
||||
override void visit(const VariableDeclaration variable)
|
||||
|
@ -92,7 +112,7 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
|
|||
override void visit(const ConditionalDeclaration cond)
|
||||
{
|
||||
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);
|
||||
else if (cond.falseDeclaration !is null)
|
||||
visit(cond.falseDeclaration);
|
||||
|
@ -100,6 +120,7 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
|
|||
|
||||
override void visit(const FunctionBody fb) {}
|
||||
override void visit(const Unittest u) {}
|
||||
override void visit(const TraitsExpression t) {}
|
||||
|
||||
mixin V!ClassDeclaration;
|
||||
mixin V!InterfaceDeclaration;
|
||||
|
@ -156,8 +177,7 @@ private:
|
|||
|
||||
static bool isGetterOrSetter(string name)
|
||||
{
|
||||
import std.algorithm:startsWith;
|
||||
return name.startsWith("get") || name.startsWith("set");
|
||||
return !matchAll(name, getSetRe).empty;
|
||||
}
|
||||
|
||||
static bool isProperty(const FunctionDeclaration dec)
|
||||
|
@ -190,9 +210,30 @@ private:
|
|||
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()
|
||||
{
|
||||
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)
|
||||
|
@ -219,6 +260,8 @@ private:
|
|||
{
|
||||
IdType protection;
|
||||
bool isOverride;
|
||||
bool isDeprecated;
|
||||
bool isDisabled;
|
||||
}
|
||||
|
||||
ProtectionInfo[] stack;
|
||||
|
@ -232,3 +275,5 @@ private immutable string[] ignoredFunctionNames = [
|
|||
"toHash",
|
||||
"main"
|
||||
];
|
||||
|
||||
private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;
|
||||
|
|
|
@ -159,6 +159,16 @@ class UnmodifiedFinder:BaseAnalyzer
|
|||
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:
|
||||
|
||||
template PartsMightModify(T)
|
||||
|
|
|
@ -316,6 +316,16 @@ class UnusedVariableCheck : BaseAnalyzer
|
|||
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:
|
||||
|
||||
mixin template PartsUseVariables(NodeType)
|
||||
|
@ -333,13 +343,11 @@ private:
|
|||
{
|
||||
if (inAggregateScope)
|
||||
return;
|
||||
// stderr.writeln("Adding ", name, " ", isParameter, " ", isRef);
|
||||
tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef));
|
||||
}
|
||||
|
||||
void variableUsed(string name)
|
||||
{
|
||||
// writeln("Marking ", name, " used");
|
||||
size_t treeIndex = tree.length - 1;
|
||||
auto uu = UnUsed(name);
|
||||
while (true)
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
module analysis.unused_label;
|
||||
|
||||
import std.stdio;
|
||||
import std.d.ast;
|
||||
import std.d.lexer;
|
||||
import analysis.base;
|
||||
|
@ -20,39 +19,11 @@ class UnusedLabelCheck : BaseAnalyzer
|
|||
super(fileName, sc);
|
||||
}
|
||||
|
||||
static struct Label
|
||||
override void visit(const Module mod)
|
||||
{
|
||||
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--;
|
||||
pushScope();
|
||||
mod.accept(this);
|
||||
popScope();
|
||||
}
|
||||
|
||||
override void visit(const FunctionBody functionBody)
|
||||
|
@ -100,15 +71,6 @@ class UnusedLabelCheck : BaseAnalyzer
|
|||
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)
|
||||
{
|
||||
if (contStatement.label.text.length)
|
||||
|
@ -124,11 +86,58 @@ class UnusedLabelCheck : BaseAnalyzer
|
|||
if (gotoStatement.label.text.length)
|
||||
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
|
||||
{
|
||||
import analysis.config : StaticAnalysisConfig;
|
||||
import std.stdio : stderr;
|
||||
|
||||
StaticAnalysisConfig sac;
|
||||
sac.unused_label_check = true;
|
||||
|
|
243
src/ctags.d
243
src/ctags.d
|
@ -13,11 +13,13 @@ import std.range;
|
|||
import std.stdio;
|
||||
import std.array;
|
||||
import std.conv;
|
||||
import std.typecons;
|
||||
|
||||
/**
|
||||
* Prints CTAGS information to the given file.
|
||||
* Includes metadata according to exuberant format used by Vim.
|
||||
* 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
|
||||
*/
|
||||
void printCtags(File output, string[] fileNames)
|
||||
|
@ -28,101 +30,141 @@ void printCtags(File output, string[] fileNames)
|
|||
foreach (fileName; fileNames)
|
||||
{
|
||||
File f = File(fileName);
|
||||
if (f.size == 0) continue;
|
||||
if (f.size == 0)
|
||||
continue;
|
||||
auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size));
|
||||
f.rawRead(bytes);
|
||||
auto tokens = getTokensForParser(bytes, config, &cache);
|
||||
Module m = parseModule(tokens.array, fileName, null, &doNothing);
|
||||
|
||||
auto printer = new CTagsPrinter;
|
||||
printer.fileName = fileName;
|
||||
printer.visit(m);
|
||||
tags ~= printer.tagLines;
|
||||
}
|
||||
output.write("!_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");
|
||||
output.write(
|
||||
"!_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");
|
||||
tags.sort().copy(output.lockingTextWriter);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
context = "\tclass:" ~ dec.name.text;
|
||||
context = ContextType("\tclass:" ~ dec.name.text, "\taccess:public");
|
||||
dec.accept(this);
|
||||
context = c;
|
||||
}
|
||||
|
||||
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;
|
||||
context = "\tstruct:" ~ dec.name.text;
|
||||
context = ContextType("\tstruct:" ~ dec.name.text, "\taccess:public");
|
||||
dec.accept(this);
|
||||
context = c;
|
||||
}
|
||||
|
||||
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;
|
||||
context = "\tclass:" ~ dec.name.text;
|
||||
context = ContextType("\tclass:" ~ dec.name.text, context.access);
|
||||
dec.accept(this);
|
||||
context = c;
|
||||
}
|
||||
|
||||
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;
|
||||
context = "\ttemplate:" ~ dec.name.text;
|
||||
context = ContextType("\ttemplate:" ~ dec.name.text, context.access);
|
||||
dec.accept(this);
|
||||
context = c;
|
||||
}
|
||||
|
||||
override void visit(const FunctionDeclaration dec)
|
||||
{
|
||||
tagLines ~= "%s\t%s\t%d;\"\tf\tarity:%d%s\n".format(dec.name.text, fileName,
|
||||
dec.name.line, dec.parameters.parameters.length, context);
|
||||
auto c = context;
|
||||
context = "\tfunction:" ~ dec.name.text;
|
||||
dec.accept(this);
|
||||
context = c;
|
||||
auto params = paramsToString(dec);
|
||||
|
||||
tagLines ~= "%s\t%s\t%d;\"\tf\tline:%d%s%s\tsignature:%s\n".format(
|
||||
dec.name.text, fileName, dec.name.line, dec.name.line, context.c,
|
||||
context.access, params);
|
||||
}
|
||||
|
||||
override void visit(const Constructor dec)
|
||||
{
|
||||
tagLines ~= "this\t%s\t%d;\"\tf\tarity:%d%s\n".format(fileName,
|
||||
dec.line, dec.parameters.parameters.length, context);
|
||||
auto c = context;
|
||||
context = "\tfunction: this";
|
||||
dec.accept(this);
|
||||
context = c;
|
||||
auto params = paramsToString(dec);
|
||||
|
||||
tagLines ~= "this\t%s\t%d;\"\tf\tline:%d%s\tsignature:%s%s\n".format(fileName,
|
||||
dec.line, dec.line, context.c, params, context.access);
|
||||
}
|
||||
|
||||
override void visit(const Destructor dec)
|
||||
{
|
||||
tagLines ~= "~this\t%s\t%d;\"\tf%s\n".format(fileName, dec.line,
|
||||
context);
|
||||
auto c = context;
|
||||
context = "\tfunction: this";
|
||||
dec.accept(this);
|
||||
context = c;
|
||||
tagLines ~= "~this\t%s\t%d;\"\tf\tline:%d%s\n".format(fileName,
|
||||
dec.line, dec.line, context.c);
|
||||
}
|
||||
|
||||
override void visit(const EnumDeclaration dec)
|
||||
{
|
||||
tagLines ~= "%s\t%s\t%d;\"\tg%s\n".format(dec.name.text, fileName,
|
||||
dec.name.line, context);
|
||||
tagLines ~= "%s\t%s\t%d;\"\tg\tline:%d%s%s\n".format(dec.name.text,
|
||||
fileName, dec.name.line, dec.name.line, context.c, context.access);
|
||||
auto c = context;
|
||||
context = "\tenum:" ~ dec.name.text;
|
||||
context = ContextType("\tenum:" ~ dec.name.text, context.access);
|
||||
dec.accept(this);
|
||||
context = c;
|
||||
}
|
||||
|
@ -134,32 +176,32 @@ class CTagsPrinter : ASTVisitor
|
|||
dec.accept(this);
|
||||
return;
|
||||
}
|
||||
tagLines ~= "%s\t%s\t%d;\"\tu%s\n".format(dec.name.text, fileName,
|
||||
dec.name.line, context);
|
||||
tagLines ~= "%s\t%s\t%d;\"\tu\tline:%d%s\n".format(dec.name.text,
|
||||
fileName, dec.name.line, dec.name.line, context.c);
|
||||
auto c = context;
|
||||
context = "\tunion:" ~ dec.name.text;
|
||||
context = ContextType("\tunion:" ~ dec.name.text, context.access);
|
||||
dec.accept(this);
|
||||
context = c;
|
||||
}
|
||||
|
||||
override void visit(const AnonymousEnumMember mem)
|
||||
{
|
||||
tagLines ~= "%s\t%s\t%d;\"\te%s\n".format(mem.name.text, fileName,
|
||||
mem.name.line, context);
|
||||
tagLines ~= "%s\t%s\t%d;\"\te\tline:%d%s\n".format(mem.name.text,
|
||||
fileName, mem.name.line, mem.name.line, context.c);
|
||||
}
|
||||
|
||||
override void visit(const EnumMember mem)
|
||||
{
|
||||
tagLines ~= "%s\t%s\t%d;\"\te%s\n".format(mem.name.text, fileName,
|
||||
mem.name.line, context);
|
||||
tagLines ~= "%s\t%s\t%d;\"\te\tline:%d%s\n".format(mem.name.text,
|
||||
fileName, mem.name.line, mem.name.line, context.c);
|
||||
}
|
||||
|
||||
override void visit(const VariableDeclaration dec)
|
||||
{
|
||||
foreach (d; dec.declarators)
|
||||
{
|
||||
tagLines ~= "%s\t%s\t%d;\"\tv%s\n".format(d.name.text, fileName,
|
||||
d.name.line, context);
|
||||
tagLines ~= "%s\t%s\t%d;\"\tv\tline:%d%s%s\n".format(d.name.text,
|
||||
fileName, d.name.line, d.name.line, context.c, context.access);
|
||||
}
|
||||
dec.accept(this);
|
||||
}
|
||||
|
@ -168,22 +210,117 @@ class CTagsPrinter : ASTVisitor
|
|||
{
|
||||
foreach (i; dec.identifiers)
|
||||
{
|
||||
tagLines ~= "%s\t%s\t%d;\"\tv%s\n".format(i.text, fileName,
|
||||
i.line, context);
|
||||
tagLines ~= "%s\t%s\t%d;\"\tv\tline:%d%s%s\n".format(i.text,
|
||||
fileName, i.line, i.line, context.c, context.access);
|
||||
}
|
||||
dec.accept(this);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
string fileName;
|
||||
string[] tagLines;
|
||||
int suppressDepth;
|
||||
string context;
|
||||
ContextType context;
|
||||
AccessState accessSt;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ module dscanner_version;
|
|||
/**
|
||||
* Human-readable version number
|
||||
*/
|
||||
enum DSCANNER_VERSION = "v0.2.0-dev";
|
||||
enum DSCANNER_VERSION = "v0.2.0-beta1";
|
||||
|
||||
version (Windows) {}
|
||||
else
|
||||
|
|
45
src/main.d
45
src/main.d
|
@ -1,4 +1,4 @@
|
|||
// Copyright Brian Schott (Hackerpilot) 2012-2015.
|
||||
// Copyright Brian Schott (Hackerpilot) 2012.
|
||||
// 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)
|
||||
|
@ -33,19 +33,10 @@ import inifiled;
|
|||
|
||||
import dsymbol.modulecache;
|
||||
|
||||
version (unittest)
|
||||
void main() {}
|
||||
else
|
||||
int main(string[] args)
|
||||
{
|
||||
version (unittest)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return run(args);
|
||||
}
|
||||
}
|
||||
|
||||
int run(string[] args)
|
||||
{
|
||||
bool sloc;
|
||||
bool highlight;
|
||||
|
@ -65,9 +56,9 @@ int run(string[] args)
|
|||
bool report;
|
||||
string symbolName;
|
||||
string configLocation;
|
||||
string[] importPaths;
|
||||
bool printVersion;
|
||||
bool explore;
|
||||
string[] importPaths;
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -127,9 +118,10 @@ int run(string[] args)
|
|||
const(string[]) absImportPaths = importPaths.map!(
|
||||
a => a.absolutePath().buildNormalizedPath()).array();
|
||||
|
||||
ModuleCache.includeParameterSymbols = true;
|
||||
ModuleCache moduleCache;
|
||||
|
||||
if (absImportPaths.length)
|
||||
ModuleCache.addImportPaths(absImportPaths);
|
||||
moduleCache.addImportPaths(absImportPaths);
|
||||
|
||||
immutable optionCount = count!"a"([sloc, highlight, ctags, tokenCount,
|
||||
syntaxCheck, ast, imports, outline, tokenDump, styleCheck, defaultConfig,
|
||||
|
@ -199,13 +191,13 @@ int run(string[] args)
|
|||
if (s.exists())
|
||||
readINIFile(config, s);
|
||||
if (report)
|
||||
generateReport(expandArgs(args), config);
|
||||
generateReport(expandArgs(args), config, cache, moduleCache);
|
||||
else
|
||||
analyze(expandArgs(args), config, true);
|
||||
return analyze(expandArgs(args), config, cache, moduleCache, true) ? 1 : 0;
|
||||
}
|
||||
else if (syntaxCheck)
|
||||
{
|
||||
return .syntaxCheck(expandArgs(args));
|
||||
return .syntaxCheck(expandArgs(args), cache, moduleCache) ? 1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -241,11 +233,11 @@ int run(string[] args)
|
|||
}
|
||||
else if (imports)
|
||||
{
|
||||
const string[] fileNames = usingStdin ? ["stdin"] : args[1 .. $];
|
||||
string[] fileNames = usingStdin ? ["stdin"] : args[1 .. $];
|
||||
LexerConfig config;
|
||||
config.stringBehavior = StringBehavior.source;
|
||||
auto visitor = new ImportPrinter;
|
||||
foreach (name; fileNames)
|
||||
foreach (name; expandArgs(fileNames))
|
||||
{
|
||||
config.fileName = name;
|
||||
auto tokens = getTokensForParser(
|
||||
|
@ -284,6 +276,7 @@ int run(string[] args)
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
string[] expandArgs(string[] args)
|
||||
{
|
||||
// isFile can throw if it's a broken symlink.
|
||||
|
@ -377,11 +370,13 @@ options:
|
|||
--syntaxCheck | -s [sourceFile]
|
||||
Lexes and parses sourceFile, printing the line and column number of any
|
||||
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]
|
||||
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
|
||||
Generates ctags information from the given source code file. Note that
|
||||
|
@ -406,7 +401,9 @@ options:
|
|||
current working directory if none are specified.
|
||||
|
||||
--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
|
||||
Use the given configuration file instead of the default located in
|
||||
|
|
Loading…
Reference in New Issue