diff --git a/changelog_unreleased/flow/pr-9071.md b/changelog_unreleased/flow/pr-9071.md new file mode 100644 index 000000000..7859461a5 --- /dev/null +++ b/changelog_unreleased/flow/pr-9071.md @@ -0,0 +1,18 @@ +#### Switch the `babel` parser to `babel-flow` if the `@flow` pragma is found (#9071 by @fisker) + +In practice, this means that as long as your Flow files have the pragma, it's safe to use the `.js` extension for them. Prettier will correctly parse and print them without any additional configuration. Previously, the pragma was recognized by the parser, but there existed minor correctness issues with the printer. E.g. it's not safe to unquote number keys in Flow. + + +```jsx +// Input (with --parser babel) +// @flow +f({ "2": 2 }) + +// Prettier stable +// @flow +f({ 2: 2 }); + +// Prettier master +// @flow +f({ "2": 2 }); +``` diff --git a/src/language-js/parser-babel.js b/src/language-js/parser-babel.js index e0a2f6d06..6786ac635 100644 --- a/src/language-js/parser-babel.js +++ b/src/language-js/parser-babel.js @@ -1,6 +1,10 @@ "use strict"; const createError = require("../common/parser-create-error"); +const { + getNextNonSpaceNonCommentCharacterIndexWithStartIndex, + getShebang, +} = require("../common/util"); const { hasPragma } = require("./pragma"); const { locStart, locEnd } = require("./loc"); const postprocess = require("./postprocess"); @@ -58,13 +62,43 @@ function resolvePluginsConflict( return combinations; } +// Similar to babel +// https://github.com/babel/babel/pull/7934/files#diff-a739835084910b0ee3ea649df5a4d223R67 +const FLOW_PRAGMA_REGEX = /@(?:no)?flow\b/; +function isFlowFile(text, options) { + if (options.filepath && options.filepath.endsWith(".js.flow")) { + return true; + } + + const shebang = getShebang(text); + if (shebang) { + text = text.slice(shebang.length); + } + + const firstNonSpaceNonCommentCharacterIndex = getNextNonSpaceNonCommentCharacterIndexWithStartIndex( + text, + 0 + ); + + if (firstNonSpaceNonCommentCharacterIndex !== false) { + text = text.slice(0, firstNonSpaceNonCommentCharacterIndex); + } + + return FLOW_PRAGMA_REGEX.test(text); +} + function createParse(parseMethod, ...pluginCombinations) { - return (text, parsers, opts) => { + return (text, parsers, opts = {}) => { + if (opts.parser === "babel" && isFlowFile(text, opts)) { + opts.parser = "babel-flow"; + return parseFlow(text, parsers, opts); + } + // Inline the require to avoid loading all the JS if we don't use it const babel = require("@babel/parser"); const sourceType = - opts && opts.__babelSourceType === "script" ? "script" : "module"; + opts.__babelSourceType === "script" ? "script" : "module"; let ast; try { diff --git a/tests/misc/babel-redirect-to-babel-flow/__snapshots__/jsfmt.spec.js.snap b/tests/misc/babel-redirect-to-babel-flow/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..2de1edb6c --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/__snapshots__/jsfmt.spec.js.snap @@ -0,0 +1,333 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`block-comment.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +/* @flow */ + +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +/* @flow */ + +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; + +exports[`block-comment-2.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +/* @noflow */ + +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +/* @noflow */ + +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; + +exports[`filename.js.flow format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; + +exports[`inline-comment.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +// @flow + +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +// @flow + +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; + +exports[`inline-comment-2.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +// @noflow + +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +// @noflow + +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; + +exports[`inline-comment-3.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +// another comment +// @flow + +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +// another comment +// @flow + +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; + +exports[`like-a-pragma.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +// by fisker@flow.prettier.com :) + +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +// by fisker@flow.prettier.com :) + +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; + +exports[`not-flow.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +foo = {"1":bar} // "1" should unquoted + +=====================================output===================================== +foo = { 1: bar }; // "1" should unquoted + +================================================================================ +`; + +exports[`not-flow-2.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +// @flowflow + +foo = {"1":bar} // "1" should unquoted + +=====================================output===================================== +// @flowflow + +foo = { 1: bar }; // "1" should unquoted + +================================================================================ +`; + +exports[`not-flow-3.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +a = " @flow " +foo = {"1":bar} // "1" should unquoted + +=====================================output===================================== +a = " @flow "; +foo = { 1: bar }; // "1" should unquoted + +================================================================================ +`; + +exports[`not-flow-4.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +a = " // @flow " +foo = {"1":bar} // "1" should unquoted + +=====================================output===================================== +a = " // @flow "; +foo = { 1: bar }; // "1" should unquoted + +================================================================================ +`; + +exports[`not-flow-5.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +#!/usr/bin/env @flow +foo = {"1":bar} // "1" should unquoted + +=====================================output===================================== +#!/usr/bin/env @flow +foo = { 1: bar }; // "1" should unquoted + +================================================================================ +`; + +exports[`pragma.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +/** + * @flow + */ + +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +/** + * @flow + */ + +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; + +exports[`pragma-2.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +/** + * @noflow + */ + +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +/** + * @noflow + */ + +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; + +exports[`pragma-react.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// DocBlock above is copied from https://github.com/facebook/react/blob/8da0da0937af154b775b243c9d28b6aa50db696b/packages/react-dom/index.js#L1-L8 + +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// DocBlock above is copied from https://github.com/facebook/react/blob/8da0da0937af154b775b243c9d28b6aa50db696b/packages/react-dom/index.js#L1-L8 + +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; + +exports[`shebang-inline-comment.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +#!/usr/bin/env node +// another comment +// @flow + +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +#!/usr/bin/env node +// another comment +// @flow + +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; + +exports[`shebang-pragma.js format 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +#!/usr/bin/env node +/** + * @format + * @noflow + */ + +foo = {"1":bar} // "1" should quoted + +=====================================output===================================== +#!/usr/bin/env node +/** + * @format + * @noflow + */ + +foo = { "1": bar }; // "1" should quoted + +================================================================================ +`; diff --git a/tests/misc/babel-redirect-to-babel-flow/block-comment-2.js b/tests/misc/babel-redirect-to-babel-flow/block-comment-2.js new file mode 100644 index 000000000..33a86aa79 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/block-comment-2.js @@ -0,0 +1,3 @@ +/* @noflow */ + +foo = {"1":bar} // "1" should quoted diff --git a/tests/misc/babel-redirect-to-babel-flow/block-comment.js b/tests/misc/babel-redirect-to-babel-flow/block-comment.js new file mode 100644 index 000000000..95e0ec2c8 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/block-comment.js @@ -0,0 +1,3 @@ +/* @flow */ + +foo = {"1":bar} // "1" should quoted diff --git a/tests/misc/babel-redirect-to-babel-flow/filename.js.flow b/tests/misc/babel-redirect-to-babel-flow/filename.js.flow new file mode 100644 index 000000000..991b1b174 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/filename.js.flow @@ -0,0 +1 @@ +foo = {"1":bar} // "1" should quoted diff --git a/tests/misc/babel-redirect-to-babel-flow/inline-comment-2.js b/tests/misc/babel-redirect-to-babel-flow/inline-comment-2.js new file mode 100644 index 000000000..96b7d9432 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/inline-comment-2.js @@ -0,0 +1,3 @@ +// @noflow + +foo = {"1":bar} // "1" should quoted diff --git a/tests/misc/babel-redirect-to-babel-flow/inline-comment-3.js b/tests/misc/babel-redirect-to-babel-flow/inline-comment-3.js new file mode 100644 index 000000000..183de9f3d --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/inline-comment-3.js @@ -0,0 +1,4 @@ +// another comment +// @flow + +foo = {"1":bar} // "1" should quoted diff --git a/tests/misc/babel-redirect-to-babel-flow/inline-comment.js b/tests/misc/babel-redirect-to-babel-flow/inline-comment.js new file mode 100644 index 000000000..b4155e0be --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/inline-comment.js @@ -0,0 +1,3 @@ +// @flow + +foo = {"1":bar} // "1" should quoted diff --git a/tests/misc/babel-redirect-to-babel-flow/jsfmt.spec.js b/tests/misc/babel-redirect-to-babel-flow/jsfmt.spec.js new file mode 100644 index 000000000..8382eddeb --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/jsfmt.spec.js @@ -0,0 +1 @@ +run_spec(__dirname, ["babel"]); diff --git a/tests/misc/babel-redirect-to-babel-flow/like-a-pragma.js b/tests/misc/babel-redirect-to-babel-flow/like-a-pragma.js new file mode 100644 index 000000000..8e5751626 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/like-a-pragma.js @@ -0,0 +1,3 @@ +// by fisker@flow.prettier.com :) + +foo = {"1":bar} // "1" should quoted diff --git a/tests/misc/babel-redirect-to-babel-flow/not-flow-2.js b/tests/misc/babel-redirect-to-babel-flow/not-flow-2.js new file mode 100644 index 000000000..b21ed8d41 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/not-flow-2.js @@ -0,0 +1,3 @@ +// @flowflow + +foo = {"1":bar} // "1" should unquoted diff --git a/tests/misc/babel-redirect-to-babel-flow/not-flow-3.js b/tests/misc/babel-redirect-to-babel-flow/not-flow-3.js new file mode 100644 index 000000000..c2ab0a01d --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/not-flow-3.js @@ -0,0 +1,2 @@ +a = " @flow " +foo = {"1":bar} // "1" should unquoted diff --git a/tests/misc/babel-redirect-to-babel-flow/not-flow-4.js b/tests/misc/babel-redirect-to-babel-flow/not-flow-4.js new file mode 100644 index 000000000..7bac25a82 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/not-flow-4.js @@ -0,0 +1,2 @@ +a = " // @flow " +foo = {"1":bar} // "1" should unquoted diff --git a/tests/misc/babel-redirect-to-babel-flow/not-flow-5.js b/tests/misc/babel-redirect-to-babel-flow/not-flow-5.js new file mode 100644 index 000000000..8ccec954e --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/not-flow-5.js @@ -0,0 +1,2 @@ +#!/usr/bin/env @flow +foo = {"1":bar} // "1" should unquoted diff --git a/tests/misc/babel-redirect-to-babel-flow/not-flow.js b/tests/misc/babel-redirect-to-babel-flow/not-flow.js new file mode 100644 index 000000000..c1fcf46f0 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/not-flow.js @@ -0,0 +1 @@ +foo = {"1":bar} // "1" should unquoted diff --git a/tests/misc/babel-redirect-to-babel-flow/pragma-2.js b/tests/misc/babel-redirect-to-babel-flow/pragma-2.js new file mode 100644 index 000000000..e305ddb00 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/pragma-2.js @@ -0,0 +1,5 @@ +/** + * @noflow + */ + +foo = {"1":bar} // "1" should quoted diff --git a/tests/misc/babel-redirect-to-babel-flow/pragma-react.js b/tests/misc/babel-redirect-to-babel-flow/pragma-react.js new file mode 100644 index 000000000..bb4f4d5ab --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/pragma-react.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// DocBlock above is copied from https://github.com/facebook/react/blob/8da0da0937af154b775b243c9d28b6aa50db696b/packages/react-dom/index.js#L1-L8 + +foo = {"1":bar} // "1" should quoted diff --git a/tests/misc/babel-redirect-to-babel-flow/pragma.js b/tests/misc/babel-redirect-to-babel-flow/pragma.js new file mode 100644 index 000000000..f9604ee85 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/pragma.js @@ -0,0 +1,5 @@ +/** + * @flow + */ + +foo = {"1":bar} // "1" should quoted diff --git a/tests/misc/babel-redirect-to-babel-flow/shebang-inline-comment.js b/tests/misc/babel-redirect-to-babel-flow/shebang-inline-comment.js new file mode 100644 index 000000000..7bbd4dd77 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/shebang-inline-comment.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node +// another comment +// @flow + +foo = {"1":bar} // "1" should quoted diff --git a/tests/misc/babel-redirect-to-babel-flow/shebang-pragma.js b/tests/misc/babel-redirect-to-babel-flow/shebang-pragma.js new file mode 100644 index 000000000..f50f83789 --- /dev/null +++ b/tests/misc/babel-redirect-to-babel-flow/shebang-pragma.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node +/** + * @format + * @noflow + */ + +foo = {"1":bar} // "1" should quoted