Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2017 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 | |
| 10 | #include "SkOSFile.h" |
| 11 | #include "SkOSPath.h" |
| 12 | |
Ben Wagner | 63fd760 | 2017-10-09 15:45:33 -0400 | [diff] [blame] | 13 | /* |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 14 | things to do |
| 15 | if cap word is beginning of sentence, add it to table as lower-case |
| 16 | word must have only a single initial capital |
| 17 | |
| 18 | if word is camel cased, look for :: matches on suffix |
| 19 | |
| 20 | when function crosses lines, whole thing isn't seen as a 'word' e.g., search for largeArc in path |
| 21 | |
| 22 | words in external not seen |
Cary Clark | 80247e5 | 2018-07-11 16:18:41 -0400 | [diff] [blame] | 23 | |
| 24 | look for x-bit but allow x bits |
Cary Clark | d2ca79c | 2018-08-10 13:09:13 -0400 | [diff] [blame] | 25 | |
| 26 | don't treat 'pos' or 'glyphs' as spell-checkable as in 'RunBuffer.pos' or 'RunBuffer.glyphs' |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 27 | */ |
Cary Clark | 80247e5 | 2018-07-11 16:18:41 -0400 | [diff] [blame] | 28 | |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 29 | struct CheckEntry { |
| 30 | string fFile; |
| 31 | int fLine; |
| 32 | int fCount; |
Cary Clark | 5538c13 | 2018-06-14 12:28:14 -0400 | [diff] [blame] | 33 | bool fOverride; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 34 | }; |
| 35 | |
| 36 | class SpellCheck : public ParserCommon { |
| 37 | public: |
| 38 | SpellCheck(const BmhParser& bmh) : ParserCommon() |
| 39 | , fBmhParser(bmh) { |
| 40 | this->reset(); |
| 41 | } |
| 42 | bool check(const char* match); |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 43 | void report(SkCommandLineFlags::StringArray report); |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 44 | private: |
| 45 | enum class TableState { |
| 46 | kNone, |
| 47 | kRow, |
| 48 | kColumn, |
| 49 | }; |
| 50 | |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 51 | enum class PrintCheck { |
| 52 | kWordsOnly, |
| 53 | kAllowNumbers, |
| 54 | }; |
| 55 | |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 56 | bool check(Definition* ); |
| 57 | bool checkable(MarkType markType); |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 58 | void childCheck(Definition* def, const char* start); |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 59 | void leafCheck(const char* start, const char* end); |
| 60 | bool parseFromFile(const char* path) override { return true; } |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 61 | void printCheck(string str, PrintCheck); |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 62 | |
| 63 | void reset() override { |
| 64 | INHERITED::resetCommon(); |
| 65 | fMethod = nullptr; |
| 66 | fRoot = nullptr; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 67 | fInCode = false; |
| 68 | fInConst = false; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 69 | fInFormula = false; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 70 | fInDescription = false; |
| 71 | fInStdOut = false; |
Cary Clark | 5538c13 | 2018-06-14 12:28:14 -0400 | [diff] [blame] | 72 | fOverride = false; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 73 | } |
| 74 | |
Cary Clark | 2d4bf5f | 2018-04-16 08:37:38 -0400 | [diff] [blame] | 75 | void wordCheck(string str); |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 76 | void wordCheck(ptrdiff_t len, const char* ch); |
| 77 | |
| 78 | unordered_map<string, CheckEntry> fCode; |
| 79 | unordered_map<string, CheckEntry> fColons; |
| 80 | unordered_map<string, CheckEntry> fDigits; |
| 81 | unordered_map<string, CheckEntry> fDots; |
| 82 | unordered_map<string, CheckEntry> fParens; // also hold destructors, operators |
| 83 | unordered_map<string, CheckEntry> fUnderscores; |
| 84 | unordered_map<string, CheckEntry> fWords; |
| 85 | const BmhParser& fBmhParser; |
| 86 | Definition* fMethod; |
| 87 | RootDefinition* fRoot; |
Cary Clark | 4855f78 | 2018-02-06 09:41:53 -0500 | [diff] [blame] | 88 | int fLocalLine; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 89 | bool fInCode; |
| 90 | bool fInConst; |
| 91 | bool fInDescription; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 92 | bool fInFormula; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 93 | bool fInStdOut; |
Cary Clark | 5538c13 | 2018-06-14 12:28:14 -0400 | [diff] [blame] | 94 | bool fOverride; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 95 | typedef ParserCommon INHERITED; |
| 96 | }; |
| 97 | |
| 98 | /* This doesn't perform a traditional spell or grammar check, although |
| 99 | maybe it should. Instead it looks for words used uncommonly and lower |
| 100 | case words that match capitalized words that are not sentence starters. |
| 101 | It also looks for articles preceeding capitalized words and their |
| 102 | modifiers to try to maintain a consistent voice. |
| 103 | Maybe also look for passive verbs (e.g. 'is') and suggest active ones? |
| 104 | */ |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 105 | void BmhParser::spellCheck(const char* match, SkCommandLineFlags::StringArray report) const { |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 106 | SpellCheck checker(*this); |
| 107 | checker.check(match); |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 108 | checker.report(report); |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 109 | } |
| 110 | |
Cary Clark | 2f46624 | 2017-12-11 16:03:17 -0500 | [diff] [blame] | 111 | void BmhParser::spellStatus(const char* statusFile, SkCommandLineFlags::StringArray report) const { |
| 112 | SpellCheck checker(*this); |
| 113 | StatusIter iter(statusFile, ".bmh", StatusFilter::kInProgress); |
Cary Clark | 7724d3f | 2018-09-14 09:12:38 -0400 | [diff] [blame] | 114 | string file; |
Cary Clark | 61313f3 | 2018-10-08 14:57:48 -0400 | [diff] [blame] | 115 | iter.next(&file, nullptr); |
Cary Clark | 2f46624 | 2017-12-11 16:03:17 -0500 | [diff] [blame] | 116 | string match = iter.baseDir(); |
| 117 | checker.check(match.c_str()); |
| 118 | checker.report(report); |
| 119 | } |
| 120 | |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 121 | bool SpellCheck::check(const char* match) { |
| 122 | for (const auto& topic : fBmhParser.fTopicMap) { |
| 123 | Definition* topicDef = topic.second; |
| 124 | if (topicDef->fParent) { |
| 125 | continue; |
| 126 | } |
| 127 | if (!topicDef->isRoot()) { |
| 128 | return this->reportError<bool>("expected root topic"); |
| 129 | } |
| 130 | fRoot = topicDef->asRoot(); |
| 131 | if (string::npos == fRoot->fFileName.rfind(match)) { |
| 132 | continue; |
| 133 | } |
Cary Clark | 5538c13 | 2018-06-14 12:28:14 -0400 | [diff] [blame] | 134 | fOverride = string::npos != fRoot->fFileName.rfind("undocumented.bmh") |
| 135 | || string::npos != fRoot->fFileName.rfind("markup.bmh") |
| 136 | || string::npos != fRoot->fFileName.rfind("usingBookmaker.bmh"); |
| 137 | this->check(topicDef); |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 138 | } |
| 139 | return true; |
| 140 | } |
| 141 | |
Cary Clark | 2d4bf5f | 2018-04-16 08:37:38 -0400 | [diff] [blame] | 142 | static bool all_lower(string str) { |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 143 | for (auto c : str) { |
| 144 | if (!islower(c)) { |
| 145 | return false; |
| 146 | } |
| 147 | } |
| 148 | return true; |
| 149 | } |
| 150 | |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 151 | bool SpellCheck::check(Definition* def) { |
| 152 | fFileName = def->fFileName; |
| 153 | fLineCount = def->fLineCount; |
| 154 | string printable = def->printableName(); |
| 155 | const char* textStart = def->fContentStart; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 156 | switch (def->fMarkType) { |
| 157 | case MarkType::kAlias: |
| 158 | break; |
| 159 | case MarkType::kAnchor: |
| 160 | break; |
| 161 | case MarkType::kBug: |
| 162 | break; |
| 163 | case MarkType::kClass: |
| 164 | this->wordCheck(def->fName); |
| 165 | break; |
| 166 | case MarkType::kCode: |
| 167 | fInCode = true; |
| 168 | break; |
| 169 | case MarkType::kColumn: |
| 170 | break; |
| 171 | case MarkType::kComment: |
| 172 | break; |
| 173 | case MarkType::kConst: { |
| 174 | fInConst = true; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 175 | this->wordCheck(def->fName); |
| 176 | const char* lineEnd = strchr(textStart, '\n'); |
| 177 | this->wordCheck(lineEnd - textStart, textStart); |
| 178 | textStart = lineEnd; |
| 179 | } break; |
| 180 | case MarkType::kDefine: |
| 181 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 182 | case MarkType::kDeprecated: |
| 183 | break; |
| 184 | case MarkType::kDescription: |
| 185 | fInDescription = true; |
| 186 | break; |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 187 | case MarkType::kDetails: |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 188 | break; |
Cary Clark | ac47b88 | 2018-01-11 10:35:44 -0500 | [diff] [blame] | 189 | case MarkType::kDuration: |
| 190 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 191 | case MarkType::kEnum: |
| 192 | case MarkType::kEnumClass: |
| 193 | this->wordCheck(def->fName); |
| 194 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 195 | case MarkType::kExample: |
| 196 | break; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 197 | case MarkType::kExperimental: |
| 198 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 199 | case MarkType::kExternal: |
| 200 | break; |
Cary Clark | 0d22539 | 2018-06-07 09:59:07 -0400 | [diff] [blame] | 201 | case MarkType::kFile: |
| 202 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 203 | case MarkType::kFormula: |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 204 | fInFormula = true; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 205 | break; |
| 206 | case MarkType::kFunction: |
| 207 | break; |
| 208 | case MarkType::kHeight: |
| 209 | break; |
Cary Clark | f895a42 | 2018-02-27 09:54:21 -0500 | [diff] [blame] | 210 | case MarkType::kIllustration: |
| 211 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 212 | case MarkType::kImage: |
| 213 | break; |
Cary Clark | 4855f78 | 2018-02-06 09:41:53 -0500 | [diff] [blame] | 214 | case MarkType::kIn: |
| 215 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 216 | case MarkType::kLegend: |
| 217 | break; |
Cary Clark | 4855f78 | 2018-02-06 09:41:53 -0500 | [diff] [blame] | 218 | case MarkType::kLine: |
| 219 | break; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 220 | case MarkType::kLink: |
| 221 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 222 | case MarkType::kList: |
| 223 | break; |
Cary Clark | 154beea | 2017-10-26 07:58:48 -0400 | [diff] [blame] | 224 | case MarkType::kLiteral: |
| 225 | break; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 226 | case MarkType::kMarkChar: |
| 227 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 228 | case MarkType::kMember: |
| 229 | break; |
| 230 | case MarkType::kMethod: { |
| 231 | string method_name = def->methodName(); |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 232 | if (all_lower(method_name)) { |
| 233 | method_name += "()"; |
| 234 | } |
Cary Clark | a560c47 | 2017-11-27 10:44:06 -0500 | [diff] [blame] | 235 | if (!def->isClone() && Definition::MethodType::kOperator != def->fMethodType) { |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 236 | this->wordCheck(method_name); |
| 237 | } |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 238 | fMethod = def; |
| 239 | } break; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 240 | case MarkType::kNoExample: |
| 241 | break; |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 242 | case MarkType::kNoJustify: |
| 243 | break; |
Cary Clark | 154beea | 2017-10-26 07:58:48 -0400 | [diff] [blame] | 244 | case MarkType::kOutdent: |
| 245 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 246 | case MarkType::kParam: { |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 247 | TextParser paramParser(def->fFileName, def->fStart, def->fContentStart, |
| 248 | def->fLineCount); |
| 249 | paramParser.skipWhiteSpace(); |
| 250 | SkASSERT(paramParser.startsWith("#Param")); |
| 251 | paramParser.next(); // skip hash |
Cary Clark | 2d4bf5f | 2018-04-16 08:37:38 -0400 | [diff] [blame] | 252 | paramParser.skipToNonName(); // skip Param |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 253 | paramParser.skipSpace(); |
| 254 | const char* paramName = paramParser.fChar; |
| 255 | paramParser.skipToSpace(); |
| 256 | fInCode = true; |
| 257 | this->wordCheck(paramParser.fChar - paramName, paramName); |
| 258 | fInCode = false; |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 259 | } break; |
| 260 | case MarkType::kPhraseDef: |
| 261 | break; |
| 262 | case MarkType::kPhraseParam: |
| 263 | break; |
| 264 | case MarkType::kPhraseRef: |
| 265 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 266 | case MarkType::kPlatform: |
| 267 | break; |
Cary Clark | 4855f78 | 2018-02-06 09:41:53 -0500 | [diff] [blame] | 268 | case MarkType::kPopulate: |
| 269 | break; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 270 | case MarkType::kPrivate: |
| 271 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 272 | case MarkType::kReturn: |
| 273 | break; |
| 274 | case MarkType::kRow: |
| 275 | break; |
| 276 | case MarkType::kSeeAlso: |
| 277 | break; |
Cary Clark | 4855f78 | 2018-02-06 09:41:53 -0500 | [diff] [blame] | 278 | case MarkType::kSet: |
| 279 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 280 | case MarkType::kStdOut: { |
| 281 | fInStdOut = true; |
| 282 | TextParser code(def); |
| 283 | code.skipSpace(); |
| 284 | while (!code.eof()) { |
| 285 | const char* end = code.trimmedLineEnd(); |
| 286 | this->wordCheck(end - code.fChar, code.fChar); |
| 287 | code.skipToLineStart(); |
| 288 | } |
| 289 | fInStdOut = false; |
| 290 | } break; |
| 291 | case MarkType::kStruct: |
| 292 | fRoot = def->asRoot(); |
| 293 | this->wordCheck(def->fName); |
| 294 | break; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 295 | case MarkType::kSubstitute: |
| 296 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 297 | case MarkType::kSubtopic: |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 298 | // TODO: add a tag that allows subtopic labels in illustrations to skip spellcheck? |
| 299 | if (string::npos == fFileName.find("illustrations.bmh")) { |
| 300 | this->printCheck(printable, PrintCheck::kAllowNumbers); |
| 301 | } |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 302 | break; |
| 303 | case MarkType::kTable: |
| 304 | break; |
| 305 | case MarkType::kTemplate: |
| 306 | break; |
| 307 | case MarkType::kText: |
| 308 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 309 | case MarkType::kToDo: |
| 310 | break; |
| 311 | case MarkType::kTopic: |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 312 | this->printCheck(printable, PrintCheck::kWordsOnly); |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 313 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 314 | case MarkType::kTypedef: |
| 315 | break; |
| 316 | case MarkType::kUnion: |
| 317 | break; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 318 | case MarkType::kVolatile: |
| 319 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 320 | case MarkType::kWidth: |
| 321 | break; |
| 322 | default: |
| 323 | SkASSERT(0); // handle everything |
| 324 | break; |
| 325 | } |
| 326 | this->childCheck(def, textStart); |
| 327 | switch (def->fMarkType) { // post child work, at least for tables |
| 328 | case MarkType::kCode: |
| 329 | fInCode = false; |
| 330 | break; |
| 331 | case MarkType::kColumn: |
| 332 | break; |
| 333 | case MarkType::kDescription: |
| 334 | fInDescription = false; |
| 335 | break; |
| 336 | case MarkType::kEnum: |
| 337 | case MarkType::kEnumClass: |
| 338 | break; |
| 339 | case MarkType::kExample: |
| 340 | break; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 341 | case MarkType::kFormula: |
| 342 | fInFormula = false; |
| 343 | break; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 344 | case MarkType::kLegend: |
| 345 | break; |
| 346 | case MarkType::kMethod: |
| 347 | fMethod = nullptr; |
| 348 | break; |
| 349 | case MarkType::kConst: |
| 350 | fInConst = false; |
| 351 | case MarkType::kParam: |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 352 | break; |
| 353 | case MarkType::kReturn: |
| 354 | case MarkType::kSeeAlso: |
| 355 | break; |
| 356 | case MarkType::kRow: |
| 357 | break; |
| 358 | case MarkType::kStruct: |
| 359 | fRoot = fRoot->rootParent(); |
| 360 | break; |
| 361 | case MarkType::kTable: |
| 362 | break; |
| 363 | default: |
| 364 | break; |
| 365 | } |
| 366 | return true; |
| 367 | } |
| 368 | |
| 369 | bool SpellCheck::checkable(MarkType markType) { |
Cary Clark | 2d4bf5f | 2018-04-16 08:37:38 -0400 | [diff] [blame] | 370 | return BmhParser::Resolvable::kYes == fBmhParser.kMarkProps[(int) markType].fResolve; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 371 | } |
| 372 | |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 373 | void SpellCheck::childCheck(Definition* def, const char* start) { |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 374 | const char* end; |
| 375 | fLineCount = def->fLineCount; |
| 376 | if (def->isRoot()) { |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 377 | fRoot = def->asRoot(); |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 378 | } |
| 379 | for (auto& child : def->fChildren) { |
| 380 | end = child->fStart; |
| 381 | if (this->checkable(def->fMarkType)) { |
| 382 | this->leafCheck(start, end); |
| 383 | } |
| 384 | this->check(child); |
| 385 | start = child->fTerminator; |
| 386 | } |
| 387 | if (this->checkable(def->fMarkType)) { |
| 388 | end = def->fContentEnd; |
| 389 | this->leafCheck(start, end); |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | void SpellCheck::leafCheck(const char* start, const char* end) { |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 394 | const char* chPtr = start; |
| 395 | int inAngles = 0; |
| 396 | int inParens = 0; |
| 397 | bool inQuotes = false; |
| 398 | bool allLower = true; |
Cary Clark | 80247e5 | 2018-07-11 16:18:41 -0400 | [diff] [blame] | 399 | char prePriorCh = 0; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 400 | char priorCh = 0; |
| 401 | char lastCh = 0; |
| 402 | const char* wordStart = nullptr; |
| 403 | const char* wordEnd = nullptr; |
| 404 | const char* possibleEnd = nullptr; |
Cary Clark | 4855f78 | 2018-02-06 09:41:53 -0500 | [diff] [blame] | 405 | fLocalLine = 0; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 406 | do { |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 407 | if (wordStart && wordEnd) { |
| 408 | if (!allLower || (!inQuotes && '\"' != lastCh && !inParens |
| 409 | && ')' != lastCh && !inAngles && '>' != lastCh)) { |
| 410 | string word(wordStart, (possibleEnd ? possibleEnd : wordEnd) - wordStart); |
Cary Clark | 80247e5 | 2018-07-11 16:18:41 -0400 | [diff] [blame] | 411 | if ("e" != word || !isdigit(prePriorCh) || ('+' != lastCh && |
| 412 | '-' != lastCh && !isdigit(lastCh))) { |
| 413 | this->wordCheck(word); |
| 414 | } |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 415 | } |
| 416 | wordStart = nullptr; |
| 417 | } |
| 418 | if (chPtr == end) { |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 419 | break; |
| 420 | } |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 421 | switch (*chPtr) { |
| 422 | case '>': |
| 423 | if (isalpha(lastCh)) { |
| 424 | --inAngles; |
| 425 | SkASSERT(inAngles >= 0); |
| 426 | } |
| 427 | wordEnd = chPtr; |
| 428 | break; |
| 429 | case '(': |
| 430 | ++inParens; |
| 431 | possibleEnd = chPtr; |
| 432 | break; |
| 433 | case ')': |
| 434 | --inParens; |
| 435 | if ('(' == lastCh) { |
| 436 | wordEnd = chPtr + 1; |
| 437 | } else { |
| 438 | wordEnd = chPtr; |
| 439 | } |
Cary Clark | 7fc1d12 | 2017-10-09 14:07:42 -0400 | [diff] [blame] | 440 | SkASSERT(inParens >= 0 || fInStdOut); |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 441 | break; |
| 442 | case '\"': |
| 443 | inQuotes = !inQuotes; |
| 444 | wordEnd = chPtr; |
| 445 | SkASSERT(inQuotes == !wordStart); |
| 446 | break; |
| 447 | case 'A': case 'B': case 'C': case 'D': case 'E': |
| 448 | case 'F': case 'G': case 'H': case 'I': case 'J': |
| 449 | case 'K': case 'L': case 'M': case 'N': case 'O': |
| 450 | case 'P': case 'Q': case 'R': case 'S': case 'T': |
| 451 | case 'U': case 'V': case 'W': case 'X': case 'Y': |
| 452 | case 'Z': |
| 453 | allLower = false; |
| 454 | case 'a': case 'b': case 'c': case 'd': case 'e': |
| 455 | case 'f': case 'g': case 'h': case 'i': case 'j': |
| 456 | case 'k': case 'l': case 'm': case 'n': case 'o': |
| 457 | case 'p': case 'q': case 'r': case 's': case 't': |
| 458 | case 'u': case 'v': case 'w': case 'x': case 'y': |
Ben Wagner | 63fd760 | 2017-10-09 15:45:33 -0400 | [diff] [blame] | 459 | case 'z': |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 460 | if (!wordStart) { |
| 461 | wordStart = chPtr; |
| 462 | wordEnd = nullptr; |
| 463 | possibleEnd = nullptr; |
| 464 | allLower = 'a' <= *chPtr; |
| 465 | if ('<' == lastCh || ('<' == priorCh && '/' == lastCh)) { |
| 466 | ++inAngles; |
| 467 | } |
| 468 | } |
| 469 | break; |
| 470 | case '0': case '1': case '2': case '3': case '4': |
| 471 | case '5': case '6': case '7': case '8': case '9': |
Ben Wagner | 63fd760 | 2017-10-09 15:45:33 -0400 | [diff] [blame] | 472 | case '_': |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 473 | allLower = false; |
| 474 | case '-': // note that dash doesn't clear allLower |
| 475 | break; |
Cary Clark | d2ca79c | 2018-08-10 13:09:13 -0400 | [diff] [blame] | 476 | case '!': |
| 477 | if (!inQuotes) { |
| 478 | wordEnd = chPtr; |
| 479 | } |
| 480 | break; |
Cary Clark | 4855f78 | 2018-02-06 09:41:53 -0500 | [diff] [blame] | 481 | case '\n': |
| 482 | ++fLocalLine; |
| 483 | // fall through |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 484 | default: |
| 485 | wordEnd = chPtr; |
| 486 | break; |
| 487 | } |
Cary Clark | 80247e5 | 2018-07-11 16:18:41 -0400 | [diff] [blame] | 488 | prePriorCh = priorCh; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 489 | priorCh = lastCh; |
| 490 | lastCh = *chPtr; |
| 491 | } while (++chPtr <= end); |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 492 | } |
| 493 | |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 494 | void SpellCheck::printCheck(string str, PrintCheck allowed) { |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 495 | string word; |
| 496 | for (std::stringstream stream(str); stream >> word; ) { |
Cary Clark | 682c58d | 2018-05-16 07:07:07 -0400 | [diff] [blame] | 497 | if (PrintCheck::kAllowNumbers == allowed && (std::isdigit(word.back()) || 'x' == word.back())) { |
| 498 | // allow ###x for RGB_888x |
| 499 | if ((size_t) std::count_if(word.begin(), word.end() - 1, |
| 500 | [](unsigned char c){ return std::isdigit(c); } ) == word.length() - 1) { |
| 501 | continue; |
| 502 | } |
| 503 | } |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 504 | wordCheck(word); |
| 505 | } |
| 506 | } |
| 507 | |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 508 | static bool stringCompare(const std::pair<string, CheckEntry>& i, const std::pair<string, CheckEntry>& j) { |
| 509 | return i.first.compare(j.first) < 0; |
| 510 | } |
| 511 | |
| 512 | void SpellCheck::report(SkCommandLineFlags::StringArray report) { |
| 513 | vector<std::pair<string, CheckEntry>> elems(fWords.begin(), fWords.end()); |
| 514 | std::sort(elems.begin(), elems.end(), stringCompare); |
| 515 | if (report.contains("once")) { |
| 516 | for (auto iter : elems) { |
Cary Clark | 5538c13 | 2018-06-14 12:28:14 -0400 | [diff] [blame] | 517 | if (iter.second.fOverride) { |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 518 | continue; |
| 519 | } |
| 520 | if (iter.second.fCount == 1) { |
Cary Clark | 4855f78 | 2018-02-06 09:41:53 -0500 | [diff] [blame] | 521 | string fullName = this->ReportFilename(iter.second.fFile); |
| 522 | SkDebugf("%s(%d): %s\n", fullName.c_str(), iter.second.fLine, |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 523 | iter.first.c_str()); |
| 524 | } |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 525 | } |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 526 | SkDebugf("\n"); |
Cary Clark | d0530ba | 2017-09-14 11:25:39 -0400 | [diff] [blame] | 527 | return; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 528 | } |
| 529 | if (report.contains("all")) { |
| 530 | int column = 0; |
Cary Clark | d0530ba | 2017-09-14 11:25:39 -0400 | [diff] [blame] | 531 | char lastInitial = 'a'; |
Cary Clark | 154beea | 2017-10-26 07:58:48 -0400 | [diff] [blame] | 532 | int count = 0; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 533 | for (auto iter : elems) { |
Cary Clark | 5538c13 | 2018-06-14 12:28:14 -0400 | [diff] [blame] | 534 | if (iter.second.fOverride) { |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 535 | continue; |
| 536 | } |
| 537 | string check = iter.first.c_str(); |
| 538 | bool allLower = true; |
| 539 | for (auto c : check) { |
| 540 | if (isupper(c)) { |
| 541 | allLower = false; |
| 542 | break; |
| 543 | } |
| 544 | } |
| 545 | if (!allLower) { |
| 546 | continue; |
| 547 | } |
Cary Clark | d0530ba | 2017-09-14 11:25:39 -0400 | [diff] [blame] | 548 | if (column + check.length() > 100 || check[0] != lastInitial) { |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 549 | SkDebugf("\n"); |
| 550 | column = 0; |
| 551 | } |
Cary Clark | d0530ba | 2017-09-14 11:25:39 -0400 | [diff] [blame] | 552 | if (check[0] != lastInitial) { |
| 553 | SkDebugf("\n"); |
| 554 | lastInitial = check[0]; |
| 555 | } |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 556 | SkDebugf("%s ", check.c_str()); |
| 557 | column += check.length(); |
Cary Clark | 154beea | 2017-10-26 07:58:48 -0400 | [diff] [blame] | 558 | ++count; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 559 | } |
Cary Clark | 154beea | 2017-10-26 07:58:48 -0400 | [diff] [blame] | 560 | SkDebugf("\n\ncount = %d\n", count); |
Cary Clark | d0530ba | 2017-09-14 11:25:39 -0400 | [diff] [blame] | 561 | return; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 562 | } |
Cary Clark | d0530ba | 2017-09-14 11:25:39 -0400 | [diff] [blame] | 563 | int index = 0; |
| 564 | const char* mispelled = report[0]; |
| 565 | for (auto iter : elems) { |
Cary Clark | 5538c13 | 2018-06-14 12:28:14 -0400 | [diff] [blame] | 566 | if (iter.second.fOverride) { |
Cary Clark | d0530ba | 2017-09-14 11:25:39 -0400 | [diff] [blame] | 567 | continue; |
| 568 | } |
| 569 | string check = iter.first.c_str(); |
| 570 | while (check.compare(mispelled) > 0) { |
| 571 | SkDebugf("%s not found\n", mispelled); |
| 572 | if (report.count() == ++index) { |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 573 | break; |
| 574 | } |
Cary Clark | d0530ba | 2017-09-14 11:25:39 -0400 | [diff] [blame] | 575 | } |
| 576 | if (report.count() == index) { |
| 577 | break; |
| 578 | } |
| 579 | if (check.compare(mispelled) == 0) { |
Cary Clark | 4855f78 | 2018-02-06 09:41:53 -0500 | [diff] [blame] | 580 | string fullName = this->ReportFilename(iter.second.fFile); |
| 581 | SkDebugf("%s(%d): %s\n", fullName.c_str(), iter.second.fLine, |
Cary Clark | d0530ba | 2017-09-14 11:25:39 -0400 | [diff] [blame] | 582 | iter.first.c_str()); |
| 583 | if (report.count() == ++index) { |
| 584 | break; |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 585 | } |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 586 | } |
| 587 | } |
| 588 | } |
| 589 | |
Cary Clark | 2d4bf5f | 2018-04-16 08:37:38 -0400 | [diff] [blame] | 590 | void SpellCheck::wordCheck(string str) { |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 591 | if ("nullptr" == str) { |
| 592 | return; // doesn't seem worth it, treating nullptr as a word in need of correction |
| 593 | } |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 594 | bool hasColon = false; |
| 595 | bool hasDot = false; |
| 596 | bool hasParen = false; |
| 597 | bool hasUnderscore = false; |
| 598 | bool sawDash = false; |
| 599 | bool sawDigit = false; |
| 600 | bool sawSpecial = false; |
| 601 | SkASSERT(str.length() > 0); |
| 602 | SkASSERT(isalpha(str[0]) || '~' == str[0]); |
| 603 | for (char ch : str) { |
| 604 | if (isalpha(ch) || '-' == ch) { |
| 605 | sawDash |= '-' == ch; |
| 606 | continue; |
| 607 | } |
| 608 | bool isColon = ':' == ch; |
| 609 | hasColon |= isColon; |
| 610 | bool isDot = '.' == ch; |
| 611 | hasDot |= isDot; |
Cary Clark | bc5697d | 2017-10-04 14:31:33 -0400 | [diff] [blame] | 612 | bool isParen = '(' == ch || ')' == ch || '~' == ch || '=' == ch || '!' == ch || |
| 613 | '[' == ch || ']' == ch; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 614 | hasParen |= isParen; |
| 615 | bool isUnderscore = '_' == ch; |
| 616 | hasUnderscore |= isUnderscore; |
| 617 | if (isColon || isDot || isUnderscore || isParen) { |
| 618 | continue; |
| 619 | } |
| 620 | if (isdigit(ch)) { |
| 621 | sawDigit = true; |
| 622 | continue; |
| 623 | } |
| 624 | if ('&' == ch || ',' == ch || ' ' == ch) { |
| 625 | sawSpecial = true; |
| 626 | continue; |
| 627 | } |
| 628 | SkASSERT(0); |
| 629 | } |
| 630 | if (sawSpecial && !hasParen) { |
| 631 | SkASSERT(0); |
| 632 | } |
| 633 | bool inCode = fInCode; |
| 634 | if (hasUnderscore && isupper(str[0]) && ('S' != str[0] || 'K' != str[1]) |
Ben Wagner | 63fd760 | 2017-10-09 15:45:33 -0400 | [diff] [blame] | 635 | && !hasColon && !hasDot && !hasParen && !fInStdOut && !inCode && !fInConst |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 636 | && !sawDigit && !sawSpecial && !sawDash) { |
| 637 | std::istringstream ss(str); |
| 638 | string token; |
| 639 | while (std::getline(ss, token, '_')) { |
Cary Clark | a560c47 | 2017-11-27 10:44:06 -0500 | [diff] [blame] | 640 | if (token.length()) { |
| 641 | this->wordCheck(token); |
| 642 | } |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 643 | } |
| 644 | return; |
| 645 | } |
Ben Wagner | 63fd760 | 2017-10-09 15:45:33 -0400 | [diff] [blame] | 646 | if (!hasColon && !hasDot && !hasParen && !hasUnderscore |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 647 | && !fInStdOut && !inCode && !fInConst && !sawDigit |
| 648 | && islower(str[0]) && isupper(str[1])) { |
| 649 | inCode = true; |
| 650 | } |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 651 | bool methodParam = false; |
| 652 | if (fMethod) { |
| 653 | for (auto child : fMethod->fChildren) { |
| 654 | if (MarkType::kParam == child->fMarkType && str == child->fName) { |
| 655 | methodParam = true; |
| 656 | break; |
| 657 | } |
| 658 | } |
| 659 | } |
Ben Wagner | 63fd760 | 2017-10-09 15:45:33 -0400 | [diff] [blame] | 660 | auto& mappy = hasColon ? fColons : |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 661 | hasDot ? fDots : |
| 662 | hasParen ? fParens : |
| 663 | hasUnderscore ? fUnderscores : |
Cary Clark | ce10124 | 2017-09-01 15:51:02 -0400 | [diff] [blame] | 664 | fInStdOut || fInFormula || inCode || fInConst || methodParam ? fCode : |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 665 | sawDigit ? fDigits : fWords; |
| 666 | auto iter = mappy.find(str); |
| 667 | if (mappy.end() != iter) { |
Cary Clark | 5538c13 | 2018-06-14 12:28:14 -0400 | [diff] [blame] | 668 | if (iter->second.fOverride && !fOverride) { |
| 669 | iter->second.fFile = fFileName; |
| 670 | iter->second.fLine = fLineCount + fLocalLine; |
| 671 | iter->second.fOverride = false; |
| 672 | } |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 673 | iter->second.fCount += 1; |
| 674 | } else { |
| 675 | CheckEntry* entry = &mappy[str]; |
| 676 | entry->fFile = fFileName; |
Cary Clark | 4855f78 | 2018-02-06 09:41:53 -0500 | [diff] [blame] | 677 | entry->fLine = fLineCount + fLocalLine; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 678 | entry->fCount = 1; |
Cary Clark | 5538c13 | 2018-06-14 12:28:14 -0400 | [diff] [blame] | 679 | entry->fOverride = fOverride; |
Cary Clark | 8032b98 | 2017-07-28 11:04:54 -0400 | [diff] [blame] | 680 | } |
| 681 | } |
| 682 | |
| 683 | void SpellCheck::wordCheck(ptrdiff_t len, const char* ch) { |
| 684 | leafCheck(ch, ch + len); |
| 685 | } |