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