1+ #! /bin/bash
2+
3+ set -e
4+
5+ # Colors for output
6+ RED=' \033[0;31m'
7+ YELLOW=' \033[1;33m'
8+ GREEN=' \033[0;32m'
9+ BLUE=' \033[0;34m'
10+ NC=' \033[0m' # No Color
11+
12+ SOURCES_DIR=" Sources"
13+ FORMAT=" text"
14+ FAIL_ON_MISSING=false
15+ THRESHOLD=100.0
16+
17+ print_usage () {
18+ echo " Usage: api-coverage.sh [OPTIONS]"
19+ echo " "
20+ echo " Options:"
21+ echo " --sources-dir PATH Path to sources directory (default: Sources)"
22+ echo " --format FORMAT Output format: text, json (default: text)"
23+ echo " --fail-on-missing Exit with error code if any APIs lack documentation"
24+ echo " --threshold PERCENT Minimum coverage threshold (0-100, default: 100)"
25+ echo " --help Show this help message"
26+ }
27+
28+ # Parse command line arguments
29+ while [[ $# -gt 0 ]]; do
30+ case $1 in
31+ --sources-dir)
32+ SOURCES_DIR=" $2 "
33+ shift 2
34+ ;;
35+ --format)
36+ FORMAT=" $2 "
37+ if [[ ! " $FORMAT " =~ ^(text| json)$ ]]; then
38+ echo " Error: format must be 'text' or 'json'"
39+ exit 1
40+ fi
41+ shift 2
42+ ;;
43+ --fail-on-missing)
44+ FAIL_ON_MISSING=true
45+ shift
46+ ;;
47+ --threshold)
48+ THRESHOLD=" $2 "
49+ shift 2
50+ ;;
51+ --help)
52+ print_usage
53+ exit 0
54+ ;;
55+ * )
56+ echo " Error: Unknown option $1 "
57+ print_usage
58+ exit 1
59+ ;;
60+ esac
61+ done
62+
63+ # Function to check if a line contains documentation comment
64+ has_documentation () {
65+ local line=" $1 "
66+ [[ " $line " =~ ^[[:space:]]* /// || " $line " =~ ^[[:space:]]* \/\*\* ]]
67+ }
68+
69+ # Function to extract public API declarations
70+ analyze_swift_file () {
71+ local file=" $1 "
72+ local undocumented_apis=()
73+ local total_apis=0
74+ local documented_apis=0
75+
76+ # Read file line by line
77+ local line_num=0
78+ local prev_line=" "
79+ local has_doc=false
80+
81+ while IFS= read -r line; do
82+ (( line_num++ ))
83+
84+ # Check if previous line had documentation
85+ if has_documentation " $prev_line " ; then
86+ has_doc=true
87+ elif [[ " $prev_line " =~ ^[[:space:]]* $ ]]; then
88+ # Empty line, keep current documentation status
89+ :
90+ elif [[ ! " $prev_line " =~ ^[[:space:]]* // ]]; then
91+ # Non-comment, non-empty line resets documentation status
92+ has_doc=false
93+ fi
94+
95+ # Check for public API declarations
96+ if [[ " $line " =~ ^[[:space:]]* public[[:space:]]+ (struct| class| enum| protocol| func| var| let| init| typealias) ]]; then
97+ (( total_apis++ ))
98+
99+ # Extract API info
100+ local api_type
101+ if [[ " $line " =~ public[[:space:]]+ (struct| class| enum| protocol| func| var| let| init| typealias) ]]; then
102+ api_type=" ${BASH_REMATCH[1]} "
103+ fi
104+
105+ local api_name
106+ case " $api_type " in
107+ struct|class|enum|protocol|typealias)
108+ api_name=$( echo " $line " | sed -E ' s/.*public[[:space:]]+(struct|class|enum|protocol|typealias)[[:space:]]+([^[:space:]{<(]+).*/\2/' )
109+ ;;
110+ func)
111+ api_name=$( echo " $line " | sed -E ' s/.*func[[:space:]]+([^[:space:](]+).*/\1/' )
112+ ;;
113+ var|let)
114+ api_name=$( echo " $line " | sed -E ' s/.*public[[:space:]]+(var|let)[[:space:]]+([^[:space:]:=]+).*/\2/' )
115+ ;;
116+ init)
117+ api_name=" init"
118+ ;;
119+ esac
120+
121+ if [[ " $has_doc " == true ]]; then
122+ (( documented_apis++ ))
123+ else
124+ undocumented_apis+=(" $file :$line_num - $api_type $api_name " )
125+ fi
126+ fi
127+
128+ prev_line=" $line "
129+ done < " $file "
130+
131+ # Return results via global variables (bash limitations)
132+ echo " $total_apis ,$documented_apis ,$( IFS=' |' ; echo " ${undocumented_apis[*]} " ) "
133+ }
134+
135+ main () {
136+ local total_apis=0
137+ local documented_apis=0
138+ local all_undocumented=()
139+
140+ # Find all Swift files
141+ while IFS= read -r -d ' ' file; do
142+ local result
143+ result=$( analyze_swift_file " $file " )
144+
145+ local file_total file_documented file_undocumented
146+ IFS=' ,' read -r file_total file_documented file_undocumented <<< " $result"
147+
148+ total_apis=$(( total_apis + file_total))
149+ documented_apis=$(( documented_apis + file_documented))
150+
151+ if [[ -n " $file_undocumented " ]]; then
152+ IFS=' |' read -ra undoc_array <<< " $file_undocumented"
153+ all_undocumented+=(" ${undoc_array[@]} " )
154+ fi
155+
156+ done < <( find " $SOURCES_DIR " -name " *.swift" -type f -print0)
157+
158+ # Calculate coverage percentage
159+ local coverage=0
160+ if [[ $total_apis -gt 0 ]]; then
161+ coverage=$( echo " scale=1; $documented_apis * 100.0 / $total_apis " | bc -l 2> /dev/null || echo " 0" )
162+ else
163+ coverage=100.0
164+ fi
165+
166+ # Output results
167+ case " $FORMAT " in
168+ json)
169+ echo " {"
170+ echo " \" totalAPIs\" : $total_apis ,"
171+ echo " \" documentedAPIs\" : $documented_apis ,"
172+ echo " \" coveragePercentage\" : $coverage ,"
173+ echo " \" undocumentedAPIs\" : ["
174+ local first=true
175+ for api in " ${all_undocumented[@]} " ; do
176+ if [[ " $first " == true ]]; then
177+ first=false
178+ else
179+ echo " ,"
180+ fi
181+ local file_line api_info
182+ file_line=" ${api% - * } "
183+ api_info=" ${api#* - } "
184+ local file_path line_num
185+ file_path=" ${file_line%:* } "
186+ line_num=" ${file_line#*: } "
187+ local api_type api_name
188+ api_type=" ${api_info% * } "
189+ api_name=" ${api_info#* } "
190+ echo -n " {\" name\" : \" $api_name \" , \" type\" : \" $api_type \" , \" filePath\" : \" $file_path \" , \" line\" : $line_num }"
191+ done
192+ if [[ ${# all_undocumented[@]} -gt 0 ]]; then
193+ echo " "
194+ fi
195+ echo " ]"
196+ echo " }"
197+ ;;
198+ * )
199+ echo -e " ${BLUE} 🔍 SyntaxKit API Documentation Coverage Report${NC} "
200+ echo " ════════════════════════════════════════════"
201+ echo " "
202+ echo -e " ${BLUE} 📊 Coverage Summary:${NC} "
203+ echo " Total public APIs: $total_apis "
204+ echo " Documented APIs: $documented_apis "
205+ echo " Coverage: ${coverage} %"
206+ echo " "
207+
208+ if [[ ${# all_undocumented[@]} -gt 0 ]]; then
209+ echo -e " ${RED} ❌ Missing Documentation:${NC} "
210+ printf ' %s\n' " ${all_undocumented[@]} " | sort
211+ echo " "
212+ fi
213+
214+ if (( $(echo "$coverage >= $THRESHOLD " | bc - l) )) ; then
215+ echo -e " ${GREEN} ✅ Coverage threshold met (${THRESHOLD} %)${NC} "
216+ else
217+ echo -e " ${RED} ❌ Coverage below threshold (${THRESHOLD} %)${NC} "
218+ fi
219+ ;;
220+ esac
221+
222+ # Exit with appropriate code
223+ local should_fail=false
224+ if [[ " $FAIL_ON_MISSING " == true && ${# all_undocumented[@]} -gt 0 ]]; then
225+ should_fail=true
226+ fi
227+ if (( $(echo "$coverage < $THRESHOLD " | bc - l) )) ; then
228+ should_fail=true
229+ fi
230+
231+ if [[ " $should_fail " == true ]]; then
232+ exit 1
233+ else
234+ exit 0
235+ fi
236+ }
237+
238+ main " $@ "
0 commit comments