diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 99f64570..e1c46cd3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -8,40 +8,11 @@ jobs:
strategy:
fail-fast: false
matrix:
- php: [7.3, 7.4, 8.0, 8.1]
- illuminate_version: [6.*, 8.*]
+ php: [8.0, 8.1]
+ illuminate_version: [8.67.*, 9.7.*]
stability: [prefer-lowest, prefer-stable]
- exclude:
- - illuminate_version: 6.*
- php: 7.3
- stability: prefer-stable
- - illuminate_version: 6.*
- php: 7.4
- stability: prefer-stable
-
- - illuminate_version: 6.*
- php: 8.0
- stability: prefer-stable
-
- # Exclude unsupported combination
- # https://laravel.com/docs/8.x/releases#support-policy
- - illuminate_version: 6.*
- php: 8.1
-
- # "Added PHP 8.1 Support from v8.67.0"
- # https://github.com/laravel/framework/blob/8.x/CHANGELOG-8.x.md#v8670-2021-10-22
- # see also `matrix.include` section
- - illuminate_version: 8.*
- php: 8.1
-
- include:
- # "Added PHP 8.1 Support from v8.67.0"
- - illuminate_version: ^8.67.0
- php: 8.1
- stability: 'prefer-stable'
-
- name: ${{ matrix.php }} | Illuminate ${{ matrix.illuminate_version }} | ${{ matrix.stability }}
+ name: P${{ matrix.php }} | I ${{ matrix.illuminate_version }} | ${{ matrix.stability }}
steps:
- name: Checkout code
@@ -56,7 +27,7 @@ jobs:
- name: Install dependencies
run: |
- composer require "illuminate/container:${{ matrix.illuminate_version }}" "illuminate/contracts:${{ matrix.illuminate_version }}" "illuminate/database:${{ matrix.illuminate_version }}" "illuminate/http:${{ matrix.illuminate_version }}" "illuminate/support:${{ matrix.illuminate_version }}" --no-interaction --no-progress --prefer-dist --${{ matrix.stability }}
+ composer require "laravel/framework:${{ matrix.illuminate_version }}" "illuminate/container:${{ matrix.illuminate_version }}" "illuminate/contracts:${{ matrix.illuminate_version }}" "illuminate/database:${{ matrix.illuminate_version }}" "illuminate/http:${{ matrix.illuminate_version }}" "illuminate/support:${{ matrix.illuminate_version }}" --no-interaction --no-progress --prefer-dist --${{ matrix.stability }}
- name: Run Tests
run: composer test
diff --git a/composer.json b/composer.json
index 86153f33..c5ddb2aa 100644
--- a/composer.json
+++ b/composer.json
@@ -11,28 +11,32 @@
}
],
"require": {
- "php": "^7.3|^8.0",
+ "php": "^8.0",
"ext-simplexml": "*",
- "illuminate/config": "^6.0 || ^8.0",
- "illuminate/container": "^6.0 || ^8.0",
- "illuminate/contracts": "^6.0 || ^8.0",
- "illuminate/database": "^6.0 || ^8.0",
- "illuminate/events": "^6.0 || ^8.0",
- "illuminate/http": "^6.0 || ^8.0",
- "illuminate/routing": "^6.0 || ^8.0",
- "illuminate/support": "^6.0 || ^8.0",
- "illuminate/view": "^6.0 || ^8.0",
+ "illuminate/config": "^8.0 || ^9.0",
+ "illuminate/container": "^8.0 || ^9.0",
+ "illuminate/contracts": "^8.0 || ^9.0",
+ "illuminate/database": "^8.0 || ^9.0",
+ "illuminate/events": "^8.0 || ^9.0",
+ "illuminate/http": "^8.0 || ^9.0",
+ "illuminate/routing": "^8.0 || ^9.0",
+ "illuminate/support": "^8.0 || ^9.0",
+ "illuminate/view": "^8.0 || ^9.0",
"vimeo/psalm": "^4.8.1",
- "orchestra/testbench": "^3.8 || ^4.0 || ^5.0 || ^6.22 || ^7.0",
- "barryvdh/laravel-ide-helper": ">=2.8.0"
+ "orchestra/testbench": "^6.22 || ^7.0",
+ "barryvdh/laravel-ide-helper": "^2.10"
},
"require-dev": {
- "codeception/codeception": "^4.1.6",
- "codeception/module-phpbrowser": "^1.0.0",
- "codeception/module-asserts": "^1.0.0",
- "weirdan/codeception-psalm-module": "^0.13.1",
+ "codeception/codeception": "^5.0",
+ "codeception/module-asserts": "*@dev",
+ "codeception/module-cli": "^2.0",
+ "codeception/module-filesystem": "^3.0",
+ "codeception/module-phpbrowser": "*@dev",
+ "slevomat/coding-standard": "^6.2",
"squizlabs/php_codesniffer": "*",
- "slevomat/coding-standard": "^6.2"
+ "phpoption/phpoption": "^1.8.0",
+ "symfony/http-foundation": "^5.3.7 || ^6.0",
+ "ramsey/collection": "^1.2.0"
},
"autoload": {
"psr-4": {
diff --git a/phpcs.xml b/phpcs.xml
index daaa4f57..c388a1fe 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -22,4 +22,5 @@
tests
src/cache/
tests/_support/
+ tests/Support/
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index d2d7f5ee..63b50bc1 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -1,5 +1,10 @@
-
+
+
+
+ require $applicationPath
+
+
databasePath
diff --git a/src/Fakes/FakeMetaCommand.php b/src/Fakes/FakeMetaCommand.php
index d15e49e9..98fc7ef3 100644
--- a/src/Fakes/FakeMetaCommand.php
+++ b/src/Fakes/FakeMetaCommand.php
@@ -14,7 +14,6 @@ protected function registerClassAutoloadExceptions(): callable
// by default, the ide-helper throws exceptions when it cannot find a class. However it does not unregister that
// autoloader when it is done, and we certainly do not want to throw exceptions when we are simply checking if
// a certain class exists. We are instead changing this to be a noop.
-
return function () {
};
}
diff --git a/src/Fakes/FakeModelsCommandLogic.php b/src/Fakes/FakeModelsCommandLogic.php
index 6674d796..bf05b2bb 100644
--- a/src/Fakes/FakeModelsCommandLogic.php
+++ b/src/Fakes/FakeModelsCommandLogic.php
@@ -26,7 +26,7 @@ public function getModels(): array
*
* @param Model $model
*/
- protected function getPropertiesFromTable($model): void
+ public function getPropertiesFromTable($model): void
{
$table_name = $model->getTable();
diff --git a/stubs/EloquentBuilder.stubphp b/stubs/EloquentBuilder.stubphp
index 37799233..ad027291 100644
--- a/stubs/EloquentBuilder.stubphp
+++ b/stubs/EloquentBuilder.stubphp
@@ -167,6 +167,20 @@ class Builder
*/
public function firstOr($columns = ['*'], Closure $callback = null) { }
+ /**
+ * @param array $attributes
+ * @param array $values
+ * @return TModel|self
+ */
+ public function firstOrNew(array $attributes = [], array $values = []) { }
+
+ /**
+ * @param array $attributes
+ * @param array $values
+ * @return TModel|self
+ */
+ public function firstOrCreate(array $attributes = [], array $values = []) { }
+
/**
* @param string $column
* @return mixed
diff --git a/stubs/HasOneOrMany.stubphp b/stubs/HasOneOrMany.stubphp
index 13463c13..86414005 100644
--- a/stubs/HasOneOrMany.stubphp
+++ b/stubs/HasOneOrMany.stubphp
@@ -66,4 +66,20 @@ abstract class HasOneOrMany extends Relation
* @psalm-return \Illuminate\Database\Eloquent\Collection
*/
public function createMany(array $records) { }
+
+ /**
+ * @param array $attributes
+ * @param array $values
+ * @return \Illuminate\Database\Eloquent\Model
+ * @psalm-return TRelatedModel
+ */
+ public function firstOrNew(array $attributes = [], array $values = []) { }
+
+ /**
+ * @param array $attributes
+ * @param array $values
+ * @return \Illuminate\Database\Eloquent\Model
+ * @psalm-return TRelatedModel
+ */
+ public function firstOrCreate(array $attributes = [], array $values = []) { }
}
diff --git a/tests/Support/Module.php b/tests/Support/Module.php
new file mode 100644
index 00000000..8ceb2d45
--- /dev/null
+++ b/tests/Support/Module.php
@@ -0,0 +1,555 @@
+ */
+ private const VERSION_OPERATORS = [
+ 'newer than' => '>',
+ 'older than' => '<',
+ ];
+
+ private const DEFAULT_PSALM_CONFIG = "\n"
+ . "\n"
+ . " \n"
+ . " \n"
+ . " \n"
+ . "\n";
+
+ /**
+ * @var ?Cli
+ */
+ private $cli;
+
+ /**
+ * @var ?Filesystem
+ */
+ private $fs;
+
+ /**
+ * @var array
+ * @psalm-suppress NonInvariantDocblockPropertyType
+ */
+ protected array $config = [
+ 'psalm_path' => 'vendor/bin/psalm',
+ 'default_dir' => 'tests/_run/',
+ ];
+
+ /** @var string */
+ private $psalmConfig = '';
+
+ /** @var string */
+ private $preamble = '';
+
+ /** @var ?array */
+ private $errors = null;
+
+ /** @var bool */
+ private $hasAutoload = false;
+
+ /** @var ?int */
+ private $exitCode = null;
+
+ /** @var ?string */
+ protected $output = null;
+
+ public function _beforeSuite($settings = []): void
+ {
+ $defaultDir = $this->config['default_dir'];
+ if (file_exists($defaultDir)) {
+ if (is_dir($defaultDir)) {
+ return;
+ }
+ unlink($defaultDir);
+ }
+
+ if (!mkdir($defaultDir, 0755, true)) {
+ throw new TestRuntimeException('Failed to create dir: ' . $defaultDir);
+ }
+ }
+
+ public function _before(TestInterface $test): void
+ {
+ $this->hasAutoload = false;
+ $this->errors = null;
+ $this->output = null;
+ $this->exitCode = null;
+ $this->config['psalm_path'] = realpath($this->config['psalm_path']);
+ $this->psalmConfig = '';
+ $this->fs()->cleanDir($this->config['default_dir']);
+ $this->preamble = '';
+ }
+
+ /**
+ * @param string[] $options
+ */
+ public function runPsalmOn(string $filename, array $options = []): void
+ {
+ $suppressProgress = $this->packageSatisfiesVersionConstraint('vimeo/psalm', '>=3.4.0');
+
+ $options = array_map('escapeshellarg', $options);
+ $cmd = $this->config['psalm_path']
+ . ' --output-format=json '
+ . ($suppressProgress ? ' --no-progress ' : ' ')
+ . join(' ', $options) . ' '
+ . ($filename ? escapeshellarg($filename) : '')
+ . ' 2>&1';
+ $this->debug('Running: ' . $cmd);
+ $this->cli()->runShellCommand($cmd, false);
+
+ /** @psalm-suppress MissingPropertyType shouldn't be required, but older Psalm needs it */
+ $this->output = (string)$this->cli()->output;
+ /** @psalm-suppress MissingPropertyType shouldn't be required, but older Psalm needs it */
+ $this->exitCode = (int)$this->cli()->result;
+
+ $this->debug(sprintf('Psalm exit code: %d', $this->exitCode));
+ // $this->debug('Psalm output: ' . $this->output);
+ }
+
+ /**
+ * @param string[] $options
+ */
+ public function runPsalmIn(string $dir, array $options = []): void
+ {
+ $pwd = getcwd();
+ $this->fs()->amInPath($dir);
+
+ $config = $this->psalmConfig ?: self::DEFAULT_PSALM_CONFIG;
+ $config = sprintf($config, $this->hasAutoload ? 'autoloader="autoload.php"' : '');
+
+ $this->fs()->writeToFile('psalm.xml', $config);
+
+ $this->runPsalmOn('', $options);
+ $this->fs()->amInPath($pwd);
+ }
+
+ /**
+ * @Then I see exit code :code
+ */
+ public function seeExitCode(string $exitCode): void
+ {
+ if ($this->exitCode === (int) $exitCode) {
+ return;
+ }
+
+ Assert::fail("Expected exit code {$exitCode}, got {$this->exitCode}");
+ }
+
+ public function seeThisError(string $type, string $message): void
+ {
+ $this->parseErrors();
+ if (empty($this->errors)) {
+ Assert::fail("No errors");
+ }
+
+ foreach ($this->errors as $i => $error) {
+ if (
+ $this->matches($type, $error['type'])
+ && $this->matches($message, $error['message'])
+ ) {
+ unset($this->errors[$i]);
+ return;
+ }
+ }
+
+ Assert::fail("Didn't see [ $type $message ] in: \n" . $this->remainingErrors());
+ }
+
+ private function matches(string $expected, string $actual): bool
+ {
+ $regexpDelimiter = '/';
+ if ($expected[0] === $regexpDelimiter && $expected[strlen($expected) - 1] === $regexpDelimiter) {
+ $regexp = $expected;
+ } else {
+ $regexp = $this->convertToRegexp($expected);
+ }
+
+ return (bool) preg_match($regexp, $actual);
+ }
+
+ /**
+ * @Then I see no errors
+ * @Then I see no other errors
+ */
+ public function seeNoErrors(): void
+ {
+ $this->parseErrors();
+ if (!empty($this->errors)) {
+ Assert::fail("There were errors: \n" . $this->remainingErrors());
+ }
+ }
+
+ private function packageSatisfiesVersionConstraint(string $package, string $versionConstraint): bool
+ {
+ try {
+ $currentVersion = $this->getShortVersion($package);
+ } catch (OutOfBoundsException $ex) {
+ $this->debug(sprintf("Package %s is not installed", $package));
+ return false;
+ }
+
+ $this->debug(sprintf("Current version of %s : %s", $package, $currentVersion));
+
+ // todo: move to init/construct/before?
+ $parser = new VersionParser();
+ $currentVersion = $parser->normalize($currentVersion);
+
+ // restore pre-composer/semver:2.0 behaviour for comparison purposes
+ if (preg_match('/^dev-/', $currentVersion)) {
+ $currentVersion = '9999999-dev';
+ }
+
+ $result = Semver::satisfies($currentVersion, $versionConstraint);
+
+ $this->debug("Comparing $currentVersion against $versionConstraint => " . ($result ? 'ok' : 'ko'));
+
+ return $result;
+ }
+
+ /**
+ * @deprecated
+ * This method is only to maintain the public API; please use `self::haveADependencySatisfied` instead.
+ */
+ public function seePsalmVersionIs(string $operator, string $version): bool
+ {
+ return $this->packageSatisfiesVersionConstraint('vimeo/psalm', $operator . $version);
+ }
+
+ /**
+ * @Given I have the following code preamble :code
+ */
+ public function haveTheFollowingCodePreamble(PyStringNode $code): void
+ {
+ $this->preamble = $code->getRaw();
+ }
+
+ /**
+ * @When I run psalm
+ * @When I run Psalm
+ */
+ public function runPsalm(): void
+ {
+ $this->runPsalmIn($this->config['default_dir']);
+ }
+
+ /**
+ * @When I run Psalm with dead code detection
+ * @When I run psalm with dead code detection
+ */
+ public function runPsalmWithDeadCodeDetection(): void
+ {
+ $this->runPsalmIn($this->config['default_dir'], ['--find-dead-code']);
+ }
+
+ public function seePsalmHasTaintAnalysis(): bool
+ {
+ $taintAnalysisAvailable = $this->packageSatisfiesVersionConstraint('vimeo/psalm', '>=3.10.0');
+ return $taintAnalysisAvailable;
+ }
+
+ /**
+ * @Given I have Psalm with taint analysis
+ * @Given I have psalm with taint analysis
+ */
+ public function havePsalmWithTaintAnalysis(): void
+ {
+ if (!$this->seePsalmHasTaintAnalysis()) {
+ /** @psalm-suppress InternalClass,InternalMethod */
+ throw new SkippedTestError("This scenario requires Psalm with taint analysis (3.10+)");
+ }
+ }
+
+ /**
+ * @When I run Psalm with taint analysis
+ * @When I run psalm with taint analysis
+ */
+ public function runPsalmWithTaintAnalysis(): void
+ {
+ if (!$this->seePsalmHasTaintAnalysis()) {
+ Assert::fail('Taint analysis is available since 3.10.0');
+ }
+ $this->runPsalmIn($this->config['default_dir'], ['--track-tainted-input']);
+ }
+
+ /**
+ * @When I run Psalm on :arg1
+ * @When I run psalm on :arg1
+ */
+ public function runPsalmOnASingleFile(string $file): void
+ {
+ $pwd = getcwd();
+ $this->fs()->amInPath($this->config['default_dir']);
+
+ $config = $this->psalmConfig ?: self::DEFAULT_PSALM_CONFIG;
+ $config = sprintf($config, $this->hasAutoload ? 'autoloader="autoload.php"' : '');
+
+ $this->fs()->writeToFile('psalm.xml', $config);
+
+ $this->runPsalmOn($file);
+ $this->fs()->amInPath($pwd);
+ }
+
+
+ /**
+ * @Given I have the following config :config
+ */
+ public function haveTheFollowingConfig(PyStringNode $config): void
+ {
+ $this->psalmConfig = $config->getRaw();
+ }
+
+ /**
+ * @Given I have the following code :code
+ */
+ public function haveTheFollowingCode(PyStringNode $code): void
+ {
+ $file = sprintf(
+ '%s/%s.php',
+ rtrim($this->config['default_dir'], '/'),
+ sha1($this->preamble . $code->getRaw())
+ );
+
+ $this->fs()->writeToFile(
+ $file,
+ $this->preamble . $code
+ );
+ }
+
+ /**
+ * @Given I have some future Psalm that supports this feature :ref
+ */
+ public function haveSomeFuturePsalmThatSupportsThisFeature(string $ref): void
+ {
+ /** @psalm-suppress InternalClass,InternalMethod */
+ throw new SkippedTestError("Future functionality that Psalm has yet to support: $ref");
+ }
+
+ /**
+ * @Given /I have Psalm (newer than|older than) "([0-9.]+)" \(because of "([^"]+)"\)/
+ */
+ public function havePsalmOfACertainVersionRangeBecauseOf(string $operator, string $version, string $reason): void
+ {
+ if (!isset(self::VERSION_OPERATORS[$operator])) {
+ throw new TestRuntimeException("Unknown operator: $operator");
+ }
+
+ /**
+ * @psalm-suppress RedundantCondition it's not redundant with older Psalm version
+ * @psalm-suppress RedundantCast
+ */
+ $op = (string) self::VERSION_OPERATORS[$operator];
+
+ if (!$this->packageSatisfiesVersionConstraint('vimeo/psalm', $op . $version)) {
+ /** @psalm-suppress InternalClass,InternalMethod */
+ throw new SkippedTestError("This scenario requires Psalm $op $version because of $reason");
+ }
+ }
+
+ /**
+ * @Then I see these errors
+ */
+ public function seeTheseErrors(TableNode $list): void
+ {
+ /** @psalm-suppress MixedAssignment */
+ foreach (array_values($list->getRows()) as $i => $error) {
+ assert(is_array($error));
+ if (0 === $i) {
+ continue;
+ }
+ $this->seeThisError((string) $error[0], (string) $error[1]);
+ }
+ }
+
+ /**
+ * @Given I have the following code in :arg1 :arg2
+ */
+ public function haveTheFollowingCodeIn(string $filename, string $code): void
+ {
+ $file = rtrim($this->config['default_dir'], '/') . '/' . $filename;
+ $this->fs()->writeToFile($file, $code);
+ }
+
+ /**
+ * @Given I have the following autoload map
+ * @Given I have the following classmap
+ * @Given I have the following class map
+ */
+ public function haveTheFollowingAutoloadMap(TableNode $list): void
+ {
+ $map = [];
+ /** @psalm-suppress MixedAssignment */
+ foreach (array_values($list->getRows()) as $i => $row) {
+ assert(is_array($row));
+ if (0 === $i) {
+ continue;
+ }
+ assert(is_string($row[0]));
+ assert(is_string($row[1]));
+ $map[] = [$row[0], $row[1]];
+ }
+
+ $code = sprintf(
+ ' $classes */
+ static $classes = null;
+ if (null === $classes) {
+ $classes = [%s];
+ }
+ if (array_key_exists($class, $classes)) {
+ /** @psalm-suppress UnresolvableInclude */
+ include $classes[$class];
+ }
+ });',
+ join(
+ ',',
+ array_map(
+ function (array $row): string {
+ return "\n'$row[0]' => '$row[1]'";
+ },
+ $map
+ )
+ )
+ );
+ $file = rtrim($this->config['default_dir'], '/') . '/' . 'autoload.php';
+ $this->fs()->writeToFile($file, $code);
+ $this->hasAutoload = true;
+ }
+
+ /**
+ * @Given I have the :package package satisfying the :versionConstraint
+ */
+ public function haveADependencySatisfied(string $package, string $versionConstraint): void
+ {
+ if ($this->packageSatisfiesVersionConstraint($package, $versionConstraint)) {
+ return;
+ }
+
+ /** @psalm-suppress InternalClass,InternalMethod */
+ throw new SkippedTestError("This scenario requires $package to match $versionConstraint");
+ }
+
+ private function convertToRegexp(string $in): string
+ {
+ return '@' . str_replace('%', '.*', preg_quote($in, '@')) . '@';
+ }
+
+ private function cli(): Cli
+ {
+ if (null === $this->cli) {
+ $cli = $this->getModule('Cli');
+ if (!$cli instanceof Cli) {
+ throw new ModuleRequireException($this, 'Needs Cli module');
+ }
+ $this->cli = $cli;
+ }
+ return $this->cli;
+ }
+
+ private function fs(): Filesystem
+ {
+ if (null === $this->fs) {
+ $fs = $this->getModule('Filesystem');
+ if (!$fs instanceof Filesystem) {
+ throw new ModuleRequireException($this, 'Needs Filesystem module');
+ }
+ $this->fs = $fs;
+ }
+ return $this->fs;
+ }
+
+ private function remainingErrors(): string
+ {
+ $this->parseErrors();
+ return (string) new TableNode(array_map(
+ function (array $error): array {
+ return [
+ 'type' => $error['type'] ?? '',
+ 'message' => $error['message'] ?? '',
+ ];
+ },
+ $this->errors
+ ));
+ }
+
+ private function getShortVersion(string $package): string
+ {
+ /** @psalm-suppress DeprecatedClass Versions is marked deprecated for no good reason */
+ if (class_exists(InstalledVersions::class)) {
+ /** @psalm-suppress UndefinedClass Composer\InstalledVersions is undefined when using Composer 1.x */
+ return (string) InstalledVersions::getPrettyVersion($package);
+ } elseif (class_exists(Versions::class)) {
+ /**
+ * @psalm-suppress ArgumentTypeCoercion Versions::getVersion() has too narrow a signature
+ * @psalm-suppress RedundantCondition not redundant with older Psalm
+ * @psalm-suppress RedundantCast
+ */
+ $version = (string) Versions::getVersion($package);
+ } else {
+ throw new RuntimeException(
+ 'Cannot determine versions. Neither of composer:2+,'
+ . ' ocramius/package-version or composer/package-versions-deprecated are installed.'
+ );
+ }
+
+ if (false === strpos($version, '@')) {
+ throw new RuntimeException('$version must contain @');
+ }
+
+ return explode('@', $version)[0];
+ }
+
+ /**
+ * @psalm-assert !null $this->errors
+ */
+ private function parseErrors(): void
+ {
+ if (null !== $this->errors) {
+ return;
+ }
+
+ if (empty($this->output)) {
+ $this->errors = [];
+ return;
+ }
+
+ /** @psalm-suppress MixedAssignment */
+ $errors = json_decode($this->output, true);
+
+ if (null === $errors && json_last_error() !== JSON_ERROR_NONE && 0 !== $this->exitCode) {
+ Assert::fail("Failed to parse output: " . $this->output . "\nError:" . json_last_error_msg());
+ }
+
+ $this->errors = array_map(
+ function (array $row): array {
+ return [
+ 'type' => (string) $row['type'],
+ 'message' => (string) $row['message'],
+ ];
+ },
+ array_values((array)$errors)
+ );
+ $this->debug($this->remainingErrors());
+ }
+}
diff --git a/tests/acceptance.suite.yml b/tests/acceptance.suite.yml
index 046e9300..22405c44 100644
--- a/tests/acceptance.suite.yml
+++ b/tests/acceptance.suite.yml
@@ -3,4 +3,4 @@ modules:
enabled:
- Cli
- Filesystem
- - \Weirdan\Codeception\Psalm\Module
+ - \Tests\Psalm\LaravelPlugin\Support\Module
diff --git a/tests/acceptance/EloquentBuilderTypes.feature b/tests/acceptance/EloquentBuilderTypes.feature
index fc3220f2..c84ae0df 100644
--- a/tests/acceptance/EloquentBuilderTypes.feature
+++ b/tests/acceptance/EloquentBuilderTypes.feature
@@ -132,7 +132,7 @@ Feature: Eloquent Builder types
When I run Psalm
Then I see no errors
- Scenario:
+ Scenario: Unknown Scenario
Given I have the following code preamble
"""
= 8.0"
+ Scenario: can call firstOrNew and firstOrCreate without parameters in Laravel 9.x
+ Given I have the "laravel/framework" package satisfying the "^9.0"
And I have the following code
"""
/**
* @psalm-param Builder $builder
- * @psalm-return User
+ * @psalm-return Builder|User
*/
- function test_firstOrCreate(Builder $builder): User {
+ function test_firstOrCreate(Builder $builder): Builder|User {
return $builder->firstOrCreate();
}
/**
* @psalm-param Builder $builder
- * @psalm-return User
+ * @psalm-return Builder|User
*/
- function test_firstOrNew(Builder $builder): User {
+ function test_firstOrNew(Builder $builder): Builder|User {
return $builder->firstOrNew();
}
"""
diff --git a/tests/acceptance/EloquentCollectionTypes.feature b/tests/acceptance/EloquentCollectionTypes.feature
index 94367e5a..214861ad 100644
--- a/tests/acceptance/EloquentCollectionTypes.feature
+++ b/tests/acceptance/EloquentCollectionTypes.feature
@@ -16,7 +16,7 @@ Feature: Eloquent Collection types
"""
- Scenario:
+ Scenario: Unknown Scenario
Given I have the following code
"""
phone()->firstOrCreate();
- }
-
- function test_hasOne_firstOrNew(User $user): Phone {
- return $user->phone()->firstOrNew();
- }
-
- function test_hasMany_firstOrCreate(Post $post): Comment {
- return $post->comments()->firstOrCreate();
- }
-
- function test_hasMany_firstOrNew(Post $post): Comment {
- return $post->comments()->firstOrNew();
- }
- """
- When I run Psalm
- Then I see these errors
- | Type | Message |
- | TooFewArguments | Too few arguments for method Illuminate\Database\Eloquent\Relations\HasOneOrMany::firstorcreate saw 0 |
- | TooFewArguments | Too few arguments for method Illuminate\Database\Eloquent\Relations\HasOneOrMany::firstornew saw 0 |
- | TooFewArguments | Too few arguments for method Illuminate\Database\Eloquent\Relations\HasOneOrMany::firstorcreate saw 0 |
- | TooFewArguments | Too few arguments for method Illuminate\Database\Eloquent\Relations\HasOneOrMany::firstornew saw 0 |
-
-
Scenario: can call firstOrNew and firstOrCreate without parameters in Laravel 8.x
Given I have the "laravel/framework" package satisfying the ">= 8.0"
And I have the following code
diff --git a/tests/acceptance/ExceptionHandler.feature b/tests/acceptance/ExceptionHandler.feature
index 9d554730..20a950b3 100644
--- a/tests/acceptance/ExceptionHandler.feature
+++ b/tests/acceptance/ExceptionHandler.feature
@@ -17,7 +17,7 @@ Feature: ExceptionHandler
"""
- Scenario:
+ Scenario: Unknown Scenario
Given I have the following code
"""
- */
- public function getFactory(): FactoryBuilder
- {
- return factory(User::class);
- }
-
- /**
- * @return FactoryBuilder
- */
- public function getFactoryForTwo(): FactoryBuilder
- {
- return factory(User::class, 2);
- }
-
- public function makeUser(): User
- {
- return factory(User::class)->make();
- }
-
- public function createUser(): User
- {
- return factory(User::class)->create();
- }
-
- /**
- * @return Collection
- */
- public function createUsers(): Collection
- {
- return factory(User::class, 2)->create();
- }
-
- /**
- * @return Collection
- */
- public function createUsersWithNameAttribute(): Collection
- {
- return factory(User::class, 'new name', 2)->create();
- }
- }
- """
- When I run Psalm
- Then I see no errors
-
Scenario: cannot use factory helper in Laravel 8.x and later
Given I have the "laravel/framework" package satisfying the ">= 8.0"
And I have the following code
diff --git a/tests/acceptance/RedirectReturnType.feature b/tests/acceptance/RedirectReturnType.feature
index 9b66a55b..d48518cf 100644
--- a/tests/acceptance/RedirectReturnType.feature
+++ b/tests/acceptance/RedirectReturnType.feature
@@ -16,7 +16,7 @@ Feature: redirect()
"""
- Scenario:
+ Scenario: Unknown Scenario
Given I have the following code
"""
"""
- Scenario:
+ Scenario: Unknown Scenario
Given I have the following code
"""