Fix inconsistent option normalization (#14622)

This commit is contained in:
fisker Cheung authored and GitHub committed 2023-03-29 17:48:29 +08:00
1 parent 97fda8c11d
commit d70f7b989b
18 files changed
+330 -324

No files matched your search

+245
View File
@@ -0,0 +1,245 @@
import { outdent } from "outdent";
import { optionCategories } from "./prettier-internal.js";
/**
* {
* [optionName]: {
* // The type of the option. For 'choice', see also `choices` below.
* // When passing a type other than the ones listed below, the option is
* // treated as taking any string as argument, and `--option <${type}>` will
* // be displayed in --help.
* type: "boolean" | "choice" | "int" | string;
*
* // Default value to be passed to the minimist option `default`.
* default?: any;
*
* // Alias name to be passed to the minimist option `alias`.
* alias?: string;
*
* // For grouping options by category in --help.
* category?: string;
*
* // Description to be displayed in --help. If omitted, the option won't be
* // shown at all in --help (but see also `oppositeDescription` below).
* description?: string;
*
* // Description for `--no-${name}` to be displayed in --help. If omitted,
* // `--no-${name}` won't be shown.
* oppositeDescription?: string;
*
* // Indicate if this option is simply passed to the API.
* // true: use camelified name as the API option name.
* // string: use this value as the API option name.
* forwardToApi?: boolean | string;
*
* // Indicate that a CLI flag should be an array when forwarded to the API.
* array?: boolean;
*
* // Specify available choices for validation. They will also be displayed
* // in --help as <a|b|c>.
* // Use an object instead of a string if a choice is deprecated and should
* // be treated as `redirect` instead, or if you'd like to add description for
* // the choice.
* choices?: Array<
* | string
* | { value: string, description?: string, deprecated?: boolean, redirect?: string }
* >;
*
* // 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?: ((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;
* }
* }
*
* Note: The options below are sorted alphabetically.
*/
/* eslint sort-keys: "error" */
const options = {
cache: {
default: false,
description: "Only format changed files. Cannot use with --stdin-filepath.",
type: "boolean",
},
cacheLocation: {
description: "Path to the cache file.",
type: "path",
},
cacheStrategy: {
choices: [
{
description: "Use the file metadata such as timestamps as cache keys",
value: "metadata",
},
{
description: "Use the file content as cache keys",
value: "content",
},
],
description: "Strategy for the cache to use for detecting changed files.",
type: "choice",
},
check: {
alias: "c",
category: optionCategories.CATEGORY_OUTPUT,
description: outdent`
Check if the given files are formatted, print a human-friendly summary
message and paths to unformatted files (see also --list-different).
`,
type: "boolean",
},
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.
default: true,
description: "Colorize error messages.",
oppositeDescription: "Do not colorize error messages.",
type: "boolean",
},
config: {
category: optionCategories.CATEGORY_CONFIG,
description:
"Path to a Prettier configuration file (.prettierrc, package.json, prettier.config.js).",
exception: (value) => value === false,
oppositeDescription: "Do not look for a configuration file.",
type: "path",
},
configPrecedence: {
category: optionCategories.CATEGORY_CONFIG,
choices: [
{
description: "CLI options take precedence over config file",
value: "cli-override",
},
{
description: "Config file take precedence over CLI options",
value: "file-override",
},
{
description: outdent`
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.
`,
value: "prefer-file",
},
],
default: "cli-override",
description:
"Define in which order config files and CLI options should be evaluated.",
type: "choice",
},
debugBenchmark: {
// Run the formatting benchmarks. Requires 'benchmark' module to be installed.
type: "boolean",
},
debugCheck: {
// Run the formatting once again on the formatted output, throw if different.
type: "boolean",
},
debugPrintAst: {
type: "boolean",
},
debugPrintComments: {
type: "boolean",
},
debugPrintDoc: {
type: "boolean",
},
debugRepeat: {
// Repeat the formatting a few times and measure the average duration.
default: 0,
type: "int",
},
editorconfig: {
category: optionCategories.CATEGORY_CONFIG,
default: true,
description: "Take .editorconfig into account when parsing configuration.",
oppositeDescription:
"Don't take .editorconfig into account when parsing configuration.",
type: "boolean",
},
errorOnUnmatchedPattern: {
oppositeDescription: "Prevent errors when pattern is unmatched.",
type: "boolean",
},
fileInfo: {
description: outdent`
Extract the following info (as JSON) for a given file path. Reported fields:
* ignored (boolean) - true if file path is filtered by --ignore-path
* inferredParser (string | null) - name of parser inferred from file path
`,
type: "path",
},
findConfigPath: {
category: optionCategories.CATEGORY_CONFIG,
description:
"Find and print the path to a configuration file for the given input file.",
type: "path",
},
help: {
alias: "h",
description: outdent`
Show CLI usage, or details about the given flag.
Example: --help write
`,
exception: (value) => value === "",
type: "flag",
},
ignorePath: {
category: optionCategories.CATEGORY_CONFIG,
default: ".prettierignore",
description: "Path to a file with patterns describing files to ignore.",
type: "path",
},
ignoreUnknown: {
alias: "u",
description: "Ignore unknown files.",
type: "boolean",
},
listDifferent: {
alias: "l",
category: optionCategories.CATEGORY_OUTPUT,
description:
"Print the names of files that are different from Prettier's formatting (see also --check).",
type: "boolean",
},
logLevel: {
choices: ["silent", "error", "warn", "log", "debug"],
default: "log",
description: "What level of logs to report.",
type: "choice",
},
pluginSearch: {
oppositeDescription: "Disable plugin autoloading.",
type: "boolean",
},
supportInfo: {
description: "Print support information as JSON.",
type: "boolean",
},
version: {
alias: "v",
description: "Print Prettier version.",
type: "boolean",
},
withNodeModules: {
category: optionCategories.CATEGORY_CONFIG,
description: "Process files inside 'node_modules' directory.",
type: "boolean",
},
write: {
alias: "w",
category: optionCategories.CATEGORY_OUTPUT,
description: "Edit files in-place. (Beware!)",
type: "boolean",
},
};
export default options;
+7 -248
View File
@@ -1,255 +1,14 @@
import { outdent } from "outdent";
import { coreOptions } from "./prettier-internal.js";
import { optionCategories } from "./prettier-internal.js";
const categoryOrder = [
coreOptions.CATEGORY_OUTPUT,
coreOptions.CATEGORY_FORMAT,
coreOptions.CATEGORY_CONFIG,
coreOptions.CATEGORY_EDITOR,
coreOptions.CATEGORY_OTHER,
optionCategories.CATEGORY_OUTPUT,
optionCategories.CATEGORY_FORMAT,
optionCategories.CATEGORY_CONFIG,
optionCategories.CATEGORY_EDITOR,
optionCategories.CATEGORY_OTHER,
];
/**
* {
* [optionName]: {
* // The type of the option. For 'choice', see also `choices` below.
* // When passing a type other than the ones listed below, the option is
* // treated as taking any string as argument, and `--option <${type}>` will
* // be displayed in --help.
* type: "boolean" | "choice" | "int" | string;
*
* // Default value to be passed to the minimist option `default`.
* default?: any;
*
* // Alias name to be passed to the minimist option `alias`.
* alias?: string;
*
* // For grouping options by category in --help.
* category?: string;
*
* // Description to be displayed in --help. If omitted, the option won't be
* // shown at all in --help (but see also `oppositeDescription` below).
* description?: string;
*
* // Description for `--no-${name}` to be displayed in --help. If omitted,
* // `--no-${name}` won't be shown.
* oppositeDescription?: string;
*
* // Indicate if this option is simply passed to the API.
* // true: use camelified name as the API option name.
* // string: use this value as the API option name.
* forwardToApi?: boolean | string;
*
* // Indicate that a CLI flag should be an array when forwarded to the API.
* array?: boolean;
*
* // Specify available choices for validation. They will also be displayed
* // in --help as <a|b|c>.
* // Use an object instead of a string if a choice is deprecated and should
* // be treated as `redirect` instead, or if you'd like to add description for
* // the choice.
* choices?: Array<
* | string
* | { value: string, description?: string, deprecated?: boolean, redirect?: string }
* >;
*
* // 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?: ((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;
* }
* }
*
* Note: The options below are sorted alphabetically.
*/
/* eslint sort-keys: "error" */
const options = {
cache: {
default: false,
description: "Only format changed files. Cannot use with --stdin-filepath.",
type: "boolean",
},
"cache-location": {
description: "Path to the cache file.",
type: "path",
},
"cache-strategy": {
choices: [
{
description: "Use the file metadata such as timestamps as cache keys",
value: "metadata",
},
{
description: "Use the file content as cache keys",
value: "content",
},
],
description: "Strategy for the cache to use for detecting changed files.",
type: "choice",
},
check: {
alias: "c",
category: coreOptions.CATEGORY_OUTPUT,
description: outdent`
Check if the given files are formatted, print a human-friendly summary
message and paths to unformatted files (see also --list-different).
`,
type: "boolean",
},
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.
default: true,
description: "Colorize error messages.",
oppositeDescription: "Do not colorize error messages.",
type: "boolean",
},
config: {
category: coreOptions.CATEGORY_CONFIG,
description:
"Path to a Prettier configuration file (.prettierrc, package.json, prettier.config.js).",
exception: (value) => value === false,
oppositeDescription: "Do not look for a configuration file.",
type: "path",
},
"config-precedence": {
category: coreOptions.CATEGORY_CONFIG,
choices: [
{
description: "CLI options take precedence over config file",
value: "cli-override",
},
{
description: "Config file take precedence over CLI options",
value: "file-override",
},
{
description: outdent`
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.
`,
value: "prefer-file",
},
],
default: "cli-override",
description:
"Define in which order config files and CLI options should be evaluated.",
type: "choice",
},
"debug-benchmark": {
// Run the formatting benchmarks. Requires 'benchmark' module to be installed.
type: "boolean",
},
"debug-check": {
// Run the formatting once again on the formatted output, throw if different.
type: "boolean",
},
"debug-print-ast": {
type: "boolean",
},
"debug-print-comments": {
type: "boolean",
},
"debug-print-doc": {
type: "boolean",
},
"debug-repeat": {
// Repeat the formatting a few times and measure the average duration.
default: 0,
type: "int",
},
editorconfig: {
category: coreOptions.CATEGORY_CONFIG,
default: true,
description: "Take .editorconfig into account when parsing configuration.",
oppositeDescription:
"Don't take .editorconfig into account when parsing configuration.",
type: "boolean",
},
"error-on-unmatched-pattern": {
oppositeDescription: "Prevent errors when pattern is unmatched.",
type: "boolean",
},
"file-info": {
description: outdent`
Extract the following info (as JSON) for a given file path. Reported fields:
* ignored (boolean) - true if file path is filtered by --ignore-path
* inferredParser (string | null) - name of parser inferred from file path
`,
type: "path",
},
"find-config-path": {
category: coreOptions.CATEGORY_CONFIG,
description:
"Find and print the path to a configuration file for the given input file.",
type: "path",
},
help: {
alias: "h",
description: outdent`
Show CLI usage, or details about the given flag.
Example: --help write
`,
exception: (value) => value === "",
type: "flag",
},
"ignore-path": {
category: coreOptions.CATEGORY_CONFIG,
default: ".prettierignore",
description: "Path to a file with patterns describing files to ignore.",
type: "path",
},
"ignore-unknown": {
alias: "u",
description: "Ignore unknown files.",
type: "boolean",
},
"list-different": {
alias: "l",
category: coreOptions.CATEGORY_OUTPUT,
description:
"Print the names of files that are different from Prettier's formatting (see also --check).",
type: "boolean",
},
"log-level": {
choices: ["silent", "error", "warn", "log", "debug"],
default: "log",
description: "What level of logs to report.",
type: "choice",
},
"plugin-search": {
oppositeDescription: "Disable plugin autoloading.",
type: "boolean",
},
"support-info": {
description: "Print support information as JSON.",
type: "boolean",
},
version: {
alias: "v",
description: "Print Prettier version.",
type: "boolean",
},
"with-node-modules": {
category: coreOptions.CATEGORY_CONFIG,
description: "Process files inside 'node_modules' directory.",
type: "boolean",
},
write: {
alias: "w",
category: coreOptions.CATEGORY_OUTPUT,
description: "Edit files in-place. (Beware!)",
type: "boolean",
},
};
const usageSummary = outdent`
Usage: prettier [options] [file/dir/glob ...]
@@ -257,4 +16,4 @@ const usageSummary = outdent`
Stdin is read if it is piped to Prettier and no files are given.
`;
export { categoryOrder, options, usageSummary };
export { categoryOrder, usageSummary };
+8 -8
View File
@@ -1,22 +1,21 @@
import dashify from "dashify";
import { getSupportInfo } from "../../index.js";
import {
coreOptions,
optionCategories,
getSupportInfoWithoutPlugins,
normalizeOptionSettings,
} from "../prettier-internal.js";
import { options as cliOptionsMap } from "../constants.evaluate.js";
import { arrayify } from "../utils.js";
import cliOptions from "../cli-options.evaluate.js";
const detailedCliOptions = arrayify(cliOptionsMap, "name").map((option) =>
const detailedCliOptions = normalizeOptionSettings(cliOptions).map((option) =>
normalizeDetailedOption(option)
);
function apiOptionToCliOption(apiOption) {
const cliOption = {
...apiOption,
name: apiOption.cliName || dashify(apiOption.name),
description: apiOption.cliDescription || apiOption.description,
category: apiOption.cliCategory || coreOptions.CATEGORY_FORMAT,
description: apiOption.cliDescription ?? apiOption.description,
category: apiOption.cliCategory ?? optionCategories.CATEGORY_FORMAT,
forwardToApi: apiOption.name,
};
@@ -34,8 +33,9 @@ function apiOptionToCliOption(apiOption) {
function normalizeDetailedOption(option) {
return {
category: coreOptions.CATEGORY_OTHER,
category: optionCategories.CATEGORY_OTHER,
...option,
name: option.cliName ?? dashify(option.name),
choices: option.choices?.map((choice) => {
const newChoice = {
description: "",
+2 -1
View File
@@ -3,11 +3,12 @@ import { __internal as sharedWithCli } from "../index.js";
export const {
errors,
coreOptions,
optionCategories,
createIsIgnoredFunction,
formatOptionsHiddenDefaults,
normalizeOptions,
getSupportInfoWithoutPlugins,
normalizeOptionSettings,
vnopts,
fastGlob,
} = sharedWithCli;
+1 -2
View File
@@ -89,8 +89,7 @@ const normalizeToPosix =
? (filepath) => filepath.replaceAll("\\", "/")
: (filepath) => filepath;
export const { arrayify, isNonEmptyArray, partition, omit } =
sharedWithCli.utils;
export const { isNonEmptyArray, partition, omit } = sharedWithCli.utils;
export {
printToScreen,
groupBy,
File renamed without changes.
+7 -5
View File
@@ -1,7 +1,10 @@
import vnopts from "vnopts";
import fastGlob from "fast-glob";
import * as core from "./main/core.js";
import { getSupportInfo as getSupportInfoWithoutPlugins } from "./main/support.js";
import {
getSupportInfo as getSupportInfoWithoutPlugins,
normalizeOptionSettings,
} from "./main/support.js";
import getFileInfoWithoutPlugins from "./common/get-file-info.js";
import {
loadBuiltinPlugins,
@@ -15,11 +18,10 @@ import {
clearCache as clearConfigCache,
} from "./config/resolve-config.js";
import * as errors from "./common/errors.js";
import * as coreOptions from "./main/core-options.evaluate.js";
import * as optionCategories from "./main/option-categories.js";
import { createIsIgnoredFunction } from "./utils/ignore.js";
import { formatOptionsHiddenDefaults } from "./main/normalize-format-options.js";
import normalizeOptions from "./main/normalize-options.js";
import arrayify from "./utils/arrayify.js";
import partition from "./utils/partition.js";
import isNonEmptyArray from "./utils/is-non-empty-array.js";
import omit from "./utils/object-omit.js";
@@ -84,15 +86,15 @@ const getSupportInfo = withPlugins(getSupportInfoWithoutPlugins, 0);
// Internal shared with cli
const sharedWithCli = {
errors,
coreOptions,
optionCategories,
createIsIgnoredFunction,
formatOptionsHiddenDefaults,
normalizeOptions,
getSupportInfoWithoutPlugins,
normalizeOptionSettings,
vnopts,
fastGlob,
utils: {
arrayify,
isNonEmptyArray,
partition,
omit,
+1 -1
View File
@@ -1,4 +1,4 @@
import commonOptions from "../common/common-options.js";
import commonOptions from "../common/common-options.evaluate.js";
// format based on https://github.com/prettier/prettier/blob/main/src/main/core-options.evaluate.js
const options = {
+1 -1
View File
@@ -1,4 +1,4 @@
import commonOptions from "../common/common-options.js";
import commonOptions from "../common/common-options.evaluate.js";
// format based on https://github.com/prettier/prettier/blob/main/src/main/core-options.evaluate.js
const options = {
+1 -1
View File
@@ -1,4 +1,4 @@
import commonOptions from "../common/common-options.js";
import commonOptions from "../common/common-options.evaluate.js";
const CATEGORY_HTML = "HTML";
+1 -1
View File
@@ -1,4 +1,4 @@
import commonOptions from "../common/common-options.js";
import commonOptions from "../common/common-options.evaluate.js";
const CATEGORY_JAVASCRIPT = "JavaScript";
+1 -1
View File
@@ -1,4 +1,4 @@
import commonOptions from "../common/common-options.js";
import commonOptions from "../common/common-options.evaluate.js";
// format based on https://github.com/prettier/prettier/blob/main/src/main/core-options.evaluate.js
const options = {
+1 -1
View File
@@ -1,4 +1,4 @@
import commonOptions from "../common/common-options.js";
import commonOptions from "../common/common-options.evaluate.js";
// format based on https://github.com/prettier/prettier/blob/main/src/main/core-options.evaluate.js
const options = {
+8 -18
View File
@@ -1,12 +1,11 @@
import { outdent } from "outdent";
const CATEGORY_CONFIG = "Config";
const CATEGORY_EDITOR = "Editor";
const CATEGORY_FORMAT = "Format";
const CATEGORY_OTHER = "Other";
const CATEGORY_OUTPUT = "Output";
const CATEGORY_GLOBAL = "Global";
const CATEGORY_SPECIAL = "Special";
import {
CATEGORY_CONFIG,
CATEGORY_EDITOR,
CATEGORY_OTHER,
CATEGORY_GLOBAL,
CATEGORY_SPECIAL,
} from "./option-categories.js";
/**
* @typedef {Object} OptionInfo
@@ -235,13 +234,4 @@ const options = {
},
};
export {
CATEGORY_CONFIG,
CATEGORY_EDITOR,
CATEGORY_FORMAT,
CATEGORY_OTHER,
CATEGORY_OUTPUT,
CATEGORY_GLOBAL,
CATEGORY_SPECIAL,
options,
};
export default options;
+7
View File
@@ -0,0 +1,7 @@
export const CATEGORY_CONFIG = "Config";
export const CATEGORY_EDITOR = "Editor";
export const CATEGORY_FORMAT = "Format";
export const CATEGORY_OTHER = "Other";
export const CATEGORY_OUTPUT = "Output";
export const CATEGORY_GLOBAL = "Global";
export const CATEGORY_SPECIAL = "Special";
+37 -25
View File
@@ -1,4 +1,4 @@
import { options as coreOptions } from "./core-options.evaluate.js";
import coreOptions from "./core-options.evaluate.js";
/**
* @typedef {import("./core-options.evaluate.js").OptionInfo} OptionInfo
@@ -18,32 +18,24 @@ function getSupportInfo({ plugins = [], showDeprecated = false } = {}) {
const languages = plugins.flatMap((plugin) => plugin.languages ?? []);
const options = [];
for (const [name, originalOption] of Object.entries(
for (const option of normalizeOptionSettings(
Object.assign({}, ...plugins.map(({ options }) => options), coreOptions)
)) {
if (!showDeprecated && originalOption.deprecated) {
if (!showDeprecated && option.deprecated) {
continue;
}
const option = { name, ...originalOption };
// This work this way because we used support `[{value: [], since: '0.0.0'}]`
if (Array.isArray(option.default)) {
option.default = option.default.at(-1).value;
}
if (Array.isArray(option.choices)) {
const choices = showDeprecated
? [...option.choices]
: option.choices.filter((choice) => !choice.deprecated);
if (option.name === "parser") {
choices.push(
...collectParsersFromLanguages(choices, languages, plugins)
);
if (!showDeprecated) {
option.choices = option.choices.filter((choice) => !choice.deprecated);
}
option.choices = choices;
if (option.name === "parser") {
option.choices = [
...option.choices,
...collectParsersFromLanguages(option.choices, languages, plugins),
];
}
}
option.pluginDefaults = Object.fromEntries(
@@ -63,19 +55,39 @@ function* collectParsersFromLanguages(parserChoices, languages, plugins) {
for (const language of languages) {
if (language.parsers) {
for (const value of language.parsers) {
if (!existingParsers.has(value)) {
existingParsers.add(value);
const plugin = plugins.find((plugin) => plugin.parsers?.[value]);
for (const parserName of language.parsers) {
if (!existingParsers.has(parserName)) {
existingParsers.add(parserName);
const plugin = plugins.find(
(plugin) =>
plugin.parsers && Object.hasOwn(plugin.parsers, parserName)
);
let description = language.name;
if (plugin?.name) {
description += ` (plugin: ${plugin.name})`;
}
yield { value, description };
yield { value: parserName, description };
}
}
}
}
}
export { getSupportInfo };
function normalizeOptionSettings(settings) {
const options = [];
for (const [name, originalOption] of Object.entries(settings)) {
const option = { name, ...originalOption };
// This work this way because we used support `[{value: [], since: '0.0.0'}]`
if (Array.isArray(option.default)) {
option.default = option.default.at(-1).value;
}
options.push(option);
}
return options;
}
export { getSupportInfo, normalizeOptionSettings };
-7
View File
@@ -1,7 +0,0 @@
const arrayify = (object, keyName) =>
Object.entries(object).map(([key, value]) => ({
[keyName]: key,
...value,
}));
export default arrayify;
+2 -4
View File
@@ -5,7 +5,7 @@ import fastGlob from "fast-glob";
import { projectRoot } from "../env.js";
import prettier from "../../config/prettier-entry.js";
import createSandBox from "../../config/utils/create-sandbox.cjs";
import * as coreOptions from "../../../src/main/core-options.evaluate.js";
import coreOptions from "../../../src/main/core-options.evaluate.js";
import codeSamples from "../../../website/playground/codeSamples.mjs";
import jestPathSerializer from "../path-serializer.js";
@@ -13,9 +13,7 @@ const { require, importModule } = createEsmUtils(import.meta);
expect.addSnapshotSerializer(jestPathSerializer);
const parserNames = coreOptions.options.parser.choices.map(
({ value }) => value
);
const parserNames = coreOptions.parser.choices.map(({ value }) => value);
const distDirectory = path.join(projectRoot, "dist");
// Files including U+FFEE can't load in Chrome Extension