Implement cosmiconfig for workspace configuration (#2434)

* Implement cosmiconfig

* Add resolveOptions API and extname support

* Add --resolve-config and --config, rename resolveOptions to resolveConfig

* Move color to top-level CLI options

* Fix unknown param warning

* Change from {} to null when no config is found

* Change override API to emulate eslint

* Add test for eslint-style overrides

* Delete overrides from resolveConfig
This commit is contained in:
Lucas Azzola authored and GitHub committed 2017-07-10 22:26:36 +10:00
1 parent 3728749d92
commit dcccfed366
21 files changed
+525 -64

No files matched your search

+152 -60
View File
@@ -2,24 +2,41 @@
"use strict";
const chalk = require("chalk");
const dashify = require("dashify");
const fs = require("fs");
const getStream = require("get-stream");
const globby = require("globby");
const chalk = require("chalk");
const minimist = require("minimist");
const path = require("path");
const readline = require("readline");
const prettier = eval("require")("../index");
const cleanAST = require("../src/clean-ast.js").cleanAST;
const argv = minimist(process.argv.slice(2), {
const prettier = eval("require")("../index");
const cleanAST = require("../src/clean-ast").cleanAST;
const resolver = require("../src/resolve-config");
const args = process.argv.slice(2);
const booleanOptionNames = [
"use-tabs",
"semi",
"single-quote",
"bracket-spacing",
"jsx-bracket-same-line",
// Deprecated in 0.0.10
"flow-parser"
];
const stringOptionNames = [
"print-width",
"tab-width",
"parser",
"trailing-comma"
];
const argv = minimist(args, {
boolean: [
"write",
"stdin",
"use-tabs",
"semi",
"single-quote",
"bracket-spacing",
"jsx-bracket-same-line",
// 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.
@@ -30,31 +47,34 @@ const argv = minimist(process.argv.slice(2), {
"version",
"debug-print-doc",
"debug-check",
"with-node-modules",
// Deprecated in 0.0.10
"flow-parser"
"with-node-modules"
],
string: [
"print-width",
"tab-width",
"parser",
"trailing-comma",
"cursor-offset",
"range-start",
"range-end",
"stdin-filepath"
"stdin-filepath",
"config",
"resolve-config"
],
default: {
semi: true,
color: true,
"bracket-spacing": true,
parser: "babylon"
color: true
},
alias: {
help: "h",
version: "v",
"list-different": "l"
},
alias: { help: "h", version: "v", "list-different": "l" },
unknown: param => {
if (param.startsWith("-")) {
console.warn("Ignored unknown option: " + param + "\n");
return false;
const paramName = param.substring(2);
if (
booleanOptionNames.indexOf(paramName) === -1 &&
stringOptionNames.indexOf(paramName) === -1
) {
console.warn("Ignored unknown option: " + param + "\n");
return false;
}
}
}
});
@@ -78,9 +98,56 @@ if (write && argv["debug-check"]) {
process.exit(1);
}
function getParserOption() {
const optionName = "parser";
const value = argv[optionName];
if (argv["resolve-config"] && filepatterns.length) {
console.error("Cannot use --resolve-config with multiple files");
process.exit(1);
}
function getOptionsForFile(filePath) {
return resolver
.resolveConfig(filePath, { configFile: argv["config"] })
.then(options => {
const parsedArgs = minimist(args, {
boolean: booleanOptionNames,
string: stringOptionNames,
default: Object.assign(
{
semi: true,
"bracket-spacing": true,
parser: "babylon"
},
dashifyObject(options)
)
});
return getOptions(Object.assign({}, argv, parsedArgs));
})
.catch(error => {
console.error("Invalid configuration file:", error.toString());
process.exit(2);
});
}
function getOptions(argv) {
return {
cursorOffset: getIntOption(argv, "cursor-offset"),
rangeStart: getIntOption(argv, "range-start"),
rangeEnd: getIntOption(argv, "range-end"),
useTabs: argv["use-tabs"],
semi: argv["semi"],
printWidth: getIntOption(argv, "print-width"),
tabWidth: getIntOption(argv, "tab-width"),
bracketSpacing: argv["bracket-spacing"],
singleQuote: argv["single-quote"],
jsxBracketSameLine: argv["jsx-bracket-same-line"],
filepath: argv["stdin-filepath"],
trailingComma: getTrailingComma(argv),
parser: getParserOption(argv)
};
}
function getParserOption(argv) {
const value = argv.parser;
if (value === undefined) {
return value;
@@ -95,7 +162,7 @@ function getParserOption() {
return value;
}
function getIntOption(optionName) {
function getIntOption(argv, optionName) {
const value = argv[optionName];
if (value === undefined) {
@@ -115,7 +182,7 @@ function getIntOption(optionName) {
process.exit(1);
}
function getTrailingComma() {
function getTrailingComma(argv) {
switch (argv["trailing-comma"]) {
case undefined:
case "none":
@@ -135,21 +202,12 @@ function getTrailingComma() {
}
}
const options = {
cursorOffset: getIntOption("cursor-offset"),
rangeStart: getIntOption("range-start"),
rangeEnd: getIntOption("range-end"),
useTabs: argv["use-tabs"],
semi: argv["semi"],
printWidth: getIntOption("print-width"),
tabWidth: getIntOption("tab-width"),
bracketSpacing: argv["bracket-spacing"],
singleQuote: argv["single-quote"],
jsxBracketSameLine: argv["jsx-bracket-same-line"],
filepath: argv["stdin-filepath"],
trailingComma: getTrailingComma(),
parser: getParserOption()
};
function dashifyObject(object) {
return Object.keys(object || {}).reduce((output, key) => {
output[dashify(key)] = object[key];
return output;
}, {});
}
function diff(a, b) {
return require("diff").createTwoFilesPatch("", "", a, b, "", "", {
@@ -214,12 +272,17 @@ function handleError(filename, e) {
process.exitCode = 2;
}
if (argv["help"] || (!filepatterns.length && !stdin)) {
if (
argv["help"] ||
(!filepatterns.length && !stdin && !argv["resolve-config"])
) {
console.log(
"Usage: prettier [opts] [filename ...]\n\n" +
"Available options:\n" +
" --write Edit the file in-place. (Beware!)\n" +
" --list-different or -l Print filenames of files that are different from Prettier formatting.\n" +
" --config Path to a prettier configuration file (.prettierrc, package.json, prettier.config.js).\n" +
" --resolve-config <path> Resolve the path to a configuration file for a given input file.\n" +
" --stdin Read input from stdin.\n" +
" --stdin-filepath Path to the file used to read from stdin.\n" +
" --print-width <int> Specify the length of line that the printer will wrap on. Defaults to 80.\n" +
@@ -251,20 +314,24 @@ if (argv["help"] || (!filepatterns.length && !stdin)) {
process.exit(argv["help"] ? 0 : 1);
}
if (stdin) {
if (argv["resolve-config"]) {
resolveConfig(argv["resolve-config"]);
} else if (stdin) {
getStream(process.stdin).then(input => {
if (listDifferent(input, options, "(stdin)")) {
return;
}
getOptionsForFile(process.cwd()).then(options => {
if (listDifferent(input, options, "(stdin)")) {
return;
}
try {
writeOutput(format(input, options));
} catch (e) {
handleError("stdin", e);
}
try {
writeOutput(format(input, options), options);
} catch (e) {
handleError("stdin", e);
}
});
});
} else {
eachFilename(filepatterns, filename => {
eachFilename(filepatterns, (filename, options) => {
if (write) {
// Don't use `console.log` here since we need to replace this line.
process.stdout.write(filename);
@@ -337,7 +404,7 @@ if (stdin) {
process.exitCode = 2;
}
} else if (!argv["list-different"]) {
writeOutput(result);
writeOutput(result, options);
}
});
}
@@ -359,7 +426,17 @@ function listDifferent(input, options, filename) {
return true;
}
function writeOutput(result) {
function resolveConfig(filePath) {
resolver.resolveConfigFile(filePath).then(configFile => {
if (configFile) {
console.log(path.relative(process.cwd(), configFile));
} else {
process.exitCode = 1;
}
});
}
function writeOutput(result, options) {
// Don't use `console.log` here since it adds an extra newline at the end.
process.stdout.write(result.formatted);
@@ -382,9 +459,11 @@ function eachFilename(patterns, callback) {
process.exitCode = 2;
return;
}
filePaths.forEach(filePath => {
return callback(filePath);
// Use map series to ensure idempotency
mapSeries(filePaths, filePath => {
return getOptionsForFile(filePath).then(options =>
callback(filePath, options)
);
});
})
.catch(err => {
@@ -395,3 +474,16 @@ function eachFilename(patterns, callback) {
process.exitCode = 2;
});
}
function mapSeries(array, iteratee) {
let current = Promise.resolve();
const promises = array.map((item, i) => {
current = current.then(() => {
return iteratee(item, i, array);
});
return current;
});
return Promise.all(promises);
}