bookmaker error handling
Bookmaker will generate instructions on how to
fix detected errors in a few cases:
- if class function is missing description
- if global function is missing description
- if function parameters don't match doxygen
- if function parameters don't match bmh
(The last case above won't happen if bmh #Method
uses #Populate to retrieve parameter descriptions
from the include.)
Adding this revealed that globals weren't always
accounted for in bookmaker's cross-check; fix
that as well.
TBR=reed@google.com
Docs-Preview: https://skia.org/?cl=171224
Bug: skia:
Change-Id: Ic1b41d4722954fa8a42685a8fe7266b8a860c362
Reviewed-on: https://skia-review.googlesource.com/c/171224
Reviewed-by: Cary Clark <caryclark@skia.org>
Commit-Queue: Cary Clark <caryclark@skia.org>
Auto-Submit: Cary Clark <caryclark@skia.org>
diff --git a/docs/SkColor_Reference.bmh b/docs/SkColor_Reference.bmh
index e3efad4..b32eebe 100644
--- a/docs/SkColor_Reference.bmh
+++ b/docs/SkColor_Reference.bmh
@@ -597,7 +597,7 @@
# ------------------------------------------------------------------------------
-#Method void SkColorToHSV(SkColor color, SkScalar hsv[3])
+#Method static void SkColorToHSV(SkColor color, SkScalar hsv[3])
#In Functions
#Line # converts RGB to HSV ##
@@ -673,7 +673,7 @@
# ------------------------------------------------------------------------------
-#Method SkColor SkHSVToColor(const SkScalar hsv[3])
+#Method static SkColor SkHSVToColor(const SkScalar hsv[3])
#In Functions
#Line # converts HSV to RGB ##
diff --git a/docs/SkImageInfo_Reference.bmh b/docs/SkImageInfo_Reference.bmh
index bf425d7..d831d94 100644
--- a/docs/SkImageInfo_Reference.bmh
+++ b/docs/SkImageInfo_Reference.bmh
@@ -189,7 +189,7 @@
#Subtopic Unpremul ##
-#Method static inline bool SkAlphaTypeIsOpaque(SkAlphaType at)
+#Method static bool SkAlphaTypeIsOpaque(SkAlphaType at)
#In Property
#Line # returns if Alpha_Type equals kOpaque_SkAlphaType ##
#Populate
diff --git a/docs/SkRegion_Reference.bmh b/docs/SkRegion_Reference.bmh
index 50ada67..a14055d 100644
--- a/docs/SkRegion_Reference.bmh
+++ b/docs/SkRegion_Reference.bmh
@@ -269,7 +269,7 @@
#Line # iterator returning IRect within clip ##
#Code
- class SK_API Cliperator {
+ class Cliperator {
public:
Cliperator(const SkRegion& region, const SkIRect& clip);
bool done();
diff --git a/docs/undocumented.bmh b/docs/undocumented.bmh
index 68fdc4b..e2c3951 100644
--- a/docs/undocumented.bmh
+++ b/docs/undocumented.bmh
@@ -27,7 +27,6 @@
SkXXX_Reference # ditto
Skia # ditto
SK_ABORT # ditto
- SK_API # ditto
SK_DEBUG # ditto
SK_RELEASE # ditto
SK_USE_FREETYPE_EMBOLDEN # ditto
@@ -189,8 +188,8 @@
##
#Topic Create_Color_Space_Xform_Canvas
-#Method std::unique_ptr<SkCanvas> SK_API SkCreateColorSpaceXformCanvas(SkCanvas* target,
- sk_sp<SkColorSpace> targetCS)
+#Method std::unique_ptr<SkCanvas> SkCreateColorSpaceXformCanvas(SkCanvas* target,
+ sk_sp<SkColorSpace> targetCS)
##
##
@@ -217,7 +216,7 @@
##
#Topic Debugging
-#Method SK_API void SkDebugf(const char format[], ...)
+#Method void SkDebugf(const char format[], ...)
##
##
@@ -772,7 +771,7 @@
#Topic PathOps
#Enum SkPathOp
##
- #Method bool SK_API Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result)
+ #Method bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result)
##
#Topic ##
diff --git a/site/user/api/SkColor_Reference.md b/site/user/api/SkColor_Reference.md
index 0868f15..ca11aed 100644
--- a/site/user/api/SkColor_Reference.md
+++ b/site/user/api/SkColor_Reference.md
@@ -569,7 +569,7 @@
---
<pre style="padding: 1em 1em 1em 1em; width: 62.5em;background-color: #f0f0f0">
-void <a href='SkColor_Reference#SkColorToHSV'>SkColorToHSV</a>(<a href='SkColor_Reference#SkColor'>SkColor</a> <a href='SkColor_Reference#Color'>color</a>, <a href='undocumented#SkScalar'>SkScalar</a> hsv[3])
+static void <a href='SkColor_Reference#SkColorToHSV'>SkColorToHSV</a>(<a href='SkColor_Reference#SkColor'>SkColor</a> <a href='SkColor_Reference#Color'>color</a>, <a href='undocumented#SkScalar'>SkScalar</a> hsv[3])
</pre>
Converts ARGB to its HSV components. <a href='SkColor_Reference#Alpha'>Alpha</a> in ARGB is ignored.
diff --git a/site/user/api/SkRegion_Reference.md b/site/user/api/SkRegion_Reference.md
index f766134..98c4ff9 100644
--- a/site/user/api/SkRegion_Reference.md
+++ b/site/user/api/SkRegion_Reference.md
@@ -389,7 +389,7 @@
---
<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
- class SK_API <a href='#SkRegion_Cliperator'>Cliperator</a> {
+ class <a href='#SkRegion_Cliperator'>Cliperator</a> {
public:
<a href='#SkRegion_Cliperator'>Cliperator</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>, const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& clip);
bool <a href='#SkRegion_Cliperator_done'>done()</a>;
diff --git a/tools/bookmaker/definition.cpp b/tools/bookmaker/definition.cpp
index 8055d69..41407f8 100644
--- a/tools/bookmaker/definition.cpp
+++ b/tools/bookmaker/definition.cpp
@@ -185,12 +185,9 @@
string className(fName, 0, doubleColons - 2);
TextParser iParser(fFileName, fStart, fContentStart, fLineCount);
SkAssertResult(iParser.skipWord("#Method"));
- iParser.skipExact("SK_API");
iParser.skipWhiteSpace();
bool isStatic = iParser.skipExact("static");
iParser.skipWhiteSpace();
- iParser.skipExact("SK_API");
- iParser.skipWhiteSpace();
bool returnsConst = iParser.skipExact("const");
if (returnsConst) {
SkASSERT(0); // incomplete
@@ -644,11 +641,7 @@
return tokenEnd;
}
-bool Definition::crossCheckInside(const char* start, const char* end,
- const Definition& includeToken) const {
- TextParser def(fFileName, start, end, fLineCount);
- const char* tokenEnd = includeToken.methodEnd();
- TextParser inc("", includeToken.fContentStart, tokenEnd, 0);
+bool Definition::SkipImplementationWords(TextParser& inc) {
if (inc.startsWith("SK_API")) {
inc.skipWord("SK_API");
}
@@ -661,7 +654,23 @@
if (inc.startsWith("SK_API")) {
inc.skipWord("SK_API");
}
- inc.skipExact("SkDEBUGCODE(");
+ return inc.skipExact("SkDEBUGCODE(");
+}
+
+bool Definition::crossCheckInside(const char* start, const char* end,
+ const Definition& includeToken) const {
+ TextParser def(fFileName, start, end, fLineCount);
+ const char* tokenEnd = includeToken.methodEnd();
+ TextParser inc("", includeToken.fContentStart, tokenEnd, 0);
+ if (inc.startsWith("static")) {
+ def.skipWhiteSpace();
+ if (!def.startsWith("static")) {
+ return false;
+ }
+ inc.skipWord("static");
+ def.skipWord("static");
+ }
+ (void) Definition::SkipImplementationWords(inc);
do {
bool defEof;
bool incEof;
@@ -1196,10 +1205,6 @@
if ("SkBitmap::validate()" == leaf.first) {
continue;
}
- // SkPath::pathRefIsValid in #ifdef ; prefer to remove chrome dependency to fix
- if ("SkPath::pathRefIsValid" == leaf.first) {
- continue;
- }
// FIXME: end of long tail bugs
SkDebugf("defined in bmh but missing in include: %s\n", leaf.first.c_str());
success = false;
diff --git a/tools/bookmaker/definition.h b/tools/bookmaker/definition.h
index 8aace77..c882ccc 100644
--- a/tools/bookmaker/definition.h
+++ b/tools/bookmaker/definition.h
@@ -197,6 +197,8 @@
fParentIndex = fParent ? (int) fParent->fTokens.size() : -1;
}
+ static bool SkipImplementationWords(TextParser& inc);
+
string simpleName() {
size_t doubleColon = fName.rfind("::");
return string::npos == doubleColon ? fName : fName.substr(doubleColon + 2);
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp
index a9ec3db..c86a187 100644
--- a/tools/bookmaker/includeParser.cpp
+++ b/tools/bookmaker/includeParser.cpp
@@ -634,6 +634,321 @@
#include <sstream>
#include <iostream>
+void IncludeParser::checkTokens(list<Definition>& tokens, string key, string className,
+ RootDefinition* root, BmhParser& bmhParser) {
+ for (const auto& token : tokens) {
+ if (token.fPrivate) {
+ continue;
+ }
+ string fullName = key + "::" + token.fName;
+ const Definition* def = nullptr;
+ if (root) {
+ def = root->find(fullName, RootDefinition::AllowParens::kYes);
+ }
+ switch (token.fMarkType) {
+ case MarkType::kMethod: {
+ if (this->isInternalName(token)) {
+ continue;
+ }
+ if (!root) {
+ if (token.fUndocumented) {
+ break;
+ }
+ auto methIter = bmhParser.fMethodMap.find(token.fName);
+ if (bmhParser.fMethodMap.end() != methIter) {
+ def = &methIter->second;
+ if (def->crossCheck2(token)) {
+ def->fVisited = true;
+ } else {
+ this->suggestFix(Suggest::kMethodDiffers, token, root, def);
+ fFailed = true;
+ }
+ } else {
+ this->suggestFix(Suggest::kMethodMissing, token, root, nullptr);
+ fFailed = true;
+ }
+ break;
+ }
+ if (!def) {
+ string paramName = className + "::";
+ paramName += string(token.fContentStart,
+ token.fContentEnd - token.fContentStart);
+ if (string::npos != paramName.find('\n')) {
+ paramName.erase(std::remove(paramName.begin(), paramName.end(), '\n'),
+ paramName.end());
+ }
+ def = root->find(paramName, RootDefinition::AllowParens::kYes);
+ if (!def && 0 == token.fName.find("operator")) {
+ string operatorName = className + "::";
+ TextParser oper("", token.fStart, token.fContentEnd, 0);
+ const char* start = oper.strnstr("operator", token.fContentEnd);
+ SkASSERT(start);
+ oper.skipTo(start);
+ oper.skipToEndBracket('(');
+ int parens = 0;
+ do {
+ if ('(' == oper.peek()) {
+ ++parens;
+ } else if (')' == oper.peek()) {
+ --parens;
+ }
+ } while (!oper.eof() && oper.next() && parens > 0);
+ operatorName += string(start, oper.fChar - start);
+ def = root->find(operatorName, RootDefinition::AllowParens::kYes);
+ }
+ }
+ if (!def) {
+ int skip = !strncmp(token.fContentStart, "explicit ", 9) ? 9 : 0;
+ skip = !strncmp(token.fContentStart, "virtual ", 8) ? 8 : skip;
+ const char* tokenEnd = token.methodEnd();
+ string constructorName = className + "::";
+ constructorName += string(token.fContentStart + skip,
+ tokenEnd - token.fContentStart - skip);
+ def = root->find(constructorName, RootDefinition::AllowParens::kYes);
+ }
+ if (!def && 0 == token.fName.find("SK_")) {
+ string incName = token.fName + "()";
+ string macroName = className + "::" + incName;
+ def = root->find(macroName, RootDefinition::AllowParens::kYes);
+ if (def) {
+ if (def->fName == incName) {
+ def->fVisited = true;
+ if ("SK_TO_STRING_NONVIRT" == token.fName) {
+ def = root->find(className + "::toString",
+ RootDefinition::AllowParens::kYes);
+ if (def) {
+ def->fVisited = true;
+ } else {
+ SkDebugf("missing toString bmh: %s\n", fullName.c_str());
+ fFailed = true;
+ }
+ }
+ break;
+ } else {
+ SkDebugf("method macro differs from bmh: %s\n", fullName.c_str());
+ fFailed = true;
+ }
+ }
+ }
+ if (!def) {
+ bool allLower = true;
+ for (size_t index = 0; index < token.fName.length(); ++index) {
+ if (!islower(token.fName[index])) {
+ allLower = false;
+ break;
+ }
+ }
+ if (allLower) {
+ string lowerName = className + "::" + token.fName + "()";
+ def = root->find(lowerName, RootDefinition::AllowParens::kYes);
+ }
+ }
+ if (!def) {
+ if (0 == token.fName.find("SkDEBUGCODE")) {
+ break;
+ }
+ }
+ if (!def) {
+ // simple method names inside nested classes have a bug and are missing trailing parens
+ string withParens = fullName + "()"; // FIXME: this shouldn't be necessary
+ def = root->find(withParens, RootDefinition::AllowParens::kNo);
+ }
+ if (!def) {
+ if (!token.fUndocumented) {
+ this->suggestFix(Suggest::kMethodMissing, token, root, nullptr);
+ fFailed = true;
+ }
+ break;
+ }
+ if (token.fUndocumented) {
+ // we can't report an error yet; if bmh documents this unnecessarily,
+ // we'll detect that later. It may be that def points to similar
+ // documented function.
+ break;
+ }
+ if (def->crossCheck2(token)) {
+ def->fVisited = true;
+ } else {
+ SkDebugf("method differs from bmh: %s\n", fullName.c_str());
+ fFailed = true;
+ }
+ } break;
+ case MarkType::kComment:
+ break;
+ case MarkType::kEnumClass:
+ case MarkType::kEnum: {
+ if (!def) {
+ // work backwards from first word to deduce #Enum name
+ TextParser firstMember("", token.fStart, token.fContentEnd, 0);
+ SkAssertResult(firstMember.skipName("enum"));
+ SkAssertResult(firstMember.skipToEndBracket('{'));
+ firstMember.next();
+ firstMember.skipWhiteSpace();
+ SkASSERT('k' == firstMember.peek());
+ const char* savePos = firstMember.fChar;
+ firstMember.skipToNonName();
+ const char* wordEnd = firstMember.fChar;
+ firstMember.fChar = savePos;
+ const char* lastUnderscore = nullptr;
+ do {
+ if (!firstMember.skipToEndBracket('_')) {
+ break;
+ }
+ if (firstMember.fChar > wordEnd) {
+ break;
+ }
+ lastUnderscore = firstMember.fChar;
+ } while (firstMember.next());
+ if (lastUnderscore) {
+ ++lastUnderscore;
+ string enumName(lastUnderscore, wordEnd - lastUnderscore);
+ if (root) {
+ string anonName = className + "::" + enumName + 's';
+ def = root->find(anonName, RootDefinition::AllowParens::kYes);
+ } else {
+ auto enumIter = bmhParser.fEnumMap.find(enumName);
+ if (bmhParser.fEnumMap.end() != enumIter) {
+ RootDefinition* rootDef = &enumIter->second;
+ def = rootDef;
+ }
+ }
+ }
+ if (!def && !root) {
+ auto enumIter = bmhParser.fEnumMap.find(token.fName);
+ if (bmhParser.fEnumMap.end() != enumIter) {
+ def = &enumIter->second;
+ }
+ if (!def) {
+ auto enumClassIter = bmhParser.fClassMap.find(token.fName);
+ if (bmhParser.fClassMap.end() != enumClassIter) {
+ def = &enumClassIter->second;
+ }
+ }
+ }
+ if (!def) {
+ if (!token.fUndocumented) {
+ SkDebugf("enum missing from bmh: %s\n", fullName.c_str());
+ fFailed = true;
+ }
+ break;
+ }
+ }
+ def->fVisited = true;
+ bool hasCode = false;
+ bool hasPopulate = true;
+ for (auto& child : def->fChildren) {
+ if (MarkType::kCode == child->fMarkType) {
+ hasPopulate = std::any_of(child->fChildren.begin(),
+ child->fChildren.end(), [](auto grandChild){
+ return MarkType::kPopulate == grandChild->fMarkType; });
+ if (!hasPopulate) {
+ def = child;
+ }
+ hasCode = true;
+ break;
+ }
+ }
+ if (!hasCode && !root) {
+ const Definition* topic = def->topicParent();
+ hasCode = std::any_of(topic->fChildren.begin(), topic->fChildren.end(),
+ [](Definition* def){ return MarkType::kCode == def->fMarkType
+ && def->fChildren.size() > 0 && MarkType::kPopulate ==
+ def->fChildren.front()->fMarkType; });
+ }
+ if (!hasCode) {
+ SkDebugf("enum code missing from bmh: %s\n", fullName.c_str());
+ fFailed = true;
+ break;
+ }
+ if (!hasPopulate) {
+ if (def->crossCheck(token)) {
+ def->fVisited = true;
+ } else {
+ SkDebugf("enum differs from bmh: %s\n", def->fName.c_str());
+ fFailed = true;
+ }
+ }
+ for (auto& member : token.fTokens) {
+ if (MarkType::kMember != member.fMarkType) {
+ continue;
+ }
+ string constName = MarkType::kEnumClass == token.fMarkType ?
+ fullName : className;
+ if (root) {
+ constName += "::" + member.fName;
+ def = root->find(constName, RootDefinition::AllowParens::kYes);
+ } else {
+ auto enumMapper = bmhParser.fEnumMap.find(token.fName);
+ if (bmhParser.fEnumMap.end() != enumMapper) {
+ auto& enumDoc = enumMapper->second;
+ auto memberIter = enumDoc.fLeaves.find(member.fName);
+ if (enumDoc.fLeaves.end() != memberIter) {
+ def = &memberIter->second;
+ }
+ }
+ }
+ if (!def) {
+ string innerName = key + "::" + member.fName;
+ def = root->find(innerName, RootDefinition::AllowParens::kYes);
+ }
+ if (!def) {
+ if (!member.fUndocumented) {
+ SkDebugf("const missing from bmh: %s\n", constName.c_str());
+ fFailed = true;
+ }
+ } else {
+ def->fVisited = true;
+ }
+ }
+ } break;
+ case MarkType::kMember:
+ if (def) {
+ def->fVisited = true;
+ } else {
+ SkDebugf("member missing from bmh: %s\n", fullName.c_str());
+ fFailed = true;
+ }
+ break;
+ case MarkType::kTypedef:
+ if (!def && !root) {
+ auto typedefIter = bmhParser.fTypedefMap.find(token.fName);
+ if (bmhParser.fTypedefMap.end() != typedefIter) {
+ def = &typedefIter->second;
+ }
+ }
+ if (def) {
+ def->fVisited = true;
+ } else {
+ SkDebugf("typedef missing from bmh: %s\n", fullName.c_str());
+ fFailed = true;
+ }
+ break;
+ case MarkType::kConst:
+ if (!def && !root) {
+ auto constIter = bmhParser.fConstMap.find(token.fName);
+ if (bmhParser.fConstMap.end() != constIter) {
+ def = &constIter->second;
+ }
+ }
+ if (def) {
+ def->fVisited = true;
+ } else {
+ if (!token.fUndocumented) {
+ SkDebugf("const missing from bmh: %s\n", fullName.c_str());
+ fFailed = true;
+ }
+ }
+ break;
+ case MarkType::kDefine:
+ // TODO: incomplete
+ break;
+ default:
+ SkASSERT(0); // unhandled
+ break;
+ }
+ }
+}
+
bool IncludeParser::crossCheck(BmhParser& bmhParser) {
for (auto& classMapper : fIClassMap) {
string className = classMapper.first;
@@ -674,242 +989,10 @@
}
}
}
- auto& classMap = classMapper.second;
- auto& tokens = classMap.fTokens;
- for (const auto& token : tokens) {
- if (token.fPrivate) {
- continue;
- }
- string fullName = classMapper.first + "::" + token.fName;
- const Definition* def = root->find(fullName, RootDefinition::AllowParens::kYes);
- switch (token.fMarkType) {
- case MarkType::kMethod: {
- if (this->isInternalName(token)) {
- continue;
- }
- if (!def) {
- string paramName = className + "::";
- paramName += string(token.fContentStart,
- token.fContentEnd - token.fContentStart);
- if (string::npos != paramName.find('\n')) {
- paramName.erase(std::remove(paramName.begin(), paramName.end(), '\n'),
- paramName.end());
- }
- def = root->find(paramName, RootDefinition::AllowParens::kYes);
- if (!def && 0 == token.fName.find("operator")) {
- string operatorName = className + "::";
- TextParser oper("", token.fStart, token.fContentEnd, 0);
- const char* start = oper.strnstr("operator", token.fContentEnd);
- SkASSERT(start);
- oper.skipTo(start);
- oper.skipToEndBracket('(');
- int parens = 0;
- do {
- if ('(' == oper.peek()) {
- ++parens;
- } else if (')' == oper.peek()) {
- --parens;
- }
- } while (!oper.eof() && oper.next() && parens > 0);
- operatorName += string(start, oper.fChar - start);
- def = root->find(operatorName, RootDefinition::AllowParens::kYes);
- }
- }
- if (!def) {
- int skip = !strncmp(token.fContentStart, "explicit ", 9) ? 9 : 0;
- skip = !strncmp(token.fContentStart, "virtual ", 8) ? 8 : skip;
- const char* tokenEnd = token.methodEnd();
- string constructorName = className + "::";
- constructorName += string(token.fContentStart + skip,
- tokenEnd - token.fContentStart - skip);
- def = root->find(constructorName, RootDefinition::AllowParens::kYes);
- }
- if (!def && 0 == token.fName.find("SK_")) {
- string incName = token.fName + "()";
- string macroName = className + "::" + incName;
- def = root->find(macroName, RootDefinition::AllowParens::kYes);
- if (def) {
- if (def->fName == incName) {
- def->fVisited = true;
- if ("SK_TO_STRING_NONVIRT" == token.fName) {
- def = root->find(className + "::toString",
- RootDefinition::AllowParens::kYes);
- if (def) {
- def->fVisited = true;
- } else {
- SkDebugf("missing toString bmh: %s\n", fullName.c_str());
- fFailed = true;
- }
- }
- break;
- } else {
- SkDebugf("method macro differs from bmh: %s\n", fullName.c_str());
- fFailed = true;
- }
- }
- }
- if (!def) {
- bool allLower = true;
- for (size_t index = 0; index < token.fName.length(); ++index) {
- if (!islower(token.fName[index])) {
- allLower = false;
- break;
- }
- }
- if (allLower) {
- string lowerName = className + "::" + token.fName + "()";
- def = root->find(lowerName, RootDefinition::AllowParens::kYes);
- }
- }
- if (!def) {
- if (0 == token.fName.find("SkDEBUGCODE")) {
- break;
- }
- }
- if (!def) {
- // simple method names inside nested classes have a bug and are missing trailing parens
- string withParens = fullName + "()"; // FIXME: this shouldn't be necessary
- def = root->find(withParens, RootDefinition::AllowParens::kNo);
- }
- if (!def) {
- if (!token.fUndocumented) {
- SkDebugf("method missing from bmh: %s\n", fullName.c_str());
- fFailed = true;
- }
- break;
- }
- if (token.fUndocumented) {
- break;
- }
- if (def->crossCheck2(token)) {
- def->fVisited = true;
- } else {
- SkDebugf("method differs from bmh: %s\n", fullName.c_str());
- fFailed = true;
- }
- } break;
- case MarkType::kComment:
- break;
- case MarkType::kEnumClass:
- case MarkType::kEnum: {
- if (!def) {
- // work backwards from first word to deduce #Enum name
- TextParser firstMember("", token.fStart, token.fContentEnd, 0);
- SkAssertResult(firstMember.skipName("enum"));
- SkAssertResult(firstMember.skipToEndBracket('{'));
- firstMember.next();
- firstMember.skipWhiteSpace();
- SkASSERT('k' == firstMember.peek());
- const char* savePos = firstMember.fChar;
- firstMember.skipToNonName();
- const char* wordEnd = firstMember.fChar;
- firstMember.fChar = savePos;
- const char* lastUnderscore = nullptr;
- do {
- if (!firstMember.skipToEndBracket('_')) {
- break;
- }
- if (firstMember.fChar > wordEnd) {
- break;
- }
- lastUnderscore = firstMember.fChar;
- } while (firstMember.next());
- if (lastUnderscore) {
- ++lastUnderscore;
- string anonName = className + "::" + string(lastUnderscore,
- wordEnd - lastUnderscore) + 's';
- def = root->find(anonName, RootDefinition::AllowParens::kYes);
- }
- if (!def) {
- if (!token.fUndocumented) {
- SkDebugf("enum missing from bmh: %s\n", fullName.c_str());
- fFailed = true;
- }
- break;
- }
- }
- def->fVisited = true;
- bool hasCode = false;
- bool hasPopulate = true;
- for (auto& child : def->fChildren) {
- if (MarkType::kCode == child->fMarkType) {
- hasPopulate = std::any_of(child->fChildren.begin(),
- child->fChildren.end(), [](auto grandChild){
- return MarkType::kPopulate == grandChild->fMarkType; });
- if (!hasPopulate) {
- def = child;
- }
- hasCode = true;
- break;
- }
- }
- if (!hasCode) {
- SkDebugf("enum code missing from bmh: %s\n", fullName.c_str());
- fFailed = true;
- break;
- }
- if (!hasPopulate) {
- if (def->crossCheck(token)) {
- def->fVisited = true;
- } else {
- SkDebugf("enum differs from bmh: %s\n", def->fName.c_str());
- fFailed = true;
- }
- }
- for (auto& member : token.fTokens) {
- if (MarkType::kMember != member.fMarkType) {
- continue;
- }
- string constName = MarkType::kEnumClass == token.fMarkType ?
- fullName : className;
- constName += "::" + member.fName;
- def = root->find(constName, RootDefinition::AllowParens::kYes);
- if (!def) {
- string innerName = classMapper.first + "::" + member.fName;
- def = root->find(innerName, RootDefinition::AllowParens::kYes);
- }
- if (!def) {
- if (!member.fUndocumented) {
- SkDebugf("const missing from bmh: %s\n", constName.c_str());
- fFailed = true;
- }
- } else {
- def->fVisited = true;
- }
- }
- } break;
- case MarkType::kMember:
- if (def) {
- def->fVisited = true;
- } else {
- SkDebugf("member missing from bmh: %s\n", fullName.c_str());
- fFailed = true;
- }
- break;
- case MarkType::kTypedef:
- if (def) {
- def->fVisited = true;
- } else {
- SkDebugf("typedef missing from bmh: %s\n", fullName.c_str());
- fFailed = true;
- }
- break;
- case MarkType::kConst:
- if (def) {
- def->fVisited = true;
- } else {
- if (!token.fUndocumented) {
- SkDebugf("const missing from bmh: %s\n", fullName.c_str());
- fFailed = true;
- }
- }
- break;
- default:
- SkASSERT(0); // unhandled
- break;
- }
- }
+ this->checkTokens(classMapper.second.fTokens, classMapper.first, className, root,
+ bmhParser);
}
+ this->checkTokens(fGlobals, "", "", nullptr, bmhParser);
int crossChecks = 0;
string firstCheck;
for (auto& classMapper : fIClassMap) {
@@ -3578,6 +3661,132 @@
remove(fullName.c_str());
}
+static const char kMethodMissingStr[] =
+ "If the method requires documentation, add to "
+ "%s at minimum:\n" // path to bmh file
+ "\n"
+ "#Method %s\n" // method declaration less implementation details
+ "#In SomeSubtopicName\n"
+ "#Line # add a one line description here ##\n"
+ "#Populate\n"
+ "#NoExample\n"
+ "// or better yet, use #Example and put C++ code here\n"
+ "##\n"
+ "#SeeAlso optional related symbols\n"
+ "#Method ##\n"
+ "\n"
+ "Add to %s, at minimum:\n" // path to include
+ "\n"
+ "/** (description) Starts with present tense action verb\n"
+ " and end with a period.\n"
+ "%s" // @param, @return if needed go here
+ "*/\n"
+ "%s ...\n" // method declaration
+ "\n"
+ "If the method does not require documentation,\n"
+ "add \"private\" or \"experimental\", as in:\n"
+ "\n"
+ "/** Experimental, do not use. And so on...\n"
+ "*/\n"
+ "%s ...\n" // method declaration
+ "\n"
+ ;
+
+// bDef does not have #Populate
+static const char kMethodDiffersNoPopStr[] =
+ "In %s:\n" // path to bmh file
+ "#Method %s\n" // method declaration less implementation details
+ "does not match doxygen comment of:\n"
+ "%s.\n" // method declaration
+ "\n"
+ ;
+
+static const char kMethodDiffersStr[] =
+ "In %s:\n" // path to include
+ "%s\n" // method declaration
+ "does not match doxygen comment.\n"
+ "\n"
+ ;
+
+void IncludeParser::suggestFix(Suggest suggest, const Definition& iDef,
+ const RootDefinition* root, const Definition* bDef) {
+ string methodNameStr(iDef.fContentStart, iDef.length());
+ const char* methodName = methodNameStr.c_str();
+ TextParser lessImplParser(&iDef);
+ if (lessImplParser.skipExact("static")) {
+ lessImplParser.skipWhiteSpace();
+ }
+ // TODO : handle debug wrapper
+ /* bool inDebugWrapper = */ Definition::SkipImplementationWords(lessImplParser);
+ string lessImplStr(lessImplParser.fChar, lessImplParser.fEnd - lessImplParser.fChar);
+ const char* methodNameLessImpl = lessImplStr.c_str();
+ // return result, if any is substr from 0 to location of iDef.fName
+ size_t namePos = methodNameStr.find(iDef.fName);
+ SkASSERT(string::npos != namePos);
+ size_t funcEnd = namePos;
+ while (funcEnd > 0 && ' ' >= methodNameStr[funcEnd - 1]) {
+ funcEnd -= 1;
+ }
+ string funcRet = methodNameStr.substr(0, funcEnd);
+// parameters, if any, are delimited by () and separate by ,
+ TextParser parser(&iDef);
+ parser.fChar += namePos + iDef.fName.length();
+ const char* start = parser.fChar;
+ vector<string> paramStrs;
+ if ('(' == start[0]) {
+ parser.skipToBalancedEndBracket('(', ')');
+ TextParser params(&iDef);
+ params.fChar = start + 1;
+ params.fEnd = parser.fChar;
+ while (!params.eof()) {
+ const char* paramEnd = params.anyOf("=,)");
+ const char* paramStart = paramEnd;
+ while (paramStart > params.fChar && ' ' >= paramStart[-1]) {
+ paramStart -= 1;
+ }
+ while (paramStart > params.fChar && (isalnum(paramStart[-1])
+ || '_' == paramStart[-1])) {
+ paramStart -= 1;
+ }
+ string param(paramStart, paramEnd - paramStart);
+ paramStrs.push_back(param);
+ params.fChar = params.anyOf(",)") + 1;
+ }
+ }
+ string bmhFile = root ? root->fFileName : bDef ? bDef->fFileName : "a *.bmh file";
+ bool hasFuncReturn = "" != funcRet && "void" != funcRet;
+ switch(suggest) {
+ case Suggest::kMethodMissing: {
+ // if include @param, @return are missing, request them as well
+ string paramDox;
+ bool firstParam = true;
+ for (auto paramStr : paramStrs) {
+ if (firstParam) {
+ paramDox += "\n";
+ firstParam = false;
+ }
+ paramDox += " @param " + paramStr + " descriptive phrase\n";
+ }
+ if (hasFuncReturn) {
+ paramDox += "\n";
+ paramDox += " @return descriptive phrase\n";
+ }
+ SkDebugf(kMethodMissingStr, bmhFile.c_str(), methodNameLessImpl, iDef.fFileName.c_str(),
+ paramDox.c_str(), methodName, methodName);
+ } break;
+ case Suggest::kMethodDiffers: {
+ bool hasPop = std::any_of(bDef->fChildren.begin(), bDef->fChildren.end(),
+ [](Definition* def) { return MarkType::kPopulate == def->fMarkType; });
+ if (!hasPop) {
+ SkDebugf(kMethodDiffersNoPopStr, bmhFile.c_str(), methodNameLessImpl, methodName);
+ }
+ SkDebugf(kMethodDiffersStr, iDef.fFileName.c_str(), methodName);
+ } break;
+ default:
+ SkASSERT(0);
+ }
+}
+
Bracket IncludeParser::topBracket() const {
Definition* parent = this->parentBracket(fParent);
return parent ? parent->fBracket : Bracket::kNone;
diff --git a/tools/bookmaker/includeParser.h b/tools/bookmaker/includeParser.h
index f132525..34e6f61 100644
--- a/tools/bookmaker/includeParser.h
+++ b/tools/bookmaker/includeParser.h
@@ -36,6 +36,11 @@
kYes,
};
+ enum class Suggest {
+ kMethodMissing,
+ kMethodDiffers,
+ };
+
struct CheckCode {
enum class State {
kNone,
@@ -107,6 +112,8 @@
void checkForMissingParams(const vector<string>& methodParams,
const vector<string>& foundParams);
bool checkForWord();
+ void checkTokens(list<Definition>& tokens, string key, string className,
+ RootDefinition* root, BmhParser& bmhParser);
string className() const;
string codeBlock(const Definition& def, bool inProgress) const {
@@ -261,6 +268,8 @@
fInCharCommentString = fInChar || fInComment || fInString;
}
+ void suggestFix(Suggest suggest, const Definition& iDef, const RootDefinition* root,
+ const Definition* bDef);
Bracket topBracket() const;
template <typename T>