@immense/vue-pom-generator 1.0.63 → 1.0.65

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.
package/RELEASE_NOTES.md CHANGED
@@ -1,30 +1,31 @@
1
- ```markdown
2
- ## Highlights
1
+ ## v1.0.65
3
2
 
4
- - Fixed nested component POMs not being properly attached to their parent views
5
- - Added comprehensive test coverage for nested component POM attachment
3
+ ### Highlights
6
4
 
7
- ## Changes
5
+ - Fixed parsing of guarded handler expressions (e.g., `@click="condition && handler"`)
6
+ - Improved semantic name resolution for wrapped and conditional event handlers
7
+ - Enhanced Vue compiler AST traversal to handle logical expressions (`&&` operators)
8
8
 
9
- ### Bug Fixes
10
- - Resolved issue where nested component POMs were not correctly associated with view classes
11
- during generation
12
- - Improved POM attachment logic in class generation module
9
+ ### Changes
13
10
 
14
- ### Testing
15
- - Added 54 lines of test coverage for class generation including nested component scenarios
16
-
17
- ## Breaking Changes
11
+ **Parser Improvements**
12
+ - Added support for `LogicalExpression` nodes in handler attribute parsing
13
+ - Introduced `unwrapSemanticHelperCall` to handle Vue compiler helpers (`_unref`,
14
+ `_withModifiers`, etc.)
15
+ - Refactored `resolveSemanticName` to recursively unwrap nested expressions and guarded calls
16
+ - Handler names now correctly extracted from conditional expressions like `isValid &&
17
+ submitForm`
18
18
 
19
- None
19
+ **Testing**
20
+ - Added test coverage for guarded handler scenarios
21
+ - Updated transform and utils test suites to validate new parsing logic
20
22
 
21
- ## Pull Requests Included
23
+ ### Pull Requests Included
22
24
 
23
- None (direct commit to main)
25
+ No pull request was created for this fix; it was committed directly to `main`.
24
26
 
25
- ## Testing
27
+ ### Testing
26
28
 
27
- Tests added to validate nested component POM attachment behavior. Run test suite with `npm
28
- test`.
29
- ```
29
+ Validated with expanded unit test coverage in `tests/transform.test.ts` and
30
+ `tests/utils-coverage.test.ts`.
30
31
 
