/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "bookmaker.h" // Check that mutiple like-named methods are under one Subtopic // Check that all subtopics are in table of contents // Check that SeeAlso reference each other // Would be nice to check if other classes have 'create' methods that are included // SkSurface::makeImageSnapShot should be referenced under SkImage 'creators' class SelfChecker { public: SelfChecker(const BmhParser& bmh) : fBmhParser(bmh) {} bool check() { for (const auto& topic : fBmhParser.fTopicMap) { Definition* topicDef = topic.second; if (topicDef->fParent) { continue; } if (!topicDef->isRoot()) { return fBmhParser.reportError("expected root topic"); } fRoot = topicDef->asRoot(); if (!this->checkMethodSummary()) { return false; } if (!this->checkMethodSubtopic()) { return false; } if (!this->checkSubtopicSummary()) { return false; } if (!this->checkConstructorsSummary()) { return false; } if (!this->checkOperatorsSummary()) { return false; } if (!this->checkSeeAlso()) { return false; } if (!this->checkCreators()) { return false; } if (!this->checkRelatedFunctions()) { return false; } } return true; } protected: // Check that all constructors are in a table of contents // should be 'creators' instead of constructors? bool checkConstructorsSummary() { for (auto& rootChild : fRoot->fChildren) { if (!rootChild->isStructOrClass()) { continue; } auto& cs = rootChild; auto constructors = this->findTopic("Constructors", Optional::kYes); if (constructors && MarkType::kSubtopic != constructors->fMarkType) { return constructors->reportError("expected #Subtopic Constructors"); } vector constructorEntries; if (constructors) { if (!this->collectEntries(constructors, &constructorEntries)) { return false; } } // mark corresponding methods as visited (may be more than one per entry) for (auto& csChild : cs->fChildren) { if (MarkType::kMethod != csChild->fMarkType) { // only check methods for now continue; } string name; if (!this->childName(csChild, &name)) { return false; } string returnType; if (Definition::MethodType::kConstructor != csChild->fMethodType && Definition::MethodType::kDestructor != csChild->fMethodType) { string makeCheck = name.substr(0, 4); if ("Make" != makeCheck && "make" != makeCheck) { continue; } // for now, assume return type of interest is first word to start Sk string search(csChild->fStart, csChild->fContentStart - csChild->fStart); auto end = search.find(makeCheck); if (string::npos == end) { return csChild->reportError("expected Make in content"); } search = search.substr(0, end); if (string::npos == search.find(cs->fName)) { // if return value doesn't match current struct or class, look in // returned struct / class instead auto sk = search.find("Sk"); if (string::npos != sk) { // todo: build class name, find it, search for match in its overview continue; } } } if (constructorEntries.end() == std::find(constructorEntries.begin(), constructorEntries.end(), name)) { return csChild->reportError("missing constructor in Constructors"); } } } return true; } bool checkCreators() { return true; } bool checkMethodSubtopic() { return true; } // Check that summary contains all methods bool checkMethodSummary() { // look for struct or class in fChildren const Definition* cs = this->classOrStruct(); if (!cs) { return true; // topics may not have included classes or structs } auto memberFunctions = this->findTopic("Member_Functions", Optional::kNo); if (MarkType::kSubtopic != memberFunctions->fMarkType) { return memberFunctions->reportError("expected #Subtopic Member_Functions"); } vector methodEntries; // build map of overview entries if (!this->collectEntries(memberFunctions, &methodEntries)) { return false; } // mark corresponding methods as visited (may be more than one per entry) for (auto& csChild : cs->fChildren) { if (MarkType::kMethod != csChild->fMarkType) { // only check methods for now continue; } if (Definition::MethodType::kConstructor == csChild->fMethodType) { continue; } if (Definition::MethodType::kDestructor == csChild->fMethodType) { continue; } if (Definition::MethodType::kOperator == csChild->fMethodType) { continue; } string name; if (!this->childName(csChild, &name)) { return false; } if (methodEntries.end() == std::find(methodEntries.begin(), methodEntries.end(), name)) { return csChild->reportError("missing method in Member_Functions"); } } return true; } // Check that all operators are in a table of contents bool checkOperatorsSummary() { const Definition* cs = this->classOrStruct(); if (!cs) { return true; // topics may not have included classes or structs } const Definition* operators = this->findTopic("Operators", Optional::kYes); if (operators && MarkType::kSubtopic != operators->fMarkType) { return operators->reportError("expected #Subtopic Operators"); } vector operatorEntries; if (operators) { if (!this->collectEntries(operators, &operatorEntries)) { return false; } } for (auto& csChild : cs->fChildren) { if (Definition::MethodType::kOperator != csChild->fMethodType) { continue; } string name; if (!this->childName(csChild, &name)) { return false; } bool found = false; for (auto str : operatorEntries) { if (string::npos != str.find(name)) { found = true; break; } } if (!found) { return csChild->reportError("missing operator in Operators"); } } return true; } bool checkRelatedFunctions() { auto related = this->findTopic("Related_Functions", Optional::kYes); if (!related) { return true; } vector relatedEntries; if (!this->collectEntries(related, &relatedEntries)) { return false; } const Definition* cs = this->classOrStruct(); vector methodNames; if (cs) { string prefix = cs->fName + "::"; for (auto& csChild : cs->fChildren) { if (MarkType::kMethod != csChild->fMarkType) { // only check methods for now continue; } if (Definition::MethodType::kConstructor == csChild->fMethodType) { continue; } if (Definition::MethodType::kDestructor == csChild->fMethodType) { continue; } if (Definition::MethodType::kOperator == csChild->fMethodType) { continue; } if (csChild->fClone) { // FIXME: check to see if all cloned methods are in table // since format of clones is in flux, defer this check for now continue; } SkASSERT(string::npos != csChild->fName.find(prefix)); string name = csChild->fName.substr(csChild->fName.find(prefix)); methodNames.push_back(name); } } vector trim = methodNames; for (auto entryName : relatedEntries) { auto entryDef = this->findTopic(entryName, Optional::kNo); if (!entryDef) { } vector entries; this->collectEntries(entryDef, &entries); for (auto entry : entries) { auto it = std::find(methodNames.begin(), methodNames.end(), entry); if (it == methodNames.end()) { return cs->reportError("missing method"); } it = std::find(trim.begin(), trim.end(), entry); if (it != trim.end()) { using std::swap; swap(*it, trim.back()); trim.pop_back(); } } } if (trim.size() > 0) { return cs->reportError("extra method"); } return true; } bool checkSeeAlso() { return true; } bool checkSubtopicSummary() { const auto& cs = this->classOrStruct(); if (!cs) { return true; } auto overview = this->findOverview(cs); if (!overview) { return false; } const Definition* subtopics = this->findTopic("Subtopics", Optional::kNo); if (MarkType::kSubtopic != subtopics->fMarkType) { return subtopics->reportError("expected #Subtopic Subtopics"); } const Definition* relatedFunctions = this->findTopic("Related_Functions", Optional::kYes); if (relatedFunctions && MarkType::kSubtopic != relatedFunctions->fMarkType) { return relatedFunctions->reportError("expected #Subtopic Related_Functions"); } vector subtopicEntries; if (!this->collectEntries(subtopics, &subtopicEntries)) { return false; } if (relatedFunctions && !this->collectEntries(relatedFunctions, &subtopicEntries)) { return false; } for (auto& csChild : cs->fChildren) { if (MarkType::kSubtopic != csChild->fMarkType) { continue; } string name; if (!this->childName(csChild, &name)) { return false; } bool found = false; for (auto str : subtopicEntries) { if (string::npos != str.find(name)) { found = true; break; } } if (!found) { return csChild->reportError("missing SubTopic in SubTopics"); } } return true; } bool childName(const Definition* def, string* name) { auto start = def->fName.find_last_of(':'); start = string::npos == start ? 0 : start + 1; *name = def->fName.substr(start); if (def->fClone) { auto lastUnderline = name->find_last_of('_'); if (string::npos == lastUnderline) { return def->reportError("expect _ in name"); } if (lastUnderline + 1 >= name->length()) { return def->reportError("expect char after _ in name"); } for (auto index = lastUnderline + 1; index < name->length(); ++index) { if (!isdigit((*name)[index])) { return def->reportError("expect digit after _ in name"); } } *name = name->substr(0, lastUnderline); bool allLower = true; for (auto ch : *name) { allLower &= (bool) islower(ch); } if (allLower) { *name += "()"; } } return true; } const Definition* classOrStruct() { for (auto& rootChild : fRoot->fChildren) { if (rootChild->isStructOrClass()) { return rootChild; } } return nullptr; } static const Definition* overview_def(const Definition* parent) { Definition* overview = nullptr; if (parent) { for (auto& csChild : parent->fChildren) { if ("Overview" == csChild->fName) { if (overview) { return csChild->reportError("expected only one Overview"); } overview = csChild; } } } return overview; } const Definition* findOverview(const Definition* parent) { // expect Overview as Topic in every main class or struct const Definition* overview = overview_def(parent); const Definition* parentOverview = parent ? overview_def(parent->fParent) : nullptr; if (overview && parentOverview) { return overview->reportError("expected only one Overview 2"); } overview = overview ? overview : parentOverview; if (!overview) { return parent->reportError("missing #Topic Overview"); } return overview; } enum class Optional { kNo, kYes, }; const Definition* findTopic(string name, Optional optional) { string undashed = name; std::replace(undashed.begin(), undashed.end(), '-', '_'); string topicKey = fRoot->fName + '_' + undashed; auto topicKeyIter = fBmhParser.fTopicMap.find(topicKey); if (fBmhParser.fTopicMap.end() == topicKeyIter) { // TODO: remove this and require member functions outside of overview topicKey = fRoot->fName + "_Overview_" + undashed; // legacy form for now topicKeyIter = fBmhParser.fTopicMap.find(topicKey); if (fBmhParser.fTopicMap.end() == topicKeyIter) { if (Optional::kNo == optional) { return fRoot->reportError("missing subtopic"); } return nullptr; } } return topicKeyIter->second; } bool collectEntries(const Definition* entries, vector* strings) { const Definition* table = nullptr; for (auto& child : entries->fChildren) { if (MarkType::kTable == child->fMarkType && child->fName == entries->fName) { table = child; break; } } if (!table) { return entries->reportError("missing #Table in Overview Subtopic"); } bool expectLegend = true; string prior = " "; // expect entries to be alphabetical for (auto& row : table->fChildren) { if (MarkType::kLegend == row->fMarkType) { if (!expectLegend) { return row->reportError("expect #Legend only once"); } // todo: check if legend format matches table's rows' format expectLegend = false; } else if (expectLegend) { return row->reportError("expect #Legend first"); } if (MarkType::kRow != row->fMarkType) { continue; // let anything through for now; can tighten up in the future } // expect column 0 to point to function name Definition* column0 = row->fChildren[0]; string name = string(column0->fContentStart, column0->fContentEnd - column0->fContentStart); if (prior > name) { return row->reportError("expect alphabetical order"); } if (prior == name) { return row->reportError("expect unique names"); } // todo: error if name is all lower case and doesn't end in () strings->push_back(name); prior = name; } return true; } private: const BmhParser& fBmhParser; RootDefinition* fRoot; }; bool SelfCheck(const BmhParser& bmh) { SelfChecker checker(bmh); return checker.check(); }