From dc26445e518dfc122810850fde23df449c8b7095 Mon Sep 17 00:00:00 2001 From: Ika Date: Thu, 18 Jan 2018 15:26:27 +0800 Subject: [PATCH] refactor(options): use supportOptions to generate CLI options (#3622) * refactor(cli-constant): use supportOptions * refactor(options): use supportOptions * refactor(cli-util): use supportOptions * fix: do not infer parser in multiparser * chore: remove unnecessary package * chore: trigger another travis build * test: add kebab-case test to ensure no regression * test: update snapshots --- package.json | 1 - src/cli/constant.js | 483 +++++++----------- src/cli/index.js | 104 ++-- src/cli/util.js | 174 ++----- src/cli/validator.js | 55 -- src/common/support.js | 34 +- src/language-markdown/embed.js | 2 +- src/main/multiparser.js | 3 +- src/main/options-descriptor.js | 22 + src/main/options-normalizer.js | 153 ++++++ src/main/options-validator.js | 81 +++ src/main/options.js | 174 +++---- .../__snapshots__/arg-parsing.js.snap | 8 +- .../__snapshots__/config-invalid.js.snap | 20 +- .../__snapshots__/early-exit.js.snap | 44 +- .../__snapshots__/support-info.js.snap | 45 +- .../with-config-precedence.js.snap | 9 +- tests_integration/__tests__/config-invalid.js | 6 + .../config/invalid/option/configPrecedence | 2 +- .../cli/config/invalid/option/kebab-case | 3 + .../cli/config/invalid/option/trailingComma | 2 +- yarn.lock | 20 - 22 files changed, 701 insertions(+), 744 deletions(-) delete mode 100644 src/cli/validator.js create mode 100644 src/main/options-descriptor.js create mode 100644 src/main/options-normalizer.js create mode 100644 src/main/options-validator.js create mode 100644 tests_integration/cli/config/invalid/option/kebab-case diff --git a/package.json b/package.json index a1142a16..f2bf29b9 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "graphql": "0.12.3", "ignore": "3.3.7", "jest-docblock": "21.3.0-beta.11", - "jest-validate": "21.1.0", "leven": "2.1.0", "mem": "1.1.0", "minimatch": "3.0.4", diff --git a/src/cli/constant.js b/src/cli/constant.js index 7bf45adc..e3e86381 100644 --- a/src/cli/constant.js +++ b/src/cli/constant.js @@ -1,7 +1,8 @@ "use strict"; -const camelCase = require("camelcase"); const dedent = require("dedent"); +const dashify = require("dashify"); +const getSupportInfo = require("../common/support").getSupportInfo; const CATEGORY_CONFIG = "Config"; const CATEGORY_EDITOR = "Editor"; @@ -64,318 +65,187 @@ const categoryOrder = [ * // If the option has a value that is an exception to the regular value * // constraints, indicate that value here (or use a function for more * // flexibility). - * exception?: any | ((value: any) => boolean); + * exception?: ((value: any) => boolean); * * // Indicate that the option is deprecated. Use a string to add an extra * // message to --help for the option, for example to suggest a replacement * // option. * deprecated?: true | string; - * - * // Custom function to get the value for the option. Useful for handling - * // deprecated options. - * // --parser example: (value, argv) => argv["flow-parser"] ? "flow" : value - * getter?: (value: any, argv: any) => any; * } * } * * Note: The options below are sorted alphabetically. */ -const detailedOptions = normalizeDetailedOptions({ - "arrow-parens": { - type: "choice", - category: CATEGORY_FORMAT, - forwardToApi: true, - description: "Include parentheses around a sole arrow function parameter.", - choices: [ - { - value: "avoid", - description: "Omit parens when possible. Example: `x => x`" - }, - { - value: "always", - description: "Always include parens. Example: `(x) => x`" +const detailedOptions = normalizeDetailedOptions( + Object.assign( + getSupportInfo(null, { + showDeprecated: true, + showUnreleased: true + }).options.reduce((reduced, option) => { + const newOption = Object.assign({}, option, { + name: dashify(option.name), + forwardToApi: option.name + }); + + switch (option.name) { + case "filepath": + Object.assign(newOption, { + name: "stdin-filepath", + description: "Path to the file to pretend that stdin comes from." + }); + break; + case "useFlowParser": + newOption.name = "flow-parser"; + break; + case "plugins": + newOption.name = "plugin"; + break; } - ] - }, - "bracket-spacing": { - type: "boolean", - category: CATEGORY_FORMAT, - forwardToApi: true, - description: "Print spaces between brackets.", - oppositeDescription: "Do not print spaces between brackets." - }, - color: { - // The supports-color package (a sub sub dependency) looks directly at - // `process.argv` for `--no-color` and such-like options. The reason it is - // listed here is to avoid "Ignored unknown option: --no-color" warnings. - // See https://github.com/chalk/supports-color/#info for more information. - type: "boolean", - default: true, - description: "Colorize error messages.", - oppositeDescription: "Do not colorize error messages." - }, - config: { - type: "path", - category: CATEGORY_CONFIG, - description: - "Path to a Prettier configuration file (.prettierrc, package.json, prettier.config.js).", - oppositeDescription: "Do not look for a configuration file." - }, - "config-precedence": { - type: "choice", - category: CATEGORY_CONFIG, - default: "cli-override", - choices: [ - { - value: "cli-override", - description: "CLI options take precedence over config file" + + switch (newOption.name) { + case "cursor-offset": + case "range-start": + case "range-end": + newOption.category = CATEGORY_EDITOR; + break; + case "stdin-filepath": + case "insert-pragma": + case "require-pragma": + newOption.category = CATEGORY_OTHER; + break; + case "plugin": + newOption.category = CATEGORY_CONFIG; + break; + default: + newOption.category = CATEGORY_FORMAT; + break; + } + + if (option.deprecated) { + delete newOption.forwardToApi; + delete newOption.description; + delete newOption.oppositeDescription; + newOption.deprecated = true; + } + + return Object.assign(reduced, { [newOption.name]: newOption }); + }, {}), + { + color: { + // The supports-color package (a sub sub dependency) looks directly at + // `process.argv` for `--no-color` and such-like options. The reason it is + // listed here is to avoid "Ignored unknown option: --no-color" warnings. + // See https://github.com/chalk/supports-color/#info for more information. + type: "boolean", + default: true, + description: "Colorize error messages.", + oppositeDescription: "Do not colorize error messages." }, - { - value: "file-override", - description: "Config file take precedence over CLI options" + config: { + type: "path", + category: CATEGORY_CONFIG, + description: + "Path to a Prettier configuration file (.prettierrc, package.json, prettier.config.js).", + oppositeDescription: "Do not look for a configuration file." }, - { - value: "prefer-file", + "config-precedence": { + type: "choice", + category: CATEGORY_CONFIG, + default: "cli-override", + choices: [ + { + value: "cli-override", + description: "CLI options take precedence over config file" + }, + { + value: "file-override", + description: "Config file take precedence over CLI options" + }, + { + value: "prefer-file", + description: dedent` + If a config file is found will evaluate it and ignore other CLI options. + If no config file is found CLI options will evaluate as normal. + ` + } + ], + description: + "Define in which order config files and CLI options should be evaluated." + }, + "debug-check": { + type: "boolean" + }, + "debug-print-doc": { + type: "boolean" + }, + editorconfig: { + type: "boolean", + category: CATEGORY_CONFIG, + description: + "Take .editorconfig into account when parsing configuration.", + oppositeDescription: + "Don't take .editorconfig into account when parsing configuration.", + default: true + }, + "find-config-path": { + type: "path", + category: CATEGORY_CONFIG, + description: + "Find and print the path to a configuration file for the given input file." + }, + help: { + type: "flag", + alias: "h", description: dedent` - If a config file is found will evaluate it and ignore other CLI options. - If no config file is found CLI options will evaluate as normal. + Show CLI usage, or details about the given flag. + Example: --help write ` + }, + "ignore-path": { + type: "path", + category: CATEGORY_CONFIG, + default: ".prettierignore", + description: "Path to a file with patterns describing files to ignore." + }, + "list-different": { + type: "boolean", + category: CATEGORY_OUTPUT, + alias: "l", + description: + "Print the names of files that are different from Prettier's formatting." + }, + loglevel: { + type: "choice", + description: "What level of logs to report.", + default: "log", + choices: ["silent", "error", "warn", "log", "debug"] + }, + stdin: { + type: "boolean", + description: "Force reading input from stdin." + }, + "support-info": { + type: "boolean", + description: "Print support information as JSON." + }, + version: { + type: "boolean", + alias: "v", + description: "Print Prettier version." + }, + "with-node-modules": { + type: "boolean", + category: CATEGORY_CONFIG, + description: "Process files inside 'node_modules' directory." + }, + write: { + type: "boolean", + category: CATEGORY_OUTPUT, + description: "Edit files in-place. (Beware!)" } - ], - description: - "Define in which order config files and CLI options should be evaluated." - }, - "cursor-offset": { - type: "int", - category: CATEGORY_EDITOR, - exception: -1, - forwardToApi: true, - description: dedent` - Print (to stderr) where a cursor at the given position would move to after formatting. - This option cannot be used with --range-start and --range-end. - ` - }, - "debug-check": { - type: "boolean" - }, - "debug-print-doc": { - type: "boolean" - }, - editorconfig: { - type: "boolean", - category: CATEGORY_CONFIG, - description: "Take .editorconfig into account when parsing configuration.", - oppositeDescription: - "Don't take .editorconfig into account when parsing configuration.", - default: true - }, - "find-config-path": { - type: "path", - category: CATEGORY_CONFIG, - description: - "Find and print the path to a configuration file for the given input file." - }, - "flow-parser": { - // Deprecated in 0.0.10 - type: "boolean", - category: CATEGORY_FORMAT, - deprecated: "Use `--parser flow` instead." - }, - help: { - type: "flag", - alias: "h", - description: dedent` - Show CLI usage, or details about the given flag. - Example: --help write - ` - }, - "ignore-path": { - type: "path", - category: CATEGORY_CONFIG, - default: ".prettierignore", - description: "Path to a file with patterns describing files to ignore." - }, - "insert-pragma": { - type: "boolean", - forwardToApi: true, - description: dedent` - Insert @format pragma into file's first docblock comment. - ` - }, - "jsx-bracket-same-line": { - type: "boolean", - category: CATEGORY_FORMAT, - forwardToApi: true, - description: "Put > on the last line instead of at a new line." - }, - "list-different": { - type: "boolean", - category: CATEGORY_OUTPUT, - alias: "l", - description: - "Print the names of files that are different from Prettier's formatting." - }, - loglevel: { - type: "choice", - description: "What level of logs to report.", - default: "log", - choices: ["silent", "error", "warn", "log", "debug"] - }, - parser: { - type: "choice", - category: CATEGORY_FORMAT, - forwardToApi: true, - exception: value => typeof value === "string", // Allow path to a parser module. - choices: [ - "flow", - "babylon", - "typescript", - "css", - { value: "postcss", deprecated: true, redirect: "css" }, - "less", - "scss", - "json", - // "glimmer", - "graphql", - "markdown", - "vue" - ], - description: "Which parser to use.", - getter: (value, argv) => (argv["flow-parser"] ? "flow" : value) - }, - plugin: { - type: "path", - category: CATEGORY_CONFIG, - description: - "Add a plugin. Multiple plugins can be passed as separate `--plugin`s.", - forwardToApi: "plugins", - array: true - }, - "print-width": { - type: "int", - category: CATEGORY_FORMAT, - forwardToApi: true, - description: "The line length where Prettier will try wrap." - }, - "prose-wrap": { - type: "choice", - category: CATEGORY_FORMAT, - forwardToApi: true, - description: "How to wrap prose. (markdown)", - choices: [ - { - value: "always", - description: "Wrap prose if it exceeds the print width." - }, - { value: "never", description: "Do not wrap prose." }, - { value: "preserve", description: "Wrap prose as-is." }, - { value: false, deprecated: true, redirect: "never" } - ] - }, - "range-end": { - type: "int", - category: CATEGORY_EDITOR, - forwardToApi: true, - exception: Infinity, - description: dedent` - Format code ending at a given character offset (exclusive). - The range will extend forwards to the end of the selected statement. - This option cannot be used with --cursor-offset. - ` - }, - "range-start": { - type: "int", - category: CATEGORY_EDITOR, - forwardToApi: true, - description: dedent` - Format code starting at a given character offset. - The range will extend backwards to the start of the first line containing the selected statement. - This option cannot be used with --cursor-offset. - ` - }, - "require-pragma": { - type: "boolean", - forwardToApi: true, - description: dedent` - Require either '@prettier' or '@format' to be present in the file's first docblock comment - in order for it to be formatted. - ` - }, - semi: { - type: "boolean", - category: CATEGORY_FORMAT, - forwardToApi: true, - description: "Print semicolons.", - oppositeDescription: - "Do not print semicolons, except at the beginning of lines which may need them." - }, - "single-quote": { - type: "boolean", - category: CATEGORY_FORMAT, - forwardToApi: true, - description: "Use single quotes instead of double quotes." - }, - stdin: { - type: "boolean", - description: "Force reading input from stdin." - }, - "stdin-filepath": { - type: "path", - forwardToApi: "filepath", - description: "Path to the file to pretend that stdin comes from." - }, - "support-info": { - type: "boolean", - description: "Print support information as JSON." - }, - "tab-width": { - type: "int", - category: CATEGORY_FORMAT, - forwardToApi: true, - description: "Number of spaces per indentation level." - }, - "trailing-comma": { - type: "choice", - category: CATEGORY_FORMAT, - forwardToApi: true, - choices: [ - { value: "none", description: "No trailing commas." }, - { - value: "es5", - description: - "Trailing commas where valid in ES5 (objects, arrays, etc.)" - }, - { - value: "all", - description: - "Trailing commas wherever possible (including function arguments)." - }, - { value: "", deprecated: true, redirect: "es5" } - ], - description: "Print trailing commas wherever possible when multi-line." - }, - "use-tabs": { - type: "boolean", - category: CATEGORY_FORMAT, - forwardToApi: true, - description: "Indent with tabs instead of spaces." - }, - version: { - type: "boolean", - alias: "v", - description: "Print Prettier version." - }, - "with-node-modules": { - type: "boolean", - category: CATEGORY_CONFIG, - description: "Process files inside 'node_modules' directory." - }, - write: { - type: "boolean", - category: CATEGORY_OUTPUT, - description: "Edit files in-place. (Beware!)" - } -}); + } + ) +); const minimistOptions = { boolean: detailedOptions @@ -385,6 +255,7 @@ const minimistOptions = { .filter(option => option.type !== "boolean") .map(option => option.name), default: detailedOptions + .filter(option => !option.deprecated) .filter(option => option.default !== undefined) .reduce( (current, option) => @@ -415,20 +286,18 @@ function normalizeDetailedOptions(rawDetailedOptions) { return Object.assign({}, option, { name, category: option.category || CATEGORY_OTHER, - forwardToApi: - option.forwardToApi && - (typeof option.forwardToApi === "string" - ? option.forwardToApi - : camelCase(name)), choices: option.choices && - option.choices.map(choice => - Object.assign( + option.choices.map(choice => { + const newChoice = Object.assign( { description: "", deprecated: false }, typeof choice === "object" ? choice : { value: choice } - ) - ), - getter: option.getter || (value => value) + ); + if (newChoice.value === true) { + newChoice.value = ""; // backward compability for original boolean option + } + return newChoice; + }) }); }); diff --git a/src/cli/index.js b/src/cli/index.js index f852e970..684d4626 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -5,58 +5,76 @@ const minimist = require("minimist"); const prettier = require("../../index"); const constant = require("./constant"); const util = require("./util"); -const validator = require("./validator"); +const normalizer = require("../main/options-normalizer"); const logger = require("./logger"); function run(args) { - const rawArgv = minimist(args, constant.minimistOptions); + try { + const rawArgv = minimist(args, constant.minimistOptions); - process.env[logger.ENV_LOG_LEVEL] = - rawArgv["loglevel"] || constant.detailedOptionMap["loglevel"].default; + process.env[logger.ENV_LOG_LEVEL] = + rawArgv["loglevel"] || constant.detailedOptionMap["loglevel"].default; - const argv = util.normalizeConfig("cli", rawArgv); - - logger.debug(`normalized argv: ${JSON.stringify(argv)}`); - - argv.__args = args; - argv.__filePatterns = argv["_"]; - - validator.validateArgv(argv); - - if (argv["version"]) { - logger.log(prettier.version); - process.exit(0); - } - - if (argv["help"] !== undefined) { - logger.log( - typeof argv["help"] === "string" && argv["help"] !== "" - ? util.createDetailedUsage(argv["help"]) - : util.createUsage() + const argv = normalizer.normalizeCliOptions( + rawArgv, + constant.detailedOptions, + { logger } ); - process.exit(0); - } - if (argv["support-info"]) { - logger.log( - prettier.format(JSON.stringify(prettier.getSupportInfo()), { - parser: "json" - }) - ); - process.exit(0); - } + logger.debug(`normalized argv: ${JSON.stringify(argv)}`); - const hasFilePatterns = argv.__filePatterns.length !== 0; - const useStdin = argv["stdin"] || (!hasFilePatterns && !process.stdin.isTTY); + argv.__args = args; + argv.__filePatterns = argv["_"]; - if (argv["find-config-path"]) { - util.logResolvedConfigPathOrDie(argv["find-config-path"]); - } else if (useStdin) { - util.formatStdin(argv); - } else if (hasFilePatterns) { - util.formatFiles(argv); - } else { - logger.log(util.createUsage()); + if (argv["write"] && argv["debug-check"]) { + logger.error("Cannot use --write and --debug-check together."); + process.exit(1); + } + + if (argv["find-config-path"] && argv.__filePatterns.length) { + logger.error("Cannot use --find-config-path with multiple files"); + process.exit(1); + } + + if (argv["version"]) { + logger.log(prettier.version); + process.exit(0); + } + + if (argv["help"] !== undefined) { + logger.log( + typeof argv["help"] === "string" && argv["help"] !== "" + ? util.createDetailedUsage(argv["help"]) + : util.createUsage() + ); + process.exit(0); + } + + if (argv["support-info"]) { + logger.log( + prettier.format(JSON.stringify(prettier.getSupportInfo()), { + parser: "json" + }) + ); + process.exit(0); + } + + const hasFilePatterns = argv.__filePatterns.length !== 0; + const useStdin = + argv["stdin"] || (!hasFilePatterns && !process.stdin.isTTY); + + if (argv["find-config-path"]) { + util.logResolvedConfigPathOrDie(argv["find-config-path"]); + } else if (useStdin) { + util.formatStdin(argv); + } else if (hasFilePatterns) { + util.formatFiles(argv); + } else { + logger.log(util.createUsage()); + process.exit(1); + } + } catch (error) { + logger.error(error.message); process.exit(1); } } diff --git a/src/cli/util.js b/src/cli/util.js index 590f87fd..22709b6a 100644 --- a/src/cli/util.js +++ b/src/cli/util.js @@ -16,27 +16,28 @@ const cleanAST = require("../common/clean-ast").cleanAST; const errors = require("../common/errors"); const resolver = require("../config/resolve-config"); const constant = require("./constant"); -const validator = require("./validator"); -const options = require("../main/options"); -const apiDefaultOptions = options.defaults; -const normalizeOptions = options.normalize; +const optionsModule = require("../main/options"); +const apiDefaultOptions = optionsModule.defaults; +const optionsNormalizer = require("../main/options-normalizer"); const logger = require("./logger"); const thirdParty = require("../common/third-party"); +const optionInfos = require("../common/support").getSupportInfo(null, { + showDeprecated: true, + showUnreleased: true +}).options; const OPTION_USAGE_THRESHOLD = 25; const CHOICE_USAGE_MARGIN = 3; const CHOICE_USAGE_INDENTATION = 2; function getOptions(argv) { - return constant.detailedOptions.filter(option => option.forwardToApi).reduce( - (current, option) => - Object.assign(current, { - [option.forwardToApi]: option.array - ? [].concat(argv[option.name] || []) - : argv[option.name] - }), - {} - ); + return constant.detailedOptions + .filter(option => option.forwardToApi) + .reduce( + (current, option) => + Object.assign(current, { [option.forwardToApi]: argv[option.name] }), + {} + ); } function cliifyOptions(object) { @@ -130,7 +131,7 @@ function format(argv, input, opt) { "prettier(input) !== prettier(prettier(input))\n" + diff(pp, pppp) ); } else { - const normalizedOpts = normalizeOptions(opt); + const normalizedOpts = optionsModule.normalize(opt); const ast = cleanAST( prettier.__debug.parse(input, opt).ast, normalizedOpts @@ -192,7 +193,8 @@ function getOptionsForFile(argv, filepath) { { filepath }, applyConfigPrecedence( argv, - options && normalizeConfig("api", options, constant.detailedOptionMap) + options && + optionsNormalizer.normalizeApiOptions(options, optionInfos, { logger }) ) ); @@ -205,8 +207,7 @@ function getOptionsForFile(argv, filepath) { function parseArgsToOptions(argv, overrideDefaults) { return getOptions( - normalizeConfig( - "cli", + optionsNormalizer.normalizeCliOptions( minimist( argv.__args, Object.assign({ @@ -219,7 +220,8 @@ function parseArgsToOptions(argv, overrideDefaults) { ) }) ), - { warning: false } + constant.detailedOptions, + { logger: false } ) ); } @@ -463,12 +465,18 @@ function createOptionUsage(option, threshold) { `${option.description}${ optionDefaultValue === undefined ? "" - : `\nDefaults to ${optionDefaultValue}.` + : `\nDefaults to ${createDefaultValueDisplay(optionDefaultValue)}.` }`, threshold ); } +function createDefaultValueDisplay(value) { + return Array.isArray(value) + ? `[${value.map(createDefaultValueDisplay).join(", ")}]` + : value; +} + function createOptionUsageHeader(option) { const name = `--${option.name}`; const alias = option.alias ? `-${option.alias},` : null; @@ -574,7 +582,7 @@ function createDetailedUsage(optionName) { const optionDefaultValue = getOptionDefaultValue(option.name); const defaults = optionDefaultValue !== undefined - ? `\n\nDefault: ${optionDefaultValue}` + ? `\n\nDefault: ${createDefaultValueDisplay(optionDefaultValue)}` : ""; return `${header}${description}${choices}${defaults}`; @@ -612,135 +620,11 @@ function groupBy(array, getKey) { }, Object.create(null)); } -/** @param {'api' | 'cli'} type */ -function normalizeConfig(type, rawConfig, options) { - if (type === "api" && rawConfig === null) { - return null; - } - - options = options || {}; - - const consoleWarn = - options.warning === false ? () => {} : logger.warn.bind(logger); - - const normalized = {}; - - Object.keys(rawConfig).forEach(rawKey => { - const rawValue = rawConfig[rawKey]; - - const key = type === "cli" ? rawKey : dashify(rawKey); - - if (type === "cli" && key === "_") { - normalized[rawKey] = rawValue; - return; - } - - if (type === "cli" && key.length === 1) { - // do nothing with alias - return; - } - - let option = constant.detailedOptionMap[key]; - if (type === "api" && option === undefined) { - option = constant.apiDetailedOptionMap[key]; - } - - // unknown option - if (option === undefined) { - if (type === "api") { - consoleWarn(`Ignored unknown option: ${rawKey}`); - } else { - const optionName = rawValue === false ? `no-${rawKey}` : rawKey; - consoleWarn(`Ignored unknown option: --${optionName}`); - } - return; - } - - const value = getValue(rawValue, option); - - if (option.exception !== undefined) { - if (typeof option.exception === "function") { - if (option.exception(value)) { - normalized[rawKey] = value; - return; - } - } else { - if (value === option.exception) { - normalized[rawKey] = value; - return; - } - } - } - - try { - switch (option.type) { - case "int": - validator.validateIntOption(type, value, option); - normalized[rawKey] = Number(value); - break; - case "choice": - validator.validateChoiceOption(type, value, option); - normalized[rawKey] = value; - break; - default: - normalized[rawKey] = value; - break; - } - } catch (error) { - logger.error(error.message); - process.exit(2); - } - }); - - return normalized; - - function getOptionName(option) { - return type === "cli" ? `--${option.name}` : camelCase(option.name); - } - - function getRedirectName(option, choice) { - return type === "cli" - ? `--${option.name}=${choice.redirect}` - : `{ ${camelCase(option.name)}: ${JSON.stringify(choice.redirect)} }`; - } - - function getValue(rawValue, option) { - const optionName = getOptionName(option); - if (rawValue && option.deprecated) { - let warning = `\`${optionName}\` is deprecated.`; - if (typeof option.deprecated === "string") { - warning += ` ${option.deprecated}`; - } - consoleWarn(warning); - } - - const value = option.getter(rawValue, rawConfig); - - if (option.type === "choice") { - const choice = option.choices.find(choice => choice.value === rawValue); - if (choice !== undefined && choice.deprecated) { - const warningDescription = - rawValue === "" - ? "without an argument" - : `with value \`${rawValue}\``; - const redirectName = getRedirectName(option, choice); - consoleWarn( - `\`${optionName}\` ${warningDescription} is deprecated. Prettier now treats it as: \`${redirectName}\`.` - ); - return choice.redirect; - } - } - - return value; - } -} - module.exports = { logResolvedConfigPathOrDie, format, formatStdin, formatFiles, createUsage, - createDetailedUsage, - normalizeConfig + createDetailedUsage }; diff --git a/src/cli/validator.js b/src/cli/validator.js deleted file mode 100644 index 72a496ea..00000000 --- a/src/cli/validator.js +++ /dev/null @@ -1,55 +0,0 @@ -"use strict"; - -const camelCase = require("camelcase"); -const logger = require("./logger"); - -function validateArgv(argv) { - if (argv["write"] && argv["debug-check"]) { - logger.error("Cannot use --write and --debug-check together."); - process.exit(1); - } - - if (argv["find-config-path"] && argv.__filePatterns.length) { - logger.error("Cannot use --find-config-path with multiple files"); - process.exit(1); - } -} - -function getOptionName(type, option) { - return type === "cli" ? `--${option.name}` : camelCase(option.name); -} - -function validateIntOption(type, value, option) { - if (!/^\d+$/.test(value) || (type === "api" && typeof value !== "number")) { - const optionName = getOptionName(type, option); - throw new Error( - `Invalid ${optionName} value.\n` + - `Expected an integer, but received: ${JSON.stringify(value)}` - ); - } -} - -function validateChoiceOption(type, value, option) { - if (!option.choices.some(choice => choice.value === value)) { - const optionName = getOptionName(type, option); - throw new Error( - `Invalid option for ${optionName}.\n` + - `Expected ${getJoinedChoices()}, but received: ${JSON.stringify(value)}` - ); - } - - function getJoinedChoices() { - const choices = option.choices - .filter(choice => !choice.deprecated) - .map(choice => `"${choice.value}"`); - const head = choices.slice(0, -2); - const tail = choices.slice(-2); - return head.concat(tail.join(" or ")).join(", "); - } -} - -module.exports = { - validateArgv, - validateIntOption, - validateChoiceOption -}; diff --git a/src/common/support.js b/src/common/support.js index b7ca81d8..1e414e12 100644 --- a/src/common/support.js +++ b/src/common/support.js @@ -14,6 +14,7 @@ const CATEGORY_SPECIAL = "Special"; * @property {string} since - available since version * @property {string} category * @property {'int' | 'boolean' | 'choice' | 'path'} type + * @property {boolean} array - indicate it's an array of the specified type * @property {boolean?} deprecated - deprecated since version * @property {OptionRedirectInfo?} redirect - redirect deprecated option * @property {string} description @@ -21,9 +22,10 @@ const CATEGORY_SPECIAL = "Special"; * @property {OptionValueInfo} default * @property {OptionRangeInfo?} range - for type int * @property {OptionChoiceInfo?} choices - for type choice + * @property {(value: any) => boolean} exception * * @typedef {number | boolean | string} OptionValue - * @typedef {OptionValue | Array<{ since: string, value: OptionValue}>} OptionValueInfo + * @typedef {OptionValue | [{ value: OptionValue[] }] | Array<{ since: string, value: OptionValue}>} OptionValueInfo * * @typedef {Object} OptionRedirectInfo * @property {string} option @@ -75,9 +77,11 @@ const supportOptions = { type: "choice", default: "babylon", description: "Which parser to use.", + exception: value => + typeof value === "string" || typeof value === "function", choices: [ - { value: "babylon", description: "JavaScript" }, { value: "flow", description: "Flow" }, + { value: "babylon", description: "JavaScript" }, { value: "typescript", since: "1.4.0", description: "TypeScript" }, { value: "css", since: "1.7.1", description: "CSS" }, { @@ -91,9 +95,20 @@ const supportOptions = { { value: "scss", since: "1.7.1", description: "SCSS" }, { value: "json", since: "1.5.0", description: "JSON" }, { value: "graphql", since: "1.5.0", description: "GraphQL" }, - { value: "markdown", since: "1.8.0", description: "Markdown" } + { value: "markdown", since: "1.8.0", description: "Markdown" }, + { value: "vue", since: "1.10.0", description: "Vue" } ] }, + plugins: { + since: "1.10.0", + type: "path", + array: true, + default: [{ value: [] }], + category: CATEGORY_GLOBAL, + description: + "Add a plugin. Multiple plugins can be passed as separate `--plugin`s.", + exception: value => typeof value === "string" || typeof value === "object" + }, printWidth: { since: "0.0.0", category: CATEGORY_GLOBAL, @@ -208,11 +223,14 @@ function getSupportInfo(version, opts) { const newOption = Object.assign({}, option); if (Array.isArray(newOption.default)) { - newOption.default = newOption.default - .filter(filterSince) - .sort((info1, info2) => - semver.compare(info2.since, info1.since) - )[0].value; + newOption.default = + newOption.default.length === 1 + ? newOption.default[0].value + : newOption.default + .filter(filterSince) + .sort((info1, info2) => + semver.compare(info2.since, info1.since) + )[0].value; } if (Array.isArray(newOption.choices)) { diff --git a/src/language-markdown/embed.js b/src/language-markdown/embed.js index 226d4296..57c249e1 100644 --- a/src/language-markdown/embed.js +++ b/src/language-markdown/embed.js @@ -34,7 +34,7 @@ function embed(path, print, textToDoc, options) { return null; function getParserName(lang) { - const supportInfo = support.getSupportInfo(undefined, { + const supportInfo = support.getSupportInfo(null, { plugins: options.plugins, pluginsLoaded: true }); diff --git a/src/main/multiparser.js b/src/main/multiparser.js index 7390d551..9ddcf8e8 100644 --- a/src/main/multiparser.js +++ b/src/main/multiparser.js @@ -20,7 +20,8 @@ function textToDoc(text, partialNextOptions, parentOptions) { Object.assign({}, parentOptions, partialNextOptions, { parentParser: parentOptions.parser, originalText: text - }) + }), + { passThrough: true, inferParser: false } ); const result = require("./parser").parse(text, nextOptions); diff --git a/src/main/options-descriptor.js b/src/main/options-descriptor.js new file mode 100644 index 00000000..8dc53ef7 --- /dev/null +++ b/src/main/options-descriptor.js @@ -0,0 +1,22 @@ +"use strict"; + +function apiDescriptor(name, value) { + return arguments.length === 1 + ? JSON.stringify(name) + : `\`{ ${apiDescriptor(name)}: ${JSON.stringify(value)} }\``; +} + +function cliDescriptor(name, value) { + return value === false + ? `\`--no-${name}\`` + : value === true || arguments.length === 1 + ? `\`--${name}\`` + : value === "" + ? `\`--${name}\` without an argument` + : `\`--${name}=${value}\``; +} + +module.exports = { + apiDescriptor, + cliDescriptor +}; diff --git a/src/main/options-normalizer.js b/src/main/options-normalizer.js new file mode 100644 index 00000000..3457a8b1 --- /dev/null +++ b/src/main/options-normalizer.js @@ -0,0 +1,153 @@ +"use strict"; + +const leven = require("leven"); +const validator = require("./options-validator"); +const descriptors = require("./options-descriptor"); + +function normalizeOptions(options, optionInfos, opts) { + opts = opts || {}; + const logger = + opts.logger === false + ? { warn() {} } + : opts.logger !== undefined ? opts.logger : console; + const descriptor = opts.descriptor || descriptors.apiDescriptor; + const passThrough = opts.passThrough || []; + + const optionInfoMap = optionInfos.reduce( + (reduced, optionInfo) => + Object.assign(reduced, { [optionInfo.name]: optionInfo }), + {} + ); + const normalizedOptions = Object.keys(options).reduce((newOptions, key) => { + const optionInfo = optionInfoMap[key]; + + let optionName = key; + let optionValue = options[key]; + + if (!optionInfo) { + if (passThrough === true || passThrough.indexOf(optionName) !== -1) { + newOptions[optionName] = optionValue; + } else { + logger.warn( + createUnknownOptionMessage( + optionName, + optionValue, + optionInfos, + descriptor + ) + ); + } + return newOptions; + } + + if (!optionInfo.deprecated) { + optionValue = normalizeOption(optionValue, optionInfo); + } else if (typeof optionInfo.redirect === "string") { + logger.warn(createRedirectOptionMessage(optionInfo, descriptor)); + optionName = optionInfo.redirect; + } else if (optionValue) { + logger.warn(createRedirectOptionMessage(optionInfo, descriptor)); + optionValue = optionInfo.redirect.value; + optionName = optionInfo.redirect.option; + } + + if (optionInfo.choices) { + const choiceInfo = optionInfo.choices.find( + choice => choice.value === optionValue + ); + if (choiceInfo && choiceInfo.deprecated) { + logger.warn( + createRedirectChoiceMessage(optionInfo, choiceInfo, descriptor) + ); + optionValue = choiceInfo.redirect; + } + } + + if (optionInfo.array && !Array.isArray(optionValue)) { + optionValue = [optionValue]; + } + + if (optionValue !== optionInfo.default) { + validator.validateOption(optionValue, optionInfoMap[optionName], { + descriptor + }); + } + + newOptions[optionName] = optionValue; + return newOptions; + }, {}); + + return normalizedOptions; +} + +function normalizeOption(option, optionInfo) { + return optionInfo.type === "int" ? Number(option) : option; +} + +function createUnknownOptionMessage(key, value, optionInfos, descriptor) { + const messages = [`Ignored unknown option ${descriptor(key, value)}.`]; + + const suggestedOptionInfo = optionInfos.find( + optionInfo => leven(optionInfo.name, key) < 3 + ); + if (suggestedOptionInfo) { + messages.push(`Did you mean ${JSON.stringify(suggestedOptionInfo.name)}?`); + } + + return messages.join(" "); +} + +function createRedirectOptionMessage(optionInfo, descriptor) { + return `${descriptor( + optionInfo.name + )} is deprecated. Prettier now treats it as ${ + typeof optionInfo.redirect === "string" + ? descriptor(optionInfo.redirect) + : descriptor(optionInfo.redirect.option, optionInfo.redirect.value) + }.`; +} + +function createRedirectChoiceMessage(optionInfo, choiceInfo, descriptor) { + return `${descriptor( + optionInfo.name, + choiceInfo.value + )} is deprecated. Prettier now treats it as ${descriptor( + optionInfo.name, + choiceInfo.redirect + )}.`; +} + +function normalizeApiOptions(options, optionInfos, opts) { + return normalizeOptions( + options, + optionInfos, + Object.assign({ descriptor: descriptors.apiDescriptor }, opts) + ); +} + +function normalizeCliOptions(options, optionInfos, opts) { + const args = options["_"] || []; + + const newOptions = normalizeOptions( + Object.keys(options).reduce( + (reduced, key) => + Object.assign( + reduced, + key.length === 1 // omit alias + ? null + : { [key]: options[key] } + ), + {} + ), + optionInfos, + Object.assign({ descriptor: descriptors.cliDescriptor }, opts) + ); + newOptions["_"] = args; + + return newOptions; +} + +module.exports = { + normalizeApiOptions, + normalizeCliOptions +}; diff --git a/src/main/options-validator.js b/src/main/options-validator.js new file mode 100644 index 00000000..7608c4b7 --- /dev/null +++ b/src/main/options-validator.js @@ -0,0 +1,81 @@ +"use strict"; + +const descriptors = require("./options-descriptor"); + +function validateOption(value, optionInfo, opts) { + opts = opts || {}; + const descriptor = opts.descriptor || descriptors.apiDescriptor; + + if ( + typeof optionInfo.exception === "function" && + optionInfo.exception(value) + ) { + return; + } + + try { + validateOptionType(value, optionInfo); + } catch (error) { + throw new Error( + `Invalid \`${descriptor(optionInfo.name)}\` value. ${ + error.message + }, but received \`${JSON.stringify(value)}\`.` + ); + } +} + +function validateOptionType(value, optionInfo) { + if (optionInfo.array) { + if (!Array.isArray(value)) { + throw new Error(`Expected an array`); + } + value.forEach(v => + validateOptionType(v, Object.assign({}, optionInfo, { array: false })) + ); + } else { + switch (optionInfo.type) { + case "int": + validateIntOption(value); + break; + case "boolean": + validateBooleanOption(value); + break; + case "choice": + validateChoiceOption(value, optionInfo.choices); + break; + } + } +} + +function validateBooleanOption(value) { + if (typeof value !== "boolean") { + throw new Error(`Expected a boolean`); + } +} + +function validateIntOption(value) { + if ( + !( + typeof value === "number" && + Math.floor(value) === value && + value >= 0 && + value !== Infinity + ) + ) { + throw new Error(`Expected an integer`); + } +} + +function validateChoiceOption(value, choiceInfos) { + if (!choiceInfos.some(choiceInfo => choiceInfo.value === value)) { + const choices = choiceInfos + .filter(choiceInfo => !choiceInfo.deprecated) + .map(choiceInfo => JSON.stringify(choiceInfo.value)) + .sort(); + const head = choices.slice(0, -2); + const tail = choices.slice(-2); + throw new Error(`Expected ${head.concat(tail.join(" or ")).join(", ")}`); + } +} + +module.exports = { validateOption }; diff --git a/src/main/options.js b/src/main/options.js index 292386c2..3087c899 100644 --- a/src/main/options.js +++ b/src/main/options.js @@ -1,142 +1,82 @@ "use strict"; const path = require("path"); - -const validate = require("jest-validate").validate; -const deprecatedConfig = require("./deprecated"); const getSupportInfo = require("../common/support").getSupportInfo; +const supportInfo = getSupportInfo(null, { showUnreleased: true }); +const normalizer = require("./options-normalizer"); const loadPlugins = require("../common/load-plugins"); const resolveParser = require("./parser").resolveParser; const getPrinter = require("./get-printer"); -const defaults = { - cursorOffset: -1, - rangeStart: 0, - rangeEnd: Infinity, - useTabs: false, - tabWidth: 2, - printWidth: 80, - singleQuote: false, - trailingComma: "none", - bracketSpacing: true, - jsxBracketSameLine: false, - parser: "babylon", - parentParser: "", - insertPragma: false, - requirePragma: false, - semi: true, - proseWrap: "preserve", - arrowParens: "avoid", - plugins: [], +const hiddenDefaults = { astFormat: "estree", - printer: {}, - __inJsTemplate: false + printer: {} }; -const exampleConfig = Object.assign({}, defaults, { - filepath: "path/to/Filename", - printWidth: 80, - originalText: "text" -}); +const defaults = supportInfo.options.reduce( + (reduced, optionInfo) => + Object.assign(reduced, { [optionInfo.name]: optionInfo.default }), + Object.assign({}, hiddenDefaults) +); // Copy options and fill in default values. -function normalize(options) { - const normalized = Object.assign({}, options || {}); - const filepath = normalized.filepath; +function normalize(options, opts) { + opts = opts || {}; - normalized.plugins = loadPlugins(normalized.plugins); + const rawOptions = Object.assign({}, options); + rawOptions.plugins = loadPlugins(rawOptions.plugins); - if ( - filepath && - !normalized.parentParser && - (!normalized.parser || normalized.parser === defaults.parser) - ) { - const extension = path.extname(filepath); - const filename = path.basename(filepath).toLowerCase(); - - const language = getSupportInfo(null, { - plugins: normalized.plugins, - pluginsLoaded: true - }).languages.find( - language => - typeof language.since === "string" && - (language.extensions.indexOf(extension) > -1 || - (language.filenames && - language.filenames.find(name => name.toLowerCase() === filename))) - ); - - if (language) { - normalized.parser = language.parsers[0]; + if (opts.inferParser !== false) { + if ( + rawOptions.filepath && + (!rawOptions.parser || rawOptions.parser === defaults.parser) + ) { + const inferredParser = inferParser( + rawOptions.filepath, + rawOptions.plugins + ); + if (inferredParser) { + rawOptions.parser = inferredParser; + } } } - if (normalized.parser === "json") { - normalized.trailingComma = "none"; - } - - /* istanbul ignore if */ - if (typeof normalized.trailingComma === "boolean") { - // Support a deprecated boolean type for the trailing comma config - // for a few versions. This code can be removed later. - normalized.trailingComma = "es5"; - - // eslint-disable-next-line no-console - console.warn( - "Warning: `trailingComma` without any argument is deprecated. " + - 'Specify "none", "es5", or "all".' - ); - } - - /* istanbul ignore if */ - if (typeof normalized.proseWrap === "boolean") { - normalized.proseWrap = normalized.proseWrap ? "always" : "never"; - - // eslint-disable-next-line no-console - console.warn( - "Warning: `proseWrap` with boolean value is deprecated. " + - 'Use "always", "never", or "preserve" instead.' - ); - } - - /* istanbul ignore if */ - if (normalized.parser === "postcss") { - normalized.parser = "css"; - - // eslint-disable-next-line no-console - console.warn( - 'Warning: `parser` with value "postcss" is deprecated. ' + - 'Use "css", "less" or "scss" instead.' - ); - } - - const parserBackup = normalized.parser; - if (typeof normalized.parser === "function") { - // Delete the function from the object to pass validation. - delete normalized.parser; - } - - validate(normalized, { exampleConfig, deprecatedConfig }); - - // Restore the option back to a function; - normalized.parser = parserBackup; - - // For backward compatibility. Deprecated in 0.0.10 - /* istanbul ignore if */ - if ("useFlowParser" in normalized) { - normalized.parser = normalized.useFlowParser ? "flow" : "babylon"; - delete normalized.useFlowParser; - } - - normalized.astFormat = resolveParser(normalized).astFormat; - normalized.printer = getPrinter(normalized); + rawOptions.astFormat = resolveParser(rawOptions).astFormat; + rawOptions.printer = getPrinter(rawOptions); Object.keys(defaults).forEach(k => { - if (normalized[k] == null) { - normalized[k] = defaults[k]; + if (rawOptions[k] == null) { + rawOptions[k] = defaults[k]; } }); - return normalized; + if (rawOptions.parser === "json") { + rawOptions.trailingComma = "none"; + } + + return normalizer.normalizeApiOptions( + rawOptions, + supportInfo.options, + Object.assign({ passThrough: Object.keys(hiddenDefaults) }, opts) + ); } -module.exports = { normalize, defaults }; +function inferParser(filepath, plugins) { + const extension = path.extname(filepath); + const filename = path.basename(filepath).toLowerCase(); + + const language = getSupportInfo(null, { + plugins, + pluginsLoaded: true + }).languages.find( + language => + typeof language.since === "string" && + (language.extensions.indexOf(extension) > -1 || + (language.filenames && + language.filenames.find(name => name.toLowerCase() === filename))) + ); + + return language && language.parsers[0]; +} + +module.exports = { normalize, defaults, hiddenDefaults }; diff --git a/tests_integration/__tests__/__snapshots__/arg-parsing.js.snap b/tests_integration/__tests__/__snapshots__/arg-parsing.js.snap index bbe7241a..b1486adf 100644 --- a/tests_integration/__tests__/__snapshots__/arg-parsing.js.snap +++ b/tests_integration/__tests__/__snapshots__/arg-parsing.js.snap @@ -10,7 +10,7 @@ exports[`boolean flags do not swallow the next argument (stdout) 1`] = ` exports[`boolean flags do not swallow the next argument (write) 1`] = `Array []`; exports[`deprecated option values are warned (stderr) 1`] = ` -"[warn] \`--trailing-comma\` without an argument is deprecated. Prettier now treats it as: \`--trailing-comma=es5\`. +"[warn] \`--trailing-comma\` without an argument is deprecated. Prettier now treats it as \`--trailing-comma=es5\`. " `; @@ -22,7 +22,7 @@ exports[`deprecated option values are warned (stdout) 1`] = ` exports[`deprecated option values are warned (write) 1`] = `Array []`; exports[`deprecated options are warned (stderr) 1`] = ` -"[warn] \`--flow-parser\` is deprecated. Use \`--parser flow\` instead. +"[warn] \`--flow-parser\` is deprecated. Prettier now treats it as \`--parser=flow\`. " `; @@ -43,7 +43,7 @@ exports[`negated options work (stdout) 1`] = ` exports[`negated options work (write) 1`] = `Array []`; exports[`unknown negated options are warned (stderr) 1`] = ` -"[warn] Ignored unknown option: --no-unknown +"[warn] Ignored unknown option \`--no-unknown\`. " `; @@ -55,7 +55,7 @@ exports[`unknown negated options are warned (stdout) 1`] = ` exports[`unknown negated options are warned (write) 1`] = `Array []`; exports[`unknown options are warned (stderr) 1`] = ` -"[warn] Ignored unknown option: --unknown +"[warn] Ignored unknown option \`--unknown\`. " `; diff --git a/tests_integration/__tests__/__snapshots__/config-invalid.js.snap b/tests_integration/__tests__/__snapshots__/config-invalid.js.snap index 66da7b9f..b5f6964d 100644 --- a/tests_integration/__tests__/__snapshots__/config-invalid.js.snap +++ b/tests_integration/__tests__/__snapshots__/config-invalid.js.snap @@ -1,7 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`show warning with kebab-case option key (stderr) 1`] = ` +"[warn] Ignored unknown option \`{ \\"print-width\\": 3 }\`. Did you mean \\"printWidth\\"? +" +`; + +exports[`show warning with kebab-case option key (stdout) 1`] = `""`; + +exports[`show warning with kebab-case option key (write) 1`] = `Array []`; + exports[`show warning with unknown option (stderr) 1`] = ` -"[warn] Ignored unknown option: hello +"[warn] Ignored unknown option \`{ \\"hello\\": \\"world\\" }\`. " `; @@ -19,8 +28,7 @@ exports[`throw error with invalid config format (stdout) 1`] = `""`; exports[`throw error with invalid config format (write) 1`] = `Array []`; exports[`throw error with invalid config option (int) (stderr) 1`] = ` -"[error] Invalid tabWidth value. -[error] Expected an integer, but received: 0.5 +"[error] Invalid \`\\"tabWidth\\"\` value. Expected an integer, but received \`0.5\`. " `; @@ -29,8 +37,7 @@ exports[`throw error with invalid config option (int) (stdout) 1`] = `""`; exports[`throw error with invalid config option (int) (write) 1`] = `Array []`; exports[`throw error with invalid config option (trailingComma) (stderr) 1`] = ` -"[error] Invalid option for trailingComma. -[error] Expected \\"none\\", \\"es5\\" or \\"all\\", but received: \\"wow\\" +"[error] Invalid \`\\"trailingComma\\"\` value. Expected \\"all\\", \\"es5\\" or \\"none\\", but received \`\\"wow\\"\`. " `; @@ -39,8 +46,7 @@ exports[`throw error with invalid config option (trailingComma) (stdout) 1`] = ` exports[`throw error with invalid config option (trailingComma) (write) 1`] = `Array []`; exports[`throw error with invalid config precedence option (configPrecedence) (stderr) 1`] = ` -"[error] Invalid option for --config-precedence. -[error] Expected \\"cli-override\\", \\"file-override\\" or \\"prefer-file\\", but received: \\"option/configPrecedence\\" +"[error] Invalid \`\`--config-precedence\`\` value. Expected \\"cli-override\\", \\"file-override\\" or \\"prefer-file\\", but received \`\\"option/configPrecedence\\"\`. " `; diff --git a/tests_integration/__tests__/__snapshots__/early-exit.js.snap b/tests_integration/__tests__/__snapshots__/early-exit.js.snap index 65884236..e602e308 100644 --- a/tests_integration/__tests__/__snapshots__/early-exit.js.snap +++ b/tests_integration/__tests__/__snapshots__/early-exit.js.snap @@ -271,16 +271,16 @@ exports[`show detailed usage with --help parser (stdout) 1`] = ` Valid options: - flow - babylon - typescript - css - less - scss - json - graphql - markdown - vue + flow Flow + babylon JavaScript + typescript TypeScript + css CSS + less Less + scss SCSS + json JSON + graphql GraphQL + markdown Markdown + vue Vue Default: babylon " @@ -294,6 +294,8 @@ exports[`show detailed usage with --help plugin (stdout) 1`] = ` "--plugin Add a plugin. Multiple plugins can be passed as separate \`--plugin\`s. + +Default: [] " `; @@ -565,6 +567,7 @@ Config options: --ignore-path Path to a file with patterns describing files to ignore. Defaults to .prettierignore. --plugin Add a plugin. Multiple plugins can be passed as separate \`--plugin\`s. + Defaults to []. --with-node-modules Process files inside 'node_modules' directory. Editor options: @@ -634,16 +637,16 @@ exports[`show warning with --help not-found (typo) (stdout) 1`] = ` Valid options: - flow - babylon - typescript - css - less - scss - json - graphql - markdown - vue + flow Flow + babylon JavaScript + typescript TypeScript + css CSS + less Less + scss SCSS + json JSON + graphql GraphQL + markdown Markdown + vue Vue Default: babylon " @@ -706,6 +709,7 @@ Config options: --ignore-path Path to a file with patterns describing files to ignore. Defaults to .prettierignore. --plugin Add a plugin. Multiple plugins can be passed as separate \`--plugin\`s. + Defaults to []. --with-node-modules Process files inside 'node_modules' directory. Editor options: diff --git a/tests_integration/__tests__/__snapshots__/support-info.js.snap b/tests_integration/__tests__/__snapshots__/support-info.js.snap index 73c5e663..cc62e0c1 100644 --- a/tests_integration/__tests__/__snapshots__/support-info.js.snap +++ b/tests_integration/__tests__/__snapshots__/support-info.js.snap @@ -17,8 +17,8 @@ exports[`API getSupportInfo() with version 0.0.0 -> 1.0.0 1`] = ` + }, + \\"parser\\": Object { + \\"choices\\": Array [ -+ \\"babylon\\", + \\"flow\\", ++ \\"babylon\\", + ], + \\"default\\": \\"babylon\\", + \\"type\\": \\"choice\\", @@ -174,8 +174,8 @@ exports[`API getSupportInfo() with version 1.0.0 -> 1.4.0 1`] = ` }, \\"parser\\": Object { \\"choices\\": Array [ - \\"babylon\\", \\"flow\\", + \\"babylon\\", + \\"typescript\\", + \\"postcss\\", ], @@ -238,8 +238,8 @@ exports[`API getSupportInfo() with version 1.4.0 -> 1.5.0 1`] = ` \\"JavaScript\\": Array [ @@ -47,10 +53,12 @@ \\"choices\\": Array [ - \\"babylon\\", \\"flow\\", + \\"babylon\\", \\"typescript\\", \\"postcss\\", + \\"json\\", @@ -287,8 +287,8 @@ exports[`API getSupportInfo() with version 1.5.0 -> 1.7.1 1`] = ` @@ -52,11 +52,13 @@ \\"parser\\": Object { \\"choices\\": Array [ - \\"babylon\\", \\"flow\\", + \\"babylon\\", \\"typescript\\", - \\"postcss\\", + \\"css\\", @@ -419,7 +419,27 @@ exports[`API getSupportInfo() with version 1.8.2 -> undefined 1`] = ` \\"type\\": \\"boolean\\", }, \\"cursorOffset\\": Object { -@@ -80,14 +91,15 @@ +@@ -65,14 +76,19 @@ + \\"less\\", + \\"scss\\", + \\"json\\", + \\"graphql\\", + \\"markdown\\", ++ \\"vue\\", + ], + \\"default\\": \\"babylon\\", + \\"type\\": \\"choice\\", + }, ++ \\"plugins\\": Object { ++ \\"default\\": Array [], ++ \\"type\\": \\"path\\", ++ }, + \\"printWidth\\": Object { + \\"default\\": 80, + \\"range\\": Object { + \\"end\\": Infinity, + \\"start\\": 0, +@@ -80,14 +96,15 @@ }, \\"type\\": \\"int\\", }, @@ -695,8 +715,8 @@ exports[`CLI --support-info (stdout) 1`] = ` \\"default\\": \\"babylon\\", \\"description\\": \\"Which parser to use.\\", \\"choices\\": [ - { \\"value\\": \\"babylon\\", \\"description\\": \\"JavaScript\\" }, { \\"value\\": \\"flow\\", \\"description\\": \\"Flow\\" }, + { \\"value\\": \\"babylon\\", \\"description\\": \\"JavaScript\\" }, { \\"value\\": \\"typescript\\", \\"since\\": \\"1.4.0\\", @@ -707,9 +727,20 @@ exports[`CLI --support-info (stdout) 1`] = ` { \\"value\\": \\"scss\\", \\"since\\": \\"1.7.1\\", \\"description\\": \\"SCSS\\" }, { \\"value\\": \\"json\\", \\"since\\": \\"1.5.0\\", \\"description\\": \\"JSON\\" }, { \\"value\\": \\"graphql\\", \\"since\\": \\"1.5.0\\", \\"description\\": \\"GraphQL\\" }, - { \\"value\\": \\"markdown\\", \\"since\\": \\"1.8.0\\", \\"description\\": \\"Markdown\\" } + { \\"value\\": \\"markdown\\", \\"since\\": \\"1.8.0\\", \\"description\\": \\"Markdown\\" }, + { \\"value\\": \\"vue\\", \\"since\\": \\"1.10.0\\", \\"description\\": \\"Vue\\" } ] }, + { + \\"name\\": \\"plugins\\", + \\"since\\": \\"1.10.0\\", + \\"type\\": \\"path\\", + \\"array\\": true, + \\"default\\": [], + \\"category\\": \\"Global\\", + \\"description\\": + \\"Add a plugin. Multiple plugins can be passed as separate \`--plugin\`s.\\" + }, { \\"name\\": \\"printWidth\\", \\"since\\": \\"0.0.0\\", diff --git a/tests_integration/__tests__/__snapshots__/with-config-precedence.js.snap b/tests_integration/__tests__/__snapshots__/with-config-precedence.js.snap index b6de0cab..fde40265 100644 --- a/tests_integration/__tests__/__snapshots__/with-config-precedence.js.snap +++ b/tests_integration/__tests__/__snapshots__/with-config-precedence.js.snap @@ -232,8 +232,7 @@ function rcYaml() { exports[`CLI overrides take precedence without --config-precedence (write) 1`] = `Array []`; exports[`CLI validate options with --config-precedence cli-override (stderr) 1`] = ` -"[error] Invalid printWidth value. -[error] Expected an integer, but received: 0.5 +"[error] Invalid \`\\"printWidth\\"\` value. Expected an integer, but received \`0.5\`. " `; @@ -242,8 +241,7 @@ exports[`CLI validate options with --config-precedence cli-override (stdout) 1`] exports[`CLI validate options with --config-precedence cli-override (write) 1`] = `Array []`; exports[`CLI validate options with --config-precedence file-override (stderr) 1`] = ` -"[error] Invalid printWidth value. -[error] Expected an integer, but received: 0.5 +"[error] Invalid \`\\"printWidth\\"\` value. Expected an integer, but received \`0.5\`. " `; @@ -252,8 +250,7 @@ exports[`CLI validate options with --config-precedence file-override (stdout) 1` exports[`CLI validate options with --config-precedence file-override (write) 1`] = `Array []`; exports[`CLI validate options with --config-precedence prefer-file (stderr) 1`] = ` -"[error] Invalid printWidth value. -[error] Expected an integer, but received: 0.5 +"[error] Invalid \`\\"printWidth\\"\` value. Expected an integer, but received \`0.5\`. " `; diff --git a/tests_integration/__tests__/config-invalid.js b/tests_integration/__tests__/config-invalid.js index 82a3c86a..159dc03e 100644 --- a/tests_integration/__tests__/config-invalid.js +++ b/tests_integration/__tests__/config-invalid.js @@ -45,3 +45,9 @@ describe("show warning with unknown option", () => { status: 0 }); }); + +describe("show warning with kebab-case option key", () => { + runPrettier("cli/config/invalid", ["--config", "option/kebab-case"]).test({ + status: 0 + }); +}); diff --git a/tests_integration/cli/config/invalid/option/configPrecedence b/tests_integration/cli/config/invalid/option/configPrecedence index 0bf25430..35b80ffb 100644 --- a/tests_integration/cli/config/invalid/option/configPrecedence +++ b/tests_integration/cli/config/invalid/option/configPrecedence @@ -1,3 +1,3 @@ { - "config-precedence": "invalidValue" + "configPrecedence": "invalidValue" } diff --git a/tests_integration/cli/config/invalid/option/kebab-case b/tests_integration/cli/config/invalid/option/kebab-case new file mode 100644 index 00000000..fd6ff735 --- /dev/null +++ b/tests_integration/cli/config/invalid/option/kebab-case @@ -0,0 +1,3 @@ +{ + "print-width": 3 +} \ No newline at end of file diff --git a/tests_integration/cli/config/invalid/option/trailingComma b/tests_integration/cli/config/invalid/option/trailingComma index 8c4f8616..21996063 100644 --- a/tests_integration/cli/config/invalid/option/trailingComma +++ b/tests_integration/cli/config/invalid/option/trailingComma @@ -1,3 +1,3 @@ { - "trailing-comma": "wow" + "trailingComma": "wow" } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 56cd2f6c..ba2c9340 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2593,10 +2593,6 @@ jest-get-type@21.3.0-beta.15: version "21.3.0-beta.15" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.3.0-beta.15.tgz#d5a510c32683124576eaba69b66a1ba3c25ed67c" -jest-get-type@^21.0.2: - version "21.0.2" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.0.2.tgz#304e6b816dd33cd1f47aba0597bcad258a509fc6" - jest-get-type@^21.2.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.2.0.tgz#f6376ab9db4b60d81e39f30749c6c466f40d4a23" @@ -2742,15 +2738,6 @@ jest-util@^21.2.1: jest-validate "^21.2.1" mkdirp "^0.5.1" -jest-validate@21.1.0: - version "21.1.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-21.1.0.tgz#39d01115544a758bce49f221a5fcbb24ebdecc65" - dependencies: - chalk "^2.0.1" - jest-get-type "^21.0.2" - leven "^2.1.0" - pretty-format "^21.1.0" - jest-validate@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-21.2.1.tgz#cc0cbca653cd54937ba4f2a111796774530dd3c7" @@ -3601,13 +3588,6 @@ pretty-format@^20.0.3: ansi-regex "^2.1.1" ansi-styles "^3.0.0" -pretty-format@^21.1.0: - version "21.1.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-21.1.0.tgz#557428254323832ee8b7c971cb613442bea67f61" - dependencies: - ansi-regex "^3.0.0" - ansi-styles "^3.2.0" - pretty-format@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-21.2.1.tgz#ae5407f3cf21066cd011aa1ba5fce7b6a2eddb36"