Skip to content

Fix F19 false positive on custom columns matching unrelated field names #106

Description

@Gremiger

Description

Rule F19 (cli/src/validate-pipeline.js) matches a hand-written custom table/panel column's key against a contract.json field name, but never checks whether the column is actually bound to that field. A pure custom-render column — no local column, backed instead by its own backendFilterKey/backendSortKey pointing at a different backend property — whose key merely coincides with an unrelated field's name gets its required flag compared against that unrelated field's required, producing a semantically meaningless BLOCK violation. This can happen on any window where a display-only computed column's key coincides with a differently-typed field elsewhere on the same entity, not just one.

Steps to reproduce

  1. Run the pipeline validator on the product window (node cli/src/validate-pipeline.js --scope=product from etendo_schema_forge, or via its pre-commit hook).
  2. tools/app-shell/src/windows/custom/product/ProductCustomTable.jsx declares a key: 'sale' grid column — a computed sales-price display cell (type: 'custom', render: (row) => <ProductSalePriceCell .../>, backendSortKey/backendFilterKey: 'eTGOSalePrice', no column prop, no real required semantics).
  3. contract.json separately has an unrelated boolean field also named sale (column: 'IsSold', type: 'boolean', required: true) — a completely different concept (the actual IsSold checkbox, correctly modeled elsewhere in the same window as { key: 'sale', column: 'IsSold', required: true } in ProductAdditionalInfoPanel.jsx).
  4. F19 fires: "Custom table column 'sale' ... has required=false but contract.json field 'sale' has required=true" — even though the two sales represent unrelated data.
  5. The same collision exists for key: 'purchase' (price display) vs. the boolean IsPurchased field, currently masked because F19 appears to report only the first violation per file/artifact.

Expected behavior

F19 should not compare required flags for a column that isn't actually bound to the matched contract field. A column with no local column and a backendFilterKey/backendSortKey that doesn't map back to the matched field's own column is a display-only cell, not a real binding, and should be skipped.

Affected components

  • cli/src/validate-pipeline.jsevaluateF19Entry / resolveContractField / extractColumnEntries

Proposed fix

Capture backendFilterKey in extractColumnEntries alongside column, then skip the comparison in evaluateF19Entry when the entry has no local column and its backendFilterKey doesn't match the resolved field's own column:

cli/src/validate-pipeline.js

       const columnProp = findObjectProp(obj, 'column');
       const column = columnProp && columnProp.value.type === 'StringLiteral' ? columnProp.value.value : null;
+      const backendFilterKeyProp = findObjectProp(obj, 'backendFilterKey');
+      const backendFilterKey = backendFilterKeyProp && backendFilterKeyProp.value.type === 'StringLiteral'
+        ? backendFilterKeyProp.value.value : null;

-      entries.push({ key: keyProp.value.value, column, required: resolveLocalRequired(obj) });
+      entries.push({ key: keyProp.value.value, column, backendFilterKey, required: resolveLocalRequired(obj) });
   const field = resolveContractField(fieldIndex, entry.key, entry.column);
   if (!field) return null; // not a real contract field — pure custom render column

+  // A column with no local `column` but a backendFilterKey pointing at a DIFFERENT
+  // backend property than the matched field's own `column` is a display-only cell
+  // whose `key` merely collides with an unrelated field's name, not a real binding.
+  if (!entry.column && entry.backendFilterKey && entry.backendFilterKey !== field.column) return null;
+
   if (isF19Allowlisted(allowlist, artifactName, entry.key)) return null;

A maintainer should double-check this heuristic doesn't need to also cover columns with type: 'custom' and no backendFilterKey at all (a more general marker already used by rule F20 for "no real filter semantics") — this diff only closes the specific backendFilterKey-mismatch case found here.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions