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 dparse.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 dparse.lexer : StringCache, Token; 57 58 StringCache cache = StringCache(StringCache.defaultBucketCount); 59 ParseAllocator p = new ParseAllocator; 60 const(Token)[] tokens; 61 const(Module) m = parseModule(file, cast(ubyte[]) code, p, cache, false, tokens); 62 63 auto moduleCache = ModuleCache(p); 64 65 // Run the code and get any warnings 66 MessageSet rawWarnings = analyze("test", m, config, moduleCache, tokens); 67 string[] codeLines = code.split("\n"); 68 69 // Get the warnings ordered by line 70 string[size_t] warnings; 71 foreach (rawWarning; rawWarnings[]) 72 { 73 // Skip the warning if it is on line zero 74 immutable size_t rawLine = rawWarning.line; 75 if (rawLine == 0) 76 { 77 stderr.writefln("!!! Skipping warning because it is on line zero:\n%s", rawWarning.message); 78 continue; 79 } 80 81 size_t warnLine = line - 1 + rawLine; 82 warnings[warnLine] = format("[warn]: %s", rawWarning.message); 83 } 84 85 // Get all the messages from the comments in the code 86 string[size_t] messages; 87 foreach (i, codeLine; codeLines) 88 { 89 // Skip if no [warn] comment 90 if (codeLine.indexOf("// [warn]:") == -1) 91 continue; 92 93 // Skip if there is no comment or code 94 immutable string codePart = codeLine.before("// "); 95 immutable string commentPart = codeLine.after("// "); 96 if (!codePart.length || !commentPart.length) 97 continue; 98 99 // Get the line of this code line 100 size_t lineNo = i + line; 101 102 // Get the message 103 messages[lineNo] = commentPart; 104 } 105 106 // Throw an assert error if any messages are not listed in the warnings 107 foreach (lineNo, message; messages) 108 { 109 // No warning 110 if (lineNo !in warnings) 111 { 112 immutable string errors = "Expected warning:\n%s\nFrom source code at (%s:?):\n%s".format( 113 messages[lineNo], 114 lineNo, 115 codeLines[lineNo - line] 116 ); 117 throw new core.exception.AssertError(errors, file, lineNo); 118 } 119 // Different warning 120 else if (warnings[lineNo] != messages[lineNo]) 121 { 122 immutable string errors = "Expected warning:\n%s\nBut was:\n%s\nFrom source code at (%s:?):\n%s".format( 123 messages[lineNo], 124 warnings[lineNo], 125 lineNo, 126 codeLines[lineNo - line] 127 ); 128 throw new core.exception.AssertError(errors, file, lineNo); 129 } 130 } 131 132 // Throw an assert error if there were any warnings that were not expected 133 string[] unexpectedWarnings; 134 foreach (lineNo, warning; warnings) 135 { 136 // Unexpected warning 137 if (lineNo !in messages) 138 { 139 unexpectedWarnings ~= "%s\nFrom source code at (%s:?):\n%s".format( 140 warning, 141 lineNo, 142 codeLines[lineNo - line] 143 ); 144 } 145 } 146 if (unexpectedWarnings.length) 147 { 148 immutable string message = "Unexpected warnings:\n" ~ unexpectedWarnings.join("\n"); 149 throw new core.exception.AssertError(message, file, line); 150 } 151 } 152