From: Erik Bernhardson Date: Tue, 13 Dec 2016 18:25:48 +0000 (-0800) Subject: Add script to support per-line @suppress annotations in Phan X-Git-Tag: 1.31.0-rc.0~4568^2~1 X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_ecrire%28%22brouteur%22%2C%28%24id_rubrique%20?a=commitdiff_plain;h=3becc477a971df20f5a9834c0c4342b9854bfa3c;p=lhc%2Fweb%2Fwiklou.git Add script to support per-line @suppress annotations in Phan Adds the functionality of per-line @suppress annotations. Phan already supports per-class or per-method annotations, but does not have any per-line support due to the PHP AST only returning comments that are class/property/method level docblocks. This is a bit of a hack, but get's the job done. Removes the PhanTypeInvalidLeftOperand issue from blacklist and suppresses it to demonstrate the supression works as expected. Change-Id: I5066b3b431fb69175a711ee366e95f31c7c47639 --- diff --git a/languages/Language.php b/languages/Language.php index 2e4ef89c5c..3eb6546490 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -466,6 +466,7 @@ class Language { $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' ); $validNamespaces = MWNamespace::getCanonicalNamespaces(); + /** @suppress PhanTypeInvalidLeftOperand */ $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces; $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace; diff --git a/tests/phan/bin/phan b/tests/phan/bin/phan index cfaf9a1a0e..bed8c69147 100755 --- a/tests/phan/bin/phan +++ b/tests/phan/bin/phan @@ -30,10 +30,13 @@ export RUN="${ISSUES}/issues-${REV}" php7.0 $PHAN \ --project-root-directory "$ROOT" \ --config-file "$ROOT/tests/phan/config.php" \ - --output "$ROOT/tests/phan/issues/issues-${REV}" \ + --output "$RUN" \ "${@}" + +cat "${RUN}" | php "$ROOT/tests/phan/bin/postprocess-phan.php" "${@}" > /tmp/phan.$$ EXIT_CODE="$?" +mv /tmp/phan.$$ "${RUN}" # Re-link the latest file rm -f "${ISSUES}/latest" diff --git a/tests/phan/bin/postprocess-phan.php b/tests/phan/bin/postprocess-phan.php new file mode 100644 index 0000000000..3e80598677 --- /dev/null +++ b/tests/phan/bin/postprocess-phan.php @@ -0,0 +1,146 @@ + 0 && preg_match( + "|/\*\* @suppress {$type} |", + $source[$lineno - 1] + ); + } +} + +class TextSuppressor extends Suppressor { + /** + * @param string $input + * @return bool do errors remain + */ + public function suppress( $input ) { + $hasErrors = false; + $errors = []; + foreach ( explode( "\n", $input ) as $error ) { + if ( empty( $error ) ) { + continue; + } + if ( !preg_match( '/^(.*):(\d+) (Phan\w+) (.*)$/', $error, $matches ) ) { + echo "Failed to parse line: $error\n"; + continue; + } + list( $source, $file, $lineno, $type, $message ) = $matches; + $errors[$file][] = [ + 'orig' => $error, + // convert from 1 indexed to 0 indexed + 'lineno' => $lineno - 1, + 'type' => $type, + ]; + } + foreach ( $errors as $file => $fileErrors ) { + $source = file( $file ); + foreach ( $fileErrors as $error ) { + if ( !$this->isSuppressed( $source, $error['type'], $error['lineno'] ) ) { + echo $error['orig'], "\n"; + $hasErrors = true; + } + } + } + + return $hasErrors; + } +} + +class CheckStyleSuppressor extends Suppressor { + /** + * @param string $input + * @return bool True do errors remain + */ + public function suppress( $input ) { + $dom = new DOMDocument(); + $dom->loadXML( $input ); + $hasErrors = false; + // DOMNodeList's are "live", convert to an array so it works as expected + $files = []; + foreach ( $dom->getElementsByTagName( 'file' ) as $file ) { + $files[] = $file; + } + foreach ( $files as $file ) { + $errors = []; + foreach ( $file->getElementsByTagName( 'error' ) as $error ) { + $errors[] = $error; + } + $source = file( $file->getAttribute( 'name' ) ); + $fileHasErrors = false; + foreach ( $errors as $error ) { + $lineno = $error->getAttribute( 'line' ) - 1; + $type = $error->getAttribute( 'source' ); + if ( $this->isSuppressed( $source, $type, $lineno ) ) { + $error->parentNode->removeChild( $error ); + } else { + $fileHasErrors = true; + $hasErrors = true; + } + } + if ( !$fileHasErrors ) { + $file->parentNode->removeChild( $file ); + } + } + echo $dom->saveXML(); + + return $hasErrors; + } +} + +class NoopSuppressor extends Suppressor { + private $mode; + + public function __construct( $mode ) { + $this->mode = $mode; + } + public function suppress( $input ) { + echo "Unsupported output mode: {$this->mode}\n$input"; + return true; + } +} + +$opt = getopt( "m:", [ "output-mode:" ] ); +// if provided multiple times getopt returns an array +if ( isset( $opt['m'] ) ) { + $mode = $opt['m']; +} elseif ( isset( $mode['output-mode'] ) ) { + $mode = $opt['output-mode']; +} else { + $mode = 'text'; +} +if ( is_array( $mode ) ) { + // If an option is passed multiple times getopt returns an + // array. Just take the last one. + $mode = end( $mode ); +} + +switch ( $mode ) { +case 'text': + $suppressor = new TextSuppressor(); + break; +case 'checkstyle': + $suppressor = new CheckStyleSuppressor(); + break; +default: + $suppressor = new NoopSuppressor( $mode ); +} + +$input = file_get_contents( 'php://stdin' ); +$hasErrors = $suppressor->suppress( $input ); + +if ( $hasErrors ) { + exit( 1 ); +} diff --git a/tests/phan/config.php b/tests/phan/config.php index 1f141437fb..7dcc5c49f5 100644 --- a/tests/phan/config.php +++ b/tests/phan/config.php @@ -323,8 +323,6 @@ return [ "PhanTypeArraySuspicious", // approximate error count: 4 "PhanTypeComparisonFromArray", - // approximate error count: 1 - "PhanTypeInvalidLeftOperand", // approximate error count: 3 "PhanTypeInvalidRightOperand", // approximate error count: 563