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