5081eede67
Add self-checking code that looks to see that overview is populated and alphabetized. Eventually, this will self-check to see if methods are collected into subtopics and have reciprocal 'see also' data. Standardize phrases so that they don't start with a capital or end with a period. Self-check is a work in progress, so it is not yet run by the bookmaker bots. The self-check should run cleanly, however. To run it: ./out/skia/bookmaker -b docs -k The expected output is doc stats. Self-check errors such as missing methods in the overview would be reported here if there are any. TBR=caryclark@google.com Docs-Preview: https://skia.org/?cl=93621 Bug: skia:6898 Change-Id: I8f1f817a7b083b13138ee33d1aa090445e9304c6 Reviewed-on: https://skia-review.googlesource.com/93621 Reviewed-by: Cary Clark <caryclark@skia.org> Commit-Queue: Cary Clark <caryclark@skia.org>
420 lines
15 KiB
C++
420 lines
15 KiB
C++
/*
|
|
* Copyright 2018 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "bookmaker.h"
|
|
|
|
|
|
// Check that mutiple like-named methods are under one Subtopic
|
|
|
|
// Check that all subtopics are in table of contents
|
|
|
|
// Check that SeeAlso reference each other
|
|
|
|
// Would be nice to check if other classes have 'create' methods that are included
|
|
// SkSurface::makeImageSnapShot should be referenced under SkImage 'creators'
|
|
|
|
class SelfChecker {
|
|
public:
|
|
SelfChecker(const BmhParser& bmh)
|
|
: fBmhParser(bmh)
|
|
{}
|
|
|
|
bool check() {
|
|
for (const auto& topic : fBmhParser.fTopicMap) {
|
|
Definition* topicDef = topic.second;
|
|
if (topicDef->fParent) {
|
|
continue;
|
|
}
|
|
if (!topicDef->isRoot()) {
|
|
return fBmhParser.reportError<bool>("expected root topic");
|
|
}
|
|
fRoot = topicDef->asRoot();
|
|
if (!this->checkMethodSummary()) {
|
|
return false;
|
|
}
|
|
if (!this->checkMethodSubtopic()) {
|
|
return false;
|
|
}
|
|
if (!this->checkSubtopicSummary()) {
|
|
return false;
|
|
}
|
|
if (!this->checkConstructorsSummary()) {
|
|
return false;
|
|
}
|
|
if (!this->checkOperatorsSummary()) {
|
|
return false;
|
|
}
|
|
if (!this->checkSeeAlso()) {
|
|
return false;
|
|
}
|
|
if (!this->checkCreators()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protected:
|
|
// Check that all constructors are in a table of contents
|
|
// should be 'creators' instead of constructors?
|
|
bool checkConstructorsSummary() {
|
|
for (auto& rootChild : fRoot->fChildren) {
|
|
if (!this->isStructOrClass(rootChild)) {
|
|
continue;
|
|
}
|
|
auto& cs = rootChild;
|
|
auto overview = this->findOverview(cs);
|
|
if (!overview) {
|
|
return false;
|
|
}
|
|
Definition* constructors = nullptr;
|
|
for (auto& overChild : overview->fChildren) {
|
|
if ("Constructors" == overChild->fName) {
|
|
constructors = overChild;
|
|
break;
|
|
}
|
|
}
|
|
if (constructors && MarkType::kSubtopic != constructors->fMarkType) {
|
|
return constructors->reportError<bool>("expected #Subtopic Constructors");
|
|
}
|
|
vector<string> constructorEntries;
|
|
if (constructors) {
|
|
if (!this->collectEntries(constructors, &constructorEntries)) {
|
|
return false;
|
|
}
|
|
}
|
|
// mark corresponding methods as visited (may be more than one per entry)
|
|
for (auto& csChild : cs->fChildren) {
|
|
if (MarkType::kMethod != csChild->fMarkType) {
|
|
// only check methods for now
|
|
continue;
|
|
}
|
|
string name;
|
|
if (!this->childName(csChild, &name)) {
|
|
return false;
|
|
}
|
|
string returnType;
|
|
if (Definition::MethodType::kConstructor != csChild->fMethodType &&
|
|
Definition::MethodType::kDestructor != csChild->fMethodType) {
|
|
string makeCheck = name.substr(0, 4);
|
|
if ("Make" != makeCheck && "make" != makeCheck) {
|
|
continue;
|
|
}
|
|
// for now, assume return type of interest is first word to start Sk
|
|
string search(csChild->fStart, csChild->fContentStart - csChild->fStart);
|
|
auto end = search.find(makeCheck);
|
|
if (string::npos == end) {
|
|
return csChild->reportError<bool>("expected Make in content");
|
|
}
|
|
search = search.substr(0, end);
|
|
if (string::npos == search.find(cs->fName)) {
|
|
// if return value doesn't match current struct or class, look in
|
|
// returned struct / class instead
|
|
auto sk = search.find("Sk");
|
|
if (string::npos != sk) {
|
|
// todo: build class name, find it, search for match in its overview
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (constructorEntries.end() ==
|
|
std::find(constructorEntries.begin(), constructorEntries.end(), name)) {
|
|
return csChild->reportError<bool>("missing constructor in Constructors");
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool checkCreators() {
|
|
return true;
|
|
}
|
|
|
|
bool checkMethodSubtopic() {
|
|
return true;
|
|
}
|
|
|
|
// Check that summary contains all methods
|
|
bool checkMethodSummary() {
|
|
// look for struct or class in fChildren
|
|
for (auto& rootChild : fRoot->fChildren) {
|
|
if (!this->isStructOrClass(rootChild)) {
|
|
continue;
|
|
}
|
|
auto& cs = rootChild;
|
|
// expect Overview as Topic in every main class or struct
|
|
auto overview = this->findOverview(cs);
|
|
if (!overview) {
|
|
return false;
|
|
}
|
|
Definition* memberFunctions = nullptr;
|
|
for (auto& overChild : overview->fChildren) {
|
|
if ("Member_Functions" == overChild->fName) {
|
|
memberFunctions = overChild;
|
|
break;
|
|
}
|
|
}
|
|
if (!memberFunctions) {
|
|
return overview->reportError<bool>("missing #Subtopic Member_Functions");
|
|
}
|
|
if (MarkType::kSubtopic != memberFunctions->fMarkType) {
|
|
return memberFunctions->reportError<bool>("expected #Subtopic Member_Functions");
|
|
}
|
|
vector<string> overviewEntries; // build map of overview entries
|
|
if (!this->collectEntries(memberFunctions, &overviewEntries)) {
|
|
return false;
|
|
}
|
|
// mark corresponding methods as visited (may be more than one per entry)
|
|
for (auto& csChild : cs->fChildren) {
|
|
if (MarkType::kMethod != csChild->fMarkType) {
|
|
// only check methods for now
|
|
continue;
|
|
}
|
|
if (Definition::MethodType::kConstructor == csChild->fMethodType) {
|
|
continue;
|
|
}
|
|
if (Definition::MethodType::kDestructor == csChild->fMethodType) {
|
|
continue;
|
|
}
|
|
if (Definition::MethodType::kOperator == csChild->fMethodType) {
|
|
continue;
|
|
}
|
|
string name;
|
|
if (!this->childName(csChild, &name)) {
|
|
return false;
|
|
}
|
|
if (overviewEntries.end() ==
|
|
std::find(overviewEntries.begin(), overviewEntries.end(), name)) {
|
|
return csChild->reportError<bool>("missing method in Member_Functions");
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check that all operators are in a table of contents
|
|
bool checkOperatorsSummary() {
|
|
for (auto& rootChild : fRoot->fChildren) {
|
|
if (!this->isStructOrClass(rootChild)) {
|
|
continue;
|
|
}
|
|
auto& cs = rootChild;
|
|
auto overview = this->findOverview(cs);
|
|
if (!overview) {
|
|
return false;
|
|
}
|
|
Definition* operators = nullptr;
|
|
for (auto& overChild : overview->fChildren) {
|
|
if ("Operators" == overChild->fName) {
|
|
operators = overChild;
|
|
break;
|
|
}
|
|
}
|
|
if (operators && MarkType::kSubtopic != operators->fMarkType) {
|
|
return operators->reportError<bool>("expected #Subtopic Operators");
|
|
}
|
|
vector<string> operatorEntries;
|
|
if (operators) {
|
|
if (!this->collectEntries(operators, &operatorEntries)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (auto& csChild : cs->fChildren) {
|
|
if (Definition::MethodType::kOperator != csChild->fMethodType) {
|
|
continue;
|
|
}
|
|
string name;
|
|
if (!this->childName(csChild, &name)) {
|
|
return false;
|
|
}
|
|
bool found = false;
|
|
for (auto str : operatorEntries) {
|
|
if (string::npos != str.find(name)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
return csChild->reportError<bool>("missing operator in Operators");
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool checkSeeAlso() {
|
|
return true;
|
|
}
|
|
|
|
bool checkSubtopicSummary() {
|
|
for (auto& rootChild : fRoot->fChildren) {
|
|
if (!this->isStructOrClass(rootChild)) {
|
|
continue;
|
|
}
|
|
auto& cs = rootChild;
|
|
auto overview = this->findOverview(cs);
|
|
if (!overview) {
|
|
return false;
|
|
}
|
|
Definition* subtopics = nullptr;
|
|
Definition* relatedFunctions = nullptr;
|
|
for (auto& overChild : overview->fChildren) {
|
|
if ("Subtopics" == overChild->fName) {
|
|
subtopics = overChild;
|
|
} else if ("Related_Functions" == overChild->fName) {
|
|
relatedFunctions = overChild;
|
|
}
|
|
}
|
|
if (!subtopics) {
|
|
return overview->reportError<bool>("missing #Subtopic Subtopics");
|
|
}
|
|
if (MarkType::kSubtopic != subtopics->fMarkType) {
|
|
return subtopics->reportError<bool>("expected #Subtopic Subtopics");
|
|
}
|
|
if (relatedFunctions && MarkType::kSubtopic != relatedFunctions->fMarkType) {
|
|
return relatedFunctions->reportError<bool>("expected #Subtopic Related_Functions");
|
|
}
|
|
vector<string> subtopicEntries;
|
|
if (!this->collectEntries(subtopics, &subtopicEntries)) {
|
|
return false;
|
|
}
|
|
if (relatedFunctions && !this->collectEntries(relatedFunctions, &subtopicEntries)) {
|
|
return false;
|
|
}
|
|
for (auto& csChild : cs->fChildren) {
|
|
if (MarkType::kSubtopic != csChild->fMarkType) {
|
|
continue;
|
|
}
|
|
string name;
|
|
if (!this->childName(csChild, &name)) {
|
|
return false;
|
|
}
|
|
bool found = false;
|
|
for (auto str : subtopicEntries) {
|
|
if (string::npos != str.find(name)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
return csChild->reportError<bool>("missing SubTopic in SubTopics");
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool childName(const Definition* def, string* name) {
|
|
auto start = def->fName.find_last_of(':');
|
|
start = string::npos == start ? 0 : start + 1;
|
|
*name = def->fName.substr(start);
|
|
if (def->fClone) {
|
|
auto lastUnderline = name->find_last_of('_');
|
|
if (string::npos == lastUnderline) {
|
|
return def->reportError<bool>("expect _ in name");
|
|
}
|
|
if (lastUnderline + 1 >= name->length()) {
|
|
return def->reportError<bool>("expect char after _ in name");
|
|
}
|
|
for (auto index = lastUnderline + 1; index < name->length(); ++index) {
|
|
if (!isdigit((*name)[index])) {
|
|
return def->reportError<bool>("expect digit after _ in name");
|
|
}
|
|
}
|
|
*name = name->substr(0, lastUnderline);
|
|
bool allLower = true;
|
|
for (auto ch : *name) {
|
|
allLower &= (bool) islower(ch);
|
|
}
|
|
if (allLower) {
|
|
*name += "()";
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const Definition* findOverview(const Definition* parent) {
|
|
// expect Overview as Topic in every main class or struct
|
|
Definition* overview = nullptr;
|
|
for (auto& csChild : parent->fChildren) {
|
|
if ("Overview" == csChild->fName) {
|
|
if (overview) {
|
|
return csChild->reportError<const Definition*>("expected only one Overview");
|
|
}
|
|
overview = csChild;
|
|
}
|
|
}
|
|
if (!overview) {
|
|
return parent->reportError<const Definition*>("missing #Topic Overview");
|
|
}
|
|
return overview;
|
|
}
|
|
|
|
bool collectEntries(const Definition* entries, vector<string>* strings) {
|
|
const Definition* table = nullptr;
|
|
for (auto& child : entries->fChildren) {
|
|
if (MarkType::kTable == child->fMarkType && child->fName == entries->fName) {
|
|
table = child;
|
|
break;
|
|
}
|
|
}
|
|
if (!table) {
|
|
return entries->reportError<bool>("missing #Table in Overview Subtopic");
|
|
}
|
|
bool expectLegend = true;
|
|
string prior = " "; // expect entries to be alphabetical
|
|
for (auto& row : table->fChildren) {
|
|
if (MarkType::kLegend == row->fMarkType) {
|
|
if (!expectLegend) {
|
|
return row->reportError<bool>("expect #Legend only once");
|
|
}
|
|
// todo: check if legend format matches table's rows' format
|
|
expectLegend = false;
|
|
} else if (expectLegend) {
|
|
return row->reportError<bool>("expect #Legend first");
|
|
}
|
|
if (MarkType::kRow != row->fMarkType) {
|
|
continue; // let anything through for now; can tighten up in the future
|
|
}
|
|
// expect column 0 to point to function name
|
|
Definition* column0 = row->fChildren[0];
|
|
string name = string(column0->fContentStart,
|
|
column0->fContentEnd - column0->fContentStart);
|
|
if (prior > name) {
|
|
return row->reportError<bool>("expect alphabetical order");
|
|
}
|
|
if (prior == name) {
|
|
return row->reportError<bool>("expect unique names");
|
|
}
|
|
// todo: error if name is all lower case and doesn't end in ()
|
|
strings->push_back(name);
|
|
prior = name;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool isStructOrClass(const Definition* definition) {
|
|
if (MarkType::kStruct != definition->fMarkType &&
|
|
MarkType::kClass != definition->fMarkType) {
|
|
return false;
|
|
}
|
|
if (string::npos != definition->fFileName.find("undocumented.bmh")) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
const BmhParser& fBmhParser;
|
|
RootDefinition* fRoot;
|
|
};
|
|
|
|
bool SelfCheck(const BmhParser& bmh) {
|
|
SelfChecker checker(bmh);
|
|
return checker.check();
|
|
}
|