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 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 analysis.helpers;
16 import analysis.base;
17 import dsymbol.scope_ : Scope;
18 
19 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)
29 	{
30 		super(fileName, sc);
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 	override void visit(const Declarator dec)
44 	{
45 		checkLowercaseName("Variable", dec.name);
46 	}
47 
48 	override void visit(const FunctionDeclaration dec)
49 	{
50 		checkLowercaseName("Function", dec.name);
51 	}
52 
53 	void checkLowercaseName(string type, ref const Token name)
54 	{
55 		if (name.text.length > 0 && name.text.matchFirst(varFunNameRegex).length == 0)
56 			addErrorMessage(name.line, name.column, KEY,
57 					type ~ " name '" ~ name.text ~ "' does not match style guidelines.");
58 	}
59 
60 	override void visit(const ClassDeclaration dec)
61 	{
62 		checkAggregateName("Class", dec.name);
63 		dec.accept(this);
64 	}
65 
66 	override void visit(const InterfaceDeclaration dec)
67 	{
68 		checkAggregateName("Interface", dec.name);
69 		dec.accept(this);
70 	}
71 
72 	override void visit(const EnumDeclaration dec)
73 	{
74 		if (dec.name.text is null || dec.name.text.length == 0)
75 			return;
76 		checkAggregateName("Enum", dec.name);
77 		dec.accept(this);
78 	}
79 
80 	override void visit(const StructDeclaration dec)
81 	{
82 		checkAggregateName("Struct", dec.name);
83 		dec.accept(this);
84 	}
85 
86 	void checkAggregateName(string aggregateType, ref const Token name)
87 	{
88 		if (name.text.length > 0 && name.text.matchFirst(aggregateNameRegex).length == 0)
89 			addErrorMessage(name.line, name.column, KEY,
90 					aggregateType ~ " name '" ~ name.text ~ "' does not match style guidelines.");
91 	}
92 }
93 
94 unittest
95 {
96 	import analysis.config : StaticAnalysisConfig;
97 
98 	StaticAnalysisConfig sac;
99 	sac.style_check = true;
100 
101 	assertAnalyzerWarnings(q{
102 		module AMODULE; // [warn]: Module/package name 'AMODULE' does not match style guidelines.
103 
104 		bool A_VARIABLE; // FIXME:
105 		bool a_variable; // ok
106 		bool aVariable; // ok
107 
108 		void A_FUNCTION() {} // FIXME:
109 		class cat {} // [warn]: Class name 'cat' does not match style guidelines.
110 		interface puma {} // [warn]: Interface name 'puma' does not match style guidelines.
111 		struct dog {} // [warn]: Struct name 'dog' does not match style guidelines.
112 		enum racoon {} // [warn]: Enum name 'racoon' does not match style guidelines.
113 	}}, sac);
114 
115 	stderr.writeln("Unittest for StyleChecker passed.");
116 }