( targetName: string, filePath: string, content: string, )
| 135 | } |
| 136 | |
| 137 | function discoverTestsInFileContent( |
| 138 | targetName: string, |
| 139 | filePath: string, |
| 140 | content: string, |
| 141 | ): DiscoveredTestFile | null { |
| 142 | const rawLines = content.split(/\r?\n/); |
| 143 | const sanitizerState: SanitizerState = { |
| 144 | inBlockComment: false, |
| 145 | inMultilineString: false, |
| 146 | }; |
| 147 | const sanitizedLines = rawLines.map((line) => sanitizeLine(line, sanitizerState)); |
| 148 | const xctestTypes = collectXCTestTypes(sanitizedLines); |
| 149 | const tests: DiscoveredTestCase[] = []; |
| 150 | const scopeStack: Array<{ typeName?: string; xctestContext: boolean; depth: number }> = []; |
| 151 | let braceDepth = 0; |
| 152 | let pendingAttributes: string[] = []; |
| 153 | let pendingMultilineAttributeIndex: number | null = null; |
| 154 | let pendingAttributeParenthesisDepth = 0; |
| 155 | |
| 156 | sanitizedLines.forEach((sanitizedLine, index) => { |
| 157 | const lineNumber = index + 1; |
| 158 | const line = sanitizedLine.trim(); |
| 159 | |
| 160 | while (scopeStack.length > 0 && braceDepth < scopeStack[scopeStack.length - 1].depth) { |
| 161 | scopeStack.pop(); |
| 162 | } |
| 163 | |
| 164 | let consumedAttributeContinuation = false; |
| 165 | |
| 166 | if (line.startsWith('@')) { |
| 167 | pendingAttributes.push(line); |
| 168 | const parenthesisDepth = countParentheses(line); |
| 169 | if (parenthesisDepth > 0) { |
| 170 | pendingMultilineAttributeIndex = pendingAttributes.length - 1; |
| 171 | pendingAttributeParenthesisDepth = parenthesisDepth; |
| 172 | } else { |
| 173 | pendingMultilineAttributeIndex = null; |
| 174 | pendingAttributeParenthesisDepth = 0; |
| 175 | } |
| 176 | } else if (pendingMultilineAttributeIndex !== null) { |
| 177 | pendingAttributes[pendingMultilineAttributeIndex] = |
| 178 | `${pendingAttributes[pendingMultilineAttributeIndex]} ${line}`; |
| 179 | pendingAttributeParenthesisDepth += countParentheses(line); |
| 180 | consumedAttributeContinuation = true; |
| 181 | if (pendingAttributeParenthesisDepth <= 0) { |
| 182 | pendingMultilineAttributeIndex = null; |
| 183 | pendingAttributeParenthesisDepth = 0; |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | const typeMatch = line.match( |
| 188 | /\b(?:final\s+)?(?:class|struct|actor)\s+([A-Za-z_][A-Za-z0-9_]*)\b(?:\s*:\s*([^{]+))?/, |
| 189 | ); |
| 190 | const extensionMatch = line.match(/\bextension\s+([A-Za-z_][A-Za-z0-9_]*)\b/); |
| 191 | |
| 192 | if (typeMatch && line.includes('{')) { |
| 193 | const typeName = typeMatch[1]; |
| 194 | const inheritanceClause = typeMatch[2] ?? ''; |
no test coverage detected