summaryrefslogtreecommitdiff
path: root/www/wiki/tests/phan/bin
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/tests/phan/bin')
-rwxr-xr-xwww/wiki/tests/phan/bin/phan90
-rw-r--r--www/wiki/tests/phan/bin/postprocess-phan.php146
2 files changed, 236 insertions, 0 deletions
diff --git a/www/wiki/tests/phan/bin/phan b/www/wiki/tests/phan/bin/phan
new file mode 100755
index 00000000..ad06823a
--- /dev/null
+++ b/www/wiki/tests/phan/bin/phan
@@ -0,0 +1,90 @@
+#!/bin/bash
+
+# mediawiki-vagrant installs dont have realpath by default
+if ! which realpath > /dev/null; then
+ realpath() {
+ php -r "echo realpath('$*');"
+ }
+fi
+
+if hash php7.0 2>/dev/null; then
+ export PHP="php7.0"
+else
+ export PHP="php"
+fi
+
+# Note that this isn't loaded in via composer because then composer can
+# only be run with php7.0
+if [ ! -f "$PHAN" ]; then
+ # If no PHAN is specified then try to get location from PATH
+ export PHAN="$(which phan)"
+ if [ ! -f "$PHAN" ]; then
+ echo "The environment variable PHAN must point to the 'phan' file"
+ echo "in a checkout of https://github.com/etsy/phan.git"
+ echo "Or phan must be included in your PATH"
+ exit 1
+ fi
+else
+ export PHAN="$PHP $PHAN"
+fi
+
+if [ -z "$MW_INSTALL_PATH" ]; then
+ # Figure out where mediawiki is based on the location of this script
+ pushd "$(dirname "$0")" > /dev/null
+ export MW_INSTALL_PATH="$(git rev-parse --show-toplevel)"
+ popd >/dev/null
+fi
+
+# If the first argument doesn't start with a -, then it's a path
+# to another project (extension, skin, etc.) to analyze
+if [[ -n "$1" && "$1" != "-"* ]]; then
+ cd $1
+ shift
+else
+ cd "$(dirname "$0")"
+fi
+
+# Root directory of project
+export ROOT="$(git rev-parse --show-toplevel)"
+
+# Go to the root of this git repo
+cd "$ROOT"
+
+export CONFIG_FILE="$ROOT/tests/phan/config.php"
+if [ ! -f "$CONFIG_FILE" ]; then
+ echo "Could not find a phan config file to apply in"
+ echo "$CONFIG_FILE"
+ exit 1
+fi
+
+# Phan's issues directory
+export ISSUES="${ROOT}/tests/phan/issues"
+mkdir -p "$ISSUES"
+
+# Get the current hash of HEAD
+export REV="$(git rev-parse HEAD)"
+
+# Destination for issues found
+export RUN="${ISSUES}/issues-${REV}"
+
+
+# Run the analysis, emitting output to the
+# issues file.
+$PHAN \
+ --project-root-directory "$ROOT" \
+ --config-file "$CONFIG_FILE" \
+ --output "php://stdout" \
+ "${@}" \
+ | php "$MW_INSTALL_PATH/tests/phan/bin/postprocess-phan.php" "${@}" \
+ > $RUN
+
+EXIT_CODE="$?"
+
+# Re-link the latest file
+rm -f "${ISSUES}/latest"
+ln -s "${RUN}" "${ISSUES}/latest"
+
+# Output any issues that were found
+cat "${RUN}"
+
+exit $EXIT_CODE
diff --git a/www/wiki/tests/phan/bin/postprocess-phan.php b/www/wiki/tests/phan/bin/postprocess-phan.php
new file mode 100644
index 00000000..3e805986
--- /dev/null
+++ b/www/wiki/tests/phan/bin/postprocess-phan.php
@@ -0,0 +1,146 @@
+<?php
+
+abstract class Suppressor {
+ /**
+ * @param string $input
+ * @return bool do errors remain
+ */
+ abstract public function suppress( $input );
+
+ /**
+ * @param string[] $source
+ * @param string $type
+ * @param int $lineno
+ * @return bool
+ */
+ protected function isSuppressed( array $source, $type, $lineno ) {
+ return $lineno > 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 );
+}