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