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 }