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 analysis.helpers; 7 8 import std.string; 9 import std.traits; 10 import std.stdio; 11 12 import std.d.ast; 13 import analysis.config; 14 import analysis.run; 15 import analysis.base; 16 17 18 S between(S)(S value, S before, S after) 19 if (isSomeString!S) 20 { 21 return value.after(before).before(after); 22 } 23 24 S before(S)(S value, S separator) 25 if (isSomeString!S) 26 { 27 auto i = indexOf(value, separator); 28 29 if (i == -1) 30 return value; 31 32 return value[0 .. i]; 33 } 34 35 S after(S)(S value, S separator) 36 if (isSomeString!S) 37 { 38 auto i = indexOf(value, separator); 39 40 if (i == -1) 41 return ""; 42 43 size_t start = i + separator.length; 44 45 return value[start .. $]; 46 } 47 48 /** 49 * This assert function will analyze the passed in code, get the warnings, 50 * and make sure they match the warnings in the comments. Warnings are 51 * marked like so: // [warn]: Failed to do somethings. 52 */ 53 void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, string file=__FILE__, size_t line=__LINE__) 54 { 55 import analysis.run : ParseAllocator, parseModule; 56 import std.d.lexer : StringCache; 57 58 StringCache cache = StringCache(StringCache.defaultBucketCount); 59 ParseAllocator p = new ParseAllocator; 60 const(Module) m = parseModule(file, cast(ubyte[]) code, p, cache, false); 61 62 // Run the code and get any warnings 63 MessageSet rawWarnings = analyze("test", m, config); 64 string[] codeLines = code.split("\n"); 65 66 // Get the warnings ordered by line 67 string[size_t] warnings; 68 foreach (rawWarning; rawWarnings[]) 69 { 70 // Skip the warning if it is on line zero 71 immutable size_t rawLine = rawWarning.line; 72 if (rawLine == 0) 73 { 74 stderr.writefln("!!! Skipping warning because it is on line zero:\n%s", 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( 110 messages[lineNo], 111 lineNo, 112 codeLines[lineNo - line] 113 ); 114 throw new core.exception.AssertError(errors, file, lineNo); 115 } 116 // Different warning 117 else if (warnings[lineNo] != messages[lineNo]) 118 { 119 immutable string errors = "Expected warning:\n%s\nBut was:\n%s\nFrom source code at (%s:?):\n%s".format( 120 messages[lineNo], 121 warnings[lineNo], 122 lineNo, 123 codeLines[lineNo - line] 124 ); 125 throw new core.exception.AssertError(errors, file, lineNo); 126 } 127 } 128 129 // Throw an assert error if there were any warnings that were not expected 130 string[] unexpectedWarnings; 131 foreach (lineNo, warning; warnings) 132 { 133 // Unexpected warning 134 if (lineNo !in messages) 135 { 136 unexpectedWarnings ~= "%s\nFrom source code at (%s:?):\n%s".format( 137 warning, 138 lineNo, 139 codeLines[lineNo - line] 140 ); 141 } 142 } 143 if (unexpectedWarnings.length) 144 { 145 immutable string message = "Unexpected warnings:\n" ~ unexpectedWarnings.join("\n"); 146 throw new core.exception.AssertError(message, file, line); 147 } 148 } 149