Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"description": "Tolerant PHP-to-AST parser designed for IDE usage scenarios",
"type": "library",
"require": {
"php": ">=7.2"
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^8.5.15",
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.8"
},
"license": "MIT",
Expand Down
4 changes: 2 additions & 2 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@

<testsuite name="api">
<file>tests/api/NodeApiTest.php</file>
<file>tests/api/getNodeAtPosition.php</file>
<file>tests/api/getResolvedName.php</file>
<file>tests/api/GetNodeAtPositionTest.php</file>
<file>tests/api/GetResolvedNameTest.php</file>
<file>tests/api/PositionUtilitiesTest.php</file>
<file>tests/api/TextEditTest.php</file>
</testsuite>
Expand Down
21 changes: 0 additions & 21 deletions tests/CallbackTestListener.php

This file was deleted.

43 changes: 13 additions & 30 deletions tests/LexicalGrammarTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,47 +11,30 @@
use PHPUnit\Framework\BaseTestListener;
use PHPUnit\Framework\AssertionFailedError;

require_once __DIR__ . '/CallbackTestListener.php';

class LexicalGrammarTest extends TestCase {
private $expectedTokensFile;
private $tokens;
const FILE_PATTERN = __DIR__ . "/cases/lexical/*";
public function run(?TestResult $result = null) : TestResult {
if (!isset($GLOBALS["GIT_CHECKOUT_LEXER"])) {
$GLOBALS["GIT_CHECKOUT_LEXER"] = true;
exec("git -C " . dirname(self::FILE_PATTERN) . " checkout *.php.tokens");
}

$result->addListener(new CallbackTestListener(function (Test $test) {
if (isset($test->expectedTokensFile) && isset($test->tokens)) {
file_put_contents($test->expectedTokensFile, str_replace("\r\n", "\n", $test->tokens));
}
}));

$result = parent::run($result);
return $result;
}


/**
* @dataProvider lexicalProvider
*/
public function testOutputTokenClassificationAndLength($testCaseFile, $expectedTokensFile) {
$fileContents = file_get_contents($testCaseFile);
if (!file_exists($expectedTokensFile)) {
file_put_contents($expectedTokensFile, $fileContents);
exec("git add " . $expectedTokensFile);
}

$expectedTokens = str_replace("\r\n", "\n", file_get_contents($expectedTokensFile));
$lexer = \Microsoft\PhpParser\TokenStreamProviderFactory::GetTokenStreamProvider($fileContents);
$GLOBALS["SHORT_TOKEN_SERIALIZE"] = true;
$tokens = str_replace("\r\n", "\n", json_encode($lexer->getTokensArray(), JSON_PRETTY_PRINT));
$GLOBALS["SHORT_TOKEN_SERIALIZE"] = false;
$this->expectedTokensFile = $expectedTokensFile;
$this->tokens = $tokens;
$this->assertEquals($expectedTokens, $tokens, "input: $testCaseFile\r\nexpected: $expectedTokensFile");

if (!file_exists($expectedTokensFile)) {
file_put_contents($expectedTokensFile, $tokens);
self::markTestSkipped('Snapshot generated');
}

$expectedTokens = str_replace("\r\n", "\n", file_get_contents($expectedTokensFile));

$this->assertEquals(
$expectedTokens,
$tokens,
"input: $testCaseFile\r\nexpected: $expectedTokensFile (delete expected to regenerate)"
);
}

public function lexicalProvider() {
Expand Down
79 changes: 29 additions & 50 deletions tests/ParserGrammarTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,66 +12,43 @@
use PHPUnit\Framework\TestListenerDefaultImplementation;
use PHPUnit\Framework\AssertionFailedError;

require_once __DIR__ . '/CallbackTestListener.php';

class ParserGrammarTest extends TestCase {
private $expectedTokensFile;
private $expectedDiagnosticsFile;
private $tokens;
private $diagnostics;
public function run(?TestResult $result = null) : TestResult {
if (!isset($GLOBALS["GIT_CHECKOUT_PARSER"])) {
$GLOBALS["GIT_CHECKOUT_PARSER"] = true;
exec("git -C " . dirname(self::FILE_PATTERN) . " checkout *.php.tree *.php.diag");
}

$result->addListener(new CallbackTestListener(function (Test $test) {
if (isset($test->expectedTokensFile) && isset($test->tokens)) {
file_put_contents($test->expectedTokensFile, str_replace("\r\n", "\n", $test->tokens));
}
if (isset($test->expectedDiagnosticsFile) && isset($test->diagnostics)) {
file_put_contents($test->expectedDiagnosticsFile, str_replace("\r\n", "\n", $test->diagnostics));
}
}));

$result = parent::run($result);
return $result;
}

/**
* @dataProvider treeProvider
*/
public function testOutputTreeClassificationAndLength($testCaseFile, $expectedTokensFile, $expectedDiagnosticsFile) {
$this->expectedTokensFile = $expectedTokensFile;
$this->expectedDiagnosticsFile = $expectedDiagnosticsFile;

$fileContents = file_get_contents($testCaseFile);
if (!file_exists($expectedTokensFile)) {
file_put_contents($expectedTokensFile, $fileContents);
exec("git add " . $expectedTokensFile);
}

if (!file_exists($expectedDiagnosticsFile)) {
file_put_contents($expectedDiagnosticsFile, $fileContents);
exec("git add " . $expectedDiagnosticsFile);
}

$parser = new \Microsoft\PhpParser\Parser();
$sourceFileNode = $parser->parseSourceFile($fileContents);

$expectedTokens = str_replace("\r\n", "\n", file_get_contents($expectedTokensFile));
$expectedDiagnostics = str_replace("\r\n", "\n", file_get_contents($expectedDiagnosticsFile));

$GLOBALS["SHORT_TOKEN_SERIALIZE"] = true;
$tokens = str_replace("\r\n", "\n", json_encode($sourceFileNode, JSON_PRETTY_PRINT));
$diagnostics = str_replace("\r\n", "\n", json_encode(\Microsoft\PhpParser\DiagnosticsProvider::getDiagnostics($sourceFileNode), JSON_PRETTY_PRINT));
$GLOBALS["SHORT_TOKEN_SERIALIZE"] = false;

$this->tokens = $tokens;
$this->diagnostics = $diagnostics;
$skip = false;
if (!file_exists($expectedTokensFile)) {
file_put_contents($expectedTokensFile, $tokens);
$skip = true;
} else {
$expectedTokens = trim(str_replace("\r\n", "\n", file_get_contents($expectedTokensFile)));
}

$tokensOutputStr = "input doc:\r\n$fileContents\r\n\r\ninput: $testCaseFile\r\nexpected: $expectedTokensFile";
$diagnosticsOutputStr = "input doc:\r\n$fileContents\r\n\r\ninput: $testCaseFile\r\nexpected: $expectedDiagnosticsFile";

if (!file_exists($expectedDiagnosticsFile)) {
file_put_contents($expectedDiagnosticsFile, $diagnostics);
$skip = true;
} else {
$expectedDiagnostics = trim(str_replace("\r\n", "\n", file_get_contents($expectedDiagnosticsFile)));
}

if ($skip) {
self::markTestSkipped('Snapshot generated');
}

$tokensOutputStr = "input doc:\r\n$fileContents\r\n\r\ninput: $testCaseFile\r\nexpected: $expectedTokensFile (deleted expected file to regenerate)";
$diagnosticsOutputStr = "input doc:\r\n$fileContents\r\n\r\ninput: $testCaseFile\r\nexpected: $expectedDiagnosticsFile (delete expected file to regenerate)";

$this->assertEquals($expectedTokens, $tokens, $tokensOutputStr);
$this->assertEquals($expectedDiagnostics, $diagnostics, $diagnosticsOutputStr);
Expand Down Expand Up @@ -104,12 +81,14 @@ public function treeProvider() {
$testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree", $testCase . ".diag"];
}

foreach (self::PATTERNS_FOR_MINIMUM_PHP_VERSION as list($minVersionId, $filePattern)) {
if (PHP_VERSION_ID >= $minVersionId) {
$testCases = glob($filePattern . ".php");
foreach ($testCases as $testCase) {
$testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree", $testCase . ".diag"];
}
foreach (self::PATTERNS_FOR_MINIMUM_PHP_VERSION as [$minVersionId, $filePattern]) {
if (PHP_VERSION_ID < $minVersionId) {

@dantleech dantleech Oct 29, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a refactor for having an early continue

continue;
}

$testCases = glob($filePattern . ".php");
foreach ($testCases as $testCase) {
$testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree", $testCase . ".diag"];
}
}

Expand Down
File renamed without changes.
File renamed without changes.