bookmaker does deprecated

Bookmaker does not require documentation for public symbols
described as "deprecated", "private", or "experimental".
Adding one of these words (case-insensitive) to the symbol
description in the include file tells bookmaker that the bmh file
should not include documentation, and the generated markdown
should omit it in its indices and descriptions.

Symbols marked as "to be deprecated" or "may be deprecated"
are still regarded as public and documented.

Private notes in the includes that start with TODO: are
omitted as well.

This CL updated generated includes to describe its symbols
accordingly. The includes will be fully regenerated in a future
CL. The corresponding documentation has been deleted from the
bmh files, and the web markup has been regenerated.

TBR=reed@google.com
Docs-Preview: https://skia.org/?cl=169830
Bug: skia:
Change-Id: Ie6ec3ccdadb7be9ac15db4811823a30948c4af25
Reviewed-on: https://skia-review.googlesource.com/c/169830
Commit-Queue: Cary Clark <caryclark@skia.org>
Auto-Submit: Cary Clark <caryclark@skia.org>
Reviewed-by: Cary Clark <caryclark@skia.org>
diff --git a/tools/bookmaker/bmhParser.cpp b/tools/bookmaker/bmhParser.cpp
index a0a46c3..1f99c72 100644
--- a/tools/bookmaker/bmhParser.cpp
+++ b/tools/bookmaker/bmhParser.cpp
@@ -46,14 +46,12 @@
 , { "",             MarkType::kComment,      R_N, E_N, 0 }
 , { "Const",        MarkType::kConst,        R_Y, E_O, M_E | M_CSST  }
 , { "Define",       MarkType::kDefine,       R_O, E_Y, M_ST }
-, { "Deprecated",   MarkType::kDeprecated,   R_Y, E_N, M_CS | M_MDCM | M_E }
 , { "Description",  MarkType::kDescription,  R_Y, E_N, M(Example) | M(NoExample) }
 , { "Details",      MarkType::kDetails,      R_N, E_N, M(Const) }
 , { "Duration",     MarkType::kDuration,     R_N, E_N, M(Example) | M(NoExample) }
 , { "Enum",         MarkType::kEnum,         R_Y, E_O, M_CSST }
 , { "EnumClass",    MarkType::kEnumClass,    R_Y, E_O, M_CSST }
 , { "Example",      MarkType::kExample,      R_O, E_N, M_CSST | M_E | M_MD | M(Const) }
-, { "Experimental", MarkType::kExperimental, R_Y, E_N, M_CS | M_MDCM | M_E }
 , { "External",     MarkType::kExternal,     R_Y, E_N, 0 }
 , { "File",         MarkType::kFile,         R_Y, E_N, M(Topic) }
 , { "Filter",       MarkType::kFilter,       R_N, E_N, M(Subtopic) | M(Code) }
@@ -81,7 +79,6 @@
 , { "",             MarkType::kPhraseRef,    R_N, E_N, 0 }
 , { "Platform",     MarkType::kPlatform,     R_N, E_N, M(Example) | M(NoExample) }
 , { "Populate",     MarkType::kPopulate,     R_N, E_N, M(Code) | M(Method) }
-, { "Private",      MarkType::kPrivate,      R_N, E_N, M_CSST | M_MDCM | M_E }
 , { "Return",       MarkType::kReturn,       R_Y, E_N, M(Method) }
 , { "",             MarkType::kRow,          R_Y, E_N, M(Table) | M(List) }
 , { "SeeAlso",      MarkType::kSeeAlso,      R_C, E_N, M_CSST | M_E | M_MD | M(Typedef) }
@@ -195,10 +192,7 @@
                      if (MarkType::kExample == child->fMarkType) {
                         hasExample = Exemplary::kYes;
                      }
-                     hasExcluder |= MarkType::kPrivate == child->fMarkType
-                            || MarkType::kDeprecated == child->fMarkType
-                            || MarkType::kExperimental == child->fMarkType
-                            || MarkType::kNoExample == child->fMarkType;
+                     hasExcluder |= MarkType::kNoExample == child->fMarkType;
                 }
                 if (kMarkProps[(int) markType].fExemplary != hasExample
                         && kMarkProps[(int) markType].fExemplary != Exemplary::kOptional) {
@@ -452,7 +446,6 @@
         case MarkType::kFunction:
         case MarkType::kLegend:
         case MarkType::kList:
-        case MarkType::kPrivate:
         case MarkType::kTable:
             if (hasEnd) {
                 definition = fParent;
@@ -504,10 +497,8 @@
             // always treated as one-liners (can't detect misuse easily)
         case MarkType::kAnchor:
         case MarkType::kBug:
-        case MarkType::kDeprecated:
         case MarkType::kDetails:
         case MarkType::kDuration:
-        case MarkType::kExperimental:
         case MarkType::kFilter:
         case MarkType::kHeight:
         case MarkType::kIllustration:
@@ -542,20 +533,7 @@
                 this->parseHashAnchor(definition);
 			} else if (MarkType::kLine == markType) {
                 this->parseHashLine(definition);
-			} else if (IncompleteAllowed(markType)) {
-                 this->skipSpace();
-                 fParent->fDeprecated = true;
-                 fParent->fDetails =
-                        this->skipExact("soon") ? Definition::Details::kSoonToBe_Deprecated :
-                        this->skipExact("testing") ? Definition::Details::kTestingOnly_Experiment :
-                        this->skipExact("do not use") ? Definition::Details::kDoNotUse_Experiment :
-                        this->skipExact("not ready") ? Definition::Details::kNotReady_Experiment :
-                        Definition::Details::kNone;
-                 this->skipSpace();
-                 if ('\n' != this->peek()) {
-                     return this->reportError<bool>("unexpected text after #Deprecated");
-                 }
-            }
+			}
             break;
         case MarkType::kExternal:
             (void) this->collectExternals();  // FIXME: detect errors in external defs?
@@ -2126,7 +2104,6 @@
         case MarkType::kLegend:
         case MarkType::kList:
         case MarkType::kNoExample:
-        case MarkType::kPrivate:
             this->skipNoName();
             break;
         case MarkType::kFormula:
@@ -2136,10 +2113,8 @@
         case MarkType::kAlias:
         case MarkType::kAnchor:
         case MarkType::kBug:  // fixme: expect number
-        case MarkType::kDeprecated:
         case MarkType::kDetails:
         case MarkType::kDuration:
-        case MarkType::kExperimental:
         case MarkType::kFile:
         case MarkType::kFilter:
         case MarkType::kHeight:
@@ -2244,11 +2219,7 @@
         for (auto& iter : fParent->fChildren) {
             if (markType == iter->fMarkType) {
                 if (iter->fName == numBuilder) {
-                    if (iter->fDeprecated) {
-                        iter->fClone = true;
-                    } else {
-                        fCloned = true;
-                    }
+                    fCloned = true;
                     numBuilder = builder + '_' + to_string(number);
                     goto tryNext;
                 }
@@ -2301,14 +2272,8 @@
         }
         if (MarkType::kMethod == markType) {
             cloned->fCloned = true;
-            if (cloned->fDeprecated) {
-                cloned->fClone = true;
-            } else {
-                fCloned = true;
-            }
-        } else {
-            fCloned = true;
         }
+        fCloned = true;
         numBuilder = builder + '_' + to_string(number);
     } while (++number);
     return numBuilder;
diff --git a/tools/bookmaker/bookmaker.h b/tools/bookmaker/bookmaker.h
index 4fb65ca..72e2503 100644
--- a/tools/bookmaker/bookmaker.h
+++ b/tools/bookmaker/bookmaker.h
@@ -109,14 +109,12 @@
     kComment,
     kConst,
     kDefine,
-    kDeprecated,
     kDescription,
     kDetails,  // used by #Const to specify #Subtopic details with examples and so on
     kDuration,
     kEnum,
     kEnumClass,
     kExample,
-    kExperimental,
     kExternal,
     kFile,
     kFilter,
@@ -143,7 +141,6 @@
     kPhraseRef,
     kPlatform,
     kPopulate,
-    kPrivate,
     kReturn,
     kRow,
     kSeeAlso,
@@ -164,10 +161,6 @@
     kWidth,
 };
 
