From 4ff0f26d3e00fc412f0c5cb7f0ea7cd0102b2f1b Mon Sep 17 00:00:00 2001 From: Ika Date: Sat, 8 Sep 2018 07:40:09 +0800 Subject: [PATCH] refactor: move cli leven suggestion to options-normalizer (#5057) Option-related processing should be done in `options-normalizer`. - unknown flag suggestions are colored ![image](https://user-images.githubusercontent.com/8341033/45103027-866c7900-b161-11e8-8ee3-6467a9ca0090.png) - unknown flags with no suggestion are now errors: ``` $ bin/prettier.js --help noseminosemi [error] Invalid `--help` value. Expected `a flag`, but received `"noseminosemi"`. ``` --- src/cli/constant.js | 3 +- src/cli/util.js | 43 ++--------------- src/main/options-normalizer.js | 46 ++++++++++++++++++- .../__snapshots__/early-exit.js.snap | 26 ++++------- tests_integration/__tests__/early-exit.js | 4 +- 5 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/cli/constant.js b/src/cli/constant.js index de815c6c0..9bfb94813 100644 --- a/src/cli/constant.js +++ b/src/cli/constant.js @@ -156,7 +156,8 @@ const options = { description: dedent` Show CLI usage, or details about the given flag. Example: --help write - ` + `, + exception: value => value === "" }, "ignore-path": { type: "path", diff --git a/src/cli/util.js b/src/cli/util.js index 23c58bbe5..081fd9b05 100644 --- a/src/cli/util.js +++ b/src/cli/util.js @@ -7,7 +7,6 @@ const fs = require("fs"); const globby = require("globby"); const chalk = require("chalk"); const readline = require("readline"); -const leven = require("leven"); const stringify = require("json-stable-stringify"); const minimist = require("./minimist"); @@ -642,40 +641,6 @@ function flattenArray(array) { return [].concat.apply([], array); } -function getOptionWithLevenSuggestion(context, options, optionName) { - // support aliases - const optionNameContainers = flattenArray( - options.map((option, index) => [ - { value: option.name, index }, - option.alias ? { value: option.alias, index } : null - ]) - ).filter(Boolean); - - const optionNameContainer = optionNameContainers.find( - optionNameContainer => optionNameContainer.value === optionName - ); - - if (optionNameContainer !== undefined) { - return options[optionNameContainer.index]; - } - - const suggestedOptionNameContainer = optionNameContainers.find( - optionNameContainer => leven(optionNameContainer.value, optionName) < 3 - ); - - if (suggestedOptionNameContainer !== undefined) { - const suggestedOptionName = suggestedOptionNameContainer.value; - context.logger.warn( - `Unknown option name "${optionName}", did you mean "${suggestedOptionName}"?` - ); - - return options[suggestedOptionNameContainer.index]; - } - - context.logger.warn(`Unknown option name "${optionName}"`); - return options.find(option => option.name === "help"); -} - function createChoiceUsages(choices, margin, indentation) { const activeChoices = choices.filter( choice => !choice.deprecated && choice.since !== null @@ -692,11 +657,9 @@ function createChoiceUsages(choices, margin, indentation) { ); } -function createDetailedUsage(context, optionName) { - const option = getOptionWithLevenSuggestion( - context, - getOptionsWithOpposites(context.detailedOptions), - optionName +function createDetailedUsage(context, flag) { + const option = getOptionsWithOpposites(context.detailedOptions).find( + option => option.name === flag || option.alias === flag ); const header = createOptionUsageHeader(option); diff --git a/src/main/options-normalizer.js b/src/main/options-normalizer.js index ddec76637..13515d7a7 100644 --- a/src/main/options-normalizer.js +++ b/src/main/options-normalizer.js @@ -1,6 +1,8 @@ "use strict"; const vnopts = require("vnopts"); +const leven = require("leven"); +const chalk = require("chalk"); const cliDescriptor = { key: key => (key.length === 1 ? `-${key}` : `--${key}`), @@ -15,6 +17,35 @@ const cliDescriptor = { : `${cliDescriptor.key(key)}=${value}` }; +class FlagSchema extends vnopts.ChoiceSchema { + constructor({ name, flags }) { + super({ name, choices: flags }); + this._flags = flags.slice().sort(); + } + preprocess(value, utils) { + if ( + typeof value === "string" && + value.length !== 0 && + this._flags.indexOf(value) === -1 + ) { + const suggestion = this._flags.find(flag => leven(flag, value) < 3); + if (suggestion) { + utils.logger.warn( + [ + `Unknown flag ${chalk.yellow(utils.descriptor.value(value))},`, + `did you mean ${chalk.blue(utils.descriptor.value(suggestion))}?` + ].join(" ") + ); + return suggestion; + } + } + return value; + } + expected() { + return "a flag"; + } +} + function normalizeOptions( options, optionInfos, @@ -40,7 +71,7 @@ function optionInfosToSchemas(optionInfos, { isCLI }) { } for (const optionInfo of optionInfos) { - schemas.push(optionInfoToSchema(optionInfo, { isCLI })); + schemas.push(optionInfoToSchema(optionInfo, { isCLI, optionInfos })); if (optionInfo.alias && isCLI) { schemas.push( @@ -55,7 +86,7 @@ function optionInfosToSchemas(optionInfos, { isCLI }) { return schemas; } -function optionInfoToSchema(optionInfo, { isCLI }) { +function optionInfoToSchema(optionInfo, { isCLI, optionInfos }) { let SchemaConstructor; const parameters = { name: optionInfo.name }; const handlers = {}; @@ -84,6 +115,17 @@ function optionInfoToSchema(optionInfo, { isCLI }) { SchemaConstructor = vnopts.BooleanSchema; break; case "flag": + SchemaConstructor = FlagSchema; + parameters.flags = optionInfos + .map(optionInfo => + [].concat( + optionInfo.alias || [], + optionInfo.description ? optionInfo.name : [], + optionInfo.oppositeDescription ? `no-${optionInfo.name}` : [] + ) + ) + .reduce((a, b) => a.concat(b), []); + break; case "path": SchemaConstructor = vnopts.StringSchema; break; diff --git a/tests_integration/__tests__/__snapshots__/early-exit.js.snap b/tests_integration/__tests__/__snapshots__/early-exit.js.snap index a648c4e9e..fc7209aaa 100644 --- a/tests_integration/__tests__/__snapshots__/early-exit.js.snap +++ b/tests_integration/__tests__/__snapshots__/early-exit.js.snap @@ -146,21 +146,8 @@ exports[`show version with --version (stderr) 1`] = `""`; exports[`show version with --version (write) 1`] = `Array []`; -exports[`show warning with --help not-found (stderr) 1`] = ` -"[warn] Unknown option name \\"not-found\\" -" -`; - -exports[`show warning with --help not-found (stdout) 1`] = ` -"-h, --help - - Show CLI usage, or details about the given flag. - Example: --help write -" -`; - exports[`show warning with --help not-found (typo) (stderr) 1`] = ` -"[warn] Unknown option name \\"parserr\\", did you mean \\"parser\\"? +"[warn] Unknown flag \\"parserr\\", did you mean \\"parser\\"? " `; @@ -190,8 +177,6 @@ Valid options: exports[`show warning with --help not-found (typo) (write) 1`] = `Array []`; -exports[`show warning with --help not-found (write) 1`] = `Array []`; - exports[`throw error and show usage with something unexpected (stderr) 1`] = `""`; exports[`throw error and show usage with something unexpected (stdout) 1`] = ` @@ -311,6 +296,15 @@ exports[`throw error with --find-config-path + multiple files (stdout) 1`] = `"" exports[`throw error with --find-config-path + multiple files (write) 1`] = `Array []`; +exports[`throw error with --help not-found (stderr) 1`] = ` +"[error] Invalid --help value. Expected a flag, but received \\"not-found\\". +" +`; + +exports[`throw error with --help not-found (stdout) 1`] = `""`; + +exports[`throw error with --help not-found (write) 1`] = `Array []`; + exports[`throw error with --write + --debug-check (stderr) 1`] = ` "[error] Cannot use --write and --debug-check together. " diff --git a/tests_integration/__tests__/early-exit.js b/tests_integration/__tests__/early-exit.js index b36100448..270266698 100644 --- a/tests_integration/__tests__/early-exit.js +++ b/tests_integration/__tests__/early-exit.js @@ -44,9 +44,9 @@ describe(`show detailed usage with plugin options (manual resolution)`, () => { }); }); -describe("show warning with --help not-found", () => { +describe("throw error with --help not-found", () => { runPrettier("cli", ["--help", "not-found"]).test({ - status: 0 + status: 1 }); });