Skip to content

fix(prefer-native-locators): stop the autofix from emitting invalid code - #502

Open
dylanpulver wants to merge 1 commit into
mskelton:mainfrom
dylanpulver:fix/prefer-native-locators-autofix
Open

fix(prefer-native-locators): stop the autofix from emitting invalid code#502
dylanpulver wants to merge 1 commit into
mskelton:mainfrom
dylanpulver:fix/prefer-native-locators-autofix

Conversation

@dylanpulver

Copy link
Copy Markdown

Running eslint --fix with this rule enabled can leave source files that no
longer parse. On a compound selector the fixer writes the rest of the selector
into the middle of the string literal it emits, so the file is corrupted rather
than fixed. In a pre-commit hook or a CI autofix step that lands silently. The
reporter in #482 had four files broken by one run.

Two selector shapes trigger it.

A selector that starts with a matched attribute and then continues:

page.locator('[data-testid="a"] [data-testid="b"]')
page.locator('[role="dialog"][data-state="open"]')

becomes

page.getByTestId("a"] [data-testid="b")
page.getByRole("dialog"][data-state="open")

A value that contains a double quote, since the emitted literal is always double
quoted and the value is never escaped:

page.locator("[aria-label='Say \"hi\"']")

becomes

page.getByLabel("Say "hi"")

I reproduced both on main before touching anything.

Cause

compilePatterns builds ^\[<attr>=['"]?(.+?)['"]?\]$ for each attribute. The
capture group is lazy, but the pattern is anchored at the end of the string, so
on a compound selector the group has to expand past the closing bracket of the
first attribute and swallow everything up to the last one. That captured text is
then interpolated into "${match[1]}" with no escaping applied.

Change

A selector is now matched only when it is exactly one attribute selector for the
attribute in question. The value is either quoted, where the escapes permitted
inside it are for a quote or for a backslash, or a bare run of characters with no
whitespace, quotes, brackets or backslashes in it.

A selector that also carries a descendant or a second attribute no longer
matches, so nothing is reported for it and no fix is offered. That lines up with
how the rule already treats a selector with a leading class or tag, and it is the
right outcome because no single native locator selects the same elements. Both
getByTestId('a') and getByRole('dialog') would have quietly dropped a
constraint even if the syntax had come out right.

The captured value is unescaped and then rendered with a quote style picked from
its contents. Double quotes remain the default so existing output is unchanged. A
value holding a double quote and no single quote is emitted in single quotes, and
a value holding both is emitted in double quotes with the inner ones escaped.

One small related thing: the attribute name is escaped before it goes into the
regex, since testIdAttribute comes from user config.

Tests

src/rules/prefer-native-locators.test.ts gains valid cases for the compound
shapes from the report, covering the ones whose autofix output previously did not
parse, plus invalid cases for a value containing a double quote and for a value
containing both quote characters. Nine of the added cases fail against the
current rule and pass with this change. Every existing case still passes
untouched, and yarn ci is green.

Fixes #482

The selector was matched with a lazy regex anchored at the end of the
string, so a compound selector such as [role="dialog"][data-state="open"]
matched with everything between the first quote and the last bracket
captured as the attribute value. The fix then wrote that text straight into
a double quoted literal, producing source that no longer parses. The
emitted literal was also always double quoted and never escaped, so a value
containing a double quote broke the same way.

The selector is now matched only when it is exactly one attribute selector,
with the value either quoted or a bare run of characters. Anything carrying
a descendant, a combinator or a second attribute no longer matches, since
no single native locator selects the same elements. The value is unescaped
and then rendered with a quote style that suits its contents.
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.

prefer-native-locators --fix emits invalid syntax for compound selectors and for quoted attribute values

1 participant