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