From f5e96f7d83cb6f938d3d71512d451d736da27dde Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 1 Sep 2020 11:23:47 +0800 Subject: [PATCH] Redirect `babel` parser to `babel-flow` (#9071) Co-authored-by: Georgii Dolzhykov --- changelog_unreleased/flow/pr-9071.md | 18 + src/language-js/parser-babel.js | 38 +- .../__snapshots__/jsfmt.spec.js.snap | 333 ++++++++++++++++++ .../block-comment-2.js | 3 + .../block-comment.js | 3 + .../filename.js.flow | 1 + .../inline-comment-2.js | 3 + .../inline-comment-3.js | 4 + .../inline-comment.js | 3 + .../jsfmt.spec.js | 1 + .../like-a-pragma.js | 3 + .../not-flow-2.js | 3 + .../not-flow-3.js | 2 + .../not-flow-4.js | 2 + .../not-flow-5.js | 2 + .../babel-redirect-to-babel-flow/not-flow.js | 1 + .../babel-redirect-to-babel-flow/pragma-2.js | 5 + .../pragma-react.js | 12 + .../babel-redirect-to-babel-flow/pragma.js | 5 + .../shebang-inline-comment.js | 5 + .../shebang-pragma.js | 7 + 21 files changed, 452 insertions(+), 2 deletions(-) create mode 100644 changelog_unreleased/flow/pr-9071.md create mode 100644 tests/misc/babel-redirect-to-babel-flow/__snapshots__/jsfmt.spec.js.snap create mode 100644 tests/misc/babel-redirect-to-babel-flow/block-comment-2.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/block-comment.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/filename.js.flow create mode 100644 tests/misc/babel-redirect-to-babel-flow/inline-comment-2.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/inline-comment-3.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/inline-comment.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/jsfmt.spec.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/like-a-pragma.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/not-flow-2.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/not-flow-3.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/not-flow-4.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/not-flow-5.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/not-flow.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/pragma-2.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/pragma-react.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/pragma.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/shebang-inline-comment.js create mode 100644 tests/misc/babel-redirect-to-babel-flow/shebang-pragma.js 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