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

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,9 @@ 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";
26
28
  declare const SYNTHETIC_ELEMENT_INTERNALS_KEY = "enableSyntheticElementInternals";
27
29
  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, };
30
+ 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, SYNTHETIC_ELEMENT_INTERNALS_KEY, COMPONENT_FEATURE_FLAG_KEY, };
29
31
  //# sourceMappingURL=constants.d.ts.map
package/dist/index.cjs CHANGED
@@ -41,6 +41,8 @@ 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';
44
46
  const SYNTHETIC_ELEMENT_INTERNALS_KEY = 'enableSyntheticElementInternals';
45
47
  const COMPONENT_FEATURE_FLAG_KEY = 'componentFeatureFlag';
46
48
 
@@ -259,6 +261,30 @@ function incrementMetricCounter(metric, state) {
259
261
  function isErrorRecoveryMode(state) {
260
262
  return state.file.opts?.parserOpts?.errorRecovery ?? false;
261
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
+ }
262
288
 
263
289
  /*
264
290
  * Copyright (c) 2023, salesforce.com, inc.
@@ -1244,6 +1270,197 @@ function compilerVersionNumber({ types: t }) {
1244
1270
  };
1245
1271
  }
1246
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: errors.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: errors.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: errors.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 = errors.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
+
1247
1464
  /*
1248
1465
  * Copyright (c) 2023, salesforce.com, inc.
1249
1466
  * All rights reserved.
@@ -1292,6 +1509,8 @@ function LwcClassTransform(api) {
1292
1509
  };
1293
1510
  }
1294
1511
 
1512
+ exports.LwcPrivateMethodTransform = privateMethodTransform;
1513
+ exports.LwcReversePrivateMethodTransform = reversePrivateMethodTransform;
1295
1514
  exports.default = LwcClassTransform;
1296
- /** version: 9.0.3 */
1515
+ /** version: 9.0.4-alpha.0 */
1297
1516
  //# 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,8 @@ 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';
40
42
  const SYNTHETIC_ELEMENT_INTERNALS_KEY = 'enableSyntheticElementInternals';
41
43
  const COMPONENT_FEATURE_FLAG_KEY = 'componentFeatureFlag';
42
44
 
@@ -255,6 +257,30 @@ function incrementMetricCounter(metric, state) {
255
257
  function isErrorRecoveryMode(state) {
256
258
  return state.file.opts?.parserOpts?.errorRecovery ?? false;
257
259
  }
260
+ /**
261
+ * Copies optional metadata properties between ClassMethod and ClassPrivateMethod nodes.
262
+ * These properties are not accepted by the t.classMethod() / t.classPrivateMethod() builders,
263
+ * so they must be transferred manually after node creation. Both the forward and reverse
264
+ * private-method transforms use this to maintain round-trip parity.
265
+ */
266
+ function copyMethodMetadata(source, target) {
267
+ if (source.returnType != null)
268
+ target.returnType = source.returnType;
269
+ if (source.typeParameters != null)
270
+ target.typeParameters = source.typeParameters;
271
+ if (source.loc != null)
272
+ target.loc = source.loc;
273
+ if (source.abstract != null)
274
+ target.abstract = source.abstract;
275
+ if (source.access != null)
276
+ target.access = source.access;
277
+ if (source.accessibility != null)
278
+ target.accessibility = source.accessibility;
279
+ if (source.optional != null)
280
+ target.optional = source.optional;
281
+ if (source.override != null)
282
+ target.override = source.override;
283
+ }
258
284
 
259
285
  /*
260
286
  * Copyright (c) 2023, salesforce.com, inc.
@@ -1240,6 +1266,197 @@ function compilerVersionNumber({ types: t }) {
1240
1266
  };
1241
1267
  }
1242
1268
 
1269
+ /*
1270
+ * Copyright (c) 2025, salesforce.com, inc.
1271
+ * All rights reserved.
1272
+ * SPDX-License-Identifier: MIT
1273
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1274
+ */
1275
+ // We only transform kind: 'method'. Other kinds ('get', 'set', 'constructor') are left alone.
1276
+ const METHOD_KIND = 'method';
1277
+ /**
1278
+ * Standalone Babel plugin that transforms private method identifiers from
1279
+ * `#privateMethod` to `__lwc_component_class_internal_private_privateMethod`.
1280
+ *
1281
+ * This must be registered BEFORE the main LWC class transform plugin so that
1282
+ * private methods are converted to regular methods before decorator and class
1283
+ * property processing.
1284
+ *
1285
+ * Uses Program > path.traverse() rather than a top-level ClassPrivateMethod visitor
1286
+ * because the reverse transform has a ClassMethod visitor in the same Babel pass.
1287
+ * A direct ClassPrivateMethod visitor would replace nodes that the reverse transform
1288
+ * immediately converts back, creating an infinite loop. The manual traverse ensures
1289
+ * all forward replacements complete before the reverse visitor sees any ClassMethod.
1290
+ */
1291
+ function privateMethodTransform({ types: t, }) {
1292
+ return {
1293
+ visitor: {
1294
+ Program(path, state) {
1295
+ const transformedNames = new Set();
1296
+ // Phase 1: Collect base names of all private methods (kind: 'method')
1297
+ // so that Phase 2 can transform invocations even for forward references
1298
+ // (call site visited before the method definition).
1299
+ const privateMethodBaseNames = new Set();
1300
+ path.traverse({
1301
+ ClassPrivateMethod(methodPath) {
1302
+ const key = methodPath.get('key');
1303
+ if (key.isPrivateName() && methodPath.node.kind === METHOD_KIND) {
1304
+ privateMethodBaseNames.add(key.node.id.name);
1305
+ }
1306
+ },
1307
+ });
1308
+ // Phase 2: Transform definitions and invocations
1309
+ path.traverse({
1310
+ ClassPrivateMethod(methodPath, methodState) {
1311
+ const key = methodPath.get('key');
1312
+ if (!key.isPrivateName()) {
1313
+ return;
1314
+ }
1315
+ if (methodPath.node.kind !== METHOD_KIND) {
1316
+ handleError(methodPath, {
1317
+ errorInfo: DecoratorErrors.UNSUPPORTED_PRIVATE_MEMBER,
1318
+ messageArgs: ['accessor methods'],
1319
+ }, methodState);
1320
+ return;
1321
+ }
1322
+ const node = methodPath.node;
1323
+ // Reject private methods with decorators (e.g. @api, @track, @wire)
1324
+ if (node.decorators && node.decorators.length > 0) {
1325
+ handleError(methodPath, {
1326
+ errorInfo: DecoratorErrors.DECORATOR_ON_PRIVATE_METHOD,
1327
+ }, methodState);
1328
+ return;
1329
+ }
1330
+ const privateName = key.node.id.name;
1331
+ const transformedName = `${PRIVATE_METHOD_PREFIX}${privateName}`;
1332
+ const keyReplacement = t.identifier(transformedName);
1333
+ // Create a new ClassMethod node to replace the ClassPrivateMethod
1334
+ // https://babeljs.io/docs/babel-types#classmethod
1335
+ const classMethod = t.classMethod(METHOD_KIND, keyReplacement, node.params, node.body, node.computed, node.static, node.generator, node.async);
1336
+ copyMethodMetadata(node, classMethod);
1337
+ // Replace the entire ClassPrivateMethod node with the new ClassMethod node
1338
+ // (we can't just replace the key of type PrivateName with type Identifier)
1339
+ methodPath.replaceWith(classMethod);
1340
+ transformedNames.add(transformedName);
1341
+ },
1342
+ MemberExpression(memberPath) {
1343
+ const property = memberPath.node.property;
1344
+ if (t.isPrivateName(property)) {
1345
+ const baseName = property.id.name;
1346
+ if (privateMethodBaseNames.has(baseName)) {
1347
+ const prefixedName = `${PRIVATE_METHOD_PREFIX}${baseName}`;
1348
+ memberPath
1349
+ .get('property')
1350
+ .replaceWith(t.identifier(prefixedName));
1351
+ }
1352
+ }
1353
+ },
1354
+ ClassPrivateProperty(propPath, propState) {
1355
+ handleError(propPath, {
1356
+ errorInfo: DecoratorErrors.UNSUPPORTED_PRIVATE_MEMBER,
1357
+ messageArgs: ['fields'],
1358
+ }, propState);
1359
+ },
1360
+ }, state);
1361
+ state.file.metadata[PRIVATE_METHOD_METADATA_KEY] = transformedNames;
1362
+ },
1363
+ },
1364
+ };
1365
+ }
1366
+
1367
+ /*
1368
+ * Copyright (c) 2025, salesforce.com, inc.
1369
+ * All rights reserved.
1370
+ * SPDX-License-Identifier: MIT
1371
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
1372
+ */
1373
+ /**
1374
+ * Standalone Babel plugin that reverses the private method transformation by converting
1375
+ * methods with prefix {@link PRIVATE_METHOD_PREFIX} back to ClassPrivateMethod nodes,
1376
+ * and restoring prefixed MemberExpression properties back to PrivateName nodes.
1377
+ *
1378
+ * This must be registered AFTER @babel/plugin-transform-class-properties so that
1379
+ * class properties are fully transformed before private methods are restored.
1380
+ *
1381
+ * Round-trip parity: to match {@link ./private-method-transform.ts}, this transform must copy the same
1382
+ * properties from ClassMethod onto ClassPrivateMethod when present: returnType, typeParameters, loc,
1383
+ * abstract, access, accessibility, optional, override (plus async, generator, computed from the builder).
1384
+ *
1385
+ * @see {@link ./private-method-transform.ts} for original transformation
1386
+ */
1387
+ function reversePrivateMethodTransform({ types: t, }) {
1388
+ // Scoped to this plugin instance's closure. Safe as long as each Babel run creates a
1389
+ // fresh plugin via LwcReversePrivateMethodTransform(); would accumulate across files if
1390
+ // the same instance were ever reused.
1391
+ const reverseTransformedNames = new Set();
1392
+ return {
1393
+ visitor: {
1394
+ ClassMethod(path, state) {
1395
+ const key = path.get('key');
1396
+ // kind: 'method' | 'get' | 'set' - only 'method' is in scope.
1397
+ if (key.isIdentifier() && path.node.kind === 'method') {
1398
+ const methodName = key.node.name;
1399
+ if (methodName.startsWith(PRIVATE_METHOD_PREFIX)) {
1400
+ const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
1401
+ // If the method was not transformed by the forward pass, it is a
1402
+ // user-defined method that collides with the reserved prefix.
1403
+ if (!forwardTransformedNames || !forwardTransformedNames.has(methodName)) {
1404
+ const message = DecoratorErrors.PRIVATE_METHOD_NAME_COLLISION.message.replace('{0}', methodName);
1405
+ throw path.buildCodeFrameError(message);
1406
+ }
1407
+ const originalPrivateName = methodName.replace(PRIVATE_METHOD_PREFIX, '');
1408
+ const node = path.node;
1409
+ const classPrivateMethod = t.classPrivateMethod('method', t.privateName(t.identifier(originalPrivateName)), node.params, node.body, node.static);
1410
+ // Properties the t.classPrivateMethod() builder doesn't accept
1411
+ classPrivateMethod.async = node.async;
1412
+ classPrivateMethod.generator = node.generator;
1413
+ classPrivateMethod.computed = node.computed;
1414
+ copyMethodMetadata(node, classPrivateMethod);
1415
+ path.replaceWith(classPrivateMethod);
1416
+ reverseTransformedNames.add(methodName);
1417
+ }
1418
+ }
1419
+ },
1420
+ MemberExpression(path, state) {
1421
+ const property = path.node.property;
1422
+ if (!t.isIdentifier(property) || !property.name.startsWith(PRIVATE_METHOD_PREFIX)) {
1423
+ return;
1424
+ }
1425
+ const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
1426
+ if (!forwardTransformedNames || !forwardTransformedNames.has(property.name)) {
1427
+ return;
1428
+ }
1429
+ const originalName = property.name.replace(PRIVATE_METHOD_PREFIX, '');
1430
+ path.get('property').replaceWith(t.privateName(t.identifier(originalName)));
1431
+ },
1432
+ // After all nodes have been visited, verify that every method the forward transform
1433
+ // renamed was also restored by the reverse transform. A mismatch here means an
1434
+ // intermediate plugin (e.g. @babel/plugin-transform-class-properties) removed or
1435
+ // renamed a prefixed method, leaving a mangled name in the final output.
1436
+ Program: {
1437
+ exit(_path, state) {
1438
+ const forwardTransformedNames = state.file.metadata[PRIVATE_METHOD_METADATA_KEY];
1439
+ if (!forwardTransformedNames) {
1440
+ return;
1441
+ }
1442
+ const missingFromReverse = [];
1443
+ for (const name of forwardTransformedNames) {
1444
+ if (!reverseTransformedNames.has(name)) {
1445
+ missingFromReverse.push(name);
1446
+ }
1447
+ }
1448
+ if (missingFromReverse.length > 0) {
1449
+ throw new Error(`Private method transform count mismatch: ` +
1450
+ `forward transformed ${forwardTransformedNames.size} method(s), ` +
1451
+ `but reverse transformed ${reverseTransformedNames.size}. ` +
1452
+ `Missing reverse transforms for: ${missingFromReverse.join(', ')}`);
1453
+ }
1454
+ },
1455
+ },
1456
+ },
1457
+ };
1458
+ }
1459
+
1243
1460
  /*
1244
1461
  * Copyright (c) 2023, salesforce.com, inc.
1245
1462
  * All rights reserved.
@@ -1288,6 +1505,6 @@ function LwcClassTransform(api) {
1288
1505
  };
1289
1506
  }
1290
1507
 
1291
- export { LwcClassTransform as default };
1292
- /** version: 9.0.3 */
1508
+ export { privateMethodTransform as LwcPrivateMethodTransform, reversePrivateMethodTransform as LwcReversePrivateMethodTransform, LwcClassTransform as default };
1509
+ /** version: 9.0.4-alpha.0 */
1293
1510
  //# 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/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.0",
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.0",
56
+ "@lwc/shared": "9.0.4-alpha.0",
57
57
  "line-column": "~1.0.2"
58
58
  },
59
59
  "devDependencies": {