33
44from __future__ import annotations
55
6+ import argparse
67import json
78import os
89import re
3233
3334
3435def main () -> int :
36+ args = parse_args ()
3537 latest_version = get_latest_wordpress_major_minor ()
3638 excluded_dirs = get_excluded_dirs ()
3739 findings = find_tested_up_to_entries (excluded_dirs )
@@ -42,41 +44,54 @@ def main() -> int:
4244 write_summary (latest_version , [], [message ])
4345 return 1
4446
45- failures = []
46- for finding in findings :
47- if finding ["version" ] is None :
48- failures .append (
49- f"{ finding ['path' ]} :{ finding ['line' ]} : Could not parse Tested up to version."
50- )
51- print_github_error (
52- "Could not parse Tested up to version." ,
53- finding ["path" ],
54- finding ["line" ],
55- )
56- continue
47+ failures = get_failures (latest_version , findings )
48+ updated_paths = []
5749
58- tested_version = normalize_major_minor ( finding [ "version" ])
59- if tested_version != latest_version :
60- message = (
61- f"Tested up to is { finding [ 'version' ] } ; expected { latest_version } "
62- "for the latest WordPress release."
63- )
64- failures . append ( f" { finding [ 'path' ] } : { finding [ 'line' ] } : { message } " )
65- print_github_error (message , finding ["path" ], finding ["line" ])
50+ if args . fix and failures :
51+ updated_paths = update_tested_up_to_entries ( findings , latest_version )
52+ findings = find_tested_up_to_entries ( excluded_dirs )
53+ failures = get_failures ( latest_version , findings )
54+
55+ if failures :
56+ for failure in failures :
57+ print_github_error (failure [ " message" ], failure ["path" ], failure ["line" ])
6658
67- write_summary (latest_version , findings , failures )
59+ write_summary (latest_version , findings , failures , updated_paths )
6860
6961 if failures :
7062 entry_label = "entry" if len (failures ) == 1 else "entries"
7163 print (f"Found { len (failures )} stale or invalid Tested up to { entry_label } ." )
7264 for failure in failures :
73- print (f"- { failure } " )
65+ print (f"- { format_failure ( failure ) } " )
7466 return 1
7567
68+ if updated_paths :
69+ path_label = "file" if len (updated_paths ) == 1 else "files"
70+ print (
71+ f"Updated Tested up to metadata to WordPress { latest_version } "
72+ f"in { len (updated_paths )} { path_label } ."
73+ )
74+ for path in updated_paths :
75+ print (f"- { path } " )
76+ return 0
77+
7678 print (f"All Tested up to entries match WordPress { latest_version } ." )
7779 return 0
7880
7981
82+ def parse_args () -> argparse .Namespace :
83+ parser = argparse .ArgumentParser (
84+ description = 'Check WordPress "Tested up to" metadata against the latest release.'
85+ )
86+ parser .add_argument (
87+ "--fix" ,
88+ action = "store_true" ,
89+ help = "Update stale or invalid Tested up to entries to the latest WordPress release." ,
90+ )
91+
92+ return parser .parse_args ()
93+
94+
8095def get_latest_wordpress_major_minor () -> str :
8196 if WORDPRESS_LATEST_VERSION :
8297 return normalize_major_minor (WORDPRESS_LATEST_VERSION )
@@ -175,10 +190,100 @@ def should_scan(path: Path, excluded_dirs: set[str]) -> bool:
175190 return not any (part in excluded_dirs for part in path .parts )
176191
177192
193+ def get_failures (
194+ latest_version : str ,
195+ findings : list [dict [str , str | int | None ]],
196+ ) -> list [dict [str , str | int ]]:
197+ failures = []
198+
199+ for finding in findings :
200+ if finding ["version" ] is None :
201+ failures .append (
202+ {
203+ "path" : str (finding ["path" ]),
204+ "line" : int (finding ["line" ]),
205+ "message" : "Could not parse Tested up to version." ,
206+ }
207+ )
208+ continue
209+
210+ tested_version = normalize_major_minor (str (finding ["version" ]))
211+ if tested_version != latest_version :
212+ failures .append (
213+ {
214+ "path" : str (finding ["path" ]),
215+ "line" : int (finding ["line" ]),
216+ "message" : (
217+ f"Tested up to is { finding ['version' ]} ; expected "
218+ f"{ latest_version } for the latest WordPress release."
219+ ),
220+ }
221+ )
222+
223+ return failures
224+
225+
226+ def update_tested_up_to_entries (
227+ findings : list [dict [str , str | int | None ]],
228+ latest_version : str ,
229+ ) -> list [str ]:
230+ paths_to_update = {
231+ str (finding ["path" ])
232+ for finding in findings
233+ if finding ["version" ] is None
234+ or normalize_major_minor (str (finding ["version" ])) != latest_version
235+ }
236+
237+ for path_string in paths_to_update :
238+ path_findings = [
239+ finding for finding in findings if str (finding ["path" ]) == path_string
240+ ]
241+ path = Path (path_string )
242+ lines = path .read_text (encoding = "utf-8" , errors = "replace" ).splitlines (
243+ keepends = True
244+ )
245+
246+ for finding in path_findings :
247+ line_index = int (finding ["line" ]) - 1
248+ lines [line_index ] = replace_tested_up_to_line (
249+ lines [line_index ], latest_version
250+ )
251+
252+ path .write_text ("" .join (lines ), encoding = "utf-8" )
253+
254+ return sorted (paths_to_update )
255+
256+
257+ def replace_tested_up_to_line (line : str , latest_version : str ) -> str :
258+ match = TESTED_UP_TO_PATTERN .search (line )
259+ if match :
260+ return f"{ line [:match .start (1 )]} { latest_version } { line [match .end (1 ):]} "
261+
262+ label_match = TESTED_UP_TO_LABEL_PATTERN .search (line )
263+ if not label_match :
264+ return line
265+
266+ line_ending = ""
267+ content = line
268+ if line .endswith ("\r \n " ):
269+ content = line [:- 2 ]
270+ line_ending = "\r \n "
271+ elif line .endswith ("\n " ):
272+ content = line [:- 1 ]
273+ line_ending = "\n "
274+
275+ return f"{ content [:label_match .end ()]} { latest_version } { line_ending } "
276+
277+
278+ def format_failure (failure : dict [str , str | int ]) -> str :
279+ return f"{ failure ['path' ]} :{ failure ['line' ]} : { failure ['message' ]} "
280+
281+
178282def write_summary (
179283 latest_version : str ,
180284 findings : list [dict [str , str | int | None ]],
181- failures : list [str ],
285+ failures : list [dict [str , str | int ] | str ],
286+ updated_paths : list [str ] | None = None ,
182287) -> None :
183288 summary_path = os .environ .get ("GITHUB_STEP_SUMMARY" )
184289 if not summary_path :
@@ -194,7 +299,14 @@ def write_summary(
194299
195300 if failures :
196301 lines .append ("## Failures" )
197- lines .extend (f"- { failure } " for failure in failures )
302+ lines .extend (
303+ f"- { format_failure (failure ) if isinstance (failure , dict ) else failure } "
304+ for failure in failures
305+ )
306+ elif updated_paths :
307+ lines .append ("## Updates" )
308+ lines .append (f"Updated Tested up to metadata in `{ len (updated_paths )} ` file(s)." )
309+ lines .extend (f"- `{ path } `" for path in updated_paths )
198310 else :
199311 lines .append ("All Tested up to entries are current." )
200312
0 commit comments