Skip to content

Conversation

@oekeur
Copy link

@oekeur oekeur commented Jan 28, 2026

What?

  • Default search now treats input as literal text (regex metacharacters are escaped).
  • Added “Use Regular Expression” toggle to opt in to regex behavior.
  • Persist the regex toggle per user via core/preferences.
  • Surface a friendly error for invalid regex input.
  • Added unit tests for regex escaping.

Why?

Editors reported this as a bug:

typing .’ and replacing with ." also replaces other characters.

This is because . was treated as “any character.” This is unexpected for non‑technical users.
Making literal matching the default aligns with non-technical user expectations while keeping regex available.

Closes #74

  • Open /wp-admin/post-new.php and add a few text blocks.
  • Open Search & Replace and try:
    • Search: .’, Replace: ."
      • only the literal sequence should change.
    • Enable “Use Regular Expression” and search for .
      • now matches any character.
      • Fun explicit functionality now: try using \d, \s \w to verify regex functionality
  • Edge cases/regression:
    • Invalid regex (e.g. () shows the error message instead of replacing.
    • Toggle persists per user across reloads.

Types of changes

  • Bug fix (non-breaking change)
  • New feature

Screenshots:

image

Copilot AI review requested due to automatic review settings January 28, 2026 16:01
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a user experience issue where non-technical users experienced unexpected text replacements due to regex metacharacters being interpreted as patterns rather than literal text. The fix makes literal text matching the default behavior while providing an opt-in "Use Regular Expression" toggle for power users who need regex functionality.

Changes:

  • Added escapeRegExp utility function to escape regex metacharacters for literal text matching
  • Introduced a "Use Regular Expression" toggle with user preference persistence via WordPress core/preferences
  • Added error handling and user-friendly error messages for invalid regex patterns

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
src/core/utils.tsx Added escapeRegExp function to safely escape regex metacharacters for literal string matching
src/core/app.tsx Integrated regex toggle, preference persistence, error handling, and conditional escaping of search input
tests/utils.test.tsx Added unit tests for the new escapeRegExp function

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +372 to +378
it( 'escapeRegExp escapes regex metacharacters', () => {
const { escapeRegExp } = require( '../src/core/utils' );

const escaped = escapeRegExp( 'a.b+c*' );

expect( escaped ).toBe( 'a\\.b\\+c\\*' );
} );
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test for escapeRegExp only covers a subset of regex metacharacters (., +, *). The implementation at line 350 escapes all regex metacharacters including ^, $, {, }, (, ), |, [, ], and . Consider adding test cases that verify these additional characters are properly escaped. For example, test inputs like "test^$" should become "test\^\$" and "(test)" should become "\(test\)".

Copilot uses AI. Check for mistakes.
Comment on lines +367 to +387
describe( 'escapeRegExp', () => {
beforeEach( () => {
jest.resetModules();
} );

it( 'escapeRegExp escapes regex metacharacters', () => {
const { escapeRegExp } = require( '../src/core/utils' );

const escaped = escapeRegExp( 'a.b+c*' );

expect( escaped ).toBe( 'a\\.b\\+c\\*' );
} );

it( 'escapeRegExp leaves plain text unchanged', () => {
const { escapeRegExp } = require( '../src/core/utils' );

const escaped = escapeRegExp( 'plain text' );

expect( escaped ).toBe( 'plain text' );
} );
} );
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test coverage for escapeRegExp is missing an important edge case test that aligns with the guard clause in the implementation (line 346-348 in utils.tsx). Based on the pattern used in other tests in this file (e.g., getNumberToBase10 at lines 333-340), there should be a test case to verify that passing a non-string parameter returns an empty string.

Copilot uses AI. Check for mistakes.
* @return {void}
*/
const handleUseRegex = ( newValue: boolean ): void => {
setUseRegex( newValue );
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When toggling the regex option (line 107-110), the error state is not cleared. If a user enters invalid regex syntax while in regex mode (which shows an error), then toggles regex off, the error message will persist even though the input would now be treated as literal text and is valid. Consider clearing the error state when toggling useRegex by adding setError('') in the handleUseRegex function.

Suggested change
setUseRegex( newValue );
setUseRegex( newValue );
setError( '' );

Copilot uses AI. Check for mistakes.
Comment on lines +367 to +387
describe( 'escapeRegExp', () => {
beforeEach( () => {
jest.resetModules();
} );

it( 'escapeRegExp escapes regex metacharacters', () => {
const { escapeRegExp } = require( '../src/core/utils' );

const escaped = escapeRegExp( 'a.b+c*' );

expect( escaped ).toBe( 'a\\.b\\+c\\*' );
} );

it( 'escapeRegExp leaves plain text unchanged', () => {
const { escapeRegExp } = require( '../src/core/utils' );

const escaped = escapeRegExp( 'plain text' );

expect( escaped ).toBe( 'plain text' );
} );
} );
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a test case for escapeRegExp with an empty string input to verify it returns an empty string, consistent with the behavior when a non-string is passed. This would complete the edge case coverage alongside the planned test for non-string inputs.

Copilot uses AI. Check for mistakes.
Comment on lines +229 to +233
setError(
__(
'Invalid regular expression.',
'search-replace-for-block-editor'
)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When an invalid regex error is shown and the user modifies the search input to correct it, the error will be cleared because the replace() function is called via the useEffect hook at line 121. However, if there's an error and the user clicks the "Replace" button (line 544), the error will persist if the regex is still invalid. This is correct behavior. However, consider providing more specific error information by including the actual regex error message from the caught exception, which would help users debug their regex patterns.

Suggested change
setError(
__(
'Invalid regular expression.',
'search-replace-for-block-editor'
)
const details =
err instanceof Error && err.message
? ' ' + err.message
: '';
setError(
__(
'Invalid regular expression.',
'search-replace-for-block-editor'
) + details

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default search should be literal; regex should be opt‑in?

1 participant