Merge master and update dependencies
This commit is contained in:
commit
380064de6a
|
@ -25,6 +25,7 @@ dscanner-report.json
|
||||||
*.o
|
*.o
|
||||||
*.obj
|
*.obj
|
||||||
*.exe
|
*.exe
|
||||||
|
obj
|
||||||
|
|
||||||
#debug build
|
#debug build
|
||||||
dsc
|
dsc
|
||||||
|
|
|
@ -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
|
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
|
.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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}|_).*`;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
243
src/ctags.d
243
src/ctags.d
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
43
src/main.d
43
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.
|
// 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
|
||||||
|
|
Loading…
Reference in New Issue