package/dist/index.cjs CHANGED
@@ -258,6 +258,7 @@ function resolveGenerationSupportOptions(options) {
258
258
  customPomImportAliases: options.customPomImportAliases,
259
259
  customPomImportNameCollisionBehavior: options.customPomImportNameCollisionBehavior ?? "error",
260
260
  nameCollisionBehavior: options.nameCollisionBehavior ?? "error",
261
+ missingSemanticNameBehavior: options.missingSemanticNameBehavior ?? "error",
261
262
  existingIdBehavior: options.existingIdBehavior ?? "error",
262
263
  testIdAttribute: (options.testIdAttribute ?? "data-testid").trim() || "data-testid",
263
264
  accessibilityAudit: options.accessibilityAudit ?? false,
@@ -1894,6 +1895,12 @@ function nodeHandlerAttributeInfo(node) {
1894
1895
  const n = node2;
1895
1896
  return typeof n.computed === "boolean" && typeof n.key === "object" && n.key !== null && typeof n.value === "object" && n.value !== null;
1896
1897
  };
1898
+ const isLogicalExpressionNode = (node2) => {
1899
+ if (!isNodeType2(node2, "LogicalExpression"))
1900
+ return false;
1901
+ const n = node2;
1902
+ return typeof n.operator === "string" && typeof n.left === "object" && n.left !== null && typeof n.right === "object" && n.right !== null;
1903
+ };
1897
1904
  const getLastIdentifierFromMemberChain = (node2) => {
1898
1905
  if (!node2)
1899
1906
  return null;
@@ -2064,48 +2071,66 @@ function nodeHandlerAttributeInfo(node) {
2064
2071
  const semanticNameHint2 = suffix ? `${toPascalCase(name)}${suffix}` : toPascalCase(name);
2065
2072
  return semanticNameHint2;
2066
2073
  };
2074
+ const unwrapSemanticHelperCall = (candidateExpr) => {
2075
+ if (!isCallExpressionNode(candidateExpr)) {
2076
+ return null;
2077
+ }
2078
+ const calleeName = getLastIdentifierFromMemberChain(candidateExpr.callee);
2079
+ if (calleeName !== "_unref" && calleeName !== "unref" && calleeName !== "_withModifiers" && calleeName !== "withModifiers") {
2080
+ return null;
2081
+ }
2082
+ const firstArg = candidateExpr.arguments[0];
2083
+ return typeof firstArg === "object" && firstArg !== null ? firstArg : null;
2084
+ };
2067
2085
  const resolveSemanticName = (candidateExpr) => {
2068
2086
  if (!candidateExpr) {
2069
2087
  return null;
2070
2088
  }
2089
+ const unwrappedHelperCandidate = unwrapSemanticHelperCall(candidateExpr);
2090
+ if (unwrappedHelperCandidate) {
2091
+ return resolveSemanticName(unwrappedHelperCandidate);
2092
+ }
2071
2093
  const direct = getLastIdentifierFromMemberChain(candidateExpr);
2072
2094
  if (direct) {
2073
2095
  return toPascalCase(direct);
2074
2096
  }
2097
+ const directCall = tryFromCallExpression(candidateExpr);
2098
+ if (directCall) {
2099
+ return directCall;
2100
+ }
2101
+ if (isAssignmentExpressionNode(candidateExpr)) {
2102
+ const lhs = getAssignmentTargetName(candidateExpr.left);
2103
+ if (lhs) {
2104
+ const rhs = stableWordFromValue(candidateExpr.right);
2105
+ return `Set${toPascalCase(lhs)}${rhs ?? ""}`;
2106
+ }
2107
+ }
2108
+ if (isLogicalExpressionNode(candidateExpr) && candidateExpr.operator === "&&") {
2109
+ return resolveSemanticName(candidateExpr.right);
2110
+ }
2075
2111
  if (isArrowFunctionExpressionNode(candidateExpr)) {
2076
2112
  const body = candidateExpr.body;
2077
- const directCall = tryFromCallExpression(body);
2078
- if (directCall) {
2079
- return directCall;
2080
- }
2081
- if (isAssignmentExpressionNode(body)) {
2082
- const lhs = getAssignmentTargetName(body.left);
2083
- if (lhs) {
2084
- const rhs = stableWordFromValue(body.right);
2085
- return `Set${toPascalCase(lhs)}${rhs ?? ""}`;
2086
- }
2087
- }
2088
2113
  if (isBlockStatementNode(body)) {
2089
2114
  const stmts = body.body ?? [];
2090
2115
  if (stmts.length > 0) {
2091
2116
  const firstStmt = stmts[0];
2092
2117
  if (isReturnStatementNode(firstStmt)) {
2093
- const fromReturn = tryFromCallExpression(firstStmt.argument ?? null);
2118
+ const fromReturn = resolveSemanticName(firstStmt.argument ?? null);
2094
2119
  if (fromReturn) {
2095
2120
  return fromReturn;
2096
2121
  }
2097
2122
  }
2098
2123
  if (isExpressionStatementNode(firstStmt)) {
2099
- const fromExpr = tryFromCallExpression(firstStmt.expression ?? null);
2124
+ const fromExpr = resolveSemanticName(firstStmt.expression ?? null);
2100
2125
  if (fromExpr) {
2101
2126
  return fromExpr;
2102
2127
  }
2103
2128
  }
2104
2129
  }
2105
2130
  }
2106
- const bodyName = getLastIdentifierFromMemberChain(body);
2131
+ const bodyName = resolveSemanticName(body);
2107
2132
  if (bodyName) {
2108
- return toPascalCase(bodyName);
2133
+ return bodyName;
2109
2134
  }
2110
2135
  }
2111
2136
  return null;
@@ -7076,6 +7101,7 @@ function createTestIdTransform(componentName, componentHierarchyMap, nativeWrapp
7076
7101
  const existingIdBehavior = options.existingIdBehavior ?? "error";
7077
7102
  const testIdAttribute = (options.testIdAttribute || "data-testid").trim() || "data-testid";
7078
7103
  const nameCollisionBehavior = options.nameCollisionBehavior ?? "error";
7104
+ const missingSemanticNameBehavior = options.missingSemanticNameBehavior ?? "error";
7079
7105
  const warn = options.warn;
7080
7106
  const vueFilesPathMap = options.vueFilesPathMap;
7081
7107
  const wrapperSearchRoots = options.wrapperSearchRoots ?? [];
@@ -7519,13 +7545,16 @@ Fix: remove the explicit ${attrLabel}, or change existingIdBehavior to "overwrit
7519
7545
  }
7520
7546
  const isSubmit = element.props.find((p) => p.type === compilerCore.NodeTypes.ATTRIBUTE && p.name === "type")?.value?.content === "submit";
7521
7547
  if (isSubmit) {
7522
- const identifier = getStaticIdOrNameHint(element) || innerText;
7548
+ let identifier = getStaticIdOrNameHint(element) || innerText;
7523
7549
  if (!identifier) {
7524
- const loc = element.loc?.start;
7525
- const locationHint = loc ? `${loc.line}:${loc.column}` : "unknown";
7526
- throw new Error(
7527
- `[vue-pom-generator] submit button appears identifiable but no usable identity could be derived in ${componentName} (${context.filename ?? "unknown"}:${locationHint}) — id/name were missing/empty and innerText was also missing/invalid`
7528
- );
7550
+ if (missingSemanticNameBehavior === "error") {
7551
+ const loc = element.loc?.start;
7552
+ const locationHint = loc ? `${loc.line}:${loc.column}` : "unknown";
7553
+ throw new Error(
7554
+ `[vue-pom-generator] submit button appears identifiable but no usable identity could be derived in ${componentName} (${context.filename ?? "unknown"}:${locationHint}) — id/name were missing/empty and innerText was also missing/invalid. Fix: give the button a static id/name, a static inner text, or set missingSemanticNameBehavior = "ignore" to fall back to the generic "submit" identifier.`
7555
+ );
7556
+ }
7557
+ identifier = "submit";
7529
7558
  }
7530
7559
  const testId = getSubmitDataTestId(identifier);
7531
7560
  applyResolvedDataTestIdForElement({
@@ -7592,6 +7621,7 @@ function createBuildProcessorPlugin(options) {
7592
7621
  customPomImportNameCollisionBehavior,
7593
7622
  testIdAttribute,
7594
7623
  nameCollisionBehavior,
7624
+ missingSemanticNameBehavior,
7595
7625
  existingIdBehavior,
7596
7626
  routerAwarePoms,
7597
7627
  routerType,
@@ -7687,6 +7717,9 @@ function createBuildProcessorPlugin(options) {
7687
7717
  prefixIdentifiers: true,
7688
7718
  inline: isScriptSetup,
7689
7719
  bindingMetadata,
7720
+ // See dev-plugin.ts — same rationale: enable TS in template
7721
+ // expressions so `(row: RowType) => ...` handlers parse.
7722
+ expressionPlugins: ["typescript"],
7690
7723
  nodeTransforms: [
7691
7724
  createTestIdTransform(
7692
7725
  componentName,
@@ -7698,6 +7731,7 @@ function createBuildProcessorPlugin(options) {
7698
7731
  existingIdBehavior: existingIdBehavior ?? "error",
7699
7732
  testIdAttribute,
7700
7733
  nameCollisionBehavior,
7734
+ missingSemanticNameBehavior,
7701
7735
  warn: (message) => loggerRef.current.warn(message),
7702
7736
  vueFilesPathMap,
7703
7737
  wrapperSearchRoots: getWrapperSearchRoots()
@@ -7845,6 +7879,7 @@ function createDevProcessorPlugin(options) {
7845
7879
  customPomImportAliases,
7846
7880
  customPomImportNameCollisionBehavior,
7847
7881
  nameCollisionBehavior,
7882
+ missingSemanticNameBehavior,
7848
7883
  existingIdBehavior,
7849
7884
  testIdAttribute,
7850
7885
  routerAwarePoms,
@@ -8029,6 +8064,12 @@ function createDevProcessorPlugin(options) {
8029
8064
  prefixIdentifiers: true,
8030
8065
  inline: isScriptSetup,
8031
8066
  bindingMetadata,
8067
+ // Vue templates may contain TypeScript type annotations in expressions
8068
+ // (e.g. `@row-click="(row: RowType) => navigateTo(...)"` in Nuxt + TS
8069
+ // apps). Without this flag, Vue's internal processExpression step
8070
+ // delegates to @babel/parser without the TS plugin and crashes on
8071
+ // "Unexpected token, expected ','".
8072
+ expressionPlugins: ["typescript"],
8032
8073
  nodeTransforms: [
8033
8074
  createTestIdTransform(
8034
8075
  componentName,
@@ -8039,6 +8080,7 @@ function createDevProcessorPlugin(options) {
8039
8080
  {
8040
8081
  existingIdBehavior: existingIdBehavior ?? "error",
8041
8082
  nameCollisionBehavior,
8083
+ missingSemanticNameBehavior,
8042
8084
  testIdAttribute,
8043
8085
  warn: (message) => loggerRef.current.warn(message),
8044
8086
  vueFilesPathMap: provisionalVuePathMap,
@@ -8786,6 +8828,7 @@ function createVuePluginWithTestIds(options) {
8786
8828
  vueOptions,
8787
8829
  existingIdBehavior,
8788
8830
  nameCollisionBehavior,
8831
+ missingSemanticNameBehavior = "error",
8789
8832
  nativeWrappers,
8790
8833
  elementMetadata,
8791
8834
  semanticNameMap,
@@ -8866,6 +8909,7 @@ function createVuePluginWithTestIds(options) {
8866
8909
  existingIdBehavior,
8867
8910
  testIdAttribute,
8868
8911
  nameCollisionBehavior,
8912
+ missingSemanticNameBehavior,
8869
8913
  warn: (message) => loggerRef.current.warn(message),
8870
8914
  vueFilesPathMap,
8871
8915
  wrapperSearchRoots: getWrapperSearchRoots()
@@ -8920,6 +8964,7 @@ function createVuePluginWithTestIds(options) {
8920
8964
  existingIdBehavior,
8921
8965
  testIdAttribute,
8922
8966
  nameCollisionBehavior,
8967
+ missingSemanticNameBehavior,
8923
8968
  warn: (message) => loggerRef.current.warn(message),
8924
8969
  vueFilesPathMap,
8925
8970
  wrapperSearchRoots: getWrapperSearchRoots()
@@ -8967,9 +9012,16 @@ function createVuePluginWithTestIds(options) {
8967
9012
  const compile = compilerDom2.compile;
8968
9013
  const { descriptor } = parse(code, { filename: cleanPath });
8969
9014
  if (descriptor.template) {
9015
+ const mergedExpressionPlugins = Array.from(
9016
+ /* @__PURE__ */ new Set([
9017
+ "typescript",
9018
+ ...userCompilerOptions.expressionPlugins ?? []
9019
+ ])
9020
+ );
8970
9021
  compile(descriptor.template.content, {
8971
9022
  ...userCompilerOptions,
8972
9023
  filename: cleanPath,
9024
+ expressionPlugins: mergedExpressionPlugins,
8973
9025
  nodeTransforms: getNodeTransforms(cleanPath, componentName)
8974
9026
  });
8975
9027
  loggerRef.current.debug(`Metadata collected for ${cleanPath}`);
@@ -9266,6 +9318,7 @@ function createVuePomGeneratorPlugins(options = {}) {
9266
9318
  customPomImportAliases: resolvedCustomPomImportAliases,
9267
9319
  customPomImportNameCollisionBehavior: customPoms?.importNameCollisionBehavior,
9268
9320
  nameCollisionBehavior: generationOptions?.nameCollisionBehavior,
9321
+ missingSemanticNameBehavior: generationOptions?.missingSemanticNameBehavior,
9269
9322
  existingIdBehavior: resolvedInjectionOptions.existingIdBehavior,
9270
9323
  testIdAttribute,
9271
9324
  accessibilityAudit: generationOptions?.accessibilityAudit,
@@ -9354,6 +9407,7 @@ function createVuePomGeneratorPlugins(options = {}) {
9354
9407
  vueOptions,
9355
9408
  existingIdBehavior: resolvedGenerationOptions.existingIdBehavior,
9356
9409
  nameCollisionBehavior: resolvedGenerationOptions.nameCollisionBehavior,
9410
+ missingSemanticNameBehavior: resolvedGenerationOptions.missingSemanticNameBehavior,
9357
9411
  nativeWrappers,
9358
9412
  elementMetadata,
9359
9413
  semanticNameMap,