From 36d1ef1774441aad72c16936d6bacea57747ae44 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 2 Jun 2023 02:10:58 +0200 Subject: [PATCH 01/11] feat: init --- src/ast/nodes/shared/FunctionNode.ts | 17 +++++++ src/utils/pureComments.ts | 28 +++++++++++- .../_config.js | 6 +++ .../_expected.js | 21 +++++++++ .../functions.js | 44 +++++++++++++++++++ .../pure-comment-function-declaration/main.js | 12 +++++ 6 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 test/form/samples/pure-comment-function-declaration/_config.js create mode 100644 test/form/samples/pure-comment-function-declaration/_expected.js create mode 100644 test/form/samples/pure-comment-function-declaration/functions.js create mode 100644 test/form/samples/pure-comment-function-declaration/main.js diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 3385ef2b37b..37ce77eb6fd 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,3 +1,4 @@ +import type { NormalizedTreeshakingOptions } from '../../../rollup/types'; import { type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; import type { NodeInteraction } from '../../NodeInteractions'; import { INTERACTION_CALLED } from '../../NodeInteractions'; @@ -45,6 +46,14 @@ export default class FunctionNode extends FunctionBase { hasEffects(context: HasEffectsContext): boolean { if (!this.deoptimized) this.applyDeoptimizations(); + + if ( + (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && + this.annotations + ) { + return false; + } + return !!this.id?.hasEffects(context); } @@ -54,6 +63,14 @@ export default class FunctionNode extends FunctionBase { context: HasEffectsContext ): boolean { if (super.hasEffectsOnInteractionAtPath(path, interaction, context)) return true; + + if ( + (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && + this.annotations + ) { + return false; + } + if (interaction.type === INTERACTION_CALLED) { const thisInit = context.replacedVariableInits.get(this.scope.thisVariable); context.replacedVariableInits.set( diff --git a/src/utils/pureComments.ts b/src/utils/pureComments.ts index dd6cd22b062..a40b789349b 100644 --- a/src/utils/pureComments.ts +++ b/src/utils/pureComments.ts @@ -1,14 +1,18 @@ import type * as acorn from 'acorn'; import { base as basicWalker } from 'acorn-walk'; import { + ArrowFunctionExpression, BinaryExpression, CallExpression, ChainExpression, ConditionalExpression, + ExportNamedDeclaration, ExpressionStatement, + FunctionDeclaration, LogicalExpression, NewExpression, - SequenceExpression + SequenceExpression, + VariableDeclaration } from '../ast/nodes/NodeType'; import { SOURCEMAPPING_URL_RE } from './sourceMappingURL'; @@ -93,11 +97,33 @@ function markPureNode(node: NodeWithComments, comment: acorn.Comment, code: stri invalidAnnotation = true; break; } + case ExportNamedDeclaration: { + node = (node as any).declaration; + continue; + } + case VariableDeclaration: { + // case: /*#__PURE__*/ const foo = () => {} + const declaration = node as any; + if ( + declaration.declarations.length === 1 && + declaration.kind === 'const' && + (declaration.declarations[0].type === ArrowFunctionExpression || + declaration.declarations[0].type === FunctionDeclaration) + ) { + node = declaration.declarations[0]; + continue; + } + invalidAnnotation = true; + break; + } + case FunctionDeclaration: + case ArrowFunctionExpression: case CallExpression: case NewExpression: { break; } default: { + console.log({ node }); invalidAnnotation = true; } } diff --git a/test/form/samples/pure-comment-function-declaration/_config.js b/test/form/samples/pure-comment-function-declaration/_config.js new file mode 100644 index 00000000000..417684a4b09 --- /dev/null +++ b/test/form/samples/pure-comment-function-declaration/_config.js @@ -0,0 +1,6 @@ +// tests compiled from https://github.com/mishoo/UglifyJS2/blob/88c8f4e363e0d585b33ea29df560243d3dc74ce1/test/compress/pure_funcs.js + +module.exports = defineTest({ + solo: true, + description: 'pure annotations for function declarations' +}); diff --git a/test/form/samples/pure-comment-function-declaration/_expected.js b/test/form/samples/pure-comment-function-declaration/_expected.js new file mode 100644 index 00000000000..c7ce8c28180 --- /dev/null +++ b/test/form/samples/pure-comment-function-declaration/_expected.js @@ -0,0 +1,21 @@ +function fnEffects(args) { + console.log(args); + return args +} + +const fnC = /*#__PURE__*/ (args) => { + console.log(args); + return args +}; + + + +const fnD = (args) => { + console.log(args); + return args +}; + +fnEffects(2); +fnC(3); +fnD(4); +fnE(5); diff --git a/test/form/samples/pure-comment-function-declaration/functions.js b/test/form/samples/pure-comment-function-declaration/functions.js new file mode 100644 index 00000000000..ca4065a319c --- /dev/null +++ b/test/form/samples/pure-comment-function-declaration/functions.js @@ -0,0 +1,44 @@ +export function fnPure(args) { + return args +} + +export function fnEffects(args) { + console.log(args) + return args +} + +/*#__PURE__*/ +function fnA (args) { + console.log(args) + return args +} +export { fnA } + +/*#__PURE__*/ +export function fnB (args) { + console.log(args) + return args +} + +export const fnC = /*#__PURE__*/ (args) => { + console.log(args) + return args +} + + +/*#__PURE__*/ +const fnD = (args) => { + console.log(args) + return args +} + +export { fnD } + +/*#__PURE__*/ +export const fnE = (args) => { + console.log(args) + return args +} + + + diff --git a/test/form/samples/pure-comment-function-declaration/main.js b/test/form/samples/pure-comment-function-declaration/main.js new file mode 100644 index 00000000000..1a0e8925f1c --- /dev/null +++ b/test/form/samples/pure-comment-function-declaration/main.js @@ -0,0 +1,12 @@ +import { fnPure, fnEffects, fnA, fnB, fnC, fnD } from './functions' + +const pure = fnPure(1) +const effects = fnEffects(2) + +const a = fnA(1) +const b = fnB(2) +const c = fnC(3) +const d = fnD(4) +const e = fnE(5) + +const _ = /*#__PURE__*/ fnEffects(1) From 9047e2131710a678c1e7438ebac15e66158690d3 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 2 Jun 2023 12:06:13 +0200 Subject: [PATCH 02/11] chore: update --- src/ast/nodes/ArrowFunctionExpression.ts | 8 +++++++ src/utils/pureComments.ts | 22 ++++++++++--------- .../_expected.js | 14 ------------ 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 2d47cad8f57..7becbcc136e 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -1,3 +1,4 @@ +import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import type { NodeInteraction } from '../NodeInteractions'; import { INTERACTION_CALLED } from '../NodeInteractions'; @@ -38,6 +39,13 @@ export default class ArrowFunctionExpression extends FunctionBase { ): boolean { if (super.hasEffectsOnInteractionAtPath(path, interaction, context)) return true; if (interaction.type === INTERACTION_CALLED) { + if ( + (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && + this.annotations + ) { + return false; + } + const { ignore, brokenFlow } = context; context.ignore = { breaks: false, diff --git a/src/utils/pureComments.ts b/src/utils/pureComments.ts index a40b789349b..25e01fadac9 100644 --- a/src/utils/pureComments.ts +++ b/src/utils/pureComments.ts @@ -12,7 +12,8 @@ import { LogicalExpression, NewExpression, SequenceExpression, - VariableDeclaration + VariableDeclaration, + VariableDeclarator } from '../ast/nodes/NodeType'; import { SOURCEMAPPING_URL_RE } from './sourceMappingURL'; @@ -104,14 +105,16 @@ function markPureNode(node: NodeWithComments, comment: acorn.Comment, code: stri case VariableDeclaration: { // case: /*#__PURE__*/ const foo = () => {} const declaration = node as any; - if ( - declaration.declarations.length === 1 && - declaration.kind === 'const' && - (declaration.declarations[0].type === ArrowFunctionExpression || - declaration.declarations[0].type === FunctionDeclaration) - ) { - node = declaration.declarations[0]; - continue; + if (declaration.declarations.length === 1 && declaration.kind === 'const') { + const init = + declaration.declarations[0].type === VariableDeclarator + ? declaration.declarations[0].init + : declaration.declarations[0].type; + + if (init.type === ArrowFunctionExpression || init.type === FunctionDeclaration) { + node = init; + continue; + } } invalidAnnotation = true; break; @@ -123,7 +126,6 @@ function markPureNode(node: NodeWithComments, comment: acorn.Comment, code: stri break; } default: { - console.log({ node }); invalidAnnotation = true; } } diff --git a/test/form/samples/pure-comment-function-declaration/_expected.js b/test/form/samples/pure-comment-function-declaration/_expected.js index c7ce8c28180..d551c34028f 100644 --- a/test/form/samples/pure-comment-function-declaration/_expected.js +++ b/test/form/samples/pure-comment-function-declaration/_expected.js @@ -3,19 +3,5 @@ function fnEffects(args) { return args } -const fnC = /*#__PURE__*/ (args) => { - console.log(args); - return args -}; - - - -const fnD = (args) => { - console.log(args); - return args -}; - fnEffects(2); -fnC(3); -fnD(4); fnE(5); From e023beb73a469a2b9e9041037bf242150b27636b Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 2 Jun 2023 12:42:44 +0200 Subject: [PATCH 03/11] chore: updates --- src/utils/pureComments.ts | 4 +++- .../samples/pure-comment-function-declaration/_expected.js | 1 - .../samples/pure-comment-function-declaration/functions.js | 7 +++++++ .../form/samples/pure-comment-function-declaration/main.js | 6 +++++- .../pure-comment-function-declaration/sub-functions.js | 5 +++++ 5 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 test/form/samples/pure-comment-function-declaration/sub-functions.js diff --git a/src/utils/pureComments.ts b/src/utils/pureComments.ts index 25e01fadac9..a35b88efb4c 100644 --- a/src/utils/pureComments.ts +++ b/src/utils/pureComments.ts @@ -6,6 +6,7 @@ import { CallExpression, ChainExpression, ConditionalExpression, + ExportDefaultDeclaration, ExportNamedDeclaration, ExpressionStatement, FunctionDeclaration, @@ -98,7 +99,8 @@ function markPureNode(node: NodeWithComments, comment: acorn.Comment, code: stri invalidAnnotation = true; break; } - case ExportNamedDeclaration: { + case ExportNamedDeclaration: + case ExportDefaultDeclaration: { node = (node as any).declaration; continue; } diff --git a/test/form/samples/pure-comment-function-declaration/_expected.js b/test/form/samples/pure-comment-function-declaration/_expected.js index d551c34028f..bc3b94942f6 100644 --- a/test/form/samples/pure-comment-function-declaration/_expected.js +++ b/test/form/samples/pure-comment-function-declaration/_expected.js @@ -4,4 +4,3 @@ function fnEffects(args) { } fnEffects(2); -fnE(5); diff --git a/test/form/samples/pure-comment-function-declaration/functions.js b/test/form/samples/pure-comment-function-declaration/functions.js index ca4065a319c..e4f97657e44 100644 --- a/test/form/samples/pure-comment-function-declaration/functions.js +++ b/test/form/samples/pure-comment-function-declaration/functions.js @@ -40,5 +40,12 @@ export const fnE = (args) => { return args } +/*#__PURE__*/ +export default function fnDefault(args) { + console.log(args) + return args +} +export * from './sub-functions' +export const fnAlias = fnA diff --git a/test/form/samples/pure-comment-function-declaration/main.js b/test/form/samples/pure-comment-function-declaration/main.js index 1a0e8925f1c..80cef2ca2b3 100644 --- a/test/form/samples/pure-comment-function-declaration/main.js +++ b/test/form/samples/pure-comment-function-declaration/main.js @@ -1,4 +1,4 @@ -import { fnPure, fnEffects, fnA, fnB, fnC, fnD } from './functions' +import fnDefault, { fnPure, fnEffects, fnA, fnB, fnC, fnD, fnE, fnAlias, fnFromSub } from './functions' const pure = fnPure(1) const effects = fnEffects(2) @@ -9,4 +9,8 @@ const c = fnC(3) const d = fnD(4) const e = fnE(5) +const defaults = fnDefault(3) +const alias = fnAlias(6) +const fromSub = fnFromSub(7) + const _ = /*#__PURE__*/ fnEffects(1) diff --git a/test/form/samples/pure-comment-function-declaration/sub-functions.js b/test/form/samples/pure-comment-function-declaration/sub-functions.js new file mode 100644 index 00000000000..796ee69ab5d --- /dev/null +++ b/test/form/samples/pure-comment-function-declaration/sub-functions.js @@ -0,0 +1,5 @@ +/*#__PURE__*/ +export function fnFromSub (args) { + console.log(args) + return args +} From 17eddf213dd8c29d38f1526c3f4b487512ea03f6 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 2 Jun 2023 12:47:38 +0200 Subject: [PATCH 04/11] chore: update --- .../_config.js | 5 ++ .../_expected.js | 48 +++++++++++++++++ .../functions.js | 51 +++++++++++++++++++ .../main.js | 1 + .../sub-functions.js | 5 ++ .../_config.js | 1 - 6 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 test/form/samples/pure-comment-function-declaration-preserve/_config.js create mode 100644 test/form/samples/pure-comment-function-declaration-preserve/_expected.js create mode 100644 test/form/samples/pure-comment-function-declaration-preserve/functions.js create mode 100644 test/form/samples/pure-comment-function-declaration-preserve/main.js create mode 100644 test/form/samples/pure-comment-function-declaration-preserve/sub-functions.js diff --git a/test/form/samples/pure-comment-function-declaration-preserve/_config.js b/test/form/samples/pure-comment-function-declaration-preserve/_config.js new file mode 100644 index 00000000000..2187370d91c --- /dev/null +++ b/test/form/samples/pure-comment-function-declaration-preserve/_config.js @@ -0,0 +1,5 @@ +// tests compiled from https://github.com/mishoo/UglifyJS2/blob/88c8f4e363e0d585b33ea29df560243d3dc74ce1/test/compress/pure_funcs.js + +module.exports = defineTest({ + description: 'preserve pure annotations for function declarations' +}); diff --git a/test/form/samples/pure-comment-function-declaration-preserve/_expected.js b/test/form/samples/pure-comment-function-declaration-preserve/_expected.js new file mode 100644 index 00000000000..03dcc7c77bf --- /dev/null +++ b/test/form/samples/pure-comment-function-declaration-preserve/_expected.js @@ -0,0 +1,48 @@ +/*#__PURE__*/ +function fnFromSub (args) { + console.log(args); + return args +} + +function fnPure(args) { + return args +} + +function fnEffects(args) { + console.log(args); + return args +} + +/*#__PURE__*/ +function fnA (args) { + console.log(args); + return args +} + +/*#__PURE__*/ +function fnB (args) { + console.log(args); + return args +} + +const fnC = /*#__PURE__*/ (args) => { + console.log(args); + return args +}; + + +/*#__PURE__*/ +const fnD = (args) => { + console.log(args); + return args +}; + +/*#__PURE__*/ +const fnE = (args) => { + console.log(args); + return args +}; + +const fnAlias = fnA; + +export { fnA, fnAlias, fnB, fnC, fnD, fnE, fnEffects, fnFromSub, fnPure }; diff --git a/test/form/samples/pure-comment-function-declaration-preserve/functions.js b/test/form/samples/pure-comment-function-declaration-preserve/functions.js new file mode 100644 index 00000000000..e4f97657e44 --- /dev/null +++ b/test/form/samples/pure-comment-function-declaration-preserve/functions.js @@ -0,0 +1,51 @@ +export function fnPure(args) { + return args +} + +export function fnEffects(args) { + console.log(args) + return args +} + +/*#__PURE__*/ +function fnA (args) { + console.log(args) + return args +} +export { fnA } + +/*#__PURE__*/ +export function fnB (args) { + console.log(args) + return args +} + +export const fnC = /*#__PURE__*/ (args) => { + console.log(args) + return args +} + + +/*#__PURE__*/ +const fnD = (args) => { + console.log(args) + return args +} + +export { fnD } + +/*#__PURE__*/ +export const fnE = (args) => { + console.log(args) + return args +} + +/*#__PURE__*/ +export default function fnDefault(args) { + console.log(args) + return args +} + +export * from './sub-functions' + +export const fnAlias = fnA diff --git a/test/form/samples/pure-comment-function-declaration-preserve/main.js b/test/form/samples/pure-comment-function-declaration-preserve/main.js new file mode 100644 index 00000000000..326e35231ee --- /dev/null +++ b/test/form/samples/pure-comment-function-declaration-preserve/main.js @@ -0,0 +1 @@ +export * from './functions' diff --git a/test/form/samples/pure-comment-function-declaration-preserve/sub-functions.js b/test/form/samples/pure-comment-function-declaration-preserve/sub-functions.js new file mode 100644 index 00000000000..796ee69ab5d --- /dev/null +++ b/test/form/samples/pure-comment-function-declaration-preserve/sub-functions.js @@ -0,0 +1,5 @@ +/*#__PURE__*/ +export function fnFromSub (args) { + console.log(args) + return args +} diff --git a/test/form/samples/pure-comment-function-declaration/_config.js b/test/form/samples/pure-comment-function-declaration/_config.js index 417684a4b09..0aa9a5715b0 100644 --- a/test/form/samples/pure-comment-function-declaration/_config.js +++ b/test/form/samples/pure-comment-function-declaration/_config.js @@ -1,6 +1,5 @@ // tests compiled from https://github.com/mishoo/UglifyJS2/blob/88c8f4e363e0d585b33ea29df560243d3dc74ce1/test/compress/pure_funcs.js module.exports = defineTest({ - solo: true, description: 'pure annotations for function declarations' }); From 6739eceb8b61ff76b9122530026dd7419cc4de28 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 2 Jun 2023 13:08:41 +0200 Subject: [PATCH 05/11] test: add test for jsdoc --- .../_expected.js | 13 ++++++++++++- .../functions.js | 11 +++++++++++ .../pure-comment-function-declaration/functions.js | 11 +++++++++++ .../pure-comment-function-declaration/main.js | 3 ++- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/test/form/samples/pure-comment-function-declaration-preserve/_expected.js b/test/form/samples/pure-comment-function-declaration-preserve/_expected.js index 03dcc7c77bf..15192b0833f 100644 --- a/test/form/samples/pure-comment-function-declaration-preserve/_expected.js +++ b/test/form/samples/pure-comment-function-declaration-preserve/_expected.js @@ -43,6 +43,17 @@ const fnE = (args) => { return args }; +/** + * This is a jsdoc comment, with pure annotation + * + * @param {any} args + * @__PURE__ + */ +const fnF = (args) => { + console.log(args); + return args +}; + const fnAlias = fnA; -export { fnA, fnAlias, fnB, fnC, fnD, fnE, fnEffects, fnFromSub, fnPure }; +export { fnA, fnAlias, fnB, fnC, fnD, fnE, fnEffects, fnF, fnFromSub, fnPure }; diff --git a/test/form/samples/pure-comment-function-declaration-preserve/functions.js b/test/form/samples/pure-comment-function-declaration-preserve/functions.js index e4f97657e44..e012b109ea2 100644 --- a/test/form/samples/pure-comment-function-declaration-preserve/functions.js +++ b/test/form/samples/pure-comment-function-declaration-preserve/functions.js @@ -40,6 +40,17 @@ export const fnE = (args) => { return args } +/** + * This is a jsdoc comment, with pure annotation + * + * @param {any} args + * @__PURE__ + */ +export const fnF = (args) => { + console.log(args) + return args +} + /*#__PURE__*/ export default function fnDefault(args) { console.log(args) diff --git a/test/form/samples/pure-comment-function-declaration/functions.js b/test/form/samples/pure-comment-function-declaration/functions.js index e4f97657e44..e012b109ea2 100644 --- a/test/form/samples/pure-comment-function-declaration/functions.js +++ b/test/form/samples/pure-comment-function-declaration/functions.js @@ -40,6 +40,17 @@ export const fnE = (args) => { return args } +/** + * This is a jsdoc comment, with pure annotation + * + * @param {any} args + * @__PURE__ + */ +export const fnF = (args) => { + console.log(args) + return args +} + /*#__PURE__*/ export default function fnDefault(args) { console.log(args) diff --git a/test/form/samples/pure-comment-function-declaration/main.js b/test/form/samples/pure-comment-function-declaration/main.js index 80cef2ca2b3..f3eaed4f998 100644 --- a/test/form/samples/pure-comment-function-declaration/main.js +++ b/test/form/samples/pure-comment-function-declaration/main.js @@ -1,4 +1,4 @@ -import fnDefault, { fnPure, fnEffects, fnA, fnB, fnC, fnD, fnE, fnAlias, fnFromSub } from './functions' +import fnDefault, { fnPure, fnEffects, fnA, fnB, fnC, fnD, fnE, fnF, fnAlias, fnFromSub } from './functions' const pure = fnPure(1) const effects = fnEffects(2) @@ -8,6 +8,7 @@ const b = fnB(2) const c = fnC(3) const d = fnD(4) const e = fnE(5) +const f = fnF(6) const defaults = fnDefault(3) const alias = fnAlias(6) From daa2e3320c6459577e992427186b99f263354324 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 2 Jun 2023 13:16:20 +0200 Subject: [PATCH 06/11] chore: coverage --- src/utils/pureComments.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/utils/pureComments.ts b/src/utils/pureComments.ts index a35b88efb4c..d9a818dd8b9 100644 --- a/src/utils/pureComments.ts +++ b/src/utils/pureComments.ts @@ -13,8 +13,7 @@ import { LogicalExpression, NewExpression, SequenceExpression, - VariableDeclaration, - VariableDeclarator + VariableDeclaration } from '../ast/nodes/NodeType'; import { SOURCEMAPPING_URL_RE } from './sourceMappingURL'; @@ -108,11 +107,7 @@ function markPureNode(node: NodeWithComments, comment: acorn.Comment, code: stri // case: /*#__PURE__*/ const foo = () => {} const declaration = node as any; if (declaration.declarations.length === 1 && declaration.kind === 'const') { - const init = - declaration.declarations[0].type === VariableDeclarator - ? declaration.declarations[0].init - : declaration.declarations[0].type; - + const init = declaration.declarations[0].init; if (init.type === ArrowFunctionExpression || init.type === FunctionDeclaration) { node = init; continue; From a46fecc55f1f37767008972be0b241b182c7b03d Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 4 Jun 2023 11:36:53 +0200 Subject: [PATCH 07/11] feat: change the anntation to `__NO_SIDE_EFFECTS__` --- src/Graph.ts | 2 +- src/ast/nodes/ArrowFunctionExpression.ts | 2 +- src/ast/nodes/CallExpression.ts | 2 +- src/ast/nodes/NewExpression.ts | 2 +- src/ast/nodes/shared/FunctionNode.ts | 4 +-- src/ast/nodes/shared/Node.ts | 16 ++++++++++-- ...{pureComments.ts => commentAnnotations.ts} | 25 ++++++++++++++----- .../_config.js | 2 +- .../_expected.js | 14 +++++------ .../functions.js | 14 +++++------ .../main.js | 0 .../sub-functions.js | 2 +- .../_config.js | 2 +- .../_expected.js | 0 .../functions.js | 14 +++++------ .../main.js | 0 .../sub-functions.js | 2 +- 17 files changed, 64 insertions(+), 39 deletions(-) rename src/utils/{pureComments.ts => commentAnnotations.ts} (90%) rename test/form/samples/{pure-comment-function-declaration-preserve => no-side-effects-function-declaration-preserve}/_config.js (66%) rename test/form/samples/{pure-comment-function-declaration-preserve => no-side-effects-function-declaration-preserve}/_expected.js (77%) rename test/form/samples/{pure-comment-function-declaration => no-side-effects-function-declaration-preserve}/functions.js (77%) rename test/form/samples/{pure-comment-function-declaration-preserve => no-side-effects-function-declaration-preserve}/main.js (100%) rename test/form/samples/{pure-comment-function-declaration => no-side-effects-function-declaration-preserve}/sub-functions.js (73%) rename test/form/samples/{pure-comment-function-declaration => no-side-effects-function-declaration}/_config.js (69%) rename test/form/samples/{pure-comment-function-declaration => no-side-effects-function-declaration}/_expected.js (100%) rename test/form/samples/{pure-comment-function-declaration-preserve => no-side-effects-function-declaration}/functions.js (77%) rename test/form/samples/{pure-comment-function-declaration => no-side-effects-function-declaration}/main.js (100%) rename test/form/samples/{pure-comment-function-declaration-preserve => no-side-effects-function-declaration}/sub-functions.js (73%) diff --git a/src/Graph.ts b/src/Graph.ts index b212354965e..13313f4efe8 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -17,6 +17,7 @@ import type { import { PluginDriver } from './utils/PluginDriver'; import Queue from './utils/Queue'; import { BuildPhase } from './utils/buildPhase'; +import { addAnnotations } from './utils/commentAnnotations'; import { error, errorCircularDependency, @@ -24,7 +25,6 @@ import { errorMissingExport } from './utils/error'; import { analyseModuleExecution } from './utils/executionOrder'; -import { addAnnotations } from './utils/pureComments'; import { getPureFunctions } from './utils/pureFunctions'; import type { PureFunctions } from './utils/pureFunctions'; import { timeEnd, timeStart } from './utils/timers'; diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 7becbcc136e..4d14130b1d0 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -41,7 +41,7 @@ export default class ArrowFunctionExpression extends FunctionBase { if (interaction.type === INTERACTION_CALLED) { if ( (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotations + this.annotationNoSideEffects ) { return false; } diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index d50686fc1f8..3a5ea39c4e3 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -59,7 +59,7 @@ export default class CallExpression } if ( (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotations + this.annotationPure ) return false; return ( diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index ccf7caa95ae..206a3663e22 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -23,7 +23,7 @@ export default class NewExpression extends NodeBase { } if ( (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotations + this.annotationPure ) { return false; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 37ce77eb6fd..562e8289153 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -49,7 +49,7 @@ export default class FunctionNode extends FunctionBase { if ( (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotations + this.annotationNoSideEffects ) { return false; } @@ -66,7 +66,7 @@ export default class FunctionNode extends FunctionBase { if ( (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotations + this.annotationNoSideEffects ) { return false; } diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 6c7d26eefda..51013b1b80a 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -2,7 +2,8 @@ import type * as acorn from 'acorn'; import { locate, type Location } from 'locate-character'; import type MagicString from 'magic-string'; import type { AstContext } from '../../../Module'; -import { ANNOTATION_KEY, INVALID_COMMENT_KEY } from '../../../utils/pureComments'; +import type { RollupAnnotation } from '../../../utils/commentAnnotations'; +import { ANNOTATION_KEY, INVALID_COMMENT_KEY } from '../../../utils/commentAnnotations'; import type { NodeRenderOptions, RenderOptions } from '../../../utils/renderHelpers'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { Entity } from '../../Entity'; @@ -124,7 +125,12 @@ export interface ChainElement extends ExpressionNode { } export class NodeBase extends ExpressionEntity implements ExpressionNode { - declare annotations?: acorn.Comment[]; + /** Marked with #__NO_SIDE_EFFECTS__ annotation */ + declare annotationNoSideEffects?: boolean; + /** Marked with #__PURE__ annotation */ + declare annotationPure?: boolean; + declare annotations?: RollupAnnotation[]; + context: AstContext; declare end: number; esTreeNode: acorn.Node | null; @@ -263,6 +269,12 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { if (key.charCodeAt(0) === 95 /* _ */) { if (key === ANNOTATION_KEY) { this.annotations = value; + this.annotationNoSideEffects = this.annotations!.some( + comment => comment.annotationType === 'noSideEffects' + ); + this.annotationPure = this.annotations!.some( + comment => comment.annotationType === 'pure' + ); } else if (key === INVALID_COMMENT_KEY) { for (const { start, end } of value as acorn.Comment[]) this.context.magicString.remove(start, end); diff --git a/src/utils/pureComments.ts b/src/utils/commentAnnotations.ts similarity index 90% rename from src/utils/pureComments.ts rename to src/utils/commentAnnotations.ts index d9a818dd8b9..87a74157125 100644 --- a/src/utils/pureComments.ts +++ b/src/utils/commentAnnotations.ts @@ -17,9 +17,15 @@ import { } from '../ast/nodes/NodeType'; import { SOURCEMAPPING_URL_RE } from './sourceMappingURL'; +export type AnnotationType = 'noSideEffects' | 'pure'; + +export interface RollupAnnotation extends acorn.Comment { + annotationType: AnnotationType; +} + interface CommentState { annotationIndex: number; - annotations: acorn.Comment[]; + annotations: RollupAnnotation[]; code: string; } @@ -159,25 +165,32 @@ function doesNotMatchOutsideComment(code: string, forbiddenChars: RegExp): boole return true; } -const pureCommentRegex = /[#@]__PURE__/; +const annotationsRegexes: [AnnotationType, RegExp][] = [ + ['pure', /[#@]__PURE__/], + ['noSideEffects', /[#@]__NO_SIDE_EFFECTS__/] +]; export function addAnnotations( comments: readonly acorn.Comment[], esTreeAst: acorn.Node, code: string ): void { - const annotations: acorn.Comment[] = []; + const annotations: RollupAnnotation[] = []; const sourceMappingComments: acorn.Comment[] = []; for (const comment of comments) { - if (pureCommentRegex.test(comment.value)) { - annotations.push(comment); - } else if (SOURCEMAPPING_URL_RE.test(comment.value)) { + for (const [annotationType, regex] of annotationsRegexes) { + if (regex.test(comment.value)) { + annotations.push({ ...comment, annotationType }); + } + } + if (SOURCEMAPPING_URL_RE.test(comment.value)) { sourceMappingComments.push(comment); } } for (const comment of sourceMappingComments) { annotateNode(esTreeAst, comment, false); } + handlePureAnnotationsOfNode(esTreeAst, { annotationIndex: 0, annotations, diff --git a/test/form/samples/pure-comment-function-declaration-preserve/_config.js b/test/form/samples/no-side-effects-function-declaration-preserve/_config.js similarity index 66% rename from test/form/samples/pure-comment-function-declaration-preserve/_config.js rename to test/form/samples/no-side-effects-function-declaration-preserve/_config.js index 2187370d91c..8791127a98b 100644 --- a/test/form/samples/pure-comment-function-declaration-preserve/_config.js +++ b/test/form/samples/no-side-effects-function-declaration-preserve/_config.js @@ -1,5 +1,5 @@ // tests compiled from https://github.com/mishoo/UglifyJS2/blob/88c8f4e363e0d585b33ea29df560243d3dc74ce1/test/compress/pure_funcs.js module.exports = defineTest({ - description: 'preserve pure annotations for function declarations' + description: 'preserve __NO_SIDE_EFFECTS__ annotations for function declarations' }); diff --git a/test/form/samples/pure-comment-function-declaration-preserve/_expected.js b/test/form/samples/no-side-effects-function-declaration-preserve/_expected.js similarity index 77% rename from test/form/samples/pure-comment-function-declaration-preserve/_expected.js rename to test/form/samples/no-side-effects-function-declaration-preserve/_expected.js index 15192b0833f..700f49b91d6 100644 --- a/test/form/samples/pure-comment-function-declaration-preserve/_expected.js +++ b/test/form/samples/no-side-effects-function-declaration-preserve/_expected.js @@ -1,4 +1,4 @@ -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ function fnFromSub (args) { console.log(args); return args @@ -13,31 +13,31 @@ function fnEffects(args) { return args } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ function fnA (args) { console.log(args); return args } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ function fnB (args) { console.log(args); return args } -const fnC = /*#__PURE__*/ (args) => { +const fnC = /*#__NO_SIDE_EFFECTS__*/ (args) => { console.log(args); return args }; -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ const fnD = (args) => { console.log(args); return args }; -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ const fnE = (args) => { console.log(args); return args @@ -47,7 +47,7 @@ const fnE = (args) => { * This is a jsdoc comment, with pure annotation * * @param {any} args - * @__PURE__ + * @__NO_SIDE_EFFECTS__ */ const fnF = (args) => { console.log(args); diff --git a/test/form/samples/pure-comment-function-declaration/functions.js b/test/form/samples/no-side-effects-function-declaration-preserve/functions.js similarity index 77% rename from test/form/samples/pure-comment-function-declaration/functions.js rename to test/form/samples/no-side-effects-function-declaration-preserve/functions.js index e012b109ea2..effd3b96950 100644 --- a/test/form/samples/pure-comment-function-declaration/functions.js +++ b/test/form/samples/no-side-effects-function-declaration-preserve/functions.js @@ -7,26 +7,26 @@ export function fnEffects(args) { return args } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ function fnA (args) { console.log(args) return args } export { fnA } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ export function fnB (args) { console.log(args) return args } -export const fnC = /*#__PURE__*/ (args) => { +export const fnC = /*#__NO_SIDE_EFFECTS__*/ (args) => { console.log(args) return args } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ const fnD = (args) => { console.log(args) return args @@ -34,7 +34,7 @@ const fnD = (args) => { export { fnD } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ export const fnE = (args) => { console.log(args) return args @@ -44,14 +44,14 @@ export const fnE = (args) => { * This is a jsdoc comment, with pure annotation * * @param {any} args - * @__PURE__ + * @__NO_SIDE_EFFECTS__ */ export const fnF = (args) => { console.log(args) return args } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ export default function fnDefault(args) { console.log(args) return args diff --git a/test/form/samples/pure-comment-function-declaration-preserve/main.js b/test/form/samples/no-side-effects-function-declaration-preserve/main.js similarity index 100% rename from test/form/samples/pure-comment-function-declaration-preserve/main.js rename to test/form/samples/no-side-effects-function-declaration-preserve/main.js diff --git a/test/form/samples/pure-comment-function-declaration/sub-functions.js b/test/form/samples/no-side-effects-function-declaration-preserve/sub-functions.js similarity index 73% rename from test/form/samples/pure-comment-function-declaration/sub-functions.js rename to test/form/samples/no-side-effects-function-declaration-preserve/sub-functions.js index 796ee69ab5d..ed703deb9db 100644 --- a/test/form/samples/pure-comment-function-declaration/sub-functions.js +++ b/test/form/samples/no-side-effects-function-declaration-preserve/sub-functions.js @@ -1,4 +1,4 @@ -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ export function fnFromSub (args) { console.log(args) return args diff --git a/test/form/samples/pure-comment-function-declaration/_config.js b/test/form/samples/no-side-effects-function-declaration/_config.js similarity index 69% rename from test/form/samples/pure-comment-function-declaration/_config.js rename to test/form/samples/no-side-effects-function-declaration/_config.js index 0aa9a5715b0..c3178070cc2 100644 --- a/test/form/samples/pure-comment-function-declaration/_config.js +++ b/test/form/samples/no-side-effects-function-declaration/_config.js @@ -1,5 +1,5 @@ // tests compiled from https://github.com/mishoo/UglifyJS2/blob/88c8f4e363e0d585b33ea29df560243d3dc74ce1/test/compress/pure_funcs.js module.exports = defineTest({ - description: 'pure annotations for function declarations' + description: '__NO_SIDE_EFFECTS__ annotations for function declarations' }); diff --git a/test/form/samples/pure-comment-function-declaration/_expected.js b/test/form/samples/no-side-effects-function-declaration/_expected.js similarity index 100% rename from test/form/samples/pure-comment-function-declaration/_expected.js rename to test/form/samples/no-side-effects-function-declaration/_expected.js diff --git a/test/form/samples/pure-comment-function-declaration-preserve/functions.js b/test/form/samples/no-side-effects-function-declaration/functions.js similarity index 77% rename from test/form/samples/pure-comment-function-declaration-preserve/functions.js rename to test/form/samples/no-side-effects-function-declaration/functions.js index e012b109ea2..effd3b96950 100644 --- a/test/form/samples/pure-comment-function-declaration-preserve/functions.js +++ b/test/form/samples/no-side-effects-function-declaration/functions.js @@ -7,26 +7,26 @@ export function fnEffects(args) { return args } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ function fnA (args) { console.log(args) return args } export { fnA } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ export function fnB (args) { console.log(args) return args } -export const fnC = /*#__PURE__*/ (args) => { +export const fnC = /*#__NO_SIDE_EFFECTS__*/ (args) => { console.log(args) return args } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ const fnD = (args) => { console.log(args) return args @@ -34,7 +34,7 @@ const fnD = (args) => { export { fnD } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ export const fnE = (args) => { console.log(args) return args @@ -44,14 +44,14 @@ export const fnE = (args) => { * This is a jsdoc comment, with pure annotation * * @param {any} args - * @__PURE__ + * @__NO_SIDE_EFFECTS__ */ export const fnF = (args) => { console.log(args) return args } -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ export default function fnDefault(args) { console.log(args) return args diff --git a/test/form/samples/pure-comment-function-declaration/main.js b/test/form/samples/no-side-effects-function-declaration/main.js similarity index 100% rename from test/form/samples/pure-comment-function-declaration/main.js rename to test/form/samples/no-side-effects-function-declaration/main.js diff --git a/test/form/samples/pure-comment-function-declaration-preserve/sub-functions.js b/test/form/samples/no-side-effects-function-declaration/sub-functions.js similarity index 73% rename from test/form/samples/pure-comment-function-declaration-preserve/sub-functions.js rename to test/form/samples/no-side-effects-function-declaration/sub-functions.js index 796ee69ab5d..ed703deb9db 100644 --- a/test/form/samples/pure-comment-function-declaration-preserve/sub-functions.js +++ b/test/form/samples/no-side-effects-function-declaration/sub-functions.js @@ -1,4 +1,4 @@ -/*#__PURE__*/ +/*#__NO_SIDE_EFFECTS__*/ export function fnFromSub (args) { console.log(args) return args From 9fa561b4057265cb9c57cc4f06b0602516e9efcf Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 4 Jun 2023 12:00:35 +0200 Subject: [PATCH 08/11] chore: coverage --- src/utils/commentAnnotations.ts | 18 +++++++++++------- .../_expected.js | 15 +++++++++++++-- .../functions.js | 13 ++++++++++++- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/utils/commentAnnotations.ts b/src/utils/commentAnnotations.ts index 87a74157125..7fdf78ec237 100644 --- a/src/utils/commentAnnotations.ts +++ b/src/utils/commentAnnotations.ts @@ -13,7 +13,8 @@ import { LogicalExpression, NewExpression, SequenceExpression, - VariableDeclaration + VariableDeclaration, + VariableDeclarator } from '../ast/nodes/NodeType'; import { SOURCEMAPPING_URL_RE } from './sourceMappingURL'; @@ -112,16 +113,18 @@ function markPureNode(node: NodeWithComments, comment: acorn.Comment, code: stri case VariableDeclaration: { // case: /*#__PURE__*/ const foo = () => {} const declaration = node as any; - if (declaration.declarations.length === 1 && declaration.kind === 'const') { - const init = declaration.declarations[0].init; - if (init.type === ArrowFunctionExpression || init.type === FunctionDeclaration) { - node = init; - continue; - } + if (declaration.kind === 'const') { + // jsdoc only applies to the first declaration + node = declaration.declarations[0].init; + continue; } invalidAnnotation = true; break; } + case VariableDeclarator: { + node = (node as any).init; + continue; + } case FunctionDeclaration: case ArrowFunctionExpression: case CallExpression: @@ -129,6 +132,7 @@ function markPureNode(node: NodeWithComments, comment: acorn.Comment, code: stri break; } default: { + console.log({ node }); invalidAnnotation = true; } } diff --git a/test/form/samples/no-side-effects-function-declaration-preserve/_expected.js b/test/form/samples/no-side-effects-function-declaration-preserve/_expected.js index 700f49b91d6..86ae63ae613 100644 --- a/test/form/samples/no-side-effects-function-declaration-preserve/_expected.js +++ b/test/form/samples/no-side-effects-function-declaration-preserve/_expected.js @@ -44,7 +44,7 @@ const fnE = (args) => { }; /** - * This is a jsdoc comment, with pure annotation + * This is a jsdoc comment, with no side effects annotation * * @param {any} args * @__NO_SIDE_EFFECTS__ @@ -56,4 +56,15 @@ const fnF = (args) => { const fnAlias = fnA; -export { fnA, fnAlias, fnB, fnC, fnD, fnE, fnEffects, fnF, fnFromSub, fnPure }; +/** + * Have both annotations + * + * @__PURE__ + * @__NO_SIDE_EFFECTS__ + */ +const fnBothAnnotations = (args) => { + console.log(args); + return args +}; + +export { fnA, fnAlias, fnB, fnBothAnnotations, fnC, fnD, fnE, fnEffects, fnF, fnFromSub, fnPure }; diff --git a/test/form/samples/no-side-effects-function-declaration-preserve/functions.js b/test/form/samples/no-side-effects-function-declaration-preserve/functions.js index effd3b96950..aba5b7fb84a 100644 --- a/test/form/samples/no-side-effects-function-declaration-preserve/functions.js +++ b/test/form/samples/no-side-effects-function-declaration-preserve/functions.js @@ -41,7 +41,7 @@ export const fnE = (args) => { } /** - * This is a jsdoc comment, with pure annotation + * This is a jsdoc comment, with no side effects annotation * * @param {any} args * @__NO_SIDE_EFFECTS__ @@ -60,3 +60,14 @@ export default function fnDefault(args) { export * from './sub-functions' export const fnAlias = fnA + +/** + * Have both annotations + * + * @__PURE__ + * @__NO_SIDE_EFFECTS__ + */ +export const fnBothAnnotations = (args) => { + console.log(args) + return args +} From 977219eb48115560133d2b27110c27a942291572 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 5 Jun 2023 13:10:00 +0200 Subject: [PATCH 09/11] chore: update suggestions --- src/ast/nodes/ArrowFunctionExpression.ts | 6 +----- src/ast/nodes/CallExpression.ts | 7 ++----- src/ast/nodes/NewExpression.ts | 6 +----- src/ast/nodes/shared/FunctionNode.ts | 11 ++--------- src/ast/nodes/shared/Node.ts | 16 +++++++++------- src/utils/commentAnnotations.ts | 1 - .../_expected.js | 9 ++++++++- .../functions.js | 7 +++++++ 8 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 4d14130b1d0..0179080f87b 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -1,4 +1,3 @@ -import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import type { NodeInteraction } from '../NodeInteractions'; import { INTERACTION_CALLED } from '../NodeInteractions'; @@ -39,10 +38,7 @@ export default class ArrowFunctionExpression extends FunctionBase { ): boolean { if (super.hasEffectsOnInteractionAtPath(path, interaction, context)) return true; if (interaction.type === INTERACTION_CALLED) { - if ( - (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotationNoSideEffects - ) { + if (this.annotationNoSideEffects) { return false; } diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 3a5ea39c4e3..689f6121421 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -1,5 +1,4 @@ import type MagicString from 'magic-string'; -import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import { BLANK } from '../../utils/blank'; import { errorCannotCallNamespace, errorEval } from '../../utils/error'; import { renderCallArguments } from '../../utils/renderCallArguments'; @@ -57,11 +56,9 @@ export default class CallExpression for (const argument of this.arguments) { if (argument.hasEffects(context)) return true; } - if ( - (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotationPure - ) + if (this.annotationPure) { return false; + } return ( this.callee.hasEffects(context) || this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context) diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index 206a3663e22..6c91ba90a6a 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -1,5 +1,4 @@ import type MagicString from 'magic-string'; -import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import { renderCallArguments } from '../../utils/renderCallArguments'; import type { RenderOptions } from '../../utils/renderHelpers'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; @@ -21,10 +20,7 @@ export default class NewExpression extends NodeBase { for (const argument of this.arguments) { if (argument.hasEffects(context)) return true; } - if ( - (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotationPure - ) { + if (this.annotationPure) { return false; } return ( diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 562e8289153..2d456407025 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,4 +1,3 @@ -import type { NormalizedTreeshakingOptions } from '../../../rollup/types'; import { type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; import type { NodeInteraction } from '../../NodeInteractions'; import { INTERACTION_CALLED } from '../../NodeInteractions'; @@ -47,10 +46,7 @@ export default class FunctionNode extends FunctionBase { hasEffects(context: HasEffectsContext): boolean { if (!this.deoptimized) this.applyDeoptimizations(); - if ( - (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotationNoSideEffects - ) { + if (this.annotationNoSideEffects) { return false; } @@ -64,10 +60,7 @@ export default class FunctionNode extends FunctionBase { ): boolean { if (super.hasEffectsOnInteractionAtPath(path, interaction, context)) return true; - if ( - (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotationNoSideEffects - ) { + if (this.annotationNoSideEffects) { return false; } diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 51013b1b80a..8a3827dcdb4 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -2,6 +2,7 @@ import type * as acorn from 'acorn'; import { locate, type Location } from 'locate-character'; import type MagicString from 'magic-string'; import type { AstContext } from '../../../Module'; +import type { NormalizedTreeshakingOptions } from '../../../rollup/types'; import type { RollupAnnotation } from '../../../utils/commentAnnotations'; import { ANNOTATION_KEY, INVALID_COMMENT_KEY } from '../../../utils/commentAnnotations'; import type { NodeRenderOptions, RenderOptions } from '../../../utils/renderHelpers'; @@ -268,13 +269,14 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { if (this.hasOwnProperty(key)) continue; if (key.charCodeAt(0) === 95 /* _ */) { if (key === ANNOTATION_KEY) { - this.annotations = value; - this.annotationNoSideEffects = this.annotations!.some( - comment => comment.annotationType === 'noSideEffects' - ); - this.annotationPure = this.annotations!.some( - comment => comment.annotationType === 'pure' - ); + const annotations = value as RollupAnnotation[]; + this.annotations = annotations; + if ((this.context.options.treeshake as NormalizedTreeshakingOptions).annotations) { + this.annotationNoSideEffects = annotations.some( + comment => comment.annotationType === 'noSideEffects' + ); + this.annotationPure = annotations.some(comment => comment.annotationType === 'pure'); + } } else if (key === INVALID_COMMENT_KEY) { for (const { start, end } of value as acorn.Comment[]) this.context.magicString.remove(start, end); diff --git a/src/utils/commentAnnotations.ts b/src/utils/commentAnnotations.ts index 7fdf78ec237..ad77451b495 100644 --- a/src/utils/commentAnnotations.ts +++ b/src/utils/commentAnnotations.ts @@ -132,7 +132,6 @@ function markPureNode(node: NodeWithComments, comment: acorn.Comment, code: stri break; } default: { - console.log({ node }); invalidAnnotation = true; } } diff --git a/test/form/samples/no-side-effects-function-declaration-preserve/_expected.js b/test/form/samples/no-side-effects-function-declaration-preserve/_expected.js index 86ae63ae613..33772214ec3 100644 --- a/test/form/samples/no-side-effects-function-declaration-preserve/_expected.js +++ b/test/form/samples/no-side-effects-function-declaration-preserve/_expected.js @@ -67,4 +67,11 @@ const fnBothAnnotations = (args) => { return args }; -export { fnA, fnAlias, fnB, fnBothAnnotations, fnC, fnD, fnE, fnEffects, fnF, fnFromSub, fnPure }; +// This annonation get ignored + +let fnLet = (args) => { + console.log(args); + return args +}; + +export { fnA, fnAlias, fnB, fnBothAnnotations, fnC, fnD, fnE, fnEffects, fnF, fnFromSub, fnLet, fnPure }; diff --git a/test/form/samples/no-side-effects-function-declaration-preserve/functions.js b/test/form/samples/no-side-effects-function-declaration-preserve/functions.js index aba5b7fb84a..399b6a6c38a 100644 --- a/test/form/samples/no-side-effects-function-declaration-preserve/functions.js +++ b/test/form/samples/no-side-effects-function-declaration-preserve/functions.js @@ -71,3 +71,10 @@ export const fnBothAnnotations = (args) => { console.log(args) return args } + +// This annonation get ignored +/** @__NO_SIDE_EFFECTS__ */ +export let fnLet = (args) => { + console.log(args) + return args +} From 071df0931655a158370cb509e1e0a3efe04c842c Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 5 Jun 2023 13:23:09 +0200 Subject: [PATCH 10/11] docs: add docs --- docs/configuration-options/index.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/configuration-options/index.md b/docs/configuration-options/index.md index 27be0d86e49..8f1f217ce39 100755 --- a/docs/configuration-options/index.md +++ b/docs/configuration-options/index.md @@ -2021,7 +2021,11 @@ If you discover a bug caused by the tree-shaking algorithm, please file an issue | CLI: | `--treeshake.annotations`/`--no-treeshake.annotations` | | Default: | `true` | -If `false`, ignore hints from pure annotations, i.e. comments containing `@__PURE__` or `#__PURE__`, when determining side effects of function calls and constructor invocations. These annotations need to immediately precede the call invocation to take effect. The following code will be completely removed unless this option is set to `false`, in which case it will remain unchanged. +If `false`, ignore hints from annotation in comments: + +##### `@__PURE__` + +For comments containing `@__PURE__` or `#__PURE__`, when determining side effects of function calls and constructor invocations. These annotations need to immediately precede the call invocation to take effect. The following code will be completely treeshaken unless this option is set to `false`, in which case it will remain unchanged. ```javascript /*@__PURE__*/ console.log('side-effect'); @@ -2035,6 +2039,25 @@ class Impure { /*@__PURE__*/ new Impure(); ``` +##### `@__NO_SIDE_EFFECTS__` + +For comments containing `@__NO_SIDE_EFFECTS__` or `#__NO_SIDE_EFFECTS__`, when determining side effects of function declaration. When a function been marked as no side effects, all calls to that function will be considered as no side effects. The following code will be completely treeshaken unless this option is set to `false`, in which case it will remain unchanged. + +```javascript +/*@__NO_SIDE_EFFECTS__*/ +function impure() { + console.log('side-effect'); +} + +/*@__NO_SIDE_EFFECTS__*/ +const impureArrowFn = () => { + console.log('side-effect'); +}; + +impure(); // <-- call will be considered as no side effects +impureArrowFn(); // <-- call will be considered as no side effects +``` + #### treeshake.correctVarValueBeforeDeclaration | | | From 16ded62742c17d133a2e017e928d00010e14e22a Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Tue, 6 Jun 2023 06:37:20 +0200 Subject: [PATCH 11/11] Slightly clarify wording --- docs/configuration-options/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/configuration-options/index.md b/docs/configuration-options/index.md index 8f1f217ce39..5def299fed0 100755 --- a/docs/configuration-options/index.md +++ b/docs/configuration-options/index.md @@ -2025,7 +2025,7 @@ If `false`, ignore hints from annotation in comments: ##### `@__PURE__` -For comments containing `@__PURE__` or `#__PURE__`, when determining side effects of function calls and constructor invocations. These annotations need to immediately precede the call invocation to take effect. The following code will be completely treeshaken unless this option is set to `false`, in which case it will remain unchanged. +Comments containing `@__PURE__` or `#__PURE__` mark a specific function call or constructor invocation as side effect free. That means that Rollup will tree-shake i.e. remove the call unless the return value is used in some code that is not tree-shaken. These annotations need to immediately precede the call invocation to take effect. The following code will be completely tree-shaken unless this option is set to `false`, in which case it will remain unchanged. ```javascript /*@__PURE__*/ console.log('side-effect'); @@ -2041,7 +2041,7 @@ class Impure { ##### `@__NO_SIDE_EFFECTS__` -For comments containing `@__NO_SIDE_EFFECTS__` or `#__NO_SIDE_EFFECTS__`, when determining side effects of function declaration. When a function been marked as no side effects, all calls to that function will be considered as no side effects. The following code will be completely treeshaken unless this option is set to `false`, in which case it will remain unchanged. +Comments containing `@__NO_SIDE_EFFECTS__` or `#__NO_SIDE_EFFECTS__` mark a function declaration itself as side effect free. When a function has been marked as having no side effects, all calls to that function will be considered to be side effect free. The following code will be completely tree-shaken unless this option is set to `false`, in which case it will remain unchanged. ```javascript /*@__NO_SIDE_EFFECTS__*/ @@ -2054,8 +2054,8 @@ const impureArrowFn = () => { console.log('side-effect'); }; -impure(); // <-- call will be considered as no side effects -impureArrowFn(); // <-- call will be considered as no side effects +impure(); // <-- call will be considered as side effect free +impureArrowFn(); // <-- call will be considered as side effect free ``` #### treeshake.correctVarValueBeforeDeclaration