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 }