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 errormaster
parent
559831d08e
commit
4d4fab39fe
|
@ -4,6 +4,7 @@ const assert = require("assert");
|
||||||
|
|
||||||
const util = require("../common/util");
|
const util = require("../common/util");
|
||||||
const comments = require("./comments");
|
const comments = require("./comments");
|
||||||
|
const { hasFlowShorthandAnnotationComment } = require("./utils");
|
||||||
|
|
||||||
function hasClosureCompilerTypeCastComment(text, path, locStart, locEnd) {
|
function hasClosureCompilerTypeCastComment(text, path, locStart, locEnd) {
|
||||||
// https://github.com/google/closure-compiler/wiki/Annotating-Types#type-casts
|
// https://github.com/google/closure-compiler/wiki/Annotating-Types#type-casts
|
||||||
|
@ -74,6 +75,16 @@ function needsParens(path, options) {
|
||||||
return true;
|
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.
|
// Identifiers never need parentheses.
|
||||||
if (node.type === "Identifier") {
|
if (node.type === "Identifier") {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -34,6 +34,7 @@ const insertPragma = require("./pragma").insertPragma;
|
||||||
const handleComments = require("./comments");
|
const handleComments = require("./comments");
|
||||||
const pathNeedsParens = require("./needs-parens");
|
const pathNeedsParens = require("./needs-parens");
|
||||||
const preprocess = require("./preprocess");
|
const preprocess = require("./preprocess");
|
||||||
|
const { hasFlowShorthandAnnotationComment } = require("./utils");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
builders: {
|
builders: {
|
||||||
|
@ -166,6 +167,14 @@ function genericPrint(path, options, printPath, args) {
|
||||||
parts.push(linesWithoutParens);
|
parts.push(linesWithoutParens);
|
||||||
|
|
||||||
if (needsParens) {
|
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(")");
|
parts.push(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2836,14 +2845,36 @@ function printPathNoParens(path, options, print, args) {
|
||||||
|
|
||||||
return group(concat(parts));
|
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([
|
return concat([
|
||||||
"(",
|
"(",
|
||||||
path.call(print, "expression"),
|
path.call(print, "expression"),
|
||||||
|
commentSyntax ? " /*" : "",
|
||||||
": ",
|
": ",
|
||||||
path.call(print, "typeAnnotation"),
|
path.call(print, "typeAnnotation"),
|
||||||
|
commentSyntax ? " */" : "",
|
||||||
")"
|
")"
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
case "TypeParameterDeclaration":
|
case "TypeParameterDeclaration":
|
||||||
case "TypeParameterInstantiation":
|
case "TypeParameterInstantiation":
|
||||||
case "TSTypeParameterDeclaration":
|
case "TSTypeParameterDeclaration":
|
||||||
|
@ -5994,7 +6025,7 @@ function willPrintOwnComments(path) {
|
||||||
const parent = path.getParentNode();
|
const parent = path.getParentNode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
((node && isJSXNode(node)) ||
|
((node && (isJSXNode(node) || hasFlowShorthandAnnotationComment(node))) ||
|
||||||
(parent &&
|
(parent &&
|
||||||
(parent.type === "JSXSpreadAttribute" ||
|
(parent.type === "JSXSpreadAttribute" ||
|
||||||
parent.type === "JSXSpreadChild" ||
|
parent.type === "JSXSpreadChild" ||
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
|
@ -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 */ => {};
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
|
@ -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 */ => {};
|
Loading…
Reference in New Issue