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.specifiedFunctionBody || 94 (dec.functionBody.missingFunctionBody && !winStyle())) 95 checkLowercaseName("Function", dec.name); 96 97 if (p) 98 popWinStyle; 99 } 100 101 void checkLowercaseName(string type, ref const Token name) 102 { 103 if (name.text.length > 0 && name.text.matchFirst(varFunNameRegex).length == 0) 104 addErrorMessage(name.line, name.column, KEY, 105 type ~ " name '" ~ name.text ~ "' does not match style guidelines."); 106 } 107 108 override void visit(const ClassDeclaration dec) 109 { 110 checkAggregateName("Class", dec.name); 111 dec.accept(this); 112 } 113 114 override void visit(const InterfaceDeclaration dec) 115 { 116 checkAggregateName("Interface", dec.name); 117 dec.accept(this); 118 } 119 120 override void visit(const EnumDeclaration dec) 121 { 122 if (dec.name.text is null || dec.name.text.length == 0) 123 return; 124 checkAggregateName("Enum", dec.name); 125 dec.accept(this); 126 } 127 128 override void visit(const StructDeclaration dec) 129 { 130 checkAggregateName("Struct", dec.name); 131 dec.accept(this); 132 } 133 134 void checkAggregateName(string aggregateType, ref const Token name) 135 { 136 if (name.text.length > 0 && name.text.matchFirst(aggregateNameRegex).length == 0) 137 addErrorMessage(name.line, name.column, KEY, 138 aggregateType ~ " name '" ~ name.text ~ "' does not match style guidelines."); 139 } 140 141 bool[] _winStyles = [false]; 142 143 bool winStyle() 144 { 145 return _winStyles[$-1]; 146 } 147 148 void pushWinStyle(const bool value) 149 { 150 _winStyles.length += 1; 151 _winStyles[$-1] = value; 152 } 153 154 void popWinStyle() 155 { 156 _winStyles.length -= 1; 157 } 158 } 159 160 unittest 161 { 162 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 163 164 StaticAnalysisConfig sac = disabledConfig(); 165 sac.style_check = Check.enabled; 166 167 assertAnalyzerWarnings(q{ 168 module AMODULE; // [warn]: Module/package name 'AMODULE' does not match style guidelines. 169 170 bool A_VARIABLE; // FIXME: 171 bool a_variable; // ok 172 bool aVariable; // ok 173 174 void A_FUNCTION() {} // FIXME: 175 class cat {} // [warn]: Class name 'cat' does not match style guidelines. 176 interface puma {} // [warn]: Interface name 'puma' does not match style guidelines. 177 struct dog {} // [warn]: Struct name 'dog' does not match style guidelines. 178 enum racoon { a } // [warn]: Enum name 'racoon' does not match style guidelines. 179 enum bool something = false; 180 enum bool someThing = false; 181 enum Cat { fritz, } 182 enum Cat = Cat.fritz; 183 }}, sac); 184 185 assertAnalyzerWarnings(q{ 186 extern(Windows) 187 { 188 bool Fun0(); 189 extern(Windows) bool Fun1(); 190 } 191 }}, sac); 192 193 assertAnalyzerWarnings(q{ 194 extern(Windows) 195 { 196 extern(D) bool Fun2(); // [warn]: Function name 'Fun2' does not match style guidelines. 197 bool Fun3(); 198 } 199 }}, sac); 200 201 assertAnalyzerWarnings(q{ 202 extern(Windows) 203 { 204 extern(C): 205 extern(D) bool Fun4(); // [warn]: Function name 'Fun4' does not match style guidelines. 206 bool Fun5(); // [warn]: Function name 'Fun5' does not match style guidelines. 207 } 208 }}, sac); 209 210 assertAnalyzerWarnings(q{ 211 extern(Windows): 212 bool Fun6(); 213 bool Fun7(); 214 extern(D): 215 void okOkay(); 216 void NotReallyOkay(); // [warn]: Function name 'NotReallyOkay' does not match style guidelines. 217 }}, sac); 218 219 assertAnalyzerWarnings(q{ 220 extern(Windows): 221 bool WinButWithBody(){} // [warn]: Function name 'WinButWithBody' does not match style guidelines. 222 }}, sac); 223 224 stderr.writeln("Unittest for StyleChecker passed."); 225 }