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