blob: 16db3da9bd1f5c9b50b417cb6bc9dd4a35612f54 [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 }
57 }
58 return true;
59 }
60
61protected:
Cary Clark5081eed2018-01-22 07:55:48 -050062 // Check that all constructors are in a table of contents
63 // should be 'creators' instead of constructors?
64 bool checkConstructorsSummary() {
65 for (auto& rootChild : fRoot->fChildren) {
66 if (!this->isStructOrClass(rootChild)) {
67 continue;
68 }
69 auto& cs = rootChild;
Cary Clark2dc84ad2018-01-26 12:56:22 -050070 auto constructors = this->findTopic("Constructors", Optional::kYes);
71 if (constructors && MarkType::kSubtopic != constructors->fMarkType) {
Cary Clark5081eed2018-01-22 07:55:48 -050072 return constructors->reportError<bool>("expected #Subtopic Constructors");
73 }
74 vector<string> constructorEntries;
75 if (constructors) {
76 if (!this->collectEntries(constructors, &constructorEntries)) {
77 return false;
78 }
79 }
80 // mark corresponding methods as visited (may be more than one per entry)
81 for (auto& csChild : cs->fChildren) {
82 if (MarkType::kMethod != csChild->fMarkType) {
83 // only check methods for now
84 continue;
85 }
86 string name;
87 if (!this->childName(csChild, &name)) {
88 return false;
89 }
90 string returnType;
91 if (Definition::MethodType::kConstructor != csChild->fMethodType &&
92 Definition::MethodType::kDestructor != csChild->fMethodType) {
93 string makeCheck = name.substr(0, 4);
94 if ("Make" != makeCheck && "make" != makeCheck) {
95 continue;
96 }
97 // for now, assume return type of interest is first word to start Sk
98 string search(csChild->fStart, csChild->fContentStart - csChild->fStart);
99 auto end = search.find(makeCheck);
100 if (string::npos == end) {
101 return csChild->reportError<bool>("expected Make in content");
102 }
103 search = search.substr(0, end);
104 if (string::npos == search.find(cs->fName)) {
105 // if return value doesn't match current struct or class, look in
106 // returned struct / class instead
107 auto sk = search.find("Sk");
108 if (string::npos != sk) {
109 // todo: build class name, find it, search for match in its overview
110 continue;
111 }
112 }
113 }
114 if (constructorEntries.end() ==
115 std::find(constructorEntries.begin(), constructorEntries.end(), name)) {
116 return csChild->reportError<bool>("missing constructor in Constructors");
117 }
118 }
119 }
Cary Clarkac47b882018-01-11 10:35:44 -0500120 return true;
121 }
122
123 bool checkCreators() {
124 return true;
125 }
126
127 bool checkMethodSubtopic() {
128 return true;
129 }
130
Cary Clark5081eed2018-01-22 07:55:48 -0500131 // Check that summary contains all methods
Cary Clarkac47b882018-01-11 10:35:44 -0500132 bool checkMethodSummary() {
Cary Clarkac47b882018-01-11 10:35:44 -0500133 // look for struct or class in fChildren
Cary Clark2dc84ad2018-01-26 12:56:22 -0500134 Definition* cs = nullptr;
135 for (auto& rootChild : fRoot->fChildren) {
136 if (!this->isStructOrClass(rootChild)) {
137 continue;
138 }
139 cs = rootChild;
140 // expect Overview as Topic in every main class or struct or its parent
141 }
142 if (!cs) {
143 return true; // topics may not have included classes or structs
144 }
145 auto memberFunctions = this->findTopic("Member_Functions", Optional::kNo);
146 if (MarkType::kSubtopic != memberFunctions->fMarkType) {
147 return memberFunctions->reportError<bool>("expected #Subtopic Member_Functions");
148 }
149 vector<string> methodEntries; // build map of overview entries
150 if (!this->collectEntries(memberFunctions, &methodEntries)) {
151 return false;
152 }
153 // mark corresponding methods as visited (may be more than one per entry)
154 for (auto& csChild : cs->fChildren) {
155 if (MarkType::kMethod != csChild->fMarkType) {
156 // only check methods for now
Cary Clark5081eed2018-01-22 07:55:48 -0500157 continue;
158 }
Cary Clark2dc84ad2018-01-26 12:56:22 -0500159 if (Definition::MethodType::kConstructor == csChild->fMethodType) {
160 continue;
161 }
162 if (Definition::MethodType::kDestructor == csChild->fMethodType) {
163 continue;
164 }
165 if (Definition::MethodType::kOperator == csChild->fMethodType) {
166 continue;
167 }
168 string name;
169 if (!this->childName(csChild, &name)) {
Cary Clark5081eed2018-01-22 07:55:48 -0500170 return false;
171 }
Cary Clark2dc84ad2018-01-26 12:56:22 -0500172 if (methodEntries.end() ==
173 std::find(methodEntries.begin(), methodEntries.end(), name)) {
174 return csChild->reportError<bool>("missing method in Member_Functions");
Cary Clark5081eed2018-01-22 07:55:48 -0500175 }
176 }
177 return true;
178 }
179
180 // Check that all operators are in a table of contents
181 bool checkOperatorsSummary() {
182 for (auto& rootChild : fRoot->fChildren) {
183 if (!this->isStructOrClass(rootChild)) {
184 continue;
185 }
186 auto& cs = rootChild;
Cary Clark2dc84ad2018-01-26 12:56:22 -0500187 const Definition* operators = this->findTopic("Operators", Optional::kYes);
Cary Clark5081eed2018-01-22 07:55:48 -0500188 if (operators && MarkType::kSubtopic != operators->fMarkType) {
189 return operators->reportError<bool>("expected #Subtopic Operators");
190 }
191 vector<string> operatorEntries;
192 if (operators) {
193 if (!this->collectEntries(operators, &operatorEntries)) {
194 return false;
195 }
196 }
197 for (auto& csChild : cs->fChildren) {
198 if (Definition::MethodType::kOperator != csChild->fMethodType) {
199 continue;
200 }
201 string name;
202 if (!this->childName(csChild, &name)) {
203 return false;
204 }
205 bool found = false;
206 for (auto str : operatorEntries) {
207 if (string::npos != str.find(name)) {
208 found = true;
Cary Clarkac47b882018-01-11 10:35:44 -0500209 break;
210 }
211 }
Cary Clark5081eed2018-01-22 07:55:48 -0500212 if (!found) {
213 return csChild->reportError<bool>("missing operator in Operators");
Cary Clarkac47b882018-01-11 10:35:44 -0500214 }
215 }
216 }
217 return true;
218 }
219
220 bool checkSeeAlso() {
221 return true;
222 }
223
Cary Clark5081eed2018-01-22 07:55:48 -0500224 bool checkSubtopicSummary() {
225 for (auto& rootChild : fRoot->fChildren) {
226 if (!this->isStructOrClass(rootChild)) {
227 continue;
228 }
229 auto& cs = rootChild;
230 auto overview = this->findOverview(cs);
231 if (!overview) {
232 return false;
233 }
Cary Clark2dc84ad2018-01-26 12:56:22 -0500234 const Definition* subtopics = this->findTopic("Subtopics", Optional::kNo);
Cary Clark5081eed2018-01-22 07:55:48 -0500235 if (MarkType::kSubtopic != subtopics->fMarkType) {
236 return subtopics->reportError<bool>("expected #Subtopic Subtopics");
237 }
Cary Clark2dc84ad2018-01-26 12:56:22 -0500238 const Definition* relatedFunctions = this->findTopic("Related_Functions", Optional::kYes);
239 if (relatedFunctions && MarkType::kSubtopic != relatedFunctions->fMarkType) {
Cary Clark5081eed2018-01-22 07:55:48 -0500240 return relatedFunctions->reportError<bool>("expected #Subtopic Related_Functions");
241 }
242 vector<string> subtopicEntries;
243 if (!this->collectEntries(subtopics, &subtopicEntries)) {
244 return false;
245 }
246 if (relatedFunctions && !this->collectEntries(relatedFunctions, &subtopicEntries)) {
247 return false;
248 }
249 for (auto& csChild : cs->fChildren) {
250 if (MarkType::kSubtopic != csChild->fMarkType) {
251 continue;
252 }
253 string name;
254 if (!this->childName(csChild, &name)) {
255 return false;
256 }
257 bool found = false;
258 for (auto str : subtopicEntries) {
259 if (string::npos != str.find(name)) {
260 found = true;
261 break;
262 }
263 }
264 if (!found) {
265 return csChild->reportError<bool>("missing SubTopic in SubTopics");
266 }
267 }
268 }
269 return true;
270 }
271
272 bool childName(const Definition* def, string* name) {
273 auto start = def->fName.find_last_of(':');
274 start = string::npos == start ? 0 : start + 1;
275 *name = def->fName.substr(start);
276 if (def->fClone) {
277 auto lastUnderline = name->find_last_of('_');
278 if (string::npos == lastUnderline) {
279 return def->reportError<bool>("expect _ in name");
280 }
281 if (lastUnderline + 1 >= name->length()) {
282 return def->reportError<bool>("expect char after _ in name");
283 }
284 for (auto index = lastUnderline + 1; index < name->length(); ++index) {
285 if (!isdigit((*name)[index])) {
286 return def->reportError<bool>("expect digit after _ in name");
287 }
288 }
289 *name = name->substr(0, lastUnderline);
290 bool allLower = true;
291 for (auto ch : *name) {
292 allLower &= (bool) islower(ch);
293 }
294 if (allLower) {
295 *name += "()";
296 }
297 }
298 return true;
299 }
300
Cary Clark2dc84ad2018-01-26 12:56:22 -0500301 static const Definition* overview_def(const Definition* parent) {
302 Definition* overview = nullptr;
303 if (parent) {
304 for (auto& csChild : parent->fChildren) {
305 if ("Overview" == csChild->fName) {
306 if (overview) {
307 return csChild->reportError<const Definition*>("expected only one Overview");
308 }
309 overview = csChild;
310 }
311 }
312 }
313 return overview;
314 }
315
Cary Clark5081eed2018-01-22 07:55:48 -0500316 const Definition* findOverview(const Definition* parent) {
317 // expect Overview as Topic in every main class or struct
Cary Clark2dc84ad2018-01-26 12:56:22 -0500318 const Definition* overview = overview_def(parent);
319 const Definition* parentOverview = overview_def(parent->fParent);
320 if (overview && parentOverview) {
321 return overview->reportError<const Definition*>("expected only one Overview 2");
322 }
323 overview = overview ? overview : parentOverview;
Cary Clark5081eed2018-01-22 07:55:48 -0500324 if (!overview) {
325 return parent->reportError<const Definition*>("missing #Topic Overview");
326 }
327 return overview;
328 }
329
Cary Clark2dc84ad2018-01-26 12:56:22 -0500330 enum class Optional {
331 kNo,
332 kYes,
333 };
334
335 const Definition* findTopic(string name, Optional optional) {
336 string topicKey = fRoot->fName + '_' + name;
337 auto topicKeyIter = fBmhParser.fTopicMap.find(topicKey);
338 if (fBmhParser.fTopicMap.end() == topicKeyIter) {
339 // TODO: remove this and require member functions outside of overview
340 topicKey = fRoot->fName + "_Overview_" + name; // legacy form for now
341 topicKeyIter = fBmhParser.fTopicMap.find(topicKey);
342 if (fBmhParser.fTopicMap.end() == topicKeyIter) {
343 if (Optional::kNo == optional) {
344 return fRoot->reportError<Definition*>("missing subtopic");
345 }
346 return nullptr;
347 }
348 }
349 return topicKeyIter->second;
350 }
351
Cary Clark5081eed2018-01-22 07:55:48 -0500352 bool collectEntries(const Definition* entries, vector<string>* strings) {
353 const Definition* table = nullptr;
354 for (auto& child : entries->fChildren) {
355 if (MarkType::kTable == child->fMarkType && child->fName == entries->fName) {
356 table = child;
357 break;
358 }
359 }
360 if (!table) {
361 return entries->reportError<bool>("missing #Table in Overview Subtopic");
362 }
363 bool expectLegend = true;
364 string prior = " "; // expect entries to be alphabetical
365 for (auto& row : table->fChildren) {
366 if (MarkType::kLegend == row->fMarkType) {
367 if (!expectLegend) {
368 return row->reportError<bool>("expect #Legend only once");
369 }
370 // todo: check if legend format matches table's rows' format
371 expectLegend = false;
372 } else if (expectLegend) {
373 return row->reportError<bool>("expect #Legend first");
374 }
375 if (MarkType::kRow != row->fMarkType) {
376 continue; // let anything through for now; can tighten up in the future
377 }
378 // expect column 0 to point to function name
379 Definition* column0 = row->fChildren[0];
380 string name = string(column0->fContentStart,
381 column0->fContentEnd - column0->fContentStart);
382 if (prior > name) {
383 return row->reportError<bool>("expect alphabetical order");
384 }
385 if (prior == name) {
386 return row->reportError<bool>("expect unique names");
387 }
388 // todo: error if name is all lower case and doesn't end in ()
389 strings->push_back(name);
390 prior = name;
391 }
392 return true;
393 }
394
395 bool isStructOrClass(const Definition* definition) {
396 if (MarkType::kStruct != definition->fMarkType &&
397 MarkType::kClass != definition->fMarkType) {
398 return false;
399 }
400 if (string::npos != definition->fFileName.find("undocumented.bmh")) {
401 return false;
402 }
Cary Clarkac47b882018-01-11 10:35:44 -0500403 return true;
404 }
405
406private:
407 const BmhParser& fBmhParser;
408 RootDefinition* fRoot;
409};
410
411bool SelfCheck(const BmhParser& bmh) {
412 SelfChecker checker(bmh);
413 return checker.check();
414}