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 }