mirror of
https://github.com/dlang/tools.git
synced 2025-04-27 05:30:35 +03:00
194 lines
5.4 KiB
D
194 lines
5.4 KiB
D
/*
|
|
* Checks that all functions have a public example
|
|
*
|
|
* Copyright (C) 2016 by D Language Foundation
|
|
*
|
|
* Author: Sebastian Wilzbach
|
|
*
|
|
* 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)
|
|
{
|
|
Declaration lastDecl;
|
|
bool hasPublicUnittest;
|
|
|
|
foreach (decl; mod.declarations)
|
|
{
|
|
if (!isPublic(decl.attributes))
|
|
continue;
|
|
|
|
if (decl.functionDeclaration !is null || decl.templateDeclaration !is null)
|
|
{
|
|
if (lastDecl !is null &&
|
|
(hasDitto(decl.functionDeclaration) || hasDitto(decl.templateDeclaration)))
|
|
continue;
|
|
|
|
if (lastDecl !is null && !hasPublicUnittest)
|
|
triggerError(lastDecl);
|
|
|
|
if (hasDdocHeader(sourceCode, decl))
|
|
lastDecl = cast(Declaration) decl;
|
|
else
|
|
lastDecl = null;
|
|
|
|
hasPublicUnittest = false;
|
|
continue;
|
|
}
|
|
|
|
if (decl.unittest_ !is null)
|
|
{
|
|
// ignore module header unittest blocks or already validated functions
|
|
hasPublicUnittest |= lastDecl is null || hasDdocHeader(sourceCode, decl);
|
|
continue;
|
|
}
|
|
|
|
// ignore dittoed template declarations
|
|
if (decl.classDeclaration !is null && hasDitto(decl.classDeclaration)
|
|
|| decl.structDeclaration !is null && hasDitto(decl.structDeclaration))
|
|
continue;
|
|
|
|
// ran into struct or something else -> reset
|
|
if (lastDecl !is null && !hasPublicUnittest)
|
|
triggerError(lastDecl);
|
|
|
|
lastDecl = null;
|
|
}
|
|
|
|
if (lastDecl !is null && !hasPublicUnittest)
|
|
triggerError(lastDecl);
|
|
}
|
|
|
|
private:
|
|
string fileName;
|
|
ubyte[] sourceCode;
|
|
|
|
void triggerError(const Declaration decl)
|
|
{
|
|
if (auto fn = decl.functionDeclaration)
|
|
stderr.writefln("function %s in %s:%d has no public unittest", fn.name.text, fileName, fn.name.line);
|
|
if (auto tpl = decl.templateDeclaration)
|
|
stderr.writefln("template %s in %s:%d has no public unittest", tpl.name.text, fileName, tpl.name.line);
|
|
hadError = true;
|
|
}
|
|
|
|
bool hasDitto(Decl)(const Decl decl)
|
|
{
|
|
if (decl is null)
|
|
return false;
|
|
|
|
if (decl.comment is null)
|
|
return false;
|
|
|
|
if (decl.comment.among!("ditto", "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.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);
|
|
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(`has_public_example
|
|
Searches the input directory recursively to ensure that all public, ddoced functions
|
|
have at least one public, ddoced unittest blocks.
|
|
`, 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);
|
|
}
|