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