regexregex-negationswiftlint

Regex where substring isn't found in pattern in specific location


I am trying to build a (multiline) pattern for a linter, to catch cases where:

  1. There is a Text( declaration
  2. That is not followed by .appFont( declaration...
  3. ...before the next occurrence of } (end of function) or Text( (another Text declaration...)

After many hours on regex101 (and consulting gpt...) I got these 2:

Text\([\s\S]*?\)[\s\S]*?(?!\.appFont)

This just catches the part that's before the .appFont, but I want the entire catch to fail if .appFont is found...

Text\([\s\S]*?\)[\s]*?(?!appFont)[\s\S]\}

This just catches everything, ignoring appFont being in the sting entirely...

In the following example, only the 2nd case should be captured:

Text("blah") 
  .appFont(.body)
}

Text("blah") 
}

Text(
  "blah"
)
.appFont(.blah)
}

I tried to read about negative lookahead but I think I still somehow just use it wrong, or somehow cause it to be ignored when I add [\s\S] maybe?


Solution

  • Using a negated character class together with a negative lookahead.

    Text\([^)]*\)(?:(?!\.appFont)[^}])*}
    

    See this demo at regex101 - A bit similar to tempered greedy token.

    regex explanation
    Text match the substring
    \([^)]*\) match ( followed by any amount of non-) negated class up to next closing )
    (?:(?!\.appFont)[^}])*} (?: non capturing group) repeated * any amount of times, containing:
    (?!\.appFont) a neg. lookahead that checks in front of each non-} if substring \.appFont is not ahead - consumes on success each matching character up to }

    Or alternatively use the lookahead assertion just once after closing ).

    Text\([^)]*\)(?![^}]*?\.appFont)[^}]*}
    

    Another demo at regex101 - Might even be a bit more efficient here.

    regex explanation
    Text match the substring
    \([^)]*\) match ( followed by any amount of non-) up to the next closing )
    (?![^}]*?\.appFont) neg. lookahead (condition): look if [^}]*?\.appFont is not ahead where [^}]*? matches lazily any amount of non-} up to the substring \.appFont
    [^}]*} if the condition succeded (it's not ahead) consume any amount of non-} up to }