1 // Distributed under the Boost Software License, Version 1.0.
2 //	  (See accompanying file LICENSE_1_0.txt or copy at
3 //			http://www.boost.org/LICENSE_1_0.txt)
4 
5 module dscanner.analysis.allman;
6 
7 import dparse.lexer;
8 import dparse.ast;
9 import dscanner.analysis.base : BaseAnalyzer;
10 import dsymbol.scope_ : Scope;
11 
12 import std.algorithm;
13 import std.range;
14 
15 /**
16 Checks for the allman style (braces should be on their own line)
17 ------------
18 if (param < 0) {
19 }
20 ------------
21 should be
22 ------------
23 if (param < 0)
24 {
25 }
26 ------------
27 */
28 class AllManCheck : BaseAnalyzer
29 {
30 	///
31 	this(string fileName, const(Token)[] tokens, bool skipTests = false)
32 	{
33 		super(fileName, null, skipTests);
34 		foreach (i; 1 .. tokens.length - 1)
35 		{
36 			const curLine = tokens[i].line;
37 			const prevTokenLine = tokens[i-1].line;
38 			if (tokens[i].type == tok!"{" && curLine == prevTokenLine)
39 			{
40 				// ignore struct initialization
41 				if (tokens[i-1].type == tok!"=")
42 					continue;
43 				// ignore duplicate braces
44 				if (tokens[i-1].type == tok!"{" && tokens[i - 2].line != curLine)
45 					continue;
46 				// ignore inline { } braces
47 				if (curLine != tokens[i + 1].line)
48 					addErrorMessage(tokens[i].line, tokens[i].column, KEY, MESSAGE);
49 			}
50 			if (tokens[i].type == tok!"}" && curLine == prevTokenLine)
51 			{
52 				// ignore duplicate braces
53 				if (tokens[i-1].type == tok!"}" && tokens[i - 2].line != curLine)
54 					continue;
55 				// ignore inline { } braces
56 				if (!tokens[0 .. i].retro.until!(t => t.line != curLine).canFind!(t => t.type == tok!"{"))
57 					addErrorMessage(tokens[i].line, tokens[i].column, KEY, MESSAGE);
58 			}
59 		}
60 	}
61 
62 	enum string KEY = "dscanner.style.allman";
63 	enum string MESSAGE = "Braces should be on their own line";
64 }
65 
66 unittest
67 {
68 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
69 	import dscanner.analysis.helpers : assertAnalyzerWarnings;
70 	import std.format : format;
71 	import std.stdio : stderr;
72 
73 	StaticAnalysisConfig sac = disabledConfig();
74 	sac.allman_braces_check = Check.enabled;
75 
76 	// check common allman style violation
77 	assertAnalyzerWarnings(q{
78 		void testAllman()
79 		{
80 			while (true) { // [warn]: %s
81 				auto f = 1;
82 			}
83 
84 			do { // [warn]: %s
85 				auto f = 1;
86 			} while (true);
87 
88 			// inline braces are OK
89 			while (true) { auto f = 1; }
90 
91 			if (true) { // [warn]: %s
92 				auto f = 1;
93 			}
94 			if (true)
95 			{
96 				auto f = 1; } // [warn]: %s
97 			if (true) { auto f = 1; }
98 			foreach (r; [1]) { // [warn]: %s
99 			}
100 			foreach (r; [1]) {	}
101 			foreach_reverse (r; [1]) { // [warn]: %s
102 			}
103 			foreach_reverse (r; [1]) {	}
104 			for (int i = 0; i < 10; i++) { // [warn]: %s
105 			}
106 			for (int i = 0; i < 10; i++) { }
107 
108 			// nested check
109 			while (true) { // [warn]: %s
110 				while (true) { // [warn]: %s
111 					auto f = 1;
112 				}
113 			}
114 		}
115 	}c.format(
116 		AllManCheck.MESSAGE,
117 		AllManCheck.MESSAGE,
118 		AllManCheck.MESSAGE,
119 		AllManCheck.MESSAGE,
120 		AllManCheck.MESSAGE,
121 		AllManCheck.MESSAGE,
122 		AllManCheck.MESSAGE,
123 		AllManCheck.MESSAGE,
124 		AllManCheck.MESSAGE,
125 	), sac);
126 
127 	// check struct initialization
128 	assertAnalyzerWarnings(q{
129 unittest
130 {
131 	struct Foo { int a; }
132 	Foo foo = {
133 		a: 1;
134 	};
135 }
136 	}, sac);
137 
138 	// allow duplicate braces
139 	assertAnalyzerWarnings(q{
140 unittest
141 {{
142 }}
143 	}, sac);
144 
145 
146 	stderr.writeln("Unittest for Allman passed.");
147 }