diff --git a/analysis/linespan.d b/analysis/linespan.d new file mode 100644 index 0000000..cedbef5 --- /dev/null +++ b/analysis/linespan.d @@ -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"); +} diff --git a/analysis/output.d b/analysis/output.d new file mode 100644 index 0000000..8bc2fcb --- /dev/null +++ b/analysis/output.d @@ -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("%d", line); + } + line++; + if (!spans.containsLine(line) && prevWasVisible) + file.writeln("
"); + } + 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(""); + foreach (char c; text) + { + if (c == '\r') + continue; + else if (c == '\n') + { + bool prevWasVisible = false; + if (spans.containsLine(line)) + { + prevWasVisible = true; + file.writef("\n%d", line, cssClass); + } + line++; + if (!spans.containsLine(line) && prevWasVisible) + file.writeln("
"); + } + else if (spans.containsLine(line)) + { + if (c == '<') + file.write("<"); + else if (c == '&') + file.write("&"); + else + file.write(c); + } + } + file.write("
"); +} + +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("
"); + file.write(message.message); + file.writeln("
"); +} + +void writeHeader(File file) +{ + file.writeln(q"[ + + + +]"); + file.writeln("D-Scanner Report"); + file.writeln(""); + file.writeln(""); + file.writeln(""); +} + +void writeFooter(File file) +{ + file.writeln(""); + file.writeln(""); +} + +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("
"); + file.writeln("

", fileName, "

"); + file.writeln("
");
+		uint currentLine = 2;
+		LineSpans ls = generateLineSpans(messageSet);
+		if (ls.containsLine(1))
+			file.write("1");
+		foreach (token; tokens)
+		{
+			writeToken(file, token, currentLine, messageSet, ls);
+		}
+		file.writeln("
"); + file.writeln("
"); + + } + 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("
No warnings detected
"); + return; + } + file.writeln("
"); + file.writeln("

Summary

"); + file.write(""); + file.writeln(""); + file.write(""); + foreach (fileName, warningCount; warningCounts) + { + if (warningCount > 0) + file.writeln(""); + } + file.write(""); + file.write("
FileWarning Count
", fileName, "", warningCount, "
"); + file.write("
"); +} + +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; +} +"; diff --git a/stdx/d/parser.d b/stdx/d/parser.d index a95ec5a..ee48209 100644 --- a/stdx/d/parser.d +++ b/stdx/d/parser.d @@ -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() {