1 // Copyright (c) 2014, Matthew Brennan Jones <matthew.brennan.jones@gmail.com> 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE_1_0.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 module dscanner.analysis.helpers; 7 8 import core.exception : AssertError; 9 import std..string; 10 import std.traits; 11 import std.stdio; 12 13 import dparse.ast; 14 import dparse.rollback_allocator; 15 import dsymbol.modulecache : ModuleCache; 16 import dscanner.analysis.config; 17 import dscanner.analysis.run; 18 import dscanner.analysis.base; 19 import stdx.allocator.mallocator; 20 import stdx.allocator; 21 22 S between(S)(S value, S before, S after) if (isSomeString!S) 23 { 24 return value.after(before).before(after); 25 } 26 27 S before(S)(S value, S separator) if (isSomeString!S) 28 { 29 immutable i = indexOf(value, separator); 30 if (i == -1) 31 return value; 32 return value[0 .. i]; 33 } 34 35 S after(S)(S value, S separator) if (isSomeString!S) 36 { 37 immutable i = indexOf(value, separator); 38 if (i == -1) 39 return ""; 40 return value[i + separator.length .. $]; 41 } 42 43 /** 44 * This assert function will analyze the passed in code, get the warnings, 45 * and make sure they match the warnings in the comments. Warnings are 46 * marked like so: // [warn]: Failed to do somethings. 47 */ 48 void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, 49 string file = __FILE__, size_t line = __LINE__) 50 { 51 import dscanner.analysis.run : parseModule; 52 import dparse.lexer : StringCache, Token; 53 54 StringCache cache = StringCache(StringCache.defaultBucketCount); 55 RollbackAllocator r; 56 const(Token)[] tokens; 57 const(Module) m = parseModule(file, cast(ubyte[]) code, &r, defaultErrorFormat, cache, false, tokens); 58 59 auto moduleCache = ModuleCache(new CAllocatorImpl!Mallocator); 60 61 // Run the code and get any warnings 62 MessageSet rawWarnings = analyze("test", m, config, moduleCache, tokens); 63 string[] codeLines = code.splitLines(); 64 65 // Get the warnings ordered by line 66 string[size_t] warnings; 67 foreach (rawWarning; rawWarnings[]) 68 { 69 // Skip the warning if it is on line zero 70 immutable size_t rawLine = rawWarning.line; 71 if (rawLine == 0) 72 { 73 stderr.writefln("!!! Skipping warning because it is on line zero:\n%s", 74 rawWarning.message); 75 continue; 76 } 77 78 size_t warnLine = line - 1 + rawLine; 79 warnings[warnLine] = format("[warn]: %s", rawWarning.message); 80 } 81 82 // Get all the messages from the comments in the code 83 string[size_t] messages; 84 foreach (i, codeLine; codeLines) 85 { 86 // Skip if no [warn] comment 87 if (codeLine.indexOf("// [warn]:") == -1) 88 continue; 89 90 // Skip if there is no comment or code 91 immutable string codePart = codeLine.before("// "); 92 immutable string commentPart = codeLine.after("// "); 93 if (!codePart.length || !commentPart.length) 94 continue; 95 96 // Get the line of this code line 97 size_t lineNo = i + line; 98 99 // Get the message 100 messages[lineNo] = commentPart; 101 } 102 103 // Throw an assert error if any messages are not listed in the warnings 104 foreach (lineNo, message; messages) 105 { 106 // No warning 107 if (lineNo !in warnings) 108 { 109 immutable string errors = "Expected warning:\n%s\nFrom source code at (%s:?):\n%s".format(messages[lineNo], 110 lineNo, codeLines[lineNo - line]); 111 throw new AssertError(errors, file, lineNo); 112 } 113 // Different warning 114 else if (warnings[lineNo] != messages[lineNo]) 115 { 116 immutable string errors = "Expected warning:\n%s\nBut was:\n%s\nFrom source code at (%s:?):\n%s".format( 117 messages[lineNo], warnings[lineNo], lineNo, codeLines[lineNo - line]); 118 throw new AssertError(errors, file, lineNo); 119 } 120 } 121 122 // Throw an assert error if there were any warnings that were not expected 123 string[] unexpectedWarnings; 124 foreach (lineNo, warning; warnings) 125 { 126 // Unexpected warning 127 if (lineNo !in messages) 128 { 129 unexpectedWarnings ~= "%s\nFrom source code at (%s:?):\n%s".format(warning, 130 lineNo, codeLines[lineNo - line]); 131 } 132 } 133 if (unexpectedWarnings.length) 134 { 135 immutable string message = "Unexpected warnings:\n" ~ unexpectedWarnings.join("\n"); 136 throw new AssertError(message, file, line); 137 } 138 }