diff --git a/src/Graph.ts b/src/Graph.ts index a8a76e2907a..f81db59e077 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -17,7 +17,7 @@ import { PluginDriver } from './utils/PluginDriver'; import { BuildPhase } from './utils/buildPhase'; import { errImplicitDependantIsNotIncluded, error } from './utils/error'; import { analyseModuleExecution } from './utils/executionOrder'; -import { markPureCallExpressions } from './utils/pureComments'; +import { addAnnotations } from './utils/pureComments'; import relativeId from './utils/relativeId'; import { timeEnd, timeStart } from './utils/timers'; import { markModuleAndImpureDependenciesAsExecuted } from './utils/traverseStaticDependencies'; @@ -137,7 +137,7 @@ export default class Graph { options.onComment = onCommentOrig; - markPureCallExpressions(comments, ast, code); + addAnnotations(comments, ast, code); return ast; } diff --git a/src/Module.ts b/src/Module.ts index f4c65ec321e..a58525fb93f 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -19,7 +19,7 @@ import Program from './ast/nodes/Program'; import TemplateLiteral from './ast/nodes/TemplateLiteral'; import VariableDeclaration from './ast/nodes/VariableDeclaration'; import { nodeConstructors } from './ast/nodes/index'; -import { ExpressionNode, GenericEsTreeNode, NodeBase } from './ast/nodes/shared/Node'; +import { ExpressionNode, NodeBase } from './ast/nodes/shared/Node'; import ModuleScope from './ast/scopes/ModuleScope'; import { PathTracker, UNKNOWN_PATH } from './ast/utils/PathTracker'; import ExportDefaultVariable from './ast/variables/ExportDefaultVariable'; @@ -62,7 +62,6 @@ import { makeLegal } from './utils/identifierHelpers'; import { basename, extname } from './utils/path'; import relativeId from './utils/relativeId'; import { RenderOptions } from './utils/renderHelpers'; -import { SOURCEMAPPING_URL_COMMENT_RE } from './utils/sourceMappingURL'; import { timeEnd, timeStart } from './utils/timers'; import { markModuleAndImpureDependenciesAsExecuted } from './utils/traverseStaticDependencies'; import { MISSING_EXPORT_SHIM_VARIABLE } from './utils/variableNames'; @@ -121,34 +120,6 @@ const MISSING_EXPORT_SHIM_DESCRIPTION: ExportDescription = { localName: MISSING_EXPORT_SHIM_VARIABLE }; -function findSourceMappingURLComments(ast: acorn.Node, code: string): [number, number][] { - const ret: [number, number][] = []; - - const addCommentsPos = (start: number, end: number): void => { - if (start == end) { - return; - } - - let sourcemappingUrlMatch; - const interStatmentCode = code.slice(start, end); - while ((sourcemappingUrlMatch = SOURCEMAPPING_URL_COMMENT_RE.exec(interStatmentCode))) { - ret.push([ - start + sourcemappingUrlMatch.index, - start + SOURCEMAPPING_URL_COMMENT_RE.lastIndex - ]); - } - }; - - let prevStmtEnd = 0; - for (const stmt of (ast as GenericEsTreeNode).body) { - addCommentsPos(prevStmtEnd, stmt.start); - prevStmtEnd = stmt.end; - } - addCommentsPos(prevStmtEnd, code.length); - - return ret; -} - function getVariableForExportNameRecursive( target: Module | ExternalModule, name: string, @@ -251,7 +222,6 @@ export default class Module { usesTopLevelAwait = false; private allExportNames: Set | null = null; - private alwaysRemovedCode!: [number, number][]; private astContext!: AstContext; private readonly context: string; private customTransformCache!: boolean; @@ -688,7 +658,6 @@ export default class Module { } setSource({ - alwaysRemovedCode, ast, code, customTransformCache, @@ -700,7 +669,6 @@ export default class Module { transformFiles, ...moduleOptions }: TransformModuleJSON & { - alwaysRemovedCode?: [number, number][]; transformFiles?: EmittedFile[] | undefined; }): void { this.info.code = code; @@ -716,11 +684,9 @@ export default class Module { timeStart('generate ast', 3); - this.alwaysRemovedCode = alwaysRemovedCode || []; if (!ast) { ast = this.tryParse(); } - this.alwaysRemovedCode.push(...findSourceMappingURLComments(ast, this.info.code)); timeEnd('generate ast', 3); @@ -734,9 +700,6 @@ export default class Module { filename: (this.excludeFromSourcemap ? null : fileName)!, // don't include plugin helpers in sourcemap indentExclusionRanges: [] }); - for (const [start, end] of this.alwaysRemovedCode) { - this.magicString.remove(start, end); - } timeStart('analyse ast', 3); @@ -778,7 +741,6 @@ export default class Module { toJSON(): ModuleJSON { return { - alwaysRemovedCode: this.alwaysRemovedCode, ast: this.ast!.esTreeNode, code: this.info.code!, customTransformCache: this.customTransformCache, diff --git a/src/ast/keys.ts b/src/ast/keys.ts index 97ea42b8738..84cfaf0b77f 100644 --- a/src/ast/keys.ts +++ b/src/ast/keys.ts @@ -9,7 +9,7 @@ export const keys: { export function getAndCreateKeys(esTreeNode: GenericEsTreeNode): string[] { keys[esTreeNode.type] = Object.keys(esTreeNode).filter( - key => typeof esTreeNode[key] === 'object' && key !== '_rollupAnnotations' + key => typeof esTreeNode[key] === 'object' && key.charCodeAt(0) !== 95 /* _ */ ); return keys[esTreeNode.type]; } diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 5219a32a1e0..dc363ec0002 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -28,13 +28,7 @@ import { UNKNOWN_EXPRESSION, UnknownValue } from './shared/Expression'; -import { - Annotation, - ExpressionNode, - INCLUDE_PARAMETERS, - IncludeChildren, - NodeBase -} from './shared/Node'; +import { ExpressionNode, INCLUDE_PARAMETERS, IncludeChildren, NodeBase } from './shared/Node'; export default class CallExpression extends NodeBase implements DeoptimizableEntity { arguments!: (ExpressionNode | SpreadElement)[]; @@ -188,7 +182,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt } if ( (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotations?.some((a: Annotation) => a.pure) + this.annotations ) return false; return ( diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index a436760162c..6276ea091e8 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -1,6 +1,5 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { removeAnnotations } from '../../utils/treeshakeNode'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { BROKEN_FLOW_NONE, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import TrackingScope from '../scopes/TrackingScope'; @@ -89,7 +88,6 @@ export default class IfStatement extends StatementBase implements DeoptimizableE if (includesIfElse) { this.test.render(code, options); } else { - removeAnnotations(this, code); code.remove(this.start, this.consequent.start); } if (this.consequent.included && (noTreeshake || testValue === UnknownValue || testValue)) { diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index 07a1c6833da..8840b65d405 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -3,7 +3,7 @@ import { CallOptions } from '../CallOptions'; import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; -import { Annotation, ExpressionNode, NodeBase } from './shared/Node'; +import { ExpressionNode, NodeBase } from './shared/Node'; export default class NewExpression extends NodeBase { arguments!: ExpressionNode[]; @@ -19,7 +19,7 @@ export default class NewExpression extends NodeBase { } if ( (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotations?.some((a: Annotation) => a.pure) + this.annotations ) return false; return ( diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 689687c22fb..c85df3be7f0 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -2,6 +2,7 @@ import * as acorn from 'acorn'; import { locate, Location } from 'locate-character'; import MagicString from 'magic-string'; import { AstContext } from '../../../Module'; +import { ANNOTATION_KEY, INVALID_COMMENT_KEY } from '../../../utils/pureComments'; import { NodeRenderOptions, RenderOptions } from '../../../utils/renderHelpers'; import { Entity } from '../../Entity'; import { @@ -21,13 +22,9 @@ export interface GenericEsTreeNode extends acorn.Node { export const INCLUDE_PARAMETERS = 'variables' as const; export type IncludeChildren = boolean | typeof INCLUDE_PARAMETERS; -export interface Annotation { - comment?: acorn.Comment; - pure?: boolean; -} export interface Node extends Entity { - annotations?: Annotation[]; + annotations?: acorn.Comment[]; context: AstContext; end: number; esTreeNode: GenericEsTreeNode; @@ -87,7 +84,7 @@ export type StatementNode = Node; export interface ExpressionNode extends ExpressionEntity, Node {} export class NodeBase extends ExpressionEntity implements ExpressionNode { - annotations?: Annotation[]; + annotations?: acorn.Comment[]; context: AstContext; end!: number; esTreeNode: acorn.Node; @@ -201,8 +198,13 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { for (const [key, value] of Object.entries(esTreeNode)) { // That way, we can override this function to add custom initialisation and then call super.parseNode if (this.hasOwnProperty(key)) continue; - if (key === '_rollupAnnotations') { - this.annotations = value; + if (key.charCodeAt(0) === 95 /* _ */) { + if (key === ANNOTATION_KEY) { + this.annotations = value; + } else if (key === INVALID_COMMENT_KEY) { + for (const { start, end } of value as acorn.Comment[]) + this.context.magicString.remove(start, end); + } } else if (typeof value !== 'object' || value === null) { (this as GenericEsTreeNode)[key] = value; } else if (Array.isArray(value)) { diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 5426a30352b..00cc25bf187 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -114,7 +114,6 @@ export interface TransformModuleJSON extends Partial> } export interface ModuleJSON extends TransformModuleJSON { - alwaysRemovedCode: [number, number][]; ast: AcornNode; dependencies: string[]; id: string; diff --git a/src/utils/pureComments.ts b/src/utils/pureComments.ts index 8b28b6c3ffa..29cefc6faaa 100644 --- a/src/utils/pureComments.ts +++ b/src/utils/pureComments.ts @@ -1,12 +1,16 @@ import * as acorn from 'acorn'; import { BaseWalker, base as basicWalker } from 'acorn-walk'; import { + BinaryExpression, CallExpression, ChainExpression, + ConditionalExpression, ExpressionStatement, - NewExpression + LogicalExpression, + NewExpression, + SequenceExpression } from '../ast/nodes/NodeType'; -import { Annotation } from '../ast/nodes/shared/Node'; +import { SOURCEMAPPING_URL_RE } from './sourceMappingURL'; // patch up acorn-walk until class-fields are officially supported basicWalker.PropertyDefinition = function (node: any, st: any, c: any) { @@ -19,26 +23,17 @@ basicWalker.PropertyDefinition = function (node: any, st: any, c: any) { }; interface CommentState { + annotationIndex: number; + annotations: acorn.Comment[]; code: string; - commentIndex: number; - commentNodes: acorn.Comment[]; } -function isOnlyWhitespaceOrComments(code: string) { - // streamline the typical case - if (/^\s*$/.test(code)) return true; - try { - // successful only if it's a valid Program without statements - const ast = acorn.parse(code, { ecmaVersion: 'latest' }) as any; - return ast.body && ast.body.length === 0; - } catch { - // should only be reached by invalid annotations like: - // - // foo() /*@__PURE__*/ /* other */, bar(); - // - // where `code` is " /* other */, " - } - return false; +export const ANNOTATION_KEY = '_rollupAnnotations'; +export const INVALID_COMMENT_KEY = '_rollupRemoved'; + +interface NodeWithComments extends acorn.Node { + [ANNOTATION_KEY]?: acorn.Comment[]; + [INVALID_COMMENT_KEY]?: acorn.Comment[]; } function handlePureAnnotationsOfNode( @@ -46,49 +41,133 @@ function handlePureAnnotationsOfNode( state: CommentState, type: string = node.type ) { - let commentNode = state.commentNodes[state.commentIndex]; - while (commentNode && node.start >= commentNode.end) { - const between = state.code.substring(commentNode.end, node.start); - if (isOnlyWhitespaceOrComments(between)) markPureNode(node, commentNode); - commentNode = state.commentNodes[++state.commentIndex]; + const { annotations } = state; + let comment = annotations[state.annotationIndex]; + while (comment && node.start >= comment.end) { + markPureNode(node, comment, state.code); + comment = annotations[++state.annotationIndex]; } - if (commentNode && commentNode.end <= node.end) { + if (comment && comment.end <= node.end) { (basicWalker as BaseWalker)[type](node, state, handlePureAnnotationsOfNode); + while ((comment = annotations[state.annotationIndex]) && comment.end <= node.end) { + ++state.annotationIndex; + annotateNode(node, comment, false); + } } } -function markPureNode( - node: acorn.Node & { _rollupAnnotations?: Annotation[] }, - comment: acorn.Comment -) { - if (node._rollupAnnotations) { - node._rollupAnnotations.push({ comment }); +const neitherWithespaceNorBrackets = /[^\s(]/g; +const noWhitespace = /\S/g; + +function markPureNode(node: NodeWithComments, comment: acorn.Comment, code: string) { + const annotatedNodes = []; + let invalidAnnotation: boolean | undefined; + const codeInBetween = code.slice(comment.end, node.start); + if (doesNotMatchOutsideComment(codeInBetween, neitherWithespaceNorBrackets)) { + const parentStart = node.start; + while (true) { + annotatedNodes.push(node); + switch (node.type) { + case ExpressionStatement: + case ChainExpression: + node = (node as any).expression; + continue; + case SequenceExpression: + // if there are parentheses, the annotation would apply to the entire expression + if (doesNotMatchOutsideComment(code.slice(parentStart, node.start), noWhitespace)) { + node = (node as any).expressions[0]; + continue; + } + invalidAnnotation = true; + break; + case ConditionalExpression: + // if there are parentheses, the annotation would apply to the entire expression + if (doesNotMatchOutsideComment(code.slice(parentStart, node.start), noWhitespace)) { + node = (node as any).test; + continue; + } + invalidAnnotation = true; + break; + case LogicalExpression: + case BinaryExpression: + // if there are parentheses, the annotation would apply to the entire expression + if (doesNotMatchOutsideComment(code.slice(parentStart, node.start), noWhitespace)) { + node = (node as any).left; + continue; + } + invalidAnnotation = true; + break; + case CallExpression: + case NewExpression: + break; + default: + invalidAnnotation = true; + } + break; + } } else { - node._rollupAnnotations = [{ comment }]; + invalidAnnotation = true; } - while (node.type === ExpressionStatement || node.type === ChainExpression) { - node = (node as any).expression; + if (invalidAnnotation) { + annotateNode(node, comment, false); + } else { + for (const node of annotatedNodes) { + annotateNode(node, comment, true); + } } - if (node.type === CallExpression || node.type === NewExpression) { - if (node._rollupAnnotations) { - node._rollupAnnotations.push({ pure: true }); - } else { - node._rollupAnnotations = [{ pure: true }]; +} + +function doesNotMatchOutsideComment(code: string, forbiddenChars: RegExp): boolean { + let nextMatch: RegExpExecArray | null; + while ((nextMatch = forbiddenChars.exec(code)) !== null) { + if (nextMatch[0] === '/') { + const charCodeAfterSlash = code.charCodeAt(forbiddenChars.lastIndex); + if (charCodeAfterSlash === 42 /*"*"*/) { + forbiddenChars.lastIndex = code.indexOf('*/', forbiddenChars.lastIndex + 1) + 2; + continue; + } else if (charCodeAfterSlash === 47 /*"/"*/) { + forbiddenChars.lastIndex = code.indexOf('\n', forbiddenChars.lastIndex + 1) + 1; + continue; + } } + forbiddenChars.lastIndex = 0; + return false; } + return true; } const pureCommentRegex = /[@#]__PURE__/; -const isPureComment = (comment: acorn.Comment) => pureCommentRegex.test(comment.value); -export function markPureCallExpressions( +export function addAnnotations( comments: acorn.Comment[], esTreeAst: acorn.Node, code: string ): void { + const annotations: acorn.Comment[] = []; + 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)) { + sourceMappingComments.push(comment); + } + } + for (const comment of sourceMappingComments) { + annotateNode(esTreeAst, comment, false); + } handlePureAnnotationsOfNode(esTreeAst, { - code, - commentIndex: 0, - commentNodes: comments.filter(isPureComment) + annotationIndex: 0, + annotations, + code }); } + +function annotateNode(node: NodeWithComments, comment: acorn.Comment, valid: boolean) { + const key = valid ? ANNOTATION_KEY : INVALID_COMMENT_KEY; + const property = node[key]; + if (property) { + property.push(comment); + } else { + node[key] = [comment]; + } +} diff --git a/src/utils/sourceMappingURL.ts b/src/utils/sourceMappingURL.ts index eebd56ab497..ef4a35413c1 100644 --- a/src/utils/sourceMappingURL.ts +++ b/src/utils/sourceMappingURL.ts @@ -1,15 +1,9 @@ // this looks ridiculous, but it prevents sourcemap tooling from mistaking // this for an actual sourceMappingURL -let SOURCEMAPPING_URL = 'sourceMa'; +export let SOURCEMAPPING_URL = 'sourceMa'; SOURCEMAPPING_URL += 'ppingURL'; + const whiteSpaceNoNewline = '[ \\f\\r\\t\\v\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]'; -const SOURCEMAPPING_URL_LINE_COMMENT_RE = `//#${whiteSpaceNoNewline}+${SOURCEMAPPING_URL}=.+`; -const SOURCEMAPPING_URL_BLOCK_COMMENT_RE = `/\\*#${whiteSpaceNoNewline}+${SOURCEMAPPING_URL}=.+\\*/`; -const SOURCEMAPPING_URL_COMMENT_RE = new RegExp( - `(${SOURCEMAPPING_URL_LINE_COMMENT_RE})|(${SOURCEMAPPING_URL_BLOCK_COMMENT_RE})`, - 'g' -); - -export { SOURCEMAPPING_URL, SOURCEMAPPING_URL_COMMENT_RE }; +export const SOURCEMAPPING_URL_RE = new RegExp(`^#${whiteSpaceNoNewline}+${SOURCEMAPPING_URL}=.+`); diff --git a/src/utils/treeshakeNode.ts b/src/utils/treeshakeNode.ts index 8c3323eb0a9..d59d4ee5dcf 100644 --- a/src/utils/treeshakeNode.ts +++ b/src/utils/treeshakeNode.ts @@ -6,11 +6,8 @@ export function treeshakeNode(node: Node, code: MagicString, start: number, end: code.remove(start, end); if (node.annotations) { for (const annotation of node.annotations) { - if (!annotation.comment) { - continue; - } - if (annotation.comment.start < start) { - code.remove(annotation.comment.start, annotation.comment.end); + if (annotation.start < start) { + code.remove(annotation.start, annotation.end); } else { return; } @@ -23,8 +20,8 @@ export function removeAnnotations(node: Node, code: MagicString): void { node = node.parent as Node; } if (node.annotations) { - for (const annotation of node.annotations.filter(a => a.comment)) { - code.remove(annotation.comment!.start, annotation.comment!.end); + for (const annotation of node.annotations) { + code.remove(annotation.start, annotation.end); } } } diff --git a/test/form/samples/nested-pure-comments/_config.js b/test/form/samples/nested-pure-comments/_config.js new file mode 100644 index 00000000000..27977b096bd --- /dev/null +++ b/test/form/samples/nested-pure-comments/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'correctly associates pure comments before sequence expressions etc.' +}; diff --git a/test/form/samples/nested-pure-comments/_expected.js b/test/form/samples/nested-pure-comments/_expected.js new file mode 100644 index 00000000000..62590bf63dc --- /dev/null +++ b/test/form/samples/nested-pure-comments/_expected.js @@ -0,0 +1,18 @@ +// Sequence expression +(keep()); +keep(); +const foo = (/*@__PURE__*/keep()); + +// Conditional expression +(keep() ? 1 : 2); +keep(); + +// Logical expression +(keep() || 1); +keep(); + +// Binary expression +(keep() / 1); +1 / keep(); + +export { foo }; diff --git a/test/form/samples/nested-pure-comments/main.js b/test/form/samples/nested-pure-comments/main.js new file mode 100644 index 00000000000..1e4ce33d89c --- /dev/null +++ b/test/form/samples/nested-pure-comments/main.js @@ -0,0 +1,24 @@ +// Sequence expression +/*@__PURE__*/(keep(), /*@__PURE__*/remove()); +/*@__PURE__*/remove(), keep(), /*@__PURE__*/remove(); +/*@__PURE__*/remove(), /*@__PURE__*/remove(); +export const foo = /*@__PURE__*/(/*@__PURE__*/remove(), /*@__PURE__*/keep()); + +// Conditional expression +/*@__PURE__*/(keep() ? 1 : 2); +/*@__PURE__*/remove() ? /*@__PURE__*/remove() : /*@__PURE__*/remove(); +false ? 1 /*@__PURE__*/ : keep(); + +// Logical expression +/*@__PURE__*/(keep() || 1); +/*@__PURE__*/remove() || /*@__PURE__*/remove(); +false /*@__PURE__*/ || keep(); + +// Binary expression +/* @__PURE__ */(keep() / 1); +/* @__PURE__ */remove() / /* @__PURE__ */remove(); +1 /* @__PURE__ */ / keep(); + +// Calls with parentheses +/*@__PURE__*/(remove()); +/*@__PURE__*/(new Remove()); diff --git a/test/form/samples/pure-comment-line-break/_expected.js b/test/form/samples/pure-comment-line-break/_expected.js index a11dfe91559..134dcf3ff30 100644 --- a/test/form/samples/pure-comment-line-break/_expected.js +++ b/test/form/samples/pure-comment-line-break/_expected.js @@ -6,7 +6,7 @@ console.log('should remain impure'); console.log('code'); console.log('should remain impure'); -console.log('code')/*@__PURE__*/; +console.log('code'); console.log('should remain impure'); console.log('should remain impure'); @@ -16,7 +16,7 @@ console.log('should remain impure'); console.log('code'), console.log('should remain impure'); -console.log('code')/*@__PURE__*/, +console.log('code'), console.log('should remain impure'); console.log('should remain impure'); @@ -35,20 +35,16 @@ console.log('should remain impure', x); { console.log('should remain impure'); } -keep1() /*@__PURE__*/ ; keep2(); +keep1() ; keep2(); keep3() ; -keep4() /*@__PURE__*/ ; /* other comment */ keep5(); -keep6() /*@__PURE__*/ ; // other comment +keep4() ; /* other comment */ keep5(); +keep6() ; // other comment keep7(); -keep8() /*@__PURE__*/ && keep9(); +keep8() && keep9(); -/*@__PURE__*/ Drop1(), // FIXME: unrelated issue -Keep1() /*@__PURE__*/ , Keep2(), + Keep1() , Keep2(), Keep3() , -Keep4() /*@__PURE__*/ , /* other comment */ Keep5(), -Keep6() /*@__PURE__*/ , // other comment +Keep4() , /* other comment */ Keep5(), +Keep6() , // other comment Keep7(), -Keep8() /*@__PURE__*/ && Keep9(); - -// FIXME: unrelated issue -/*@__PURE__*/ Drop10(); +Keep8() && Keep9(); diff --git a/test/form/samples/pure-comment-line-break/main.js b/test/form/samples/pure-comment-line-break/main.js index 38dc5792269..cadb2158f58 100644 --- a/test/form/samples/pure-comment-line-break/main.js +++ b/test/form/samples/pure-comment-line-break/main.js @@ -54,7 +54,7 @@ keep6() /*@__PURE__*/ ; // other comment keep7(); keep8() /*@__PURE__*/ && keep9(); -/*@__PURE__*/ Drop1(), // FIXME: unrelated issue +/*@__PURE__*/ Drop1(), /*@__PURE__*/ Drop2(), Keep1() /*@__PURE__*/ , Keep2(), @@ -65,7 +65,6 @@ Keep6() /*@__PURE__*/ , // other comment Keep7(), Keep8() /*@__PURE__*/ && Keep9(); -// FIXME: unrelated issue /*@__PURE__*/ Drop10(), /*@__PURE__*/ Drop11(), /*@__PURE__*/ Drop12(), diff --git a/test/form/samples/pure-comment-scenarios-complex/_expected.js b/test/form/samples/pure-comment-scenarios-complex/_expected.js index 32b4350eab2..11dd422687e 100644 --- a/test/form/samples/pure-comment-scenarios-complex/_expected.js +++ b/test/form/samples/pure-comment-scenarios-complex/_expected.js @@ -7,8 +7,8 @@ global.iife1 = /*@__PURE__*/(function() { return iife1; })(); -// comment #__PURE__ comment -bar(), baz(), quux(); + +baz(), quux(); a.b(), f.g(); /* @__PURE__ */(function(){})() || true ? foo() : bar(); foo() ; diff --git a/test/form/samples/pure-comment-scenarios-simple/_expected.js b/test/form/samples/pure-comment-scenarios-simple/_expected.js index 4ff2aeb3e3e..58e48fb6031 100644 --- a/test/form/samples/pure-comment-scenarios-simple/_expected.js +++ b/test/form/samples/pure-comment-scenarios-simple/_expected.js @@ -1,8 +1,8 @@ // removed // can be simplified -(/*@__PURE__*/ x(), y()); -(/*@__PURE__*/ new x(), y()); +( y()); +( y()); (w(), y()); (w(), y()); /*@__PURE__*/(g() || h())(x(), y()); @@ -15,23 +15,23 @@ new (/*@__PURE__*/ (a() || b()))(c(), d()); [ w(), /*@__PURE__*/ new x(), y() ]; // retained -(/*@__PURE__*/ a)(); -(/*@__PURE__*/ b)(1)(2)(3); +( a)(); +( b)(1)(2)(3); (/*@__PURE__*/ c(1))(2)(3); (/*@__PURE__*/ d(1)(2))(3); -(/*@__PURE__*/ e).x(1).y(2).z(3); -(/*@__PURE__*/ f.x)(1).y(2).z(3); +( e).x(1).y(2).z(3); +( f.x)(1).y(2).z(3); (/*@__PURE__*/ g.x(1)).y(2).z(3); -(/*@__PURE__*/ h.x(1).y)(2).z(3); +( h.x(1).y)(2).z(3); (/*@__PURE__*/ i.x(1).y(2)).z(3); -(/*@__PURE__*/ j.x(1).y(2).z)(3); -new (/*@__PURE__*/ k)(); -new (/*@__PURE__*/ l)(1)(2)(3); +( j.x(1).y(2).z)(3); +new ( k)(); +new ( l)(1)(2)(3); (/*@__PURE__*/ new m(1))(2)(3); (/*@__PURE__*/ new n(1)(2))(3); -new (/*@__PURE__*/ o).x(1).y(2).z(3); -/* */ new (/*@__PURE__*/ p.x)(1).y(2).z(3); +new ( o).x(1).y(2).z(3); +/* */ new ( p.x)(1).y(2).z(3); (/*@__PURE__*/ new q.x(1)).y(2).z(3); -(/*@__PURE__*/ new r.x(1).y)(2).z(3); +( new r.x(1).y)(2).z(3); (/*@__PURE__*/ new s.x(1).y(2)).z(3); -(/*@__PURE__*/ new t.x(1).y(2).z)(3); +( new t.x(1).y(2).z)(3); diff --git a/test/form/samples/pure-comments-disabled/_expected.js b/test/form/samples/pure-comments-disabled/_expected.js index e19ce552917..01d3844659d 100644 --- a/test/form/samples/pure-comments-disabled/_expected.js +++ b/test/form/samples/pure-comments-disabled/_expected.js @@ -2,27 +2,27 @@ /*@__PURE__*/ a(); /*@__PURE__*/ new a(); -console.log('code')/*@__PURE__*/; +console.log('code'); console.log('should remain impure'); /*@__PURE__*/ drop1(); /*@__PURE__*/ drop2(); -keep1() /*@__PURE__*/ ; keep2(); +keep1() ; keep2(); keep3() ; /*@__PURE__*/ drop3(); -keep4() /*@__PURE__*/ ; /* other comment */ keep5(); -keep6() /*@__PURE__*/ ; // other comment +keep4() ; /* other comment */ keep5(); +keep6() ; // other comment keep7(); -keep8() /*@__PURE__*/ && keep9(); +keep8() && keep9(); /*@__PURE__*/ Drop1(), /*@__PURE__*/ Drop2(), -Keep1() /*@__PURE__*/ , Keep2(), +Keep1() , Keep2(), Keep3() , /*@__PURE__*/ Drop3(), -Keep4() /*@__PURE__*/ , /* other comment */ Keep5(), -Keep6() /*@__PURE__*/ , // other comment +Keep4() , /* other comment */ Keep5(), +Keep6() , // other comment Keep7(), -Keep8() /*@__PURE__*/ && Keep9(); +Keep8() && Keep9(); diff --git a/test/form/samples/remove-invalid-pure-comments/_config.js b/test/form/samples/remove-invalid-pure-comments/_config.js new file mode 100644 index 00000000000..f3066021ab3 --- /dev/null +++ b/test/form/samples/remove-invalid-pure-comments/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes invalidly placed pure annotations' +}; diff --git a/test/form/samples/remove-invalid-pure-comments/_expected.js b/test/form/samples/remove-invalid-pure-comments/_expected.js new file mode 100644 index 00000000000..606d9639510 --- /dev/null +++ b/test/form/samples/remove-invalid-pure-comments/_expected.js @@ -0,0 +1,5 @@ +const a = 3; +const b = 3; +const c = 3; + +export { a, b, c }; diff --git a/test/form/samples/remove-invalid-pure-comments/main.js b/test/form/samples/remove-invalid-pure-comments/main.js new file mode 100644 index 00000000000..7bd0f87aaf8 --- /dev/null +++ b/test/form/samples/remove-invalid-pure-comments/main.js @@ -0,0 +1,3 @@ +/*@__PURE__*/export const a = 3; +export /*@__PURE__*/const b = 3; +export const /*@__PURE__*/c = 3; diff --git a/test/form/samples/remove-tree-shaken-pure-comments/_config.js b/test/form/samples/remove-tree-shaken-pure-comments/_config.js new file mode 100644 index 00000000000..39021abc906 --- /dev/null +++ b/test/form/samples/remove-tree-shaken-pure-comments/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes pure comments of tree-shaken nodes' +}; diff --git a/test/form/samples/remove-tree-shaken-pure-comments/_expected.js b/test/form/samples/remove-tree-shaken-pure-comments/_expected.js new file mode 100644 index 00000000000..a00ec1cf2a3 --- /dev/null +++ b/test/form/samples/remove-tree-shaken-pure-comments/_expected.js @@ -0,0 +1,3 @@ +kept() ; + +kept() ; diff --git a/test/form/samples/remove-tree-shaken-pure-comments/main.js b/test/form/samples/remove-tree-shaken-pure-comments/main.js new file mode 100644 index 00000000000..81868238328 --- /dev/null +++ b/test/form/samples/remove-tree-shaken-pure-comments/main.js @@ -0,0 +1,3 @@ +/*@__PURE__*/(() => false)() /*@__PURE__*/ ? removed() /*@__PURE__*/ : kept() /*@__PURE__*/; + +/*@__PURE__*/(() => true)() /*@__PURE__*/ && kept() /*@__PURE__*/; diff --git a/test/function/samples/uses-supplied-ast/_config.js b/test/function/samples/uses-supplied-ast/_config.js index 66f80912006..eff176ea03b 100644 --- a/test/function/samples/uses-supplied-ast/_config.js +++ b/test/function/samples/uses-supplied-ast/_config.js @@ -18,6 +18,7 @@ const modules = { baz: 'export default 42;' }; +modules.foo.ast._ignoredProp = {}; module.exports = { description: 'uses supplied AST',