diff --git a/src/ast/nodes/Literal.ts b/src/ast/nodes/Literal.ts index ca17e979675..192cffa8fe3 100644 --- a/src/ast/nodes/Literal.ts +++ b/src/ast/nodes/Literal.ts @@ -57,10 +57,6 @@ export default class Literal extends Node return path.length > 1; } - hasEffectsWhenAssignedAtPath(path: ObjectPath): boolean { - return path.length > 0; - } - hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, diff --git a/src/ast/nodes/TemplateLiteral.ts b/src/ast/nodes/TemplateLiteral.ts index 5f267419109..85ac5537ae7 100644 --- a/src/ast/nodes/TemplateLiteral.ts +++ b/src/ast/nodes/TemplateLiteral.ts @@ -1,9 +1,21 @@ import type MagicString from 'magic-string'; import type { RenderOptions } from '../../utils/renderHelpers'; +import { CallOptions } from '../CallOptions'; +import { HasEffectsContext } from '../ExecutionContext'; import type { ObjectPath } from '../utils/PathTracker'; +import { + getMemberReturnExpressionWhenCalled, + hasMemberEffectWhenCalled, + literalStringMembers +} from '../values'; import type * as NodeType from './NodeType'; import type TemplateElement from './TemplateElement'; -import { type LiteralValueOrUnknown, UnknownValue } from './shared/Expression'; +import { + ExpressionEntity, + type LiteralValueOrUnknown, + UNKNOWN_EXPRESSION, + UnknownValue +} from './shared/Expression'; import { type ExpressionNode, NodeBase } from './shared/Node'; export default class TemplateLiteral extends NodeBase { @@ -11,6 +23,8 @@ export default class TemplateLiteral extends NodeBase { declare quasis: TemplateElement[]; declare type: NodeType.tTemplateLiteral; + deoptimizeThisOnEventAtPath(): void {} + getLiteralValueAtPath(path: ObjectPath): LiteralValueOrUnknown { if (path.length > 0 || this.quasis.length !== 1) { return UnknownValue; @@ -18,6 +32,28 @@ export default class TemplateLiteral extends NodeBase { return this.quasis[0].value.cooked; } + getReturnExpressionWhenCalledAtPath(path: ObjectPath): ExpressionEntity { + if (path.length !== 1) { + return UNKNOWN_EXPRESSION; + } + return getMemberReturnExpressionWhenCalled(literalStringMembers, path[0]); + } + + hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { + return path.length > 1; + } + + hasEffectsWhenCalledAtPath( + path: ObjectPath, + callOptions: CallOptions, + context: HasEffectsContext + ): boolean { + if (path.length === 1) { + return hasMemberEffectWhenCalled(literalStringMembers, path[0], callOptions, context); + } + return true; + } + render(code: MagicString, options: RenderOptions): void { (code.indentExclusionRanges as [number, number][]).push([this.start, this.end]); super.render(code, options); diff --git a/src/ast/values.ts b/src/ast/values.ts index 1cf037140db..b6f0f90eb84 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -190,7 +190,7 @@ const literalNumberMembers: MemberDescriptions = assembleMemberDescriptions( objectMembers ); -const literalStringMembers: MemberDescriptions = assembleMemberDescriptions( +export const literalStringMembers: MemberDescriptions = assembleMemberDescriptions( { anchor: returnsString, diff --git a/test/form/samples/builtin-prototypes/template-literal/_config.js b/test/form/samples/builtin-prototypes/template-literal/_config.js new file mode 100644 index 00000000000..5884ca08844 --- /dev/null +++ b/test/form/samples/builtin-prototypes/template-literal/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'Tree-shake known string template literal prototype functions' +}; diff --git a/test/form/samples/builtin-prototypes/template-literal/_expected.js b/test/form/samples/builtin-prototypes/template-literal/_expected.js new file mode 100644 index 00000000000..693f9e21560 --- /dev/null +++ b/test/form/samples/builtin-prototypes/template-literal/_expected.js @@ -0,0 +1,8 @@ +// deep property access is forbidden +`ab`.x.y; + +// due to strict mode, extension is forbidden +`ab`.x = 1; + +// throws when called +`ab`(); diff --git a/test/form/samples/builtin-prototypes/template-literal/main.js b/test/form/samples/builtin-prototypes/template-literal/main.js new file mode 100644 index 00000000000..4e7a67f32fa --- /dev/null +++ b/test/form/samples/builtin-prototypes/template-literal/main.js @@ -0,0 +1,15 @@ +`ab`.trim(); +`ab`.trim().trim(); +`ab`.toString().trim(); + +// property access is allowed +const accessString = `ab`.x; + +// deep property access is forbidden +const deepString = `ab`.x.y; + +// due to strict mode, extension is forbidden +`ab`.x = 1; + +// throws when called +`ab`();