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, 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 	override void visit(const VariableDeclaration vd)
44 	{
45 		import std.algorithm.iteration : filter;
46 
47 		varIsEnum = !vd.storageClasses.filter!(a => a.token == tok!"enum").empty;
48 		vd.accept(this);
49 	}
50 
51 	override void visit(const Declarator dec)
52 	{
53 		if (varIsEnum)
54 			checkAggregateName("Variable", dec.name);
55 		else
56 			checkLowercaseName("Variable", dec.name);
57 	}
58 
59 	override void visit(const FunctionDeclaration dec)
60 	{
61 		checkLowercaseName("Function", dec.name);
62 	}
63 
64 	void checkLowercaseName(string type, ref const Token name)
65 	{
66 		if (name.text.length > 0 && name.text.matchFirst(varFunNameRegex).length == 0)
67 			addErrorMessage(name.line, name.column, KEY,
68 					type ~ " name '" ~ name.text ~ "' does not match style guidelines.");
69 	}
70 
71 	override void visit(const ClassDeclaration dec)
72 	{
73 		checkAggregateName("Class", dec.name);
74 		dec.accept(this);
75 	}
76 
77 	override void visit(const InterfaceDeclaration dec)
78 	{
79 		checkAggregateName("Interface", dec.name);
80 		dec.accept(this);
81 	}
82 
83 	override void visit(const EnumDeclaration dec)
84 	{
85 		if (dec.name.text is null || dec.name.text.length == 0)
86 			return;
87 		checkAggregateName("Enum", dec.name);
88 		dec.accept(this);
89 	}
90 
91 	override void visit(const StructDeclaration dec)
92 	{
93 		checkAggregateName("Struct", dec.name);
94 		dec.accept(this);
95 	}
96 
97 	void checkAggregateName(string aggregateType, ref const Token name)
98 	{
99 		if (name.text.length > 0 && name.text.matchFirst(aggregateNameRegex).length == 0)
100 			addErrorMessage(name.line, name.column, KEY,
101 					aggregateType ~ " name '" ~ name.text ~ "' does not match style guidelines.");
102 	}
103 
104 	bool varIsEnum;
105 }
106 
107 unittest
108 {
109 	import analysis.config : StaticAnalysisConfig, Check;
110 
111 	StaticAnalysisConfig sac;
112 	sac.style_check = Check.enabled;
113 
114 	assertAnalyzerWarnings(q{
115 		module AMODULE; // [warn]: Module/package name 'AMODULE' does not match style guidelines.
116 
117 		bool A_VARIABLE; // FIXME:
118 		bool a_variable; // ok
119 		bool aVariable; // ok
120 
121 		void A_FUNCTION() {} // FIXME:
122 		class cat {} // [warn]: Class name 'cat' does not match style guidelines.
123 		interface puma {} // [warn]: Interface name 'puma' does not match style guidelines.
124 		struct dog {} // [warn]: Struct name 'dog' does not match style guidelines.
125 		enum racoon { a } // [warn]: Enum name 'racoon' does not match style guidelines.
126 		enum bool Something = false;
127 	}}, sac);
128 
129 	stderr.writeln("Unittest for StyleChecker passed.");
130 }