@lwc/babel-plugin-component 9.0.3 → 9.0.4-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,7 +23,10 @@ declare const TEMPLATE_KEY = "tmpl";
23
23
  declare const COMPONENT_NAME_KEY = "sel";
24
24
  declare const API_VERSION_KEY = "apiVersion";
25
25
  declare const COMPONENT_CLASS_ID = "__lwc_component_class_internal";
26
+ declare const PRIVATE_METHOD_PREFIX = "__lwc_component_class_internal_private_";
27
+ declare const PRIVATE_METHOD_METADATA_KEY = "__lwcTransformedPrivateMethods";
28
+ declare const ENABLE_PRIVATE_METHODS_KEY = "enablePrivateMethods";
26
29
  declare const SYNTHETIC_ELEMENT_INTERNALS_KEY = "enableSyntheticElementInternals";
27
30
  declare const COMPONENT_FEATURE_FLAG_KEY = "componentFeatureFlag";
28
- export { DECORATOR_TYPES, LWC_PACKAGE_ALIAS, LWC_PACKAGE_EXPORTS, LWC_COMPONENT_PROPERTIES, REGISTER_COMPONENT_ID, REGISTER_DECORATORS_ID, TEMPLATE_KEY, COMPONENT_NAME_KEY, API_VERSION_KEY, COMPONENT_CLASS_ID, SYNTHETIC_ELEMENT_INTERNALS_KEY, COMPONENT_FEATURE_FLAG_KEY, };
31
+ export { DECORATOR_TYPES, LWC_PACKAGE_ALIAS, LWC_PACKAGE_EXPORTS, LWC_COMPONENT_PROPERTIES, REGISTER_COMPONENT_ID, REGISTER_DECORATORS_ID, TEMPLATE_KEY, COMPONENT_NAME_KEY, API_VERSION_KEY, COMPONENT_CLASS_ID, PRIVATE_METHOD_PREFIX, PRIVATE_METHOD_METADATA_KEY, ENABLE_PRIVATE_METHODS_KEY, SYNTHETIC_ELEMENT_INTERNALS_KEY, COMPONENT_FEATURE_FLAG_KEY, };
29
32
  //# sourceMappingURL=constants.d.ts.map
package/dist/index.cjs CHANGED
@@ -41,6 +41,9 @@ const TEMPLATE_KEY = 'tmpl';
41
41
  const COMPONENT_NAME_KEY = 'sel';
42
42
  const API_VERSION_KEY = 'apiVersion';
43
43
  const COMPONENT_CLASS_ID = '__lwc_component_class_internal';
44
+ const PRIVATE_METHOD_PREFIX = '__lwc_component_class_internal_private_';
45
+ const PRIVATE_METHOD_METADATA_KEY = '__lwcTransformedPrivateMethods';
46
+ const ENABLE_PRIVATE_METHODS_KEY = 'enablePrivateMethods';
44
47
  const SYNTHETIC_ELEMENT_INTERNALS_KEY = 'enableSyntheticElementInternals';
45
48
  const COMPONENT_FEATURE_FLAG_KEY = 'componentFeatureFlag';
46
49
 
