From d5e5d664075727b0d2e5adca274368f785a2ce17 Mon Sep 17 00:00:00 2001 From: Will Binns-Smith Date: Wed, 13 Sep 2017 09:03:18 -0700 Subject: [PATCH] Add option to require @prettier or @format pragma (#2772) * Add option to require @prettier or @format pragma Fixes #2397. Inspired by `eslint-plugin-prettier` and the discussion in #2397, this implements requiring a special comment pragma to be present in a file's first comment in order to be formatted. This will help large codebases gradually transition to prettier over time without tons of churn or large code reviews. I implemented this as a standard prettier "option", not just a typical `argv` flag, as it is relevant in both the cli and the api. This way it can be provided programmatically, on the command line, or standardized in a prettierrc file so like the style options, every user can use this setting consistently and only apply prettier to relevant files, no mattier their editor integration. This requires the pragma begin with `@` (in fact it's inserted if the user doesn't provide it). Currently the usage implies it must be "prettier" or "format", but it can technically be any value other than "none", which is similar to the `trailingCommas` option. cc @vjeux * Don't quote anything in runPrettier; this is usually handled by a shell * Make --require-pragma a boolean option * Use jest-docblock to find pragmas without parsing the ast * Clarify docs * includes -> indexOf * Move test out of integration --- README.md | 13 +++- docs/options.md | 23 ++++++++ index.js | 10 ++++ package.json | 1 + src/cli-constant.js | 5 +- src/cli-util.js | 1 + src/options.js | 1 + .../__snapshots__/jsfmt.spec.js.snap | 59 +++++++++++++++++++ tests/require-pragma/jsfmt.spec.js | 1 + tests/require-pragma/module-with-pragma.js | 14 +++++ tests/require-pragma/module-without-pragma.js | 13 ++++ .../__snapshots__/early-exit.js.snap | 2 + yarn.lock | 4 ++ 13 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 tests/require-pragma/__snapshots__/jsfmt.spec.js.snap create mode 100644 tests/require-pragma/jsfmt.spec.js create mode 100644 tests/require-pragma/module-with-pragma.js create mode 100644 tests/require-pragma/module-without-pragma.js diff --git a/README.md b/README.md index 937fdcdc9..3d6d85313 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,17 @@ you can pass `--no-config` instead. Path to a file containing patterns that describe files to ignore. By default, prettier looks for `./.prettierignore`. +#### `--require-pragma` + +Require a special comment, called a pragma, to be present in the file's first docblock comment in order for prettier to format it. +```js +/** + * @prettier + */ +``` + +Valid pragmas are `@prettier` and `@format`. + #### `--list-different` Another useful flag is `--list-different` (or `-l`) which prints the filenames of files that are different from Prettier formatting. If there are differences the script errors out, which is useful in a CI scenario. @@ -288,7 +299,7 @@ Do not look for a configuration file. The default settings will be used. #### `--config-precedence` -Defines how config file should be evaluated in combination of CLI options. +Defines how config file should be evaluated in combination of CLI options. **cli-override (default)** diff --git a/docs/options.md b/docs/options.md index 06406013d..67c339da8 100644 --- a/docs/options.md +++ b/docs/options.md @@ -133,3 +133,26 @@ Default | CLI Override | API Override --------|--------------|------------- None | `--stdin-filepath ` | `filepath: ""` +## Require pragma +Prettier can restrict itself to only format files that contain a special comment, called a pragma, at the top of the file. This is very useful +when gradually transitioning large, unformatted codebases to prettier. + +For example, a file with the following as its first comment will be formatted when `--require-pragma` is supplied: + +```js +/** + * @prettier + */ +``` + +or + +```js +/** + * @format + */ +``` + +Default | CLI Override | API Override +--------|--------------|------------- +None | `--require-pragma` | `requirePragma` diff --git a/index.js b/index.js index cb351acc6..25452ce78 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,7 @@ 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 docblock = require("jest-docblock"); function guessLineEnding(text) { const index = text.indexOf("\n"); @@ -30,6 +31,11 @@ function attachComments(text, ast, opts) { return astComments; } +function hasPragma(text) { + const pragmas = Object.keys(docblock.parse(docblock.extract(text))); + return pragmas.indexOf("prettier") !== -1 || pragmas.indexOf("format") !== -1; +} + function ensureAllCommentsPrinted(astComments) { if (!astComments) { return; @@ -56,6 +62,10 @@ function ensureAllCommentsPrinted(astComments) { } function formatWithCursor(text, opts, addAlignmentSize) { + if (opts.requirePragma && !hasPragma(text)) { + return { formatted: text }; + } + text = stripBom(text); addAlignmentSize = addAlignmentSize || 0; diff --git a/package.json b/package.json index 305b4f870..49db81f52 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "globby": "^6.1.0", "graphql": "0.10.1", "ignore": "^3.3.3", + "jest-docblock": "^21.0.2", "jest-validate": "20.0.3", "minimatch": "3.0.4", "minimist": "1.2.0", diff --git a/src/cli-constant.js b/src/cli-constant.js index 516475bd2..05de15a9a 100644 --- a/src/cli-constant.js +++ b/src/cli-constant.js @@ -7,7 +7,8 @@ const booleanOptionNames = [ "bracket-spacing", "jsx-bracket-same-line", // Deprecated in 0.0.10 - "flow-parser" + "flow-parser", + "require-pragma" ]; const stringOptionNames = [ @@ -86,6 +87,8 @@ Available options: Finds and prints the path to a configuration file for a given input file. --ignore-path Path to a file containing patterns that describe files to ignore. Defaults to ./.prettierignore. + --require-pragma + Require either '@prettier' or '@format' to be present in the file's first docblock comment in order for it to be formatted. --stdin Read input from stdin. --stdin-filepath Path to the file used to read from stdin. --print-width Specify the length of line that the printer will wrap on. Defaults to 80. diff --git a/src/cli-util.js b/src/cli-util.js index 3fd500017..c6b6952e1 100644 --- a/src/cli-util.js +++ b/src/cli-util.js @@ -25,6 +25,7 @@ function getOptions(argv) { printWidth: getIntOption(argv, "print-width"), tabWidth: getIntOption(argv, "tab-width"), bracketSpacing: argv["bracket-spacing"], + requirePragma: argv["require-pragma"], singleQuote: argv["single-quote"], jsxBracketSameLine: argv["jsx-bracket-same-line"], filepath: argv["stdin-filepath"], diff --git a/src/options.js b/src/options.js index 25ad8c865..fb824ae59 100644 --- a/src/options.js +++ b/src/options.js @@ -15,6 +15,7 @@ const defaults = { bracketSpacing: true, jsxBracketSameLine: false, parser: "babylon", + requirePragma: false, semi: true }; diff --git a/tests/require-pragma/__snapshots__/jsfmt.spec.js.snap b/tests/require-pragma/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..66392b09f --- /dev/null +++ b/tests/require-pragma/__snapshots__/jsfmt.spec.js.snap @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`module-with-pragma.js 1`] = ` +/** + * @flow + * @format + */ + +function foo(bar) + + +{ + + return bar + + 3 + + 4; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/** + * @flow + * @format + */ + +function foo(bar) { + return bar + 3 + 4; +} + +`; + +exports[`module-without-pragma.js 1`] = ` +/** + * @flow + */ + +function foo(bar) + + +{ + + return bar + + 3 + + 4; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/** + * @flow + */ + +function foo(bar) + + +{ + + return bar + + 3 + + 4; +} + +`; diff --git a/tests/require-pragma/jsfmt.spec.js b/tests/require-pragma/jsfmt.spec.js new file mode 100644 index 000000000..61c7d29f1 --- /dev/null +++ b/tests/require-pragma/jsfmt.spec.js @@ -0,0 +1 @@ +run_spec(__dirname, { requirePragma: true }); diff --git a/tests/require-pragma/module-with-pragma.js b/tests/require-pragma/module-with-pragma.js new file mode 100644 index 000000000..79499ba96 --- /dev/null +++ b/tests/require-pragma/module-with-pragma.js @@ -0,0 +1,14 @@ +/** + * @flow + * @format + */ + +function foo(bar) + + +{ + + return bar + + 3 + + 4; +} diff --git a/tests/require-pragma/module-without-pragma.js b/tests/require-pragma/module-without-pragma.js new file mode 100644 index 000000000..8ece37915 --- /dev/null +++ b/tests/require-pragma/module-without-pragma.js @@ -0,0 +1,13 @@ +/** + * @flow + */ + +function foo(bar) + + +{ + + return bar + + 3 + + 4; +} diff --git a/tests_integration/__tests__/__snapshots__/early-exit.js.snap b/tests_integration/__tests__/__snapshots__/early-exit.js.snap index 000979558..c41392dee 100644 --- a/tests_integration/__tests__/__snapshots__/early-exit.js.snap +++ b/tests_integration/__tests__/__snapshots__/early-exit.js.snap @@ -19,6 +19,8 @@ Available options: Finds and prints the path to a configuration file for a given input file. --ignore-path Path to a file containing patterns that describe files to ignore. Defaults to ./.prettierignore. + --require-pragma + Require either '@prettier' or '@format' to be present in the file's first docblock comment in order for it to be formatted. --stdin Read input from stdin. --stdin-filepath Path to the file used to read from stdin. --print-width Specify the length of line that the printer will wrap on. Defaults to 80. diff --git a/yarn.lock b/yarn.lock index fbdccb6d7..da7e212c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2258,6 +2258,10 @@ jest-docblock@^20.0.1, jest-docblock@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712" +jest-docblock@^21.0.2: + version "21.0.2" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.0.2.tgz#66f69ddb440799fc32f91d0ac3d8d35e99e2032f" + jest-environment-jsdom@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-20.0.3.tgz#048a8ac12ee225f7190417713834bb999787de99"