1 //          Copyright Brian Schott (Hackerpilot) 2014.
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.style;
7 
8 import std.stdio;
9 import dparse.ast;
10 import dparse.lexer;
11 import std.regex;
12 import std.array;
13 import std.conv;
14 import std.format;
15 import dscanner.analysis.helpers;
16 import dscanner.analysis.base;
17 import dsymbol.scope_ : Scope;
18 
19 final class StyleChecker : BaseAnalyzer
20 {
21 	alias visit = ASTVisitor.visit;
22 
23 	enum string varFunNameRegex = `^([\p{Ll}_][_\w\d]*|[\p{Lu}\d_]+)$`;
24 	enum string aggregateNameRegex = `^\p{Lu}[\w\d]*$`;
25 	enum string moduleNameRegex = `^[\p{Ll}_\d]+$`;
26 	enum string KEY = "dscanner.style.phobos_naming_convention";
27 
28 	this(string fileName, const(Scope)* sc, bool skipTests = false)
29 	{
30 		super(fileName, sc, skipTests);
31 	}
32 
33 	override void visit(const ModuleDeclaration dec)
34 	{
35 		foreach (part; dec.moduleName.identifiers)
36 		{
37 			if (part.text.matchFirst(moduleNameRegex).length == 0)
38 				addErrorMessage(part.line, part.column, KEY,
39 						"Module/package name '" ~ part.text ~ "' does not match style guidelines.");
40 		}
41 	}
42 
43 	// "extern (Windows) {}" : push visit pop
44 	override void visit(const Declaration dec)
45 	{
46 		bool p;
47 		if (dec.attributes)
48 			foreach (attrib; dec.attributes)
49 				if (const LinkageAttribute la = attrib.linkageAttribute)
50 		{
51 			p = true;
52 			pushWinStyle(la.identifier.text.length && la.identifier.text == "Windows");
53 		}
54 
55 		dec.accept(this);
56 
57 		if (p)
58 			popWinStyle;
59 	}
60 
61 	// "extern (Windows) :" : overwrite current
62 	override void visit(const AttributeDeclaration dec)
63 	{
64 		if (dec.attribute && dec.attribute.linkageAttribute)
65 		{
66 			const LinkageAttribute la = dec.attribute.linkageAttribute;
67 			_winStyles[$-1] = la.identifier.text.length && la.identifier.text == "Windows";
68 		}
69 	}
70 
71 	override void visit(const VariableDeclaration vd)
72 	{
73 		vd.accept(this);
74 	}
75 
76 	override void visit(const Declarator dec)
77 	{
78 		checkLowercaseName("Variable", dec.name);
79 	}
80 
81 	override void visit(const FunctionDeclaration dec)
82 	{
83 		// "extern(Windows) Name();" push visit pop
84 		bool p;
85 		if (dec.attributes)
86 			foreach (attrib; dec.attributes)
87 				if (const LinkageAttribute la = attrib.linkageAttribute)
88 		{
89 			p = true;
90 			pushWinStyle(la.identifier.text.length && la.identifier.text == "Windows");
91 		}
92 
93 		if (dec.functionBody || (!dec.functionBody && !winStyle()))
94 			checkLowercaseName("Function", dec.name);
95 
96 		if (p)
97 			popWinStyle;
98 	}
99 
100 	void checkLowercaseName(string type, ref const Token name)
101 	{
102 		if (name.text.length > 0 && name.text.matchFirst(varFunNameRegex).length == 0)
103 			addErrorMessage(name.line, name.column, KEY,
104 					type ~ " name '" ~ name.text ~ "' does not match style guidelines.");
105 	}
106 
107 	override void visit(const ClassDeclaration dec)
108 	{
109 		checkAggregateName("Class", dec.name);
110 		dec.accept(this);
111 	}
112 
113 	override void visit(const InterfaceDeclaration dec)
114 	{
115 		checkAggregateName("Interface", dec.name);
116 		dec.accept(this);
117 	}
118 
119 	override void visit(const EnumDeclaration dec)
120 	{
121 		if (dec.name.text is null || dec.name.text.length == 0)
122 			return;
123 		checkAggregateName("Enum", dec.name);
124 		dec.accept(this);
125 	}
126 
127 	override void visit(const StructDeclaration dec)
128 	{
129 		checkAggregateName("Struct", dec.name);
130 		dec.accept(this);
131 	}
132 
133 	void checkAggregateName(string aggregateType, ref const Token name)
134 	{
135 		if (name.text.length > 0 && name.text.matchFirst(aggregateNameRegex).length == 0)
136 			addErrorMessage(name.line, name.column, KEY,
137 					aggregateType ~ " name '" ~ name.text ~ "' does not match style guidelines.");
138 	}
139 
140 	bool[] _winStyles = [false];
141 
142 	bool winStyle()
143 	{
144 		return _winStyles[$-1];
145 	}
146 
147 	void pushWinStyle(const bool value)
148 	{
149 		_winStyles.length += 1;
150 		_winStyles[$-1] = value;
151 	}
152 
153 	void popWinStyle()
154 	{
155 		_winStyles.length -= 1;
156 	}
157 }
158 
159 unittest
160 {
161 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
162 
163 	StaticAnalysisConfig sac = disabledConfig();
164 	sac.style_check = Check.enabled;
165 
166 	assertAnalyzerWarnings(q{
167 		module AMODULE; // [warn]: Module/package name 'AMODULE' does not match style guidelines.
168 
169 		bool A_VARIABLE; // FIXME:
170 		bool a_variable; // ok
171 		bool aVariable; // ok
172 
173 		void A_FUNCTION() {} // FIXME:
174 		class cat {} // [warn]: Class name 'cat' does not match style guidelines.
175 		interface puma {} // [warn]: Interface name 'puma' does not match style guidelines.
176 		struct dog {} // [warn]: Struct name 'dog' does not match style guidelines.
177 		enum racoon { a } // [warn]: Enum name 'racoon' does not match style guidelines.
178 		enum bool something = false;
179 		enum bool someThing = false;
180 		enum Cat { fritz, }
181 		enum Cat = Cat.fritz;
182 	}}, sac);
183 
184 	assertAnalyzerWarnings(q{
185 		extern(Windows)
186 		{
187 			bool Fun0();
188 			extern(Windows) bool Fun1();
189 		}
190 	}}, sac);
191 
192 	assertAnalyzerWarnings(q{
193 		extern(Windows)
194 		{
195 			extern(D) bool Fun2(); // [warn]: Function name 'Fun2' does not match style guidelines.
196 			bool Fun3();
197 		}
198 	}}, sac);
199 
200 	assertAnalyzerWarnings(q{
201 		extern(Windows)
202 		{
203 			extern(C):
204 				extern(D) bool Fun4(); // [warn]: Function name 'Fun4' does not match style guidelines.
205 				bool Fun5(); // [warn]: Function name 'Fun5' does not match style guidelines.
206 		}
207 	}}, sac);
208 
209 	assertAnalyzerWarnings(q{
210 		extern(Windows):
211 			bool Fun6();
212 			bool Fun7();
213 		extern(D):
214 			void okOkay();
215 			void NotReallyOkay(); // [warn]: Function name 'NotReallyOkay' does not match style guidelines.
216 	}}, sac);
217 
218 	assertAnalyzerWarnings(q{
219 		extern(Windows):
220 			bool WinButWithBody(){} // [warn]: Function name 'WinButWithBody' does not match style guidelines.
221 	}}, sac);
222 
223 	stderr.writeln("Unittest for StyleChecker passed.");
224 }