-static inline bool IncompleteAllowed(MarkType markType) {
-    return MarkType::kDeprecated == markType || MarkType::kExperimental == markType;
-}
-
 enum {
     Last_MarkType = (int) MarkType::kWidth,
 };
diff --git a/tools/bookmaker/definition.cpp b/tools/bookmaker/definition.cpp
index e6707f6..8055d69 100644
--- a/tools/bookmaker/definition.cpp
+++ b/tools/bookmaker/definition.cpp
@@ -468,11 +468,8 @@
     }
     bool expectReturn = this->methodHasReturn(name, &methodParser);
     bool foundReturn = false;
-    bool foundException = false;
     bool foundPopulate = false;
     for (auto& child : fChildren) {
-        foundException |= MarkType::kDeprecated == child->fMarkType
-                || MarkType::kExperimental == child->fMarkType;
         foundPopulate |= MarkType::kPopulate == child->fMarkType;
         if (MarkType::kReturn != child->fMarkType) {
             if (MarkType::kParam == child->fMarkType) {
@@ -488,7 +485,7 @@
         }
         foundReturn = true;
     }
-    if (expectReturn && !foundReturn && !foundException && !foundPopulate) {
+    if (expectReturn && !foundReturn && !foundPopulate) {
         return methodParser.reportError<bool>("missing #Return marker");
     }
     const char* paren = methodParser.strnchr('(', methodParser.fEnd);
@@ -522,7 +519,7 @@
             foundParam = true;
 
         }
-        if (!foundParam && !foundException && !foundPopulate) {
+        if (!foundParam && !foundPopulate) {
             return methodParser.reportError<bool>("no #Param found");
         }
         if (')' == nextEnd[0]) {
@@ -552,12 +549,6 @@
             priorDef = child;
             continue;
         }
-        if (MarkType::kDeprecated == child->fMarkType) {
-            return true;
-        }
-        if (MarkType::kExperimental == child->fMarkType) {
-            return true;
-        }
         if (MarkType::kFormula == child->fMarkType) {
             continue;
         }
@@ -576,9 +567,6 @@
         if (MarkType::kPhraseRef == child->fMarkType) {
             continue;
         }
-        if (MarkType::kPrivate == child->fMarkType) {
-            return true;
-        }
         TextParser emptyCheck(fFileName, descStart, child->fStart, child->fLineCount);
         if (!emptyCheck.eof() && emptyCheck.skipWhiteSpace()) {
             descStart = emptyCheck.fChar;
@@ -933,31 +921,6 @@
     return false;
 }
 
-string Definition::incompleteMessage(DetailsType detailsType) const {
-    SkASSERT(!IncompleteAllowed(fMarkType));
-    auto iter = std::find_if(fChildren.begin(), fChildren.end(),
-            [](const Definition* test) { return IncompleteAllowed(test->fMarkType); });
-    SkASSERT(fChildren.end() != iter);
-    SkASSERT(Details::kNone == (*iter)->fDetails);
-    string message = MarkType::kExperimental == (*iter)->fMarkType ?
-            "Experimental." : "Deprecated.";
-    if (Details::kDoNotUse_Experiment == fDetails) {
-        message += " Do not use.";
-    } else if (Details::kNotReady_Experiment == fDetails) {
-        message += " Not ready for general use.";
-    } else if (Details::kSoonToBe_Deprecated == fDetails) {
-        message = "To be deprecated soon.";
-    } else if (Details::kTestingOnly_Experiment == fDetails) {
-        message += " For testing only.";
-    }
-    if (DetailsType::kPhrase == detailsType) {
-        message = message.substr(0, message.length() - 1);  // remove trailing period
-        std::replace(message.begin(), message.end(), '.', ':');
-        std::transform(message.begin(), message.end(), message.begin(), ::tolower);
-    }
-    return message;
-}
-
 bool Definition::isStructOrClass() const {
     if (MarkType::kStruct != fMarkType && MarkType::kClass != fMarkType) {
         return false;
diff --git a/tools/bookmaker/definition.h b/tools/bookmaker/definition.h
index 632135b..8aace77 100644
--- a/tools/bookmaker/definition.h
+++ b/tools/bookmaker/definition.h
@@ -148,7 +148,6 @@
     const Definition* hasChild(MarkType markType) const;
     bool hasMatch(string name) const;
     Definition* hasParam(string ref);
-    string incompleteMessage(DetailsType ) const;
     bool isClone() const { return fClone; }
 
     const Definition* iRootParent() const {
@@ -253,12 +252,12 @@
     char fMC = '#';
     bool fClone = false;
     bool fCloned = false;
-    bool fDeprecated = false;
     bool fOperatorConst = false;
     bool fPrivate = false;
     Details fDetails = Details::kNone;
     bool fMemberStart = false;
     bool fAnonymous = false;
+    bool fUndocumented = false;  // include symbol comment has deprecated, private, experimental
     mutable bool fVisited = false;
 };
 
diff --git a/tools/bookmaker/hackParser.cpp b/tools/bookmaker/hackParser.cpp
index 03823a3..dcd8346 100644
--- a/tools/bookmaker/hackParser.cpp
+++ b/tools/bookmaker/hackParser.cpp
@@ -73,10 +73,7 @@
                         || MarkType::kFormula == def->fMarkType
                         || MarkType::kAnchor == def->fMarkType
                         || MarkType::kList == def->fMarkType
-                        || MarkType::kTable == def->fMarkType
-                        || MarkType::kDeprecated == def->fMarkType
-                        || MarkType::kExperimental == def->fMarkType
-                        || MarkType::kPrivate == def->fMarkType;
+                        || MarkType::kTable == def->fMarkType;
                 } )) {
             continue;
         }
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp
index 523a3e8..a9ec3db 100644
--- a/tools/bookmaker/includeParser.cpp
+++ b/tools/bookmaker/includeParser.cpp
@@ -577,15 +577,15 @@
         break;
         // these continue a # directive link
         case KeyWord::kElif:
-        case KeyWord::kElse: {
+        case KeyWord::kElse:
             this->popObject();  // pop elif
             if (Bracket::kPound != fParent->fBracket) {
                 return this->reportError<bool>("expected preprocessor directive");
             }
             this->popBracket();  // pop if
             poundDef->fParent = fParent;
-            this->addDefinition(poundDef);  // push elif back
-        } break;
+            fParent = poundDef;  // push elif back
+        break;
         // this ends a # directive link
         case KeyWord::kEndif:
         // FIXME : should this be calling popBracket() instead?
@@ -644,6 +644,9 @@
         }
     }
     for (auto& classMapper : fIClassMap) {
+        if (classMapper.second.fUndocumented) {
+           continue;
+        }
         string className = classMapper.first;
         std::istringstream iss(className);
         string classStr;
@@ -769,17 +772,17 @@
                         def = root->find(withParens, RootDefinition::AllowParens::kNo);
                     }
                     if (!def) {
-                        if (!root->fDeprecated) {
+                        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;
-                        if (token.fDeprecated && !def->fDeprecated) {
-                            fFailed = !def->reportError<bool>("expect bmh to be marked deprecated");
-                        }
                     } else {
                        SkDebugf("method differs from bmh: %s\n", fullName.c_str());
                        fFailed = true;
@@ -818,7 +821,7 @@
                             def = root->find(anonName, RootDefinition::AllowParens::kYes);
                         }
                         if (!def) {
-                            if (!root->fDeprecated) {
+                            if (!token.fUndocumented) {
                                 SkDebugf("enum missing from bmh: %s\n", fullName.c_str());
                                 fFailed = true;
                             }
@@ -841,10 +844,8 @@
                         }
                     }
                     if (!hasCode) {
-                        if (!root->fDeprecated) {
-                            SkDebugf("enum code missing from bmh: %s\n", fullName.c_str());
-                            fFailed = true;
-                        }
+                        SkDebugf("enum code missing from bmh: %s\n", fullName.c_str());
+                        fFailed = true;
                         break;
                     }
                     if (!hasPopulate) {
@@ -855,21 +856,22 @@
                             fFailed = true;
                         }
                     }
