@@ -1043,6 +1043,178 @@ class BlockDirectiveArgumentParserTests: XCTestCase {
10431043 """
10441044 XCTAssertEqual ( expected, documentation. debugDescription ( ) )
10451045 }
1046+
1047+ func testParsingDirectiveArgumentsWithWhitespaceBeforeDirective( ) throws {
1048+ struct ExpectedArgumentInfo {
1049+ var line : Int
1050+ let name : String
1051+ var nameRange : Range < Int >
1052+ let value : String
1053+ var valueRange : Range < Int >
1054+ }
1055+
1056+ func assertDirectiveArguments(
1057+ _ expectedArguments: ExpectedArgumentInfo ... ,
1058+ parsing content: String ,
1059+ file: StaticString = #file,
1060+ line: UInt = #line
1061+ ) throws {
1062+ func substring( with range: SourceRange ) -> String {
1063+ let line = content. split ( omittingEmptySubsequences: false , whereSeparator: \. isNewline) [ range. lowerBound. line - 1 ]
1064+ let startIndex = line. utf8. index ( line. utf8. startIndex, offsetBy: range. lowerBound. column - 1 )
1065+ let endIndex = line. utf8. index ( line. utf8. startIndex, offsetBy: range. upperBound. column - 1 )
1066+ return String ( line [ startIndex ..< endIndex] )
1067+ }
1068+
1069+ let source = URL ( fileURLWithPath: " /test-file-location " )
1070+ let document = Document ( parsing: content, source: source, options: . parseBlockDirectives)
1071+ let directive = try XCTUnwrap ( document. children. compactMap ( { $0 as? BlockDirective } ) . first, file: file, line: line)
1072+ let arguments = directive. argumentText. parseNameValueArguments ( )
1073+ XCTAssertEqual ( arguments. count, expectedArguments. count, file: file, line: line)
1074+ for expectedArgument in expectedArguments {
1075+ let argument = try XCTUnwrap ( arguments [ expectedArgument. name] , file: file, line: line)
1076+
1077+ XCTAssertEqual ( expectedArgument. name, argument. name, file: file, line: line)
1078+ XCTAssertEqual (
1079+ argument. nameRange,
1080+ SourceLocation ( line: expectedArgument. line, column: expectedArgument. nameRange. lowerBound, source: source) ..< SourceLocation ( line: expectedArgument. line, column: expectedArgument. nameRange. upperBound, source: source) ,
1081+ file: file,
1082+ line: line
1083+ )
1084+ XCTAssertEqual ( expectedArgument. name, argument. nameRange. map ( substring ( with: ) ) , file: file, line: line)
1085+
1086+ XCTAssertEqual ( expectedArgument. value, argument. value, file: file, line: line)
1087+ XCTAssertEqual (
1088+ argument. valueRange,
1089+ SourceLocation ( line: expectedArgument. line, column: expectedArgument. valueRange. lowerBound, source: source) ..< SourceLocation ( line: expectedArgument. line, column: expectedArgument. valueRange. upperBound, source: source) ,
1090+ file: file,
1091+ line: line
1092+ )
1093+ XCTAssertEqual ( expectedArgument. value, argument. valueRange. map ( substring ( with: ) ) , file: file, line: line)
1094+ }
1095+ }
1096+
1097+ // One argument
1098+
1099+ try assertDirectiveArguments (
1100+ ExpectedArgumentInfo ( line: 1 , name: " firstArgument " , nameRange: 16 ..< 29 , value: " firstValue " , valueRange: 31 ..< 41 ) ,
1101+ parsing: " @DirectiveName(firstArgument: firstValue) "
1102+ )
1103+
1104+ try assertDirectiveArguments (
1105+ ExpectedArgumentInfo ( line: 2 , name: " firstArgument " , nameRange: 16 ..< 29 , value: " firstValue " , valueRange: 31 ..< 41 ) ,
1106+ parsing: """
1107+
1108+ @DirectiveName(firstArgument: firstValue)
1109+ """
1110+ )
1111+
1112+ // Argument on single line
1113+
1114+ try assertDirectiveArguments (
1115+ ExpectedArgumentInfo ( line: 1 , name: " firstArgument " , nameRange: 17 ..< 30 , value: " firstValue " , valueRange: 31 ..< 41 ) ,
1116+ ExpectedArgumentInfo ( line: 1 , name: " secondArgument " , nameRange: 44 ..< 58 , value: " secondValue " , valueRange: 62 ..< 73 ) ,
1117+ parsing: " @DirectiveName( firstArgument:firstValue , \t secondArgument: \t secondValue) "
1118+ )
1119+
1120+ try assertDirectiveArguments (
1121+ ExpectedArgumentInfo ( line: 2 , name: " firstArgument " , nameRange: 17 ..< 30 , value: " firstValue " , valueRange: 31 ..< 41 ) ,
1122+ ExpectedArgumentInfo ( line: 2 , name: " secondArgument " , nameRange: 44 ..< 58 , value: " secondValue " , valueRange: 62 ..< 73 ) ,
1123+ parsing: """
1124+
1125+ @DirectiveName( firstArgument:firstValue , \t secondArgument: \t secondValue)
1126+ """
1127+ )
1128+
1129+ try assertDirectiveArguments (
1130+ ExpectedArgumentInfo ( line: 2 , name: " firstArgument " , nameRange: 19 ..< 32 , value: " firstValue " , valueRange: 33 ..< 43 ) ,
1131+ ExpectedArgumentInfo ( line: 2 , name: " secondArgument " , nameRange: 46 ..< 60 , value: " secondValue " , valueRange: 64 ..< 75 ) ,
1132+ parsing: """
1133+
1134+ @DirectiveName( firstArgument:firstValue , \t secondArgument: \t secondValue)
1135+ """
1136+ )
1137+
1138+ // Second argument on new line
1139+
1140+ try assertDirectiveArguments (
1141+ ExpectedArgumentInfo ( line: 1 , name: " firstArgument " , nameRange: 17 ..< 30 , value: " firstValue " , valueRange: 31 ..< 41 ) ,
1142+ ExpectedArgumentInfo ( line: 2 , name: " secondArgument " , nameRange: 16 ..< 30 , value: " secondValue " , valueRange: 34 ..< 45 ) ,
1143+ parsing: """
1144+ @DirectiveName( firstArgument:firstValue ,
1145+ secondArgument: \t secondValue)
1146+ """
1147+ )
1148+
1149+ try assertDirectiveArguments (
1150+ ExpectedArgumentInfo ( line: 2 , name: " firstArgument " , nameRange: 17 ..< 30 , value: " firstValue " , valueRange: 31 ..< 41 ) ,
1151+ ExpectedArgumentInfo ( line: 3 , name: " secondArgument " , nameRange: 16 ..< 30 , value: " secondValue " , valueRange: 34 ..< 45 ) ,
1152+ parsing: """
1153+
1154+ @DirectiveName( firstArgument:firstValue ,
1155+ secondArgument: \t secondValue)
1156+ """
1157+ )
1158+
1159+ try assertDirectiveArguments (
1160+ ExpectedArgumentInfo ( line: 2 , name: " firstArgument " , nameRange: 19 ..< 32 , value: " firstValue " , valueRange: 33 ..< 43 ) ,
1161+ ExpectedArgumentInfo ( line: 3 , name: " secondArgument " , nameRange: 18 ..< 32 , value: " secondValue " , valueRange: 36 ..< 47 ) ,
1162+ parsing: """
1163+
1164+ @DirectiveName( firstArgument:firstValue ,
1165+ secondArgument: \t secondValue)
1166+ """
1167+ )
1168+
1169+ // Arguments on separate lines
1170+
1171+ try assertDirectiveArguments (
1172+ ExpectedArgumentInfo ( line: 2 , name: " firstArgument " , nameRange: 3 ..< 16 , value: " firstValue " , valueRange: 17 ..< 27 ) ,
1173+ ExpectedArgumentInfo ( line: 3 , name: " secondArgument " , nameRange: 2 ..< 16 , value: " secondValue " , valueRange: 20 ..< 31 ) ,
1174+ parsing: """
1175+ @DirectiveName(
1176+ firstArgument:firstValue ,
1177+ \t secondArgument: \t secondValue
1178+ )
1179+ """
1180+ )
1181+
1182+ try assertDirectiveArguments (
1183+ ExpectedArgumentInfo ( line: 3 , name: " firstArgument " , nameRange: 3 ..< 16 , value: " firstValue " , valueRange: 17 ..< 27 ) ,
1184+ ExpectedArgumentInfo ( line: 4 , name: " secondArgument " , nameRange: 2 ..< 16 , value: " secondValue " , valueRange: 20 ..< 31 ) ,
1185+ parsing: """
1186+
1187+ @DirectiveName(
1188+ firstArgument:firstValue ,
1189+ \t secondArgument: \t secondValue
1190+ )
1191+ """
1192+ )
1193+
1194+ try assertDirectiveArguments (
1195+ ExpectedArgumentInfo ( line: 3 , name: " firstArgument " , nameRange: 5 ..< 18 , value: " firstValue " , valueRange: 19 ..< 29 ) ,
1196+ ExpectedArgumentInfo ( line: 4 , name: " secondArgument " , nameRange: 4 ..< 18 , value: " secondValue " , valueRange: 22 ..< 33 ) ,
1197+ parsing: """
1198+
1199+ @DirectiveName(
1200+ firstArgument:firstValue ,
1201+ \t secondArgument: \t secondValue
1202+ )
1203+ """
1204+ )
1205+
1206+ // Content and directives with emoji
1207+
1208+ try assertDirectiveArguments (
1209+ ExpectedArgumentInfo ( line: 3 , name: " firstArgument " , nameRange: 20 ..< 33 , value: " first💻Value " , valueRange: 35 ..< 49 ) ,
1210+ ExpectedArgumentInfo ( line: 3 , name: " secondArgument " , nameRange: 51 ..< 65 , value: " secondValue " , valueRange: 67 ..< 78 ) ,
1211+ parsing: """
1212+ Paragraph before with emoji: 💻
1213+
1214+ @Directive💻Name(firstArgument: first💻Value, secondArgument: secondValue)
1215+ """
1216+ )
1217+ }
10461218
10471219 // FIXME: swift-testing macro for specifying the relationship between a bug and a test
10481220 // Uncomment the following code when we integrate swift-testing
0 commit comments