From 4c9d4061daed4b8d3e84cfdd8f224a15f0881684 Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Tue, 26 Dec 2017 12:23:50 +1100 Subject: [PATCH] Prettier Plugin API (#3536) * Move files around in preparation for refactor * Update paths in build script * Extract generic printing logic from the JavaScript printer * Conform printer API * Fixup decorator handling * Fix multiparser * Create plugin entry for markdown * Create plugin entry for javascript/typescript * Create plugin entry for html * Create plugin entry for graphql * Create plugin entry for css/less/scss * Move JSON to JS plugin entry * Integrate plugins into getSupportInfo() * Move astFormat to parser definition * Move util to common * Implement parser loading * remark -> mdast * Rename cli/cli -> cli/index * Rename builder -> doc package, fix printer resolution * Fix doc shape assumption in CSS-in-JS logic * Fix third-party.js prod resolution * Fixup build-docs script * Distribute multiparser code * Remove requirement to forward options * Flatten closure * Remove debug directory * Expose doc * Add external plugins * Pass options to loadPlugins * Export getParsers * Pin resolve version * Use getSupportInfo in Markdown embed * Document plugin API * Update build-docs * Add CLI for plugins * Lint docs * Fixup build.js * Add vue language * Fixup multiparser for vue * Upgrade rollup and rollup-plugin-commonjs * Fixup third-party build * Change AST format in docs --- docs/plugins.md | 155 ++++++++ index.js | 21 +- package.json | 5 +- scripts/build/build-docs.js | 23 +- scripts/build/build.js | 21 +- scripts/build/parsers.js | 12 + scripts/build/rollup.bin.config.js | 4 +- scripts/build/rollup.index.config.js | 4 +- scripts/build/rollup.parser.config.js | 11 +- scripts/build/rollup.third-party.config.js | 2 +- src/{cli-constant.js => cli/constant.js} | 11 + src/{cli.js => cli/index.js} | 10 +- src/{cli-logger.js => cli/logger.js} | 0 src/{cli-util.js => cli/util.js} | 34 +- src/{cli-validator.js => cli/validator.js} | 2 +- src/{ => common}/clean-ast.js | 0 src/{ => common}/deprecated.js | 0 src/{ => common}/errors.js | 0 src/{ => common}/fast-path.js | 2 +- src/common/load-plugins.js | 25 ++ src/{ => common}/options.js | 7 +- src/{ => common}/parser-create-error.js | 0 src/{ => common}/parser-include-shebang.js | 0 src/common/support.js | 43 +++ src/{ => common}/third-party.js | 0 src/{ => common}/util.js | 18 +- .../resolve-config-editorconfig.js | 0 src/{ => config}/resolve-config.js | 2 +- src/{ => doc}/doc-builders.js | 0 src/{ => doc}/doc-debug.js | 0 src/{ => doc}/doc-printer.js | 2 +- src/{ => doc}/doc-utils.js | 16 + src/doc/index.js | 8 + src/language-css/index.js | 72 ++++ src/{ => language-css}/parser-postcss.js | 2 +- src/{ => language-css}/printer-postcss.js | 13 +- src/language-graphql/index.js | 38 ++ src/{ => language-graphql}/parser-graphql.js | 2 +- src/{ => language-graphql}/printer-graphql.js | 9 +- src/language-html/embed.js | 93 +++++ src/language-html/index.js | 42 +++ src/{ => language-html}/parser-parse5.js | 0 .../printer-htmlparser2.js | 11 +- src/{multiparser.js => language-js/embed.js} | 345 ++++-------------- src/language-js/index.js | 138 +++++++ src/{ => language-js}/parser-babylon.js | 2 +- src/{ => language-js}/parser-flow.js | 4 +- src/{ => language-js}/parser-typescript.js | 4 +- .../printer-estree.js} | 241 ++++-------- src/language-markdown/embed.js | 43 +++ src/language-markdown/index.js | 57 +++ .../parser-markdown.js | 2 +- .../printer-markdown.js | 15 +- src/language-vue/embed.js | 50 +++ src/language-vue/index.js | 41 +++ src/{ => language-vue}/parser-vue.js | 0 src/{ => language-vue}/printer-vue.js | 8 +- src/main/ast-to-doc.js | 100 +++++ src/{ => main}/comments.js | 4 +- src/main/get-printer.js | 20 + src/main/multiparser.js | 35 ++ src/main/parser.js | 78 ++++ src/parser.js | 83 ----- src/support.js | 229 ------------ .../__snapshots__/jsfmt.spec.js.snap | 44 +-- tests_config/run_spec.js | 4 +- .../__snapshots__/early-exit.js.snap | 13 + .../__snapshots__/support-info.js.snap | 122 +++---- tests_integration/__tests__/early-exit.js | 2 +- tests_integration/runPrettier.js | 6 +- website/sidebars.json | 1 + yarn.lock | 56 +-- 72 files changed, 1474 insertions(+), 993 deletions(-) create mode 100644 docs/plugins.md create mode 100644 scripts/build/parsers.js rename src/{cli-constant.js => cli/constant.js} (97%) rename src/{cli.js => cli/index.js} (86%) rename src/{cli-logger.js => cli/logger.js} (100%) rename src/{cli-util.js => cli/util.js} (96%) rename src/{cli-validator.js => cli/validator.js} (97%) rename src/{ => common}/clean-ast.js (100%) rename src/{ => common}/deprecated.js (100%) rename src/{ => common}/errors.js (100%) rename src/{ => common}/fast-path.js (99%) create mode 100644 src/common/load-plugins.js rename src/{ => common}/options.js (95%) rename src/{ => common}/parser-create-error.js (100%) rename src/{ => common}/parser-include-shebang.js (100%) create mode 100644 src/common/support.js rename src/{ => common}/third-party.js (100%) rename src/{ => common}/util.js (98%) rename src/{ => config}/resolve-config-editorconfig.js (100%) rename src/{ => config}/resolve-config.js (98%) rename src/{ => doc}/doc-builders.js (100%) rename src/{ => doc}/doc-debug.js (100%) rename src/{ => doc}/doc-printer.js (99%) rename src/{ => doc}/doc-utils.js (91%) create mode 100644 src/doc/index.js create mode 100644 src/language-css/index.js rename src/{ => language-css}/parser-postcss.js (99%) rename src/{ => language-css}/printer-postcss.js (98%) create mode 100644 src/language-graphql/index.js rename src/{ => language-graphql}/parser-graphql.js (95%) rename src/{ => language-graphql}/printer-graphql.js (98%) create mode 100644 src/language-html/embed.js create mode 100644 src/language-html/index.js rename src/{ => language-html}/parser-parse5.js (100%) rename src/{ => language-html}/printer-htmlparser2.js (91%) rename src/{multiparser.js => language-js/embed.js} (55%) create mode 100644 src/language-js/index.js rename src/{ => language-js}/parser-babylon.js (96%) rename src/{ => language-js}/parser-flow.js (85%) rename src/{ => language-js}/parser-typescript.js (92%) rename src/{printer.js => language-js/printer-estree.js} (97%) create mode 100644 src/language-markdown/embed.js create mode 100644 src/language-markdown/index.js rename src/{ => language-markdown}/parser-markdown.js (98%) rename src/{ => language-markdown}/printer-markdown.js (98%) create mode 100644 src/language-vue/embed.js create mode 100644 src/language-vue/index.js rename src/{ => language-vue}/parser-vue.js (100%) rename src/{ => language-vue}/printer-vue.js (77%) create mode 100644 src/main/ast-to-doc.js rename src/{ => main}/comments.js (99%) create mode 100644 src/main/get-printer.js create mode 100644 src/main/multiparser.js create mode 100644 src/main/parser.js delete mode 100644 src/parser.js delete mode 100644 src/support.js diff --git a/docs/plugins.md b/docs/plugins.md new file mode 100644 index 00000000..7738a3c3 --- /dev/null +++ b/docs/plugins.md @@ -0,0 +1,155 @@ +--- +id: plugins +title: Plugins +--- + +# IN DEVELOPMENT + +> The plugin API is unreleased and the API may change! + +Plugins are ways of adding new languages to Prettier. Prettier's own implementations of all languages are expressed using the plugin API. The core `prettier` package contains JavaScript and other web-focussed languages built in. For additional languages you'll need to install a plugin. + +## Using Plugins + +There are three ways to add plugins to Prettier: + +* Via the CLI. +* Via the API. +* With a configuration file. + +### Configuration File (Recommended) + +In your [configuration file](./configuration.md), add the `plugins` property: + +```json +{ + "plugins": ["prettier-python"] +} +``` + +### CLI + +With the [CLI](./cli.md), pass the `--plugin` flag: + +```bash +prettier --write main.py --plugin prettier-python +``` + +> Tip: You can pass multiple `--plugin` flags. + +## Official Plugins + +* [`prettier-python`](https://github.com/prettier/prettier-python) +* [`prettier-php`](https://github.com/prettier/prettier-php) + +## Developing Plugins + +Prettier plugins are regular JavaScript modules with three exports, `languages`, `parsers` and `printers`. + +### `languages` + +Languages is an array of language definitions that your plugin will contribute to Prettier. It can include all of the fields specified in [`prettier.getSupportInfo()`](./api.md#prettiergetsupportinfo-version). + +It **must** include `name` and `parsers`. + +```js +export const languages = [ + { + // The language name + name: "InterpretedDanceScript", + // Parsers that can parse this language. + // This can be built-in parsers, or parsers you have contributed via this plugin. + parsers: ["dance-parse"] + } +]; +``` + +### `parsers` + +Parsers convert code as a string into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree). + +The key must match the name in the `parsers` array from `languages`. The value contains a parse function and an AST format name. + +```js +export const parsers = { + "dance-parse": { + parse, + // The name of the AST that + astFormat: "dance-ast" + } +}; +``` + +The signature of the `parse` function is: + +```ts +function parse(text: string, parsers: object, options: object): AST; +``` + +### `printers` + +Printers convert ASTs into a Prettier intermediate representation, also known as a Doc. + +The key must match the `astFormat` that the parser produces. The value contains an object with a `print` function and (optionally) an `embed` function. + +```js +export const printers = { + "dance-ast": { + print, + embed + } +}; +``` + +Printing is a recursive process of coverting an AST node (represented by a path to that node) into a doc. The doc is constructed using the [builder commands](https://github.com/prettier/prettier/blob/master/commands.md): + +```js +const { concat, join, line, ifBreak, group } = require("prettier").doc.builders; +``` + +The signature of the `print` function is: + +```ts +function print( + // Path to the AST node to print + path: FastPath, + options: object, + // Recursively print a child node + print: (path: FastPath) => Doc +): Doc; +``` + +Check out [prettier-python's printer](https://github.com/prettier/prettier-python/blob/034ba8a9551f3fa22cead41b323be0b28d06d13b/src/printer.js#L174) as an example. + +Embedding refers to printing one language inside another. Examples of this are CSS-in-JS and Markdown code blocks. Plugins can switch to alternate languages using the `embed` function. Its signature is: + +```ts +function embed( + // Path to the current AST node + path: FastPath, + // Print a node with the current printer + print: (path: FastPath) => Doc, + // Parse and print some text using a different parser. + // You should set `options.parser` to specify which parser to use. + textToDoc: (text: string, options: object) => Doc, + // Current options + options: object +): Doc | null; +``` + +If you don't want to switch to a different parser, simply return `null` or `undefined`. + +## Testing Plugins + +Since plugins can be resolved using relative paths, when working on one you can do: + +```js +const prettier = require("prettier"); +const code = "(add 1 2)"; +prettier.format(code, { + parser: "lisp", + plugins: ["."] +}); +``` + +This will resolve a plugin relative to the current working direcrory. diff --git a/index.js b/index.js index 23f0fbfd..ea23ffb0 100644 --- a/index.js +++ b/index.js @@ -1,15 +1,16 @@ "use strict"; -const comments = require("./src/comments"); +const comments = require("./src/main/comments"); const version = require("./package.json").version; -const printAstToDoc = require("./src/printer").printAstToDoc; -const util = require("./src/util"); -const printDocToString = require("./src/doc-printer").printDocToString; -const normalizeOptions = require("./src/options").normalize; -const parser = require("./src/parser"); -const printDocToDebug = require("./src/doc-debug").printDocToDebug; -const config = require("./src/resolve-config"); -const getSupportInfo = require("./src/support").getSupportInfo; +const printAstToDoc = require("./src/main/ast-to-doc"); +const util = require("./src/common/util"); +const doc = require("./src/doc"); +const printDocToString = doc.printer.printDocToString; +const printDocToDebug = doc.debug.printDocToDebug; +const normalizeOptions = require("./src/common/options").normalize; +const parser = require("./src/main/parser"); +const config = require("./src/config/resolve-config"); +const getSupportInfo = require("./src/common/support").getSupportInfo; const docblock = require("jest-docblock"); function guessLineEnding(text) { @@ -385,6 +386,8 @@ module.exports = { } }, + doc, + resolveConfig: config.resolveConfig, clearConfigCache: config.clearCache, diff --git a/package.json b/package.json index af9eb519..834d2fb6 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "postcss-values-parser": "1.3.1", "remark-frontmatter": "1.1.0", "remark-parse": "4.0.0", + "resolve": "1.5.0", "semver": "5.4.1", "string-width": "2.1.1", "typescript": "2.6.2", @@ -70,8 +71,8 @@ "prettier": "1.9.2", "prettylint": "1.0.0", "rimraf": "2.6.2", - "rollup": "0.41.6", - "rollup-plugin-commonjs": "7.0.2", + "rollup": "0.47.6", + "rollup-plugin-commonjs": "8.2.6", "rollup-plugin-json": "2.1.1", "rollup-plugin-node-builtins": "2.0.0", "rollup-plugin-node-globals": "1.1.0", diff --git a/scripts/build/build-docs.js b/scripts/build/build-docs.js index 5aff131b..abba57ce 100644 --- a/scripts/build/build-docs.js +++ b/scripts/build/build-docs.js @@ -4,19 +4,12 @@ const path = require("path"); const shell = require("shelljs"); +const parsers = require("./parsers"); const rootDir = path.join(__dirname, "..", ".."); const docs = path.join(rootDir, "website/static/lib"); -const parsers = [ - "babylon", - "flow", - "typescript", - "graphql", - "postcss", - "parse5", - "markdown", - "vue" -]; + +const stripLanguageDirectory = parserPath => parserPath.replace(/.*\//, ""); function pipe(string) { return new shell.ShellString(string); @@ -25,6 +18,8 @@ function pipe(string) { const isPullRequest = process.env.PULL_REQUEST === "true"; const prettierPath = isPullRequest ? "dist" : "node_modules/prettier/"; +const parserPaths = parsers.map(stripLanguageDirectory); + // --- Build prettier for PR --- if (isPullRequest) { @@ -44,7 +39,6 @@ shell.exec( `node_modules/babel-cli/bin/babel.js ${docs}/index.js --out-file ${docs}/index.js --presets=es2015` ); -shell.echo("Bundling docs babylon..."); shell.exec( `rollup -c scripts/build/rollup.docs.config.js --environment filepath:parser-babylon.js -i ${prettierPath}/parser-babylon.js` ); @@ -52,13 +46,12 @@ shell.exec( `node_modules/babel-cli/bin/babel.js ${docs}/parser-babylon.js --out-file ${docs}/parser-babylon.js --presets=es2015` ); -for (const parser of parsers) { - if (parser === "babylon") { +for (const parser of parserPaths) { + if (parser.endsWith("babylon")) { continue; } - shell.echo(`Bundling docs ${parser}...`); shell.exec( - `rollup -c scripts/build/rollup.docs.config.js --environment filepath:parser-${parser}.js -i ${prettierPath}/parser-${parser}.js` + `rollup -c scripts/build/rollup.docs.config.js --environment filepath:${parser}.js -i ${prettierPath}/${parser}.js` ); } diff --git a/scripts/build/build.js b/scripts/build/build.js index 7668d18f..7a52a633 100755 --- a/scripts/build/build.js +++ b/scripts/build/build.js @@ -5,19 +5,10 @@ const path = require("path"); const pkg = require("../../package.json"); const formatMarkdown = require("../../website/static/markdown"); +const parsers = require("./parsers"); const shell = require("shelljs"); const rootDir = path.join(__dirname, "..", ".."); -const parsers = [ - "babylon", - "flow", - "typescript", - "graphql", - "postcss", - "parse5", - "markdown", - "vue" -]; process.env.PATH += path.delimiter + path.join(rootDir, "node_modules", ".bin"); @@ -32,30 +23,26 @@ shell.rm("-Rf", "dist/"); // --- Lib --- -shell.echo("Bundling lib index..."); shell.exec("rollup -c scripts/build/rollup.index.config.js"); -shell.echo("Bundling lib bin..."); shell.exec("rollup -c scripts/build/rollup.bin.config.js"); shell.chmod("+x", "./dist/bin/prettier.js"); -shell.echo("Bundling lib third-party..."); shell.exec("rollup -c scripts/build/rollup.third-party.config.js"); for (const parser of parsers) { - if (parser === "postcss") { + if (parser.endsWith("postcss")) { continue; } - shell.echo(`Bundling lib ${parser}...`); shell.exec( `rollup -c scripts/build/rollup.parser.config.js --environment parser:${parser}` ); } -shell.echo("Bundling lib postcss..."); +shell.echo("\nsrc/language-css/parser-postcss.js → dist/parser-postcss.js"); // PostCSS has dependency cycles and won't work correctly with rollup :( shell.exec( - "webpack --hide-modules src/parser-postcss.js dist/parser-postcss.js" + "webpack --hide-modules src/language-css/parser-postcss.js dist/parser-postcss.js" ); // Prepend module.exports = const content = shell.cat("dist/parser-postcss.js").stdout; diff --git a/scripts/build/parsers.js b/scripts/build/parsers.js new file mode 100644 index 00000000..e888bf1b --- /dev/null +++ b/scripts/build/parsers.js @@ -0,0 +1,12 @@ +"use strict"; + +module.exports = [ + "language-js/parser-babylon", + "language-js/parser-flow", + "language-js/parser-typescript", + "language-graphql/parser-graphql", + "language-css/parser-postcss", + "language-html/parser-parse5", + "language-markdown/parser-markdown", + "language-vue/parser-vue" +]; diff --git a/scripts/build/rollup.bin.config.js b/scripts/build/rollup.bin.config.js index eeacfaac..1844407e 100644 --- a/scripts/build/rollup.bin.config.js +++ b/scripts/build/rollup.bin.config.js @@ -24,9 +24,9 @@ export default Object.assign(baseConfig, { "assert", "util", "events", - path.resolve("src/third-party.js") + path.resolve("src/common/third-party.js") ], paths: { - [path.resolve("src/third-party.js")]: "../third-party" + [path.resolve("src/common/third-party.js")]: "../third-party" } }); diff --git a/scripts/build/rollup.index.config.js b/scripts/build/rollup.index.config.js index 1304995d..80a8f9c1 100644 --- a/scripts/build/rollup.index.config.js +++ b/scripts/build/rollup.index.config.js @@ -8,7 +8,7 @@ import * as path from "path"; const external = ["assert"]; if (process.env.BUILD_TARGET !== "website") { - external.push(path.resolve("src/third-party.js")); + external.push(path.resolve("src/common/third-party.js")); } export default Object.assign(baseConfig, { @@ -25,6 +25,6 @@ export default Object.assign(baseConfig, { ], external, paths: { - [path.resolve("src/third-party.js")]: "./third-party" + [path.resolve("src/common/third-party.js")]: "./third-party" } }); diff --git a/scripts/build/rollup.parser.config.js b/scripts/build/rollup.parser.config.js index 71f387fe..2d5f25c2 100644 --- a/scripts/build/rollup.parser.config.js +++ b/scripts/build/rollup.parser.config.js @@ -4,15 +4,16 @@ import commonjs from "rollup-plugin-commonjs"; import json from "rollup-plugin-json"; import replace from "rollup-plugin-replace"; import uglify from "uglify-es"; +import path from "path"; const parser = process.env.parser; export default Object.assign(baseConfig, { - entry: "src/parser-" + parser + ".js", - dest: "dist/parser-" + parser + ".js", + entry: "src/" + parser + ".js", + dest: "dist/" + path.basename(parser) + ".js", format: "cjs", plugins: [ - parser === "typescript" + parser.endsWith("typescript") ? replace({ "exports.Syntax =": "1,", include: "node_modules/typescript-eslint-parser/parser.js" @@ -21,7 +22,7 @@ export default Object.assign(baseConfig, { // In flow-parser 0.59.0 there's a dynamic require: `require(s8)` which not // supported by rollup-plugin-commonjs, so we have to replace the variable // by its value before bundling. - parser === "flow" + parser.endsWith("flow") ? replace({ "require(s8)": 'require("fs")', include: "node_modules/flow-parser/flow_parser.js" @@ -50,5 +51,5 @@ export default Object.assign(baseConfig, { "os", "crypto" ], - useStrict: parser !== "flow" + useStrict: !parser.endsWith("flow") }); diff --git a/scripts/build/rollup.third-party.config.js b/scripts/build/rollup.third-party.config.js index 7f0ff277..6d80163f 100644 --- a/scripts/build/rollup.third-party.config.js +++ b/scripts/build/rollup.third-party.config.js @@ -5,7 +5,7 @@ import json from "rollup-plugin-json"; import replace from "rollup-plugin-replace"; export default Object.assign(baseConfig, { - entry: "src/third-party.js", + entry: "src/common/third-party.js", dest: "dist/third-party.js", format: "cjs", plugins: [ diff --git a/src/cli-constant.js b/src/cli/constant.js similarity index 97% rename from src/cli-constant.js rename to src/cli/constant.js index 5cda091c..902bff79 100644 --- a/src/cli-constant.js +++ b/src/cli/constant.js @@ -47,6 +47,9 @@ const categoryOrder = [ * // 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 . * // Use an object instead of a string if a choice is deprecated and should @@ -238,6 +241,14 @@ const detailedOptions = normalizeDetailedOptions({ 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, diff --git a/src/cli.js b/src/cli/index.js similarity index 86% rename from src/cli.js rename to src/cli/index.js index 42122e52..3e17efcb 100644 --- a/src/cli.js +++ b/src/cli/index.js @@ -2,11 +2,11 @@ const minimist = require("minimist"); -const prettier = eval("require")("../index"); -const constant = require("./cli-constant"); -const util = require("./cli-util"); -const validator = require("./cli-validator"); -const logger = require("./cli-logger"); +const prettier = eval("require")("../../index"); +const constant = require("./constant"); +const util = require("./util"); +const validator = require("./validator"); +const logger = require("./logger"); function run(args) { const rawArgv = minimist(args, constant.minimistOptions); diff --git a/src/cli-logger.js b/src/cli/logger.js similarity index 100% rename from src/cli-logger.js rename to src/cli/logger.js diff --git a/src/cli-util.js b/src/cli/util.js similarity index 96% rename from src/cli-util.js rename to src/cli/util.js index efeab2e5..421a6834 100644 --- a/src/cli-util.js +++ b/src/cli/util.js @@ -11,28 +11,30 @@ const chalk = require("chalk"); const readline = require("readline"); const leven = require("leven"); -const prettier = eval("require")("../index"); -const cleanAST = require("./clean-ast").cleanAST; -const resolver = require("./resolve-config"); -const constant = require("./cli-constant"); -const validator = require("./cli-validator"); -const apiDefaultOptions = require("./options").defaults; -const errors = require("./errors"); -const logger = require("./cli-logger"); -const thirdParty = require("./third-party"); +const prettier = eval("require")("../../index"); +const cleanAST = require("../common/clean-ast").cleanAST; +const resolver = require("../config/resolve-config"); +const constant = require("./constant"); +const validator = require("./validator"); +const apiDefaultOptions = require("../common/options").defaults; +const errors = require("../common/errors"); +const logger = require("./logger"); +const thirdParty = require("../common/third-party"); 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]: argv[option.name] }), - {} - ); + return constant.detailedOptions.filter(option => option.forwardToApi).reduce( + (current, option) => + Object.assign(current, { + [option.forwardToApi]: option.array + ? [].concat(argv[option.name] || []) + : argv[option.name] + }), + {} + ); } function dashifyObject(object) { diff --git a/src/cli-validator.js b/src/cli/validator.js similarity index 97% rename from src/cli-validator.js rename to src/cli/validator.js index a5aa2770..72a496ea 100644 --- a/src/cli-validator.js +++ b/src/cli/validator.js @@ -1,7 +1,7 @@ "use strict"; const camelCase = require("camelcase"); -const logger = require("./cli-logger"); +const logger = require("./logger"); function validateArgv(argv) { if (argv["write"] && argv["debug-check"]) { diff --git a/src/clean-ast.js b/src/common/clean-ast.js similarity index 100% rename from src/clean-ast.js rename to src/common/clean-ast.js diff --git a/src/deprecated.js b/src/common/deprecated.js similarity index 100% rename from src/deprecated.js rename to src/common/deprecated.js diff --git a/src/errors.js b/src/common/errors.js similarity index 100% rename from src/errors.js rename to src/common/errors.js diff --git a/src/fast-path.js b/src/common/fast-path.js similarity index 99% rename from src/fast-path.js rename to src/common/fast-path.js index b5a0f7c0..4c149001 100644 --- a/src/fast-path.js +++ b/src/common/fast-path.js @@ -1,7 +1,7 @@ "use strict"; const assert = require("assert"); -const util = require("./util"); +const util = require("../common/util"); const startsWithNoLookaheadToken = util.startsWithNoLookaheadToken; function FastPath(value) { diff --git a/src/common/load-plugins.js b/src/common/load-plugins.js new file mode 100644 index 00000000..42ef58c2 --- /dev/null +++ b/src/common/load-plugins.js @@ -0,0 +1,25 @@ +"use strict"; + +const resolve = require("resolve"); + +function loadPlugins(options) { + options = Object.assign({ plugins: [] }, options); + + const internalPlugins = [ + require("../language-js"), + require("../language-css"), + require("../language-graphql"), + require("../language-markdown"), + require("../language-html"), + require("../language-vue") + ]; + + const externalPlugins = options.plugins.map(plugin => { + const pluginPath = resolve.sync(plugin, { basedir: process.cwd() }); + return eval("require")(pluginPath); + }); + + return internalPlugins.concat(externalPlugins); +} + +module.exports = loadPlugins; diff --git a/src/options.js b/src/common/options.js similarity index 95% rename from src/options.js rename to src/common/options.js index 2735778b..b47d8140 100644 --- a/src/options.js +++ b/src/common/options.js @@ -4,7 +4,7 @@ const path = require("path"); const validate = require("jest-validate").validate; const deprecatedConfig = require("./deprecated"); -const supportTable = require("./support").supportTable; +const getSupportInfo = require("./support").getSupportInfo; const defaults = { cursorOffset: -1, @@ -22,7 +22,8 @@ const defaults = { requirePragma: false, semi: true, proseWrap: "preserve", - arrowParens: "avoid" + arrowParens: "avoid", + plugins: [] }; const exampleConfig = Object.assign({}, defaults, { @@ -43,7 +44,7 @@ function normalize(options) { const extension = path.extname(filepath); const filename = path.basename(filepath).toLowerCase(); - const language = supportTable.find( + const language = getSupportInfo().languages.find( language => typeof language.since === "string" && (language.extensions.indexOf(extension) > -1 || diff --git a/src/parser-create-error.js b/src/common/parser-create-error.js similarity index 100% rename from src/parser-create-error.js rename to src/common/parser-create-error.js diff --git a/src/parser-include-shebang.js b/src/common/parser-include-shebang.js similarity index 100% rename from src/parser-include-shebang.js rename to src/common/parser-include-shebang.js diff --git a/src/common/support.js b/src/common/support.js new file mode 100644 index 00000000..89797c36 --- /dev/null +++ b/src/common/support.js @@ -0,0 +1,43 @@ +"use strict"; + +const semver = require("semver"); +const currentVersion = require("../../package.json").version; +const loadPlugins = require("./load-plugins"); + +function getSupportInfo(version, options) { + if (!version) { + version = currentVersion; + } + + const usePostCssParser = semver.lt(version, "1.7.1"); + + const languages = loadPlugins(options) + .reduce((all, plugin) => all.concat(plugin.languages), []) + .filter(language => language.since && semver.gte(version, language.since)) + .map(language => { + // Prevent breaking changes + if (language.name === "Markdown") { + return Object.assign({}, language, { + parsers: ["markdown"] + }); + } + if (language.name === "TypeScript") { + return Object.assign({}, language, { + parsers: ["typescript"] + }); + } + + if (usePostCssParser && language.group === "CSS") { + return Object.assign({}, language, { + parsers: ["postcss"] + }); + } + return language; + }); + + return { languages }; +} + +module.exports = { + getSupportInfo +}; diff --git a/src/third-party.js b/src/common/third-party.js similarity index 100% rename from src/third-party.js rename to src/common/third-party.js diff --git a/src/util.js b/src/common/util.js similarity index 98% rename from src/util.js rename to src/common/util.js index 958bf3c1..c4c1e550 100644 --- a/src/util.js +++ b/src/common/util.js @@ -791,6 +791,20 @@ function getStringWidth(text) { return stringWidth(text.replace(emojiRegex, " ")); } +function hasIgnoreComment(path) { + const node = path.getValue(); + return hasNodeIgnoreComment(node); +} + +function hasNodeIgnoreComment(node) { + return ( + node && + node.comments && + node.comments.length > 0 && + node.comments.some(comment => comment.value.trim() === "prettier-ignore") + ); +} + module.exports = { punctuationRegex, punctuationCharRange, @@ -827,5 +841,7 @@ module.exports = { getAlignmentSize, getIndentSize, printString, - printNumber + printNumber, + hasIgnoreComment, + hasNodeIgnoreComment }; diff --git a/src/resolve-config-editorconfig.js b/src/config/resolve-config-editorconfig.js similarity index 100% rename from src/resolve-config-editorconfig.js rename to src/config/resolve-config-editorconfig.js diff --git a/src/resolve-config.js b/src/config/resolve-config.js similarity index 98% rename from src/resolve-config.js rename to src/config/resolve-config.js index 56270016..229caf3d 100644 --- a/src/resolve-config.js +++ b/src/config/resolve-config.js @@ -1,6 +1,6 @@ "use strict"; -const thirdParty = require("./third-party"); +const thirdParty = require("../common/third-party"); const minimatch = require("minimatch"); const path = require("path"); const mem = require("mem"); diff --git a/src/doc-builders.js b/src/doc/doc-builders.js similarity index 100% rename from src/doc-builders.js rename to src/doc/doc-builders.js diff --git a/src/doc-debug.js b/src/doc/doc-debug.js similarity index 100% rename from src/doc-debug.js rename to src/doc/doc-debug.js diff --git a/src/doc-printer.js b/src/doc/doc-printer.js similarity index 99% rename from src/doc-printer.js rename to src/doc/doc-printer.js index 72ae477c..813f3269 100644 --- a/src/doc-printer.js +++ b/src/doc/doc-printer.js @@ -1,6 +1,6 @@ "use strict"; -const util = require("./util"); +const util = require("../common/util"); const docBuilders = require("./doc-builders"); const concat = docBuilders.concat; const fill = docBuilders.fill; diff --git a/src/doc-utils.js b/src/doc/doc-utils.js similarity index 91% rename from src/doc-utils.js rename to src/doc/doc-utils.js index 0dae885c..14a664bb 100644 --- a/src/doc-utils.js +++ b/src/doc/doc-utils.js @@ -167,6 +167,21 @@ function removeLines(doc) { }); } +function stripTrailingHardline(doc) { + // HACK remove ending hardline, original PR: #1984 + if ( + doc.type === "concat" && + doc.parts.length === 2 && + doc.parts[1].type === "concat" && + doc.parts[1].parts.length === 2 && + doc.parts[1].parts[0].hard && + doc.parts[1].parts[1].type === "break-parent" + ) { + return doc.parts[0]; + } + return doc; +} + function rawText(node) { return node.extra ? node.extra.raw : node.raw; } @@ -179,5 +194,6 @@ module.exports = { mapDoc, propagateBreaks, removeLines, + stripTrailingHardline, rawText }; diff --git a/src/doc/index.js b/src/doc/index.js new file mode 100644 index 00000000..ef40ddc3 --- /dev/null +++ b/src/doc/index.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { + builders: require("./doc-builders"), + printer: require("./doc-printer"), + utils: require("./doc-utils"), + debug: require("./doc-debug") +}; diff --git a/src/language-css/index.js b/src/language-css/index.js new file mode 100644 index 00000000..b52dd49f --- /dev/null +++ b/src/language-css/index.js @@ -0,0 +1,72 @@ +"use strict"; + +const printer = require("./printer-postcss"); + +// Based on: +// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml + +const languages = [ + { + name: "CSS", + since: "1.4.0", + parsers: ["css"], + group: "CSS", + tmScope: "source.css", + aceMode: "css", + codemirrorMode: "css", + codemirrorMimeType: "text/css", + extensions: [".css", ".pcss", ".postcss"], + liguistLanguageId: 50, + vscodeLanguageIds: ["css", "postcss"] + }, + { + name: "Less", + since: "1.4.0", + parsers: ["less"], + group: "CSS", + extensions: [".less"], + tmScope: "source.css.less", + aceMode: "less", + codemirrorMode: "css", + codemirrorMimeType: "text/css", + liguistLanguageId: 198, + vscodeLanguageIds: ["less"] + }, + { + name: "SCSS", + since: "1.4.0", + parsers: ["scss"], + group: "CSS", + tmScope: "source.scss", + aceMode: "scss", + codemirrorMode: "css", + codemirrorMimeType: "text/x-scss", + extensions: [".scss"], + liguistLanguageId: 329, + vscodeLanguageIds: ["scss"] + } +]; + +const postcss = { + get parse() { + return eval("require")("./parser-postcss"); + }, + astFormat: "postcss" +}; + +// TODO: switch these to just `postcss` and use `language` instead. +const parsers = { + css: postcss, + less: postcss, + scss: postcss +}; + +const printers = { + postcss: printer +}; + +module.exports = { + languages, + parsers, + printers +}; diff --git a/src/parser-postcss.js b/src/language-css/parser-postcss.js similarity index 99% rename from src/parser-postcss.js rename to src/language-css/parser-postcss.js index 6c2868f0..56723381 100644 --- a/src/parser-postcss.js +++ b/src/language-css/parser-postcss.js @@ -1,6 +1,6 @@ "use strict"; -const createError = require("./parser-create-error"); +const createError = require("../common/parser-create-error"); function parseSelector(selector) { const selectorParser = require("postcss-selector-parser"); diff --git a/src/printer-postcss.js b/src/language-css/printer-postcss.js similarity index 98% rename from src/printer-postcss.js rename to src/language-css/printer-postcss.js index e5e7ffa7..2772b383 100644 --- a/src/printer-postcss.js +++ b/src/language-css/printer-postcss.js @@ -1,7 +1,8 @@ "use strict"; -const util = require("./util"); -const docBuilders = require("./doc-builders"); +const util = require("../common/util"); +const doc = require("../doc"); +const docBuilders = doc.builders; const concat = docBuilders.concat; const join = docBuilders.join; const line = docBuilders.line; @@ -11,8 +12,7 @@ const group = docBuilders.group; const fill = docBuilders.fill; const indent = docBuilders.indent; -const docUtils = require("./doc-utils"); -const removeLines = docUtils.removeLines; +const removeLines = doc.utils.removeLines; function genericPrint(path, options, print) { const n = path.getValue(); @@ -533,4 +533,7 @@ function maybeToLowerCase(value) { : value.toLowerCase(); } -module.exports = genericPrint; +module.exports = { + print: genericPrint, + hasPrettierIgnore: util.hasIgnoreComment +}; diff --git a/src/language-graphql/index.js b/src/language-graphql/index.js new file mode 100644 index 00000000..194f7e5c --- /dev/null +++ b/src/language-graphql/index.js @@ -0,0 +1,38 @@ +"use strict"; + +const printer = require("./printer-graphql"); + +// Based on: +// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml + +const languages = [ + { + name: "GraphQL", + since: "1.5.0", + parsers: ["graphql"], + extensions: [".graphql", ".gql"], + tmScope: "source.graphql", + aceMode: "text", + liguistLanguageId: 139, + vscodeLanguageIds: ["graphql"] + } +]; + +const parsers = { + graphql: { + get parse() { + return eval("require")("./parser-graphql"); + }, + astFormat: "graphql" + } +}; + +const printers = { + graphql: printer +}; + +module.exports = { + languages, + parsers, + printers +}; diff --git a/src/parser-graphql.js b/src/language-graphql/parser-graphql.js similarity index 95% rename from src/parser-graphql.js rename to src/language-graphql/parser-graphql.js index fe1e118e..d72e9b36 100644 --- a/src/parser-graphql.js +++ b/src/language-graphql/parser-graphql.js @@ -1,6 +1,6 @@ "use strict"; -const createError = require("./parser-create-error"); +const createError = require("../common/parser-create-error"); function parseComments(ast) { const comments = []; diff --git a/src/printer-graphql.js b/src/language-graphql/printer-graphql.js similarity index 98% rename from src/printer-graphql.js rename to src/language-graphql/printer-graphql.js index e9fdfb63..c8615744 100644 --- a/src/printer-graphql.js +++ b/src/language-graphql/printer-graphql.js @@ -1,6 +1,6 @@ "use strict"; -const docBuilders = require("./doc-builders"); +const docBuilders = require("../doc").builders; const concat = docBuilders.concat; const join = docBuilders.join; const hardline = docBuilders.hardline; @@ -10,7 +10,7 @@ const group = docBuilders.group; const indent = docBuilders.indent; const ifBreak = docBuilders.ifBreak; -const util = require("./util"); +const util = require("../common/util"); function genericPrint(path, options, print) { const n = path.getValue(); @@ -539,4 +539,7 @@ function printSequence(sequencePath, options, print) { }); } -module.exports = genericPrint; +module.exports = { + print: genericPrint, + hasPrettierIgnore: util.hasIgnoreComment +}; diff --git a/src/language-html/embed.js b/src/language-html/embed.js new file mode 100644 index 00000000..8c1f7a6e --- /dev/null +++ b/src/language-html/embed.js @@ -0,0 +1,93 @@ +"use strict"; + +const util = require("../common/util"); +const doc = require("../doc"); +const docUtils = doc.utils; +const docBuilders = doc.builders; +const hardline = docBuilders.hardline; +const concat = docBuilders.concat; + +function embed(path, print, textToDoc, options) { + const node = path.getValue(); + + switch (node.type) { + case "text": { + const parent = path.getParentNode(); + // Inline JavaScript + if ( + parent.type === "script" && + ((!parent.attribs.lang && !parent.attribs.lang) || + parent.attribs.type === "text/javascript" || + parent.attribs.type === "application/javascript") + ) { + const parser = options.parser === "flow" ? "flow" : "babylon"; + const doc = textToDoc(getText(options, node), { parser }); + return concat([hardline, doc]); + } + + // Inline TypeScript + if ( + parent.type === "script" && + (parent.attribs.type === "application/x-typescript" || + parent.attribs.lang === "ts") + ) { + const doc = textToDoc( + getText(options, node), + { parser: "typescript" }, + options + ); + return concat([hardline, doc]); + } + + // Inline Styles + if (parent.type === "style") { + const doc = textToDoc(getText(options, node), { parser: "css" }); + return concat([hardline, docUtils.stripTrailingHardline(doc)]); + } + + break; + } + + case "attribute": { + /* + * Vue binding sytax: JS expressions + * :class="{ 'some-key': value }" + * v-bind:id="'list-' + id" + * v-if="foo && !bar" + * @click="someFunction()" + */ + if (/(^@)|(^v-)|:/.test(node.key) && !/^\w+$/.test(node.value)) { + const doc = textToDoc(node.value, { + parser: parseJavaScriptExpression, + // Use singleQuote since HTML attributes use double-quotes. + // TODO(azz): We still need to do an entity escape on the attribute. + singleQuote: true + }); + return concat([ + node.key, + '="', + util.hasNewlineInRange(node.value, 0, node.value.length) + ? doc + : docUtils.removeLines(doc), + '"' + ]); + } + } + } +} + +function parseJavaScriptExpression(text, parsers) { + // Force parsing as an expression + const ast = parsers.babylon(`(${text})`); + // Extract expression from the declaration + return { + type: "File", + program: ast.program.body[0].expression + }; +} + +function getText(options, node) { + return options.originalText.slice(util.locStart(node), util.locEnd(node)); +} + +module.exports = embed; diff --git a/src/language-html/index.js b/src/language-html/index.js new file mode 100644 index 00000000..0842539d --- /dev/null +++ b/src/language-html/index.js @@ -0,0 +1,42 @@ +"use strict"; + +const printer = require("./printer-htmlparser2"); + +// Based on: +// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml + +const languages = [ + { + name: "HTML", + since: undefined, // unreleased + parsers: ["parse5"], + group: "HTML", + tmScope: "text.html.basic", + aceMode: "html", + codemirrorMode: "htmlmixed", + codemirrorMimeType: "text/html", + aliases: ["xhtml"], + extensions: [".html", ".htm", ".html.hl", ".inc", ".st", ".xht", ".xhtml"], + linguistLanguageId: 146, + vscodeLanguageIds: ["html"] + } +]; + +const parsers = { + parse5: { + get parse() { + return eval("require")("./parser-parse5"); + }, + astFormat: "htmlparser2" + } +}; + +const printers = { + htmlparser2: printer +}; + +module.exports = { + languages, + parsers, + printers +}; diff --git a/src/parser-parse5.js b/src/language-html/parser-parse5.js similarity index 100% rename from src/parser-parse5.js rename to src/language-html/parser-parse5.js diff --git a/src/printer-htmlparser2.js b/src/language-html/printer-htmlparser2.js similarity index 91% rename from src/printer-htmlparser2.js rename to src/language-html/printer-htmlparser2.js index 87fe93d1..7a697679 100644 --- a/src/printer-htmlparser2.js +++ b/src/language-html/printer-htmlparser2.js @@ -1,7 +1,8 @@ "use strict"; -const util = require("./util"); -const docBuilders = require("./doc-builders"); +const embed = require("./embed"); +const util = require("../common/util"); +const docBuilders = require("../doc").builders; const concat = docBuilders.concat; const join = docBuilders.join; const hardline = docBuilders.hardline; @@ -114,4 +115,8 @@ function printChildren(path, print) { return concat(children); } -module.exports = genericPrint; +module.exports = { + print: genericPrint, + embed, + hasPrettierIgnore: util.hasIgnoreComment +}; diff --git a/src/multiparser.js b/src/language-js/embed.js similarity index 55% rename from src/multiparser.js rename to src/language-js/embed.js index 3e794596..b439aa08 100644 --- a/src/multiparser.js +++ b/src/language-js/embed.js @@ -1,139 +1,16 @@ "use strict"; -const util = require("./util"); -const docUtils = require("./doc-utils"); -const docBuilders = require("./doc-builders"); -const comments = require("./comments"); +const util = require("../common/util"); +const doc = require("../doc"); +const docUtils = doc.utils; +const docBuilders = doc.builders; const indent = docBuilders.indent; const join = docBuilders.join; const hardline = docBuilders.hardline; const softline = docBuilders.softline; const concat = docBuilders.concat; -function printSubtree(path, print, options) { - switch (options.parser) { - case "parse5": - return fromHtmlParser2(path, print, options); - case "babylon": - case "flow": - case "typescript": - return fromBabylonFlowOrTypeScript(path, print, options); - case "markdown": - return fromMarkdown(path, print, options); - case "vue": - return fromVue(path, print, options); - } -} - -function parseAndPrint(text, partialNextOptions, parentOptions) { - const nextOptions = Object.assign({}, parentOptions, partialNextOptions, { - parentParser: parentOptions.parser, - originalText: text - }); - if (nextOptions.parser === "json") { - nextOptions.trailingComma = "none"; - } - const ast = require("./parser").parse(text, nextOptions); - const astComments = ast.comments; - delete ast.comments; - comments.attach(astComments, ast, text, nextOptions); - return require("./printer").printAstToDoc(ast, nextOptions); -} - -function fromVue(path, print, options) { - const node = path.getValue(); - const parent = path.getParentNode(); - if (!parent || parent.tag !== "root") { - return null; - } - - let parser; - - if (node.tag === "style") { - const langAttr = node.attrs.find(attr => attr.name === "lang"); - if (!langAttr) { - parser = "css"; - } else if (langAttr.value === "scss") { - parser = "scss"; - } else if (langAttr.value === "less") { - parser = "less"; - } else { - return null; - } - } - - if (node.tag === "script") { - const langAttr = node.attrs.find(attr => attr.name === "lang"); - if (!langAttr) { - parser = "babylon"; - } else if (langAttr.value === "ts") { - parser = "typescript"; - } else { - return null; - } - } - - return concat([ - options.originalText.slice(node.start, node.contentStart), - hardline, - parseAndPrint( - options.originalText.slice(node.contentStart, node.contentEnd), - parser, - options - ), - options.originalText.slice(node.contentEnd, node.end) - ]); -} - -function fromMarkdown(path, print, options) { - const node = path.getValue(); - - if (node.type === "code") { - const parser = getParserName(node.lang); - if (parser) { - const styleUnit = options.__inJsTemplate ? "~" : "`"; - const style = styleUnit.repeat( - Math.max(3, util.getMaxContinuousCount(node.value, styleUnit) + 1) - ); - const doc = parseAndPrint(node.value, { parser }, options); - return concat([style, node.lang, hardline, doc, style]); - } - } - - return null; - - function getParserName(lang) { - switch (lang) { - case "js": - case "jsx": - case "javascript": - return "babylon"; - case "ts": - case "tsx": - case "typescript": - return "typescript"; - case "gql": - case "graphql": - return "graphql"; - case "css": - return "css"; - case "less": - return "less"; - case "scss": - return "scss"; - case "json": - case "json5": - return "json"; - case "md": - case "markdown": - return "markdown"; - default: - return null; - } - } -} - -function fromBabylonFlowOrTypeScript(path, print, options) { +function embed(path, print, textToDoc /*, options */) { const node = path.getValue(); const parent = path.getParentNode(); const parentParent = path.getParentNode(1); @@ -148,7 +25,7 @@ function fromBabylonFlowOrTypeScript(path, print, options) { // Get full template literal with expressions replaced by placeholders const rawQuasis = node.quasis.map(q => q.value.raw); const text = rawQuasis.join("@prettier-placeholder"); - const doc = parseAndPrint(text, { parser: "css" }, options); + const doc = textToDoc(text, { parser: "css" }); return transformCssDoc(doc, path, print); } @@ -213,8 +90,8 @@ function fromBabylonFlowOrTypeScript(path, print, options) { doc = printGraphqlComments(lines); } else { try { - doc = stripTrailingHardline( - parseAndPrint(text, { parser: "graphql" }, options) + doc = docUtils.stripTrailingHardline( + textToDoc(text, { parser: "graphql" }) ); } catch (_error) { // Bail if any part fails to parse. @@ -263,18 +140,20 @@ function fromBabylonFlowOrTypeScript(path, print, options) { (parentParent.tag.name === "md" || parentParent.tag.name === "markdown"))) ) { - const doc = parseAndPrint( + const doc = textToDoc( // leading whitespaces matter in markdown dedent(parent.quasis[0].value.cooked), { parser: "markdown", __inJsTemplate: true - }, - options + } ); return concat([ indent( - concat([softline, stripTrailingHardline(escapeBackticks(doc))]) + concat([ + softline, + docUtils.stripTrailingHardline(escapeBackticks(doc)) + ]) ), softline ]); @@ -285,32 +164,6 @@ function fromBabylonFlowOrTypeScript(path, print, options) { } } -function printGraphqlComments(lines) { - const parts = []; - let seenComment = false; - - lines.map(textLine => textLine.trim()).forEach((textLine, i, array) => { - // Lines are either whitespace only, or a comment (with poential whitespace - // around it). Drop whitespace-only lines. - if (textLine === "") { - return; - } - - if (array[i - 1] === "" && seenComment) { - // If a non-first comment is preceded by a blank (whitespace only) line, - // add in a blank line. - parts.push(concat([hardline, textLine])); - } else { - parts.push(textLine); - } - - seenComment = true; - }); - - // If `lines` was whitespace only, return `null`. - return parts.length === 0 ? null : join(hardline, parts); -} - function dedent(str) { const firstMatchedIndent = str.match(/\n^( *)/m); const spaces = firstMatchedIndent === null ? 0 : firstMatchedIndent[1].length; @@ -337,83 +190,6 @@ function escapeBackticks(doc) { }); } -function fromHtmlParser2(path, print, options) { - const node = path.getValue(); - - switch (node.type) { - case "text": { - const parent = path.getParentNode(); - // Inline JavaScript - if ( - parent.type === "script" && - ((!parent.attribs.lang && !parent.attribs.lang) || - parent.attribs.type === "text/javascript" || - parent.attribs.type === "application/javascript") - ) { - const parser = options.parser === "flow" ? "flow" : "babylon"; - const doc = parseAndPrint(getText(options, node), { parser }, options); - return concat([hardline, doc]); - } - - // Inline TypeScript - if ( - parent.type === "script" && - (parent.attribs.type === "application/x-typescript" || - parent.attribs.lang === "ts") - ) { - const doc = parseAndPrint( - getText(options, node), - { parser: "typescript" }, - options - ); - return concat([hardline, doc]); - } - - // Inline Styles - if (parent.type === "style") { - const doc = parseAndPrint( - getText(options, node), - { parser: "css" }, - options - ); - return concat([hardline, stripTrailingHardline(doc)]); - } - - break; - } - - case "attribute": { - /* - * Vue binding sytax: JS expressions - * :class="{ 'some-key': value }" - * v-bind:id="'list-' + id" - * v-if="foo && !bar" - * @click="someFunction()" - */ - if (/(^@)|(^v-)|:/.test(node.key) && !/^\w+$/.test(node.value)) { - const doc = parseAndPrint( - node.value, - { - parser: parseJavaScriptExpression, - // Use singleQuote since HTML attributes use double-quotes. - // TODO(azz): We still need to do an entity escape on the attribute. - singleQuote: true - }, - options - ); - return concat([ - node.key, - '="', - util.hasNewlineInRange(node.value, 0, node.value.length) - ? doc - : docUtils.removeLines(doc), - '"' - ]); - } - } - } -} - function transformCssDoc(quasisDoc, path, print) { const parentNode = path.getValue(); @@ -433,7 +209,7 @@ function transformCssDoc(quasisDoc, path, print) { } return concat([ "`", - indent(concat([hardline, stripTrailingHardline(newDoc)])), + indent(concat([hardline, docUtils.stripTrailingHardline(newDoc)])), softline, "`" ]); @@ -454,31 +230,39 @@ function replacePlaceholders(quasisDoc, expressionDocs) { return doc; } let parts = doc.parts; + const atIndex = parts.indexOf("@"); + const placeholderIndex = atIndex + 1; if ( - parts.length > 1 && - parts[0] === "@" && - typeof parts[1] === "string" && - parts[1].startsWith("prettier-placeholder") + atIndex > -1 && + typeof parts[placeholderIndex] === "string" && + parts[placeholderIndex].startsWith("prettier-placeholder") ) { // If placeholder is split, join it - const at = parts[0]; - const placeholder = parts[1]; - const rest = parts.slice(2); - parts = [at + placeholder].concat(rest); + const at = parts[atIndex]; + const placeholder = parts[placeholderIndex]; + const rest = parts.slice(placeholderIndex + 1); + parts = parts + .slice(0, atIndex) + .concat([at + placeholder]) + .concat(rest); } - if ( - typeof parts[0] === "string" && - parts[0].startsWith("@prettier-placeholder") - ) { - const placeholder = parts[0]; - const rest = parts.slice(1); + const atPlaceholderIndex = parts.findIndex( + part => + typeof part === "string" && part.startsWith("@prettier-placeholder") + ); + if (atPlaceholderIndex > -1) { + const placeholder = parts[atPlaceholderIndex]; + const rest = parts.slice(atPlaceholderIndex + 1); // When the expression has a suffix appended, like: // animation: linear ${time}s ease-out; const suffix = placeholder.slice("@prettier-placeholder".length); const expression = expressions.shift(); - parts = ["${", expression, "}" + suffix].concat(rest); + parts = parts + .slice(0, atPlaceholderIndex) + .concat(["${", expression, "}" + suffix]) + .concat(rest); } return Object.assign({}, doc, { parts: parts @@ -488,35 +272,30 @@ function replacePlaceholders(quasisDoc, expressionDocs) { return expressions.length === 0 ? newDoc : null; } -function parseJavaScriptExpression(text, parsers) { - // Force parsing as an expression - const ast = parsers.babylon(`(${text})`); - // Extract expression from the declaration - return { - type: "File", - program: ast.program.body[0].expression - }; -} +function printGraphqlComments(lines) { + const parts = []; + let seenComment = false; -function getText(options, node) { - return options.originalText.slice(util.locStart(node), util.locEnd(node)); -} + lines.map(textLine => textLine.trim()).forEach((textLine, i, array) => { + // Lines are either whitespace only, or a comment (with poential whitespace + // around it). Drop whitespace-only lines. + if (textLine === "") { + return; + } -function stripTrailingHardline(doc) { - // HACK remove ending hardline, original PR: #1984 - if ( - doc.type === "concat" && - doc.parts[0].type === "concat" && - doc.parts[0].parts.length === 2 && - // doc.parts[0].parts[1] === hardline : - doc.parts[0].parts[1].type === "concat" && - doc.parts[0].parts[1].parts.length === 2 && - doc.parts[0].parts[1].parts[0].hard && - doc.parts[0].parts[1].parts[1].type === "break-parent" - ) { - return doc.parts[0].parts[0]; - } - return doc; + if (array[i - 1] === "" && seenComment) { + // If a non-first comment is preceded by a blank (whitespace only) line, + // add in a blank line. + parts.push(concat([hardline, textLine])); + } else { + parts.push(textLine); + } + + seenComment = true; + }); + + // If `lines` was whitespace only, return `null`. + return parts.length === 0 ? null : join(hardline, parts); } /** @@ -601,6 +380,4 @@ function isStyledIdentifier(node) { return node.type === "Identifier" && node.name === "styled"; } -module.exports = { - printSubtree -}; +module.exports = embed; diff --git a/src/language-js/index.js b/src/language-js/index.js new file mode 100644 index 00000000..ae20ff44 --- /dev/null +++ b/src/language-js/index.js @@ -0,0 +1,138 @@ +"use strict"; + +const printer = require("./printer-estree"); + +// Based on: +// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml + +const languages = [ + { + name: "JavaScript", + since: "0.0.0", + parsers: ["babylon", "flow"], + group: "JavaScript", + tmScope: "source.js", + aceMode: "javascript", + codemirrorMode: "javascript", + codemirrorMimeType: "text/javascript", + aliases: ["js", "node"], + extensions: [ + ".js", + "._js", + ".bones", + ".es", + ".es6", + ".frag", + ".gs", + ".jake", + ".jsb", + ".jscad", + ".jsfl", + ".jsm", + ".jss", + ".mjs", + ".njs", + ".pac", + ".sjs", + ".ssjs", + ".xsjs", + ".xsjslib" + ], + filenames: ["Jakefile"], + linguistLanguageId: 183, + vscodeLanguageIds: ["javascript"] + }, + { + name: "JSX", + since: "0.0.0", + parsers: ["babylon", "flow"], + group: "JavaScript", + extensions: [".jsx"], + tmScope: "source.js.jsx", + aceMode: "javascript", + codemirrorMode: "jsx", + codemirrorMimeType: "text/jsx", + liguistLanguageId: 178, + vscodeLanguageIds: ["javascriptreact"] + }, + { + name: "TypeScript", + since: "1.4.0", + parsers: ["typescript-eslint"], + group: "JavaScript", + aliases: ["ts"], + extensions: [".ts", ".tsx"], + tmScope: "source.ts", + aceMode: "typescript", + codemirrorMode: "javascript", + codemirrorMimeType: "application/typescript", + liguistLanguageId: 378, + vscodeLanguageIds: ["typescript", "typescriptreact"] + }, + { + name: "JSON", + since: "1.5.0", + parsers: ["json"], + group: "JavaScript", + tmScope: "source.json", + aceMode: "json", + codemirrorMode: "javascript", + codemirrorMimeType: "application/json", + extensions: [ + ".json", + ".json5", + ".geojson", + ".JSON-tmLanguage", + ".topojson" + ], + filenames: [ + ".arcconfig", + ".jshintrc", + ".babelrc", + ".eslintrc", + ".prettierrc", + "composer.lock", + "mcmod.info" + ], + linguistLanguageId: 174, + vscodeLanguageIds: ["json", "jsonc"] + } +]; + +const typescript = { + get parse() { + return eval("require")("./parser-typescript"); + }, + astFormat: "estree" +}; + +const babylon = { + get parse() { + return eval("require")("./parser-babylon"); + }, + astFormat: "estree" +}; + +const parsers = { + babylon, + json: babylon, + flow: { + get parse() { + return eval("require")("./parser-flow"); + }, + astFormat: "estree" + }, + "typescript-eslint": typescript, + // TODO: Delete this in 2.0 + typescript +}; + +const printers = { + estree: printer +}; + +module.exports = { + languages, + parsers, + printers +}; diff --git a/src/parser-babylon.js b/src/language-js/parser-babylon.js similarity index 96% rename from src/parser-babylon.js rename to src/language-js/parser-babylon.js index b3651778..23abbaf5 100644 --- a/src/parser-babylon.js +++ b/src/language-js/parser-babylon.js @@ -1,6 +1,6 @@ "use strict"; -const createError = require("./parser-create-error"); +const createError = require("../common/parser-create-error"); function parse(text, parsers, opts) { // Inline the require to avoid loading all the JS if we don't use it diff --git a/src/parser-flow.js b/src/language-js/parser-flow.js similarity index 85% rename from src/parser-flow.js rename to src/language-js/parser-flow.js index d83260d2..652c6229 100644 --- a/src/parser-flow.js +++ b/src/language-js/parser-flow.js @@ -1,7 +1,7 @@ "use strict"; -const createError = require("./parser-create-error"); -const includeShebang = require("./parser-include-shebang"); +const createError = require("../common/parser-create-error"); +const includeShebang = require("../common/parser-include-shebang"); function parse(text /*, parsers, opts*/) { // Fixes Node 4 issue (#1986) diff --git a/src/parser-typescript.js b/src/language-js/parser-typescript.js similarity index 92% rename from src/parser-typescript.js rename to src/language-js/parser-typescript.js index 6c22e9e5..e931a4b1 100644 --- a/src/parser-typescript.js +++ b/src/language-js/parser-typescript.js @@ -1,7 +1,7 @@ "use strict"; -const createError = require("./parser-create-error"); -const includeShebang = require("./parser-include-shebang"); +const createError = require("../common/parser-create-error"); +const includeShebang = require("../common/parser-include-shebang"); function parse(text /*, parsers, opts*/) { const jsx = isProbablyJsx(text); diff --git a/src/printer.js b/src/language-js/printer-estree.js similarity index 97% rename from src/printer.js rename to src/language-js/printer-estree.js index 36df1176..27734bd8 100644 --- a/src/printer.js +++ b/src/language-js/printer-estree.js @@ -1,13 +1,14 @@ "use strict"; const assert = require("assert"); -const comments = require("./comments"); -const FastPath = require("./fast-path"); -const multiparser = require("./multiparser"); -const util = require("./util"); +// TODO(azz): anything that imports from main shouldn't be in a `language-*` dir. +const comments = require("../main/comments"); +const util = require("../common/util"); const isIdentifierName = require("esutils").keyword.isIdentifierNameES6; +const embed = require("./embed"); -const docBuilders = require("./doc-builders"); +const doc = require("../doc"); +const docBuilders = doc.builders; const concat = docBuilders.concat; const join = docBuilders.join; const line = docBuilders.line; @@ -24,7 +25,7 @@ const breakParent = docBuilders.breakParent; const lineSuffixBoundary = docBuilders.lineSuffixBoundary; const addAlignmentToDoc = docBuilders.addAlignmentToDoc; -const docUtils = require("./doc-utils"); +const docUtils = doc.utils; const willBreak = docUtils.willBreak; const isLineNext = docUtils.isLineNext; const isEmpty = docUtils.isEmpty; @@ -50,102 +51,10 @@ function shouldPrintComma(options, level) { } } -function getPrintFunction(options) { - switch (options.parser) { - case "graphql": - return require("./printer-graphql"); - case "parse5": - return require("./printer-htmlparser2"); - case "vue": - return require("./printer-vue"); - case "css": - case "less": - case "scss": - return require("./printer-postcss"); - case "markdown": - return require("./printer-markdown"); - default: - return genericPrintNoParens; - } -} - -function hasIgnoreComment(path) { - const node = path.getValue(); - return hasNodeIgnoreComment(node) || hasJsxIgnoreComment(path); -} - -function hasNodeIgnoreComment(node) { - return ( - node && - node.comments && - node.comments.length > 0 && - node.comments.some(comment => comment.value.trim() === "prettier-ignore") - ); -} - -function hasJsxIgnoreComment(path) { - const node = path.getValue(); - const parent = path.getParentNode(); - if (!parent || !node || !isJSXNode(node) || !isJSXNode(parent)) { - return false; - } - - // Lookup the previous sibling, ignoring any empty JSXText elements - const index = parent.children.indexOf(node); - let prevSibling = null; - for (let i = index; i > 0; i--) { - const candidate = parent.children[i - 1]; - if (candidate.type === "JSXText" && !isMeaningfulJSXText(candidate)) { - continue; - } - prevSibling = candidate; - break; - } - - return ( - prevSibling && - prevSibling.type === "JSXExpressionContainer" && - prevSibling.expression.type === "JSXEmptyExpression" && - prevSibling.expression.comments && - prevSibling.expression.comments.find( - comment => comment.value.trim() === "prettier-ignore" - ) - ); -} - function genericPrint(path, options, printPath, args) { - assert.ok(path instanceof FastPath); - const node = path.getValue(); - - // Escape hatch - if (hasIgnoreComment(path)) { - return options.originalText.slice(util.locStart(node), util.locEnd(node)); - } - - if (node) { - try { - // Potentially switch to a different parser - const sub = multiparser.printSubtree(path, printPath, options); - if (sub) { - return sub; - } - } catch (error) { - /* istanbul ignore if */ - if (process.env.PRETTIER_DEBUG) { - throw error; - } - // Continue with current parser - } - } - let needsParens = false; - const linesWithoutParens = getPrintFunction(options)( - path, - options, - printPath, - args - ); + const linesWithoutParens = printPathNoParens(path, options, printPath, args); if (!node || isEmpty(linesWithoutParens)) { return linesWithoutParens; @@ -226,7 +135,41 @@ function genericPrint(path, options, printPath, args) { return concat(parts); } -function genericPrintNoParens(path, options, print, args) { +function hasPrettierIgnore(path) { + return util.hasIgnoreComment(path) || hasJsxIgnoreComment(path); +} + +function hasJsxIgnoreComment(path) { + const node = path.getValue(); + const parent = path.getParentNode(); + if (!parent || !node || !isJSXNode(node) || !isJSXNode(parent)) { + return false; + } + + // Lookup the previous sibling, ignoring any empty JSXText elements + const index = parent.children.indexOf(node); + let prevSibling = null; + for (let i = index; i > 0; i--) { + const candidate = parent.children[i - 1]; + if (candidate.type === "JSXText" && !isMeaningfulJSXText(candidate)) { + continue; + } + prevSibling = candidate; + break; + } + + return ( + prevSibling && + prevSibling.type === "JSXExpressionContainer" && + prevSibling.expression.type === "JSXEmptyExpression" && + prevSibling.expression.comments && + prevSibling.expression.comments.find( + comment => comment.value.trim() === "prettier-ignore" + ) + ); +} + +function printPathNoParens(path, options, print, args) { const n = path.getValue(); const semi = options.semi ? ";" : ""; @@ -988,8 +931,8 @@ function genericPrintNoParens(path, options, print, args) { const result = concat(separatorParts.concat(group(prop.printed))); separatorParts = [separator, line]; if ( - hasNodeIgnoreComment(prop.node) && - prop.node.type === "TSPropertySignature" + prop.node.type === "TSPropertySignature" && + util.hasNodeIgnoreComment(prop.node) ) { separatorParts.shift(); } @@ -1006,7 +949,7 @@ function genericPrintNoParens(path, options, print, args) { (lastElem.type === "RestProperty" || lastElem.type === "RestElement" || lastElem.type === "ExperimentalRestProperty" || - hasNodeIgnoreComment(lastElem)) + util.hasNodeIgnoreComment(lastElem)) ); let content; @@ -4765,7 +4708,7 @@ function hasTrailingComment(node) { function hasLeadingOwnLineComment(text, node) { if (isJSXNode(node)) { - return hasNodeIgnoreComment(node); + return util.hasNodeIgnoreComment(node); } const res = @@ -5190,71 +5133,6 @@ function isTestCall(n) { ); } -function printAstToDoc(ast, options, addAlignmentSize) { - addAlignmentSize = addAlignmentSize || 0; - - const cache = new Map(); - - function printGenerically(path, args) { - const node = path.getValue(); - - const shouldCache = node && typeof node === "object" && args === undefined; - if (shouldCache && cache.has(node)) { - return cache.get(node); - } - - const parent = path.getParentNode(0); - // We let JSXElement print its comments itself because it adds () around - // UnionTypeAnnotation has to align the child without the comments - let res; - if ( - ((node && isJSXNode(node)) || - (parent && - (parent.type === "JSXSpreadAttribute" || - parent.type === "JSXSpreadChild" || - parent.type === "UnionTypeAnnotation" || - parent.type === "TSUnionType" || - ((parent.type === "ClassDeclaration" || - parent.type === "ClassExpression") && - parent.superClass === node)))) && - !hasIgnoreComment(path) - ) { - res = genericPrint(path, options, printGenerically, args); - } else { - res = comments.printComments( - path, - p => genericPrint(p, options, printGenerically, args), - options, - args && args.needsSemi - ); - } - - if (shouldCache) { - cache.set(node, res); - } - - return res; - } - - let doc = printGenerically(new FastPath(ast)); - if (addAlignmentSize > 0) { - // Add a hardline to make the indents take effect - // It should be removed in index.js format() - doc = addAlignmentToDoc( - docUtils.removeLines(concat([hardline, doc])), - addAlignmentSize, - options.tabWidth - ); - } - docUtils.propagateBreaks(doc); - - if (options.parser === "json") { - doc = concat([doc, hardline]); - } - - return doc; -} - function isTheOnlyJSXElementInMarkdown(options, path) { if (options.parentParser !== "markdown") { return false; @@ -5271,4 +5149,27 @@ function isTheOnlyJSXElementInMarkdown(options, path) { return parent.type === "Program" && parent.body.length == 1; } -module.exports = { printAstToDoc }; +function willPrintOwnComments(path) { + const node = path.getValue(); + const parent = path.getParentNode(); + + return ( + ((node && isJSXNode(node)) || + (parent && + (parent.type === "JSXSpreadAttribute" || + parent.type === "JSXSpreadChild" || + parent.type === "UnionTypeAnnotation" || + parent.type === "TSUnionType" || + ((parent.type === "ClassDeclaration" || + parent.type === "ClassExpression") && + parent.superClass === node)))) && + !util.hasIgnoreComment(path) + ); +} + +module.exports = { + print: genericPrint, + embed, + hasPrettierIgnore, + willPrintOwnComments +}; diff --git a/src/language-markdown/embed.js b/src/language-markdown/embed.js new file mode 100644 index 00000000..d70cf6a1 --- /dev/null +++ b/src/language-markdown/embed.js @@ -0,0 +1,43 @@ +"use strict"; + +const util = require("../common/util"); +const support = require("../common/support"); +const doc = require("../doc"); +const docBuilders = doc.builders; +const hardline = docBuilders.hardline; +const concat = docBuilders.concat; + +function embed(path, print, textToDoc, options) { + const node = path.getValue(); + + if (node.type === "code") { + const parser = getParserName(node.lang); + if (parser) { + const styleUnit = options.__inJsTemplate ? "~" : "`"; + const style = styleUnit.repeat( + Math.max(3, util.getMaxContinuousCount(node.value, styleUnit) + 1) + ); + const doc = textToDoc(node.value, { parser }); + return concat([style, node.lang, hardline, doc, style]); + } + } + + return null; + + function getParserName(lang) { + const supportInfo = support.getSupportInfo(undefined, options); + const language = supportInfo.languages.find( + language => + language.name.toLowerCase() === lang || + (language.extensions && + language.extensions.find(ext => ext.substring(1) === lang)) + ); + if (language) { + return language.parsers[0]; + } + + return null; + } +} + +module.exports = embed; diff --git a/src/language-markdown/index.js b/src/language-markdown/index.js new file mode 100644 index 00000000..d287c46b --- /dev/null +++ b/src/language-markdown/index.js @@ -0,0 +1,57 @@ +"use strict"; + +const printer = require("./printer-markdown"); + +// Based on: +// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml + +const languages = [ + { + name: "Markdown", + since: "1.8.0", + parsers: ["remark"], + aliases: ["pandoc"], + aceMode: "markdown", + codemirrorMode: "gfm", + codemirrorMimeType: "text/x-gfm", + wrap: true, + extensions: [ + ".md", + ".markdown", + ".mdown", + ".mdwn", + ".mkd", + ".mkdn", + ".mkdown", + ".ron", + ".workbook" + ], + filenames: ["README"], + tmScope: "source.gfm", + linguistLanguageId: 222, + vscodeLanguageIds: ["markdown"] + } +]; + +const remark = { + get parse() { + return eval("require")("./parser-markdown"); + }, + astFormat: "mdast" +}; + +const parsers = { + remark, + // TODO: Delete this in 2.0 + markdown: remark +}; + +const printers = { + mdast: printer +}; + +module.exports = { + languages, + parsers, + printers +}; diff --git a/src/parser-markdown.js b/src/language-markdown/parser-markdown.js similarity index 98% rename from src/parser-markdown.js rename to src/language-markdown/parser-markdown.js index a20fdc45..39c71102 100644 --- a/src/parser-markdown.js +++ b/src/language-markdown/parser-markdown.js @@ -3,7 +3,7 @@ const remarkFrontmatter = require("remark-frontmatter"); const remarkParse = require("remark-parse"); const unified = require("unified"); -const util = require("./util"); +const util = require("../common/util"); /** * based on [MDAST](https://github.com/syntax-tree/mdast) with following modifications: diff --git a/src/printer-markdown.js b/src/language-markdown/printer-markdown.js similarity index 98% rename from src/printer-markdown.js rename to src/language-markdown/printer-markdown.js index a4777d15..061d27d3 100644 --- a/src/printer-markdown.js +++ b/src/language-markdown/printer-markdown.js @@ -1,7 +1,9 @@ "use strict"; -const util = require("./util"); -const docBuilders = require("./doc-builders"); +const util = require("../common/util"); +const embed = require("./embed"); +const doc = require("../doc"); +const docBuilders = doc.builders; const concat = docBuilders.concat; const join = docBuilders.join; const line = docBuilders.line; @@ -9,8 +11,7 @@ const hardline = docBuilders.hardline; const softline = docBuilders.softline; const fill = docBuilders.fill; const align = docBuilders.align; -const docPrinter = require("./doc-printer"); -const printDocToString = docPrinter.printDocToString; +const printDocToString = doc.printer.printDocToString; const SINGLE_LINE_NODE_TYPES = [ "heading", @@ -657,4 +658,8 @@ function normalizeParts(parts) { }, []); } -module.exports = genericPrint; +module.exports = { + print: genericPrint, + embed, + hasPrettierIgnore: util.hasIgnoreComment +}; diff --git a/src/language-vue/embed.js b/src/language-vue/embed.js new file mode 100644 index 00000000..805b338e --- /dev/null +++ b/src/language-vue/embed.js @@ -0,0 +1,50 @@ +"use strict"; + +const docBuilders = require("../doc").builders; +const concat = docBuilders.concat; +const hardline = docBuilders.hardline; + +function embed(path, print, textToDoc, options) { + const node = path.getValue(); + const parent = path.getParentNode(); + if (!parent || parent.tag !== "root") { + return null; + } + + let parser; + + if (node.tag === "style") { + const langAttr = node.attrs.find(attr => attr.name === "lang"); + if (!langAttr) { + parser = "css"; + } else if (langAttr.value === "scss") { + parser = "scss"; + } else if (langAttr.value === "less") { + parser = "less"; + } + } + + if (node.tag === "script") { + const langAttr = node.attrs.find(attr => attr.name === "lang"); + if (!langAttr) { + parser = "babylon"; + } else if (langAttr.value === "ts") { + parser = "typescript"; + } + } + + if (!parser) { + return null; + } + + return concat([ + options.originalText.slice(node.start, node.contentStart), + hardline, + textToDoc(options.originalText.slice(node.contentStart, node.contentEnd), { + parser + }), + options.originalText.slice(node.contentEnd, node.end) + ]); +} + +module.exports = embed; diff --git a/src/language-vue/index.js b/src/language-vue/index.js new file mode 100644 index 00000000..fc580c7c --- /dev/null +++ b/src/language-vue/index.js @@ -0,0 +1,41 @@ +"use strict"; + +const printer = require("./printer-vue"); + +// Based on: +// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml + +const languages = [ + { + name: "Vue", + since: "1.10.0", + parsers: ["vue"], + group: "HTML", + tmScope: "text.html.vue", + aceMode: "html", + codemirrorMode: "htmlmixed", + codemirrorMimeType: "text/html", + extensions: [".vue"], + linguistLanguageId: 146, + vscodeLanguageIds: ["vue"] + } +]; + +const parsers = { + vue: { + get parse() { + return eval("require")("./parser-vue"); + }, + astFormat: "vue" + } +}; + +const printers = { + vue: printer +}; + +module.exports = { + languages, + parsers, + printers +}; diff --git a/src/parser-vue.js b/src/language-vue/parser-vue.js similarity index 100% rename from src/parser-vue.js rename to src/language-vue/parser-vue.js diff --git a/src/printer-vue.js b/src/language-vue/printer-vue.js similarity index 77% rename from src/printer-vue.js rename to src/language-vue/printer-vue.js index daf8e2b3..a248e8e3 100644 --- a/src/printer-vue.js +++ b/src/language-vue/printer-vue.js @@ -1,6 +1,7 @@ "use strict"; -const docBuilders = require("./doc-builders"); +const embed = require("./embed"); +const docBuilders = require("../doc").builders; const concat = docBuilders.concat; function genericPrint(path, options, print) { @@ -19,4 +20,7 @@ function genericPrint(path, options, print) { return concat(res); } -module.exports = genericPrint; +module.exports = { + print: genericPrint, + embed +}; diff --git a/src/main/ast-to-doc.js b/src/main/ast-to-doc.js new file mode 100644 index 00000000..d4f333cf --- /dev/null +++ b/src/main/ast-to-doc.js @@ -0,0 +1,100 @@ +"use strict"; + +const assert = require("assert"); +const comments = require("./comments"); +const FastPath = require("../common/fast-path"); +const multiparser = require("./multiparser"); +const util = require("../common/util"); + +const doc = require("../doc"); +const docBuilders = doc.builders; +const concat = docBuilders.concat; +const hardline = docBuilders.hardline; +const addAlignmentToDoc = docBuilders.addAlignmentToDoc; +const docUtils = doc.utils; +const getPrinter = require("./get-printer"); + +function printAstToDoc(ast, options, addAlignmentSize) { + addAlignmentSize = addAlignmentSize || 0; + + const printer = getPrinter(options); + const cache = new Map(); + + function printGenerically(path, args) { + const node = path.getValue(); + + const shouldCache = node && typeof node === "object" && args === undefined; + if (shouldCache && cache.has(node)) { + return cache.get(node); + } + + // We let JSXElement print its comments itself because it adds () around + // UnionTypeAnnotation has to align the child without the comments + let res; + if (printer.willPrintOwnComments && printer.willPrintOwnComments(path)) { + res = genericPrint(path, options, printer, printGenerically, args); + } else { + res = comments.printComments( + path, + p => genericPrint(p, options, printer, printGenerically, args), + options, + args && args.needsSemi + ); + } + + if (shouldCache) { + cache.set(node, res); + } + + return res; + } + + let doc = printGenerically(new FastPath(ast)); + if (addAlignmentSize > 0) { + // Add a hardline to make the indents take effect + // It should be removed in index.js format() + doc = addAlignmentToDoc( + docUtils.removeLines(concat([hardline, doc])), + addAlignmentSize, + options.tabWidth + ); + } + docUtils.propagateBreaks(doc); + + if (options.parser === "json") { + doc = concat([doc, hardline]); + } + + return doc; +} + +function genericPrint(path, options, printer, printPath, args) { + assert.ok(path instanceof FastPath); + + const node = path.getValue(); + + // Escape hatch + if (printer.hasPrettierIgnore && printer.hasPrettierIgnore(path)) { + return options.originalText.slice(util.locStart(node), util.locEnd(node)); + } + + if (node) { + try { + // Potentially switch to a different parser + const sub = multiparser.printSubtree(printer, path, printPath, options); + if (sub) { + return sub; + } + } catch (error) { + /* istanbul ignore if */ + if (process.env.PRETTIER_DEBUG) { + throw error; + } + // Continue with current parser + } + } + + return printer.print(path, options, printPath, args); +} + +module.exports = printAstToDoc; diff --git a/src/comments.js b/src/main/comments.js similarity index 99% rename from src/comments.js rename to src/main/comments.js index 6d043eb8..5c2e1d31 100644 --- a/src/comments.js +++ b/src/main/comments.js @@ -1,7 +1,7 @@ "use strict"; const assert = require("assert"); -const docBuilders = require("./doc-builders"); +const docBuilders = require("../doc").builders; const concat = docBuilders.concat; const hardline = docBuilders.hardline; const breakParent = docBuilders.breakParent; @@ -9,7 +9,7 @@ const indent = docBuilders.indent; const lineSuffix = docBuilders.lineSuffix; const join = docBuilders.join; const cursor = docBuilders.cursor; -const util = require("./util"); +const util = require("../common/util"); const childNodesCacheKey = Symbol("child-nodes"); const locStart = util.locStart; const locEnd = util.locEnd; diff --git a/src/main/get-printer.js b/src/main/get-printer.js new file mode 100644 index 00000000..86c7eb92 --- /dev/null +++ b/src/main/get-printer.js @@ -0,0 +1,20 @@ +"use strict"; + +const loadPlugins = require("../common/load-plugins"); +const parser = require("./parser"); + +function getPrinter(options) { + const plugins = loadPlugins(options); + const parsers = parser.getParsers(plugins, options); + const astFormat = parser.resolveParser(parsers, options).astFormat; + const printerPlugin = plugins.find(plugin => plugin.printers[astFormat]); + if (!printerPlugin) { + throw new Error( + `Couldn't find printer plugin for AST format "${astFormat}"` + ); + } + + return printerPlugin.printers[astFormat]; +} + +module.exports = getPrinter; diff --git a/src/main/multiparser.js b/src/main/multiparser.js new file mode 100644 index 00000000..8c635782 --- /dev/null +++ b/src/main/multiparser.js @@ -0,0 +1,35 @@ +"use strict"; + +const comments = require("./comments"); + +function printSubtree(printer, path, print, options) { + if (printer.embed) { + return printer.embed( + path, + print, + (text, partialNextOptions) => + textToDoc(text, partialNextOptions, options), + options + ); + } +} + +function textToDoc(text, partialNextOptions, parentOptions) { + const nextOptions = Object.assign({}, parentOptions, partialNextOptions, { + parentParser: parentOptions.parser, + originalText: text + }); + if (nextOptions.parser === "json") { + nextOptions.trailingComma = "none"; + } + + const ast = require("./parser").parse(text, nextOptions); + const astComments = ast.comments; + delete ast.comments; + comments.attach(astComments, ast, text, nextOptions); + return require("./ast-to-doc")(ast, nextOptions); +} + +module.exports = { + printSubtree +}; diff --git a/src/main/parser.js b/src/main/parser.js new file mode 100644 index 00000000..20b25467 --- /dev/null +++ b/src/main/parser.js @@ -0,0 +1,78 @@ +"use strict"; + +const path = require("path"); +const ConfigError = require("../common/errors").ConfigError; +const loadPlugins = require("../common/load-plugins"); + +function getParsers(plugins) { + return plugins.reduce( + (parsers, plugin) => Object.assign({}, parsers, plugin.parsers), + {} + ); +} + +function resolveParser(parsers, opts) { + if (typeof opts.parser === "function") { + // Custom parser API always works with JavaScript. + return { + parse: opts.parser, + astFormat: "estree" + }; + } + + if (typeof opts.parser === "string") { + if (parsers.hasOwnProperty(opts.parser)) { + return parsers[opts.parser]; + } + try { + return { + parse: eval("require")(path.resolve(process.cwd(), opts.parser)), + astFormat: "estree" + }; + } catch (err) { + /* istanbul ignore next */ + throw new ConfigError(`Couldn't resolve parser "${opts.parser}"`); + } + } + /* istanbul ignore next */ + return parsers.babylon; +} + +function parse(text, opts) { + const parsers = getParsers(loadPlugins(opts), opts); + + // Copy the "parse" function from parser to a new object whose values are + // functions. Use defineProperty()/getOwnPropertyDescriptor() such that we + // don't invoke the parser.parse getters. + const parsersForCustomParserApi = Object.keys(parsers).reduce( + (object, parserName) => + Object.defineProperty( + object, + parserName, + Object.getOwnPropertyDescriptor(parsers[parserName], "parse") + ), + {} + ); + + const parser = resolveParser(parsers, opts); + + try { + return parser.parse(text, parsersForCustomParserApi, opts); + } catch (error) { + const loc = error.loc; + + if (loc) { + const codeFrame = require("@babel/code-frame"); + error.codeFrame = codeFrame.codeFrameColumns(text, loc, { + highlightCode: true + }); + error.message += "\n" + error.codeFrame; + throw error; + } + + /* istanbul ignore next */ + throw error.stack; + } +} + +module.exports = { getParsers, parse, resolveParser }; diff --git a/src/parser.js b/src/parser.js deleted file mode 100644 index b7eb6fe3..00000000 --- a/src/parser.js +++ /dev/null @@ -1,83 +0,0 @@ -"use strict"; - -const path = require("path"); -const ConfigError = require("./errors").ConfigError; - -const parsers = { - get flow() { - return eval("require")("./parser-flow"); - }, - get graphql() { - return eval("require")("./parser-graphql"); - }, - get parse5() { - return eval("require")("./parser-parse5"); - }, - get babylon() { - return eval("require")("./parser-babylon"); - }, - get typescript() { - return eval("require")("./parser-typescript"); - }, - get css() { - return eval("require")("./parser-postcss"); - }, - get less() { - return eval("require")("./parser-postcss"); - }, - get scss() { - return eval("require")("./parser-postcss"); - }, - get json() { - return eval("require")("./parser-babylon"); - }, - get markdown() { - return eval("require")("./parser-markdown"); - }, - get vue() { - return eval("require")("./parser-vue"); - } -}; - -function resolveParseFunction(opts) { - if (typeof opts.parser === "function") { - return opts.parser; - } - if (typeof opts.parser === "string") { - if (parsers.hasOwnProperty(opts.parser)) { - return parsers[opts.parser]; - } - try { - return eval("require")(path.resolve(process.cwd(), opts.parser)); - } catch (err) { - /* istanbul ignore next */ - throw new ConfigError(`Couldn't resolve parser "${opts.parser}"`); - } - } - /* istanbul ignore next */ - return parsers.babylon; -} - -function parse(text, opts) { - const parseFunction = resolveParseFunction(opts); - - try { - return parseFunction(text, parsers, opts); - } catch (error) { - const loc = error.loc; - - if (loc) { - const codeFrame = require("@babel/code-frame"); - error.codeFrame = codeFrame.codeFrameColumns(text, loc, { - highlightCode: true - }); - error.message += "\n" + error.codeFrame; - throw error; - } - - /* istanbul ignore next */ - throw error.stack; - } -} - -module.exports = { parse }; diff --git a/src/support.js b/src/support.js deleted file mode 100644 index 81c91e2f..00000000 --- a/src/support.js +++ /dev/null @@ -1,229 +0,0 @@ -"use strict"; - -const semver = require("semver"); -const currentVersion = require("../package.json").version; - -// Based on: -// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml - -const supportTable = [ - { - name: "JavaScript", - since: "0.0.0", - parsers: ["babylon", "flow"], - group: "JavaScript", - tmScope: "source.js", - aceMode: "javascript", - codemirrorMode: "javascript", - codemirrorMimeType: "text/javascript", - aliases: ["js", "node"], - extensions: [ - ".js", - "._js", - ".bones", - ".es", - ".es6", - ".frag", - ".gs", - ".jake", - ".jsb", - ".jscad", - ".jsfl", - ".jsm", - ".jss", - ".mjs", - ".njs", - ".pac", - ".sjs", - ".ssjs", - ".xsjs", - ".xsjslib" - ], - filenames: ["Jakefile"], - linguistLanguageId: 183, - vscodeLanguageIds: ["javascript"] - }, - { - name: "JSX", - since: "0.0.0", - parsers: ["babylon", "flow"], - group: "JavaScript", - extensions: [".jsx"], - tmScope: "source.js.jsx", - aceMode: "javascript", - codemirrorMode: "jsx", - codemirrorMimeType: "text/jsx", - liguistLanguageId: 178, - vscodeLanguageIds: ["javascriptreact"] - }, - { - name: "TypeScript", - since: "1.4.0", - parsers: ["typescript"], - group: "JavaScript", - aliases: ["ts"], - extensions: [".ts", ".tsx"], - tmScope: "source.ts", - aceMode: "typescript", - codemirrorMode: "javascript", - codemirrorMimeType: "application/typescript", - liguistLanguageId: 378, - vscodeLanguageIds: ["typescript", "typescriptreact"] - }, - { - name: "CSS", - since: "1.4.0", - parsers: ["css"], - group: "CSS", - tmScope: "source.css", - aceMode: "css", - codemirrorMode: "css", - codemirrorMimeType: "text/css", - extensions: [".css", ".pcss", ".postcss"], - liguistLanguageId: 50, - vscodeLanguageIds: ["css", "postcss"] - }, - { - name: "Less", - since: "1.4.0", - parsers: ["less"], - group: "CSS", - extensions: [".less"], - tmScope: "source.css.less", - aceMode: "less", - codemirrorMode: "css", - codemirrorMimeType: "text/css", - liguistLanguageId: 198, - vscodeLanguageIds: ["less"] - }, - { - name: "SCSS", - since: "1.4.0", - parsers: ["scss"], - group: "CSS", - tmScope: "source.scss", - aceMode: "scss", - codemirrorMode: "css", - codemirrorMimeType: "text/x-scss", - extensions: [".scss"], - liguistLanguageId: 329, - vscodeLanguageIds: ["scss"] - }, - { - name: "GraphQL", - since: "1.5.0", - parsers: ["graphql"], - extensions: [".graphql", ".gql"], - tmScope: "source.graphql", - aceMode: "text", - liguistLanguageId: 139, - vscodeLanguageIds: ["graphql"] - }, - { - name: "JSON", - since: "1.5.0", - parsers: ["json"], - group: "JavaScript", - tmScope: "source.json", - aceMode: "json", - codemirrorMode: "javascript", - codemirrorMimeType: "application/json", - extensions: [ - ".json", - ".json5", - ".geojson", - ".JSON-tmLanguage", - ".topojson" - ], - filenames: [ - ".arcconfig", - ".jshintrc", - ".babelrc", - ".eslintrc", - ".prettierrc", - "composer.lock", - "mcmod.info" - ], - linguistLanguageId: 174, - vscodeLanguageIds: ["json", "jsonc"] - }, - - { - name: "Markdown", - since: "1.8.0", - parsers: ["markdown"], - aliases: ["pandoc"], - aceMode: "markdown", - codemirrorMode: "gfm", - codemirrorMimeType: "text/x-gfm", - wrap: true, - extensions: [ - ".md", - ".markdown", - ".mdown", - ".mdwn", - ".mkd", - ".mkdn", - ".mkdown", - ".ron", - ".workbook" - ], - filenames: ["README"], - tmScope: "source.gfm", - linguistLanguageId: 222, - vscodeLanguageIds: ["markdown"] - }, - { - name: "Vue", - since: "1.10.0", - parsers: ["vue"], - group: "HTML", - tmScope: "text.html.vue", - aceMode: "html", - codemirrorMode: "htmlmixed", - codemirrorMimeType: "text/html", - extensions: [".vue"], - linguistLanguageId: 146, - vscodeLanguageIds: ["vue"] - }, - { - name: "HTML", - since: undefined, // unreleased - parsers: ["parse5"], - group: "HTML", - tmScope: "text.html.basic", - aceMode: "html", - codemirrorMode: "htmlmixed", - codemirrorMimeType: "text/html", - aliases: ["xhtml"], - extensions: [".html", ".htm", ".html.hl", ".inc", ".st", ".xht", ".xhtml"], - linguistLanguageId: 146, - vscodeLanguageIds: ["html"] - } -]; - -function getSupportInfo(version) { - if (!version) { - version = currentVersion; - } - - const usePostCssParser = semver.lt(version, "1.7.1"); - - const languages = supportTable - .filter(language => language.since && semver.gte(version, language.since)) - .map(language => { - if (usePostCssParser && language.group === "CSS") { - return Object.assign({}, language, { - parsers: ["postcss"] - }); - } - return language; - }); - - return { languages }; -} - -module.exports = { - supportTable, - getSupportInfo -}; diff --git a/tests/multiparser_vue/__snapshots__/jsfmt.spec.js.snap b/tests/multiparser_vue/__snapshots__/jsfmt.spec.js.snap index ebefd542..938d84cc 100644 --- a/tests/multiparser_vue/__snapshots__/jsfmt.spec.js.snap +++ b/tests/multiparser_vue/__snapshots__/jsfmt.spec.js.snap @@ -10,15 +10,10 @@ exports[`template-bind.vue 1`] = ` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ `; @@ -34,14 +29,12 @@ exports[`template-class.vue 1`] = ` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ `; @@ -66,25 +59,24 @@ p { font-size : 2em ; text-align : center ; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +p { + font-size: 2em; + text-align: center; +} + `; diff --git a/tests_config/run_spec.js b/tests_config/run_spec.js index ef9fa4fb..bfc649e5 100644 --- a/tests_config/run_spec.js +++ b/tests_config/run_spec.js @@ -3,8 +3,8 @@ const fs = require("fs"); const extname = require("path").extname; const prettier = require("./require_prettier"); -const parser = require("../src/parser"); -const massageAST = require("../src/clean-ast.js").massageAST; +const parser = require("../src/main/parser"); +const massageAST = require("../src/common/clean-ast.js").massageAST; const AST_COMPARE = process.env["AST_COMPARE"]; const VERIFY_ALL_PARSERS = process.env["VERIFY_ALL_PARSERS"] || false; diff --git a/tests_integration/__tests__/__snapshots__/early-exit.js.snap b/tests_integration/__tests__/__snapshots__/early-exit.js.snap index 5f33a940..65884236 100644 --- a/tests_integration/__tests__/__snapshots__/early-exit.js.snap +++ b/tests_integration/__tests__/__snapshots__/early-exit.js.snap @@ -288,6 +288,17 @@ Default: babylon exports[`show detailed usage with --help parser (write) 1`] = `Array []`; +exports[`show detailed usage with --help plugin (stderr) 1`] = `""`; + +exports[`show detailed usage with --help plugin (stdout) 1`] = ` +"--plugin + + Add a plugin. Multiple plugins can be passed as separate \`--plugin\`s. +" +`; + +exports[`show detailed usage with --help plugin (write) 1`] = `Array []`; + exports[`show detailed usage with --help print-width (stderr) 1`] = `""`; exports[`show detailed usage with --help print-width (stdout) 1`] = ` @@ -553,6 +564,7 @@ Config options: Find and print the path to a configuration file for the given input file. --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. --with-node-modules Process files inside 'node_modules' directory. Editor options: @@ -693,6 +705,7 @@ Config options: Find and print the path to a configuration file for the given input file. --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. --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 884927da..05b3a22c 100644 --- a/tests_integration/__tests__/__snapshots__/support-info.js.snap +++ b/tests_integration/__tests__/__snapshots__/support-info.js.snap @@ -93,6 +93,39 @@ Object { "typescriptreact", ], }, + Object { + "aceMode": "json", + "codemirrorMimeType": "application/json", + "codemirrorMode": "javascript", + "extensions": Array [ + ".json", + ".json5", + ".geojson", + ".JSON-tmLanguage", + ".topojson", + ], + "filenames": Array [ + ".arcconfig", + ".jshintrc", + ".babelrc", + ".eslintrc", + ".prettierrc", + "composer.lock", + "mcmod.info", + ], + "group": "JavaScript", + "linguistLanguageId": 174, + "name": "JSON", + "parsers": Array [ + "json", + ], + "since": "1.5.0", + "tmScope": "source.json", + "vscodeLanguageIds": Array [ + "json", + "jsonc", + ], + }, Object { "aceMode": "css", "codemirrorMimeType": "text/css", @@ -170,39 +203,6 @@ Object { "graphql", ], }, - Object { - "aceMode": "json", - "codemirrorMimeType": "application/json", - "codemirrorMode": "javascript", - "extensions": Array [ - ".json", - ".json5", - ".geojson", - ".JSON-tmLanguage", - ".topojson", - ], - "filenames": Array [ - ".arcconfig", - ".jshintrc", - ".babelrc", - ".eslintrc", - ".prettierrc", - "composer.lock", - "mcmod.info", - ], - "group": "JavaScript", - "linguistLanguageId": 174, - "name": "JSON", - "parsers": Array [ - "json", - ], - "since": "1.5.0", - "tmScope": "source.json", - "vscodeLanguageIds": Array [ - "json", - "jsonc", - ], - }, Object { "aceMode": "markdown", "aliases": Array [ @@ -455,6 +455,34 @@ exports[`CLI --support-info (stdout) 1`] = ` \\"liguistLanguageId\\": 378, \\"vscodeLanguageIds\\": [\\"typescript\\", \\"typescriptreact\\"] }, + { + \\"name\\": \\"JSON\\", + \\"since\\": \\"1.5.0\\", + \\"parsers\\": [\\"json\\"], + \\"group\\": \\"JavaScript\\", + \\"tmScope\\": \\"source.json\\", + \\"aceMode\\": \\"json\\", + \\"codemirrorMode\\": \\"javascript\\", + \\"codemirrorMimeType\\": \\"application/json\\", + \\"extensions\\": [ + \\".json\\", + \\".json5\\", + \\".geojson\\", + \\".JSON-tmLanguage\\", + \\".topojson\\" + ], + \\"filenames\\": [ + \\".arcconfig\\", + \\".jshintrc\\", + \\".babelrc\\", + \\".eslintrc\\", + \\".prettierrc\\", + \\"composer.lock\\", + \\"mcmod.info\\" + ], + \\"linguistLanguageId\\": 174, + \\"vscodeLanguageIds\\": [\\"json\\", \\"jsonc\\"] + }, { \\"name\\": \\"CSS\\", \\"since\\": \\"1.4.0\\", @@ -504,34 +532,6 @@ exports[`CLI --support-info (stdout) 1`] = ` \\"liguistLanguageId\\": 139, \\"vscodeLanguageIds\\": [\\"graphql\\"] }, - { - \\"name\\": \\"JSON\\", - \\"since\\": \\"1.5.0\\", - \\"parsers\\": [\\"json\\"], - \\"group\\": \\"JavaScript\\", - \\"tmScope\\": \\"source.json\\", - \\"aceMode\\": \\"json\\", - \\"codemirrorMode\\": \\"javascript\\", - \\"codemirrorMimeType\\": \\"application/json\\", - \\"extensions\\": [ - \\".json\\", - \\".json5\\", - \\".geojson\\", - \\".JSON-tmLanguage\\", - \\".topojson\\" - ], - \\"filenames\\": [ - \\".arcconfig\\", - \\".jshintrc\\", - \\".babelrc\\", - \\".eslintrc\\", - \\".prettierrc\\", - \\"composer.lock\\", - \\"mcmod.info\\" - ], - \\"linguistLanguageId\\": 174, - \\"vscodeLanguageIds\\": [\\"json\\", \\"jsonc\\"] - }, { \\"name\\": \\"Markdown\\", \\"since\\": \\"1.8.0\\", diff --git a/tests_integration/__tests__/early-exit.js b/tests_integration/__tests__/early-exit.js index 43dedb85..c71e8478 100644 --- a/tests_integration/__tests__/early-exit.js +++ b/tests_integration/__tests__/early-exit.js @@ -2,7 +2,7 @@ const prettier = require("../../tests_config/require_prettier"); const runPrettier = require("../runPrettier"); -const constant = require("../../src/cli-constant"); +const constant = require("../../src/cli/constant"); describe("show version with --version", () => { runPrettier("cli/with-shebang", ["--version"]).test({ diff --git a/tests_integration/runPrettier.js b/tests_integration/runPrettier.js index e55a974a..0218d77c 100644 --- a/tests_integration/runPrettier.js +++ b/tests_integration/runPrettier.js @@ -3,11 +3,13 @@ const fs = require("fs"); const path = require("path"); const stripAnsi = require("strip-ansi"); -const ENV_LOG_LEVEL = require("../src/cli-logger").ENV_LOG_LEVEL; +const ENV_LOG_LEVEL = require("../src/cli/logger").ENV_LOG_LEVEL; const isProduction = process.env.NODE_ENV === "production"; const prettierCli = isProduction ? "../dist/bin/prettier" : "../bin/prettier"; -const thirdParty = isProduction ? "../dist/third-party" : "../src/third-party"; +const thirdParty = isProduction + ? "../dist/third-party" + : "../src/common/third-party"; function runPrettier(dir, args, options) { args = args || []; diff --git a/website/sidebars.json b/website/sidebars.json index d1289a57..aeebc39e 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -10,6 +10,7 @@ "Usage": [ "install", "cli", + "plugins", "precommit", "watching-files", "eslint", diff --git a/yarn.lock b/yarn.lock index d3190b46..e01a669d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -52,6 +52,10 @@ acorn@^5.0.0, acorn@^5.0.1: version "5.0.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d" +acorn@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7" + ajv-keywords@^1.0.0, ajv-keywords@^1.1.1: version "1.5.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" @@ -1635,6 +1639,10 @@ estree-walker@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.3.1.tgz#e6b1a51cf7292524e7237c312e5fe6660c1ce1aa" +estree-walker@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.1.tgz#64fc375053abc6f57d73e9bd2f004644ad3c5854" + esutils@2.0.2, esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -2921,12 +2929,6 @@ magic-string@^0.16.0: dependencies: vlq "^0.2.1" -magic-string@^0.19.0: - version "0.19.1" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.19.1.tgz#14d768013caf2ec8fdea16a49af82fc377e75201" - dependencies: - vlq "^0.2.1" - magic-string@^0.22.4: version "0.22.4" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.4.tgz#31039b4e40366395618c1d6cf8193c53917475ff" @@ -3847,7 +3849,13 @@ resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.2.0: +resolve@1.5.0, resolve@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + dependencies: + path-parse "^1.0.5" + +resolve@^1.1.6, resolve@^1.2.0: version "1.3.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" dependencies: @@ -3885,14 +3893,14 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^2.0.0" inherits "^2.0.1" -rollup-plugin-commonjs@7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-7.0.2.tgz#d8778939570d1cb8c1d02fe62533bc2e454f329a" +rollup-plugin-commonjs@8.2.6: + version "8.2.6" + resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.2.6.tgz#27e5b9069ff94005bb01e01bb46a1e4873784677" dependencies: - acorn "^4.0.1" - estree-walker "^0.3.0" - magic-string "^0.19.0" - resolve "^1.1.7" + acorn "^5.2.1" + estree-walker "^0.5.0" + magic-string "^0.22.4" + resolve "^1.4.0" rollup-pluginutils "^2.0.1" rollup-plugin-json@2.1.1: @@ -3950,11 +3958,9 @@ rollup-pluginutils@^2.0.1: estree-walker "^0.3.0" micromatch "^2.3.11" -rollup@0.41.6: - version "0.41.6" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.41.6.tgz#e0d05497877a398c104d816d2733a718a7a94e2a" - dependencies: - source-map-support "^0.4.0" +rollup@0.47.6: + version "0.47.6" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.47.6.tgz#83b90a1890dd3321a3f8d0b2bd216e88483f33de" run-async@^2.2.0: version "2.3.0" @@ -4072,18 +4078,18 @@ source-list-map@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.2.tgz#9889019d1024cce55cdc069498337ef6186a11a1" -source-map-support@^0.4.0, source-map-support@^0.4.2: - version "0.4.15" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" - dependencies: - source-map "^0.5.6" - source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" dependencies: source-map "^0.5.6" +source-map-support@^0.4.2: + version "0.4.15" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" + dependencies: + source-map "^0.5.6" + source-map@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"