@@ -624,48 +624,53 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr
624624
625625 // Filtering
626626 var filters = [ Configuration . TestFilter] ( )
627- func testFilter( forRegularExpressions regexes: [ String ] ? , label: String , membership: Configuration . TestFilter . Membership ) throws -> Configuration . TestFilter {
628- guard let regexes, !regexes. isEmpty else {
629- // Return early if empty, even though the `reduce` logic below can handle
630- // this case, in order to avoid the `#available` guard.
631- return . unfiltered
627+ func testFilters( forOptionArguments optionArguments: [ String ] ? , label: String , membership: Configuration . TestFilter . Membership ) throws -> [ Configuration . TestFilter ] {
628+
629+ // Filters will come in two flavors: those with `tag:` as a prefix, and
630+ // those without. We split them into two collections, taking care to handle
631+ // an escaped colon, treating it as a pseudo-operator.
632+ let tagPrefix = " tag: "
633+ let escapedTagPrefix = #"tag\:"#
634+ var tags = [ Tag] ( )
635+ var regexes = [ String] ( )
636+
637+ // Loop through all the option arguments, separating tags from regex filters
638+ for var optionArg in optionArguments ?? [ ] {
639+ if optionArg. hasPrefix ( tagPrefix) {
640+ // Running into the `tag:` prefix means we should strip it and use the
641+ // actual tag name the user has provided
642+ let tagStringWithoutPrefix = String ( optionArg. dropFirst ( tagPrefix. count) )
643+ tags. append ( Tag ( userProvidedStringValue: tagStringWithoutPrefix) )
644+ } else {
645+ // If we run into the escaped tag prefix, the user has indicated they
646+ // want to us to treat it as a regex filter. We need to to unescape it
647+ // before adding it as a regex filter
648+ if optionArg. hasPrefix ( escapedTagPrefix) {
649+ optionArg. replaceSubrange ( escapedTagPrefix. startIndex..< escapedTagPrefix. endIndex, with: tagPrefix)
650+ }
651+ regexes. append ( optionArg)
652+ }
632653 }
633654
634- return try Configuration . TestFilter ( membership: membership, matchingAnyOf: regexes)
635- }
636-
637- // Extract any filters or skips without the `tag:` prefix; those will be treated as normal regexes.
638- let tagPrefix = " tag: "
639- let escapedTagPrefix = " tag \\ : "
640- var nonTagFilterRegexes : [ String ] = [ ]
641- var nonTagSkipRegexes : [ String ] = [ ]
642-
643- for var filter in args. filter ?? [ ] {
644- if filter. hasPrefix ( tagPrefix) {
645- filters. append ( Configuration . TestFilter ( includingAnyOf: [ Tag ( userProvidedStringValue: String ( filter. dropFirst ( 4 ) ) ) ] ) )
646- } else {
647- // If we run into the escaped tag prefix, we need to to remove the escape character before adding it as a regex filter
648- if filter. hasPrefix ( escapedTagPrefix) {
649- filter. replaceSubrange ( escapedTagPrefix. startIndex..< escapedTagPrefix. endIndex, with: tagPrefix)
650- }
651- nonTagFilterRegexes. append ( filter)
655+ // If we didn't find any tags, the tagFilter should be .unfiltered,
656+ // otherwise we construct it with the provided tags
657+ let tagFilter : Configuration . TestFilter = switch ( membership, tags. isEmpty) {
658+ case ( _, true ) : . unfiltered
659+ case ( . including, false ) : Configuration . TestFilter ( includingAnyOf: tags)
660+ case ( . excluding, false ) : Configuration . TestFilter ( excludingAnyOf: tags)
652661 }
653- }
654662
655- for var skip in args. skip ?? [ ] {
656- if skip. hasPrefix ( tagPrefix) {
657- filters. append ( Configuration . TestFilter ( includingAnyOf: [ Tag ( userProvidedStringValue: String ( skip. dropFirst ( 4 ) ) ) ] ) )
658- } else {
659- // If we run into the escaped tag prefix, we need to to remove the escape character before adding it as a regex filter
660- if skip. hasPrefix ( escapedTagPrefix) {
661- skip. replaceSubrange ( escapedTagPrefix. startIndex..< escapedTagPrefix. endIndex, with: tagPrefix)
662- }
663- nonTagSkipRegexes. append ( skip)
663+ guard !regexes. isEmpty else {
664+ // Return early with just the tag filter, even though the `reduce` logic
665+ // below can handle this case, in order to avoid the `#available` guard.
666+ return [ tagFilter]
664667 }
668+
669+ return [ try Configuration . TestFilter ( membership: membership, matchingAnyOf: regexes) , tagFilter]
665670 }
666671
667- filters. append ( try testFilter ( forRegularExpressions : nonTagFilterRegexes , label: " --filter " , membership: . including) )
668- filters. append ( try testFilter ( forRegularExpressions : nonTagSkipRegexes , label: " --skip " , membership: . excluding) )
672+ filters += try testFilters ( forOptionArguments : args . filter , label: " --filter " , membership: . including)
673+ filters += try testFilters ( forOptionArguments : args . skip , label: " --skip " , membership: . excluding)
669674
670675 configuration. testFilter = filters. reduce ( . unfiltered) { $0. combining ( with: $1) }
671676 if args. includeHiddenTests == true {
0 commit comments