Fix flow comments plugin issues (#10329)

* Fix issues in flow-comments to preserve comments and there order (fixes #10324)

* Add support in flow-comments for extends in class declarations (fixes #10323, #10321)

* Refactoring and cleanup of flow-comments plugin

* Fix comments preservation logic of flow-comments

* Fix flow-comments where comments are class identifier and extends keyword
This commit is contained in:
Ziad El Khoury Hanna 2019-08-14 15:46:26 +02:00 committed by Nicolò Ribaudo
parent 469a5a71cd
commit 64041e1669
11 changed files with 222 additions and 88 deletions

View File

@ -6,27 +6,73 @@ import generateCode from "@babel/generator";
export default declare(api => { export default declare(api => {
api.assertVersion(7); api.assertVersion(7);
function attachComment(path, comment) { function commentFromString(comment) {
let attach = path.getPrevSibling(); return typeof comment === "string"
let where = "trailing"; ? { type: "CommentBlock", value: comment }
if (!attach.node) { : comment;
attach = path.parentPath; }
function attachComment({
ofPath,
toPath,
where = "trailing",
optional = false,
comments = generateComment(ofPath, optional),
keepType = false,
}) {
if (!toPath || !toPath.node) {
toPath = ofPath.getPrevSibling();
where = "trailing";
}
if (!toPath.node) {
toPath = ofPath.getNextSibling();
where = "leading";
}
if (!toPath.node) {
toPath = ofPath.parentPath;
where = "inner"; where = "inner";
} }
attach.addComment(where, comment); if (!Array.isArray(comments)) {
path.remove(); comments = [comments];
}
comments = comments.map(commentFromString);
if (!keepType && ofPath && ofPath.node) {
// Removes the node at `ofPath` while conserving the comments attached
// to it.
const node = ofPath.node;
const parent = ofPath.parentPath;
const prev = ofPath.getPrevSibling();
const next = ofPath.getNextSibling();
const isSingleChild = !(prev.node || next.node);
const leading = node.leadingComments;
const trailing = node.trailingComments;
if (isSingleChild && leading) {
parent.addComments("inner", leading);
}
toPath.addComments(where, comments);
ofPath.remove();
if (isSingleChild && trailing) {
parent.addComments("inner", trailing);
}
} else {
toPath.addComments(where, comments);
}
} }
function wrapInFlowComment(path, parent) { function wrapInFlowComment(path) {
attachComment(path, generateComment(path, parent)); attachComment({
ofPath: path,
comments: generateComment(path, path.parent.optional),
});
} }
function generateComment(path, parent) { function generateComment(path, optional) {
let comment = path let comment = path
.getSource() .getSource()
.replace(/\*-\//g, "*-ESCAPED/") .replace(/\*-\//g, "*-ESCAPED/")
.replace(/\*\//g, "*-/"); .replace(/\*\//g, "*-/");
if (parent && parent.optional) comment = "?" + comment; if (optional) comment = "?" + comment;
if (comment[0] !== ":") comment = ":: " + comment; if (comment[0] !== ":") comment = ":: " + comment;
return comment; return comment;
} }
@ -42,28 +88,32 @@ export default declare(api => {
visitor: { visitor: {
TypeCastExpression(path) { TypeCastExpression(path) {
const { node } = path; const { node } = path;
path attachComment({
.get("expression") ofPath: path.get("typeAnnotation"),
.addComment("trailing", generateComment(path.get("typeAnnotation"))); toPath: path.get("expression"),
keepType: true,
});
path.replaceWith(t.parenthesizedExpression(node.expression)); path.replaceWith(t.parenthesizedExpression(node.expression));
}, },
// support function a(b?) {} // support function a(b?) {}
Identifier(path) { Identifier(path) {
if (path.parentPath.isFlow()) { if (path.parentPath.isFlow()) return;
return;
}
const { node } = path; const { node } = path;
if (node.typeAnnotation) { if (node.typeAnnotation) {
const typeAnnotation = path.get("typeAnnotation"); attachComment({
path.addComment("trailing", generateComment(typeAnnotation, node)); ofPath: path.get("typeAnnotation"),
typeAnnotation.remove(); toPath: path,
optional: node.optional || node.typeAnnotation.optional,
});
if (node.optional) { if (node.optional) {
node.optional = false; node.optional = false;
} }
} else if (node.optional) { } else if (node.optional) {
path.addComment("trailing", ":: ?"); attachComment({
toPath: path,
comments: ":: ?",
});
node.optional = false; node.optional = false;
} }
}, },
@ -81,58 +131,51 @@ export default declare(api => {
Function(path) { Function(path) {
if (path.isDeclareFunction()) return; if (path.isDeclareFunction()) return;
const { node } = path; const { node } = path;
if (node.returnType) {
const returnType = path.get("returnType");
const typeAnnotation = returnType.get("typeAnnotation");
const block = path.get("body");
block.addComment(
"leading",
generateComment(returnType, typeAnnotation.node),
);
returnType.remove();
}
if (node.typeParameters) { if (node.typeParameters) {
const typeParameters = path.get("typeParameters"); attachComment({
const id = path.get("id"); ofPath: path.get("typeParameters"),
id.addComment( toPath: path.get("id"),
"trailing", optional: node.typeParameters.optional,
generateComment(typeParameters, typeParameters.node), });
); }
typeParameters.remove(); if (node.returnType) {
attachComment({
ofPath: path.get("returnType"),
toPath: path.get("body"),
where: "leading",
optional: node.returnType.typeAnnotation.optional,
});
} }
}, },
// support for `class X { foo: string }` - #4622 // support for `class X { foo: string }` - #4622
ClassProperty(path) { ClassProperty(path) {
const { node, parent } = path; const { node } = path;
if (!node.value) { if (!node.value) {
wrapInFlowComment(path, parent); wrapInFlowComment(path);
} else if (node.typeAnnotation) { } else if (node.typeAnnotation) {
const typeAnnotation = path.get("typeAnnotation"); attachComment({
path ofPath: path.get("typeAnnotation"),
.get("key") toPath: path.get("key"),
.addComment( optional: node.typeAnnotation.optional,
"trailing", });
generateComment(typeAnnotation, typeAnnotation.node),
);
typeAnnotation.remove();
} }
}, },
// support `export type a = {}` - #8 Error: You passed path.replaceWith() a falsy node // support `export type a = {}` - #8 Error: You passed path.replaceWith() a falsy node
ExportNamedDeclaration(path) { ExportNamedDeclaration(path) {
const { node, parent } = path; const { node } = path;
if (node.exportKind !== "type" && !t.isFlow(node.declaration)) { if (node.exportKind !== "type" && !t.isFlow(node.declaration)) {
return; return;
} }
wrapInFlowComment(path, parent); wrapInFlowComment(path);
}, },
// support `import type A` and `import typeof A` #10 // support `import type A` and `import typeof A` #10
ImportDeclaration(path) { ImportDeclaration(path) {
const { node, parent } = path; const { node } = path;
if (isTypeImport(node.importKind)) { if (isTypeImport(node.importKind)) {
wrapInFlowComment(path, parent); wrapInFlowComment(path);
return; return;
} }
@ -148,61 +191,88 @@ export default declare(api => {
if (typeSpecifiers.length > 0) { if (typeSpecifiers.length > 0) {
const typeImportNode = t.cloneNode(node); const typeImportNode = t.cloneNode(node);
typeImportNode.specifiers = typeSpecifiers; typeImportNode.specifiers = typeSpecifiers;
const comment = `:: ${generateCode(typeImportNode).code}`;
if (nonTypeSpecifiers.length > 0) { if (nonTypeSpecifiers.length > 0) {
path.addComment( attachComment({ toPath: path, comments: comment });
"trailing",
`:: ${generateCode(typeImportNode).code}`,
);
} else { } else {
attachComment(path, `:: ${generateCode(typeImportNode).code}`); attachComment({ ofPath: path, comments: comment });
} }
} }
}, },
ObjectPattern(path) { ObjectPattern(path) {
const { node } = path; const { node } = path;
if (node.typeAnnotation) { if (node.typeAnnotation) {
const typeAnnotation = path.get("typeAnnotation"); attachComment({
path.addComment( ofPath: path.get("typeAnnotation"),
"trailing", toPath: path,
generateComment(typeAnnotation, typeAnnotation.node), optional: node.optional || node.typeAnnotation.optional,
); });
typeAnnotation.remove();
} }
}, },
Flow(path) { Flow(path) {
const { parent } = path; wrapInFlowComment(path);
wrapInFlowComment(path, parent);
}, },
Class(path) { Class(path) {
const { node } = path; const { node } = path;
if (node.typeParameters || node.implements) { let comments = [];
const comments = [];
if (node.typeParameters) { if (node.typeParameters) {
const typeParameters = path.get("typeParameters"); const typeParameters = path.get("typeParameters");
comments.push( comments.push(
generateComment(typeParameters, typeParameters.node).replace( generateComment(typeParameters, node.typeParameters.optional),
/^:: /,
"",
),
); );
const trailingComments = node.typeParameters.trailingComments;
if (trailingComments) {
comments.push(...trailingComments);
}
typeParameters.remove(); typeParameters.remove();
} }
if (node.superClass) {
if (comments.length > 0) {
attachComment({
toPath: path.get("id"),
comments: comments,
});
comments = [];
}
if (node.superTypeParameters) {
const superTypeParameters = path.get("superTypeParameters");
comments.push(
generateComment(
superTypeParameters,
superTypeParameters.node.optional,
),
);
superTypeParameters.remove();
}
}
if (node.implements) { if (node.implements) {
const impls = path.get("implements"); const impls = path.get("implements");
comments.push( const comment =
"implements " + "implements " +
impls impls
.map(impl => generateComment(impl).replace(/^:: /, "")) .map(impl => generateComment(impl).replace(/^:: /, ""))
.join(", "), .join(", ");
);
delete node["implements"]; delete node["implements"];
if (comments.length === 1) {
comments[0] += ` ${comment}`;
} else {
comments.push(`:: ${comment}`);
}
} }
const block = path.get("body"); if (comments.length > 0) {
block.addComment("leading", ":: " + comments.join(" ")); attachComment({
toPath: path.get("body"),
where: "leading",
comments: comments,
});
} }
}, },
}, },

View File

@ -0,0 +1 @@
class Foo extends Bar<T> {}

View File

@ -0,0 +1,3 @@
class Foo extends Bar
/*:: <T>*/
{}

View File

@ -0,0 +1,3 @@
class Foo<T> /* inner */ extends Bar<R> {}
/*a*/class /*b*/Baz/*c*/<T>/*d*/extends /*e*/Bar/*f*/<R>/*g*/ {/*h*/}/*i*/

View File

@ -0,0 +1,30 @@
class Foo
/*:: <T>*/
/* inner */
extends Bar
/*:: <R>*/
{}
/*a*/
class
/*b*/
Baz
/*c*/
/*:: <T>*/
/*d*/
extends
/*e*/
Bar
/*f*/
/*:: <R>*/
/*g*/
{}
/*h*/
/*i*/

View File

@ -0,0 +1 @@
class Foo<T> extends Bar {}

View File

@ -0,0 +1,3 @@
class Foo
/*:: <T>*/
extends Bar {}

View File

@ -0,0 +1,7 @@
/*a*/
type Foo = number;
/*b*/
var foo;
/*c*/
type Bar = number;
/*d*/

View File

@ -0,0 +1,11 @@
/*a*/
/*:: type Foo = number;*/
/*b*/
var foo;
/*c*/
/*:: type Bar = number;*/
/*d*/

View File

@ -0,0 +1,2 @@
/**/
type Foo = number;

View File

@ -0,0 +1,3 @@
/**/
/*:: type Foo = number;*/