Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(support-info): add options field #3433

Merged
merged 17 commits into from Dec 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -22,6 +22,7 @@
"cjk-regex": "1.0.2",
"cosmiconfig": "3.1.0",
"dashify": "0.2.2",
"dedent": "0.7.0",
"diff": "3.2.0",
"editorconfig": "0.14.2",
"editorconfig-to-prettier": "0.0.6",
Expand Down Expand Up @@ -80,6 +81,7 @@
"rollup-plugin-node-resolve": "2.0.0",
"rollup-plugin-replace": "1.2.1",
"shelljs": "0.7.8",
"snapshot-diff": "0.2.2",
"strip-ansi": "4.0.0",
"sw-toolbox": "3.6.0",
"uglify-es": "3.0.28",
Expand Down
44 changes: 20 additions & 24 deletions src/cli/constant.js
@@ -1,6 +1,7 @@
"use strict";

const camelCase = require("camelcase");
const dedent = require("dedent");

const CATEGORY_CONFIG = "Config";
const CATEGORY_EDITOR = "Editor";
Expand Down Expand Up @@ -135,10 +136,10 @@ const detailedOptions = normalizeDetailedOptions({
},
{
value: "prefer-file",
description: dedent(`
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:
Expand All @@ -149,10 +150,10 @@ const detailedOptions = normalizeDetailedOptions({
category: CATEGORY_EDITOR,
exception: -1,
forwardToApi: true,
description: dedent(`
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"
Expand Down Expand Up @@ -183,10 +184,10 @@ const detailedOptions = normalizeDetailedOptions({
help: {
type: "flag",
alias: "h",
description: dedent(`
description: dedent`
Show CLI usage, or details about the given flag.
Example: --help write
`)
`
},
"ignore-path": {
type: "path",
Expand All @@ -197,9 +198,9 @@ const detailedOptions = normalizeDetailedOptions({
"insert-pragma": {
type: "boolean",
forwardToApi: true,
description: dedent(`
description: dedent`
Insert @format pragma into file's first docblock comment.
`)
`
},
"jsx-bracket-same-line": {
type: "boolean",
Expand Down Expand Up @@ -276,29 +277,29 @@ const detailedOptions = normalizeDetailedOptions({
category: CATEGORY_EDITOR,
forwardToApi: true,
exception: Infinity,
description: dedent(`
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(`
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(`
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",
Expand Down Expand Up @@ -399,17 +400,12 @@ const minimistOptions = {
)
};

const usageSummary = `
Usage: prettier [options] [file/glob ...]
const usageSummary = dedent`
Usage: prettier [options] [file/glob ...]

By default, output is written to stdout.
Stdin is read if it is piped to Prettier and no files are given.
`.trim();

function dedent(str) {
const spaces = str.match(/\n^( +)/m)[1].length;
return str.replace(new RegExp(`^ {${spaces}}`, "gm"), "").trim();
}
By default, output is written to stdout.
Stdin is read if it is piped to Prettier and no files are given.
`;

function normalizeDetailedOptions(rawDetailedOptions) {
const names = Object.keys(rawDetailedOptions).sort();
Expand Down
238 changes: 235 additions & 3 deletions src/common/support.js
@@ -1,17 +1,225 @@
"use strict";

const util = require("./util");
const dedent = require("dedent");
const semver = require("semver");
const currentVersion = require("../../package.json").version;
const loadPlugins = require("./load-plugins");

function getSupportInfo(version, options) {
const CATEGORY_GLOBAL = "Global";
const CATEGORY_SPECIAL = "Special";

/**
* @typedef {Object} OptionInfo
* @property {string} since - available since version
* @property {string} category
* @property {'int' | 'boolean' | 'choice' | 'path'} type
* @property {boolean?} deprecated - deprecated since version
* @property {OptionRedirectInfo?} redirect - redirect deprecated option
* @property {string} description
* @property {string?} oppositeDescription - for `false` option
* @property {OptionValueInfo} default
* @property {OptionRangeInfo?} range - for type int
* @property {OptionChoiceInfo?} choices - for type choice
*
* @typedef {number | boolean | string} OptionValue
* @typedef {OptionValue | Array<{ since: string, value: OptionValue}>} OptionValueInfo
*
* @typedef {Object} OptionRedirectInfo
* @property {string} option
* @property {OptionValue} value
*
* @typedef {Object} OptionRangeInfo
* @property {number} start - recommended range start
* @property {number} end - recommended range end
* @property {number} step - recommended range step
*
* @typedef {Object} OptionChoiceInfo
* @property {boolean | string} value - boolean for the option that is originally boolean type
* @property {string?} description - undefined if redirect
* @property {string?} since - undefined if available since the first version of the option
* @property {string?} deprecated - deprecated since version
* @property {OptionValueInfo?} redirect - redirect deprecated value
*/
/** @type {{ [name: string]: OptionInfo } */
const supportOptions = {
cursorOffset: {
since: "1.4.0",
category: CATEGORY_SPECIAL,
type: "int",
default: -1,
range: { start: -1, end: Infinity, step: 1 },
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.
`
},
filepath: {
since: "1.4.0",
category: CATEGORY_SPECIAL,
type: "path",
default: undefined,
description:
"Specify the input filepath. This will be used to do parser inference."
},
insertPragma: {
since: "1.8.0",
category: CATEGORY_SPECIAL,
type: "boolean",
default: false,
description: "Insert @format pragma into file's first docblock comment."
},
parser: {
since: "0.0.10",
category: CATEGORY_GLOBAL,
type: "choice",
default: "babylon",
description: "Which parser to use.",
choices: [
{ value: "babylon", description: "JavaScript" },
{ value: "flow", description: "Flow" },
{ value: "typescript", since: "1.4.0", description: "TypeScript" },
{ value: "css", since: "1.7.1", description: "CSS" },
{
value: "postcss",
since: "1.4.0",
description: "CSS/Less/SCSS",
deprecated: "1.7.1",
redirect: "css"
},
{ value: "less", since: "1.7.1", description: "Less" },
{ 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" }
]
},
printWidth: {
since: "0.0.0",
category: CATEGORY_GLOBAL,
type: "int",
default: 80,
description: "The line length where Prettier will try wrap.",
range: { start: 0, end: Infinity, step: 1 }
},
rangeEnd: {
since: "1.4.0",
category: CATEGORY_SPECIAL,
type: "int",
default: Infinity,
range: { start: 0, end: Infinity, step: 1 },
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.
`
},
rangeStart: {
since: "1.4.0",
category: CATEGORY_SPECIAL,
type: "int",
default: 0,
range: { start: 0, end: Infinity, step: 1 },
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.
`
},
requirePragma: {
since: "1.7.0",
category: CATEGORY_SPECIAL,
type: "boolean",
default: false,
description: dedent`
Require either '@prettier' or '@format' to be present in the file's first docblock comment
in order for it to be formatted.
`
},
tabWidth: {
type: "int",
category: CATEGORY_GLOBAL,
default: 2,
description: "Number of spaces per indentation level.",
range: { start: 0, end: Infinity, step: 1 }
},
useFlowParser: {
since: "0.0.0",
category: CATEGORY_GLOBAL,
type: "boolean",
default: false,
deprecated: "0.0.10",
description: "Use flow parser.",
redirect: { option: "parser", value: "flow" }
},
useTabs: {
since: "1.0.0",
category: CATEGORY_GLOBAL,
type: "boolean",
default: false,
description: "Indent with tabs instead of spaces."
}
};

function getSupportInfo(version, opts) {
opts = opts || {};

if (!version) {
version = currentVersion;
}

const plugins = loadPlugins();

const options = util
.arrayify(
Object.assign(
plugins
.reduce(
(currentPrinters, plugin) =>
currentPrinters.concat(
Object.keys(plugin.printers).map(
printerName => plugin.printers[printerName]
)
),
[]
)
.reduce(
(currentOptions, printer) =>
Object.assign(currentOptions, printer.options),
{}
),
supportOptions
),
"name"
)
.sort((a, b) => (a.name === b.name ? 0 : a.name < b.name ? -1 : 1))
.filter(filterSince)
.filter(filterDeprecated)
.map(mapDeprecated)
.map(option => {
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;
}

if (Array.isArray(newOption.choices)) {
newOption.choices = newOption.choices
.filter(filterSince)
.filter(filterDeprecated)
.map(mapDeprecated);
}

return newOption;
});

const usePostCssParser = semver.lt(version, "1.7.1");

const languages = loadPlugins(options)
const languages = plugins
.reduce((all, plugin) => all.concat(plugin.languages), [])
.filter(language => language.since && semver.gte(version, language.since))
.map(language => {
Expand All @@ -35,7 +243,31 @@ function getSupportInfo(version, options) {
return language;
});

return { languages };
return { languages, options };

function filterSince(object) {
return (
opts.showUnreleased ||
!("since" in object) ||
(object.since && semver.gte(version, object.since))
);
}
function filterDeprecated(object) {
return (
opts.showDeprecated ||
!("deprecated" in object) ||
(object.deprecated && semver.lt(version, object.deprecated))
);
}
function mapDeprecated(object) {
if (!object.deprecated || opts.showDeprecated) {
return object;
}
const newObject = Object.assign({}, object);
delete newObject.deprecated;
delete newObject.redirect;
return newObject;
}
}

module.exports = {
Expand Down