@@ -484,59 +484,264 @@ def test_astIsFloat(self, sample_cfg):
484484
485485
486486class TestMatch :
487- def test_simpleMatch (self , sample_cfg ):
488- add_def = find_token (sample_cfg , 'add' )
489- assert cppcheckdata .simpleMatch (add_def , 'add ( int a , int b )' )
490- assert not cppcheckdata .simpleMatch (add_def , 'add ( int b' )
491- assert not cppcheckdata .simpleMatch (None , 'add' )
487+ """Tests for the cppcheckdata.match()/simpleMatch() pattern matching."""
488+
489+ def test_simpleMatch (self , match_cfg ):
490+ calc_def = find_token (match_cfg , 'calc' )
491+ assert cppcheckdata .simpleMatch (calc_def , 'calc' )
492+ assert cppcheckdata .simpleMatch (calc_def , 'calc ( int a , int b )' )
493+ assert not cppcheckdata .simpleMatch (calc_def , 'calc ( int b' )
494+ assert not cppcheckdata .simpleMatch (None , 'calc' )
495+
496+ def test_literal_sequence (self , match_cfg ):
497+ calc_def = find_token (match_cfg , 'calc' )
498+ assert cppcheckdata .match (calc_def , 'calc ( int' )
499+ assert cppcheckdata .match (calc_def , 'calc ( int a , int b )' )
500+ assert not cppcheckdata .match (calc_def , 'calc ( char' )
501+ # the match is anchored at the given token
502+ assert not cppcheckdata .match (calc_def , 'int calc' )
503+
504+ def test_empty_pattern_and_no_token (self , match_cfg ):
505+ calc_def = find_token (match_cfg , 'calc' )
506+ assert not cppcheckdata .match (calc_def , '' )
507+ assert not cppcheckdata .match (None , 'calc' )
508+
509+ def test_end (self , match_cfg ):
510+ calc_def = find_token (match_cfg , 'calc' )
511+ res = cppcheckdata .match (calc_def , 'calc' )
512+ assert res .end is calc_def
513+ res = cppcheckdata .match (calc_def , 'calc ( int' )
514+ assert res .end .str == 'int'
492515
493- def test_match_plain (self , sample_cfg ):
494- add_def = find_token (sample_cfg , 'add' )
495- res = cppcheckdata .match (add_def , 'add ( int' )
516+ # ---- literal operator tokens ----
517+
518+ def test_literal_bitwise_or (self , match_cfg ):
519+ # a literal '|' in the pattern matches a '|' token ...
520+ bit_or = find_token (match_cfg , '|' )
521+ assert cppcheckdata .match (bit_or , '|' )
522+ assert cppcheckdata .match (bit_or .previous , '%var% | %var% ;' )
523+ # ... but not a '||' token, and it is not an either-or alternation
524+ log_or = find_token (match_cfg , '||' )
525+ assert not cppcheckdata .match (log_or , '|' )
526+
527+ def test_literal_logical_or (self , match_cfg ):
528+ log_or = find_token (match_cfg , '||' )
529+ assert cppcheckdata .match (log_or , '||' )
530+ assert cppcheckdata .match (log_or .previous , '%var% || %var% ;' )
531+ bit_or = find_token (match_cfg , '|' )
532+ assert not cppcheckdata .match (bit_or , '||' )
533+
534+ def test_literal_not (self , match_cfg ):
535+ not_tok = find_token (match_cfg , '!' )
536+ assert cppcheckdata .match (not_tok , '! %var% ;' )
537+ assert not cppcheckdata .match (find_token (match_cfg , '!=' ), '!' )
538+
539+ def test_literal_not_equal (self , match_cfg ):
540+ neq = find_token (match_cfg , '!=' )
541+ assert cppcheckdata .match (neq , '!=' )
542+ assert cppcheckdata .match (neq .previous , '%var% != %var% ;' )
543+ assert not cppcheckdata .match (find_token (match_cfg , '=' ), '!=' )
544+ assert not cppcheckdata .match (neq , '=' )
545+
546+ def test_literal_star (self , match_cfg ):
547+ mul = find_token (match_cfg , '*' )
548+ assert cppcheckdata .match (mul , '*' )
549+ assert cppcheckdata .match (mul .previous , '%var% * %var% ;' )
550+
551+ def test_literal_percent (self , match_cfg ):
552+ mod = find_token (match_cfg , '%' )
553+ assert cppcheckdata .match (mod , '%' )
554+ assert cppcheckdata .match (mod .previous , '%var% % %var% ;' )
555+
556+ def test_literal_parentheses (self , match_cfg ):
557+ calc_call = find_token (match_cfg , 'calc' , skip = 1 )
558+ assert cppcheckdata .match (calc_call , 'calc ( %var% , %var% ) ;' )
559+
560+ # ---- %keyword% patterns ----
561+
562+ def test_any (self , match_cfg ):
563+ for token_str in ('calc' , '(' , '3' , ';' , '|' , '{' ):
564+ assert cppcheckdata .match (find_token (match_cfg , token_str ), '%any%' )
565+ plus_assign = find_token (match_cfg , '+=' )
566+ assert cppcheckdata .match (plus_assign , '%assign% %any% ;' )
567+
568+ def test_assign (self , match_cfg ):
569+ assert cppcheckdata .match (find_token (match_cfg , '=' ), '%assign%' )
570+ assert cppcheckdata .match (find_token (match_cfg , '+=' ), '%assign%' )
571+ assert not cppcheckdata .match (find_token (match_cfg , '!=' ), '%assign%' )
572+ assert not cppcheckdata .match (find_token (match_cfg , '|' ), '%assign%' )
573+
574+ def test_comp (self , match_cfg ):
575+ assert cppcheckdata .match (find_token (match_cfg , '>' ), '%comp%' )
576+ assert cppcheckdata .match (find_token (match_cfg , '!=' ), '%comp%' )
577+ assert not cppcheckdata .match (find_token (match_cfg , '=' ), '%comp%' )
578+ assert not cppcheckdata .match (find_token (match_cfg , '|' ), '%comp%' )
579+
580+ def test_name (self , match_cfg ):
581+ assert cppcheckdata .match (find_token (match_cfg , 'calc' ), '%name%' )
582+ assert cppcheckdata .match (find_token (match_cfg , 'int' ), '%name%' )
583+ assert not cppcheckdata .match (find_token (match_cfg , '3' ), '%name%' )
584+ assert not cppcheckdata .match (find_token (match_cfg , '(' ), '%name%' )
585+
586+ def test_op (self , match_cfg ):
587+ for op in ('|' , '||' , '*' , '%' , '!=' , '>' , '=' , '+=' ):
588+ assert cppcheckdata .match (find_token (match_cfg , op ), '%op%' ), op
589+ assert not cppcheckdata .match (find_token (match_cfg , ';' ), '%op%' )
590+ assert not cppcheckdata .match (find_token (match_cfg , 'calc' ), '%op%' )
591+
592+ def test_or (self , match_cfg ):
593+ assert cppcheckdata .match (find_token (match_cfg , '|' ), '%or%' )
594+ assert not cppcheckdata .match (find_token (match_cfg , '||' ), '%or%' )
595+
596+ def test_oror (self , match_cfg ):
597+ assert cppcheckdata .match (find_token (match_cfg , '||' ), '%oror%' )
598+ assert not cppcheckdata .match (find_token (match_cfg , '|' ), '%oror%' )
599+
600+ def test_var (self , match_cfg ):
601+ a_use = find_token (match_cfg , 'a' , skip = 1 )
602+ assert cppcheckdata .match (a_use , '%var%' )
603+ # a function name is a %name% but not a %var%
604+ assert not cppcheckdata .match (find_token (match_cfg , 'calc' ), '%var%' )
605+ assert not cppcheckdata .match (find_token (match_cfg , '3' ), '%var%' )
606+
607+ # ---- link patterns ----
608+
609+ def test_link_parentheses (self , match_cfg ):
610+ calc_call = find_token (match_cfg , 'calc' , skip = 1 )
611+ res = cppcheckdata .match (calc_call .next , '(*)' )
612+ assert res
613+ assert res .end .str == ')'
614+ assert res .end is calc_call .next .link
615+ # the pattern continues after the linked token
616+ assert cppcheckdata .match (calc_call , '%name% (*) ;' )
617+ # '(*)' only matches at a '(' token
618+ assert not cppcheckdata .match (calc_call , '(*)' )
619+
620+ def test_link_brackets (self , match_cfg ):
621+ arr_decl = find_token (match_cfg , 'arr' )
622+ assert cppcheckdata .match (arr_decl , 'arr [*] ;' )
623+ arr_use = find_token (match_cfg , 'arr' , skip = 1 )
624+ res = cppcheckdata .match (arr_use , '%name% [*] %assign% %var% ;' )
496625 assert res
497- assert res .end .str == 'int'
498- assert not cppcheckdata .match (add_def , 'sub (' )
499- assert not cppcheckdata .match (add_def , '' )
500- assert not cppcheckdata .match (None , 'add' )
501-
502- def test_match_patterns (self , sample_cfg ):
503- x_assign = find_token (sample_cfg , 'x' , skip = 1 ) # the 'x' in 'x = 42 ;'
504- assert cppcheckdata .match (x_assign , '%name% %assign% %any% ;' )
505- assert cppcheckdata .match (x_assign , '%var% = 42' )
506- assert not cppcheckdata .match (x_assign , '%op%' )
507- plus = find_token (sample_cfg , '+' )
508- assert cppcheckdata .match (plus , '%op%' )
509- comparison = find_token (sample_cfg , '+' )
510- assert not cppcheckdata .match (comparison , '%comp%' )
511626
512- def test_match_bindings (self , sample_cfg ):
513- x_assign = find_token ( sample_cfg , 'x' , skip = 1 )
514- res = cppcheckdata .match (x_assign , '%var%@lhs = %any%@rhs ; ' )
627+ def test_link_braces (self , match_cfg ):
628+ if_scope = find_scope ( match_cfg , 'If' )
629+ res = cppcheckdata .match (if_scope . bodyStart , '{*} ' )
515630 assert res
516- assert res .lhs .str == 'x'
517- assert res .rhs .str == '42'
518- # bindings are None on a failed match
519- res = cppcheckdata .match (x_assign , '%var%@lhs + %any%@rhs' )
520- assert not res
521- assert res .lhs is None
522- assert res .rhs is None
631+ assert res .end is if_scope .bodyEnd
523632
524- def test_match_alternatives (self , sample_cfg ):
525- x_assign = find_token (sample_cfg , 'x' , skip = 1 )
526- assert cppcheckdata .match (x_assign , 'y|x =' )
527- assert not cppcheckdata .match (x_assign , 'y|z =' )
633+ def test_link_combined (self , match_cfg ):
634+ if_tok = find_token (match_cfg , 'if' )
635+ res = cppcheckdata .match (if_tok , 'if (*) {*}' )
636+ assert res
637+ assert res .end .str == '}'
638+
639+ def test_link_angle_brackets (self , match_cpp_cfg ):
640+ # the '<' of 'std::vector<int>' is linked to the '>'
641+ template_lt = find_token (match_cpp_cfg , '<' )
642+ assert template_lt .link
643+ res = cppcheckdata .match (template_lt , '<*>' )
644+ assert res
645+ assert res .end .str == '>'
646+ assert cppcheckdata .match (template_lt .previous , 'vector <*> %var%' )
647+ # the '<' in 'i < j' is a comparison without a link
648+ comparison_lt = find_token (match_cpp_cfg , '<' , skip = 1 )
649+ assert comparison_lt .link is None
650+ assert not cppcheckdata .match (comparison_lt , '<*>' )
528651
529- def test_match_negation (self , sample_cfg ):
530- x_assign = find_token (sample_cfg , 'x' , skip = 1 )
531- assert cppcheckdata .match (x_assign , 'x !!+' )
532- assert not cppcheckdata .match (x_assign , 'x !!=' )
652+ # ---- either-or alternation ----
533653
534- def test_match_link (self , sample_cfg ):
535- add_def = find_token (sample_cfg , 'add' )
536- res = cppcheckdata .match (add_def .next , '(*)' )
654+ def test_alternatives (self , match_cfg ):
655+ struct_tok = find_token (match_cfg , 'struct' )
656+ assert cppcheckdata .match (struct_tok , 'struct|class %name% {' )
657+ assert not cppcheckdata .match (struct_tok , 'union|enum %name% {' )
658+
659+ def test_alternatives_cpp (self , match_cpp_cfg ):
660+ class_tok = find_token (match_cpp_cfg , 'class' )
661+ assert cppcheckdata .match (class_tok , 'struct|class %name% {' )
662+
663+ def test_alternatives_with_keyword (self , match_cfg ):
664+ struct_tok = find_token (match_cfg , 'struct' )
665+ assert cppcheckdata .match (struct_tok , '%op%|struct' )
666+ assert not cppcheckdata .match (struct_tok , '%op%|%comp%' )
667+
668+ # ---- negation ----
669+
670+ def test_negation (self , match_cfg ):
671+ struct_tok = find_token (match_cfg , 'struct' )
672+ assert cppcheckdata .match (struct_tok , 'struct !!;' )
673+ assert not cppcheckdata .match (struct_tok , 'struct !!Point' )
674+
675+ def test_negation_keyword (self , match_cfg ):
676+ calc_call = find_token (match_cfg , 'calc' , skip = 1 )
677+ assert cppcheckdata .match (calc_call , '%name% !!%op%' )
678+ assert not cppcheckdata .match (calc_call , '%name% !!(' )
679+
680+ @pytest .mark .xfail (strict = False ,
681+ reason = 'in the C++ Token::Match a negation also matches when there is no token, '
682+ 'in match() it does not' )
683+ def test_negation_no_token (self , match_cfg ):
684+ last = match_cfg .tokenlist [- 1 ]
685+ assert last .str == '}'
686+ assert cppcheckdata .match (last , '} !!x' )
687+
688+ # ---- bindings ----
689+
690+ def test_bindings (self , match_cfg ):
691+ calc_call = find_token (match_cfg , 'calc' , skip = 1 )
692+ res = cppcheckdata .match (calc_call , '%name%@ftok (*)' )
537693 assert res
694+ assert res .ftok is calc_call
695+ assert res .ftok .str == 'calc'
538696 assert res .end .str == ')'
539697
698+ def test_multiple_bindings (self , match_cfg ):
699+ bit_or_lhs = find_token (match_cfg , 'bit_or' , skip = 1 )
700+ res = cppcheckdata .match (bit_or_lhs , '%var%@lhs = %var%@op1 | %var%@op2 ;' )
701+ assert res
702+ assert res .lhs .str == 'bit_or'
703+ assert res .op1 .str == 'a'
704+ assert res .op2 .str == 'b'
705+
706+ def test_binding_on_link_pattern (self , match_cfg ):
707+ calc_call = find_token (match_cfg , 'calc' , skip = 1 )
708+ # the binding is the '(' token, end is behind the linked ')'
709+ res = cppcheckdata .match (calc_call , '%name% (*)@paren' )
710+ assert res
711+ assert res .paren .str == '('
712+ assert res .paren .link is res .end
713+
714+ def test_bindings_on_failure (self , match_cfg ):
715+ calc_call = find_token (match_cfg , 'calc' , skip = 1 )
716+ res = cppcheckdata .match (calc_call , '%name%@ftok [*]@brackets' )
717+ assert not res
718+ # all bindings (including 'end') read as None on a failed match
719+ assert res .ftok is None
720+ assert res .brackets is None
721+ assert res .end is None
722+
723+ def test_unknown_binding_raises (self , match_cfg ):
724+ calc_call = find_token (match_cfg , 'calc' , skip = 1 )
725+ res = cppcheckdata .match (calc_call , '%name%@ftok (*)' )
726+ assert res
727+ with pytest .raises (AttributeError ):
728+ res .nosuchbinding
729+ res = cppcheckdata .match (calc_call , '%name%@ftok [*]' )
730+ assert not res
731+ with pytest .raises (AttributeError ):
732+ res .nosuchbinding
733+
734+ # ---- '**' forward search ----
735+
736+ def test_find_forward (self , match_cfg ):
737+ calc_def = find_token (match_cfg , 'calc' )
738+ # '**' searches forward, skipping linked groups: the '{' found here
739+ # is the function body, not a '{' inside the parameter list
740+ res = cppcheckdata .match (calc_def , 'calc **{' )
741+ assert res
742+ assert res .end .str == '{'
743+ assert not cppcheckdata .match (calc_def , 'calc **nosuchtoken' )
744+
540745
541746class TestSuppressions :
542747 def make_suppression (self , ** kwargs ):
0 commit comments