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 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 analysis.config; 17 import analysis.run; 18 import analysis.base; 19 import std.experimental.allocator.mallocator; 20 import std.experimental.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 analysis.run : parseModule; 52 import dparse.lexer : StringCache, Token; 53 import std.ascii : newline; 54 55 StringCache cache = StringCache(StringCache.defaultBucketCount); 56 RollbackAllocator r; 57 const(Token)[] tokens; 58 const(Module) m = parseModule(file, cast(ubyte[]) code, &r, cache, false, tokens); 59 60 auto moduleCache = ModuleCache(new CAllocatorImpl!Mallocator); 61 62 // Run the code and get any warnings 63 MessageSet rawWarnings = analyze("test", m, config, moduleCache, tokens); 64 string[] codeLines = code.split(newline); 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", 75 rawWarning.message); 76 continue; 77 } 78 79 size_t warnLine = line - 1 + rawLine; 80 warnings[warnLine] = format("[warn]: %s", rawWarning.message); 81 } 82 83 // Get all the messages from the comments in the code 84 string[size_t] messages; 85 foreach (i, codeLine; codeLines) 86 { 87 // Skip if no [warn] comment 88 if (codeLine.indexOf("// [warn]:") == -1) 89 continue; 90 91 // Skip if there is no comment or code 92 immutable string codePart = codeLine.before("// "); 93 immutable string commentPart = codeLine.after("// "); 94 if (!codePart.length || !commentPart.length) 95 continue; 96 97 // Get the line of this code line 98 size_t lineNo = i + line; 99 100 // Get the message 101 messages[lineNo] = commentPart; 102 } 103 104 // Throw an assert error if any messages are not listed in the warnings 105 foreach (lineNo, message; messages) 106 { 107 // No warning 108 if (lineNo !in warnings) 109 { 110 immutable string errors = "Expected warning:\n%s\nFrom source code at (%s:?):\n%s".format(messages[lineNo], 111 lineNo, codeLines[lineNo - line]); 112 throw new AssertError(errors, file, lineNo); 113 } 114 // Different warning 115 else if (warnings[lineNo] != messages[lineNo]) 116 { 117 immutable string errors = "Expected warning:\n%s\nBut was:\n%s\nFrom source code at (%s:?):\n%s".format( 118 messages[lineNo], warnings[lineNo], lineNo, codeLines[lineNo - line]); 119 throw new AssertError(errors, file, lineNo); 120 } 121 } 122 123 // Throw an assert error if there were any warnings that were not expected 124 string[] unexpectedWarnings; 125 foreach (lineNo, warning; warnings) 126 { 127 // Unexpected warning 128 if (lineNo !in messages) 129 { 130 unexpectedWarnings ~= "%s\nFrom source code at (%s:?):\n%s".format(warning, 131 lineNo, codeLines[lineNo - line]); 132 } 133 } 134 if (unexpectedWarnings.length) 135 { 136 immutable string message = "Unexpected warnings:\n" ~ unexpectedWarnings.join("\n"); 137 throw new AssertError(message, file, line); 138 } 139 }