Skip to content

Commit

Permalink
✨ New PathValidationHelper class
Browse files Browse the repository at this point in the history
... to help validate paths provided via a ruleset property before using them in sniffs.

The utility method in this class will also convert the paths from relative paths to absolute paths.
The resulting array will have the original relative path as the key and the final absolute path as the value to allow for lookups.

Includes dedicated unit tests.
  • Loading branch information
jrfnl committed Nov 19, 2023
1 parent b54504e commit f02f160
Show file tree
Hide file tree
Showing 2 changed files with 222 additions and 0 deletions.
149 changes: 149 additions & 0 deletions Yoast/Tests/Utils/PathValidationHelperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php

namespace YoastCS\Yoast\Tests\Utils;

use YoastCS\Yoast\Tests\NonSniffTestCase;
use YoastCS\Yoast\Utils\PathValidationHelper;

/**
* Tests for the YoastCS\Yoast\Utils\PathValidationHelper class.
*
* @coversDefaultClass \YoastCS\Yoast\Utils\PathValidationHelper
*
* @since 3.0.0
*/
final class PathValidationHelperTest extends NonSniffTestCase {

/**
* Default basepath.
*
* @var string
*/
private const DIRTY_BASEPATH = '/base/path';

/**
* Cleaned up version of the default basepath.
*
* @var string
*/
private const CLEAN_BASEPATH = 'base/path/';

/**
* Test converting a set of relative paths to absolute paths when no basepath is present.
*
* @covers ::relative_to_absolute
*
* @return void
*/
public function test_relative_to_absolute_no_basepath() {
$phpcsFile = $this->get_mock_file();

$this->assertSame( [], PathValidationHelper::relative_to_absolute( $phpcsFile, [ 'path' ] ) );
}

/**
* Test converting a set of relative paths to absolute paths when no paths where passed.
*
* @dataProvider data_relative_to_absolute_no_paths
* @covers ::relative_to_absolute
*
* @param array<string> $input The input array.
*
* @return void
*/
public function test_relative_to_absolute_no_paths( $input ) {
$phpcsFile = $this->get_mock_file();
$phpcsFile->config->basepath = self::DIRTY_BASEPATH;

$this->assertSame( [], PathValidationHelper::relative_to_absolute( $phpcsFile, $input ) );
}

/**
* Data provider.
*
* @see test_relative_to_absolute_no_paths() For the array format.
*
* @return array<string, array<string, array<string>>>
*/
public static function data_relative_to_absolute_no_paths() {
return [
'empty array' => [
'input' => [],
],
'array with only empty values' => [
'input' => [
'',
' ',
' ',
],
],
];
}

/**
* Test converting a set of relative paths to absolute paths.
*
* @dataProvider data_relative_to_absolute
* @covers ::relative_to_absolute
*
* @param array<string> $input The input array.
* @param array<string> $expected The expected function output.
*
* @return void
*/
public function test_relative_to_absolute( $input, $expected ) {
$phpcsFile = $this->get_mock_file();
$phpcsFile->config->basepath = self::DIRTY_BASEPATH;

$this->assertSame( $expected, PathValidationHelper::relative_to_absolute( $phpcsFile, $input ) );
}

/**
* Data provider.
*
* @see test_relative_to_absolute() For the array format.
*
* @return array<string, array<string, array<string>>>
*/
public static function data_relative_to_absolute() {
return [
'all cases' => [
'input' => [
'../walking/up/',
'/walking/../up/',
'/walking/up/../',
' . ',
'.',
'./',
'.\\',
'./some/path',
'./some/path/to/file.ext',
'/some/path',
'/some/path/to/file.ext',
'some/path',
'some/path/to/file.ext',
'\some\path',
'\some\path\to\file.ext',
'.\some\path',
'.\some\path\to\file.ext',
],
'expected' => [
' . ' => self::CLEAN_BASEPATH,
'.' => self::CLEAN_BASEPATH,
'./' => self::CLEAN_BASEPATH,
'.\\' => self::CLEAN_BASEPATH,
'./some/path' => self::CLEAN_BASEPATH . 'some/path/',
'./some/path/to/file.ext' => self::CLEAN_BASEPATH . 'some/path/to/file.ext',
'/some/path' => self::CLEAN_BASEPATH . 'some/path/',
'/some/path/to/file.ext' => self::CLEAN_BASEPATH . 'some/path/to/file.ext',
'some/path' => self::CLEAN_BASEPATH . 'some/path/',
'some/path/to/file.ext' => self::CLEAN_BASEPATH . 'some/path/to/file.ext',
'\some\path' => self::CLEAN_BASEPATH . 'some/path/',
'\some\path\to\file.ext' => self::CLEAN_BASEPATH . 'some/path/to/file.ext',
'.\some\path' => self::CLEAN_BASEPATH . 'some/path/',
'.\some\path\to\file.ext' => self::CLEAN_BASEPATH . 'some/path/to/file.ext',
],
],
];
}
}
73 changes: 73 additions & 0 deletions Yoast/Utils/PathValidationHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace YoastCS\Yoast\Utils;

use PHP_CodeSniffer\Files\File;

/**
* Utility class containing methods for validating paths.
*
* ---------------------------------------------------------------------------------------------
* This class is only intended for internal use by YoastCS and is not part of the public API.
* This also means that it has no promise of backward compatibility. Use at your own risk.
* ---------------------------------------------------------------------------------------------
*
* @internal
*
* @since 3.0.0
*/
final class PathValidationHelper {

/**
* Convert an array with relative paths to an array with absolute paths.
*
* Note: path walking is prohibited and relative paths containing ".." will be ignored.
*
* @param File $phpcsFile The current file being scanned.
* @param array<string> $relative_paths Array of relative paths which should become absolute paths.
* Paths are expected to be relative to the "basepath" setting.
*
* @return array<string, string> Array of absolute paths or an empty array if the conversion could not be executed.
* The array will contain the original relative paths as the keys and the absolute paths
* as the values.
* Note: multiple relative paths may result in the same absolute path.
* The values are not guaranteed to be unique!
*/
public static function relative_to_absolute( File $phpcsFile, array $relative_paths ) {
$absolute = [];

if ( ! isset( $phpcsFile->config->basepath ) ) {
// No use continuing as we can't turn relative paths into absolute paths.
return $absolute;
}

$base_path = PathHelper::normalize_path( $phpcsFile->config->basepath );

foreach ( $relative_paths as $path ) {
$result_path = \trim( $path );
$result_path = PathHelper::normalize_path( $result_path );

if ( $result_path === '' ) {
continue;
}

if ( \strpos( $result_path, '..' ) !== false ) {
// Ignore paths containing path walking.
continue;
}

if ( $result_path === './' ) {
$absolute[ $path ] = $base_path;
continue;
}

if ( \strpos( $result_path, './' ) === 0 ) {
$result_path = \substr( $result_path, 2 );
}

$absolute[ $path ] = $base_path . $result_path;
}

return $absolute;
}
}

0 comments on commit f02f160

Please sign in to comment.