llvm.org GIT mirror llvm / 1fed623
[Option] Add 'findNearest' method to catch typos Summary: Add a method `OptTable::findNearest`, which allows users of OptTable to check user input for misspelled options. In addition, have llvm-mt check for misspelled options. For example, if a user invokes `llvm-mt /oyt:foo`, the error message will indicate that while an option named `/oyt:` does not exist, `/out:` does. The method ports the functionality of the `LookupNearestOption` method from LLVM CommandLine to libLLVMOption. This allows tools like Clang and Swift, which do not use CommandLine, to use this functionality to suggest similarly spelled options. As room for future improvement, the new method as-is cannot yet properly suggest nearby "joined" options -- that is, for an option string "-FozBar", where "-Foo" is the correct option name and "Bar" is the value being passed along with the misspelled option, this method will calculate an edit distance of 4, by deleting "Bar" and changing "z" to "o". It should instead calculate an edit distance of just 1, by changing "z" to "o" and recognizing "Bar" as a value. This commit includes a disabled test that expresses this limitation. Test Plan: `check-llvm` Reviewers: yamaguchi, v.g.vassilev, teemperor, ruiu, jroelofs Reviewed By: jroelofs Subscribers: jroelofs, llvm-commits Differential Revision: https://reviews.llvm.org/D41732 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@321877 91177308-0d34-0410-b5e6-96231b3b80d8 Brian Gesiak 1 year, 7 months ago
6 changed file(s) with 149 addition(s) and 3 deletion(s). Raw diff Collapse all Expand all
142142 std::vector findByPrefix(StringRef Cur,
143143 unsigned short DisableFlags) const;
144144
145 /// Find the OptTable option that most closely matches the given string.
146 ///
147 /// \param [in] Option - A string, such as "-stdlibs=l", that represents user
148 /// input of an option that may not exist in the OptTable. Note that the
149 /// string includes prefix dashes "-" as well as values "=l".
150 /// \param [out] NearestString - The nearest option string found in the
151 /// OptTable.
152 /// \param [in] FlagsToInclude - Only find options with any of these flags.
153 /// Zero is the default, which includes all flags.
154 /// \param [in] FlagsToExclude - Don't find options with this flag. Zero
155 /// is the default, and means exclude nothing.
156 /// \param [in] MinimumLength - Don't find options shorter than this length.
157 /// For example, a minimum length of 3 prevents "-x" from being considered
158 /// near to "-S".
159 ///
160 /// \return The edit distance of the nearest string found.
161 unsigned findNearest(StringRef Option, std::string &NearestString,
162 unsigned FlagsToInclude = 0, unsigned FlagsToExclude = 0,
163 unsigned MinimumLength = 4) const;
164
145165 /// Add Values to Option's Values class
146166 ///
147167 /// \param [in] Option - Prefix + Name of the flag which Values will be
246246 return Ret;
247247 }
248248
249 unsigned OptTable::findNearest(StringRef Option, std::string &NearestString,
250 unsigned FlagsToInclude, unsigned FlagsToExclude,
251 unsigned MinimumLength) const {
252 assert(!Option.empty());
253
254 // Consider each option as a candidate, finding the closest match.
255 unsigned BestDistance = UINT_MAX;
256 for (const Info &CandidateInfo :
257 ArrayRef(OptionInfos).drop_front(FirstSearchableIndex)) {
258 StringRef CandidateName = CandidateInfo.Name;
259
260 // Ignore option candidates with empty names, such as "--", or names
261 // that do not meet the minimum length.
262 if (CandidateName.empty() || CandidateName.size() < MinimumLength)
263 continue;
264
265 // If FlagsToInclude were specified, ignore options that don't include
266 // those flags.
267 if (FlagsToInclude && !(CandidateInfo.Flags & FlagsToInclude))
268 continue;
269 // Ignore options that contain the FlagsToExclude.
270 if (CandidateInfo.Flags & FlagsToExclude)
271 continue;
272
273 // Ignore positional argument option candidates (which do not
274 // have prefixes).
275 if (!CandidateInfo.Prefixes)
276 continue;
277 // Find the most appropriate prefix. For example, if a user asks for
278 // "--helm", suggest "--help" over "-help".
279 StringRef Prefix;
280 for (int P = 0; CandidateInfo.Prefixes[P]; P++) {
281 if (Option.startswith(CandidateInfo.Prefixes[P]))
282 Prefix = CandidateInfo.Prefixes[P];
283 }
284
285 // Check if the candidate ends with a character commonly used when
286 // delimiting an option from its value, such as '=' or ':'. If it does,
287 // attempt to split the given option based on that delimiter.
288 std::string Delimiter = "";
289 char Last = CandidateName.back();
290 if (Last == '=' || Last == ':')
291 Delimiter = std::string(1, Last);
292
293 StringRef LHS, RHS;
294 if (Delimiter.empty())
295 LHS = Option;
296 else
297 std::tie(LHS, RHS) = Option.split(Last);
298
299 std::string NormalizedName =
300 (LHS.drop_front(Prefix.size()) + Delimiter).str();
301 unsigned Distance =
302 CandidateName.edit_distance(NormalizedName, /*AllowReplacements=*/true,
303 /*MaxEditDistance=*/BestDistance);
304 if (Distance < BestDistance) {
305 BestDistance = Distance;
306 NearestString = (Prefix + CandidateName + RHS).str();
307 }
308 }
309 return BestDistance;
310 }
311
249312 bool OptTable::addValues(const char *Option, const char *Values) {
250313 for (size_t I = FirstSearchableIndex, E = OptionInfos.size(); I < E; I++) {
251314 Info &In = OptionInfos[I];
22 HELP: OVERVIEW: Manifest Tool
33
44 RUN: not llvm-mt /foo 2>&1 >/dev/null | FileCheck %s -check-prefix=INVALID
5 INVALID: llvm-mt error: invalid option '/foo'
56
6 INVALID: llvm-mt error: invalid option /foo
7 RUN: not llvm-mt /oyt:%t 2>&1 | FileCheck %s -check-prefix=INVALID-BUT-CLOSE
8 INVALID-BUT-CLOSE: llvm-mt error: invalid option '/oyt:{{.*}}/help.test.tmp', did you mean '/out:{{.*}}/help.test.tmp'?
9
102102 ArrayRef ArgsArr = makeArrayRef(argv + 1, argc);
103103 opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC);
104104
105 for (auto *Arg : InputArgs.filtered(OPT_INPUT))
106 reportError(Twine("invalid option ") + Arg->getSpelling());
105 for (auto *Arg : InputArgs.filtered(OPT_INPUT)) {
106 auto ArgString = Arg->getAsString(InputArgs);
107 std::string Diag;
108 raw_string_ostream OS(Diag);
109 OS << "invalid option '" << ArgString << "'";
110
111 std::string Nearest;
112 if (T.findNearest(ArgString, Nearest) < 2)
113 OS << ", did you mean '" << Nearest << "'?";
114
115 reportError(OS.str());
116 }
107117
108118 for (auto &Arg : InputArgs) {
109119 if (Arg->getOption().matches(OPT_unsupported)) {
265265 EXPECT_EQ(1U, AL.getAllArgValues(OPT_B).size());
266266 EXPECT_EQ("", AL.getAllArgValues(OPT_B)[0]);
267267 }
268
269 TEST(Option, FindNearest) {
270 TestOptTable T;
271 std::string Nearest;
272
273 // Options that are too short should not be considered
274 // "near" other short options.
275 EXPECT_GT(T.findNearest("-A", Nearest), 4U);
276 EXPECT_GT(T.findNearest("/C", Nearest), 4U);
277 EXPECT_GT(T.findNearest("--C=foo", Nearest), 4U);
278
279 // The nearest candidate should mirror the amount of prefix
280 // characters used in the original string.
281 EXPECT_EQ(1U, T.findNearest("-blorb", Nearest));
282 EXPECT_EQ(Nearest, "-blorp");
283 EXPECT_EQ(1U, T.findNearest("--blorm", Nearest));
284 EXPECT_EQ(Nearest, "--blorp");
285
286 // The nearest candidate respects the prefix and value delimiter
287 // of the original string.
288 EXPECT_EQ(1U, T.findNearest("/framb:foo", Nearest));
289 EXPECT_EQ(Nearest, "/cramb:foo");
290
291 // Flags should be included and excluded as specified.
292 EXPECT_EQ(1U, T.findNearest("-doopf", Nearest, /*FlagsToInclude=*/OptFlag2));
293 EXPECT_EQ(Nearest, "-doopf2");
294 EXPECT_EQ(1U, T.findNearest("-doopf", Nearest,
295 /*FlagsToInclude=*/0,
296 /*FlagsToExclude=*/OptFlag2));
297 EXPECT_EQ(Nearest, "-doopf1");
298 }
299
300 TEST(DISABLED_Option, FindNearestFIXME) {
301 TestOptTable T;
302 std::string Nearest;
303
304 // FIXME: Options with joined values should not have those values considered
305 // when calculating distance. The test below would fail if run, but it should
306 // succeed.
307 EXPECT_EQ(1U, T.findNearest("--erbghFoo", Nearest));
308 EXPECT_EQ(Nearest, "--ermghFoo");
309
310 }
2727 def Slurp : Option<["-"], "slurp", KIND_REMAINING_ARGS>;
2828
2929 def SlurpJoined : Option<["-"], "slurpjoined", KIND_REMAINING_ARGS_JOINED>;
30
31 def Blorp : Flag<["-", "--"], "blorp">, HelpText<"The blorp option">, Flags<[OptFlag1]>;
32 def Cramb : Joined<["/"], "cramb:">, HelpText<"The cramb option">, MetaVarName<"CRAMB">, Flags<[OptFlag1]>;
33 def Doopf1 : Flag<["-"], "doopf1">, HelpText<"The doopf1 option">, Flags<[OptFlag1]>;
34 def Doopf2 : Flag<["-"], "doopf2">, HelpText<"The doopf2 option">, Flags<[OptFlag2]>;
35 def Ermgh : Joined<["--"], "ermgh">, HelpText<"The ermgh option">, MetaVarName<"ERMGH">, Flags<[OptFlag1]>;
36 def DashDash : Option<["--"], "", KIND_REMAINING_ARGS>;