@@ -163,6 +163,7 @@ class TestSpec(ABC):
163163 suffixes : "list[str]" = [] # suffixes of files to test
164164 per_line = True # Test lines separately one by one.
165165 n_issues = 0 # issue counter
166+ n_disabled = 0 # counter of disabled issues
166167
167168 def file_matches (self , path : str ) -> bool :
168169 """Test whether the path matches the pattern for files to test."""
@@ -175,7 +176,10 @@ def is_disabled(self, line: str, prefix_comment="//") -> bool:
175176 return False
176177 line = line [(line .index (prefix ) + len (prefix )) :] # Strip away part before prefix.
177178 if self .name in line :
178- return True
179+ self .n_disabled += 1
180+ # Look for a comment with a reason for disabling.
181+ if re .search (r" \([\w\s]{3,}\)" , line ):
182+ return True
179183 return False
180184
181185 def test_line (self , line : str ) -> bool :
@@ -225,7 +229,7 @@ class TestIOStream(TestSpec):
225229 """Detect included iostream."""
226230
227231 name = "include-iostream"
228- message = "Including iostream is discouraged . Use O2 logging instead."
232+ message = "Do not include iostream . Use O2 logging instead."
229233 suffixes = [".h" , ".cxx" ]
230234
231235 def test_line (self , line : str ) -> bool :
@@ -238,7 +242,7 @@ class TestUsingStd(TestSpec):
238242 """Detect importing names from the std namespace."""
239243
240244 name = "import-std-name"
241- message = "Importing names from the std namespace is not allowed in headers."
245+ message = "Do not import names from the std namespace in headers."
242246 suffixes = [".h" ]
243247
244248 def test_line (self , line : str ) -> bool :
@@ -251,7 +255,7 @@ class TestUsingDirectives(TestSpec):
251255 """Detect using directives in headers."""
252256
253257 name = "using-directive"
254- message = "Using directives are not allowed in headers."
258+ message = "Do not put using directives at global scope in headers."
255259 suffixes = [".h" ]
256260
257261 def test_line (self , line : str ) -> bool :
@@ -308,7 +312,7 @@ class TestROOT(TestSpec):
308312 """Detect unnecessary use of ROOT entities."""
309313
310314 name = "root-entity"
311- message = "Consider replacing ROOT entities with equivalents from standard C++ or from O2."
315+ message = "Replace ROOT entities with equivalents from standard C++ or from O2."
312316 suffixes = [".h" , ".cxx" ]
313317
314318 def file_matches (self , path : str ) -> bool :
@@ -329,14 +333,14 @@ class TestPi(TestSpec):
329333 """Detect use of external pi."""
330334
331335 name = "external-pi"
332- message = "Consider using the PI constant (and its multiples and fractions) defined in o2::constants::math."
336+ message = "Use the PI constant (and its multiples and fractions) defined in o2::constants::math."
333337 suffixes = [".h" , ".cxx" ]
334338
335339 def file_matches (self , path : str ) -> bool :
336340 return super ().file_matches (path ) and "Macros/" not in path
337341
338342 def test_line (self , line : str ) -> bool :
339- pattern = r"M_PI|TMath::(Two)?Pi"
343+ pattern = r"[^\w] M_PI|TMath::(Two)?Pi"
340344 if is_comment_cpp (line ):
341345 return True
342346 line = remove_comment_cpp (line )
@@ -347,7 +351,7 @@ class TestTwoPiAddSubtract(TestSpec):
347351 """Detect adding/subtracting of 2 pi."""
348352
349353 name = "two-pi-add-subtract"
350- message = "Consider using RecoDecay::constrainAngle to restrict angle to a given range."
354+ message = "Use RecoDecay::constrainAngle to restrict angle to a given range."
351355 suffixes = [".h" , ".cxx" ]
352356
353357 def test_line (self , line : str ) -> bool :
@@ -366,14 +370,14 @@ class TestPiMultipleFraction(TestSpec):
366370 """Detect multiples/fractions of pi for existing equivalent constants."""
367371
368372 name = "pi-multiple-fraction"
369- message = "Consider using multiples/fractions of PI defined in o2::constants::math."
373+ message = "Use multiples/fractions of PI defined in o2::constants::math."
370374 suffixes = [".h" , ".cxx" ]
371375
372376 def test_line (self , line : str ) -> bool :
373377 pattern_pi = r"(M_PI|TMath::(Two)?Pi\(\)|(((o2::)?constants::)?math::)?(Two)?PI)"
374378 pattern_multiple = r"(2(\.0*f?)?|0\.2?5f?) \* " # * 2, 0.25, 0.5
375379 pattern_fraction = r" / ((2|3|4)([ ,;\)]|\.0*f?))" # / 2, 3, 4
376- pattern = rf"{ pattern_multiple } { pattern_pi } |{ pattern_pi } { pattern_fraction } "
380+ pattern = rf"{ pattern_multiple } { pattern_pi } [^\w] |{ pattern_pi } { pattern_fraction } "
377381 if is_comment_cpp (line ):
378382 return True
379383 line = remove_comment_cpp (line )
@@ -385,8 +389,8 @@ class TestPdgDatabase(TestSpec):
385389
386390 name = "pdg/database"
387391 message = (
388- "Direct use of TDatabasePDG is not allowed . "
389- "Use o2::constants::physics::Mass... or Service<o2::framework::O2DatabasePDG>."
392+ "Do not use TDatabasePDG directly . "
393+ "Use o2::constants::physics::Mass... or Service<o2::framework::O2DatabasePDG> instead ."
390394 )
391395 suffixes = [".h" , ".cxx" ]
392396
@@ -413,7 +417,7 @@ def test_line(self, line: str) -> bool:
413417 line = remove_comment_cpp (line )
414418 if re .search (r"->(GetParticle|Mass)\([+-]?[0-9]+\)" , line ):
415419 return False
416- match = re .search (r"[Pp][Dd][Gg][\w]* ={1,2} [+-]?([0-9]+); " , line )
420+ match = re .search (r"[Pp][Dd][Gg].* !? ={1,2} [+-]?([0-9]+)" , line )
417421 if match :
418422 code = match .group (1 )
419423 if code not in ("0" , "1" , "999" ):
@@ -425,9 +429,7 @@ class TestPdgMass(TestSpec):
425429 """Detect unnecessary call of Mass() for a known PDG code."""
426430
427431 name = "pdg/known-mass"
428- message = (
429- "Consider using o2::constants::physics::Mass... instead of calling a database method for a known PDG code."
430- )
432+ message = "Use o2::constants::physics::Mass... instead of calling a database method for a known PDG code."
431433 suffixes = [".h" , ".cxx" , ".C" ]
432434
433435 def test_line (self , line : str ) -> bool :
@@ -446,7 +448,7 @@ class TestLogging(TestSpec):
446448 """Detect non-O2 logging."""
447449
448450 name = "logging"
449- message = "Consider using O2 logging (LOG, LOGF, LOGP)."
451+ message = "Use O2 logging (LOG, LOGF, LOGP)."
450452 suffixes = [".h" , ".cxx" ]
451453
452454 def file_matches (self , path : str ) -> bool :
@@ -694,6 +696,8 @@ def test_line(self, line: str) -> bool:
694696 "namespace" ,
695697 "struct" ,
696698 "class" ,
699+ "explicit" ,
700+ "concept" ,
697701 ):
698702 return True
699703 if len (words ) > 2 and words [1 ] in ("typename" , "class" , "struct" ):
@@ -792,6 +796,8 @@ def test_line(self, line: str) -> bool:
792796 constant_name = constant_name [: constant_name .index ("[" )]
793797 if "::" in constant_name : # Remove the class prefix for methods.
794798 constant_name = constant_name .split ("::" )[- 1 ]
799+ if "#" in constant_name : # Remove "#" for strings in macros.
800+ constant_name = constant_name [: constant_name .index ("#" )]
795801 # The actual test comes here.
796802 if constant_name .startswith ("k" ) and len (constant_name ) > 1 : # exception for special constants
797803 constant_name = constant_name [1 :] # test the name without "k"
@@ -822,6 +828,10 @@ def test_line(self, line: str) -> bool:
822828 # return True
823829 if column_type_name [0 ] == "_" : # probably a macro variable
824830 return True
831+ if "#" in column_type_name : # Remove "#" for strings in macros.
832+ column_type_name = column_type_name [: column_type_name .index ("#" )]
833+ if "#" in column_getter_name : # Remove "#" for strings in macros.
834+ column_getter_name = column_getter_name [: column_getter_name .index ("#" )]
825835 # The actual test comes here.
826836 if not is_upper_camel_case (column_type_name ):
827837 return False
@@ -858,6 +868,8 @@ def test_line(self, line: str) -> bool:
858868 # print(f"Got versioned table \"{table_type_name}\", version {table_version}")
859869 if table_type_name [0 ] == "_" : # probably a macro variable
860870 return True
871+ if "#" in table_type_name : # Remove "#" for strings in macros.
872+ table_type_name = table_type_name [: table_type_name .index ("#" )]
861873 # The actual test comes here.
862874 return is_upper_camel_case (table_type_name )
863875
@@ -888,16 +900,16 @@ class TestNameType(TestSpec):
888900 """Test names of defined types."""
889901
890902 name = "name/type"
891- message = "Use UpperCamelCase for names of defined types."
903+ message = "Use UpperCamelCase for names of defined types (including concepts) ."
892904 suffixes = [".h" , ".cxx" , ".C" ]
893905
894906 def test_line (self , line : str ) -> bool :
895907 if is_comment_cpp (line ):
896908 return True
897- if not (match := re .match (r"using (\w+) = " , line )):
909+ if not (match := re .match (r"( using|concept) (\w+) = " , line )):
898910 return True
899911 # Extract type name.
900- type_name = match .group (1 )
912+ type_name = match .group (2 )
901913 # The actual test comes here.
902914 return is_upper_camel_case (type_name )
903915
@@ -1193,8 +1205,13 @@ def file_matches(self, path: str) -> bool:
11931205 def test_file (self , path : str , content ) -> bool :
11941206 file_name = os .path .basename (path ).rstrip (".cxx" )
11951207 base_struct_name = f"{ file_name [0 ].upper ()} { file_name [1 :]} " # expected base of struct names
1196- if "PWGHF/" in path :
1197- base_struct_name = "Hf" + base_struct_name
1208+ if match := re .search ("PWG([A-Z]{2})/" , path ):
1209+ name_pwg = match .group (1 )
1210+ prefix_pwg = name_pwg .capitalize ()
1211+ if name_pwg in ("HF" ):
1212+ base_struct_name = rf"{ prefix_pwg } { base_struct_name } " # mandatory PWG prefix
1213+ else :
1214+ base_struct_name = rf"({ prefix_pwg } )?{ base_struct_name } " # optional PWG prefix
11981215 # print(f"For file {file_name} expecting to find {base_struct_name}.")
11991216 struct_names = [] # actual struct names in the file
12001217 for line in content :
@@ -1204,13 +1221,13 @@ def test_file(self, path: str, content) -> bool:
12041221 continue
12051222 # Extract struct name.
12061223 words = line .split ()
1207- if not words [1 ].isalnum (): # "struct : ..."
1224+ if not words [1 ].isidentifier (): # "struct : ..."
12081225 continue
12091226 struct_name = words [1 ]
12101227 struct_names .append (struct_name )
12111228 # print(f"Found structs: {struct_names}.")
12121229 for struct_name in struct_names :
1213- if struct_name . startswith (base_struct_name ):
1230+ if re . match (base_struct_name , struct_name ):
12141231 return True
12151232 return False
12161233
@@ -1486,9 +1503,12 @@ def main():
14861503 # Report results per test.
14871504 print ("\n Results per test" )
14881505 len_max = max (len (name ) for name in test_names )
1489- print (f"test{ ' ' * (len_max - len ('test' ))} \t issues\t bad files" )
1506+ print (f"test{ ' ' * (len_max - len ('test' ))} \t issues\t disabled \ t bad files" )
14901507 for test in tests :
1491- print (f"{ test .name } { ' ' * (len_max - len (test .name ))} \t { test .n_issues } \t { n_files_bad [test .name ]} " )
1508+ print (
1509+ f"{ test .name } { ' ' * (len_max - len (test .name ))} \t { test .n_issues } \t { test .n_disabled } "
1510+ f"\t \t { n_files_bad [test .name ]} "
1511+ )
14921512
14931513 # Report global result.
14941514 title_result = "O2 linter result"
@@ -1501,8 +1521,8 @@ def main():
15011521 else :
15021522 msg_result = "Issues have been found."
15031523 msg_disable = (
1504- f'You can disable a test for a line by adding a comment with "{ prefix_disable } "'
1505- " followed by the name of the test."
1524+ f'Exceptionally, you can disable a test for a line by adding a comment with "{ prefix_disable } "'
1525+ " followed by the name of the test and parentheses with a reason for the exception ."
15061526 )
15071527 if github_mode :
15081528 print (f"::error title={ title_result } ::{ msg_result } " )
0 commit comments