-                    for (auto& child : token.fChildren) {
+                    for (auto& member : token.fTokens) {
+                        if (MarkType::kMember != member.fMarkType) {
+                            continue;
+                        }
                         string constName = MarkType::kEnumClass == token.fMarkType ?
                                 fullName : className;
-                        constName += "::" + child->fName;
+                        constName += "::" + member.fName;
                         def = root->find(constName, RootDefinition::AllowParens::kYes);
                         if (!def) {
-                            string innerName = classMapper.first + "::" + child->fName;
+                            string innerName = classMapper.first + "::" + member.fName;
                             def = root->find(innerName, RootDefinition::AllowParens::kYes);
                         }
                         if (!def) {
-                            if (string::npos == child->fName.find("Legacy_")) {
-                                if (!root->fDeprecated) {
-                                    SkDebugf("const missing from bmh: %s\n", constName.c_str());
-                                    fFailed = true;
-                                }
+                            if (!member.fUndocumented) {
+                                SkDebugf("const missing from bmh: %s\n", constName.c_str());
+                                fFailed = true;
                             }
                         } else {
                             def->fVisited = true;
@@ -879,7 +881,7 @@
                 case MarkType::kMember:
                     if (def) {
                         def->fVisited = true;
-                    } else if (!root->fDeprecated) {
+                    } else {
                         SkDebugf("member missing from bmh: %s\n", fullName.c_str());
                         fFailed = true;
                     }
@@ -887,7 +889,7 @@
                 case MarkType::kTypedef:
                     if (def) {
                         def->fVisited = true;
-                    } else if (!root->fDeprecated) {
+                    } else {
                         SkDebugf("typedef missing from bmh: %s\n", fullName.c_str());
                         fFailed = true;
                     }
@@ -895,9 +897,11 @@
                 case MarkType::kConst:
                     if (def) {
                         def->fVisited = true;
-                    } else if (!root->fDeprecated) {
-                        SkDebugf("const missing from bmh: %s\n", fullName.c_str());
-                        fFailed = true;
+                    } else {
+                        if (!token.fUndocumented) {
+                            SkDebugf("const missing from bmh: %s\n", fullName.c_str());
+                            fFailed = true;
+                        }
                     }
                     break;
                 default:
@@ -966,6 +970,11 @@
             MarkType::kStruct : MarkType::kClass;
     markupDef.fKeyWord = includeDef.fKeyWord;
     markupDef.fType = Definition::Type::kMark;
+    auto tokenIter = includeDef.fParent->fTokens.begin();
+    SkASSERT(includeDef.fParentIndex > 0);
+    std::advance(tokenIter, includeDef.fParentIndex - 1);
+    const Definition* priorComment = &*tokenIter;
+    markupDef.fUndocumented = priorComment->fUndocumented;
     fParent = &markupDef;
     return &markupDef;
 }
@@ -1625,6 +1634,9 @@
         if (this->isInternalName(token)) {
             continue;
         }
+        if (token.fUndocumented) {
+            continue;
+        }
         if (this->isConstructor(token, skClassName)) {
             hasConstructor = true;
             continue;
@@ -1783,9 +1795,32 @@
     return result;
 }
 
+bool IncludeParser::findCommentAfter(const Definition& includeDef, Definition* markupDef) {
+    const Definition* parent = includeDef.fParent;
+    int index = includeDef.fParentIndex;
+    auto wordIter = parent->fTokens.begin();
+    std::advance(wordIter, index);
+    SkASSERT(&*wordIter == &includeDef);
+    size_t commentLine = 0;
+    do {
+        wordIter = std::next(wordIter);
+        if (parent->fTokens.end() == wordIter) {
+            break;
+        }
+        commentLine = wordIter->fLineCount;
+    } while (Punctuation::kSemicolon != wordIter->fPunctuation);
+    wordIter = std::next(wordIter);
+    if (parent->fTokens.end() != wordIter && Bracket::kSlashSlash == wordIter->fBracket
+            && wordIter->fLineCount == commentLine) {
+        return this->parseComment(wordIter->fFileName, wordIter->fContentStart,
+                wordIter->fContentEnd, wordIter->fLineCount, markupDef, &markupDef->fUndocumented);
+    }
+    return true;
+}
+
 bool IncludeParser::findComments(const Definition& includeDef, Definition* markupDef) {
     // add comment preceding class, if any
-    const Definition* parent = includeDef.fParent;
+    Definition* parent = includeDef.fParent;
     int index = includeDef.fParentIndex;
     auto wordIter = parent->fTokens.begin();
     std::advance(wordIter, index);
@@ -1815,9 +1850,11 @@
     }
     while (commentIter != wordIter) {
         if (!this->parseComment(commentIter->fFileName, commentIter->fContentStart,
-                commentIter->fContentEnd, commentIter->fLineCount, markupDef)) {
+                commentIter->fContentEnd, commentIter->fLineCount, markupDef,
+                &markupDef->fUndocumented)) {
             return false;
         }
+        commentIter->fUndocumented = markupDef->fUndocumented;
         commentIter = std::next(commentIter);
     }
     return true;
@@ -1854,10 +1891,6 @@
 }
 
 Definition* IncludeParser::findMethod(const Definition& bmhDef) {
-    if (std::any_of(bmhDef.fChildren.begin(), bmhDef.fChildren.end(), [](Definition* def) {
-            return MarkType::kDeprecated == def->fMarkType; } )) {
-        return nullptr;
-    }
     auto doubleColon = bmhDef.fName.rfind("::");
     if (string::npos == doubleColon) {
         const auto& iGlobalMethod = fIFunctionMap.find(bmhDef.fName);
@@ -1874,6 +1907,7 @@
     const auto& iMethod = std::find_if(iTokens.begin(), iTokens.end(),
             [methodName](Definition& token) {
             return MarkType::kMethod == token.fMarkType
+                    && !token.fUndocumented
                     && (methodName == token.fName
                     || methodName == token.fName + "()"); } );
     if (iTokens.end() != iMethod) {
@@ -1910,6 +1944,9 @@
         if (MarkType::kMethod != token.fMarkType) {
             return false;
         }
+        if (token.fUndocumented) {
+            return false;
+        }
         TextParser parser(&token);
         const char* match = parser.strnstr(className.c_str(), parser.fEnd);
         if (!match) {
@@ -2027,6 +2064,9 @@
     if (!this->findComments(*includeDef, markupDef)) {
         return iter->reportError<bool>("find comments failed");
     }
+    if (markupDef->fUndocumented) {
+        includeDef->fUndocumented = true;
+    }
 //    if (1 != includeDef->fChildren.size()) {
 //        return false;  // fix me: SkCanvasClipVisitor isn't correctly parsed
 //    }
@@ -2107,10 +2147,29 @@
     return true;
 }
 
-bool IncludeParser::parseComment(string filename, const char* start, const char* end,
-        int lineCount, Definition* markupDef) {
+bool IncludeParser::isUndocumentable(string filename, const char* start, const char* end,
+        int lineCount) {
     TextParser parser(filename, start, end, lineCount);
+    const vector<string> skipWords = { "experimental",  "deprecated", "private" };
+    const vector<string> butNot = { "to be deprecated", "may be deprecated" };
+    const vector<string> alsoNot = { "todo" };
+    string match = parser.anyWord(skipWords, 0);
+    if ("" != match) {
+        if ("" == parser.anyWord(alsoNot, 0)
+                && ("deprecated" != match || "" == parser.anyWord(butNot, 2))) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool IncludeParser::parseComment(string filename, const char* start, const char* end,
+        int lineCount, Definition* markupDef, bool* undocumentedPtr) {
+    if (this->isUndocumentable(filename, start, end, lineCount)) {
+        *undocumentedPtr = true;
+    }
     // parse doxygen if present
+    TextParser parser(filename, start, end, lineCount);
     if (parser.startsWith("**")) {
         parser.next();
         parser.next();
@@ -2160,6 +2219,10 @@
     return true;
 }
 
+/*
+    find comment either in front of or after the const def and then extract if the
+    const is undocumented
+ */
 bool IncludeParser::parseConst(Definition* child, Definition* markupDef) {
     if (!markupDef) {
         fGlobals.emplace_back(MarkType::kConst, child->fContentStart, child->fContentEnd,
@@ -2170,7 +2233,14 @@
         if (!this->findComments(*child, globalMarkupChild)) {
             return false;
         }
-        fIConstMap[globalUniqueName] = globalMarkupChild;
+        if (!this->findCommentAfter(*child, globalMarkupChild)) {
+            return false;
+        }
+        if (globalMarkupChild->fUndocumented) {
+            child->fUndocumented = true;
+        } else {
+            fIConstMap[globalUniqueName] = globalMarkupChild;
+        }
         return true;
     }
     markupDef->fTokens.emplace_back(MarkType::kConst, child->fContentStart, child->fContentEnd,
@@ -2180,7 +2250,17 @@
     markupChild->fTerminator = markupChild->fContentEnd;
     IClassDefinition& classDef = fIClassMap[markupDef->fName];
     classDef.fConsts[child->fName] = markupChild;
-    fIConstMap[child->fName] = markupChild;
+    if (!this->findComments(*child, markupChild)) {
+        return false;
+    }
+    if (!this->findCommentAfter(*child, markupChild)) {
+        return false;
+    }
+    if (markupChild->fUndocumented) {
+        child->fUndocumented = true;
+    } else {
+        fIConstMap[child->fName] = markupChild;
+    }
     return true;
 }
 
@@ -2245,7 +2325,9 @@
         if (!this->findComments(*child, globalMarkupChild)) {
             return false;
         }
-        fIDefineMap[globalUniqueName] = globalMarkupChild;
+        if (!globalMarkupChild->fUndocumented) {
+            fIDefineMap[globalUniqueName] = globalMarkupChild;
+        }
         for (Param param : params) {
             globalMarkupChild->fTokens.emplace_back(MarkType::kParam, param.fStart, param.fEnd,
                     child->fLineCount, globalMarkupChild, '\0');
@@ -2264,28 +2346,29 @@
     if (!this->findComments(*child, markupChild)) {
         return false;
     }
-    classDef.fDefines[nameStr] = markupChild;
-    fIDefineMap[nameStr] = markupChild;
+    if (markupChild->fUndocumented) {
+        child->fUndocumented = true;
+    } else {
+        classDef.fDefines[nameStr] = markupChild;
+        fIDefineMap[nameStr] = markupChild;
+    }
     return true;
 }
 
 bool IncludeParser::parseEnum(Definition* child, Definition* markupDef) {
-	TextParser parser(child);
-	parser.skipToEndBracket('{');
-	if (parser.eof()) {
+	if (!child->fTokens.size()) {
 		return true;	// if enum is a forward declaration, do nothing
 	}
-	parser.next();
-	string nameStr;
-    if (child->fTokens.size() > 0) {
-        auto token = child->fTokens.begin();
-        if (Definition::Type::kKeyWord == token->fType && KeyWord::kClass == token->fKeyWord) {
-            token = token->fTokens.begin();
-        }
-        if (Definition::Type::kWord == token->fType) {
-            nameStr += string(token->fStart, token->fContentEnd - token->fStart);
-        }
+    bool isEnumClass = false;
+    Definition* parent = child;
+    auto token = parent->fTokens.begin();
+    if (Definition::Type::kKeyWord == token->fType && KeyWord::kClass == token->fKeyWord) {
+        isEnumClass = true;
+        parent = &*token;
+        token = parent->fTokens.begin();
     }
+    SkASSERT(Definition::Type::kWord == token->fType);
+    string nameStr = string(token->fStart, token->fContentEnd - token->fStart);
     Definition* markupChild;
     if (!markupDef) {
         fGlobals.emplace_back(MarkType::kEnum, child->fContentStart, child->fContentEnd,
@@ -2294,7 +2377,9 @@
         string globalUniqueName = this->uniqueName(fIEnumMap, nameStr);
         markupChild->fName = globalUniqueName;
         markupChild->fTerminator = child->fContentEnd;
-        fIEnumMap[globalUniqueName] = markupChild;
+        if (!markupChild->fUndocumented) {
+            fIEnumMap[globalUniqueName] = markupChild;
+        }
     } else {
         markupDef->fTokens.emplace_back(MarkType::kEnum, child->fContentStart, child->fContentEnd,
             child->fLineCount, markupDef, '\0');
@@ -2302,116 +2387,21 @@
     }
     SkASSERT(KeyWord::kNone == markupChild->fKeyWord);
     markupChild->fKeyWord = KeyWord::kEnum;
-    TextParser enumName(child);
-    enumName.skipExact("enum ");
-    enumName.skipWhiteSpace();
-    if (enumName.skipExact("class ")) {
-        enumName.skipWhiteSpace();
+    if (isEnumClass) {
         markupChild->fMarkType = MarkType::kEnumClass;
     }
-    const char* nameStart = enumName.fChar;
-    enumName.skipToSpace();
     if (markupDef) {
-        markupChild->fName = markupDef->fName + "::" +
-                string(nameStart, (size_t) (enumName.fChar - nameStart));
+        markupChild->fName = markupDef->fName + "::" + nameStr;
     }
     if (!this->findComments(*child, markupChild)) {
         return false;
     }
-    const char* dataEnd;
-    do {
-        parser.skipWhiteSpace();
-        if ('}' == parser.peek()) {
-            break;
-        }
-        Definition* comment = nullptr;
-        // note that comment, if any, can be before or after (on the same line, though) as member
-        if ('#' == parser.peek()) {
-            // fixme: handle preprecessor, but just skip it for now
-            parser.skipToLineStart();
-        }
-        while (parser.startsWith("/*") || parser.startsWith("//")) {
-            parser.next();
-            const char* start = parser.fChar;
-            const char* end;
-            if ('*' == parser.peek()) {
-                end = parser.strnstr("*/", parser.fEnd);
-                parser.fChar = end;
-                parser.next();
-                parser.next();
-            } else {
-                end = parser.trimmedLineEnd();
-                parser.skipToLineStart();
-            }
-            markupChild->fTokens.emplace_back(MarkType::kComment, start, end, parser.fLineCount,
-                    markupChild, '\0');
-            comment = &markupChild->fTokens.back();
-            comment->fTerminator = end;
-            if (!this->parseComment(parser.fFileName, start, end, parser.fLineCount, comment)) {
-                return false;
-            }
-            parser.skipWhiteSpace();
-        }
-        parser.skipWhiteSpace();
-        const char* memberStart = parser.fChar;
-        if ('}' == memberStart[0]) {
-            break;
-        }
-        // if there's comment on same the line as member def, output first as if it was before
-
-        parser.skipToNonName();
-        string memberName(memberStart, parser.fChar);
-        if (parser.eof() || !parser.skipWhiteSpace()) {
-            return parser.reportError<bool>("enum member must end with comma 1");
-        }
-        const char* dataStart = parser.fChar;
-        if ('=' == parser.peek()) {
-            parser.skipToEndBracket(',');
-        }
-        if (!parser.eof() && '#' == parser.peek()) {
-            // fixme: handle preprecessor, but just skip it for now
-            continue;
-        }
-        if (parser.eof() || ',' != parser.peek()) {
-            return parser.reportError<bool>("enum member must end with comma 2");
-        }
-        dataEnd = parser.fChar;
-        const char* start = parser.anyOf("/\n");
-        SkASSERT(start);
-        parser.skipTo(start);
-        if ('/' == parser.next()) {
-            char slashStar = parser.next();
-            if ('/' == slashStar || '*' == slashStar) {
-                TextParserSave save(&parser);
-                char doxCheck = parser.next();
-                if ((slashStar != doxCheck && '!' != doxCheck) || '<' != parser.next()) {
-                    save.restore();
-                }
-            }
-            parser.skipWhiteSpace();
-            const char* commentStart = parser.fChar;
-            if ('/' == slashStar) {
-                parser.skipToEndBracket('\n');
-            } else {
-                parser.skipToEndBracket("*/");
-            }
-            SkASSERT(!parser.eof());
-            const char* commentEnd = parser.fChar;
-            markupChild->fTokens.emplace_back(MarkType::kComment, commentStart, commentEnd,
-                    parser.fLineCount, markupChild, '\0');
-            comment = &markupChild->fTokens.back();
-            comment->fTerminator = commentEnd;
-        }
-        markupChild->fTokens.emplace_back(MarkType::kMember, dataStart, dataEnd, parser.fLineCount,
-                markupChild, '\0');
-        Definition* member = &markupChild->fTokens.back();
-        member->fName = memberName;
-        if (comment) {
-            member->fChildren.push_back(comment);
-            comment->fPrivate = true;
-        }
-        markupChild->fChildren.push_back(member);
-    } while (true);
+    if (markupChild->fUndocumented) {
+        child->fUndocumented = true;
+    }
+    if (!this->parseEnumConst(token, parent->fTokens.end(), markupChild)) {
+        return false;
+    }
     for (auto outsideMember : child->fChildren) {
         if (Definition::Type::kBracket == outsideMember->fType) {
             continue;
@@ -2435,11 +2425,111 @@
         string fullName = markupChild->fName;
         markupChild->fName = uniqueName;
         classDef.fEnums[uniqueName] = markupChild;
-        fIEnumMap[fullName] = markupChild;
+        if (!markupChild->fUndocumented) {
+            fIEnumMap[fullName] = markupChild;
+        }
     }
     return true;
 }
 
+bool IncludeParser::parseOneEnumConst(list<Definition>& constList,
+        Definition* markupChild, bool skipWord) {
+    auto memberIter = constList.begin();
+    const auto memberIterEnd = constList.end();
+    if (skipWord) {
+        SkASSERT(Definition::Type::kWord == memberIter->fType);
+        memberIter = std::next(memberIter);
+        SkASSERT(memberIterEnd != memberIter);
+    }
+    // token array has parse atoms; child array has comments
+    bool undocumented = false;
+    while (memberIterEnd != memberIter) {
+        while (Bracket::kSlashStar == memberIter->fBracket) {
+            if (!this->parseComment(memberIter->fFileName, memberIter->fContentStart,
+                    memberIter->fContentEnd, memberIter->fLineCount, markupChild, &undocumented)) {
+                return false;
+            }
+            memberIter = std::next(memberIter);
+            if (memberIterEnd == memberIter) {
+                return false;
+            }
+        }
+        if (Bracket::kPound == memberIter->fBracket) {
+            KeyWord keyWord = memberIter->fKeyWord;
+            bool sawIf = KeyWord::kIfdef == keyWord || KeyWord::kIf == keyWord
+                    || KeyWord::kElif == keyWord;
+            if (sawIf || KeyWord::kElse == keyWord) {
+                if (!parseOneEnumConst(memberIter->fTokens, markupChild, sawIf)) {
+                    return false;
+                }
+            } else {
+                SkASSERT(KeyWord::kEndif == keyWord || KeyWord::kError == keyWord);
+            }
+            memberIter = std::next(memberIter);
+            if (memberIterEnd == memberIter) {
+                break;
+            }
+            continue;
+        }
+        while (Definition::Type::kWord != memberIter->fType) {
+            memberIter = std::next(memberIter);
+            if (memberIterEnd == memberIter) {
+                return false;
+            }
+        }
+        auto memberStart = memberIter;
+        Definition* memberEnd = nullptr;
+        const char* last;
+        do {
+            last = memberIter->fContentEnd;
+            memberIter = std::next(memberIter);
+            if (memberIterEnd == memberIter) {
+                break;
+            }
+            memberEnd = &*memberIter;
+        } while (string::npos == string(last, memberIter->fContentStart).find(','));
+        if (!memberEnd) {
+            return false;
+        }
+        if (memberIterEnd != memberIter && Bracket::kSlashSlash == memberIter->fBracket) {
+            if (!this->parseComment(memberIter->fFileName, memberIter->fContentStart,
+                    memberIter->fContentEnd, memberIter->fLineCount, markupChild, &undocumented)) {
+                return false;
+            }
+            memberIter = std::next(memberIter);
+        }
+        markupChild->fTokens.emplace_back(MarkType::kMember, memberStart->fContentStart,
+                memberEnd->fContentEnd, memberStart->fLineCount, markupChild, '\0');
+        Definition* markupMember = &markupChild->fTokens.back();
+        string name = string(memberStart->fContentStart, memberStart->length());
+        memberStart->fName = name;
+        markupMember->fName = name;
+        memberStart->fUndocumented = undocumented;
+        markupMember->fUndocumented = undocumented;
+        memberStart->fMarkType = MarkType::kMember;
+        undocumented = false;
+    }
+    return true;
+}
+
+bool IncludeParser::parseEnumConst(list<Definition>::iterator& tokenIter,
+        const list<Definition>::iterator& tokenEnd, Definition* markupChild) {
+    SkASSERT(Definition::Type::kWord == tokenIter->fType);  // should be enum name
+    tokenIter = std::next(tokenIter);
+    SkASSERT(tokenEnd != tokenIter);
+    if (Definition::Type::kKeyWord == tokenIter->fType) {
+        SkASSERT((unsigned) tokenIter->fKeyWord < SK_ARRAY_COUNT(kKeyWords));
+        SkASSERT(KeyProperty::kNumber == kKeyWords[(int) tokenIter->fKeyWord].fProperty);
+        tokenIter = std::next(tokenIter);
+        SkASSERT(tokenEnd != tokenIter);
+    }
+    SkASSERT(Punctuation::kLeftBrace == tokenIter->fPunctuation);
+    tokenIter = std::next(tokenIter);
+    SkASSERT(tokenEnd != tokenIter);
+    SkASSERT(Bracket::kBrace == tokenIter->fBracket);
+    return parseOneEnumConst(tokenIter->fTokens, markupChild, false);
+}
+
 bool IncludeParser::parseInclude(string name) {
     fParent = &fIncludeMap[name];
     fParent->fName = name;
@@ -2473,7 +2563,9 @@
     string uniqueName = this->uniqueName(classDef.fMethods, nameStr);
     markupChild->fName = uniqueName;
     markupChild->fTerminator = markupChild->fContentEnd;
-    classDef.fMembers[uniqueName] = markupChild;
+    if (!markupChild->fUndocumented) {
+        classDef.fMembers[uniqueName] = markupChild;
+    }
     if (child->fParentIndex >= 2) {
         auto comment = child->fParent->fTokens.begin();
         std::advance(comment, child->fParentIndex - 2);
@@ -2633,7 +2725,11 @@
         if (!this->findComments(*child, globalMarkupChild)) {
             return false;
         }
-        fIFunctionMap[globalUniqueName] = globalMarkupChild;
+        if (globalMarkupChild->fUndocumented) {
+            child->fUndocumented = true;
+        } else {
+            fIFunctionMap[globalUniqueName] = globalMarkupChild;
+        }
         return true;
     }
     markupDef->fTokens.emplace_back(MarkType::kMethod, start, end, tokenIter->fLineCount,
@@ -2649,7 +2745,11 @@
         if (!this->findComments(*child, markupChild)) {
             return false;
         }
-        classDef.fMethods[uniqueName] = markupChild;
+        if (markupChild->fUndocumented) {
+            tokenIter->fUndocumented = true;
+        } else {
+            classDef.fMethods[uniqueName] = markupChild;
+        }
     }
     return true;
 }
@@ -2780,6 +2880,10 @@
                             // ignored for now
                             break;
                         case KeyWord::kElse:
+                            if (!this->parseObjects(child, markupDef)) {
+                                return false;
+                            }
+                            break;
                         case KeyWord::kElif:
                             // todo: handle these
                             break;
@@ -2841,7 +2945,11 @@
         if (!this->findComments(*child, globalMarkupChild)) {
             return false;
         }
-        fITypedefMap[globalUniqueName] = globalMarkupChild;
+        if (globalMarkupChild->fUndocumented) {
+            child->fUndocumented = true;
+        } else {
+            fITypedefMap[globalUniqueName] = globalMarkupChild;
+        }
         child->fName = nameStr;
         return true;
     }
diff --git a/tools/bookmaker/includeParser.h b/tools/bookmaker/includeParser.h
index cd01687..f132525 100644
--- a/tools/bookmaker/includeParser.h
+++ b/tools/bookmaker/includeParser.h
@@ -163,6 +163,7 @@
 
     string elidedCodeBlock(const Definition& );
     string filteredBlock(string inContents, string filterContents);
+    bool findCommentAfter(const Definition& includeDef, Definition* markupDef);
     bool findComments(const Definition& includeDef, Definition* markupDef);
     Definition* findIncludeObject(const Definition& includeDef, MarkType markType,
                                   string typeName);
@@ -175,14 +176,17 @@
     bool isInternalName(const Definition& token);
     bool isMember(const Definition& token) const;
     bool isOperator(const Definition& token);
+    bool isUndocumentable(string filename, const char* start, const char* end, int lineCount);
     Definition* parentBracket(Definition* parent) const;
     bool parseChar();
     bool parseComment(string filename, const char* start, const char* end, int lineCount,
-            Definition* markupDef);
+            Definition* markupDef, bool* undocumentedPtr);
     bool parseClass(Definition* def, IsStruct);
     bool parseConst(Definition* child, Definition* markupDef);
     bool parseDefine(Definition* child, Definition* markupDef);
     bool parseEnum(Definition* child, Definition* markupDef);
+    bool parseEnumConst(list<Definition>::iterator& tokenIter,
+            const list<Definition>::iterator& tokenEnd, Definition* markupChild);
 
     bool parseFromFile(const char* path) override {
         this->reset();
@@ -198,6 +202,7 @@
     bool parseMethod(Definition* child, Definition* markupDef);
     bool parseObject(Definition* child, Definition* markupDef);
     bool parseObjects(Definition* parent, Definition* markupDef);
+    bool parseOneEnumConst(list<Definition>& constList, Definition* markupChild, bool skipWord);
     bool parseTemplate(Definition* child, Definition* markupDef);
     bool parseTypedef(Definition* child, Definition* markupDef);
     bool parseUsing();
@@ -215,6 +220,9 @@
     }
 
     void pushBracket(Bracket bracket) {
+        if ("#else" == string(fChar, 5)) {
+            SkDebugf("");
+        }
         this->setBracketShortCuts(bracket);
         fParent->fTokens.emplace_back(bracket, fChar, fLineCount, fParent, '\0');
         Definition* container = &fParent->fTokens.back();
diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp
index 77851f6..379c4eb 100644
--- a/tools/bookmaker/includeWriter.cpp
+++ b/tools/bookmaker/includeWriter.cpp
@@ -20,15 +20,6 @@
         oneMember = true;
         int lineLen = 0;
         for (auto& itemChild : item->fChildren) {
-            if (MarkType::kExperimental == itemChild->fMarkType) {
-                lineLen = sizeof("experimental") - 1;
-                break;
-            }
-            if (MarkType::kDeprecated == itemChild->fMarkType) {
-                lineLen = sizeof("deprecated") - 1;
-                // todo: look for 'soon'
-                break;
-            }
             if (MarkType::kLine == itemChild->fMarkType) {
                 lineLen = itemChild->length();
                 break;
@@ -100,15 +91,6 @@
     int commentLen = (int) (def->fContentEnd - commentStart);
     bool breakOut = false;
     SkDEBUGCODE(bool wroteCode = false);
-    if (def->fDeprecated) {
-        if (fReturnOnWrite) {
-            return true;
-        }
-        string message = def->incompleteMessage(Definition::DetailsType::kSentence);
-        this->writeString(message);
-        this->lfcr();
-        wroteSomething = true;
-    }
     const Definition* lastDescription = def;
     for (auto prop : def->fChildren) {
         fLastDescription = lastDescription;
@@ -169,61 +151,6 @@
                 this->lfcr();
                 wroteSomething = true;
             }
-            case MarkType::kDeprecated:
-            case MarkType::kPrivate:
-                commentLen = (int) (prop->fStart - commentStart);
-                if (commentLen > 0) {
-                    SkASSERT(commentLen < 1000);
-                    if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
-                        if (fReturnOnWrite) {
-                            return true;
-                        }
-                        this->lfcr();
-                        wroteSomething = true;
-                    }
-                }
-                commentStart = prop->fContentStart;
-                if (MarkType::kPrivate != prop->fMarkType && ' ' < commentStart[0]) {
-                    commentStart = strchr(commentStart, '\n');
-                }
-                if (MarkType::kBug == prop->fMarkType) {
-                    commentStart = prop->fContentEnd;
-                }
-                commentLen = (int) (prop->fContentEnd - commentStart);
-                if (commentLen > 0) {
-                    wroteSomething |= this->writeBlockIndent(commentLen, commentStart, false);
-                    if (wroteSomething && fReturnOnWrite) {
-                        return true;
-                    }
-                    const char* end = commentStart + commentLen;
-                    while (end > commentStart && ' ' == end[-1]) {
-                        --end;
-                    }
-                    if (end > commentStart && '\n' == end[-1]) {
-                        this->lfcr();
-                    }
-                }
-                commentStart = prop->fTerminator;
-                commentLen = (int) (def->fContentEnd - commentStart);
-                break;
-            case MarkType::kExperimental:
-                commentStart = prop->fContentStart;
-                if (' ' < commentStart[0]) {
-                    commentStart = strchr(commentStart, '\n');
-                }
-                commentLen = (int) (prop->fContentEnd - commentStart);
-                if (commentLen > 0) {
-                    if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, Phrase::kNo)) {
-                        if (fReturnOnWrite) {
-                            return true;
-                        }
-                        this->lfcr();
-                        wroteSomething = true;
-                    }
-                }
-                commentStart = prop->fTerminator;
-                commentLen = (int) (def->fContentEnd - commentStart);
-                break;
             case MarkType::kFormula: {
                 commentLen = prop->fStart - commentStart;
                 if (commentLen > 0) {
@@ -382,7 +309,7 @@
     if (!breakOut) {
         commentLen = (int) (def->fContentEnd - commentStart);
     }
-    SkASSERT(wroteCode || (commentLen > 0 && commentLen < 1500) || def->fDeprecated);
+    SkASSERT(wroteCode || (commentLen > 0 && commentLen < 1500));
     if (commentLen > 0) {
         if (Wrote::kNone != this->rewriteBlock(commentLen, commentStart, phrase)) {
             if (fReturnOnWrite) {
@@ -543,9 +470,7 @@
 
 const Definition* IncludeWriter::enumMemberForComment(const Definition* currentEnumItem) const {
     for (auto constItem : currentEnumItem->fChildren) {
-        if (MarkType::kLine == constItem->fMarkType
-                || MarkType::kExperimental == constItem->fMarkType
-                || MarkType::kDeprecated == constItem->fMarkType) {
+        if (MarkType::kLine == constItem->fMarkType) {
             return constItem;
         }
     }
@@ -562,9 +487,6 @@
             shortComment = string(constItem->fContentStart, constItem->length());
             break;
         }
-        if (IncompleteAllowed(constItem->fMarkType)) {
-            shortComment = constItem->fParent->incompleteMessage(Definition::DetailsType::kPhrase);
-        }
     }
     if (!shortComment.length()) {
         currentEnumItem->reportError<void>("missing #Line or #Deprecated or #Experimental");
@@ -675,7 +597,7 @@
 // put ones that fit on same line, ones that are too big wrap
 void IncludeWriter::enumMembersOut(Definition& child) {
     ItemState state = ItemState::kNone;
-    const Definition* currentEnumItem;
+    const Definition* currentEnumItem = nullptr;
     LastItem last = { nullptr, nullptr };
     auto brace = child.fChildren[0];
     if (KeyWord::kClass == brace->fKeyWord) {
@@ -704,14 +626,14 @@
                 continue;
             }
         }
-        if (ItemState::kNone != state) {
+        if (ItemState::kNone != state && currentEnumItem) {
             this->enumMemberOut(currentEnumItem, child, item, preprocessor);
             item.reset();
             this->setStartBack(token.fContentStart, &token);
             state = ItemState::kNone;
             last.fStart = nullptr;
         }
-        SkASSERT(ItemState::kNone == state);
+        SkASSERT(ItemState::kNone == state || !currentEnumItem);
         if (!last.fStart) {
             last.fStart = fStart;
         }
@@ -721,7 +643,7 @@
     if (ItemState::kName == state) {
         state = this->enumMemberName(child, nullptr, &item, &last, &currentEnumItem);
     }
-    if (ItemState::kValue == state || ItemState::kComment == state) {
+    if ((ItemState::kValue == state || ItemState::kComment == state) && currentEnumItem) {
         this->enumMemberOut(currentEnumItem, child, item, preprocessor);
     }
     this->indentOut();
@@ -800,6 +722,7 @@
     IterState* iterState = &iterStack[0];
     Preprocessor preprocessor;
     string enumName;
+    bool undocumented = false;
     while (iterState->fDefIter != iterState->fDefEnd) {
         auto& token = *iterState->fDefIter++;
         if (this->enumPreprocessor(&token, MemberPass::kCount, iterStack, &iterState,
@@ -826,7 +749,9 @@
             }
         }
         if (ItemState::kNone != state) {
-            this->checkEnumLengths(child, enumName, &lengths);
+            if (!undocumented) {
+                this->checkEnumLengths(child, enumName, &lengths);
+            }
             lengths.fCurValue = 0;
             state = ItemState::kNone;
         }
@@ -834,9 +759,10 @@
         lastEnd = token.fContentEnd;
         lengths.fCurName = (int) (lastEnd - token.fContentStart);
         enumName = string(token.fContentStart, lengths.fCurName);
+        undocumented = token.fUndocumented;
         state = ItemState::kName;
     }
-    if (ItemState::kNone != state) {
+    if (ItemState::kNone != state && !undocumented) {
         this->checkEnumLengths(child, enumName, &lengths);
     }
     fEnumItemValueTab = lengths.fLongestName + fIndent + 1 /* 1: , */ ;
@@ -872,8 +798,7 @@
         enumItem = testItem;
         break;
     }
-    SkASSERT(enumItem);
-    return enumItem;
+    return enumItem;    // returns nullptr if matchName is undocumented
 }
 
 // walk children and output complete method doxygen description
@@ -891,9 +816,6 @@
         this->indentIn(IndentKind::kMethodOut);
         fIndentNext = false;
     }
-    if (string::npos != method->fName.find("validate")) {
-        SkDebugf("");
-    }
     if (method->fChildren.end() != std::find_if(method->fChildren.begin(), method->fChildren.end(),
             [](const Definition* def) { return MarkType::kPopulate == def->fMarkType; } )) {
         std::list<Definition>::iterator iter;
@@ -982,11 +904,7 @@
     this->writeString(child.fName.c_str());
     fIndent += 4;
     this->lfcr();
-    if (child.fDeprecated) {
-        this->writeString(child.incompleteMessage(Definition::DetailsType::kSentence));
-    } else {
-        this->rewriteBlock((int)(commentEnd - commentStart), commentStart, Phrase::kNo);
-    }
+    this->rewriteBlock((int)(commentEnd - commentStart), commentStart, Phrase::kNo);
     fIndent -= 4;
     this->lfcr();
     this->writeCommentTrailer(OneLine::kNo);
@@ -1519,10 +1437,6 @@
                 }
                 method = this->findMethod(methodName, root);
                 if (!method) {
-                    if (fBmhStructDef && fBmhStructDef->fDeprecated) {
-                        fContinuation = nullptr;
-                        continue;
-                    }
                     return child.reportError<bool>("method not found");
                 }
                 this->methodOut(method, child);
@@ -1546,9 +1460,6 @@
                 this->methodOut(method, child);
                 sawConst = false;
                 continue;
-            } else if (fBmhStructDef && fBmhStructDef->fDeprecated) {
-                fContinuation = nullptr;
-                continue;
             }
             if (KeyWord::kTemplate == child.fParent->fKeyWord) {
                 // incomplete; no support to template specialization in public includes
@@ -1567,6 +1478,9 @@
             if (this->isInternalName(child)) {
                 continue;
             }
+            if (child.fUndocumented) {
+                continue;
+            }
             if (KeyWord::kTemplate == child.fParent->fKeyWord) {
                 // todo: support template specializations
                 continue;
@@ -1619,6 +1533,9 @@
             continue;
         }
         if (Definition::Type::kKeyWord == child.fType) {
+            if (child.fUndocumented) {
+                continue;
+            }
             switch (child.fKeyWord) {
                 case KeyWord::kStruct:
                 case KeyWord::kClass:
@@ -1745,34 +1662,32 @@
                                 priorBlock = test;
                             }
                       // FIXME: trigger error earlier if inner #Struct or #Class is missing #Code
-                            if (!fBmhStructDef->fDeprecated) {
-                                SkASSERT(codeBlock);
-                                SkASSERT(nextBlock);  // FIXME: check enum for correct order earlier
-                                const char* commentStart = codeBlock->fTerminator;
-                                const char* commentEnd = nextBlock->fStart;
-                      // FIXME: trigger error if #Code is present but comment is before it earlier
-                                SkASSERT(priorBlock); // code always preceded by #Line (I think)
-                                TextParser priorComment(priorBlock->fFileName,
-                                        priorBlock->fTerminator, codeBlock->fStart,
-                                        priorBlock->fLineCount);
-                                priorComment.trimEnd();
-                                if (!priorComment.eof()) {
-                                    return priorBlock->reportError<bool>(
-                                            "expect no comment before #Code");
-                                }
-                                TextParser nextComment(codeBlock->fFileName, commentStart,
-                                        commentEnd, codeBlock->fLineCount);
-                                nextComment.trimEnd();
-                                if (!priorComment.eof()) {
-                                    return priorBlock->reportError<bool>(
-                                            "expect comment after #Code");
-                                }
-                                if (!nextComment.eof()) {
-
-                                }
-                                fIndentNext = true;
-                                this->structOut(root, *fBmhStructDef, commentStart, commentEnd);
+                            SkASSERT(codeBlock);
+                            SkASSERT(nextBlock);  // FIXME: check enum for correct order earlier
+                            const char* commentStart = codeBlock->fTerminator;
+                            const char* commentEnd = nextBlock->fStart;
+                    // FIXME: trigger error if #Code is present but comment is before it earlier
+                            SkASSERT(priorBlock); // code always preceded by #Line (I think)
+                            TextParser priorComment(priorBlock->fFileName,
+                                    priorBlock->fTerminator, codeBlock->fStart,
+                                    priorBlock->fLineCount);
+                            priorComment.trimEnd();
+                            if (!priorComment.eof()) {
+                                return priorBlock->reportError<bool>(
+                                        "expect no comment before #Code");
                             }
+                            TextParser nextComment(codeBlock->fFileName, commentStart,
+                                    commentEnd, codeBlock->fLineCount);
+                            nextComment.trimEnd();
+                            if (!priorComment.eof()) {
+                                return priorBlock->reportError<bool>(
+                                        "expect comment after #Code");
+                            }
+                            if (!nextComment.eof()) {
+
+                            }
+                            fIndentNext = true;
+                            this->structOut(root, *fBmhStructDef, commentStart, commentEnd);
                         }
                         fDeferComment = nullptr;
                     } else {
@@ -1957,14 +1872,11 @@
                     fIndentNext = true;
                 }
                 SkASSERT(fBmhStructDef);
-                if (!fBmhStructDef->fDeprecated) {
-                    memberEnd = this->structMemberOut(memberStart, child);
-                    startDef = &child;
-                    this->setStart(child.fContentEnd + 1, &child);
-                    fDeferComment = nullptr;
-                }
-            } else if (MarkType::kNone == child.fMarkType && sawConst
-                    && fEnumDef && !fEnumDef->fDeprecated) {
+                memberEnd = this->structMemberOut(memberStart, child);
+                startDef = &child;
+                this->setStart(child.fContentEnd + 1, &child);
+                fDeferComment = nullptr;
+            } else if (MarkType::kNone == child.fMarkType && sawConst && fEnumDef) {
                 const Definition* bmhConst = nullptr;
                 string match;
                 if (root) {
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp
index 90702d8..20a48ae 100644
--- a/tools/bookmaker/mdOut.cpp
+++ b/tools/bookmaker/mdOut.cpp
@@ -75,14 +75,8 @@
         if (!std::any_of(allDefs.begin(), allDefs.end(),
                 [str](AnchorDef compare) { return compare.fDef == str; } )) {
             MarkType markType = fLastDef->fMarkType;
-            if (MarkType::kMethod == markType
-                    && std::any_of(fLastDef->fChildren.begin(), fLastDef->fChildren.end(),
-                    [](const Definition* compare) {
-                    return IncompleteAllowed(compare->fMarkType); } )) {
-                markType = MarkType::kDeprecated;
-            }
             if (MarkType::kMethod == markType && fLastDef->fClone) {
-                markType = MarkType::kDeprecated;  // TODO: hack to allow missing reference
+                SkASSERT(0);  // incomplete
             }
             allDefs.push_back( { str, markType } );
         }
@@ -581,7 +575,7 @@
                 && fGlobals->fRefMap.end() != fGlobals->fRefMap.find(fWord + ' ');
         if (!withSpace && (Resolvable::kInclude == fResolvable ? !fInMatrix :
                 '"' != fPriorSeparator.back() || '"' != fSeparator.back())) {
-            SkDebugf("fWord %s not found\n", fWord.c_str());
+            SkDebugf("word %s not found\n", fWord.c_str());
             fBmhParser->fGlobalNames.fRefMap[fWord] = nullptr;
         }
     }
@@ -601,6 +595,12 @@
             break;
         }
         s.fWord = string(start, s.fEnd - start);
+        if ("TODO" == s.fWord) {
+            while('\n' != *s.fEnd++)
+                ;
+            start = s.fEnd;
+            continue;
+        }
         s.setLower();
         if (s.setPriorSpaceWord(&start)) {
             continue;
@@ -767,8 +767,7 @@
                 break;
             }
             if (allRefsEnded || (!allDefsEnded && allDefsIter->fDef < *allRefsIter)) {
-                if (MarkType::kParam != allDefsIter->fMarkType
-                        && !IncompleteAllowed(allDefsIter->fMarkType)) {
+                if (MarkType::kParam != allDefsIter->fMarkType) {
                     // If undocumented but parent or child is referred to: good enough for now
                     bool goodEnough = false;
                     if ("undocumented" == defIter->first) {
@@ -845,9 +844,6 @@
 }
 
 void MdOut::childrenOut(Definition* def, const char* start) {
-    if (MarkType::kDeprecated == def->fMarkType || MarkType::kExperimental == def->fMarkType) {
-        return;
-    }
     const char* end;
     fLineCount = def->fLineCount;
     if (MarkType::kEnumClass == def->fMarkType) {
@@ -1376,11 +1372,6 @@
             FPRINTF("%s", out_table_data_description_start().c_str()); // start of Description
             this->lfAlways(1);
         } break;
-        case MarkType::kDeprecated:
-            this->writeString(def->fParent->incompleteMessage(
-                    Definition::DetailsType::kSentence).c_str());
-            this->lf(2);
-            break;
         case MarkType::kDescription:
             fInDescription = true;
             this->writePending();
@@ -1436,11 +1427,6 @@
                 fLiteralAndIndent = true;
             }
             } break;
-        case MarkType::kExperimental:
-            this->writeString(def->fParent->incompleteMessage(
-                    Definition::DetailsType::kSentence).c_str());
-            this->lf(2);
-            break;
         case MarkType::kExternal:
             break;
         case MarkType::kFile:
@@ -1522,17 +1508,12 @@
             this->lf(2);
             fTableState = TableState::kNone;
             fMethod = def;
-            if ("SkTextBlobBuilder::allocRun_2" == def->fName) {
-                SkDebugf("");
-            }
             Definition* iMethod = fIncludeParser.findMethod(*def);
             if (iMethod) {
                 fMethod = iMethod;
                 paramMap.fParent = &fBmhParser.fGlobalNames;
                 paramMap.setParams(def, iMethod);
                 fNames = &paramMap;
-            } else {
-                SkDebugf("");
             }
             } break;
         case MarkType::kNoExample:
@@ -1721,12 +1702,6 @@
                 }
             }
             } break;
-        case MarkType::kPrivate:
-            this->writeString("Private:");
-            this->writeSpace();
-            this->writeBlock(def->length(), def->fContentStart);
-            this->lf(2);
-            break;
         case MarkType::kReturn:
             this->returnHeaderOut(prior, def);
             break;
@@ -1972,8 +1947,6 @@
             break;
         case MarkType::kPhraseDef:
             break;
-        case MarkType::kPrivate:
-            break;
         case MarkType::kSubtopic:
             SkASSERT(def);
             do {
@@ -2375,9 +2348,6 @@
         items[entryName] = entry;
     }
     for (auto entry : items) {
-        if (entry.second->fDeprecated) {
-            continue;
-        }
         if (!this->subtopicRowOut(entry.first, entry.second)) {
             return;
         }
diff --git a/tools/bookmaker/parserCommon.h b/tools/bookmaker/parserCommon.h
index f31cbd6..f8e0ca8 100644
--- a/tools/bookmaker/parserCommon.h
+++ b/tools/bookmaker/parserCommon.h
@@ -64,6 +64,9 @@
     }
 
     void addDefinition(Definition* def) {
+        if (KeyWord::kElse == def->fKeyWord) {
+            SkDebugf("");
+        }
         fParent->fChildren.push_back(def);
         fParent = def;
     }
@@ -161,6 +164,9 @@
    }
 
     void setAsParent(Definition* definition) {
+        if (KeyWord::kElse == definition->fKeyWord) {
+            SkDebugf("");
+        }
         if (fParent) {
             fParent->fChildren.push_back(definition);
             definition->fParent = fParent;
diff --git a/tools/bookmaker/selfCheck.cpp b/tools/bookmaker/selfCheck.cpp
index 1c822c2..341b9e3 100644
--- a/tools/bookmaker/selfCheck.cpp
+++ b/tools/bookmaker/selfCheck.cpp
@@ -79,8 +79,8 @@
             // only check methods for now
             return;
         }
-        bool containsMarkTypeIn = csChild->fDeprecated  // no markup for deprecated
-                || Definition::MethodType::kConstructor == csChild->fMethodType
+        bool containsMarkTypeIn =
+                   Definition::MethodType::kConstructor == csChild->fMethodType
                 || Definition::MethodType::kDestructor == csChild->fMethodType
                 || Definition::MethodType::kOperator == csChild->fMethodType
                 || csChild->fClone;
diff --git a/tools/bookmaker/spellCheck.cpp b/tools/bookmaker/spellCheck.cpp
index c6cfe5d..c8de4a3 100644
--- a/tools/bookmaker/spellCheck.cpp
+++ b/tools/bookmaker/spellCheck.cpp
@@ -180,8 +180,6 @@
         } break;
         case MarkType::kDefine:
             break;
-        case MarkType::kDeprecated:
-            break;
         case MarkType::kDescription:
             fInDescription = true;
             break;
@@ -195,8 +193,6 @@
             break;
         case MarkType::kExample:
             break;
-        case MarkType::kExperimental:
-            break;
         case MarkType::kExternal:
             break;
         case MarkType::kFile:
@@ -270,8 +266,6 @@
             break;
         case MarkType::kPopulate:
             break;
-        case MarkType::kPrivate:
-            break;
         case MarkType::kReturn:
             break;
         case MarkType::kRow:
diff --git a/tools/bookmaker/textParser.h b/tools/bookmaker/textParser.h
index ffe5e78..9ecd466 100644
--- a/tools/bookmaker/textParser.h
+++ b/tools/bookmaker/textParser.h
@@ -58,6 +58,46 @@
         return nullptr;
     }
 
+    // words must be alpha only
+    string anyWord(const vector<string>& wordList, int spaces) const {
+        const char* matchStart = fChar;
+        do {
+            int count = spaces;
+            while (matchStart < fEnd && !isalpha(matchStart[0])) {
+                ++matchStart;
+            }
+            const char* matchEnd = matchStart;
+            const char* nextWord = nullptr;
+            while (matchEnd < fEnd) {
+                if (isalpha(matchEnd[0])) {
+                    ;
+                } else if (' ' == matchEnd[0] && --count >= 0) {
+                    if (!nextWord) {
+                        nextWord = matchEnd;
+                    }
+                } else {
+                    break;
+                }
+                ++matchEnd;
+            }
+            size_t matchLen = matchEnd - matchStart;
+            for (auto word : wordList) {
+                if (word.length() != matchLen) {
+                    continue;
+                }
+                for (unsigned index = 0; index < matchLen; ++index) {
+                    if (tolower(matchStart[index]) != word[index]) {
+                        goto nextWord;
+                    }
+                }
+                return word;
+        nextWord: ;
+            }
+            matchStart = nextWord ? nextWord : matchEnd;
+        } while (matchStart < fEnd);
+        return "";
+    }
+
     bool back(const char* pattern) {
         size_t len = strlen(pattern);
         const char* start = fChar - len;