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;
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 final class AllManCheck : BaseAnalyzer
29 {
30 	mixin AnalyzerInfo!"allman_braces_check";
31 
32 	///
33 	this(string fileName, const(Token)[] tokens, bool skipTests = false)
34 	{
35 		super(fileName, null, skipTests);
36 		foreach (i; 1 .. tokens.length - 1)
37 		{
38 			const curLine = tokens[i].line;
39 			const prevTokenLine = tokens[i-1].line;
40 			if (tokens[i].type == tok!"{" && curLine == prevTokenLine)
41 			{
42 				// ignore struct initialization
43 				if (tokens[i-1].type == tok!"=")
44 					continue;
45 				// ignore duplicate braces
46 				if (tokens[i-1].type == tok!"{" && tokens[i - 2].line != curLine)
47 					continue;
48 				// ignore inline { } braces
49 				if (curLine != tokens[i + 1].line)
50 					addErrorMessage(tokens[i].line, tokens[i].column, KEY, MESSAGE);
51 			}
52 			if (tokens[i].type == tok!"}" && curLine == prevTokenLine)
53 			{
54 				// ignore duplicate braces
55 				if (tokens[i-1].type == tok!"}" && tokens[i - 2].line != curLine)
56 					continue;
57 				// ignore inline { } braces
58 				if (!tokens[0 .. i].retro.until!(t => t.line != curLine).canFind!(t => t.type == tok!"{"))
59 					addErrorMessage(tokens[i].line, tokens[i].column, KEY, MESSAGE);
60 			}
61 		}
62 	}
63 
64 	enum string KEY = "dscanner.style.allman";
65 	enum string MESSAGE = "Braces should be on their own line";
66 }
67 
68 unittest
69 {
70 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
71 	import dscanner.analysis.helpers : assertAnalyzerWarnings;
72 	import std.format : format;
73 	import std.stdio : stderr;
74 
75 	StaticAnalysisConfig sac = disabledConfig();
76 	sac.allman_braces_check = Check.enabled;
77 
78 	// check common allman style violation
79 	assertAnalyzerWarnings(q{
80 		void testAllman()
81 		{
82 			while (true) { // [warn]: %s
83 				auto f = 1;
84 			}
85 
86 			do { // [warn]: %s
87 				auto f = 1;
88 			} while (true);
89 
90 			// inline braces are OK
91 			while (true) { auto f = 1; }
92 
93 			if (true) { // [warn]: %s
94 				auto f = 1;
95 			}
96 			if (true)
97 			{
98 				auto f = 1; } // [warn]: %s
99 			if (true) { auto f = 1; }
100 			foreach (r; [1]) { // [warn]: %s
101 			}
102 			foreach (r; [1]) {	}
103 			foreach_reverse (r; [1]) { // [warn]: %s
104 			}
105 			foreach_reverse (r; [1]) {	}
106 			for (int i = 0; i < 10; i++) { // [warn]: %s
107 			}
108 			for (int i = 0; i < 10; i++) { }
109 
110 			// nested check
111 			while (true) { // [warn]: %s
112 				while (true) { // [warn]: %s
113 					auto f = 1;
114 				}
115 			}
116 		}
117 	}}.format(
118 		AllManCheck.MESSAGE,
119 		AllManCheck.MESSAGE,
120 		AllManCheck.MESSAGE,
121 		AllManCheck.MESSAGE,
122 		AllManCheck.MESSAGE,
123 		AllManCheck.MESSAGE,
124 		AllManCheck.MESSAGE,
125 		AllManCheck.MESSAGE,
126 		AllManCheck.MESSAGE,
127 	), sac);
128 
129 	// check struct initialization
130 	assertAnalyzerWarnings(q{
131 unittest
132 {
133 	struct Foo { int a; }
134 	Foo foo = {
135 		a: 1;
136 	};
137 }
138 	}, sac);
139 
140 	// allow duplicate braces
141 	assertAnalyzerWarnings(q{
142 unittest
143 {{
144 }}
145 	}, sac);
146 
147 
148 	stderr.writeln("Unittest for Allman passed.");
149 }