Skip to content

Commit 05b6475

Browse files
Added lookbehind option for prefer-lookaround (#499)
1 parent 5ab1acb commit 05b6475

File tree

3 files changed

+49
-13
lines changed

3 files changed

+49
-13
lines changed

docs/rules/prefer-lookaround.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,23 @@ var str = 'JavaScript'.replace(/Java(Script)/g, 'Type$1')
3434
```json
3535
{
3636
"regexp/prefer-lookaround": ["error", {
37+
"lookbehind": true,
3738
"strictTypes": true
3839
}]
3940
}
4041
```
4142

42-
- `strictTypes` ... If `true`, strictly check the type of object to determine if the regex instance was used in `replace()` and `replaceAll()`. Default is `true`.\
43-
This option is always on when using TypeScript.
43+
### `lookbehind`
44+
45+
This option controls whether this rule will suggest using lookbehinds. Default is `true`.
46+
47+
Safari is the last major browser that still does not support regex lookbehind assertions (as of December 2022). So if your code base targets Safari, you cannot use lookbehinds.
48+
49+
### `strictTypes`
50+
51+
If `true`, strictly check the type of object to determine if the regex instance was used in `replace()` and `replaceAll()`. Default is `true`.
52+
53+
This option is always on when using TypeScript.
4454

4555
## :rocket: Version
4656

lib/rules/prefer-lookaround.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -364,19 +364,14 @@ function leadingTrailingElementsToLookaroundAssertionPatternText(
364364
function parseOption(
365365
userOption:
366366
| {
367+
lookbehind?: boolean
367368
strictTypes?: boolean
368369
}
369370
| undefined,
370371
) {
371-
let strictTypes = true
372-
if (userOption) {
373-
if (userOption.strictTypes != null) {
374-
strictTypes = userOption.strictTypes
375-
}
376-
}
377-
378372
return {
379-
strictTypes,
373+
lookbehind: userOption?.lookbehind ?? true,
374+
strictTypes: userOption?.strictTypes ?? true,
380375
}
381376
}
382377

@@ -393,6 +388,7 @@ export default createRule("prefer-lookaround", {
393388
{
394389
type: "object",
395390
properties: {
391+
lookbehind: { type: "boolean" },
396392
strictTypes: { type: "boolean" },
397393
},
398394
additionalProperties: false,
@@ -406,7 +402,7 @@ export default createRule("prefer-lookaround", {
406402
type: "suggestion",
407403
},
408404
create(context) {
409-
const { strictTypes } = parseOption(context.options[0])
405+
const { lookbehind, strictTypes } = parseOption(context.options[0])
410406
const typeTracer = createTypeTracker(context)
411407

412408
/**
@@ -645,7 +641,7 @@ export default createRule("prefer-lookaround", {
645641
},
646642
onPatternLeave() {
647643
// verify
648-
let reportStart = null
644+
let reportStart: ParsedStartPattern | null = null
649645
if (
650646
!startRefState.isUseOther &&
651647
startRefState.capturingGroups.length === 1 && // It will not be referenced from more than one, but check it just in case.
@@ -654,7 +650,7 @@ export default createRule("prefer-lookaround", {
654650
) {
655651
reportStart = parsedElements.start
656652
}
657-
let reportEnd = null
653+
let reportEnd: ParsedEndPattern | null = null
658654
if (
659655
!endRefState.isUseOther &&
660656
endRefState.capturingGroups.length === 1 && // It will not be referenced from more than one, but check it just in case.
@@ -676,6 +672,9 @@ export default createRule("prefer-lookaround", {
676672
if (sideEffects.has(SideEffect.endRef)) {
677673
reportEnd = null
678674
}
675+
if (!lookbehind) {
676+
reportStart = null
677+
}
679678

680679
if (reportStart && reportEnd) {
681680
const fix = buildFixer(

tests/lib/rules/prefer-lookaround.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,14 @@ tester.run("prefer-lookaround", rule as any, {
202202
// cannot replace assertions
203203
`var str = 'JavaScriptCode'.replace(/Java(Script)(?:Code)/g, 'Type$1')`,
204204
`var str = 'JavaScriptCode'.replace(/(?:Java)(Script)Code/g, '$1Element')`,
205+
206+
// no lookbehinds
207+
{
208+
code: `
209+
const str = 'A JavaScript formatter written in JavaScript.'.replace(/(Java)Script/g, '$1');
210+
`,
211+
options: [{ lookbehind: false }],
212+
},
205213
],
206214
invalid: [
207215
{
@@ -800,5 +808,24 @@ tester.run("prefer-lookaround", rule as any, {
800808
"This capturing group can be replaced with a lookbehind assertion ('(?<=((?<=Java))Script)').",
801809
],
802810
},
811+
812+
// no lookbehinds
813+
{
814+
code: `
815+
const str = 'I love unicorn! I hate unicorn?'.replace(/(?<before>love )unicorn(?<after>!)/, '$<before>🦄$<after>');
816+
`,
817+
output: `
818+
const str = 'I love unicorn! I hate unicorn?'.replace(/(?<before>love )unicorn(?=!)/, '$<before>🦄');
819+
`,
820+
options: [{ lookbehind: false }],
821+
errors: [
822+
{
823+
message:
824+
"This capturing group can be replaced with a lookahead assertion ('(?=!)').",
825+
column: 91,
826+
endColumn: 102,
827+
},
828+
],
829+
},
803830
],
804831
})

0 commit comments

Comments
 (0)