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