Convert message function to delegate so that it can actually do something useful
This commit is contained in:
parent
997373a069
commit
d157d31634
|
@ -0,0 +1,81 @@
|
|||
// Copyright Brian Schott (Sir Alaran) 2014.
|
||||
// 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.linespan;
|
||||
|
||||
|
||||
/**
|
||||
* Used for determining which lines to include as context in the generated HTML
|
||||
* report.
|
||||
*/
|
||||
struct LineSpans
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Returns: true if any of the spans contain the given line number
|
||||
*/
|
||||
bool containsLine(size_t line) pure const nothrow @safe
|
||||
{
|
||||
foreach (span; spans)
|
||||
{
|
||||
if (span.low <= line && span.high >= line)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params: line = the line to add
|
||||
*/
|
||||
void addLine(size_t line) pure nothrow @safe
|
||||
{
|
||||
immutable size_t low = line >= context ? line - context : 0;
|
||||
immutable size_t high = line + context;
|
||||
foreach (ref span; spans)
|
||||
{
|
||||
if (low <= span.low && high >= span.low && high <= span.high)
|
||||
{
|
||||
span.low = low;
|
||||
return;
|
||||
}
|
||||
else if (high >= span.high && low <= span.high && low >= span.low)
|
||||
{
|
||||
span.high = high;
|
||||
return;
|
||||
}
|
||||
}
|
||||
spans ~= Span(low, high);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static struct Span
|
||||
{
|
||||
size_t low;
|
||||
size_t high;
|
||||
}
|
||||
|
||||
Span[] spans;
|
||||
|
||||
enum context = 3;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
import std.stdio;
|
||||
LineSpans l;
|
||||
l.addLine(4);
|
||||
foreach (i; 2 .. 7)
|
||||
assert (l.containsLine(i));
|
||||
assert (!l.containsLine(1));
|
||||
assert (!l.containsLine(7));
|
||||
l.addLine(5);
|
||||
assert (l.containsLine(7));
|
||||
l.addLine(40);
|
||||
l.addLine(35);
|
||||
foreach (i; 33 .. 43)
|
||||
assert (l.containsLine(i));
|
||||
stderr.writeln("Unit tests for LineSpans passed");
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
module analysis.output;
|
||||
|
||||
import std.stdio;
|
||||
import std.algorithm;
|
||||
import stdx.d.lexer;
|
||||
import analysis.base;
|
||||
import analysis.linespan;
|
||||
import highlighter;
|
||||
|
||||
void writeWhitespace(File file, string text, ref uint line, MessageSet messages,
|
||||
ref const LineSpans spans)
|
||||
{
|
||||
foreach (char c; text)
|
||||
{
|
||||
if (c == '\r')
|
||||
continue;
|
||||
else if (c == '\n')
|
||||
{
|
||||
if (spans.containsLine(line))
|
||||
file.write("\n");
|
||||
foreach (message; messages[].filter!(a => a.line == line - 1))
|
||||
writeMessage(file, message);
|
||||
bool prevWasVisible = false;
|
||||
if (spans.containsLine(line))
|
||||
{
|
||||
prevWasVisible = true;
|
||||
file.writef("<span class=\"ln\">%d</span>", line);
|
||||
}
|
||||
line++;
|
||||
if (!spans.containsLine(line) && prevWasVisible)
|
||||
file.writeln("<div class=\"separator\"/></div>");
|
||||
}
|
||||
else if (spans.containsLine(line))
|
||||
{
|
||||
if (c == '\t')
|
||||
file.write(" ");
|
||||
else
|
||||
file.write(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeStrOrCom(File file, string text, string cssClass, ref uint line,
|
||||
ref const LineSpans spans)
|
||||
{
|
||||
file.write("<span class=\"", cssClass, "\">");
|
||||
foreach (char c; text)
|
||||
{
|
||||
if (c == '\r')
|
||||
continue;
|
||||
else if (c == '\n')
|
||||
{
|
||||
bool prevWasVisible = false;
|
||||
if (spans.containsLine(line))
|
||||
{
|
||||
prevWasVisible = true;
|
||||
file.writef("\n</span><span class=\"ln\">%d</span><span class=\"%s\">", line, cssClass);
|
||||
}
|
||||
line++;
|
||||
if (!spans.containsLine(line) && prevWasVisible)
|
||||
file.writeln("<div class=\"separator\"/></div>");
|
||||
}
|
||||
else if (spans.containsLine(line))
|
||||
{
|
||||
if (c == '<')
|
||||
file.write("<");
|
||||
else if (c == '&')
|
||||
file.write("&");
|
||||
else
|
||||
file.write(c);
|
||||
}
|
||||
}
|
||||
file.write("</span>");
|
||||
}
|
||||
|
||||
void writeToken(File file, ref const Token t, ref uint line, MessageSet messages,
|
||||
ref const LineSpans spans)
|
||||
{
|
||||
if (t == tok!"whitespace")
|
||||
writeWhitespace(file, t.text, line, messages, spans);
|
||||
else if (t.type == tok!"comment")
|
||||
writeStrOrCom(file, t.text, "com", line, spans);
|
||||
else if (isStringLiteral(t.type) || t.type == tok!"characterLiteral")
|
||||
writeStrOrCom(file, t.text, "str", line, spans);
|
||||
else if (spans.containsLine(line))
|
||||
{
|
||||
if (isBasicType(t.type))
|
||||
file.writeSpan("type", str(t.type));
|
||||
else if (isKeyword(t.type))
|
||||
file.writeSpan("kwrd", str(t.type));
|
||||
else if (isNumberLiteral(t.type))
|
||||
file.writeSpan("num", t.text);
|
||||
else if (isOperator(t.type))
|
||||
file.writeSpan("op", str(t.type));
|
||||
else
|
||||
file.write(t.text);
|
||||
}
|
||||
}
|
||||
|
||||
void writeMessage(File file, ref const Message message)
|
||||
{
|
||||
file.write("<div class=\"warning\">");
|
||||
file.write(message.message);
|
||||
file.writeln("</div>");
|
||||
}
|
||||
|
||||
void writeHeader(File file)
|
||||
{
|
||||
file.writeln(q"[
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>]");
|
||||
file.writeln("<title>D-Scanner Report</title>");
|
||||
file.writeln("<style type=\"text/css\">\n", SOLARIZED_CSS, "\n",
|
||||
STATIC_ANALYSIS_CSS, "\n</style>");
|
||||
file.writeln("</head>");
|
||||
file.writeln("<body>");
|
||||
}
|
||||
|
||||
void writeFooter(File file)
|
||||
{
|
||||
file.writeln("</body>");
|
||||
file.writeln("</html>");
|
||||
}
|
||||
|
||||
void writeHtmlReport(File file, MessageSet[string] messages, shared(StringCache)* cache)
|
||||
{
|
||||
import std.array;
|
||||
import std.conv;
|
||||
writeHeader(file);
|
||||
writeSummary(file, messages);
|
||||
foreach (fileName, messageSet; messages)
|
||||
{
|
||||
writeln("Processing messages for ", fileName);
|
||||
if (messageSet.empty)
|
||||
continue;
|
||||
File f = File(fileName);
|
||||
auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size));
|
||||
f.rawRead(bytes);
|
||||
LexerConfig config;
|
||||
config.commentBehavior = CommentBehavior.include;
|
||||
config.whitespaceBehavior = WhitespaceBehavior.include;
|
||||
config.stringBehavior = StringBehavior.source;
|
||||
const(Token)[] tokens = byToken(bytes, config, cache).array;
|
||||
file.writeln("<div class=\"section\" name=\"", fileName, "\">");
|
||||
file.writeln("<h1><a name=\"", fileName, "\">", fileName, "</a></h1>");
|
||||
file.writeln("<pre>");
|
||||
uint currentLine = 2;
|
||||
LineSpans ls = generateLineSpans(messageSet);
|
||||
if (ls.containsLine(1))
|
||||
file.write("<span class=\"ln\">1</span>");
|
||||
foreach (token; tokens)
|
||||
{
|
||||
writeToken(file, token, currentLine, messageSet, ls);
|
||||
}
|
||||
file.writeln("</pre>");
|
||||
file.writeln("</div>");
|
||||
|
||||
}
|
||||
writeFooter(file);
|
||||
}
|
||||
|
||||
void writeSummary(File file, MessageSet[string] messages)
|
||||
{
|
||||
size_t[string] warningCounts;
|
||||
bool hasWarnings;
|
||||
foreach (fileName, messageSet; messages)
|
||||
{
|
||||
warningCounts[fileName] = messageSet.length;
|
||||
if (messageSet.length > 0)
|
||||
hasWarnings = true;
|
||||
}
|
||||
if (!hasWarnings)
|
||||
{
|
||||
file.writeln("<div id=\"summary\" class=\"section\">No warnings detected</div>");
|
||||
return;
|
||||
}
|
||||
file.writeln("<div id=\"summary\" class=\"section\">");
|
||||
file.writeln("<h1>Summary</h1>");
|
||||
file.write("<table>");
|
||||
file.writeln("<thead><tr><th>File</th></th><th>Warning Count</th></tr></thead>");
|
||||
file.write("<tbody>");
|
||||
foreach (fileName, warningCount; warningCounts)
|
||||
{
|
||||
if (warningCount > 0)
|
||||
file.writeln("<tr><td><a href=\"#", fileName, "\">", fileName, "</td><td>", warningCount, "</td></tr>");
|
||||
}
|
||||
file.write("</tbody>");
|
||||
file.write("</table>");
|
||||
file.write("</div>");
|
||||
}
|
||||
|
||||
LineSpans generateLineSpans(MessageSet messages)
|
||||
{
|
||||
LineSpans l;
|
||||
foreach (message; messages[])
|
||||
{
|
||||
l.addLine(message.line);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
immutable string STATIC_ANALYSIS_CSS = "
|
||||
.ln {
|
||||
width: 5em;
|
||||
display: inline-block;
|
||||
margin-left: -4em;
|
||||
border-right: .1em solid #839496;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.warning {
|
||||
display: block;
|
||||
border-radius: 1em;
|
||||
border-top: .1em solid ##dc322f;
|
||||
color: #002b36;
|
||||
background-color: #fdf6e3;
|
||||
padding: 1em;
|
||||
margin: .5em 0 -.5em 0;
|
||||
}
|
||||
pre {
|
||||
padding-left: 5em;
|
||||
margin: 0;
|
||||
border-radius: 0 0 1em 1em;
|
||||
}
|
||||
|
||||
.section {
|
||||
border: .1em solid #839496;
|
||||
margin: 2em 0;
|
||||
padding: 0;
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
.section h1 {
|
||||
font-weight: normal;
|
||||
color: #002b36;
|
||||
background-color: #fdf6e3;
|
||||
margin: 0;
|
||||
padding: .5em;
|
||||
border-radius: 1em 1em 0 0;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
.separator {
|
||||
display: block;
|
||||
height: 1em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: .1em dashed #fdf6e3;
|
||||
}
|
||||
";
|
|
@ -142,7 +142,7 @@ private:
|
|||
* Returns: the parsed module
|
||||
*/
|
||||
Module parseModule(const(Token)[] tokens, string fileName, CAllocator allocator = null,
|
||||
void function(string, size_t, size_t, string, bool) messageFunction = null)
|
||||
void delegate(string, size_t, size_t, string, bool) messageFunction = null)
|
||||
{
|
||||
auto parser = new Parser();
|
||||
parser.fileName = fileName;
|
||||
|
@ -218,8 +218,8 @@ class Parser
|
|||
error(`"(" expected for the linkage attribute`);
|
||||
node.linkageAttribute = parseLinkageAttribute();
|
||||
}
|
||||
warn("Syntax \"'alias' type identifier ';'\" is deprecated. Please use "
|
||||
~ " \"'alias' identifier '=' type ';'\" instead.");
|
||||
warn("Prefer the new \"'alias' identifier '=' type ';'\" syntax"
|
||||
~ " to the old \"'alias' type identifier ';'\" syntax");
|
||||
if ((node.type = parseType()) is null) return null;
|
||||
auto ident = expect(tok!"identifier");
|
||||
if (ident is null)
|
||||
|
@ -6220,7 +6220,7 @@ q{doStuff(5)}c;
|
|||
* The parameters are the file name, line number, column number,
|
||||
* and the error or warning message.
|
||||
*/
|
||||
void function(string, size_t, size_t, string, bool) messageFunction;
|
||||
void delegate(string, size_t, size_t, string, bool) messageFunction;
|
||||
|
||||
bool isSliceExpression()
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue