Add support for flow typecast comments (#5280)

* Add support for flow typecast comments

* Allow whitespace between comment start and colon

Also rename flow-comments.js to utils.js

* fix bug where type annotations could be accidentally created

* use better regex in detecting flow comment whitespace

* fix comment in utils.js

* simplify conditionals and improve comments

* fix lint error
master
Ashwin Bhat 2018-10-23 07:46:20 -07:00 committed by Jed Fox
parent 559831d08e
commit 4d4fab39fe
5 changed files with 111 additions and 2 deletions

View File

@ -4,6 +4,7 @@ const assert = require("assert");
const util = require("../common/util");
const comments = require("./comments");
const { hasFlowShorthandAnnotationComment } = require("./utils");
function hasClosureCompilerTypeCastComment(text, path, locStart, locEnd) {
// https://github.com/google/closure-compiler/wiki/Annotating-Types#type-casts
@ -74,6 +75,16 @@ function needsParens(path, options) {
return true;
}
if (
// Preserve parens if we have a Flow annotation comment, unless we're using the Flow
// parser. The Flow parser turns Flow comments into type annotation nodes in its
// AST, which we handle separately.
options.parser !== "flow" &&
hasFlowShorthandAnnotationComment(path.getValue())
) {
return true;
}
// Identifiers never need parentheses.
if (node.type === "Identifier") {
return false;

View File

@ -34,6 +34,7 @@ const insertPragma = require("./pragma").insertPragma;
const handleComments = require("./comments");
const pathNeedsParens = require("./needs-parens");
const preprocess = require("./preprocess");
const { hasFlowShorthandAnnotationComment } = require("./utils");
const {
builders: {
@ -166,6 +167,14 @@ function genericPrint(path, options, printPath, args) {
parts.push(linesWithoutParens);
if (needsParens) {
const node = path.getValue();
if (hasFlowShorthandAnnotationComment(node)) {
parts.push(" /*");
parts.push(node.trailingComments[0].value.trimLeft());
parts.push("*/");
node.trailingComments[0].printed = true;
}
parts.push(")");
}
@ -2836,14 +2845,36 @@ function printPathNoParens(path, options, print, args) {
return group(concat(parts));
}
case "TypeCastExpression":
case "TypeCastExpression": {
const value = path.getValue();
// Flow supports a comment syntax for specifying type annotations: https://flow.org/en/docs/types/comments/.
// Unfortunately, its parser doesn't differentiate between comment annotations and regular
// annotations when producing an AST. So to preserve parentheses around type casts that use
// the comment syntax, we need to hackily read the source itself to see if the code contains
// a type annotation comment.
//
// Note that we're able to use the normal whitespace regex here because the Flow parser has
// already deemed this AST node to be a type cast. Only the Babylon parser needs the
// non-line-break whitespace regex, which is why hasFlowShorthandAnnotationComment() is
// implemented differently.
const commentSyntax =
value &&
value.typeAnnotation &&
value.typeAnnotation.range &&
options.originalText
.substring(value.typeAnnotation.range[0])
.match(/^\/\*\s*:/);
return concat([
"(",
path.call(print, "expression"),
commentSyntax ? " /*" : "",
": ",
path.call(print, "typeAnnotation"),
commentSyntax ? " */" : "",
")"
]);
}
case "TypeParameterDeclaration":
case "TypeParameterInstantiation":
case "TSTypeParameterDeclaration":
@ -5994,7 +6025,7 @@ function willPrintOwnComments(path) {
const parent = path.getParentNode();
return (
((node && isJSXNode(node)) ||
((node && (isJSXNode(node) || hasFlowShorthandAnnotationComment(node))) ||
(parent &&
(parent.type === "JSXSpreadAttribute" ||
parent.type === "JSXSpreadChild" ||

26
src/language-js/utils.js Normal file
View File

@ -0,0 +1,26 @@
"use strict";
function hasFlowShorthandAnnotationComment(node) {
// https://flow.org/en/docs/types/comments/
// Syntax example: const r = new (window.Request /*: Class<Request> */)("");
return (
node.extra &&
node.extra.parenthesized &&
node.trailingComments &&
// We match any whitespace except line terminators because
// Flow annotation comments cannot be split across lines. For example:
//
// (this /*
// : any */).foo = 5;
//
// is not picked up by Flow (see https://github.com/facebook/flow/issues/7050), so
// removing the newline would create a type annotation that the user did not intend
// to create.
node.trailingComments[0].value.match(/^(?:(?=.)\s)*:/)
);
}
module.exports = {
hasFlowShorthandAnnotationComment
};

View File

@ -85,3 +85,32 @@ type Props = {
};
`;
exports[`type_annotations.js - flow-verify 1`] = `
new (window.Request/*: Class<Request> */)("");
(this/*: any */).foo = 5;
(this/* : any */).foo = 5;
// This next example illustrates that Prettier will not remove a line break
// and unintentionally create a type annotation that was not there before.
(this/*
: any */).foo = 5;
const x = (input /*: string */) /*: string */ => {};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
new (window.Request /*: Class<Request> */)("");
(this /*: any */).foo = 5;
(this /*: any */).foo = 5;
// This next example illustrates that Prettier will not remove a line break
// and unintentionally create a type annotation that was not there before.
this /*
: any */.foo = 5;
const x = (input /*: string */) /*: string */ => {};
`;

View File

@ -0,0 +1,12 @@
new (window.Request/*: Class<Request> */)("");
(this/*: any */).foo = 5;
(this/* : any */).foo = 5;
// This next example illustrates that Prettier will not remove a line break
// and unintentionally create a type annotation that was not there before.
(this/*
: any */).foo = 5;
const x = (input /*: string */) /*: string */ => {};