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.specifiedFunctionBody ||
94 			(dec.functionBody.missingFunctionBody && !winStyle()))
95 				checkLowercaseName("Function", dec.name);
96 
97 		if (p)
98 			popWinStyle;
99 	}
100 
101 	void checkLowercaseName(string type, ref const Token name)
102 	{
103 		if (name.text.length > 0 && name.text.matchFirst(varFunNameRegex).length == 0)
104 			addErrorMessage(name.line, name.column, KEY,
105 					type ~ " name '" ~ name.text ~ "' does not match style guidelines.");
106 	}
107 
108 	override void visit(const ClassDeclaration dec)
109 	{
110 		checkAggregateName("Class", dec.name);
111 		dec.accept(this);
112 	}
113 
114 	override void visit(const InterfaceDeclaration dec)
115 	{
116 		checkAggregateName("Interface", dec.name);
117 		dec.accept(this);
118 	}
119 
120 	override void visit(const EnumDeclaration dec)
121 	{
122 		if (dec.name.text is null || dec.name.text.length == 0)
123 			return;
124 		checkAggregateName("Enum", dec.name);
125 		dec.accept(this);
126 	}
127 
128 	override void visit(const StructDeclaration dec)
129 	{
130 		checkAggregateName("Struct", dec.name);
131 		dec.accept(this);
132 	}
133 
134 	void checkAggregateName(string aggregateType, ref const Token name)
135 	{
136 		if (name.text.length > 0 && name.text.matchFirst(aggregateNameRegex).length == 0)
137 			addErrorMessage(name.line, name.column, KEY,
138 					aggregateType ~ " name '" ~ name.text ~ "' does not match style guidelines.");
139 	}
140 
141 	bool[] _winStyles = [false];
142 
143 	bool winStyle()
144 	{
145 		return _winStyles[$-1];
146 	}
147 
148 	void pushWinStyle(const bool value)
149 	{
150 		_winStyles.length += 1;
151 		_winStyles[$-1] = value;
152 	}
153 
154 	void popWinStyle()
155 	{
156 		_winStyles.length -= 1;
157 	}
158 }
159 
160 unittest
161 {
162 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
163 
164 	StaticAnalysisConfig sac = disabledConfig();
165 	sac.style_check = Check.enabled;
166 
167 	assertAnalyzerWarnings(q{
168 		module AMODULE; // [warn]: Module/package name 'AMODULE' does not match style guidelines.
169 
170 		bool A_VARIABLE; // FIXME:
171 		bool a_variable; // ok
172 		bool aVariable; // ok
173 
174 		void A_FUNCTION() {} // FIXME:
175 		class cat {} // [warn]: Class name 'cat' does not match style guidelines.
176 		interface puma {} // [warn]: Interface name 'puma' does not match style guidelines.
177 		struct dog {} // [warn]: Struct name 'dog' does not match style guidelines.
178 		enum racoon { a } // [warn]: Enum name 'racoon' does not match style guidelines.
179 		enum bool something = false;
180 		enum bool someThing = false;
181 		enum Cat { fritz, }
182 		enum Cat = Cat.fritz;
183 	}}, sac);
184 
185 	assertAnalyzerWarnings(q{
186 		extern(Windows)
187 		{
188 			bool Fun0();
189 			extern(Windows) bool Fun1();
190 		}
191 	}}, sac);
192 
193 	assertAnalyzerWarnings(q{
194 		extern(Windows)
195 		{
196 			extern(D) bool Fun2(); // [warn]: Function name 'Fun2' does not match style guidelines.
197 			bool Fun3();
198 		}
199 	}}, sac);
200 
201 	assertAnalyzerWarnings(q{
202 		extern(Windows)
203 		{
204 			extern(C):
205 				extern(D) bool Fun4(); // [warn]: Function name 'Fun4' does not match style guidelines.
206 				bool Fun5(); // [warn]: Function name 'Fun5' does not match style guidelines.
207 		}
208 	}}, sac);
209 
210 	assertAnalyzerWarnings(q{
211 		extern(Windows):
212 			bool Fun6();
213 			bool Fun7();
214 		extern(D):
215 			void okOkay();
216 			void NotReallyOkay(); // [warn]: Function name 'NotReallyOkay' does not match style guidelines.
217 	}}, sac);
218 
219 	assertAnalyzerWarnings(q{
220 		extern(Windows):
221 			bool WinButWithBody(){} // [warn]: Function name 'WinButWithBody' does not match style guidelines.
222 	}}, sac);
223 
224 	stderr.writeln("Unittest for StyleChecker passed.");
225 }