@@ -123,6 +126,9 @@ function component ({ types: t }) {
123
126
  if (state.opts.enableSyntheticElementInternals === true) {
124
127
  properties.push(t.objectProperty(t.identifier(SYNTHETIC_ELEMENT_INTERNALS_KEY), t.booleanLiteral(true)));
125
128
  }
129
+ if (state.opts.enablePrivateMethods === true) {
130
+ properties.push(t.objectProperty(t.identifier(ENABLE_PRIVATE_METHODS_KEY), t.booleanLiteral(true)));
131
+ }
126
132
  const registerComponentExpression = t.callExpression(registerComponentId, [
127
133
  node,
128
134
  t.objectExpression(properties),
@@ -259,6 +265,30 @@ function incrementMetricCounter(metric, state) {
259
265
  function isErrorRecoveryMode(state) {
260
266
  return state.file.opts?.parserOpts?.errorRecovery ?? false;
261
267
  }
268
+ /**
269
+ * Copies optional metadata properties between ClassMethod and ClassPrivateMethod nodes.
270
+ * These properties are not accepted by the t.classMethod() / t.classPrivateMethod() builders,
271
+ * so they must be transferred manually after node creation. Both the forward and reverse
272
+ * private-method transforms use this to maintain round-trip parity.
273
+ */
274
+ function copyMethodMetadata(source, target) {
275
+ if (source.returnType != null)
276
+ target.returnType = source.returnType;
277
+ if (source.typeParameters != null)
278
+ target.typeParameters = source.typeParameters;
279
+ if (source.loc != null)
280
+ target.loc = source.loc;
281
+ if (source.abstract != null)
282
+ target.abstract = source.abstract;
283
+ if (source.access != null)
284
+ target.access = source.access;
285
+ if (source.accessibility != null)
286
+ target.accessibility = source.accessibility;
287
+ if (source.optional != null)
288
+ target.optional = source.optional;
289
+ if (source.override != null)
290
+ target.override = source.override;
291
+ }
262
292
 
263
293
  /*
264
294
  * Copyright (c) 2023, salesforce.com, inc.
@@ -1244,6 +1274,197 @@ function compilerVersionNumber({ types: t }) {
1244
1274
  };
1245
1275
  }
1246
1276
 
1277
+ /*
1278
+ * Copyright (c) 2025, salesforce.com, inc.
1279
+ * All rights reserved.
1280
+ * SPDX-License-Identifier: MIT
1281
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1282
+ */
1283
+ // We only transform kind: 'method'. Other kinds ('get', 'set', 'constructor') are left alone.
1284
+ const METHOD_KIND = 'method';
1285
+ /**
1286
+ * Standalone Babel plugin that transforms private method identifiers from
1287
+ * `#privateMethod` to `__lwc_component_class_internal_private_privateMethod`.
1288
+ *
1289
+ * This must be registered BEFORE the main LWC class transform plugin so that
1290
+ * private methods are converted to regular methods before decorator and class
1291
+ * property processing.
1292
+ *
1293
+ * Uses Program > path.traverse() rather than a top-level ClassPrivateMethod visitor
1294
+ * because the reverse transform has a ClassMethod visitor in the same Babel pass.
1295
+ * A direct ClassPrivateMethod visitor would replace nodes that the reverse transform
1296
+ * immediately converts back, creating an infinite loop. The manual traverse ensures
1297
+ * all forward replacements complete before the reverse visitor sees any ClassMethod.
1298
+ */
1299
+ function privateMethodTransform({ types: t, }) {
1300
+ return {
1301
+ visitor: {
1302
+ Program(path, state) {
1303
+ const transformedNames = new Set();
1304
+ // Phase 1: Collect base names of all private methods (kind: 'method')
1305
+ // so that Phase 2 can transform invocations even for forward references
1306
+ // (call site visited before the method definition).
1307
+ const privateMethodBaseNames = new Set();
1308
+ path.traverse({
1309
+ ClassPrivateMethod(methodPath) {
1310
+ const key = methodPath.get('key');
1311
+ if (key.isPrivateName() && methodPath.node.kind === METHOD_KIND) {
1312
+ privateMethodBaseNames.add(key.node.id.name);
1313
+ }
1314
+ },
1315
+ });
1316
+ // Phase 2: Transform definitions and invocations
1317
+ path.traverse({
1318
+ ClassPrivateMethod(methodPath, methodState) {
1319
+ const key = methodPath.get('key');
1320
+ if (!key.isPrivateName()) {
1321
+ return;
1322
+ }
1323
+ if (methodPath.node.kind !== METHOD_KIND) {
1324
+ handleError(methodPath, {
1325
+ errorInfo: errors.DecoratorErrors.UNSUPPORTED_PRIVATE_MEMBER,
1326
+ messageArgs: ['accessor methods'],
1327
+ }, methodState);
1328
+ return;
1329
+ }
1330
+ const node = methodPath.node;
1331
+ // Reject private methods with decorators (e.g. @api, @track, @wire)
1332
+ if (node.decorators && node.decorators.length > 0) {
1333
+ handleError(methodPath, {
1334
+ errorInfo: errors.DecoratorErrors.DECORATOR_ON_PRIVATE_METHOD,
1335
+ }, methodState);
1336
+ return;
1337
+ }
1338
+ const privateName = key.node.id.name;
1339
+ const transformedName = `${PRIVATE_METHOD_PREFIX}${privateName}`;
1340
+ const keyReplacement = t.identifier(transformedName);
1341
+ // Create a new ClassMethod node to replace the ClassPrivateMethod
1342
+ // https://babeljs.io/docs/babel-types#classmethod
1343
+ const classMethod = t.classMethod(METHOD_KIND, keyReplacement, node.params, node.body, node.computed, node.static, node.generator, node.async);
1344
+ copyMethodMetadata(node, classMethod);
1345
+ // Replace the entire ClassPrivateMethod node with the new ClassMethod node
1346
+ // (we can't just replace the key of type PrivateName with type Identifier)
1347
+ methodPath.replaceWith(classMethod);
1348
+ transformedNames.add(transformedName);
1349
+ },
1350
+ MemberExpression(memberPath) {
1351
+ const property = memberPath.node.property;
1352
+ if (t.isPrivateName(property)) {
1353
+ const baseName = property.id.name;
1354
+ if (privateMethodBaseNames.has(baseName)) {
1355
+ const prefixedName = `${PRIVATE_METHOD_PREFIX}${baseName}`;
1356
+ memberPath
1357
+ .get('property')
1358
+ .replaceWith(t.identifier(prefixedName));
1359
+ }
1360
+ }
1361
+ },
1362
+ ClassPrivateProperty(propPath, propState) {
1363
+ handleError(propPath, {
1364
+ errorInfo: errors.DecoratorErrors.UNSUPPORTED_PRIVATE_MEMBER,
1365
+ messageArgs: ['fields'],
1366
+ }, propState);
1367
+ },
1368
+ }, state);
1369
+ state.file.metadata[PRIVATE_METHOD_METADATA_KEY] = transformedNames;
1370
+ },
1371
+ },
1372
+ };
1373
+ }
1374
+
1375
+ /*
1376
+ * Copyright (c) 2025, salesforce.com, inc.
1377
+ * All rights reserved.
1378
+ * SPDX-License-Identifier: MIT
1379
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1380
+ */
1381
+ /**
1382
+ * Standalone Babel plugin that reverses the private method transformation by converting
1383
+ * methods with prefix {@link PRIVATE_METHOD_PREFIX} back to ClassPrivateMethod nodes,
1384
+ * and restoring prefixed MemberExpression properties back to PrivateName nodes.
1385
+ *
1386
+ * This must be registered AFTER @babel/plugin-transform-class-properties so that
1387
+ * class properties are fully transformed before private methods are restored.
1388
+ *
1389
+ * Round-trip parity: to match {@link ./private-method-transform.ts}, this transform must copy the same
1390
+ * properties from ClassMethod onto ClassPrivateMethod when present: returnType, typeParameters, loc,
1391
+ * abstract, access, accessibility, optional, override (plus async, generator, computed from the builder).
1392
+ *
1393
+ * @see {@link ./private-method-transform.ts} for original transformation
1394
+ */
1395
+ function reversePrivateMethodTransform({ types: t, }) {
1396
+ // Scoped to this plugin instance's closure. Safe as long as each Babel run creates a
1397
+ // fresh plugin via LwcReversePrivateMethodTransform(); would accumulate across files if
1398
+ // the same instance were ever reused.
1399
+ const reverseTransformedNames = new Set();
1400
+ return {
1401
+ visitor: {
1402
+ ClassMethod(path, state) {
1403
+ const key = path.get('key');
1404
+ // kind: 'method' | 'get' | 'set' - only 'method' is in scope.
1405
+ if (key.isIdentifier() && path.node.kind === 'method') {
1406
+ const methodName = key.node.name;
1407
+ if (methodName.startsWith(PRIVATE_METHOD_PREFIX)) {
1408
+ const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
1409
+ // If the method was not transformed by the forward pass, it is a
1410
+ // user-defined method that collides with the reserved prefix.
1411
+ if (!forwardTransformedNames || !forwardTransformedNames.has(methodName)) {
1412
+ const message = errors.DecoratorErrors.PRIVATE_METHOD_NAME_COLLISION.message.replace('{0}', methodName);
1413
+ throw path.buildCodeFrameError(message);
1414
+ }
1415
+ const originalPrivateName = methodName.replace(PRIVATE_METHOD_PREFIX, '');
1416
+ const node = path.node;
1417
+ const classPrivateMethod = t.classPrivateMethod('method', t.privateName(t.identifier(originalPrivateName)), node.params, node.body, node.static);
1418
+ // Properties the t.classPrivateMethod() builder doesn't accept
1419
+ classPrivateMethod.async = node.async;
1420
+ classPrivateMethod.generator = node.generator;
1421
+ classPrivateMethod.computed = node.computed;
1422
+ copyMethodMetadata(node, classPrivateMethod);
1423
+ path.replaceWith(classPrivateMethod);
1424
+ reverseTransformedNames.add(methodName);
1425
+ }
1426
+ }
1427
+ },
1428
+ MemberExpression(path, state) {
1429
+ const property = path.node.property;
1430
+ if (!t.isIdentifier(property) || !property.name.startsWith(PRIVATE_METHOD_PREFIX)) {
1431
+ return;
1432
+ }
1433
+ const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
1434
+ if (!forwardTransformedNames || !forwardTransformedNames.has(property.name)) {
1435
+ return;
1436
+ }
1437
+ const originalName = property.name.replace(PRIVATE_METHOD_PREFIX, '');
1438
+ path.get('property').replaceWith(t.privateName(t.identifier(originalName)));
1439
+ },
1440
+ // After all nodes have been visited, verify that every method the forward transform
1441
+ // renamed was also restored by the reverse transform. A mismatch here means an
1442
+ // intermediate plugin (e.g. @babel/plugin-transform-class-properties) removed or
1443
+ // renamed a prefixed method, leaving a mangled name in the final output.
1444
+ Program: {
1445
+ exit(_path, state) {
1446
+ const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
1447
+ if (!forwardTransformedNames) {
1448
+ return;
1449
+ }
1450
+ const missingFromReverse = [];
1451
+ for (const name of forwardTransformedNames) {
1452
+ if (!reverseTransformedNames.has(name)) {
1453
+ missingFromReverse.push(name);
1454
+ }
1455
+ }
1456
+ if (missingFromReverse.length > 0) {
1457
+ throw new Error(`Private method transform count mismatch: ` +
1458
+ `forward transformed ${forwardTransformedNames.size} method(s), ` +
1459
+ `but reverse transformed ${reverseTransformedNames.size}. ` +
1460
+ `Missing reverse transforms for: ${missingFromReverse.join(', ')}`);
1461
+ }
1462
+ },
1463
+ },
1464
+ },
1465
+ };
1466
+ }
1467
+
1247
1468
  /*
1248
1469
  * Copyright (c) 2023, salesforce.com, inc.
1249
1470
  * All rights reserved.
@@ -1292,6 +1513,8 @@ function LwcClassTransform(api) {
1292
1513
  };
1293
1514
  }
1294
1515
 
1516
+ exports.LwcPrivateMethodTransform = privateMethodTransform;
1517
+ exports.LwcReversePrivateMethodTransform = reversePrivateMethodTransform;
1295
1518
  exports.default = LwcClassTransform;
1296
- /** version: 9.0.3 */
1519
+ /** version: 9.0.4-alpha.1 */
1297
1520
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { BabelAPI, LwcBabelPluginPass } from './types';
2
2
  import type { PluginObj } from '@babel/core';
3
3
  export type { LwcBabelPluginOptions } from './types';
4
+ export { default as LwcPrivateMethodTransform } from './private-method-transform';
5
+ export { default as LwcReversePrivateMethodTransform } from './reverse-private-method-transform';
4
6
  /**
5
7
  * The transform is done in 2 passes:
6
8
  * - First, apply in a single AST traversal the decorators and the component transformation.
package/dist/index.js CHANGED
@@ -37,6 +37,9 @@ const TEMPLATE_KEY = 'tmpl';
37
37
  const COMPONENT_NAME_KEY = 'sel';
38
38
  const API_VERSION_KEY = 'apiVersion';
39
39
  const COMPONENT_CLASS_ID = '__lwc_component_class_internal';
40
+ const PRIVATE_METHOD_PREFIX = '__lwc_component_class_internal_private_';
41
+ const PRIVATE_METHOD_METADATA_KEY = '__lwcTransformedPrivateMethods';
42
+ const ENABLE_PRIVATE_METHODS_KEY = 'enablePrivateMethods';
40
43
  const SYNTHETIC_ELEMENT_INTERNALS_KEY = 'enableSyntheticElementInternals';
41
44
  const COMPONENT_FEATURE_FLAG_KEY = 'componentFeatureFlag';
42
45
 
@@ -119,6 +122,9 @@ function component ({ types: t }) {
119
122
  if (state.opts.enableSyntheticElementInternals === true) {
120
123
  properties.push(t.objectProperty(t.identifier(SYNTHETIC_ELEMENT_INTERNALS_KEY), t.booleanLiteral(true)));
121
124
  }
125
+ if (state.opts.enablePrivateMethods === true) {
126
+ properties.push(t.objectProperty(t.identifier(ENABLE_PRIVATE_METHODS_KEY), t.booleanLiteral(true)));
127
+ }
122
128
  const registerComponentExpression = t.callExpression(registerComponentId, [
123
129
  node,
124
130
  t.objectExpression(properties),
@@ -255,6 +261,30 @@ function incrementMetricCounter(metric, state) {
255
261
  function isErrorRecoveryMode(state) {
256
262
  return state.file.opts?.parserOpts?.errorRecovery ?? false;
257
263
  }
264
+ /**
265
+ * Copies optional metadata properties between ClassMethod and ClassPrivateMethod nodes.
266
+ * These properties are not accepted by the t.classMethod() / t.classPrivateMethod() builders,
267
+ * so they must be transferred manually after node creation. Both the forward and reverse
268
+ * private-method transforms use this to maintain round-trip parity.
269
+ */
270
+ function copyMethodMetadata(source, target) {
271
+ if (source.returnType != null)
272
+ target.returnType = source.returnType;
273
+ if (source.typeParameters != null)
274
+ target.typeParameters = source.typeParameters;
275
+ if (source.loc != null)
276
+ target.loc = source.loc;
277
+ if (source.abstract != null)
278
+ target.abstract = source.abstract;
279
+ if (source.access != null)
280
+ target.access = source.access;
281
+ if (source.accessibility != null)
282
+ target.accessibility = source.accessibility;
283
+ if (source.optional != null)
284
+ target.optional = source.optional;
285
+ if (source.override != null)
286
+ target.override = source.override;
287
+ }
258
288
 
259
289
  /*
260
290
  * Copyright (c) 2023, salesforce.com, inc.
@@ -1240,6 +1270,197 @@ function compilerVersionNumber({ types: t }) {
1240
1270
  };
1241
1271
  }
1242
1272
 
1273
+ /*
1274
+ * Copyright (c) 2025, salesforce.com, inc.
1275
+ * All rights reserved.
1276
+ * SPDX-License-Identifier: MIT
1277
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1278
+ */
1279
+ // We only transform kind: 'method'. Other kinds ('get', 'set', 'constructor') are left alone.
1280
+ const METHOD_KIND = 'method';
1281
+ /**
1282
+ * Standalone Babel plugin that transforms private method identifiers from
1283
+ * `#privateMethod` to `__lwc_component_class_internal_private_privateMethod`.
1284
+ *
1285
+ * This must be registered BEFORE the main LWC class transform plugin so that
1286
+ * private methods are converted to regular methods before decorator and class
1287
+ * property processing.
1288
+ *
1289
+ * Uses Program > path.traverse() rather than a top-level ClassPrivateMethod visitor
1290
+ * because the reverse transform has a ClassMethod visitor in the same Babel pass.
1291
+ * A direct ClassPrivateMethod visitor would replace nodes that the reverse transform
1292
+ * immediately converts back, creating an infinite loop. The manual traverse ensures
1293
+ * all forward replacements complete before the reverse visitor sees any ClassMethod.
1294
+ */
1295
+ function privateMethodTransform({ types: t, }) {
1296
+ return {
1297
+ visitor: {
1298
+ Program(path, state) {
1299
+ const transformedNames = new Set();
1300
+ // Phase 1: Collect base names of all private methods (kind: 'method')
1301
+ // so that Phase 2 can transform invocations even for forward references
1302
+ // (call site visited before the method definition).
1303
+ const privateMethodBaseNames = new Set();
1304
+ path.traverse({
1305
+ ClassPrivateMethod(methodPath) {
1306
+ const key = methodPath.get('key');
1307
+ if (key.isPrivateName() && methodPath.node.kind === METHOD_KIND) {
1308
+ privateMethodBaseNames.add(key.node.id.name);
1309
+ }
1310
+ },
1311
+ });
1312
+ // Phase 2: Transform definitions and invocations
1313
+ path.traverse({
1314
+ ClassPrivateMethod(methodPath, methodState) {
1315
+ const key = methodPath.get('key');
1316
+ if (!key.isPrivateName()) {
1317
+ return;
1318
+ }
1319
+ if (methodPath.node.kind !== METHOD_KIND) {
1320
+ handleError(methodPath, {
1321
+ errorInfo: DecoratorErrors.UNSUPPORTED_PRIVATE_MEMBER,
1322
+ messageArgs: ['accessor methods'],
1323
+ }, methodState);
1324
+ return;
1325
+ }
1326
+ const node = methodPath.node;
1327
+ // Reject private methods with decorators (e.g. @api, @track, @wire)
1328
+ if (node.decorators && node.decorators.length > 0) {
1329
+ handleError(methodPath, {
1330
+ errorInfo: DecoratorErrors.DECORATOR_ON_PRIVATE_METHOD,
1331
+ }, methodState);
1332
+ return;
1333
+ }
1334
+ const privateName = key.node.id.name;
1335
+ const transformedName = `${PRIVATE_METHOD_PREFIX}${privateName}`;
1336
+ const keyReplacement = t.identifier(transformedName);
1337
+ // Create a new ClassMethod node to replace the ClassPrivateMethod
1338
+ // https://babeljs.io/docs/babel-types#classmethod
1339
+ const classMethod = t.classMethod(METHOD_KIND, keyReplacement, node.params, node.body, node.computed, node.static, node.generator, node.async);
1340
+ copyMethodMetadata(node, classMethod);
1341
+ // Replace the entire ClassPrivateMethod node with the new ClassMethod node
1342
+ // (we can't just replace the key of type PrivateName with type Identifier)
1343
+ methodPath.replaceWith(classMethod);
1344
+ transformedNames.add(transformedName);
1345
+ },
1346
+ MemberExpression(memberPath) {
1347
+ const property = memberPath.node.property;
1348
+ if (t.isPrivateName(property)) {
1349
+ const baseName = property.id.name;
1350
+ if (privateMethodBaseNames.has(baseName)) {
1351
+ const prefixedName = `${PRIVATE_METHOD_PREFIX}${baseName}`;
1352
+ memberPath
1353
+ .get('property')
1354
+ .replaceWith(t.identifier(prefixedName));
1355
+ }
1356
+ }
1357
+ },
1358
+ ClassPrivateProperty(propPath, propState) {
1359
+ handleError(propPath, {
1360
+ errorInfo: DecoratorErrors.UNSUPPORTED_PRIVATE_MEMBER,
1361
+ messageArgs: ['fields'],
1362
+ }, propState);
1363
+ },
1364
+ }, state);
1365
+ state.file.metadata[PRIVATE_METHOD_METADATA_KEY] = transformedNames;
1366
+ },
1367
+ },
1368
+ };
1369
+ }
1370
+
1371
+ /*
1372
+ * Copyright (c) 2025, salesforce.com, inc.
1373
+ * All rights reserved.
1374
+ * SPDX-License-Identifier: MIT
1375
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1376
+ */
1377
+ /**
1378
+ * Standalone Babel plugin that reverses the private method transformation by converting
1379
+ * methods with prefix {@link PRIVATE_METHOD_PREFIX} back to ClassPrivateMethod nodes,
1380
+ * and restoring prefixed MemberExpression properties back to PrivateName nodes.
1381
+ *
1382
+ * This must be registered AFTER @babel/plugin-transform-class-properties so that
1383
+ * class properties are fully transformed before private methods are restored.
1384
+ *
1385
+ * Round-trip parity: to match {@link ./private-method-transform.ts}, this transform must copy the same
1386
+ * properties from ClassMethod onto ClassPrivateMethod when present: returnType, typeParameters, loc,
1387
+ * abstract, access, accessibility, optional, override (plus async, generator, computed from the builder).
1388
+ *
1389
+ * @see {@link ./private-method-transform.ts} for original transformation
1390
+ */
1391
+ function reversePrivateMethodTransform({ types: t, }) {
1392
+ // Scoped to this plugin instance's closure. Safe as long as each Babel run creates a
1393
+ // fresh plugin via LwcReversePrivateMethodTransform(); would accumulate across files if
1394
+ // the same instance were ever reused.
1395
+ const reverseTransformedNames = new Set();
1396
+ return {
1397
+ visitor: {
1398
+ ClassMethod(path, state) {
1399
+ const key = path.get('key');
1400
+ // kind: 'method' | 'get' | 'set' - only 'method' is in scope.
1401
+ if (key.isIdentifier() && path.node.kind === 'method') {
1402
+ const methodName = key.node.name;
1403
+ if (methodName.startsWith(PRIVATE_METHOD_PREFIX)) {
1404
+ const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
1405
+ // If the method was not transformed by the forward pass, it is a
1406
+ // user-defined method that collides with the reserved prefix.
1407
+ if (!forwardTransformedNames || !forwardTransformedNames.has(methodName)) {
1408
+ const message = DecoratorErrors.PRIVATE_METHOD_NAME_COLLISION.message.replace('{0}', methodName);
1409
+ throw path.buildCodeFrameError(message);
1410
+ }
1411
+ const originalPrivateName = methodName.replace(PRIVATE_METHOD_PREFIX, '');
1412
+ const node = path.node;
1413
+ const classPrivateMethod = t.classPrivateMethod('method', t.privateName(t.identifier(originalPrivateName)), node.params, node.body, node.static);
1414
+ // Properties the t.classPrivateMethod() builder doesn't accept
1415
+ classPrivateMethod.async = node.async;
1416
+ classPrivateMethod.generator = node.generator;
1417
+ classPrivateMethod.computed = node.computed;
1418
+ copyMethodMetadata(node, classPrivateMethod);
1419
+ path.replaceWith(classPrivateMethod);
1420
+ reverseTransformedNames.add(methodName);
1421
+ }
1422
+ }
1423
+ },
1424
+ MemberExpression(path, state) {
1425
+ const property = path.node.property;
1426
+ if (!t.isIdentifier(property) || !property.name.startsWith(PRIVATE_METHOD_PREFIX)) {
1427
+ return;
1428
+ }
1429
+ const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
1430
+ if (!forwardTransformedNames || !forwardTransformedNames.has(property.name)) {
1431
+ return;
1432
+ }
1433
+ const originalName = property.name.replace(PRIVATE_METHOD_PREFIX, '');
1434
+ path.get('property').replaceWith(t.privateName(t.identifier(originalName)));
1435
+ },
1436
+ // After all nodes have been visited, verify that every method the forward transform
1437
+ // renamed was also restored by the reverse transform. A mismatch here means an
1438
+ // intermediate plugin (e.g. @babel/plugin-transform-class-properties) removed or
1439
+ // renamed a prefixed method, leaving a mangled name in the final output.
1440
+ Program: {
1441
+ exit(_path, state) {
1442
+ const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
1443
+ if (!forwardTransformedNames) {
1444
+ return;
1445
+ }
1446
+ const missingFromReverse = [];
1447
+ for (const name of forwardTransformedNames) {
1448
+ if (!reverseTransformedNames.has(name)) {
1449
+ missingFromReverse.push(name);
1450
+ }
1451
+ }
1452
+ if (missingFromReverse.length > 0) {
1453
+ throw new Error(`Private method transform count mismatch: ` +
1454
+ `forward transformed ${forwardTransformedNames.size} method(s), ` +
1455
+ `but reverse transformed ${reverseTransformedNames.size}. ` +
1456
+ `Missing reverse transforms for: ${missingFromReverse.join(', ')}`);
1457
+ }
1458
+ },
1459
+ },
1460
+ },
1461
+ };
1462
+ }
1463
+
1243
1464
  /*
1244
1465
  * Copyright (c) 2023, salesforce.com, inc.
1245
1466
  * All rights reserved.
@@ -1288,6 +1509,6 @@ function LwcClassTransform(api) {
1288
1509
  };
1289
1510
  }
1290
1511
 
1291
- export { LwcClassTransform as default };
1292
- /** version: 9.0.3 */
1512
+ export { privateMethodTransform as LwcPrivateMethodTransform, reversePrivateMethodTransform as LwcReversePrivateMethodTransform, LwcClassTransform as default };
1513
+ /** version: 9.0.4-alpha.1 */
1293
1514
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,18 @@
1
+ import type { BabelAPI, LwcBabelPluginPass } from './types';
2
+ import type { PluginObj } from '@babel/core';
3
+ /**
4
+ * Standalone Babel plugin that transforms private method identifiers from
5
+ * `#privateMethod` to `__lwc_component_class_internal_private_privateMethod`.
6
+ *
7
+ * This must be registered BEFORE the main LWC class transform plugin so that
8
+ * private methods are converted to regular methods before decorator and class
9
+ * property processing.
10
+ *
11
+ * Uses Program > path.traverse() rather than a top-level ClassPrivateMethod visitor
12
+ * because the reverse transform has a ClassMethod visitor in the same Babel pass.
13
+ * A direct ClassPrivateMethod visitor would replace nodes that the reverse transform
14
+ * immediately converts back, creating an infinite loop. The manual traverse ensures
15
+ * all forward replacements complete before the reverse visitor sees any ClassMethod.
16
+ */
17
+ export default function privateMethodTransform({ types: t, }: BabelAPI): PluginObj<LwcBabelPluginPass>;
18
+ //# sourceMappingURL=private-method-transform.d.ts.map
@@ -0,0 +1,18 @@
1
+ import type { BabelAPI, LwcBabelPluginPass } from './types';
2
+ import type { PluginObj } from '@babel/core';
3
+ /**
4
+ * Standalone Babel plugin that reverses the private method transformation by converting
5
+ * methods with prefix {@link PRIVATE_METHOD_PREFIX} back to ClassPrivateMethod nodes,
6
+ * and restoring prefixed MemberExpression properties back to PrivateName nodes.
7
+ *
8
+ * This must be registered AFTER @babel/plugin-transform-class-properties so that
9
+ * class properties are fully transformed before private methods are restored.
10
+ *
11
+ * Round-trip parity: to match {@link ./private-method-transform.ts}, this transform must copy the same
12
+ * properties from ClassMethod onto ClassPrivateMethod when present: returnType, typeParameters, loc,
13
+ * abstract, access, accessibility, optional, override (plus async, generator, computed from the builder).
14
+ *
15
+ * @see {@link ./private-method-transform.ts} for original transformation
16
+ */
17
+ export default function reversePrivateMethodTransform({ types: t, }: BabelAPI): PluginObj<LwcBabelPluginPass>;
18
+ //# sourceMappingURL=reverse-private-method-transform.d.ts.map
package/dist/types.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface LwcBabelPluginOptions {
14
14
  instrumentation?: InstrumentationObject;
15
15
  apiVersion?: number;
16
16
  enableSyntheticElementInternals?: boolean;
17
+ enablePrivateMethods?: boolean;
17
18
  componentFeatureFlagModulePath?: string;
18
19
  }
19
20
  export interface LwcBabelPluginPass extends PluginPass {
package/dist/utils.d.ts CHANGED
@@ -21,5 +21,12 @@ declare function getEngineImportSpecifiers(path: NodePath): ImportSpecifier[];
21
21
  declare function handleError(source: NodePath<types.Node>, decoratorErrorOpts: DecoratorErrorOptions, state: LwcBabelPluginPass): void;
22
22
  declare function incrementMetricCounter(metric: CompilerMetrics, state: LwcBabelPluginPass): void;
23
23
  declare function isErrorRecoveryMode(state: LwcBabelPluginPass): boolean;
24
- export { isClassMethod, isGetterClassMethod, isSetterClassMethod, getEngineImportSpecifiers, handleError, incrementMetricCounter, isErrorRecoveryMode, };
24
+ /**
25
+ * Copies optional metadata properties between ClassMethod and ClassPrivateMethod nodes.
26
+ * These properties are not accepted by the t.classMethod() / t.classPrivateMethod() builders,
27
+ * so they must be transferred manually after node creation. Both the forward and reverse
28
+ * private-method transforms use this to maintain round-trip parity.
29
+ */
30
+ declare function copyMethodMetadata(source: types.ClassMethod | types.ClassPrivateMethod, target: types.ClassMethod | types.ClassPrivateMethod): void;
31
+ export { isClassMethod, isGetterClassMethod, isSetterClassMethod, getEngineImportSpecifiers, handleError, incrementMetricCounter, isErrorRecoveryMode, copyMethodMetadata, };
25
32
  //# sourceMappingURL=utils.d.ts.map
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "You can safely modify dependencies, devDependencies, keywords, etc., but other props will be overwritten."
5
5
  ],
6
6
  "name": "@lwc/babel-plugin-component",
7
- "version": "9.0.3",
7
+ "version": "9.0.4-alpha.1",
8
8
  "description": "Babel plugin to transform a LWC module",
9
9
  "keywords": [
10
10
  "lwc"
@@ -52,8 +52,8 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@babel/helper-module-imports": "7.28.6",
55
- "@lwc/errors": "9.0.3",
56
- "@lwc/shared": "9.0.3",
55
+ "@lwc/errors": "9.0.4-alpha.1",
56
+ "@lwc/shared": "9.0.4-alpha.1",
57
57
  "line-column": "~1.0.2"
58
58
  },
59
59
  "devDependencies": {