Skip to content
Open
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: 1 addition & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
"type": "phpstan-extension",
"require": {
"php": "^7.4 || ^8.0",
"phpstan/phpstan": "^1.8",
"symfony/polyfill-php80": "^v1.27.0",
"symfony/yaml": "^5.4 || ^6.0"
"phpstan/phpstan": "^1.8"
},
"require-dev": {
"phpstan/phpstan-phpunit": "^1.2",
Expand Down
16 changes: 8 additions & 8 deletions src/ModuliteYaml/ModuliteData.php
Original file line number Diff line number Diff line change
Expand Up @@ -373,11 +373,11 @@ static function create_from_composer_json(ComposerJsonData $composer_json, bool
if ($has_modulite_yaml_also) {
$yaml_filename = dirname($composer_json->json_filename) . '/.modulite.yaml';
try {
$y_file = \Symfony\Component\Yaml\Yaml::parseFile($yaml_filename);
$y_file = YamlParserNoSymfony::parseFromFile($yaml_filename);
$parser = new ModuliteYamlParser($out);
$parser->parse_modulite_yaml_file(is_array($y_file) ? $y_file : []);
} catch (\Symfony\Component\Yaml\Exception\ParseException $ex) {
$out->fire_yaml_error($ex->getMessage(), $ex->getParsedLine());
$parser->parse_modulite_yaml_file($y_file);
} catch (YamlParserNoSymfonyException $ex) {
$out->fire_yaml_error($ex->getMessage(), $ex->getLine());
}
}

Expand All @@ -393,11 +393,11 @@ static function create_from_modulite_yaml(string $yaml_filename, ?ModuliteData $
$out->is_composer_package = false;

try {
$y_file = \Symfony\Component\Yaml\Yaml::parseFile($out->yaml_filename);
$y_file = YamlParserNoSymfony::parseFromFile($out->yaml_filename);
$parser = new ModuliteYamlParser($out);
$parser->parse_modulite_yaml_file(is_array($y_file) ? $y_file : []);
} catch (\Symfony\Component\Yaml\Exception\ParseException $ex) {
$out->fire_yaml_error($ex->getMessage(), $ex->getParsedLine());
$parser->parse_modulite_yaml_file($y_file);
} catch (YamlParserNoSymfonyException $ex) {
$out->fire_yaml_error($ex->getMessage(), $ex->getLine());
}

return $out;
Expand Down
179 changes: 179 additions & 0 deletions src/ModuliteYaml/YamlParserNoSymfony.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<?php

namespace ModulitePHPStan\ModuliteYaml;

/**
* The purpose of this class is to avoid "symfony/yaml" dependency.
* It successfully parses a limited subset of .yaml files,
* which is quite enough for .modulite.yaml
* (no links, strings are quoted, only two levels of depth)
*/
class YamlParserNoSymfony {
static public function parseFromString(string $yaml): array {
return self::parseYamlContents(preg_split('/\n/', $yaml), 'user-string');
}

static public function parseFromFile(string $file_name): array {
return self::parseYamlContents(file($file_name), $file_name);
}

static private function parseYamlContents(array $lines, string $file_name): array {
/** @var mixed[] $out */
$out = [];

$last_key = null;
$last_key_nested = null;

$assignValueAtCurrentKey = function(?string $value) use (&$last_key, &$last_key_nested, &$out) {
if ($last_key_nested !== null) {
$out[$last_key][$last_key_nested] = $value;
} else if ($last_key !== null) {
$out[$last_key] = $value;
}
};

$pushValueAtCurrentKey = function(string $value) use (&$last_key, &$last_key_nested, &$out) {
if ($last_key_nested !== null) {
$out[$last_key][$last_key_nested][] = $value;
} else if ($last_key !== null) {
$out[$last_key][] = $value;
}
};

foreach ($lines as $i => $line) {
$line = rtrim($line);
$offset = 0;
self::skipSpaces($line, $offset);
$n_spaces = $offset;

if ($offset >= strlen($line)) {
continue;
}

if ($line[$offset] === '#') {
continue;
}

if ($line[$offset] === '-') {
if ($last_key === null || is_string($out[$last_key])) {
throw new YamlParserNoSymfonyException($file_name, "list in a strange place", $i + 1);
}
$offset++;
$value = self::parseString($line, $offset);
if ($value === null) {
throw new YamlParserNoSymfonyException($file_name, "expected a string", $i + 1);
}
$pushValueAtCurrentKey($value);
continue;
}

$nameBeforeColon = self::parseNameBeforeColon($line, $offset);
if ($nameBeforeColon === null) {
throw new YamlParserNoSymfonyException($file_name, "expected ':'", $i + 1);
}

if ($n_spaces > 0) { // support only 2 depth levels
$last_key_nested = $nameBeforeColon;
} else {
$last_key = $nameBeforeColon;
$last_key_nested = null;
}

self::skipSpaces($line, $offset);
if ($offset < strlen($line) && $line[$offset] !== '#') {
$value = self::parseString($line, $offset);
if ($value === null) {
throw new YamlParserNoSymfonyException($file_name, "expected a string", $i + 1);
}
$assignValueAtCurrentKey($value);
} else {
$assignValueAtCurrentKey(null);
}
}

return $out;
}

static private function parseString(string $line, int &$offset): ?string {
self::skipSpaces($line, $offset);
if ($offset >= strlen($line)) {
return null;
}

return $line[$offset] === '"'
? self::parseQuotedString($line, $offset)
: self::parseNonQuotedString($line, $offset);
}

static private function skipSpaces(string $line, int &$offset) {
while ($offset < strlen($line) && $line[$offset] === ' ') {
$offset++;
}
}

static private function parseNameBeforeColon(string $line, int &$offset): ?string {
$name = '';

if ($line[$offset] === '"' || $line[$offset] === "'") {
$name = self::parseQuotedString($line, $offset);
if ($name === null || $offset >= strlen($line) || $line[$offset] !== ':') {
return null;
}
} else {
$pos = strpos($line, ':', $offset);
if ($pos === false) {
return null;
}
$name = substr($line, $offset, $pos - $offset);
$offset = $pos;
}

$offset = $offset + 1;
return $name;
}

static private function parseNonQuotedString(string $line, int &$offset): ?string {
$value = '';

for ($i = $offset; $i < strlen($line); ++$i) {
switch ($line[$i]) {
case '#':
break 2;
default:
$value .= $line[$i];
}
}

while ($i > $offset && $line[$i - 1] === ' ') {
$i--;
$value = substr($value, 0, -1);
}

$offset = $i;
return $value;
}

static private function parseQuotedString(string $line, int &$offset): ?string {
$quote = $line[$offset]; // " or '
$value = '';

for ($i = $offset + 1; $i < strlen($line); ++$i) {
switch ($line[$i]) {
case '\\':
$value .= $line[++$i];
break;
case $quote:
break 2;
default:
$value .= $line[$i];
}
}

if ($i == strlen($line) || $line[$i] !== $quote) {
return null;
}

$offset = $i + 1;
return $value;
}
}
11 changes: 11 additions & 0 deletions src/ModuliteYaml/YamlParserNoSymfonyException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace ModulitePHPStan\ModuliteYaml;

class YamlParserNoSymfonyException extends \RuntimeException {
public function __construct(string $file_name, string $message, int $line_number) {
parent::__construct($message);
$this->file = $file_name;
$this->line = $line_number;
}
}
109 changes: 109 additions & 0 deletions tests/YamlParserNoSymfonyTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace ModuliteTests;

use ModulitePHPStan\ModuliteYaml\YamlParserNoSymfony;
use ModulitePHPStan\ModuliteYaml\YamlParserNoSymfonyException;

class YamlParserNoSymfonyTestCase extends \PHPUnit\Framework\TestCase {
function testSuccess() {
$yaml = <<<'YAML'
name: "@utils"
namespace: "Algo101\\"
hello1: hello1
hello2: hello2 # comment
hello3: "hello3"
hello4: "hello4" # comment
"hello5": hello5
"hello 6": "hello 6"
"hello:7": "hello:7"
"hello'\"8": "hello'\"8" # comment
hello9:
hello10: # comment

export:
"asdf":
- ""
nested:
- one
- Export\ Class
- Export\ Class # comment
- "Export\\ Class " # comment
- "Export\\Class::method()" # comment

require: "asdf"

map:
k1: v1
k2:
- v2
k3: v3

allow-internal-access:
- "Algo101"
YAML;
$expected = [
'name' => '@utils',
'namespace' => 'Algo101\\',
'hello1' => 'hello1',
'hello2' => 'hello2',
'hello3' => 'hello3',
'hello4' => 'hello4',
'hello5' => 'hello5',
'hello 6' => 'hello 6',
'hello:7' => 'hello:7',
'hello\'"8' => 'hello\'"8',
'hello9' => null,
'hello10' => null,
'export' => [
'asdf' => [''],
'nested' => [
'one',
"Export\\ Class",
"Export\\ Class",
"Export\\ Class ",
"Export\\Class::method()"
]
],
'require' => 'asdf',
'map' => [
'k1' => 'v1',
'k2' => ['v2'],
'k3' => 'v3',
],
'allow-internal-access' => ['Algo101'],
];

$actual = YamlParserNoSymfony::parseFromString($yaml);
$this->assertSame($expected, $actual);
}

function testError1() {
$yaml = <<<'YAML'
name: "asdf"
- asdf
YAML;
$this->expectException(YamlParserNoSymfonyException::class);
$this->expectExceptionMessage("list in a strange place");
YamlParserNoSymfony::parseFromString($yaml);
}

function testError2() {
$yaml = <<<'YAML'
"asdf"
YAML;
$this->expectException(YamlParserNoSymfonyException::class);
$this->expectExceptionMessage("expected ':'");
YamlParserNoSymfony::parseFromString($yaml);
}

function testError3() {
$yaml = <<<'YAML'
exports:
-
YAML;
$this->expectException(YamlParserNoSymfonyException::class);
$this->expectExceptionMessage("expected a string");
YamlParserNoSymfony::parseFromString($yaml);
}
}
2 changes: 2 additions & 0 deletions tests/php/005_inheritance/005_inheritance.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Feed005\Rank005\RankImpl1;
use Feed005\Rank005\RankImpl2;
use Feed005\SenderFactory;
require_once 'plain005/plain005.php';

GImportTrait::pubStaticFn();
$mmm = new GImportTrait;
Expand All @@ -32,6 +33,7 @@ function printCurDescInherit() {
callSend(SenderFactory::createSender('sms'));

printCurDescInherit();
plainPrintCurDescInherit();

Common005\CallOthers005::accessGloDer();
Common005\CallOthers005::accessMessage();
6 changes: 6 additions & 0 deletions tests/php/005_inheritance/ConnectNoMod005/ErrNoMod005.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

namespace ConnectNoMod005;

final class ErrNoMod005 extends \SakCommon005\ErrSak005 {
}
12 changes: 12 additions & 0 deletions tests/php/005_inheritance/SakCommon005/.modulite.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: "@sak"
namespace: "SakCommon005\\"

export:
- "ErrSak005"

force-internal:

require:
- "@feed"

allow-internal-access:
13 changes: 13 additions & 0 deletions tests/php/005_inheritance/SakCommon005/ErrSak005.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace SakCommon005;

class ErrSak005 {
const MODE = 1;
static public int $count = 0;

static function create() {
echo "create err";
return new static;
}
}
Loading