Separate analyze with dmd and autofix flows from libdparse analyze flow (#142)
* Separate analyze with dmd and autofix flows from libdparse analyze flow * Make locally used functions private * Extract parsing using DMD in a separate function * Address feedback
This commit is contained in:
parent
531f75bd29
commit
c0c881ed39
|
|
@ -0,0 +1,340 @@
|
||||||
|
module dscanner.analysis.autofix;
|
||||||
|
|
||||||
|
import std.algorithm : filter, findSplit;
|
||||||
|
import std.conv : to;
|
||||||
|
import std.functional : toDelegate;
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
|
import dparse.lexer;
|
||||||
|
import dparse.rollback_allocator;
|
||||||
|
import dparse.ast : Module;
|
||||||
|
|
||||||
|
import dsymbol.modulecache : ModuleCache;
|
||||||
|
|
||||||
|
import dscanner.analysis.base : AutoFix, AutoFixFormatting, BaseAnalyzer, Message;
|
||||||
|
import dscanner.analysis.config : StaticAnalysisConfig;
|
||||||
|
import dscanner.analysis.run : analyze, doNothing;
|
||||||
|
import dscanner.utils : readFile, readStdin;
|
||||||
|
|
||||||
|
private void resolveAutoFixes(
|
||||||
|
ref Message message,
|
||||||
|
string fileName,
|
||||||
|
ref ModuleCache moduleCache,
|
||||||
|
scope const(Token)[] tokens,
|
||||||
|
const Module m,
|
||||||
|
const StaticAnalysisConfig analysisConfig,
|
||||||
|
const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid
|
||||||
|
)
|
||||||
|
{
|
||||||
|
resolveAutoFixes(message.checkName, message.autofixes, fileName, moduleCache,
|
||||||
|
tokens, m, analysisConfig, overrideFormattingConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveAutoFixes(string messageCheckName, AutoFix[] autofixes, string fileName,
|
||||||
|
ref ModuleCache moduleCache,
|
||||||
|
scope const(Token)[] tokens, const Module m,
|
||||||
|
const StaticAnalysisConfig analysisConfig,
|
||||||
|
const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid)
|
||||||
|
{
|
||||||
|
import core.memory : GC;
|
||||||
|
import dsymbol.conversion.first : FirstPass;
|
||||||
|
import dsymbol.conversion.second : secondPass;
|
||||||
|
import dsymbol.scope_ : Scope;
|
||||||
|
import dsymbol.semantic : SemanticSymbol;
|
||||||
|
import dsymbol.string_interning : internString;
|
||||||
|
import dsymbol.symbol : DSymbol;
|
||||||
|
import dscanner.analysis.run : getAnalyzersForModuleAndConfig;
|
||||||
|
|
||||||
|
const(AutoFixFormatting) formattingConfig =
|
||||||
|
overrideFormattingConfig is AutoFixFormatting.invalid
|
||||||
|
? analysisConfig.getAutoFixFormattingConfig()
|
||||||
|
: overrideFormattingConfig;
|
||||||
|
|
||||||
|
scope first = new FirstPass(m, internString(fileName), &moduleCache, null);
|
||||||
|
first.run();
|
||||||
|
|
||||||
|
secondPass(first.rootSymbol, first.moduleScope, moduleCache);
|
||||||
|
auto moduleScope = first.moduleScope;
|
||||||
|
scope(exit) typeid(DSymbol).destroy(first.rootSymbol.acSymbol);
|
||||||
|
scope(exit) typeid(SemanticSymbol).destroy(first.rootSymbol);
|
||||||
|
scope(exit) typeid(Scope).destroy(first.moduleScope);
|
||||||
|
|
||||||
|
GC.disable;
|
||||||
|
scope (exit)
|
||||||
|
GC.enable;
|
||||||
|
|
||||||
|
foreach (BaseAnalyzer check; getAnalyzersForModuleAndConfig(fileName, tokens, m, analysisConfig, moduleScope))
|
||||||
|
{
|
||||||
|
if (check.getName() == messageCheckName)
|
||||||
|
{
|
||||||
|
foreach (ref autofix; autofixes)
|
||||||
|
autofix.resolveAutoFixFromCheck(check, m, tokens, formattingConfig);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Cannot find analyzer " ~ messageCheckName
|
||||||
|
~ " to resolve autofix with.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void resolveAutoFixFromCheck(
|
||||||
|
ref AutoFix autofix,
|
||||||
|
BaseAnalyzer check,
|
||||||
|
const Module m,
|
||||||
|
scope const(Token)[] tokens,
|
||||||
|
const AutoFixFormatting formattingConfig
|
||||||
|
)
|
||||||
|
{
|
||||||
|
import std.sumtype : match;
|
||||||
|
|
||||||
|
autofix.replacements.match!(
|
||||||
|
(AutoFix.ResolveContext context) {
|
||||||
|
autofix.replacements = check.resolveAutoFix(m, tokens, context, formattingConfig);
|
||||||
|
},
|
||||||
|
(_) {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AutoFix.CodeReplacement[] resolveAutoFix(string messageCheckName, AutoFix.ResolveContext context,
|
||||||
|
string fileName,
|
||||||
|
ref ModuleCache moduleCache,
|
||||||
|
scope const(Token)[] tokens, const Module m,
|
||||||
|
const StaticAnalysisConfig analysisConfig,
|
||||||
|
const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid)
|
||||||
|
{
|
||||||
|
AutoFix temp;
|
||||||
|
temp.replacements = context;
|
||||||
|
resolveAutoFixes(messageCheckName, (&temp)[0 .. 1], fileName, moduleCache,
|
||||||
|
tokens, m, analysisConfig, overrideFormattingConfig);
|
||||||
|
return temp.expectReplacements("resolving didn't work?!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void listAutofixes(
|
||||||
|
StaticAnalysisConfig config,
|
||||||
|
string resolveMessage,
|
||||||
|
bool usingStdin,
|
||||||
|
string fileName,
|
||||||
|
StringCache* cache,
|
||||||
|
ref ModuleCache moduleCache
|
||||||
|
)
|
||||||
|
{
|
||||||
|
import dparse.parser : parseModule;
|
||||||
|
import dscanner.analysis.base : Message;
|
||||||
|
import std.format : format;
|
||||||
|
import std.json : JSONValue;
|
||||||
|
|
||||||
|
union RequestedLocation
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint line, column;
|
||||||
|
}
|
||||||
|
ulong bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestedLocation req;
|
||||||
|
bool isBytes = resolveMessage[0] == 'b';
|
||||||
|
if (isBytes)
|
||||||
|
req.bytes = resolveMessage[1 .. $].to!ulong;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto parts = resolveMessage.findSplit(":");
|
||||||
|
req.line = parts[0].to!uint;
|
||||||
|
req.column = parts[2].to!uint;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matchesCursor(Message m)
|
||||||
|
{
|
||||||
|
return isBytes
|
||||||
|
? req.bytes >= m.startIndex && req.bytes <= m.endIndex
|
||||||
|
: req.line >= m.startLine && req.line <= m.endLine
|
||||||
|
&& (req.line > m.startLine || req.column >= m.startColumn)
|
||||||
|
&& (req.line < m.endLine || req.column <= m.endColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
RollbackAllocator rba;
|
||||||
|
LexerConfig lexerConfig;
|
||||||
|
lexerConfig.fileName = fileName;
|
||||||
|
lexerConfig.stringBehavior = StringBehavior.source;
|
||||||
|
auto tokens = getTokensForParser(usingStdin ? readStdin()
|
||||||
|
: readFile(fileName), lexerConfig, cache);
|
||||||
|
auto mod = parseModule(tokens, fileName, &rba, toDelegate(&doNothing));
|
||||||
|
|
||||||
|
auto messages = analyze(fileName, mod, config, moduleCache, tokens);
|
||||||
|
|
||||||
|
with (stdout.lockingTextWriter)
|
||||||
|
{
|
||||||
|
put("[");
|
||||||
|
foreach (message; messages[].filter!matchesCursor)
|
||||||
|
{
|
||||||
|
resolveAutoFixes(message, fileName, moduleCache, tokens, mod, config);
|
||||||
|
|
||||||
|
foreach (i, autofix; message.autofixes)
|
||||||
|
{
|
||||||
|
put(i == 0 ? "\n" : ",\n");
|
||||||
|
put("\t{\n");
|
||||||
|
put(format!"\t\t\"name\": %s,\n"(JSONValue(autofix.name)));
|
||||||
|
put("\t\t\"replacements\": [");
|
||||||
|
foreach (j, replacement; autofix.expectReplacements)
|
||||||
|
{
|
||||||
|
put(j == 0 ? "\n" : ",\n");
|
||||||
|
put(format!"\t\t\t{\"range\": [%d, %d], \"newText\": %s}"(
|
||||||
|
replacement.range[0],
|
||||||
|
replacement.range[1],
|
||||||
|
JSONValue(replacement.newText)));
|
||||||
|
}
|
||||||
|
put("\n");
|
||||||
|
put("\t\t]\n");
|
||||||
|
put("\t}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
put("\n]");
|
||||||
|
}
|
||||||
|
stdout.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void improveAutoFixWhitespace(scope const(char)[] code, AutoFix.CodeReplacement[] replacements)
|
||||||
|
{
|
||||||
|
import std.algorithm : endsWith, startsWith;
|
||||||
|
import std.ascii : isWhite;
|
||||||
|
import std.string : strip;
|
||||||
|
import std.utf : stride, strideBack;
|
||||||
|
|
||||||
|
enum WS
|
||||||
|
{
|
||||||
|
none, tab, space, newline
|
||||||
|
}
|
||||||
|
|
||||||
|
WS getWS(size_t i)
|
||||||
|
{
|
||||||
|
if (cast(ptrdiff_t) i < 0 || i >= code.length)
|
||||||
|
return WS.newline;
|
||||||
|
switch (code[i])
|
||||||
|
{
|
||||||
|
case '\n':
|
||||||
|
case '\r':
|
||||||
|
return WS.newline;
|
||||||
|
case '\t':
|
||||||
|
return WS.tab;
|
||||||
|
case ' ':
|
||||||
|
return WS.space;
|
||||||
|
default:
|
||||||
|
return WS.none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ref replacement; replacements)
|
||||||
|
{
|
||||||
|
assert(replacement.range[0] >= 0 && replacement.range[0] < code.length
|
||||||
|
&& replacement.range[1] >= 0 && replacement.range[1] < code.length
|
||||||
|
&& replacement.range[0] <= replacement.range[1], "trying to autofix whitespace on code that doesn't match with what the replacements were generated for");
|
||||||
|
|
||||||
|
void growRight()
|
||||||
|
{
|
||||||
|
// this is basically: replacement.range[1]++;
|
||||||
|
if (code[replacement.range[1] .. $].startsWith("\r\n"))
|
||||||
|
replacement.range[1] += 2;
|
||||||
|
else if (replacement.range[1] < code.length)
|
||||||
|
replacement.range[1] += code.stride(replacement.range[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void growLeft()
|
||||||
|
{
|
||||||
|
// this is basically: replacement.range[0]--;
|
||||||
|
if (code[0 .. replacement.range[0]].endsWith("\r\n"))
|
||||||
|
replacement.range[0] -= 2;
|
||||||
|
else if (replacement.range[0] > 0)
|
||||||
|
replacement.range[0] -= code.strideBack(replacement.range[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replacement.newText.strip.length)
|
||||||
|
{
|
||||||
|
if (replacement.newText.startsWith(" "))
|
||||||
|
{
|
||||||
|
// we insert with leading space, but there is a space/NL/SOF before
|
||||||
|
// remove to-be-inserted space
|
||||||
|
if (getWS(replacement.range[0] - 1))
|
||||||
|
replacement.newText = replacement.newText[1 .. $];
|
||||||
|
}
|
||||||
|
if (replacement.newText.startsWith("]", ")"))
|
||||||
|
{
|
||||||
|
// when inserting `)`, consume regular space before
|
||||||
|
if (getWS(replacement.range[0] - 1) == WS.space)
|
||||||
|
growLeft();
|
||||||
|
}
|
||||||
|
if (replacement.newText.endsWith(" "))
|
||||||
|
{
|
||||||
|
// we insert with trailing space, but there is a space/NL/EOF after, chomp off
|
||||||
|
if (getWS(replacement.range[1]))
|
||||||
|
replacement.newText = replacement.newText[0 .. $ - 1];
|
||||||
|
}
|
||||||
|
if (replacement.newText.endsWith("[", "("))
|
||||||
|
{
|
||||||
|
if (getWS(replacement.range[1]))
|
||||||
|
growRight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!replacement.newText.length)
|
||||||
|
{
|
||||||
|
// after removing code and ending up with whitespace on both sides,
|
||||||
|
// collapse 2 whitespace into one
|
||||||
|
switch (getWS(replacement.range[1]))
|
||||||
|
{
|
||||||
|
case WS.newline:
|
||||||
|
switch (getWS(replacement.range[0] - 1))
|
||||||
|
{
|
||||||
|
case WS.newline:
|
||||||
|
// after removal we have NL ~ NL or SOF ~ NL,
|
||||||
|
// remove right NL
|
||||||
|
growRight();
|
||||||
|
break;
|
||||||
|
case WS.space:
|
||||||
|
case WS.tab:
|
||||||
|
// after removal we have space ~ NL,
|
||||||
|
// remove the space
|
||||||
|
growLeft();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WS.space:
|
||||||
|
case WS.tab:
|
||||||
|
// for NL ~ space, SOF ~ space, space ~ space, tab ~ space,
|
||||||
|
// for NL ~ tab, SOF ~ tab, space ~ tab, tab ~ tab
|
||||||
|
// remove right space/tab
|
||||||
|
if (getWS(replacement.range[0] - 1))
|
||||||
|
growRight();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
import std.algorithm : sort;
|
||||||
|
|
||||||
|
AutoFix.CodeReplacement r(int start, int end, string s)
|
||||||
|
{
|
||||||
|
return AutoFix.CodeReplacement([start, end], s);
|
||||||
|
}
|
||||||
|
|
||||||
|
string test(string code, AutoFix.CodeReplacement[] replacements...)
|
||||||
|
{
|
||||||
|
replacements.sort!"a.range[0] < b.range[0]";
|
||||||
|
improveAutoFixWhitespace(code, replacements);
|
||||||
|
foreach_reverse (r; replacements)
|
||||||
|
code = code[0 .. r.range[0]] ~ r.newText ~ code[r.range[1] .. $];
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(test("import a;\nimport b;", r(0, 9, "")) == "import b;");
|
||||||
|
assert(test("import a;\r\nimport b;", r(0, 9, "")) == "import b;");
|
||||||
|
assert(test("import a;\nimport b;", r(8, 9, "")) == "import a\nimport b;");
|
||||||
|
assert(test("import a;\nimport b;", r(7, 8, "")) == "import ;\nimport b;");
|
||||||
|
assert(test("import a;\r\nimport b;", r(7, 8, "")) == "import ;\r\nimport b;");
|
||||||
|
assert(test("a b c", r(2, 3, "")) == "a c");
|
||||||
|
}
|
||||||
|
|
@ -238,6 +238,7 @@ void assertAutoFix(string before, string after, const StaticAnalysisConfig confi
|
||||||
string file = __FILE__, size_t line = __LINE__)
|
string file = __FILE__, size_t line = __LINE__)
|
||||||
{
|
{
|
||||||
import dparse.lexer : StringCache, Token;
|
import dparse.lexer : StringCache, Token;
|
||||||
|
import dscanner.analysis.autofix : improveAutoFixWhitespace;
|
||||||
import dscanner.analysis.run : parseModule;
|
import dscanner.analysis.run : parseModule;
|
||||||
import std.algorithm : canFind, findSplit, map, sort;
|
import std.algorithm : canFind, findSplit, map, sort;
|
||||||
import std.conv : to;
|
import std.conv : to;
|
||||||
|
|
@ -359,43 +360,28 @@ void assertAutoFix(string before, string after, const StaticAnalysisConfig confi
|
||||||
void assertAnalyzerWarningsDMD(string code, const StaticAnalysisConfig config, bool semantic = false,
|
void assertAnalyzerWarningsDMD(string code, const StaticAnalysisConfig config, bool semantic = false,
|
||||||
string file = __FILE__, size_t line = __LINE__)
|
string file = __FILE__, size_t line = __LINE__)
|
||||||
{
|
{
|
||||||
import dmd.globals : global;
|
|
||||||
import dscanner.utils : getModuleName;
|
|
||||||
import std.file : remove, exists;
|
|
||||||
import std.stdio : File;
|
|
||||||
import std.path : dirName;
|
|
||||||
import dmd.arraytypes : Strings;
|
|
||||||
|
|
||||||
import std.stdio : File;
|
|
||||||
import std.file : exists, remove;
|
import std.file : exists, remove;
|
||||||
|
import std.path : dirName;
|
||||||
|
import std.stdio : File;
|
||||||
|
import dscanner.analysis.rundmd : analyzeDmd, parseDmdModule;
|
||||||
|
import dscanner.utils : getModuleName;
|
||||||
|
|
||||||
auto deleteme = "test.txt";
|
auto testFileName = "test.d";
|
||||||
File f = File(deleteme, "w");
|
File f = File(testFileName, "w");
|
||||||
scope(exit)
|
scope(exit)
|
||||||
{
|
{
|
||||||
assert(exists(deleteme));
|
assert(exists(testFileName));
|
||||||
remove(deleteme);
|
remove(testFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
f.write(code);
|
f.write(code);
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
auto dmdParentDir = dirName(dirName(dirName(dirName(__FILE_FULL_PATH__))));
|
auto dmdModule = parseDmdModule(file, code);
|
||||||
|
|
||||||
global.params.useUnitTests = true;
|
|
||||||
global.path = Strings();
|
|
||||||
global.path.push((dmdParentDir ~ "/dmd" ~ "\0").ptr);
|
|
||||||
global.path.push((dmdParentDir ~ "/dmd/druntime/src" ~ "\0").ptr);
|
|
||||||
|
|
||||||
initDMD();
|
|
||||||
|
|
||||||
auto input = cast(char[]) code;
|
|
||||||
input ~= '\0';
|
|
||||||
auto t = dmd.frontend.parseModule(cast(const(char)[]) file, cast(const (char)[]) input);
|
|
||||||
if (semantic)
|
if (semantic)
|
||||||
t.module_.fullSemantic();
|
dmdModule.fullSemantic();
|
||||||
|
|
||||||
MessageSet rawWarnings = analyzeDmd("test.txt", t.module_, getModuleName(t.module_.md), config);
|
MessageSet rawWarnings = analyzeDmd(testFileName, dmdModule, getModuleName(dmdModule.md), config);
|
||||||
|
|
||||||
string[] codeLines = code.splitLines();
|
string[] codeLines = code.splitLines();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import dparse.ast;
|
||||||
import dparse.lexer;
|
import dparse.lexer;
|
||||||
import dparse.parser;
|
import dparse.parser;
|
||||||
import dparse.rollback_allocator;
|
import dparse.rollback_allocator;
|
||||||
|
|
||||||
import std.algorithm;
|
import std.algorithm;
|
||||||
import std.array;
|
import std.array;
|
||||||
import std.conv;
|
import std.conv;
|
||||||
|
|
@ -26,6 +27,7 @@ import std.experimental.allocator.mallocator : Mallocator;
|
||||||
import std.experimental.allocator.building_blocks.region : Region;
|
import std.experimental.allocator.building_blocks.region : Region;
|
||||||
import std.experimental.allocator.building_blocks.allocator_list : AllocatorList;
|
import std.experimental.allocator.building_blocks.allocator_list : AllocatorList;
|
||||||
|
|
||||||
|
import dscanner.analysis.autofix : improveAutoFixWhitespace;
|
||||||
import dscanner.analysis.config;
|
import dscanner.analysis.config;
|
||||||
import dscanner.analysis.base;
|
import dscanner.analysis.base;
|
||||||
import dscanner.analysis.style;
|
import dscanner.analysis.style;
|
||||||
|
|
@ -390,37 +392,21 @@ void generateSonarQubeGenericIssueDataReport(string[] fileNames, const StaticAna
|
||||||
bool analyze(string[] fileNames, const StaticAnalysisConfig config, string errorFormat,
|
bool analyze(string[] fileNames, const StaticAnalysisConfig config, string errorFormat,
|
||||||
ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
|
ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
|
||||||
{
|
{
|
||||||
import dmd.parse : Parser;
|
|
||||||
import dmd.astbase : ASTBase;
|
|
||||||
import dmd.id : Id;
|
|
||||||
import dmd.globals : global;
|
|
||||||
import dmd.identifier : Identifier;
|
|
||||||
import std.string : toStringz;
|
import std.string : toStringz;
|
||||||
import dmd.arraytypes : Strings;
|
import dscanner.analysis.rundmd : parseDmdModule;
|
||||||
|
|
||||||
|
import dscanner.analysis.rundmd : analyzeDmd;
|
||||||
|
|
||||||
bool hasErrors;
|
bool hasErrors;
|
||||||
foreach (fileName; fileNames)
|
foreach (fileName; fileNames)
|
||||||
{
|
{
|
||||||
|
|
||||||
auto dmdParentDir = dirName(dirName(dirName(dirName(__FILE_FULL_PATH__))));
|
|
||||||
|
|
||||||
global.params.useUnitTests = true;
|
|
||||||
global.path = Strings();
|
|
||||||
global.path.push((dmdParentDir ~ "/dmd" ~ "\0").ptr);
|
|
||||||
global.path.push((dmdParentDir ~ "/dmd/druntime/src" ~ "\0").ptr);
|
|
||||||
|
|
||||||
initDMD();
|
|
||||||
|
|
||||||
auto code = readFile(fileName);
|
auto code = readFile(fileName);
|
||||||
auto input = cast(char[]) code;
|
|
||||||
input ~= '\0';
|
|
||||||
|
|
||||||
auto t = dmd.frontend.parseModule(cast(const(char)[]) fileName, cast(const (char)[]) input);
|
|
||||||
// t.module_.fullSemantic();
|
|
||||||
|
|
||||||
// Skip files that could not be read and continue with the rest
|
// Skip files that could not be read and continue with the rest
|
||||||
if (code.length == 0)
|
if (code.length == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
auto dmdModule = parseDmdModule(fileName, cast(string) code);
|
||||||
|
|
||||||
RollbackAllocator r;
|
RollbackAllocator r;
|
||||||
uint errorCount;
|
uint errorCount;
|
||||||
uint warningCount;
|
uint warningCount;
|
||||||
|
|
@ -431,7 +417,7 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config, string error
|
||||||
if (errorCount > 0 || (staticAnalyze && warningCount > 0))
|
if (errorCount > 0 || (staticAnalyze && warningCount > 0))
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
MessageSet results = analyze(fileName, m, config, moduleCache, tokens, staticAnalyze);
|
MessageSet results = analyze(fileName, m, config, moduleCache, tokens, staticAnalyze);
|
||||||
MessageSet resultsDmd = analyzeDmd(fileName, t.module_, getModuleName(t.module_.md), config);
|
MessageSet resultsDmd = analyzeDmd(fileName, dmdModule, getModuleName(dmdModule.md), config);
|
||||||
foreach (result; resultsDmd[])
|
foreach (result; resultsDmd[])
|
||||||
{
|
{
|
||||||
results.insert(result);
|
results.insert(result);
|
||||||
|
|
@ -521,90 +507,6 @@ bool autofix(string[] fileNames, const StaticAnalysisConfig config, string error
|
||||||
return hasErrors;
|
return hasErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
void listAutofixes(
|
|
||||||
StaticAnalysisConfig config,
|
|
||||||
string resolveMessage,
|
|
||||||
bool usingStdin,
|
|
||||||
string fileName,
|
|
||||||
StringCache* cache,
|
|
||||||
ref ModuleCache moduleCache
|
|
||||||
)
|
|
||||||
{
|
|
||||||
import dparse.parser : parseModule;
|
|
||||||
import dscanner.analysis.base : Message;
|
|
||||||
import std.format : format;
|
|
||||||
import std.json : JSONValue;
|
|
||||||
|
|
||||||
union RequestedLocation
|
|
||||||
{
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
uint line, column;
|
|
||||||
}
|
|
||||||
ulong bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestedLocation req;
|
|
||||||
bool isBytes = resolveMessage[0] == 'b';
|
|
||||||
if (isBytes)
|
|
||||||
req.bytes = resolveMessage[1 .. $].to!ulong;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto parts = resolveMessage.findSplit(":");
|
|
||||||
req.line = parts[0].to!uint;
|
|
||||||
req.column = parts[2].to!uint;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool matchesCursor(Message m)
|
|
||||||
{
|
|
||||||
return isBytes
|
|
||||||
? req.bytes >= m.startIndex && req.bytes <= m.endIndex
|
|
||||||
: req.line >= m.startLine && req.line <= m.endLine
|
|
||||||
&& (req.line > m.startLine || req.column >= m.startColumn)
|
|
||||||
&& (req.line < m.endLine || req.column <= m.endColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
RollbackAllocator rba;
|
|
||||||
LexerConfig lexerConfig;
|
|
||||||
lexerConfig.fileName = fileName;
|
|
||||||
lexerConfig.stringBehavior = StringBehavior.source;
|
|
||||||
auto tokens = getTokensForParser(usingStdin ? readStdin()
|
|
||||||
: readFile(fileName), lexerConfig, cache);
|
|
||||||
auto mod = parseModule(tokens, fileName, &rba, toDelegate(&doNothing));
|
|
||||||
|
|
||||||
auto messages = analyze(fileName, mod, config, moduleCache, tokens);
|
|
||||||
|
|
||||||
with (stdout.lockingTextWriter)
|
|
||||||
{
|
|
||||||
put("[");
|
|
||||||
foreach (message; messages[].filter!matchesCursor)
|
|
||||||
{
|
|
||||||
resolveAutoFixes(message, fileName, moduleCache, tokens, mod, config);
|
|
||||||
|
|
||||||
foreach (i, autofix; message.autofixes)
|
|
||||||
{
|
|
||||||
put(i == 0 ? "\n" : ",\n");
|
|
||||||
put("\t{\n");
|
|
||||||
put(format!"\t\t\"name\": %s,\n"(JSONValue(autofix.name)));
|
|
||||||
put("\t\t\"replacements\": [");
|
|
||||||
foreach (j, replacement; autofix.expectReplacements)
|
|
||||||
{
|
|
||||||
put(j == 0 ? "\n" : ",\n");
|
|
||||||
put(format!"\t\t\t{\"range\": [%d, %d], \"newText\": %s}"(
|
|
||||||
replacement.range[0],
|
|
||||||
replacement.range[1],
|
|
||||||
JSONValue(replacement.newText)));
|
|
||||||
}
|
|
||||||
put("\n");
|
|
||||||
put("\t\t]\n");
|
|
||||||
put("\t}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
put("\n]");
|
|
||||||
}
|
|
||||||
stdout.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct UserSelect
|
private struct UserSelect
|
||||||
{
|
{
|
||||||
import std.string : strip;
|
import std.string : strip;
|
||||||
|
|
@ -736,82 +638,7 @@ bool shouldRun(check : BaseAnalyzer)(string moduleName, const ref StaticAnalysis
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
BaseAnalyzer[] getAnalyzersForModuleAndConfig(string fileName,
|
||||||
* Checks whether a module is part of a user-specified include/exclude list.
|
|
||||||
*
|
|
||||||
* The user can specify a comma-separated list of filters, everyone needs to start with
|
|
||||||
* either a '+' (inclusion) or '-' (exclusion).
|
|
||||||
*
|
|
||||||
* If no includes are specified, all modules are included.
|
|
||||||
*/
|
|
||||||
bool shouldRunDmd(check : BaseAnalyzerDmd)(const char[] moduleName, const ref StaticAnalysisConfig config)
|
|
||||||
{
|
|
||||||
enum string a = check.name;
|
|
||||||
|
|
||||||
if (mixin("config." ~ a) == Check.disabled)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// By default, run the check
|
|
||||||
if (!moduleName.length)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
auto filters = mixin("config.filters." ~ a);
|
|
||||||
|
|
||||||
// Check if there are filters are defined
|
|
||||||
// filters starting with a comma are invalid
|
|
||||||
if (filters.length == 0 || filters[0].length == 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
auto includers = filters.filter!(f => f[0] == '+').map!(f => f[1..$]);
|
|
||||||
auto excluders = filters.filter!(f => f[0] == '-').map!(f => f[1..$]);
|
|
||||||
|
|
||||||
// exclusion has preference over inclusion
|
|
||||||
if (!excluders.empty && excluders.any!(s => moduleName.canFind(s)))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!includers.empty)
|
|
||||||
return includers.any!(s => moduleName.canFind(s));
|
|
||||||
|
|
||||||
// by default: include all modules
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
bool test(string moduleName, string filters)
|
|
||||||
{
|
|
||||||
StaticAnalysisConfig config;
|
|
||||||
// it doesn't matter which check we test here
|
|
||||||
config.asm_style_check = Check.enabled;
|
|
||||||
// this is done automatically by inifiled
|
|
||||||
config.filters.asm_style_check = filters.split(",");
|
|
||||||
return moduleName.shouldRunDmd!(AsmStyleCheck!ASTCodegen)(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
// test inclusion
|
|
||||||
assert(test("std.foo", "+std."));
|
|
||||||
// partial matches are ok
|
|
||||||
assert(test("std.foo", "+bar,+foo"));
|
|
||||||
// full as well
|
|
||||||
assert(test("std.foo", "+bar,+std.foo,+foo"));
|
|
||||||
// mismatch
|
|
||||||
assert(!test("std.foo", "+bar,+banana"));
|
|
||||||
|
|
||||||
// test exclusion
|
|
||||||
assert(!test("std.foo", "-std."));
|
|
||||||
assert(!test("std.foo", "-bar,-std.foo"));
|
|
||||||
assert(!test("std.foo", "-bar,-foo"));
|
|
||||||
// mismatch
|
|
||||||
assert(test("std.foo", "-bar,-banana"));
|
|
||||||
|
|
||||||
// test combination (exclusion has precedence)
|
|
||||||
assert(!test("std.foo", "+foo,-foo"));
|
|
||||||
assert(test("std.foo", "+foo,-bar"));
|
|
||||||
assert(test("std.bar.foo", "-barr,+bar"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private BaseAnalyzer[] getAnalyzersForModuleAndConfig(string fileName,
|
|
||||||
const(Token)[] tokens, const Module m,
|
const(Token)[] tokens, const Module m,
|
||||||
const StaticAnalysisConfig analysisConfig, const Scope* moduleScope)
|
const StaticAnalysisConfig analysisConfig, const Scope* moduleScope)
|
||||||
{
|
{
|
||||||
|
|
@ -862,6 +689,7 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a
|
||||||
const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid)
|
const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid)
|
||||||
{
|
{
|
||||||
import dsymbol.symbol : DSymbol;
|
import dsymbol.symbol : DSymbol;
|
||||||
|
import dscanner.analysis.autofix : resolveAutoFixFromCheck;
|
||||||
|
|
||||||
if (!staticAnalyze)
|
if (!staticAnalyze)
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -900,231 +728,6 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resolveAutoFixFromCheck(
|
|
||||||
ref AutoFix autofix,
|
|
||||||
BaseAnalyzer check,
|
|
||||||
const Module m,
|
|
||||||
scope const(Token)[] tokens,
|
|
||||||
const AutoFixFormatting formattingConfig
|
|
||||||
)
|
|
||||||
{
|
|
||||||
import std.sumtype : match;
|
|
||||||
|
|
||||||
autofix.replacements.match!(
|
|
||||||
(AutoFix.ResolveContext context) {
|
|
||||||
autofix.replacements = check.resolveAutoFix(m, tokens, context, formattingConfig);
|
|
||||||
},
|
|
||||||
(_) {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void resolveAutoFixes(ref Message message, string fileName,
|
|
||||||
ref ModuleCache moduleCache,
|
|
||||||
scope const(Token)[] tokens, const Module m,
|
|
||||||
const StaticAnalysisConfig analysisConfig,
|
|
||||||
const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid)
|
|
||||||
{
|
|
||||||
resolveAutoFixes(message.checkName, message.autofixes, fileName, moduleCache,
|
|
||||||
tokens, m, analysisConfig, overrideFormattingConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoFix.CodeReplacement[] resolveAutoFix(string messageCheckName, AutoFix.ResolveContext context,
|
|
||||||
string fileName,
|
|
||||||
ref ModuleCache moduleCache,
|
|
||||||
scope const(Token)[] tokens, const Module m,
|
|
||||||
const StaticAnalysisConfig analysisConfig,
|
|
||||||
const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid)
|
|
||||||
{
|
|
||||||
AutoFix temp;
|
|
||||||
temp.replacements = context;
|
|
||||||
resolveAutoFixes(messageCheckName, (&temp)[0 .. 1], fileName, moduleCache,
|
|
||||||
tokens, m, analysisConfig, overrideFormattingConfig);
|
|
||||||
return temp.expectReplacements("resolving didn't work?!");
|
|
||||||
}
|
|
||||||
|
|
||||||
void resolveAutoFixes(string messageCheckName, AutoFix[] autofixes, string fileName,
|
|
||||||
ref ModuleCache moduleCache,
|
|
||||||
scope const(Token)[] tokens, const Module m,
|
|
||||||
const StaticAnalysisConfig analysisConfig,
|
|
||||||
const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid)
|
|
||||||
{
|
|
||||||
import dsymbol.symbol : DSymbol;
|
|
||||||
|
|
||||||
const(AutoFixFormatting) formattingConfig =
|
|
||||||
overrideFormattingConfig is AutoFixFormatting.invalid
|
|
||||||
? analysisConfig.getAutoFixFormattingConfig()
|
|
||||||
: overrideFormattingConfig;
|
|
||||||
|
|
||||||
scope first = new FirstPass(m, internString(fileName), &moduleCache, null);
|
|
||||||
first.run();
|
|
||||||
|
|
||||||
secondPass(first.rootSymbol, first.moduleScope, moduleCache);
|
|
||||||
auto moduleScope = first.moduleScope;
|
|
||||||
scope(exit) typeid(DSymbol).destroy(first.rootSymbol.acSymbol);
|
|
||||||
scope(exit) typeid(SemanticSymbol).destroy(first.rootSymbol);
|
|
||||||
scope(exit) typeid(Scope).destroy(first.moduleScope);
|
|
||||||
|
|
||||||
GC.disable;
|
|
||||||
scope (exit)
|
|
||||||
GC.enable;
|
|
||||||
|
|
||||||
foreach (BaseAnalyzer check; getAnalyzersForModuleAndConfig(fileName, tokens, m, analysisConfig, moduleScope))
|
|
||||||
{
|
|
||||||
if (check.getName() == messageCheckName)
|
|
||||||
{
|
|
||||||
foreach (ref autofix; autofixes)
|
|
||||||
autofix.resolveAutoFixFromCheck(check, m, tokens, formattingConfig);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("Cannot find analyzer " ~ messageCheckName
|
|
||||||
~ " to resolve autofix with.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void improveAutoFixWhitespace(scope const(char)[] code, AutoFix.CodeReplacement[] replacements)
|
|
||||||
{
|
|
||||||
import std.ascii : isWhite;
|
|
||||||
import std.string : strip;
|
|
||||||
import std.utf : stride, strideBack;
|
|
||||||
|
|
||||||
enum WS
|
|
||||||
{
|
|
||||||
none, tab, space, newline
|
|
||||||
}
|
|
||||||
|
|
||||||
WS getWS(size_t i)
|
|
||||||
{
|
|
||||||
if (cast(ptrdiff_t) i < 0 || i >= code.length)
|
|
||||||
return WS.newline;
|
|
||||||
switch (code[i])
|
|
||||||
{
|
|
||||||
case '\n':
|
|
||||||
case '\r':
|
|
||||||
return WS.newline;
|
|
||||||
case '\t':
|
|
||||||
return WS.tab;
|
|
||||||
case ' ':
|
|
||||||
return WS.space;
|
|
||||||
default:
|
|
||||||
return WS.none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ref replacement; replacements)
|
|
||||||
{
|
|
||||||
assert(replacement.range[0] >= 0 && replacement.range[0] < code.length
|
|
||||||
&& replacement.range[1] >= 0 && replacement.range[1] < code.length
|
|
||||||
&& replacement.range[0] <= replacement.range[1], "trying to autofix whitespace on code that doesn't match with what the replacements were generated for");
|
|
||||||
|
|
||||||
void growRight()
|
|
||||||
{
|
|
||||||
// this is basically: replacement.range[1]++;
|
|
||||||
if (code[replacement.range[1] .. $].startsWith("\r\n"))
|
|
||||||
replacement.range[1] += 2;
|
|
||||||
else if (replacement.range[1] < code.length)
|
|
||||||
replacement.range[1] += code.stride(replacement.range[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void growLeft()
|
|
||||||
{
|
|
||||||
// this is basically: replacement.range[0]--;
|
|
||||||
if (code[0 .. replacement.range[0]].endsWith("\r\n"))
|
|
||||||
replacement.range[0] -= 2;
|
|
||||||
else if (replacement.range[0] > 0)
|
|
||||||
replacement.range[0] -= code.strideBack(replacement.range[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (replacement.newText.strip.length)
|
|
||||||
{
|
|
||||||
if (replacement.newText.startsWith(" "))
|
|
||||||
{
|
|
||||||
// we insert with leading space, but there is a space/NL/SOF before
|
|
||||||
// remove to-be-inserted space
|
|
||||||
if (getWS(replacement.range[0] - 1))
|
|
||||||
replacement.newText = replacement.newText[1 .. $];
|
|
||||||
}
|
|
||||||
if (replacement.newText.startsWith("]", ")"))
|
|
||||||
{
|
|
||||||
// when inserting `)`, consume regular space before
|
|
||||||
if (getWS(replacement.range[0] - 1) == WS.space)
|
|
||||||
growLeft();
|
|
||||||
}
|
|
||||||
if (replacement.newText.endsWith(" "))
|
|
||||||
{
|
|
||||||
// we insert with trailing space, but there is a space/NL/EOF after, chomp off
|
|
||||||
if (getWS(replacement.range[1]))
|
|
||||||
replacement.newText = replacement.newText[0 .. $ - 1];
|
|
||||||
}
|
|
||||||
if (replacement.newText.endsWith("[", "("))
|
|
||||||
{
|
|
||||||
if (getWS(replacement.range[1]))
|
|
||||||
growRight();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!replacement.newText.length)
|
|
||||||
{
|
|
||||||
// after removing code and ending up with whitespace on both sides,
|
|
||||||
// collapse 2 whitespace into one
|
|
||||||
switch (getWS(replacement.range[1]))
|
|
||||||
{
|
|
||||||
case WS.newline:
|
|
||||||
switch (getWS(replacement.range[0] - 1))
|
|
||||||
{
|
|
||||||
case WS.newline:
|
|
||||||
// after removal we have NL ~ NL or SOF ~ NL,
|
|
||||||
// remove right NL
|
|
||||||
growRight();
|
|
||||||
break;
|
|
||||||
case WS.space:
|
|
||||||
case WS.tab:
|
|
||||||
// after removal we have space ~ NL,
|
|
||||||
// remove the space
|
|
||||||
growLeft();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WS.space:
|
|
||||||
case WS.tab:
|
|
||||||
// for NL ~ space, SOF ~ space, space ~ space, tab ~ space,
|
|
||||||
// for NL ~ tab, SOF ~ tab, space ~ tab, tab ~ tab
|
|
||||||
// remove right space/tab
|
|
||||||
if (getWS(replacement.range[0] - 1))
|
|
||||||
growRight();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
AutoFix.CodeReplacement r(int start, int end, string s)
|
|
||||||
{
|
|
||||||
return AutoFix.CodeReplacement([start, end], s);
|
|
||||||
}
|
|
||||||
|
|
||||||
string test(string code, AutoFix.CodeReplacement[] replacements...)
|
|
||||||
{
|
|
||||||
replacements.sort!"a.range[0] < b.range[0]";
|
|
||||||
improveAutoFixWhitespace(code, replacements);
|
|
||||||
foreach_reverse (r; replacements)
|
|
||||||
code = code[0 .. r.range[0]] ~ r.newText ~ code[r.range[1] .. $];
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(test("import a;\nimport b;", r(0, 9, "")) == "import b;");
|
|
||||||
assert(test("import a;\r\nimport b;", r(0, 9, "")) == "import b;");
|
|
||||||
assert(test("import a;\nimport b;", r(8, 9, "")) == "import a\nimport b;");
|
|
||||||
assert(test("import a;\nimport b;", r(7, 8, "")) == "import ;\nimport b;");
|
|
||||||
assert(test("import a;\r\nimport b;", r(7, 8, "")) == "import ;\r\nimport b;");
|
|
||||||
assert(test("a b c", r(2, 3, "")) == "a c");
|
|
||||||
}
|
|
||||||
|
|
||||||
version (unittest)
|
version (unittest)
|
||||||
{
|
{
|
||||||
shared static this()
|
shared static this()
|
||||||
|
|
@ -1142,237 +745,3 @@ version (unittest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageSet analyzeDmd(string fileName, ASTCodegen.Module m, const char[] moduleName, const StaticAnalysisConfig config)
|
|
||||||
{
|
|
||||||
MessageSet set = new MessageSet;
|
|
||||||
BaseAnalyzerDmd[] visitors;
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(ObjectConstCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new ObjectConstCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(EnumArrayVisitor!ASTCodegen)(config))
|
|
||||||
visitors ~= new EnumArrayVisitor!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(DeleteCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new DeleteCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(FinalAttributeChecker!ASTCodegen)(config))
|
|
||||||
visitors ~= new FinalAttributeChecker!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(ImportSortednessCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new ImportSortednessCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(IncorrectInfiniteRangeCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new IncorrectInfiniteRangeCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(RedundantAttributesCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new RedundantAttributesCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(LengthSubtractionCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new LengthSubtractionCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(AliasSyntaxCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new AliasSyntaxCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(ExplicitlyAnnotatedUnittestCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new ExplicitlyAnnotatedUnittestCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(ConstructorCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new ConstructorCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(AssertWithoutMessageCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new AssertWithoutMessageCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.assert_without_msg == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(LocalImportCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new LocalImportCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(OpEqualsWithoutToHashCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new OpEqualsWithoutToHashCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.opequals_tohash_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(TrustTooMuchCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new TrustTooMuchCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.trust_too_much == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(AutoRefAssignmentCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new AutoRefAssignmentCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(LogicPrecedenceCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new LogicPrecedenceCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.logical_precedence_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(UnusedLabelCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new UnusedLabelCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.unused_label_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(BuiltinPropertyNameCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new BuiltinPropertyNameCheck!ASTCodegen(fileName);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(PokemonExceptionCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new PokemonExceptionCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.exception_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(BackwardsRangeCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new BackwardsRangeCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.backwards_range_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(ProperlyDocumentedPublicFunctions!ASTCodegen)(config))
|
|
||||||
visitors ~= new ProperlyDocumentedPublicFunctions!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.properly_documented_public_functions == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(RedundantParenCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new RedundantParenCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.redundant_parens_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(StaticIfElse!ASTCodegen)(config))
|
|
||||||
visitors ~= new StaticIfElse!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.static_if_else_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(UselessAssertCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new UselessAssertCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.useless_assert_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(AsmStyleCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new AsmStyleCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.asm_style_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(RedundantStorageClassCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new RedundantStorageClassCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.redundant_storage_classes == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(NumberStyleCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new NumberStyleCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.number_style_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(IfElseSameCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new IfElseSameCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.if_else_same_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(CyclomaticComplexityCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new CyclomaticComplexityCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.cyclomatic_complexity == Check.skipTests && !ut,
|
|
||||||
config.max_cyclomatic_complexity.to!int
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(LabelVarNameCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new LabelVarNameCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.label_var_same_name_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(LambdaReturnCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new LambdaReturnCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.lambda_return_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(AlwaysCurlyCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new AlwaysCurlyCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.always_curly_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(StyleChecker!ASTCodegen)(config))
|
|
||||||
visitors ~= new StyleChecker!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.style_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(AutoFunctionChecker!ASTCodegen)(config))
|
|
||||||
visitors ~= new AutoFunctionChecker!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.auto_function_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(UnusedParameterCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new UnusedParameterCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.unused_parameter_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(UnusedVariableCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new UnusedVariableCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.unused_variable_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(UnmodifiedFinder!ASTCodegen)(config))
|
|
||||||
visitors ~= new UnmodifiedFinder!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.could_be_immutable_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(BodyOnDisabledFuncsCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new BodyOnDisabledFuncsCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.body_on_disabled_func_check == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(UselessInitializerChecker!ASTCodegen)(config))
|
|
||||||
visitors ~= new UselessInitializerChecker!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.useless_initializer == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(HasPublicExampleCheck!ASTCodegen)(config))
|
|
||||||
visitors ~= new HasPublicExampleCheck!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.has_public_example == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!LineLengthCheck(config))
|
|
||||||
visitors ~= new LineLengthCheck(
|
|
||||||
fileName,
|
|
||||||
config.long_line_check == Check.skipTests && !ut,
|
|
||||||
config.max_line_length
|
|
||||||
);
|
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(UnusedResultChecker!ASTCodegen)(config))
|
|
||||||
visitors ~= new UnusedResultChecker!ASTCodegen(
|
|
||||||
fileName,
|
|
||||||
config.unused_result == Check.skipTests && !ut
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach (visitor; visitors)
|
|
||||||
{
|
|
||||||
m.accept(visitor);
|
|
||||||
|
|
||||||
foreach (message; visitor.messages)
|
|
||||||
set.insert(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,399 @@
|
||||||
|
module dscanner.analysis.rundmd;
|
||||||
|
|
||||||
|
import std.algorithm : any, canFind, filter, map;
|
||||||
|
import std.conv : to;
|
||||||
|
|
||||||
|
import dmd.astcodegen;
|
||||||
|
import dmd.dmodule : Module;
|
||||||
|
import dmd.frontend;
|
||||||
|
|
||||||
|
import dscanner.analysis.config : Check, StaticAnalysisConfig;
|
||||||
|
import dscanner.analysis.base : BaseAnalyzerDmd, MessageSet;
|
||||||
|
|
||||||
|
import dscanner.analysis.alias_syntax_check : AliasSyntaxCheck;
|
||||||
|
import dscanner.analysis.always_curly : AlwaysCurlyCheck;
|
||||||
|
import dscanner.analysis.asm_style : AsmStyleCheck;
|
||||||
|
import dscanner.analysis.assert_without_msg : AssertWithoutMessageCheck;
|
||||||
|
import dscanner.analysis.auto_function : AutoFunctionChecker;
|
||||||
|
import dscanner.analysis.auto_ref_assignment : AutoRefAssignmentCheck;
|
||||||
|
import dscanner.analysis.body_on_disabled_funcs : BodyOnDisabledFuncsCheck;
|
||||||
|
import dscanner.analysis.builtin_property_names : BuiltinPropertyNameCheck;
|
||||||
|
import dscanner.analysis.constructors : ConstructorCheck;
|
||||||
|
import dscanner.analysis.cyclomatic_complexity : CyclomaticComplexityCheck;
|
||||||
|
import dscanner.analysis.del : DeleteCheck;
|
||||||
|
import dscanner.analysis.enumarrayliteral : EnumArrayVisitor;
|
||||||
|
import dscanner.analysis.explicitly_annotated_unittests : ExplicitlyAnnotatedUnittestCheck;
|
||||||
|
import dscanner.analysis.final_attribute : FinalAttributeChecker;
|
||||||
|
import dscanner.analysis.has_public_example : HasPublicExampleCheck;
|
||||||
|
import dscanner.analysis.ifelsesame : IfElseSameCheck;
|
||||||
|
import dscanner.analysis.imports_sortedness : ImportSortednessCheck;
|
||||||
|
import dscanner.analysis.incorrect_infinite_range : IncorrectInfiniteRangeCheck;
|
||||||
|
import dscanner.analysis.label_var_same_name_check : LabelVarNameCheck;
|
||||||
|
import dscanner.analysis.lambda_return_check : LambdaReturnCheck;
|
||||||
|
import dscanner.analysis.length_subtraction : LengthSubtractionCheck;
|
||||||
|
import dscanner.analysis.line_length : LineLengthCheck;
|
||||||
|
import dscanner.analysis.local_imports : LocalImportCheck;
|
||||||
|
import dscanner.analysis.logic_precedence : LogicPrecedenceCheck;
|
||||||
|
import dscanner.analysis.numbers : NumberStyleCheck;
|
||||||
|
import dscanner.analysis.objectconst : ObjectConstCheck;
|
||||||
|
import dscanner.analysis.opequals_without_tohash : OpEqualsWithoutToHashCheck;
|
||||||
|
import dscanner.analysis.pokemon : PokemonExceptionCheck;
|
||||||
|
import dscanner.analysis.properly_documented_public_functions : ProperlyDocumentedPublicFunctions;
|
||||||
|
import dscanner.analysis.range : BackwardsRangeCheck;
|
||||||
|
import dscanner.analysis.redundant_attributes : RedundantAttributesCheck;
|
||||||
|
import dscanner.analysis.redundant_parens : RedundantParenCheck;
|
||||||
|
import dscanner.analysis.redundant_storage_class : RedundantStorageClassCheck;
|
||||||
|
import dscanner.analysis.static_if_else : StaticIfElse;
|
||||||
|
import dscanner.analysis.style : StyleChecker;
|
||||||
|
import dscanner.analysis.trust_too_much : TrustTooMuchCheck;
|
||||||
|
import dscanner.analysis.unmodified : UnmodifiedFinder;
|
||||||
|
import dscanner.analysis.unused_label : UnusedLabelCheck;
|
||||||
|
import dscanner.analysis.unused_parameter : UnusedParameterCheck;
|
||||||
|
import dscanner.analysis.unused_result : UnusedResultChecker;
|
||||||
|
import dscanner.analysis.unused_variable : UnusedVariableCheck;
|
||||||
|
import dscanner.analysis.useless_assert : UselessAssertCheck;
|
||||||
|
import dscanner.analysis.useless_initializer : UselessInitializerChecker;
|
||||||
|
|
||||||
|
version (unittest)
|
||||||
|
enum ut = true;
|
||||||
|
else
|
||||||
|
enum ut = false;
|
||||||
|
|
||||||
|
Module parseDmdModule(string fileName, string sourceCode)
|
||||||
|
{
|
||||||
|
setupDmd();
|
||||||
|
|
||||||
|
auto code = sourceCode;
|
||||||
|
if (code[$ - 1] != '\0')
|
||||||
|
code ~= '\0';
|
||||||
|
|
||||||
|
auto dmdModule = dmd.frontend.parseModule(cast(const(char)[]) fileName, cast(const (char)[]) code);
|
||||||
|
return dmdModule.module_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupDmd()
|
||||||
|
{
|
||||||
|
import std.path : dirName;
|
||||||
|
import dmd.arraytypes : Strings;
|
||||||
|
import dmd.globals : global;
|
||||||
|
|
||||||
|
auto dmdParentDir = dirName(dirName(dirName(dirName(__FILE_FULL_PATH__))));
|
||||||
|
auto dmdDirPath = dmdParentDir ~ "/dmd" ~ "\0";
|
||||||
|
auto druntimeDirPath = dmdParentDir ~ "/dmd/druntime/src" ~ "\0";
|
||||||
|
global.params.useUnitTests = true;
|
||||||
|
global.path = Strings();
|
||||||
|
global.path.push(dmdDirPath.ptr);
|
||||||
|
global.path.push(druntimeDirPath.ptr);
|
||||||
|
initDMD();
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageSet analyzeDmd(string fileName, ASTCodegen.Module m, const char[] moduleName, const StaticAnalysisConfig config)
|
||||||
|
{
|
||||||
|
MessageSet set = new MessageSet;
|
||||||
|
BaseAnalyzerDmd[] visitors;
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(ObjectConstCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new ObjectConstCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(EnumArrayVisitor!ASTCodegen)(config))
|
||||||
|
visitors ~= new EnumArrayVisitor!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(DeleteCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new DeleteCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(FinalAttributeChecker!ASTCodegen)(config))
|
||||||
|
visitors ~= new FinalAttributeChecker!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(ImportSortednessCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new ImportSortednessCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(IncorrectInfiniteRangeCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new IncorrectInfiniteRangeCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(RedundantAttributesCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new RedundantAttributesCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(LengthSubtractionCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new LengthSubtractionCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(AliasSyntaxCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new AliasSyntaxCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(ExplicitlyAnnotatedUnittestCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new ExplicitlyAnnotatedUnittestCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(ConstructorCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new ConstructorCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(AssertWithoutMessageCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new AssertWithoutMessageCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.assert_without_msg == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(LocalImportCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new LocalImportCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(OpEqualsWithoutToHashCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new OpEqualsWithoutToHashCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.opequals_tohash_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(TrustTooMuchCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new TrustTooMuchCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.trust_too_much == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(AutoRefAssignmentCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new AutoRefAssignmentCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(LogicPrecedenceCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new LogicPrecedenceCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.logical_precedence_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(UnusedLabelCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new UnusedLabelCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.unused_label_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(BuiltinPropertyNameCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new BuiltinPropertyNameCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(PokemonExceptionCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new PokemonExceptionCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.exception_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(BackwardsRangeCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new BackwardsRangeCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.backwards_range_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(ProperlyDocumentedPublicFunctions!ASTCodegen)(config))
|
||||||
|
visitors ~= new ProperlyDocumentedPublicFunctions!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.properly_documented_public_functions == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(RedundantParenCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new RedundantParenCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.redundant_parens_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(StaticIfElse!ASTCodegen)(config))
|
||||||
|
visitors ~= new StaticIfElse!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.static_if_else_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(UselessAssertCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new UselessAssertCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.useless_assert_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(AsmStyleCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new AsmStyleCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.asm_style_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(RedundantStorageClassCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new RedundantStorageClassCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.redundant_storage_classes == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(NumberStyleCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new NumberStyleCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.number_style_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(IfElseSameCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new IfElseSameCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.if_else_same_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(CyclomaticComplexityCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new CyclomaticComplexityCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.cyclomatic_complexity == Check.skipTests && !ut,
|
||||||
|
config.max_cyclomatic_complexity.to!int
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(LabelVarNameCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new LabelVarNameCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.label_var_same_name_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(LambdaReturnCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new LambdaReturnCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.lambda_return_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(AlwaysCurlyCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new AlwaysCurlyCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.always_curly_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(StyleChecker!ASTCodegen)(config))
|
||||||
|
visitors ~= new StyleChecker!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.style_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(AutoFunctionChecker!ASTCodegen)(config))
|
||||||
|
visitors ~= new AutoFunctionChecker!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.auto_function_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(UnusedParameterCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new UnusedParameterCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.unused_parameter_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(UnusedVariableCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new UnusedVariableCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.unused_variable_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(UnmodifiedFinder!ASTCodegen)(config))
|
||||||
|
visitors ~= new UnmodifiedFinder!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.could_be_immutable_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(BodyOnDisabledFuncsCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new BodyOnDisabledFuncsCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.body_on_disabled_func_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(UselessInitializerChecker!ASTCodegen)(config))
|
||||||
|
visitors ~= new UselessInitializerChecker!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.useless_initializer == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(HasPublicExampleCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new HasPublicExampleCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.has_public_example == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!LineLengthCheck(config))
|
||||||
|
visitors ~= new LineLengthCheck(
|
||||||
|
fileName,
|
||||||
|
config.long_line_check == Check.skipTests && !ut,
|
||||||
|
config.max_line_length
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(UnusedResultChecker!ASTCodegen)(config))
|
||||||
|
visitors ~= new UnusedResultChecker!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.unused_result == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (visitor; visitors)
|
||||||
|
{
|
||||||
|
m.accept(visitor);
|
||||||
|
|
||||||
|
foreach (message; visitor.messages)
|
||||||
|
set.insert(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a module is part of a user-specified include/exclude list.
|
||||||
|
*
|
||||||
|
* The user can specify a comma-separated list of filters, everyone needs to start with
|
||||||
|
* either a '+' (inclusion) or '-' (exclusion).
|
||||||
|
*
|
||||||
|
* If no includes are specified, all modules are included.
|
||||||
|
*/
|
||||||
|
private bool shouldRunDmd(check : BaseAnalyzerDmd)(const char[] moduleName, const ref StaticAnalysisConfig config)
|
||||||
|
{
|
||||||
|
enum string a = check.name;
|
||||||
|
|
||||||
|
if (mixin("config." ~ a) == Check.disabled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// By default, run the check
|
||||||
|
if (!moduleName.length)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto filters = mixin("config.filters." ~ a);
|
||||||
|
|
||||||
|
// Check if there are filters are defined
|
||||||
|
// filters starting with a comma are invalid
|
||||||
|
if (filters.length == 0 || filters[0].length == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto includers = filters.filter!(f => f[0] == '+').map!(f => f[1..$]);
|
||||||
|
auto excluders = filters.filter!(f => f[0] == '-').map!(f => f[1..$]);
|
||||||
|
|
||||||
|
// exclusion has preference over inclusion
|
||||||
|
if (!excluders.empty && excluders.any!(s => moduleName.canFind(s)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!includers.empty)
|
||||||
|
return includers.any!(s => moduleName.canFind(s));
|
||||||
|
|
||||||
|
// by default: include all modules
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
bool test(string moduleName, string filters)
|
||||||
|
{
|
||||||
|
import std.array : split;
|
||||||
|
|
||||||
|
StaticAnalysisConfig config;
|
||||||
|
// it doesn't matter which check we test here
|
||||||
|
config.asm_style_check = Check.enabled;
|
||||||
|
// this is done automatically by inifiled
|
||||||
|
config.filters.asm_style_check = filters.split(",");
|
||||||
|
return moduleName.shouldRunDmd!(AsmStyleCheck!ASTCodegen)(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test inclusion
|
||||||
|
assert(test("std.foo", "+std."));
|
||||||
|
// partial matches are ok
|
||||||
|
assert(test("std.foo", "+bar,+foo"));
|
||||||
|
// full as well
|
||||||
|
assert(test("std.foo", "+bar,+std.foo,+foo"));
|
||||||
|
// mismatch
|
||||||
|
assert(!test("std.foo", "+bar,+banana"));
|
||||||
|
|
||||||
|
// test exclusion
|
||||||
|
assert(!test("std.foo", "-std."));
|
||||||
|
assert(!test("std.foo", "-bar,-std.foo"));
|
||||||
|
assert(!test("std.foo", "-bar,-foo"));
|
||||||
|
// mismatch
|
||||||
|
assert(test("std.foo", "-bar,-banana"));
|
||||||
|
|
||||||
|
// test combination (exclusion has precedence)
|
||||||
|
assert(!test("std.foo", "+foo,-foo"));
|
||||||
|
assert(test("std.foo", "+foo,-bar"));
|
||||||
|
assert(test("std.bar.foo", "-barr,+bar"));
|
||||||
|
}
|
||||||
|
|
@ -1,461 +0,0 @@
|
||||||
// Copyright Brian Schott (Hackerpilot) 2014-2015.
|
|
||||||
// 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 dscanner.analysis.unused;
|
|
||||||
|
|
||||||
import dparse.ast;
|
|
||||||
import dparse.lexer;
|
|
||||||
import dscanner.analysis.base;
|
|
||||||
import std.container;
|
|
||||||
import std.regex : Regex, regex, matchAll;
|
|
||||||
import dsymbol.scope_ : Scope;
|
|
||||||
import std.algorithm : all;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for unused variables.
|
|
||||||
*/
|
|
||||||
abstract class UnusedIdentifierCheck : BaseAnalyzer
|
|
||||||
{
|
|
||||||
alias visit = BaseAnalyzer.visit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
this(BaseAnalyzerArguments args)
|
|
||||||
{
|
|
||||||
super(args);
|
|
||||||
re = regex("[\\p{Alphabetic}_][\\w_]*");
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const Module mod)
|
|
||||||
{
|
|
||||||
pushScope();
|
|
||||||
mod.accept(this);
|
|
||||||
popScope();
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const Declaration declaration)
|
|
||||||
{
|
|
||||||
if (!isOverride)
|
|
||||||
foreach (attribute; declaration.attributes)
|
|
||||||
isOverride = isOverride || (attribute.attribute == tok!"override");
|
|
||||||
declaration.accept(this);
|
|
||||||
isOverride = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const FunctionDeclaration functionDec)
|
|
||||||
{
|
|
||||||
pushScope();
|
|
||||||
if (functionDec.functionBody
|
|
||||||
&& (functionDec.functionBody.specifiedFunctionBody
|
|
||||||
|| functionDec.functionBody.shortenedFunctionBody))
|
|
||||||
{
|
|
||||||
immutable bool ias = inAggregateScope;
|
|
||||||
inAggregateScope = false;
|
|
||||||
if (!isOverride)
|
|
||||||
functionDec.parameters.accept(this);
|
|
||||||
functionDec.functionBody.accept(this);
|
|
||||||
inAggregateScope = ias;
|
|
||||||
}
|
|
||||||
popScope();
|
|
||||||
}
|
|
||||||
|
|
||||||
mixin PartsUseVariables!AliasInitializer;
|
|
||||||
mixin PartsUseVariables!ArgumentList;
|
|
||||||
mixin PartsUseVariables!AssertExpression;
|
|
||||||
mixin PartsUseVariables!ClassDeclaration;
|
|
||||||
mixin PartsUseVariables!FunctionBody;
|
|
||||||
mixin PartsUseVariables!FunctionCallExpression;
|
|
||||||
mixin PartsUseVariables!FunctionDeclaration;
|
|
||||||
mixin PartsUseVariables!IndexExpression;
|
|
||||||
mixin PartsUseVariables!Initializer;
|
|
||||||
mixin PartsUseVariables!InterfaceDeclaration;
|
|
||||||
mixin PartsUseVariables!NewExpression;
|
|
||||||
mixin PartsUseVariables!StaticIfCondition;
|
|
||||||
mixin PartsUseVariables!StructDeclaration;
|
|
||||||
mixin PartsUseVariables!TemplateArgumentList;
|
|
||||||
mixin PartsUseVariables!ThrowExpression;
|
|
||||||
mixin PartsUseVariables!CastExpression;
|
|
||||||
|
|
||||||
override void dynamicDispatch(const ExpressionNode n)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
super.dynamicDispatch(n);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const SwitchStatement switchStatement)
|
|
||||||
{
|
|
||||||
if (switchStatement.expression !is null)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
switchStatement.expression.accept(this);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
switchStatement.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const WhileStatement whileStatement)
|
|
||||||
{
|
|
||||||
if (whileStatement.condition.expression !is null)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
whileStatement.condition.expression.accept(this);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
if (whileStatement.declarationOrStatement !is null)
|
|
||||||
whileStatement.declarationOrStatement.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const DoStatement doStatement)
|
|
||||||
{
|
|
||||||
if (doStatement.expression !is null)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
doStatement.expression.accept(this);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
if (doStatement.statementNoCaseNoDefault !is null)
|
|
||||||
doStatement.statementNoCaseNoDefault.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const ForStatement forStatement)
|
|
||||||
{
|
|
||||||
if (forStatement.initialization !is null)
|
|
||||||
forStatement.initialization.accept(this);
|
|
||||||
if (forStatement.test !is null)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
forStatement.test.accept(this);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
if (forStatement.increment !is null)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
forStatement.increment.accept(this);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
if (forStatement.declarationOrStatement !is null)
|
|
||||||
forStatement.declarationOrStatement.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const IfStatement ifStatement)
|
|
||||||
{
|
|
||||||
if (ifStatement.condition.expression !is null)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
ifStatement.condition.expression.accept(this);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
if (ifStatement.thenStatement !is null)
|
|
||||||
ifStatement.thenStatement.accept(this);
|
|
||||||
if (ifStatement.elseStatement !is null)
|
|
||||||
ifStatement.elseStatement.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const ForeachStatement foreachStatement)
|
|
||||||
{
|
|
||||||
if (foreachStatement.low !is null)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
foreachStatement.low.accept(this);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
if (foreachStatement.high !is null)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
foreachStatement.high.accept(this);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
foreachStatement.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const AssignExpression assignExp)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
assignExp.accept(this);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const TemplateDeclaration templateDeclaration)
|
|
||||||
{
|
|
||||||
immutable inAgg = inAggregateScope;
|
|
||||||
inAggregateScope = true;
|
|
||||||
templateDeclaration.accept(this);
|
|
||||||
inAggregateScope = inAgg;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const IdentifierOrTemplateChain chain)
|
|
||||||
{
|
|
||||||
if (interestDepth > 0 && chain.identifiersOrTemplateInstances[0].identifier != tok!"")
|
|
||||||
variableUsed(chain.identifiersOrTemplateInstances[0].identifier.text);
|
|
||||||
chain.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const TemplateSingleArgument single)
|
|
||||||
{
|
|
||||||
if (single.token != tok!"")
|
|
||||||
variableUsed(single.token.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const UnaryExpression unary)
|
|
||||||
{
|
|
||||||
const bool interesting = unary.prefix == tok!"*" || unary.unaryExpression !is null;
|
|
||||||
interestDepth += interesting;
|
|
||||||
unary.accept(this);
|
|
||||||
interestDepth -= interesting;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const MixinExpression mix)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
mixinDepth++;
|
|
||||||
mix.accept(this);
|
|
||||||
mixinDepth--;
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const PrimaryExpression primary)
|
|
||||||
{
|
|
||||||
if (interestDepth > 0)
|
|
||||||
{
|
|
||||||
const IdentifierOrTemplateInstance idt = primary.identifierOrTemplateInstance;
|
|
||||||
|
|
||||||
if (idt !is null)
|
|
||||||
{
|
|
||||||
if (idt.identifier != tok!"")
|
|
||||||
variableUsed(idt.identifier.text);
|
|
||||||
else if (idt.templateInstance && idt.templateInstance.identifier != tok!"")
|
|
||||||
variableUsed(idt.templateInstance.identifier.text);
|
|
||||||
}
|
|
||||||
if ((mixinDepth > 0 && primary.primary == tok!"stringLiteral")
|
|
||||||
|| primary.primary == tok!"wstringLiteral"
|
|
||||||
|| primary.primary == tok!"dstringLiteral")
|
|
||||||
{
|
|
||||||
foreach (part; matchAll(primary.primary.text, re))
|
|
||||||
{
|
|
||||||
void checkTree(in size_t treeIndex)
|
|
||||||
{
|
|
||||||
auto uu = UnUsed(part.hit);
|
|
||||||
auto r = tree[treeIndex].equalRange(&uu);
|
|
||||||
if (!r.empty)
|
|
||||||
r.front.uncertain = true;
|
|
||||||
}
|
|
||||||
checkTree(tree.length - 1);
|
|
||||||
if (tree.length >= 2)
|
|
||||||
checkTree(tree.length - 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
primary.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const ReturnStatement retStatement)
|
|
||||||
{
|
|
||||||
if (retStatement.expression !is null)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
visit(retStatement.expression);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const BlockStatement blockStatement)
|
|
||||||
{
|
|
||||||
immutable bool sb = inAggregateScope;
|
|
||||||
inAggregateScope = false;
|
|
||||||
if (blockStatementIntroducesScope)
|
|
||||||
pushScope();
|
|
||||||
blockStatement.accept(this);
|
|
||||||
if (blockStatementIntroducesScope)
|
|
||||||
popScope();
|
|
||||||
inAggregateScope = sb;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const Type2 tp)
|
|
||||||
{
|
|
||||||
if (tp.typeIdentifierPart &&
|
|
||||||
tp.typeIdentifierPart.identifierOrTemplateInstance)
|
|
||||||
{
|
|
||||||
const IdentifierOrTemplateInstance idt = tp.typeIdentifierPart.identifierOrTemplateInstance;
|
|
||||||
if (idt.identifier != tok!"")
|
|
||||||
variableUsed(idt.identifier.text);
|
|
||||||
else if (idt.templateInstance)
|
|
||||||
{
|
|
||||||
const TemplateInstance ti = idt.templateInstance;
|
|
||||||
if (ti.identifier != tok!"")
|
|
||||||
variableUsed(idt.templateInstance.identifier.text);
|
|
||||||
if (ti.templateArguments && ti.templateArguments.templateSingleArgument)
|
|
||||||
variableUsed(ti.templateArguments.templateSingleArgument.token.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tp.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const WithStatement withStatetement)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
if (withStatetement.expression)
|
|
||||||
withStatetement.expression.accept(this);
|
|
||||||
interestDepth--;
|
|
||||||
if (withStatetement.declarationOrStatement)
|
|
||||||
withStatetement.declarationOrStatement.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const StructBody structBody)
|
|
||||||
{
|
|
||||||
immutable bool sb = inAggregateScope;
|
|
||||||
inAggregateScope = true;
|
|
||||||
foreach (dec; structBody.declarations)
|
|
||||||
visit(dec);
|
|
||||||
inAggregateScope = sb;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const ConditionalStatement conditionalStatement)
|
|
||||||
{
|
|
||||||
immutable bool cs = blockStatementIntroducesScope;
|
|
||||||
blockStatementIntroducesScope = false;
|
|
||||||
conditionalStatement.accept(this);
|
|
||||||
blockStatementIntroducesScope = cs;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const AsmPrimaryExp primary)
|
|
||||||
{
|
|
||||||
if (primary.token != tok!"")
|
|
||||||
variableUsed(primary.token.text);
|
|
||||||
if (primary.identifierChain !is null)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract protected void popScope();
|
|
||||||
|
|
||||||
protected uint interestDepth;
|
|
||||||
|
|
||||||
protected Tree[] tree;
|
|
||||||
|
|
||||||
protected void variableDeclared(string name, Token token, bool isRef)
|
|
||||||
{
|
|
||||||
if (inAggregateScope || name.all!(a => a == '_'))
|
|
||||||
return;
|
|
||||||
tree[$ - 1].insert(new UnUsed(name, token, isRef));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void pushScope()
|
|
||||||
{
|
|
||||||
tree ~= new Tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
struct UnUsed
|
|
||||||
{
|
|
||||||
string name;
|
|
||||||
Token token;
|
|
||||||
bool isRef;
|
|
||||||
bool uncertain;
|
|
||||||
}
|
|
||||||
|
|
||||||
alias Tree = RedBlackTree!(UnUsed*, "a.name < b.name");
|
|
||||||
|
|
||||||
mixin template PartsUseVariables(NodeType)
|
|
||||||
{
|
|
||||||
override void visit(const NodeType node)
|
|
||||||
{
|
|
||||||
interestDepth++;
|
|
||||||
node.accept(this);
|
|
||||||
interestDepth--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void variableUsed(string name)
|
|
||||||
{
|
|
||||||
size_t treeIndex = tree.length - 1;
|
|
||||||
auto uu = UnUsed(name);
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0)
|
|
||||||
break;
|
|
||||||
treeIndex--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Regex!char re;
|
|
||||||
|
|
||||||
bool inAggregateScope;
|
|
||||||
|
|
||||||
uint mixinDepth;
|
|
||||||
|
|
||||||
bool isOverride;
|
|
||||||
|
|
||||||
bool blockStatementIntroducesScope = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Base class for unused parameter/variables checks
|
|
||||||
abstract class UnusedStorageCheck : UnusedIdentifierCheck
|
|
||||||
{
|
|
||||||
alias visit = UnusedIdentifierCheck.visit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Ignore declarations which are allowed to be unused, e.g. inside of a
|
|
||||||
speculative compilation: __traits(compiles, { S s = 0; })
|
|
||||||
**/
|
|
||||||
uint ignoreDeclarations = 0;
|
|
||||||
|
|
||||||
/// Kind of declaration for error messages e.g. "Variable"
|
|
||||||
const string publicType;
|
|
||||||
|
|
||||||
/// Kind of declaration for error reports e.g. "unused_variable"
|
|
||||||
const string reportType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* args = commonly shared analyzer arguments
|
|
||||||
* publicType = declaration kind used in error messages, e.g. "Variable"s
|
|
||||||
* reportType = declaration kind used in error reports, e.g. "unused_variable"
|
|
||||||
*/
|
|
||||||
this(BaseAnalyzerArguments args, string publicType = null, string reportType = null)
|
|
||||||
{
|
|
||||||
super(args);
|
|
||||||
this.publicType = publicType;
|
|
||||||
this.reportType = reportType;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const TraitsExpression traitsExp)
|
|
||||||
{
|
|
||||||
// issue #788: Enum values might be used inside of `__traits` expressions, e.g.:
|
|
||||||
// enum name = "abc";
|
|
||||||
// __traits(hasMember, S, name);
|
|
||||||
ignoreDeclarations++;
|
|
||||||
if (traitsExp.templateArgumentList)
|
|
||||||
traitsExp.templateArgumentList.accept(this);
|
|
||||||
ignoreDeclarations--;
|
|
||||||
}
|
|
||||||
|
|
||||||
override final protected void popScope()
|
|
||||||
{
|
|
||||||
if (!ignoreDeclarations)
|
|
||||||
{
|
|
||||||
foreach (uu; tree[$ - 1])
|
|
||||||
{
|
|
||||||
if (!uu.isRef && tree.length > 1)
|
|
||||||
{
|
|
||||||
if (uu.uncertain)
|
|
||||||
continue;
|
|
||||||
immutable string errorMessage = publicType ~ ' ' ~ uu.name ~ " is never used.";
|
|
||||||
addErrorMessage(uu.token, "dscanner.suspicious." ~ reportType, errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tree = tree[0 .. $ - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -31,6 +31,7 @@ import dscanner.outliner;
|
||||||
import dscanner.symbol_finder;
|
import dscanner.symbol_finder;
|
||||||
import dscanner.analysis.run;
|
import dscanner.analysis.run;
|
||||||
import dscanner.analysis.config;
|
import dscanner.analysis.config;
|
||||||
|
import dscanner.analysis.autofix : listAutofixes;
|
||||||
import dscanner.dscanner_version;
|
import dscanner.dscanner_version;
|
||||||
import dscanner.utils;
|
import dscanner.utils;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue