Skip to content

Commit

Permalink
Add has_public_example and tests_extractor
Browse files Browse the repository at this point in the history
  • Loading branch information
wilzbach committed Dec 20, 2016
1 parent 184f5e6 commit 21a23b8
Show file tree
Hide file tree
Showing 7 changed files with 415 additions and 49 deletions.
20 changes: 19 additions & 1 deletion README.md
Expand Up @@ -19,13 +19,31 @@ dget | Internal | D source code downloader.
dman | Public | D documentation lookup tool.
dustmite | Public | [Test case minimization tool](https://github.com/CyberShadow/DustMite/wiki).
get_dlibcurl32 | Internal | Win32 libcurl downloader/converter.
phobos_tests_extractor | Internal | Extracts public unittests from Phobos (requires DUB)
has_public_example | Internal | Checks public functions for public examples (requires DUB)
rdmd | Public | [D build tool](http://dlang.org/rdmd.html).
rdmd_test | Internal | rdmd test suite.
tests_extractor | Internal | Extracts public unittests (requires DUB)
tolf | Internal | Line endings converter.

To report a problem or browse the list of open bugs, please visit the
[bug tracker](http://issues.dlang.org/).

For a list and descriptions of D development tools, please visit the
[D wiki](http://wiki.dlang.org/Development_tools).

Running DUB tools
-----------------

Some tools require D's package manager DUB.
By default DUB builds a binary and executes it:

```
dub --root styles -c has_public_example
```

Remember that when programs are run via DUB, you need to pass in `--` before
the program's arguments, e.g `dub --root styles -c has_public_example -- -i ../phobos/std/algorithm`.

For more information, please see [DUB's documentation][dub-doc].

[dub-doc]: https://code.dlang.org/docs/commandline
4 changes: 4 additions & 0 deletions styles/.gitignore
@@ -0,0 +1,4 @@
.dub
has_public_example
test_extractor
out
16 changes: 16 additions & 0 deletions styles/dub.sdl
@@ -0,0 +1,16 @@
dependency "libdparse" version="~>0.7.0-beta.2"
name "styles"
targetType "executable"
sourceFiles "utils.d"

configuration "has_public_example" {
name "has_public_example"
targetName "has_public_example"
sourceFiles "has_public_example.d"
}

configuration "tests_extractor" {
name "test_extractor"
targetName "test_extractor"
sourceFiles "tests_extractor.d"
}
6 changes: 6 additions & 0 deletions styles/dub.selections.json
@@ -0,0 +1,6 @@
{
"fileVersion": 1,
"versions": {
"libdparse": "0.7.0-beta.2"
}
}
206 changes: 206 additions & 0 deletions styles/has_public_example.d
@@ -0,0 +1,206 @@
/*
* Checks that all functions have a public example
*
* Copyright (C) 2016 by D Language Foundation
*
* 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)
*/
// Written in the D programming language.

import dparse.ast;
import std.algorithm;
import std.experimental.logger;
import std.range;
import std.stdio;
import utils;

bool hadError;

class TestVisitor : ASTVisitor
{

this(string fileName, ubyte[] sourceCode)
{
this.fileName = fileName;
this.sourceCode = sourceCode;
}

alias visit = ASTVisitor.visit;

override void visit(const Module mod)
{
FunctionDeclaration lastFun;
bool hasPublicUnittest;

foreach (decl; mod.declarations)
{
if (!isPublic(decl.attributes))
continue;

if (decl.functionDeclaration !is null)
{
if (hasDitto(decl.functionDeclaration))
continue;

if (lastFun !is null && !hasPublicUnittest)
triggerError(lastFun);

lastFun = cast(FunctionDeclaration) decl.functionDeclaration;
//debug {
//lastFun.name.text.writeln;
//}
hasPublicUnittest = false;
continue;
}

if (decl.unittest_ !is null)
{
hasPublicUnittest |= validate(lastFun, decl);
continue;
}

// ignore dittoed template declarations
if (decl.templateDeclaration !is null)
if (hasDitto(decl.templateDeclaration))
continue;

// ignore dittoed struct declarations
if (decl.structDeclaration !is null)
if (hasDitto(decl.structDeclaration))
continue;

// ran into struct or something else -> reset
if (lastFun !is null && !hasPublicUnittest)
triggerError(lastFun);

lastFun = null;
}

if (lastFun !is null && !hasPublicUnittest)
triggerError(lastFun);
}

private:
string fileName;
ubyte[] sourceCode;

void triggerError(const FunctionDeclaration decl)
{
stderr.writefln("%s:%d %s has no public unittest", fileName, decl.name.line, decl.name.text);
hadError = true;
}

bool validate(const FunctionDeclaration lastFun, const Declaration decl)
{
// ignore module header unittest blocks or already validated functions
if (lastFun is null)
return true;

if (!hasUnittestDdocHeader(sourceCode, decl))
return false;

return true;
}

bool hasDitto(Decl)(const Decl decl)
{
if (decl.comment is null)
return false;

if (decl.comment == "ditto")
return true;

if (decl.comment == "Ditto")
return true;

return false;
}

bool isPublic(const Attribute[] attrs)
{
import dparse.lexer : tok;
import std.algorithm.searching : any;
import std.algorithm.iteration : map;

enum tokPrivate = tok!"private", tokProtected = tok!"protected", tokPackage = tok!"package";

if (attrs !is null)
if (attrs.map!`a.attribute`.any!(x => x == tokPrivate || x == tokProtected || x == tokPackage))
return false;

return true;
}
}

void parseFile(string fileName)
{
import dparse.lexer;
import dparse.parser : parseModule;
import dparse.rollback_allocator : RollbackAllocator;
import std.array : uninitializedArray;

auto inFile = File(fileName, "r");
if (inFile.size == 0)
warningf("%s is empty", inFile.name);

ubyte[] sourceCode = uninitializedArray!(ubyte[])(to!size_t(inFile.size));
inFile.rawRead(sourceCode);
LexerConfig config;
auto cache = StringCache(StringCache.defaultBucketCount);
auto tokens = getTokensForParser(sourceCode, config, &cache);

RollbackAllocator rba;
auto m = parseModule(tokens.array, fileName, &rba);
auto visitor = new TestVisitor(fileName, sourceCode);
visitor.visit(m);
}

void main(string[] args)
{
import std.file;
import std.getopt;
import std.path : asNormalizedPath;

string inputDir;
string ignoredFilesStr;

auto helpInfo = getopt(args, config.required,
"inputdir|i", "Folder to start the recursive search for unittest blocks (can be a single file)", &inputDir,
"ignore", "Comma-separated list of files to exclude (partial matching is supported)", &ignoredFilesStr);

if (helpInfo.helpWanted)
{
return defaultGetoptPrinter(`example_validator
Searches the input directory recursively to ensure that all public functions
have a public unittest blocks, i.e.
unittest blocks that are annotated with three slashes (///).
`, helpInfo.options);
}

inputDir = inputDir.asNormalizedPath.array;

DirEntry[] files;

if (inputDir.isFile)
{
files = [DirEntry(inputDir)];
inputDir = ".";
}
else
{
files = dirEntries(inputDir, SpanMode.depth).filter!(
a => a.name.endsWith(".d") && !a.name.canFind(".git")).array;
}

auto ignoringFiles = ignoredFilesStr.split(",");

foreach (file; files)
if (!ignoringFiles.any!(x => file.name.canFind(x)))
file.name.parseFile;

import core.stdc.stdlib : exit;
if (hadError)
exit(1);
}

0 comments on commit 21a23b8

Please sign in to comment.