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 dscanner.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 dscanner.analysis.config;
17 import dscanner.analysis.run;
18 import dscanner.analysis.base;
19 import stdx.allocator.mallocator;
20 import stdx.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 dscanner.analysis.run : parseModule;
52 	import dparse.lexer : StringCache, Token;
53 
54 	StringCache cache = StringCache(StringCache.defaultBucketCount);
55 	RollbackAllocator r;
56 	const(Token)[] tokens;
57 	const(Module) m = parseModule(file, cast(ubyte[]) code, &r, defaultErrorFormat, cache, false, tokens);
58 
59 	auto moduleCache = ModuleCache(new CAllocatorImpl!Mallocator);
60 
61 	// Run the code and get any warnings
62 	MessageSet rawWarnings = analyze("test", m, config, moduleCache, tokens);
63 	string[] codeLines = code.splitLines();
64 
65 	// Get the warnings ordered by line
66 	string[size_t] warnings;
67 	foreach (rawWarning; rawWarnings[])
68 	{
69 		// Skip the warning if it is on line zero
70 		immutable size_t rawLine = rawWarning.line;
71 		if (rawLine == 0)
72 		{
73 			stderr.writefln("!!! Skipping warning because it is on line zero:\n%s",
74 					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(messages[lineNo],
110 					lineNo, codeLines[lineNo - line]);
111 			throw new AssertError(errors, file, lineNo);
112 		}
113 		// Different warning
114 		else if (warnings[lineNo] != messages[lineNo])
115 		{
116 			immutable string errors = "Expected warning:\n%s\nBut was:\n%s\nFrom source code at (%s:?):\n%s".format(
117 					messages[lineNo], warnings[lineNo], lineNo, codeLines[lineNo - line]);
118 			throw new AssertError(errors, file, lineNo);
119 		}
120 	}
121 
122 	// Throw an assert error if there were any warnings that were not expected
123 	string[] unexpectedWarnings;
124 	foreach (lineNo, warning; warnings)
125 	{
126 		// Unexpected warning
127 		if (lineNo !in messages)
128 		{
129 			unexpectedWarnings ~= "%s\nFrom source code at (%s:?):\n%s".format(warning,
130 					lineNo, codeLines[lineNo - line]);
131 		}
132 	}
133 	if (unexpectedWarnings.length)
134 	{
135 		immutable string message = "Unexpected warnings:\n" ~ unexpectedWarnings.join("\n");
136 		throw new AssertError(message, file, line);
137 	}
138 }