diff --git a/packages/babel-helper-annotate-as-pure/.npmignore b/packages/babel-helper-annotate-as-pure/.npmignore new file mode 100644 index 0000000000..f980694583 --- /dev/null +++ b/packages/babel-helper-annotate-as-pure/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-helper-annotate-as-pure/README.md b/packages/babel-helper-annotate-as-pure/README.md new file mode 100644 index 0000000000..2b52b189bb --- /dev/null +++ b/packages/babel-helper-annotate-as-pure/README.md @@ -0,0 +1,36 @@ +# babel-helper-annotate-as-pure + +## API + +```js +declare export default annotateAsPure(nodeOrPath: Node | NodePath); +``` + +## Usage + +```js +import traverse from "babel-traverse"; +import annotateAsPure from "babel-helper-annotate-as-pure"; + +// ... + +traverse(file, { + CallExpression(path) { + annotateAsPure(path); + }, +}); +``` + +## Caveat with UglifyJS pre v3.1.0 + +`babel-helper-annotate-as-pure` concatenates existing leading comments to the `#__PURE__` annotation, but versions of UglifyJS before v3.1.0 checks only the last leading comment for the annotation. + +So for the example input when annotating all CallExpressions: +```js +const four = /* foo */ add(2, 2); +``` +it produces: +```js +const four = /* #__PURE__ */ /* foo */ add(2, 2); +``` +and such generated annotation will be ignored in those previous versions of the UglifyJS. diff --git a/packages/babel-helper-annotate-as-pure/package.json b/packages/babel-helper-annotate-as-pure/package.json new file mode 100644 index 0000000000..744f32e825 --- /dev/null +++ b/packages/babel-helper-annotate-as-pure/package.json @@ -0,0 +1,11 @@ +{ + "name": "babel-helper-annotate-as-pure", + "version": "7.0.0-beta.1", + "description": "Helper function to annotate paths and nodes with #__PURE__ comment", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-helper-annotate-as-pure", + "license": "MIT", + "main": "lib/index.js", + "dependencies": { + "babel-types": "7.0.0-beta.1" + } +} diff --git a/packages/babel-helper-annotate-as-pure/src/index.js b/packages/babel-helper-annotate-as-pure/src/index.js new file mode 100644 index 0000000000..5c46e93f99 --- /dev/null +++ b/packages/babel-helper-annotate-as-pure/src/index.js @@ -0,0 +1,19 @@ +import * as t from "babel-types"; + +const PURE_ANNOTATION = "#__PURE__"; + +const isPureAnnotated = node => { + const { leadingComments } = node; + if (leadingComments === undefined) { + return false; + } + return leadingComments.some(comment => /[@#]__PURE__/.test(comment.value)); +}; + +export default function annotateAsPure(pathOrNode) { + const node = pathOrNode.node || pathOrNode; + if (isPureAnnotated(node)) { + return; + } + t.addComment(node, "leading", PURE_ANNOTATION); +} diff --git a/packages/babel-plugin-transform-es2015-classes/package.json b/packages/babel-plugin-transform-es2015-classes/package.json index f37bf66fa1..e477828378 100644 --- a/packages/babel-plugin-transform-es2015-classes/package.json +++ b/packages/babel-plugin-transform-es2015-classes/package.json @@ -6,6 +6,7 @@ "license": "MIT", "main": "lib/index.js", "dependencies": { + "babel-helper-annotate-as-pure": "7.0.0-beta.1", "babel-helper-define-map": "7.0.0-beta.1", "babel-helper-function-name": "7.0.0-beta.1", "babel-helper-optimise-call-expression": "7.0.0-beta.1", diff --git a/packages/babel-plugin-transform-es2015-classes/src/index.js b/packages/babel-plugin-transform-es2015-classes/src/index.js index 6bdac143f9..125be035fe 100644 --- a/packages/babel-plugin-transform-es2015-classes/src/index.js +++ b/packages/babel-plugin-transform-es2015-classes/src/index.js @@ -1,9 +1,8 @@ import LooseTransformer from "./loose"; import VanillaTransformer from "./vanilla"; +import annotateAsPure from "babel-helper-annotate-as-pure"; import nameFunction from "babel-helper-function-name"; -const PURE_ANNOTATION = "#__PURE__"; - export default function({ types: t }) { // todo: investigate traversal requeueing const VISITED = Symbol(); @@ -57,7 +56,7 @@ export default function({ types: t }) { path.replaceWith(new Constructor(path, state.file).run()); if (path.isCallExpression()) { - path.addComment("leading", PURE_ANNOTATION); + annotateAsPure(path); if (path.get("callee").isArrowFunctionExpression()) { path.get("callee").arrowFunctionToExpression(); } diff --git a/packages/babel-traverse/src/path/comments.js b/packages/babel-traverse/src/path/comments.js index 909ea43015..38aeaa66f5 100644 --- a/packages/babel-traverse/src/path/comments.js +++ b/packages/babel-traverse/src/path/comments.js @@ -1,4 +1,5 @@ // This file contains methods responsible for dealing with comments. +import * as t from "babel-types"; /** * Share comments amongst siblings. @@ -27,13 +28,8 @@ export function shareCommentsWithSiblings() { } } -export function addComment(type, content, line?) { - this.addComments(type, [ - { - type: line ? "CommentLine" : "CommentBlock", - value: content, - }, - ]); +export function addComment(type: string, content: string, line?: boolean) { + t.addComment(this.node, type, content, line); } /** @@ -41,20 +37,5 @@ export function addComment(type, content, line?) { */ export function addComments(type: string, comments: Array) { - if (!comments) return; - - const node = this.node; - if (!node) return; - - const key = `${type}Comments`; - - if (node[key]) { - if (type === "leading") { - node[key] = comments.concat(node[key]); - } else { - node[key] = node[key].concat(comments); - } - } else { - node[key] = comments; - } + t.addComments(this.node, type, comments); } diff --git a/packages/babel-types/src/index.js b/packages/babel-types/src/index.js index 2e5fa0797d..72bdb99560 100644 --- a/packages/babel-types/src/index.js +++ b/packages/babel-types/src/index.js @@ -399,6 +399,49 @@ export function buildMatchMemberExpression( }; } +/** + * Add comment of certain type to a node. + */ + +export function addComment( + node: Object, + type: string, + content: string, + line?: boolean, +): Object { + addComments(node, type, [ + { + type: line ? "CommentLine" : "CommentBlock", + value: content, + }, + ]); +} + +/** + * Add comments of certain type to a node. + */ + +export function addComments( + node: Object, + type: string, + comments: Array, +): Object { + if (!comments || !node) return; + + const key = `${type}Comments`; + + if (node[key]) { + if (type === "leading") { + node[key] = comments.concat(node[key]); + } else { + node[key] = node[key].concat(comments); + } + } else { + node[key] = comments; + } + return node; +} + /** * Remove comment properties from a node. */