D-Scanner/src/dscanner/analysis/style.d

226 lines
5.6 KiB
D

// Copyright Brian Schott (Hackerpilot) 2014.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
module dscanner.analysis.style;
import std.stdio;
import dparse.ast;
import dparse.lexer;
import std.regex;
import std.array;
import std.conv;
import std.format;
import dscanner.analysis.helpers;
import dscanner.analysis.base;
import dsymbol.scope_ : Scope;
final class StyleChecker : BaseAnalyzer
{
alias visit = ASTVisitor.visit;
enum string varFunNameRegex = `^([\p{Ll}_][_\w\d]*|[\p{Lu}\d_]+)$`;
enum string aggregateNameRegex = `^\p{Lu}[\w\d]*$`;
enum string moduleNameRegex = `^[\p{Ll}_\d]+$`;
enum string KEY = "dscanner.style.phobos_naming_convention";
this(string fileName, const(Scope)* sc, bool skipTests = false)
{
super(fileName, sc, skipTests);
}
override void visit(const ModuleDeclaration dec)
{
foreach (part; dec.moduleName.identifiers)
{
if (part.text.matchFirst(moduleNameRegex).length == 0)
addErrorMessage(part.line, part.column, KEY,
"Module/package name '" ~ part.text ~ "' does not match style guidelines.");
}
}
// "extern (Windows) {}" : push visit pop
override void visit(const Declaration dec)
{
bool p;
if (dec.attributes)
foreach (attrib; dec.attributes)
if (const LinkageAttribute la = attrib.linkageAttribute)
{
p = true;
pushWinStyle(la.identifier.text.length && la.identifier.text == "Windows");
}
dec.accept(this);
if (p)
popWinStyle;
}
// "extern (Windows) :" : overwrite current
override void visit(const AttributeDeclaration dec)
{
if (dec.attribute && dec.attribute.linkageAttribute)
{
const LinkageAttribute la = dec.attribute.linkageAttribute;
_winStyles[$-1] = la.identifier.text.length && la.identifier.text == "Windows";
}
}
override void visit(const VariableDeclaration vd)
{
vd.accept(this);
}
override void visit(const Declarator dec)
{
checkLowercaseName("Variable", dec.name);
}
override void visit(const FunctionDeclaration dec)
{
// "extern(Windows) Name();" push visit pop
bool p;
if (dec.attributes)
foreach (attrib; dec.attributes)
if (const LinkageAttribute la = attrib.linkageAttribute)
{
p = true;
pushWinStyle(la.identifier.text.length && la.identifier.text == "Windows");
}
if (dec.functionBody.specifiedFunctionBody ||
(dec.functionBody.missingFunctionBody && !winStyle()))
checkLowercaseName("Function", dec.name);
if (p)
popWinStyle;
}
void checkLowercaseName(string type, ref const Token name)
{
if (name.text.length > 0 && name.text.matchFirst(varFunNameRegex).length == 0)
addErrorMessage(name.line, name.column, KEY,
type ~ " name '" ~ name.text ~ "' does not match style guidelines.");
}
override void visit(const ClassDeclaration dec)
{
checkAggregateName("Class", dec.name);
dec.accept(this);
}
override void visit(const InterfaceDeclaration dec)
{
checkAggregateName("Interface", dec.name);
dec.accept(this);
}
override void visit(const EnumDeclaration dec)
{
if (dec.name.text is null || dec.name.text.length == 0)
return;
checkAggregateName("Enum", dec.name);
dec.accept(this);
}
override void visit(const StructDeclaration dec)
{
checkAggregateName("Struct", dec.name);
dec.accept(this);
}
void checkAggregateName(string aggregateType, ref const Token name)
{
if (name.text.length > 0 && name.text.matchFirst(aggregateNameRegex).length == 0)
addErrorMessage(name.line, name.column, KEY,
aggregateType ~ " name '" ~ name.text ~ "' does not match style guidelines.");
}
bool[] _winStyles = [false];
bool winStyle()
{
return _winStyles[$-1];
}
void pushWinStyle(const bool value)
{
_winStyles.length += 1;
_winStyles[$-1] = value;
}
void popWinStyle()
{
_winStyles.length -= 1;
}
}
unittest
{
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
StaticAnalysisConfig sac = disabledConfig();
sac.style_check = Check.enabled;
assertAnalyzerWarnings(q{
module AMODULE; // [warn]: Module/package name 'AMODULE' does not match style guidelines.
bool A_VARIABLE; // FIXME:
bool a_variable; // ok
bool aVariable; // ok
void A_FUNCTION() {} // FIXME:
class cat {} // [warn]: Class name 'cat' does not match style guidelines.
interface puma {} // [warn]: Interface name 'puma' does not match style guidelines.
struct dog {} // [warn]: Struct name 'dog' does not match style guidelines.
enum racoon { a } // [warn]: Enum name 'racoon' does not match style guidelines.
enum bool something = false;
enum bool someThing = false;
enum Cat { fritz, }
enum Cat = Cat.fritz;
}c, sac);
assertAnalyzerWarnings(q{
extern(Windows)
{
bool Fun0();
extern(Windows) bool Fun1();
}
}c, sac);
assertAnalyzerWarnings(q{
extern(Windows)
{
extern(D) bool Fun2(); // [warn]: Function name 'Fun2' does not match style guidelines.
bool Fun3();
}
}c, sac);
assertAnalyzerWarnings(q{
extern(Windows)
{
extern(C):
extern(D) bool Fun4(); // [warn]: Function name 'Fun4' does not match style guidelines.
bool Fun5(); // [warn]: Function name 'Fun5' does not match style guidelines.
}
}c, sac);
assertAnalyzerWarnings(q{
extern(Windows):
bool Fun6();
bool Fun7();
extern(D):
void okOkay();
void NotReallyOkay(); // [warn]: Function name 'NotReallyOkay' does not match style guidelines.
}c, sac);
assertAnalyzerWarnings(q{
extern(Windows):
bool WinButWithBody(){} // [warn]: Function name 'WinButWithBody' does not match style guidelines.
}c, sac);
stderr.writeln("Unittest for StyleChecker passed.");
}