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 }