fix: regex field-formatters swapping and preserve grouped#8152
fix: regex field-formatters swapping and preserve grouped#8152grantfitzsimmons wants to merge 6 commits into
Conversation
📝 WalkthroughWalkthroughThis PR fixes a regression in regex field formatter handling introduced in v7.12.0, where outer wrapping parentheses in regex patterns were being incorrectly stripped. The fix improves parentheses detection using bracket-depth scanning, refactors regex field UI rendering to clarify which column handles pattern versus placeholder, and adds test coverage for grouped patterns, numeric decimals, and catalog numbers. ChangesRegex field formatter fix and refactoring
🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
alesan99
left a comment
There was a problem hiding this comment.
- Save and confirm the XML retains the
valueunchanged. - Enter a sample value such as
CNS-2026-503in a form/query and confirm the formatted/canonicalized value remains the full string. Verify that this appears correctly in the form view, query results, and in Batch Edit.
Works, I don't experience the original issue 👍👍
value="" does change when you save the formatters in the visual editor, but its because of normalizeRegexString.
|
@CodeRabbit full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@specifyweb/frontend/js_src/lib/components/FieldFormatters/spec.ts`:
- Around line 183-206: The current unwrapping logic in
hasWrappingParens/trimRegexString removes any outer parentheses (and treats
escaped \( \) as structural), which strips user-authored groups; change the
logic so we only remove the exact legacy alternation wrapper we add in
normalizeRegexString: update hasWrappingParens to ignore escaped paren
characters (skip parens preceded by an odd number of backslashes) when tracking
depth and only return true if the outermost pair encloses the full string AND
there is at least one unescaped '|' at top-level inside that pair (i.e., a '|'
encountered when depth === 1); only then allow trimRegexString to slice off the
outer parens. Ensure you touch hasWrappingParens and any callers
(trimRegexString/normalizeRegexString) to reflect this narrower unwrapping rule.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 4bb52df6-f1d6-49c7-a236-158e5ba541e8
📒 Files selected for processing (3)
specifyweb/frontend/js_src/lib/components/FieldFormatters/Parts.tsxspecifyweb/frontend/js_src/lib/components/FieldFormatters/__tests__/index.test.tsspecifyweb/frontend/js_src/lib/components/FieldFormatters/spec.ts
| // Only remove parentheses that wrap the entire pattern. | ||
| if (hasWrappingParens(pattern)) pattern = pattern.slice(1, -1); | ||
| return pattern; | ||
| } | ||
| function normalizeRegexString(regexString: string): string { | ||
| let pattern: string = trimRegexString(regexString); | ||
| // Wrap alternations so they remain grouped when re-anchored for legacy XML. | ||
| if (pattern.includes('|')) pattern = `(${pattern})`; | ||
| return `/^${pattern}$/`; | ||
| } | ||
|
|
||
| function hasWrappingParens(pattern: string): boolean { | ||
| // Detect a single pair of outer parentheses that encloses the full string. | ||
| if (!pattern.startsWith('(') || !pattern.endsWith(')')) return false; | ||
| let depth = 0; | ||
| for (let index = 0; index < pattern.length; index += 1) { | ||
| const character = pattern[index]; | ||
| if (character === '(') depth += 1; | ||
| else if (character === ')') { | ||
| depth -= 1; | ||
| if (depth === 0 && index < pattern.length - 1) return false; | ||
| } | ||
| } | ||
| return depth === 0; |
There was a problem hiding this comment.
Preserve user-authored outer groups instead of trimming every full wrapper.
normalizeRegexString() only adds outer parentheses for the legacy alternation case, but this helper removes any pair that encloses the whole pattern. That still rewrites valid values like /^([A-Z]{2})$/ to /^[A-Z]{2}$/ on round-trip, which misses the PR’s preservation goal. The depth scan also counts escaped \( / \) as structural, so wrappers around literal parens are misdetected. Please narrow the unwrap to the legacy wrapper shape you generate rather than any outer parens.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@specifyweb/frontend/js_src/lib/components/FieldFormatters/spec.ts` around
lines 183 - 206, The current unwrapping logic in
hasWrappingParens/trimRegexString removes any outer parentheses (and treats
escaped \( \) as structural), which strips user-authored groups; change the
logic so we only remove the exact legacy alternation wrapper we add in
normalizeRegexString: update hasWrappingParens to ignore escaped paren
characters (skip parens preceded by an odd number of backslashes) when tracking
depth and only return true if the outermost pair encloses the full string AND
there is at least one unescaped '|' at top-level inside that pair (i.e., a '|'
encountered when depth === 1); only then allow trimRegexString to slice off the
outer parens. Ensure you touch hasWrappingParens and any callers
(trimRegexString/normalizeRegexString) to reflect this narrower unwrapping rule.
Fixes #8129
valueandpatternswapped and where grouping/anchors in regexes were being stripped or rewritten, causing saved values and form results to change.RegexFieldARIA label/placeholder so the value and pattern inputs are distinct for screen readers.Changed
Checklist
Testing Instructions
Compare this against
v7.12.0.6. See #8129Open Field Formatters UI in the app.
Create/edit a regex formatter with
value="(CANB|CBG|CNS|JCT|QRS)-([0-9]{4})-([0-9]{3})"andpattern="eg. CANB-2024-001".Save and confirm the XML retains the
valueunchanged.Enter a sample value such as
CNS-2026-503in a form/query and confirm the formatted/canonicalized value remains the full string. Verify that this appears correctly in the form view, query results, and in Batch Edit.Summary by CodeRabbit
Release Notes