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"`. ```master
parent
3c77c5dcfb
commit
4ff0f26d3e
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 <flag>
|
||||
|
||||
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.
|
||||
"
|
||||
|
|
|
@ -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
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue