blob: f30a5824bc57a25bec8ccdaae161de4502da6e5e [file] [log] [blame]
Cary Clarkac47b882018-01-11 10:35:44 -05001/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "bookmaker.h"
9
Cary Clarkac47b882018-01-11 10:35:44 -050010
11// Check that mutiple like-named methods are under one Subtopic
12
13// Check that all subtopics are in table of contents
14
Cary Clarkac47b882018-01-11 10:35:44 -050015// Check that SeeAlso reference each other
16
17// Would be nice to check if other classes have 'create' methods that are included
18// SkSurface::makeImageSnapShot should be referenced under SkImage 'creators'
19
20class SelfChecker {
21public:
22 SelfChecker(const BmhParser& bmh)
23 : fBmhParser(bmh)
24 {}
25
26 bool check() {
27 for (const auto& topic : fBmhParser.fTopicMap) {
28 Definition* topicDef = topic.second;
29 if (topicDef->fParent) {
30 continue;
31 }
32 if (!topicDef->isRoot()) {
33 return fBmhParser.reportError<bool>("expected root topic");
34 }
35 fRoot = topicDef->asRoot();
36 if (!this->checkMethodSummary()) {
37 return false;
38 }
39 if (!this->checkMethodSubtopic()) {
40 return false;
41 }
Cary Clark5081eed2018-01-22 07:55:48 -050042 if (!this->checkSubtopicSummary()) {
Cary Clarkac47b882018-01-11 10:35:44 -050043 return false;
44 }
Cary Clark5081eed2018-01-22 07:55:48 -050045 if (!this->checkConstructorsSummary()) {
46 return false;
47 }
48 if (!this->checkOperatorsSummary()) {
Cary Clarkac47b882018-01-11 10:35:44 -050049 return false;
50 }
51 if (!this->checkSeeAlso()) {
52 return false;
53 }
54 if (!this->checkCreators()) {
55 return false;
56 }
Cary Clarkab2621d2018-01-30 10:08:57 -050057 if (!this->checkRelatedFunctions()) {
58 return false;
59 }
Cary Clarkac47b882018-01-11 10:35:44 -050060 }
61 return true;
62 }
63
64protected:
Cary Clark5081eed2018-01-22 07:55:48 -050065 // Check that all constructors are in a table of contents
66 // should be 'creators' instead of constructors?
67 bool checkConstructorsSummary() {
68 for (auto& rootChild : fRoot->fChildren) {
69 if (!this->isStructOrClass(rootChild)) {
70 continue;
71 }
72 auto& cs = rootChild;
Cary Clark2dc84ad2018-01-26 12:56:22 -050073 auto constructors = this->findTopic("Constructors", Optional::kYes);
74 if (constructors && MarkType::kSubtopic != constructors->fMarkType) {
Cary Clark5081eed2018-01-22 07:55:48 -050075 return constructors->reportError<bool>("expected #Subtopic Constructors");
76 }
77 vector<string> constructorEntries;
78 if (constructors) {
79 if (!this->collectEntries(constructors, &constructorEntries)) {
80 return false;
81 }
82 }
83 // mark corresponding methods as visited (may be more than one per entry)
84 for (auto& csChild : cs->fChildren) {
85 if (MarkType::kMethod != csChild->fMarkType) {
86 // only check methods for now
87 continue;
88 }
89 string name;
90 if (!this->childName(csChild, &name)) {
91 return false;
92 }
93 string returnType;
94 if (Definition::MethodType::kConstructor != csChild->fMethodType &&
95 Definition::MethodType::kDestructor != csChild->fMethodType) {
96 string makeCheck = name.substr(0, 4);
97 if ("Make" != makeCheck && "make" != makeCheck) {
98 continue;
99 }
100 // for now, assume return type of interest is first word to start Sk
101 string search(csChild->fStart, csChild->fContentStart - csChild->fStart);
102 auto end = search.find(makeCheck);
103 if (string::npos == end) {
104 return csChild->reportError<bool>("expected Make in content");
105 }
106 search = search.substr(0, end);
107 if (string::npos == search.find(cs->fName)) {
108 // if return value doesn't match current struct or class, look in
109 // returned struct / class instead
110 auto sk = search.find("Sk");
111 if (string::npos != sk) {
112 // todo: build class name, find it, search for match in its overview
113 continue;
114 }
115 }
116 }
117 if (constructorEntries.end() ==
118 std::find(constructorEntries.begin(), constructorEntries.end(), name)) {
119 return csChild->reportError<bool>("missing constructor in Constructors");
120 }
121 }
122 }
Cary Clarkac47b882018-01-11 10:35:44 -0500123 return true;
124 }
125
126 bool checkCreators() {
127 return true;
128 }
129
130 bool checkMethodSubtopic() {
131 return true;
132 }
133
Cary Clark5081eed2018-01-22 07:55:48 -0500134 // Check that summary contains all methods
Cary Clarkac47b882018-01-11 10:35:44 -0500135 bool checkMethodSummary() {
Cary Clarkac47b882018-01-11 10:35:44 -0500136 // look for struct or class in fChildren
Cary Clarkab2621d2018-01-30 10:08:57 -0500137 const Definition* cs = this->classOrStruct();
Cary Clark2dc84ad2018-01-26 12:56:22 -0500138 if (!cs) {
139 return true; // topics may not have included classes or structs
140 }
141 auto memberFunctions = this->findTopic("Member_Functions", Optional::kNo);
142 if (MarkType::kSubtopic != memberFunctions->fMarkType) {
143 return memberFunctions->reportError<bool>("expected #Subtopic Member_Functions");
144 }
145 vector<string> methodEntries; // build map of overview entries
146 if (!this->collectEntries(memberFunctions, &methodEntries)) {
147 return false;
148 }
149 // mark corresponding methods as visited (may be more than one per entry)
150 for (auto& csChild : cs->fChildren) {
151 if (MarkType::kMethod != csChild->fMarkType) {
152 // only check methods for now
Cary Clark5081eed2018-01-22 07:55:48 -0500153 continue;
154 }
Cary Clark2dc84ad2018-01-26 12:56:22 -0500155 if (Definition::MethodType::kConstructor == csChild->fMethodType) {
156 continue;
157 }
158 if (Definition::MethodType::kDestructor == csChild->fMethodType) {
159 continue;
160 }
161 if (Definition::MethodType::kOperator == csChild->fMethodType) {
162 continue;
163 }
164 string name;
165 if (!this->childName(csChild, &name)) {
Cary Clark5081eed2018-01-22 07:55:48 -0500166 return false;
167 }
Cary Clark2dc84ad2018-01-26 12:56:22 -0500168 if (methodEntries.end() ==
169 std::find(methodEntries.begin(), methodEntries.end(), name)) {
170 return csChild->reportError<bool>("missing method in Member_Functions");
Cary Clark5081eed2018-01-22 07:55:48 -0500171 }
172 }
173 return true;
174 }
175
176 // Check that all operators are in a table of contents
177 bool checkOperatorsSummary() {
Cary Clarkab2621d2018-01-30 10:08:57 -0500178 const Definition* cs = this->classOrStruct();
179 if (!cs) {
180 return true; // topics may not have included classes or structs
181 }
182 const Definition* operators = this->findTopic("Operators", Optional::kYes);
183 if (operators && MarkType::kSubtopic != operators->fMarkType) {
184 return operators->reportError<bool>("expected #Subtopic Operators");
185 }
186 vector<string> operatorEntries;
187 if (operators) {
188 if (!this->collectEntries(operators, &operatorEntries)) {
189 return false;
190 }
191 }
192 for (auto& csChild : cs->fChildren) {
193 if (Definition::MethodType::kOperator != csChild->fMethodType) {
Cary Clark5081eed2018-01-22 07:55:48 -0500194 continue;
195 }
Cary Clarkab2621d2018-01-30 10:08:57 -0500196 string name;
197 if (!this->childName(csChild, &name)) {
198 return false;
Cary Clark5081eed2018-01-22 07:55:48 -0500199 }
Cary Clarkab2621d2018-01-30 10:08:57 -0500200 bool found = false;
201 for (auto str : operatorEntries) {
202 if (string::npos != str.find(name)) {
203 found = true;
204 break;
Cary Clark5081eed2018-01-22 07:55:48 -0500205 }
206 }
Cary Clarkab2621d2018-01-30 10:08:57 -0500207 if (!found) {
208 return csChild->reportError<bool>("missing operator in Operators");
Cary Clarkac47b882018-01-11 10:35:44 -0500209 }
210 }
211 return true;
212 }
213
Cary Clarkab2621d2018-01-30 10:08:57 -0500214 bool checkRelatedFunctions() {
215 auto related = this->findTopic("Related_Functions", Optional::kYes);
216 if (!related) {
217 return true;
218 }
219 vector<string> relatedEntries;
220 if (!this->collectEntries(related, &relatedEntries)) {
221 return false;
222 }
223 const Definition* cs = this->classOrStruct();
224 vector<string> methodNames;
225 if (cs) {
226 string prefix = cs->fName + "::";
227 for (auto& csChild : cs->fChildren) {
228 if (MarkType::kMethod != csChild->fMarkType) {
229 // only check methods for now
230 continue;
231 }
232 if (Definition::MethodType::kConstructor == csChild->fMethodType) {
233 continue;
234 }
235 if (Definition::MethodType::kDestructor == csChild->fMethodType) {
236 continue;
237 }
238 if (Definition::MethodType::kOperator == csChild->fMethodType) {
239 continue;
240 }
241 if (csChild->fClone) {
242 // FIXME: check to see if all cloned methods are in table
243 // since format of clones is in flux, defer this check for now
244 continue;
245 }
246
247 SkASSERT(string::npos != csChild->fName.find(prefix));
248 string name = csChild->fName.substr(csChild->fName.find(prefix));
249 methodNames.push_back(name);
250 }
251 }
252 vector<string> trim = methodNames;
253 for (auto entryName : relatedEntries) {
254 auto entryDef = this->findTopic(entryName, Optional::kNo);
255 if (!entryDef) {
256
257 }
258 vector<string> entries;
259 this->collectEntries(entryDef, &entries);
260 for (auto entry : entries) {
261 auto it = std::find(methodNames.begin(), methodNames.end(), entry);
262 if (it == methodNames.end()) {
263 return cs->reportError<bool>("missing method");
264 }
265 it = std::find(trim.begin(), trim.end(), entry);
266 if (it != trim.end()) {
267 using std::swap;
268 swap(*it, trim.back());
269 trim.pop_back();
270 }
271 }
272 }
273 if (trim.size() > 0) {
274 return cs->reportError<bool>("extra method");
275 }
276 return true;
277 }
278
Cary Clarkac47b882018-01-11 10:35:44 -0500279 bool checkSeeAlso() {
280 return true;
281 }
282
Cary Clark5081eed2018-01-22 07:55:48 -0500283 bool checkSubtopicSummary() {
Cary Clarkab2621d2018-01-30 10:08:57 -0500284 const auto& cs = this->classOrStruct();
285 if (!cs) {
286 return true;
287 }
288 auto overview = this->findOverview(cs);
289 if (!overview) {
290 return false;
291 }
292 const Definition* subtopics = this->findTopic("Subtopics", Optional::kNo);
293 if (MarkType::kSubtopic != subtopics->fMarkType) {
294 return subtopics->reportError<bool>("expected #Subtopic Subtopics");
295 }
296 const Definition* relatedFunctions = this->findTopic("Related_Functions", Optional::kYes);
297 if (relatedFunctions && MarkType::kSubtopic != relatedFunctions->fMarkType) {
298 return relatedFunctions->reportError<bool>("expected #Subtopic Related_Functions");
299 }
300 vector<string> subtopicEntries;
301 if (!this->collectEntries(subtopics, &subtopicEntries)) {
302 return false;
303 }
304 if (relatedFunctions && !this->collectEntries(relatedFunctions, &subtopicEntries)) {
305 return false;
306 }
307 for (auto& csChild : cs->fChildren) {
308 if (MarkType::kSubtopic != csChild->fMarkType) {
Cary Clark5081eed2018-01-22 07:55:48 -0500309 continue;
310 }
Cary Clarkab2621d2018-01-30 10:08:57 -0500311 string name;
312 if (!this->childName(csChild, &name)) {
Cary Clark5081eed2018-01-22 07:55:48 -0500313 return false;
314 }
Cary Clarkab2621d2018-01-30 10:08:57 -0500315 bool found = false;
316 for (auto str : subtopicEntries) {
317 if (string::npos != str.find(name)) {
318 found = true;
319 break;
Cary Clark5081eed2018-01-22 07:55:48 -0500320 }
Cary Clarkab2621d2018-01-30 10:08:57 -0500321 }
322 if (!found) {
323 return csChild->reportError<bool>("missing SubTopic in SubTopics");
Cary Clark5081eed2018-01-22 07:55:48 -0500324 }
325 }
326 return true;
327 }
328
329 bool childName(const Definition* def, string* name) {
330 auto start = def->fName.find_last_of(':');
331 start = string::npos == start ? 0 : start + 1;
332 *name = def->fName.substr(start);
333 if (def->fClone) {
334 auto lastUnderline = name->find_last_of('_');
335 if (string::npos == lastUnderline) {
336 return def->reportError<bool>("expect _ in name");
337 }
338 if (lastUnderline + 1 >= name->length()) {
339 return def->reportError<bool>("expect char after _ in name");
340 }
341 for (auto index = lastUnderline + 1; index < name->length(); ++index) {
342 if (!isdigit((*name)[index])) {
343 return def->reportError<bool>("expect digit after _ in name");
344 }
345 }
346 *name = name->substr(0, lastUnderline);
347 bool allLower = true;
348 for (auto ch : *name) {
349 allLower &= (bool) islower(ch);
350 }
351 if (allLower) {
352 *name += "()";
353 }
354 }
355 return true;
356 }
357
Cary Clarkab2621d2018-01-30 10:08:57 -0500358 const Definition* classOrStruct() {
359 for (auto& rootChild : fRoot->fChildren) {
360 if (this->isStructOrClass(rootChild)) {
361 return rootChild;
362 }
363 }
364 return nullptr;
365 }
366
Cary Clark2dc84ad2018-01-26 12:56:22 -0500367 static const Definition* overview_def(const Definition* parent) {
368 Definition* overview = nullptr;
369 if (parent) {
370 for (auto& csChild : parent->fChildren) {
371 if ("Overview" == csChild->fName) {
372 if (overview) {
373 return csChild->reportError<const Definition*>("expected only one Overview");
374 }
375 overview = csChild;
376 }
377 }
378 }
379 return overview;
380 }
381
Cary Clark5081eed2018-01-22 07:55:48 -0500382 const Definition* findOverview(const Definition* parent) {
383 // expect Overview as Topic in every main class or struct
Cary Clark2dc84ad2018-01-26 12:56:22 -0500384 const Definition* overview = overview_def(parent);
Cary Clarkab2621d2018-01-30 10:08:57 -0500385 const Definition* parentOverview = parent ? overview_def(parent->fParent) : nullptr;
Cary Clark2dc84ad2018-01-26 12:56:22 -0500386 if (overview && parentOverview) {
387 return overview->reportError<const Definition*>("expected only one Overview 2");
388 }
389 overview = overview ? overview : parentOverview;
Cary Clark5081eed2018-01-22 07:55:48 -0500390 if (!overview) {
391 return parent->reportError<const Definition*>("missing #Topic Overview");
392 }
393 return overview;
394 }
395
Cary Clark2dc84ad2018-01-26 12:56:22 -0500396 enum class Optional {
397 kNo,
398 kYes,
399 };
400
401 const Definition* findTopic(string name, Optional optional) {
Cary Clarkab2621d2018-01-30 10:08:57 -0500402 string undashed = name;
403 std::replace(undashed.begin(), undashed.end(), '-', '_');
404 string topicKey = fRoot->fName + '_' + undashed;
Cary Clark2dc84ad2018-01-26 12:56:22 -0500405 auto topicKeyIter = fBmhParser.fTopicMap.find(topicKey);
406 if (fBmhParser.fTopicMap.end() == topicKeyIter) {
407 // TODO: remove this and require member functions outside of overview
Cary Clarkab2621d2018-01-30 10:08:57 -0500408 topicKey = fRoot->fName + "_Overview_" + undashed; // legacy form for now
Cary Clark2dc84ad2018-01-26 12:56:22 -0500409 topicKeyIter = fBmhParser.fTopicMap.find(topicKey);
410 if (fBmhParser.fTopicMap.end() == topicKeyIter) {
411 if (Optional::kNo == optional) {
412 return fRoot->reportError<Definition*>("missing subtopic");
413 }
414 return nullptr;
415 }
416 }
417 return topicKeyIter->second;
418 }
419
Cary Clark5081eed2018-01-22 07:55:48 -0500420 bool collectEntries(const Definition* entries, vector<string>* strings) {
421 const Definition* table = nullptr;
422 for (auto& child : entries->fChildren) {
423 if (MarkType::kTable == child->fMarkType && child->fName == entries->fName) {
424 table = child;
425 break;
426 }
427 }
428 if (!table) {
429 return entries->reportError<bool>("missing #Table in Overview Subtopic");
430 }
431 bool expectLegend = true;
432 string prior = " "; // expect entries to be alphabetical
433 for (auto& row : table->fChildren) {
434 if (MarkType::kLegend == row->fMarkType) {
435 if (!expectLegend) {
436 return row->reportError<bool>("expect #Legend only once");
437 }
438 // todo: check if legend format matches table's rows' format
439 expectLegend = false;
440 } else if (expectLegend) {
441 return row->reportError<bool>("expect #Legend first");
442 }
443 if (MarkType::kRow != row->fMarkType) {
444 continue; // let anything through for now; can tighten up in the future
445 }
446 // expect column 0 to point to function name
447 Definition* column0 = row->fChildren[0];
448 string name = string(column0->fContentStart,
449 column0->fContentEnd - column0->fContentStart);
450 if (prior > name) {
451 return row->reportError<bool>("expect alphabetical order");
452 }
453 if (prior == name) {
454 return row->reportError<bool>("expect unique names");
455 }
456 // todo: error if name is all lower case and doesn't end in ()
457 strings->push_back(name);
458 prior = name;
459 }
460 return true;
461 }
462
Cary Clarkab2621d2018-01-30 10:08:57 -0500463 bool isStructOrClass(const Definition* definition) const {
Cary Clark5081eed2018-01-22 07:55:48 -0500464 if (MarkType::kStruct != definition->fMarkType &&
465 MarkType::kClass != definition->fMarkType) {
466 return false;
467 }
468 if (string::npos != definition->fFileName.find("undocumented.bmh")) {
469 return false;
470 }
Cary Clarkac47b882018-01-11 10:35:44 -0500471 return true;
472 }
473
474private:
475 const BmhParser& fBmhParser;
476 RootDefinition* fRoot;
477};
478
479bool SelfCheck(const BmhParser& bmh) {
480 SelfChecker checker(bmh);
481 return checker.check();
482}