@@ -47,33 +47,39 @@ class Severity(Enum):
4747
4848
4949class Reference (Enum ):
50- O2 = 1
51- ISO_CPP = 2
52- LLVM = 3
53- GOOGLE = 4
54- LINTER = 5
55- PWG_HF = 6
56- PY_ZEN = 7
57- PY_PEP8 = 8
50+ ISO_CPP = 1
51+ LLVM = 2
52+ GOOGLE = 3
53+ PY_ZEN = 4
54+ PY_PEP8 = 5
55+ O2 = 6
56+ PWG_HF = 7
57+ LINTER_1 = 8
58+ LINTER_2 = 9
5859
5960
6061references_list : "list[tuple[Reference, str, str]]" = [
61- (Reference .O2 , "ALICE O2 Coding Guidelines" , "https://github.com/AliceO2Group/CodingGuidelines" ),
6262 (Reference .ISO_CPP , "C++ Core Guidelines" , "https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines" ),
6363 (Reference .LLVM , "LLVM Coding Standards" , "https://llvm.org/docs/CodingStandards.html" ),
6464 (Reference .GOOGLE , "Google C++ Style Guide" , "https://google.github.io/styleguide/cppguide.html" ),
65+ (Reference .PY_ZEN , "The Zen of Python" , "https://peps.python.org/pep-0020/" ),
66+ (Reference .PY_PEP8 , "Style Guide for Python Code" , "https://peps.python.org/pep-0008/" ),
67+ (Reference .O2 , "ALICE O2 Coding Guidelines" , "https://github.com/AliceO2Group/CodingGuidelines" ),
68+ (
69+ Reference .PWG_HF ,
70+ "PWG-HF guidelines" ,
71+ "https://aliceo2group.github.io/analysis-framework/docs/advanced-specifics/pwghf.html#contribute" ,
72+ ),
6573 (
66- Reference .LINTER ,
74+ Reference .LINTER_1 ,
6775 "Proposal of the O2 linter" ,
6876 "https://indico.cern.ch/event/1482467/#29-development-of-the-o2-linte" ,
6977 ),
7078 (
71- Reference .PWG_HF ,
72- "PWG-HF guidelines " ,
73- "https://aliceo2group.github.io/analysis-framework/docs/advanced-specifics/pwghf.html#contribute " ,
79+ Reference .LINTER_2 ,
80+ "Update of the O2 linter " ,
81+ "https://indico.cern.ch/event/1513748/#29-o2-linter-development " ,
7482 ),
75- (Reference .PY_ZEN , "The Zen of Python" , "https://peps.python.org/pep-0020/" ),
76- (Reference .PY_PEP8 , "Style Guide for Python Code" , "https://peps.python.org/pep-0008/" ),
7783]
7884
7985references : "dict[Reference, dict]" = {name : {"title" : title , "url" : url } for name , title , url in references_list }
@@ -312,7 +318,7 @@ class TestIoStream(TestSpec):
312318 name = "include-iostream"
313319 message = "Do not include iostream. Use O2 logging instead."
314320 rationale = "Performance. Avoid injection of static constructors. Consistent logging."
315- references = [Reference .LLVM , Reference .LINTER ]
321+ references = [Reference .LLVM , Reference .LINTER_1 ]
316322 suffixes = [".h" , ".cxx" ]
317323
318324 def test_line (self , line : str ) -> bool :
@@ -327,7 +333,7 @@ class TestUsingStd(TestSpec):
327333 name = "import-std-name"
328334 message = "Do not import names from the std namespace in headers."
329335 rationale = "Code safety. Avoid namespace pollution with common names."
330- references = [Reference .LINTER ]
336+ references = [Reference .LINTER_1 ]
331337 suffixes = [".h" ]
332338
333339 def test_line (self , line : str ) -> bool :
@@ -342,7 +348,7 @@ class TestUsingDirective(TestSpec):
342348 name = "using-directive"
343349 message = "Do not put using directives at global scope in headers."
344350 rationale = "Code safety. Avoid namespace pollution."
345- references = [Reference .O2 , Reference .ISO_CPP , Reference .LLVM , Reference .GOOGLE , Reference .LINTER ]
351+ references = [Reference .O2 , Reference .ISO_CPP , Reference .LLVM , Reference .GOOGLE , Reference .LINTER_1 ]
346352 suffixes = [".h" ]
347353
348354 def test_line (self , line : str ) -> bool :
@@ -357,7 +363,7 @@ class TestStdPrefix(TestSpec):
357363 name = "std-prefix"
358364 message = "Use std:: prefix for names from the std namespace."
359365 rationale = "Code clarity, safety and portability. Avoid ambiguity (e.g. abs)."
360- references = [Reference .LLVM , Reference .LINTER ]
366+ references = [Reference .LLVM , Reference .LINTER_1 ]
361367 suffixes = [".h" , ".cxx" , ".C" ]
362368 prefix_bad = r"[^\w:\.\"]"
363369 patterns = [
@@ -401,7 +407,7 @@ class TestRootEntity(TestSpec):
401407 name = "root/entity"
402408 message = "Replace ROOT entities with equivalents from standard C++ or from O2."
403409 rationale = "Code simplicity and maintainability. O2 is not a ROOT code."
404- references = [Reference .ISO_CPP , Reference .LINTER , Reference .PY_ZEN ]
410+ references = [Reference .ISO_CPP , Reference .LINTER_1 , Reference .PY_ZEN ]
405411 suffixes = [".h" , ".cxx" ]
406412
407413 def file_matches (self , path : str ) -> bool :
@@ -427,7 +433,7 @@ class TestRootLorentzVector(TestSpec):
427433 "Use std::array with RecoDecay methods or the ROOT::Math::LorentzVector template instead."
428434 )
429435 rationale = "Performance. Use up-to-date tools."
430- references = []
436+ references = [Reference . LINTER_2 ]
431437 suffixes = [".h" , ".cxx" ]
432438
433439 def test_line (self , line : str ) -> bool :
@@ -443,7 +449,7 @@ class TestPi(TestSpec):
443449 name = "external-pi"
444450 message = "Use the PI constant (and its multiples and fractions) defined in o2::constants::math."
445451 rationale = "Code maintainability."
446- references = [Reference .LINTER , Reference .PY_ZEN ]
452+ references = [Reference .LINTER_1 , Reference .PY_ZEN ]
447453 suffixes = [".h" , ".cxx" ]
448454
449455 def file_matches (self , path : str ) -> bool :
@@ -463,7 +469,7 @@ class TestTwoPiAddSubtract(TestSpec):
463469 name = "two-pi-add-subtract"
464470 message = "Use RecoDecay::constrainAngle to restrict angle to a given range."
465471 rationale = "Code maintainability and safety. Use existing tools."
466- references = [Reference .ISO_CPP , Reference .LINTER , Reference .PY_ZEN ]
472+ references = [Reference .ISO_CPP , Reference .LINTER_1 , Reference .PY_ZEN ]
467473 suffixes = [".h" , ".cxx" ]
468474
469475 def test_line (self , line : str ) -> bool :
@@ -484,7 +490,7 @@ class TestPiMultipleFraction(TestSpec):
484490 name = "pi-multiple-fraction"
485491 message = "Use multiples/fractions of PI defined in o2::constants::math."
486492 rationale = "Code maintainability."
487- references = [Reference .LINTER , Reference .PY_ZEN ]
493+ references = [Reference .LINTER_1 , Reference .PY_ZEN ]
488494 suffixes = [".h" , ".cxx" ]
489495
490496 def test_line (self , line : str ) -> bool :
@@ -507,7 +513,7 @@ class TestPdgDatabase(TestSpec):
507513 "Use o2::constants::physics::Mass... or Service<o2::framework::O2DatabasePDG> instead."
508514 )
509515 rationale = "Performance."
510- references = [Reference .LINTER ]
516+ references = [Reference .LINTER_1 ]
511517 suffixes = [".h" , ".cxx" ]
512518
513519 def file_matches (self , path : str ) -> bool :
@@ -526,7 +532,7 @@ class TestPdgExplicitCode(TestSpec):
526532 name = "pdg/explicit-code"
527533 message = "Avoid hard-coded PDG codes. Use named values from PDG_t or o2::constants::physics::Pdg instead."
528534 rationale = "Code comprehensibility, readability, maintainability and safety."
529- references = [Reference .O2 , Reference .ISO_CPP , Reference .LINTER ]
535+ references = [Reference .O2 , Reference .ISO_CPP , Reference .LINTER_1 ]
530536 suffixes = [".h" , ".cxx" , ".C" ]
531537
532538 def test_line (self , line : str ) -> bool :
@@ -549,7 +555,7 @@ class TestPdgExplicitMass(TestSpec):
549555 name = "pdg/explicit-mass"
550556 message = "Avoid hard-coded particle masses. Use o2::constants::physics::Mass... instead."
551557 rationale = "Code comprehensibility, readability, maintainability and safety."
552- references = [Reference .O2 , Reference .ISO_CPP , Reference .LINTER ]
558+ references = [Reference .O2 , Reference .ISO_CPP , Reference .LINTER_2 ]
553559 suffixes = [".h" , ".cxx" ]
554560 masses : "list[str]" = [] # list of mass values to detect
555561
@@ -593,7 +599,7 @@ class TestPdgKnownMass(TestSpec):
593599 name = "pdg/known-mass"
594600 message = "Use o2::constants::physics::Mass... instead of calling a database method for a known PDG code."
595601 rationale = "Performance."
596- references = [Reference .LINTER ]
602+ references = [Reference .LINTER_1 ]
597603 suffixes = [".h" , ".cxx" , ".C" ]
598604
599605 def test_line (self , line : str ) -> bool :
@@ -612,7 +618,7 @@ class TestLogging(TestSpec):
612618 name = "logging"
613619 message = "Use O2 logging (LOG, LOGF, LOGP)."
614620 rationale = "Logs easy to read and process."
615- references = [Reference .LINTER ]
621+ references = [Reference .LINTER_1 ]
616622 suffixes = [".h" , ".cxx" ]
617623
618624 def file_matches (self , path : str ) -> bool :
@@ -653,7 +659,7 @@ class TestConstRefInSubscription(TestSpec):
653659 name = "const-ref-in-process"
654660 message = "Use constant references for table subscriptions in process functions."
655661 rationale = "Performance, code comprehensibility and safety."
656- references = [Reference .O2 , Reference .ISO_CPP , Reference .LINTER ]
662+ references = [Reference .O2 , Reference .ISO_CPP , Reference .LINTER_1 ]
657663 suffixes = [".cxx" ]
658664 per_line = False
659665
@@ -731,7 +737,7 @@ class TestWorkflowOptions(TestSpec):
731737 "Use process function switches or metadata instead."
732738 )
733739 rationale = "Not supported on AliHyperloop."
734- references = [Reference .LINTER ]
740+ references = [Reference .LINTER_1 ]
735741 suffixes = [".cxx" ]
736742 per_line = False
737743
@@ -767,10 +773,10 @@ class TestMagicNumber(TestSpec):
767773 name = "magic-number"
768774 message = "Avoid magic numbers in expressions. Assign the value to a clearly named variable or constant."
769775 rationale = "Code comprehensibility, maintainability and safety."
770- references = [Reference .O2 , Reference .ISO_CPP ]
776+ references = [Reference .O2 , Reference .ISO_CPP , Reference . LINTER_2 ]
771777 suffixes = [".h" , ".cxx" , ".C" ]
772778 pattern_compare = r"([<>]=?|[!=]=)"
773- pattern_number = r"[\+-]?([\d\.]+)f?"
779+ pattern_number = r"[\+-]?([\d\.]+(e[\+-]?\d+)? )f?"
774780
775781 def test_line (self , line : str ) -> bool :
776782 if is_comment_cpp (line ):
@@ -779,7 +785,7 @@ def test_line(self, line: str) -> bool:
779785 iterators = re .finditer (
780786 rf" { self .pattern_compare } { self .pattern_number } |\W{ self .pattern_number } { self .pattern_compare } " , line
781787 )
782- matches = [(it .start (), it .group (2 ), it .group (3 )) for it in iterators ]
788+ matches = [(it .start (), it .group (2 ), it .group (4 )) for it in iterators ]
783789 if not matches :
784790 return True
785791 # Ignore matches inside strings.
@@ -805,7 +811,7 @@ class TestDocumentationFile(TestSpec):
805811 name = "doc/file"
806812 message = "Provide mandatory file documentation."
807813 rationale = "Code comprehensibility. Collaboration."
808- references = [Reference .O2 , Reference .LINTER ]
814+ references = [Reference .O2 , Reference .LINTER_1 ]
809815 suffixes = [".h" , ".cxx" , ".C" ]
810816 per_line = False
811817
@@ -849,7 +855,7 @@ def test_file(self, path: str, content) -> bool:
849855# Reference: https://rawgit.com/AliceO2Group/CodingGuidelines/master/naming_formatting.html
850856
851857rationale_names = "Code readability, comprehensibility and searchability."
852- references_names = [Reference .O2 , Reference .LINTER ]
858+ references_names = [Reference .O2 , Reference .LINTER_1 ]
853859
854860
855861class TestNameFunctionVariable (TestSpec ):
@@ -989,29 +995,23 @@ class TestNameConstant(TestSpec):
989995 references = references_names
990996 suffixes = [".h" , ".cxx" , ".C" ]
991997
998+ def __init__ (self ) -> None :
999+ super ().__init__ ()
1000+ keyword = r"(.+ )" # e.g. "static "
1001+ type_val = r"([\w:<>+\-*\/, ]+ )" # value type e.g. "std::array<Type, n + 1> "
1002+ prefix = r"(\w+::)" # prefix with namespace or class, e.g. "MyClass::"
1003+ name_val = r"(\w+)" # name of the constant
1004+ array = r"(\[.*\])" # array declaration: "[...]"
1005+ assignment = r"( =|\(\d|{)" # value assignment, e.g. " = 2", " = expression", "(2)", "{2}", "{{...}}"
1006+ self .pattern = re .compile (rf"{ keyword } ?constexpr { type_val } ?{ prefix } *{ name_val } { array } ?{ assignment } " )
1007+
9921008 def test_line (self , line : str ) -> bool :
9931009 if is_comment_cpp (line ):
9941010 return True
9951011 line = remove_comment_cpp (line )
996- words = line .split ()
997- if "constexpr" not in words or "=" not in words :
1012+ if not (match := self .pattern .match (line )):
9981013 return True
999- # Extract constant name.
1000- words = words [: words .index ("=" )] # keep only words before "="
1001- constant_name = words [- 1 ] # last word before "="
1002- if (
1003- constant_name .endswith ("]" ) and "[" not in constant_name
1004- ): # it's an array and we do not have the name before "[" here
1005- opens_brackets = ["[" in w for w in words ]
1006- if not any (opens_brackets ): # The opening "[" is not on this line. We have to give up.
1007- return True
1008- constant_name = words [opens_brackets .index (True )] # the name is in the first element with "["
1009- if "[" in constant_name : # Remove brackets for arrays.
1010- constant_name = constant_name [: constant_name .index ("[" )]
1011- if "::" in constant_name : # Remove the class prefix for methods.
1012- constant_name = constant_name .split ("::" )[- 1 ]
1013- if "#" in constant_name : # Remove "#" for strings in macros.
1014- constant_name = constant_name [: constant_name .index ("#" )]
1014+ constant_name = match .group (4 )
10151015 # The actual test comes here.
10161016 if constant_name .startswith ("k" ) and len (constant_name ) > 1 : # exception for special constants
10171017 constant_name = constant_name [1 :] # test the name without "k"
@@ -1149,15 +1149,10 @@ class TestNameUpperCamelCase(TestSpec):
11491149 def test_line (self , line : str ) -> bool :
11501150 if is_comment_cpp (line ):
11511151 return True
1152- if not line . startswith ( f "{ self .keyword } " ):
1152+ if not ( match := re . match ( rf "{ self .keyword } ( (class|struct))? (\w+)" , line ) ):
11531153 return True
11541154 # Extract object name.
1155- words = line .split ()
1156- if not words [1 ].isalnum (): # "struct : ...", "enum { ..."
1157- return True
1158- object_name = words [1 ]
1159- if object_name in ("class" , "struct" ) and len (words ) > 2 : # enum class ... or enum struct
1160- object_name = words [2 ]
1155+ object_name = match .group (3 )
11611156 # The actual test comes here.
11621157 return is_upper_camel_case (object_name )
11631158
@@ -1216,7 +1211,7 @@ class TestNameFilePython(TestSpec):
12161211 name = "name/file-python"
12171212 message = "Use snake_case for names of Python files."
12181213 rationale = rationale_names
1219- references = [Reference .LINTER , Reference .PY_PEP8 ]
1214+ references = [Reference .LINTER_1 , Reference .PY_PEP8 ]
12201215 suffixes = [".py" , ".ipynb" ]
12211216 per_line = False
12221217
@@ -1278,7 +1273,7 @@ class TestNameTask(TestSpec):
12781273 name = "name/o2-task"
12791274 message = "Specify task name only when it cannot be derived from the struct name. Only append to the default name."
12801275 rationale = f"{ rationale_names } Correspondence struct ↔ device."
1281- references = [Reference .LINTER ]
1276+ references = [Reference .LINTER_1 ]
12821277 suffixes = [".cxx" ]
12831278 per_line = False
12841279
@@ -1423,7 +1418,7 @@ class TestNameFileWorkflow(TestSpec):
14231418 '(Class implementation files should be in "Core" directories.)'
14241419 )
14251420 rationale = f"{ rationale_names } Correspondence file ↔ struct."
1426- references = [Reference .LINTER ]
1421+ references = [Reference .LINTER_1 ]
14271422 suffixes = [".cxx" ]
14281423 per_line = False
14291424
@@ -1466,7 +1461,7 @@ class TestNameConfigurable(TestSpec):
14661461 "for the struct member as for the JSON string. (Declare the type and names on the same line.)"
14671462 )
14681463 rationale = f"{ rationale_names } Correspondence C++ code ↔ JSON."
1469- references = [Reference .O2 , Reference .LINTER ]
1464+ references = [Reference .O2 , Reference .LINTER_1 ]
14701465 suffixes = [".h" , ".cxx" ]
14711466
14721467 def file_matches (self , path : str ) -> bool :
@@ -1475,33 +1470,21 @@ def file_matches(self, path: str) -> bool:
14751470 def test_line (self , line : str ) -> bool :
14761471 if is_comment_cpp (line ):
14771472 return True
1478- if not line . startswith ( " Configurable" ):
1479- return True
1473+ if not ( match := re . match ( r"((o2::)?framework::)? Configurable(\w+|<.+>) (\w+)( = )?{([^,{]+)," , line ) ):
1474+ return not re . match ( r"((o2::)?framework::)?Configurable" , line )
14801475 # Extract Configurable name.
1481- words = line .split ()
1482- if len (words ) < 2 :
1483- return False
1484- if len (words ) > 2 and words [2 ] == "=" : # expecting Configurable... nameCpp = {"nameJson",
1485- name_cpp = words [1 ] # nameCpp
1486- name_json = words [3 ][1 :] # expecting "nameJson",
1487- else :
1488- names = words [1 ].split ("{" ) # expecting Configurable... nameCpp{"nameJson",
1489- if len (names ) < 2 :
1490- return False
1491- name_cpp = names [0 ] # nameCpp
1492- name_json = names [1 ] # expecting "nameJson",
1493- if not name_json :
1494- return False
1476+ name_cpp = match .group (4 ) # nameCpp
1477+ name_json = match .group (6 ) # expecting "nameJson"
14951478 if name_json [0 ] != '"' : # JSON name is not a literal string.
14961479 return True
1497- name_json = name_json . strip ( '",' ) # expecting nameJson
1480+ name_json = name_json [ 1 : - 1 ] # Strip away quotation marks.
14981481 # The actual test comes here.
14991482 return is_lower_camel_case (name_cpp ) and name_cpp == name_json
15001483
15011484
15021485# PWG-HF
15031486
1504- references_hf = [Reference .LINTER , Reference .PWG_HF ]
1487+ references_hf = [Reference .LINTER_1 , Reference .PWG_HF ]
15051488
15061489
15071490class TestHfNameStructClass (TestSpec ):
@@ -1747,7 +1730,7 @@ def main():
17471730 ref_names = []
17481731 for test in tests :
17491732 if any (n > 0 for n in (test .n_issues , test .n_disabled , test .n_tolerated , n_files_bad [test .name ])):
1750- ref_ids = [ ref .value for ref in test .references ]
1733+ ref_ids = sorted ( ref .value for ref in test .references )
17511734 ref_names += test .references
17521735 print (
17531736 f"{ test .name } { ' ' * (len_max - len (test .name ))} \t { test .n_issues } \t { test .n_tolerated } "
@@ -1802,7 +1785,7 @@ def main():
18021785 print ("Skipping writing in GITHUB_OUTPUT." )
18031786
18041787 # Print tips.
1805- print ("\n Tip: You can run the O2 linter locally with: python3 Scripts/o2_linter.py <files>" )
1788+ print ("\n Tip: You can run the O2 linter locally from the O2Physics directory with: python3 Scripts/o2_linter.py <files>" )
18061789
18071790 if not passed :
18081791 sys .exit (1 )
0 commit comments