@@ -108,6 +108,11 @@ class PHP_CodeCoverage
108108 */
109109 protected $ data = array ();
110110
111+ /**
112+ * @var array
113+ */
114+ protected $ ignoredLines = array ();
115+
111116 /**
112117 * Test data.
113118 *
@@ -266,6 +271,7 @@ public function append(array $data, $id = NULL, $append = TRUE)
266271 }
267272
268273 $ this ->applyListsFilter ($ data );
274+ $ this ->applyIgnoredLinesFilter ($ data );
269275 $ this ->initializeFilesThatAreSeenTheFirstTime ($ data );
270276
271277 if (!$ append ) {
@@ -525,6 +531,20 @@ protected function applyListsFilter(&$data)
525531 }
526532 }
527533
534+ /**
535+ * Applies the "ignored lines" filtering.
536+ *
537+ * @param array $data
538+ */
539+ protected function applyIgnoredLinesFilter (&$ data )
540+ {
541+ foreach (array_keys ($ data ) as $ filename ) {
542+ foreach ($ this ->getLinesToBeIgnored ($ filename ) as $ line ) {
543+ unset($ data [$ filename ][$ line ]);
544+ }
545+ }
546+ }
547+
528548 /**
529549 * @since Method available since Release 1.1.0
530550 */
@@ -825,4 +845,175 @@ class_parents($coveredElement)
825845
826846 return $ codeToCoverList ;
827847 }
848+
849+ /**
850+ * Returns the lines of a source file that should be ignored.
851+ *
852+ * @param string $filename
853+ * @return array
854+ * @throws PHP_CodeCoverage_Exception
855+ * @since Method available since Release 1.3.0
856+ */
857+ protected function getLinesToBeIgnored ($ filename )
858+ {
859+ if (!is_string ($ filename )) {
860+ throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory (
861+ 1 , 'string '
862+ );
863+ }
864+
865+ if (!isset ($ this ->ignoredLines [$ filename ])) {
866+ $ this ->ignoredLines [$ filename ] = array ();
867+ $ ignore = FALSE ;
868+ $ stop = FALSE ;
869+ $ lines = file ($ filename );
870+
871+ foreach ($ lines as $ index => $ line ) {
872+ if (!trim ($ line )) {
873+ $ this ->ignoredLines [$ filename ][] = $ index + 1 ;
874+ }
875+ }
876+
877+ if ($ this ->cacheTokens ) {
878+ $ tokens = PHP_Token_Stream_CachingFactory::get ($ filename );
879+ } else {
880+ $ tokens = new PHP_Token_Stream ($ filename );
881+ }
882+
883+ $ classes = array_merge ($ tokens ->getClasses (), $ tokens ->getTraits ());
884+ $ tokens = $ tokens ->tokens ();
885+
886+ foreach ($ tokens as $ token ) {
887+ switch (get_class ($ token )) {
888+ case 'PHP_Token_COMMENT ' :
889+ case 'PHP_Token_DOC_COMMENT ' : {
890+ $ count = substr_count ($ token , "\n" );
891+ $ line = $ token ->getLine ();
892+
893+ for ($ i = $ line ; $ i < $ line + $ count ; $ i ++) {
894+ $ this ->ignoredLines [$ filename ][] = $ i ;
895+ }
896+
897+ if ($ token instanceof PHP_Token_DOC_COMMENT) {
898+ // Workaround for the fact the DOC_COMMENT token
899+ // does not include the final \n character in its
900+ // text.
901+ if (substr (trim ($ lines [$ i -1 ]), -2 ) == '*/ ' ) {
902+ $ this ->ignoredLines [$ filename ][] = $ i ;
903+ }
904+
905+ break ;
906+ }
907+
908+ $ _token = trim ($ token );
909+
910+ if ($ _token == '// @codeCoverageIgnore ' ||
911+ $ _token == '//@codeCoverageIgnore ' ) {
912+ $ ignore = TRUE ;
913+ $ stop = TRUE ;
914+ }
915+
916+ else if ($ _token == '// @codeCoverageIgnoreStart ' ||
917+ $ _token == '//@codeCoverageIgnoreStart ' ) {
918+ $ ignore = TRUE ;
919+ }
920+
921+ else if ($ _token == '// @codeCoverageIgnoreEnd ' ||
922+ $ _token == '//@codeCoverageIgnoreEnd ' ) {
923+ $ stop = TRUE ;
924+ }
925+ }
926+ break ;
927+
928+ case 'PHP_Token_INTERFACE ' :
929+ case 'PHP_Token_TRAIT ' :
930+ case 'PHP_Token_CLASS ' :
931+ case 'PHP_Token_FUNCTION ' : {
932+ $ docblock = $ token ->getDocblock ();
933+
934+ if (strpos ($ docblock , '@codeCoverageIgnore ' )) {
935+ $ endLine = $ token ->getEndLine ();
936+
937+ for ($ i = $ token ->getLine (); $ i <= $ endLine ; $ i ++) {
938+ $ this ->ignoredLines [$ filename ][] = $ i ;
939+ }
940+ }
941+
942+ else if ($ token instanceof PHP_Token_INTERFACE ||
943+ $ token instanceof PHP_Token_TRAIT ||
944+ $ token instanceof PHP_Token_CLASS) {
945+ if (empty ($ classes [$ token ->getName ()]['methods ' ])) {
946+ for ($ i = $ token ->getLine ();
947+ $ i <= $ token ->getEndLine ();
948+ $ i ++) {
949+ $ this ->ignoredLines [$ filename ][] = $ i ;
950+ }
951+ } else {
952+ $ firstMethod = array_shift (
953+ $ classes [$ token ->getName ()]['methods ' ]
954+ );
955+
956+ $ lastMethod = array_pop (
957+ $ classes [$ token ->getName ()]['methods ' ]
958+ );
959+
960+ if ($ lastMethod === NULL ) {
961+ $ lastMethod = $ firstMethod ;
962+ }
963+
964+ for ($ i = $ token ->getLine ();
965+ $ i < $ firstMethod ['startLine ' ];
966+ $ i ++) {
967+ $ this ->ignoredLines [$ filename ][] = $ i ;
968+ }
969+
970+ for ($ i = $ token ->getEndLine ();
971+ $ i > $ lastMethod ['endLine ' ];
972+ $ i --) {
973+ $ this ->ignoredLines [$ filename ][] = $ i ;
974+ }
975+ }
976+ }
977+ }
978+ break ;
979+
980+ case 'PHP_Token_INTERFACE ' : {
981+ $ endLine = $ token ->getEndLine ();
982+
983+ for ($ i = $ token ->getLine (); $ i <= $ endLine ; $ i ++) {
984+ $ this ->ignoredLines [$ filename ][] = $ i ;
985+ }
986+ }
987+ break ;
988+
989+ case 'PHP_Token_NAMESPACE ' : {
990+ $ this ->ignoredLines [$ filename ][] = $ token ->getEndLine ();
991+ } // Intentional fallthrough
992+ case 'PHP_Token_OPEN_TAG ' :
993+ case 'PHP_Token_CLOSE_TAG ' :
994+ case 'PHP_Token_USE ' : {
995+ $ this ->ignoredLines [$ filename ][] = $ token ->getLine ();
996+ }
997+ break ;
998+ }
999+
1000+ if ($ ignore ) {
1001+ $ this ->ignoredLines [$ filename ][] = $ token ->getLine ();
1002+
1003+ if ($ stop ) {
1004+ $ ignore = FALSE ;
1005+ $ stop = FALSE ;
1006+ }
1007+ }
1008+ }
1009+
1010+ $ this ->ignoredLines [$ filename ] = array_unique (
1011+ $ this ->ignoredLines [$ filename ]
1012+ );
1013+
1014+ sort ($ this ->ignoredLines [$ filename ]);
1015+ }
1016+
1017+ return $ this ->ignoredLines [$ filename ];
1018+ }
8281019}
0 commit comments