diff --git a/build.sh b/build.sh index 25c1f57..be987c0 100755 --- a/build.sh +++ b/build.sh @@ -8,6 +8,7 @@ dmd\ astprinter.d\ formatter.d\ outliner.d\ + style.d\ stdx/*.d\ stdx/d/*.d\ datapicked/dpick/buffer/*.d\ diff --git a/main.d b/main.d index 5ddf6a0..d24e91b 100644 --- a/main.d +++ b/main.d @@ -25,6 +25,7 @@ import ctags; import astprinter; import imports; import outliner; +import style; int main(string[] args) { @@ -41,6 +42,7 @@ int main(string[] args) bool muffin; bool outline; bool tokenDump; + bool styleCheck; try { @@ -48,7 +50,8 @@ int main(string[] args) "ctags|c", &ctags, "recursive|r|R", &recursive, "help|h", &help, "tokenCount|t", &tokenCount, "syntaxCheck|s", &syntaxCheck, "ast|xml", &ast, "imports|i", &imports, "outline|o", &outline, - "tokenDump", &tokenDump, "muffinButton", &muffin); + "tokenDump", &tokenDump, "styleCheck", &styleCheck, + "muffinButton", &muffin); } catch (Exception e) { @@ -77,7 +80,7 @@ int main(string[] args) } auto optionCount = count!"a"([sloc, highlight, ctags, tokenCount, - syntaxCheck, ast, imports, outline, tokenDump]); + syntaxCheck, ast, imports, outline, tokenDump, styleCheck]); if (optionCount > 1) { stderr.writeln("Too many options specified"); @@ -112,6 +115,10 @@ int main(string[] args) { stdout.printCtags(expandArgs(args, recursive)); } + else if (styleCheck) + { + stdout.styleCheck(expandArgs(args, recursive)); + } else { bool usingStdin = args.length == 1; @@ -251,6 +258,10 @@ options: syntax errors to stdout. One error or warning is printed per line. If no files are specified, input is read from stdin. + --styleCheck [sourceFiles] + Lexes and parses sourceFiles, printing the line and column number of any + style guideline violations to stdout. + --ctags | -c sourceFile Generates ctags information from the given source code file. Note that ctags information requires a filename, so stdin cannot be used in place diff --git a/style.d b/style.d new file mode 100644 index 0000000..928eca2 --- /dev/null +++ b/style.d @@ -0,0 +1,96 @@ +// Copyright Brian Schott (Sir Alaran) 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 style; + +import stdx.d.ast; +import stdx.d.lexer; +import stdx.d.parser; +import std.stdio; +import std.regex; +import std.array; +import std.conv; + +void doNothing(string, size_t, size_t, string) {} + +void styleCheck(File output, string[] fileNames) +{ + foreach (fileName; fileNames) + { + File f = File(fileName); + auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size)); + f.rawRead(bytes); + auto tokens = byToken(bytes); + Module m = parseModule(tokens.array, fileName, &doNothing); + auto checker = new StyleChecker; + checker.fileName = fileName; + checker.visit(m); + } +} + +class StyleChecker : ASTVisitor +{ + enum varFunNameRegex = `^([\p{Ll}_][_\w\d]*|[\p{Lu}_]+)$`; + enum aggregateNameRegex = `^\p{Lu}[\w\d]*$`; + enum moduleNameRegex = `^\p{Ll}+$`; + + override void visit(ModuleDeclaration dec) + { + foreach (part; dec.moduleName.identifiers) + { + if (part.text.matchFirst(moduleNameRegex).length == 0) + writeln(fileName, "(", part.line, ":", part.column, ") ", + "Module/package name ", part.text, " does not match style guidelines"); + } + } + + override void visit(Declarator dec) + { + checkLowercaseName("Variable", dec.name); + } + + override void visit(FunctionDeclaration dec) + { + checkLowercaseName("Function", dec.name); + } + + void checkLowercaseName(string type, ref Token name) + { + if (name.text.matchFirst(varFunNameRegex).length == 0) + writeln(fileName, "(", name.line, ":", name.column, ") ", + type, " name ", name.text, " does not match style guidelines"); + } + + override void visit(ClassDeclaration dec) + { + checkAggregateName("Class", dec.name); + dec.accept(this); + } + + override void visit(EnumDeclaration dec) + { + if (dec.name.text is null || dec.name.text.length == 0) + return; + checkAggregateName("Enum", dec.name); + dec.accept(this); + } + + override void visit(StructDeclaration dec) + { + checkAggregateName("Struct", dec.name); + dec.accept(this); + } + + void checkAggregateName(string aggregateType, ref Token name) + { + if (name.text.matchFirst(aggregateNameRegex).length == 0) + writeln(fileName, "(", name.line, ":", name.column, ") ", + aggregateType, " name ", name.text, + " does not match style guidelines"); + } + + alias ASTVisitor.visit visit; + string fileName; +}