diff --git a/.vscode/launch.json b/.vscode/launch.json index 68c578734..7ab804b3f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,23 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Python: Remote Attach", + "type": "python", + "request": "attach", + "connect": + { + "host":"localhost", + "port":5678 + }, + "pathMappings": [ + { + "localRoot":"${workspaceFolder:cookbook}/.github/actions/run-tests", + "remoteRoot":"." + } + ], + "justMyCode":true + }, //{ // "name": "Xdebug on 9000", // "type": "php", @@ -14,6 +31,16 @@ // "/var/www/html": "${workspaceFolder:base}" // } //}, + { + "name": "Xdebug PHPUnit", + "type": "php", + "request": "launch", + "port": 9000, + "pathMappings": { + "/var/www/html/custom_apps/cookbook": "${workspaceFolder:cookbook}", + "/var/www/html": "${workspaceFolder:base}" + } + }, { "name": "Xdebug on 9003", "type": "php", diff --git a/CHANGELOG.md b/CHANGELOG.md index d49435b98..da8c1ba52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ [#1691](https://github.com/nextcloud/cookbook/pull/1691) @christianlupus - Fix some comments and updated PHP coding style [#1710](https://github.com/nextcloud/cookbook/pull/1710) @dependabot @christianlupus +- Update Psalm and fix some introduced issues + [#1707](https://github.com/nextcloud/cookbook/pull/1707) @christianlupus ## 0.10.2 - 2023-03-24 diff --git a/composer.json b/composer.json index f92f99e20..672d8a806 100644 --- a/composer.json +++ b/composer.json @@ -14,18 +14,20 @@ "require-dev": { "nextcloud/coding-standard": "^1.0.0", "christophwurst/nextcloud_testing": "^0.12.4", - "psalm/phar": "^4.10", + "psalm/phar": "^5.12", "nextcloud/ocp": "^25.0" }, "scripts": { "cs:check": "./vendor/bin/php-cs-fixer fix --dry-run --diff", "cs:fix": "./vendor/bin/php-cs-fixer fix", "lint:lint": "find . -name '*.php' -not -path './vendor/*' -not -path './.github/*' -not -path './node_modules/*' -not -path './tests/phpunit/*' -print0 | xargs -0 -n1 php -l", - "psalm": "psalm.phar --threads=1", - "psalm:update-baseline": "psalm.phar --threads=1 --update-baseline", - "psalm:update-baseline:force": "psalm.phar --threads=1 --update-baseline --set-baseline=tests/psalm-baseline.xml", + "psalm": "psalm.phar --threads=1 --no-diff", + "psalm:nobaseline": "psalm.phar --threads=1 --no-diff --ignore-baseline", + "psalm:nocache": "psalm.phar --no-cache --threads=1", + "psalm:update-baseline": "psalm.phar --threads=1 --no-diff --update-baseline", + "psalm:update-baseline:force": "psalm.phar --threads=1 --no-diff --update-baseline --set-baseline=tests/psalm-baseline.xml", "psalm:clear": "psalm.phar --clear-cache && psalm.phar --clear-global-cache", - "psalm:fix": "psalm.phar --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType" + "psalm:fix": "psalm.phar --alter --no-cache --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType" }, "config": { "platform": { diff --git a/composer.lock b/composer.lock index 340d54300..c7ab796df 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a9c310cb64ad580fa39354e7ede6801a", + "content-hash": "1b559c286a22bb728a5d84d2c7942f3e", "packages": [], "packages-dev": [ { @@ -967,16 +967,16 @@ }, { "name": "psalm/phar", - "version": "4.30.0", + "version": "5.12.0", "source": { "type": "git", "url": "https://github.com/psalm/phar.git", - "reference": "33723713902e1345904a5c9064ef7848bee0d490" + "reference": "e7f9306ec83c706b4dba451f6adfb865ce4688e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psalm/phar/zipball/33723713902e1345904a5c9064ef7848bee0d490", - "reference": "33723713902e1345904a5c9064ef7848bee0d490", + "url": "https://api.github.com/repos/psalm/phar/zipball/e7f9306ec83c706b4dba451f6adfb865ce4688e4", + "reference": "e7f9306ec83c706b4dba451f6adfb865ce4688e4", "shasum": "" }, "require": { @@ -996,9 +996,9 @@ "description": "Composer-based Psalm Phar", "support": { "issues": "https://github.com/psalm/phar/issues", - "source": "https://github.com/psalm/phar/tree/4.30.0" + "source": "https://github.com/psalm/phar/tree/5.12.0" }, - "time": "2022-11-06T20:41:58+00:00" + "time": "2023-05-22T21:30:41+00:00" }, { "name": "psr/container", diff --git a/cookbook.code-workspace b/cookbook.code-workspace index 4a979312b..2fec2534a 100644 --- a/cookbook.code-workspace +++ b/cookbook.code-workspace @@ -57,6 +57,8 @@ }, "intelephense.environment.includePaths": [ "${workspaceFolder:base}/3rdparty/doctrine/dbal/src" - ] + ], + "psalm.phpExecutablePath": "", + "psalm.psalmScriptPath": "vendor/bin/psalm.phar" } } diff --git a/lib/Controller/RecipeController.php b/lib/Controller/RecipeController.php index 0302c3f0f..31af18c3d 100755 --- a/lib/Controller/RecipeController.php +++ b/lib/Controller/RecipeController.php @@ -4,6 +4,8 @@ use OCA\Cookbook\Controller\Implementation\RecipeImplementation; use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\Response; use OCP\IRequest; class RecipeController extends Controller { @@ -72,7 +74,7 @@ public function destroy($id) { * @NoAdminRequired * @NoCSRFRequired * @param $id - * @return JSONResponse|FileDisplayResponse|DataDisplayResponse + * @return Response */ public function image($id) { return $this->impl->image($id); diff --git a/lib/Db/RecipeDb.php b/lib/Db/RecipeDb.php index 3fe305ee6..ed75eebf6 100755 --- a/lib/Db/RecipeDb.php +++ b/lib/Db/RecipeDb.php @@ -122,7 +122,7 @@ public function findAllRecipes(string $user_id) { return $this->unique($recipesGroupedTags); } - private function mapDbNames($results) { + private function mapDbNames(array $results) { return array_map(function ($x) { $x['dateCreated'] = $x['date_created']; $x['dateModified'] = $x['date_modified']; @@ -347,7 +347,7 @@ public function getRecipesByKeywords(string $keywords, string $user_id) { * @throws \OCP\AppFramework\Db\DoesNotExistException if not found */ public function findRecipes(array $keywords, string $user_id) { - $has_keywords = $keywords && is_array($keywords) && sizeof($keywords) > 0 && $keywords[0]; + $has_keywords = $keywords && is_array($keywords) && $keywords[0]; if (!$has_keywords) { return $this->findAllRecipes($user_id); @@ -453,7 +453,7 @@ private function isRecipeEmpty($json) { * @param array $ids */ public function deleteRecipes(array $ids, string $userId) { - if (!is_array($ids) || empty($ids)) { + if (empty($ids)) { return; } @@ -488,7 +488,7 @@ public function deleteRecipes(array $ids, string $userId) { * @param array $recipes */ public function insertRecipes(array $recipes, string $userId) { - if (!is_array($recipes) || empty($recipes)) { + if (empty($recipes)) { return; } @@ -520,7 +520,7 @@ public function insertRecipes(array $recipes, string $userId) { } public function updateRecipes(array $recipes, string $userId) { - if (!is_array($recipes) || empty($recipes)) { + if (empty($recipes)) { return; } @@ -635,7 +635,7 @@ public function removeCategoryOfRecipe(int $recipeId, string $userId) { } public function addKeywordPairs(array $pairs, string $userId) { - if (!is_array($pairs) || empty($pairs)) { + if (empty($pairs)) { return; } @@ -657,7 +657,7 @@ public function addKeywordPairs(array $pairs, string $userId) { } public function removeKeywordPairs(array $pairs, string $userId) { - if (!is_array($pairs) || empty($pairs)) { + if (empty($pairs)) { return; } diff --git a/lib/Helper/DownloadHelper.php b/lib/Helper/DownloadHelper.php index 0828389be..5eabca994 100644 --- a/lib/Helper/DownloadHelper.php +++ b/lib/Helper/DownloadHelper.php @@ -18,7 +18,7 @@ class DownloadHelper { /** * The content of the last download * - * @var ?string + * @var string */ private $content; /** @@ -30,7 +30,7 @@ class DownloadHelper { /** * The HTTP status of the last download * - * @var ?int + * @var int */ private $status; @@ -43,6 +43,8 @@ public function __construct( $this->downloaded = false; $this->l = $l; $this->headers = []; + $this->status = 0; + $this->content = ''; } /** @@ -94,6 +96,7 @@ public function downloadFile(string $url, array $options = []): void { * Note: You must first trigger the download using downloadFile method. * * @return string The content of the downloaded file + * * @throws NoDownloadWasCarriedOutException if there was no successful download carried out before calling this method. */ public function getContent(): string { @@ -133,6 +136,7 @@ public function getContentType(): ?string { * Note: You must first trigger the download using downloadFile method. * * @return int The HTTP status code + * * @throws NoDownloadWasCarriedOutException if there was no successful download carried out before calling this method. */ public function getStatus(): int { diff --git a/lib/Helper/Filter/AbstractJSONFilter.php b/lib/Helper/Filter/AbstractJSONFilter.php index 68f272e7f..bb5ddbb5c 100644 --- a/lib/Helper/Filter/AbstractJSONFilter.php +++ b/lib/Helper/Filter/AbstractJSONFilter.php @@ -23,7 +23,10 @@ abstract class AbstractJSONFilter { */ abstract public function apply(array &$json): bool; - protected function setJSONValue(array &$json, string $key, $value): bool { + /** + * @param string|int|float|array $value + */ + protected function setJSONValue(array &$json, string $key, string|int|float|array $value): bool { if (!array_key_exists($key, $json)) { $json[$key] = $value; return true; diff --git a/lib/Helper/Filter/JSON/SchemaConformityFilter.php b/lib/Helper/Filter/JSON/SchemaConformityFilter.php index c8845c00a..68f60c21e 100644 --- a/lib/Helper/Filter/JSON/SchemaConformityFilter.php +++ b/lib/Helper/Filter/JSON/SchemaConformityFilter.php @@ -14,6 +14,6 @@ public function apply(array &$json): bool { $changed |= $this->setJSONValue($json, '@context', 'http://schema.org'); $changed |= $this->setJSONValue($json, '@type', 'Recipe'); - return $changed; + return (bool) $changed; } } diff --git a/lib/Helper/HTMLParser/HttpMicrodataParser.php b/lib/Helper/HTMLParser/HttpMicrodataParser.php index 6a5d92643..52963d58f 100644 --- a/lib/Helper/HTMLParser/HttpMicrodataParser.php +++ b/lib/Helper/HTMLParser/HttpMicrodataParser.php @@ -233,7 +233,6 @@ private function searchChildEntries(DOMNode $recipeNode, string $prop): DOMNodeL private function extractAttribute(DOMNodeList $nodes, array $attributes): array { $foundEntries = []; - /** @var $node \DOMElement */ foreach ($nodes as $node) { try { $foundEntries[] = $this->extractSingeAttribute($node, $attributes); diff --git a/lib/Helper/ISO8601DurationHelper.php b/lib/Helper/ISO8601DurationHelper.php index 73803a4e8..5cdb3f3d3 100644 --- a/lib/Helper/ISO8601DurationHelper.php +++ b/lib/Helper/ISO8601DurationHelper.php @@ -47,9 +47,9 @@ private function parseIsoFormat(string $duration): string { $ret = preg_match($pattern, trim($duration), $matches); if ($ret === 1) { - $hours = $matches[1] ?? 0; - $minutes = $matches[2] ?? 0; - $seconds = $matches[3] ?? 0; + $hours = (int)$matches[1]; + $minutes = (int) ($matches[2] ?? 0); + $seconds = (int) ($matches[3] ?? 0); while ($seconds >= 60) { $seconds -= 60; diff --git a/lib/Helper/UserFolderHelper.php b/lib/Helper/UserFolderHelper.php index e278bb0f6..9eb88db47 100644 --- a/lib/Helper/UserFolderHelper.php +++ b/lib/Helper/UserFolderHelper.php @@ -108,7 +108,7 @@ public function getFolder(): Folder { return $this->cache; } - private function getOrCreateFolder($path): Folder { + private function getOrCreateFolder(string $path): Folder { try { $node = $this->root->get($path); } catch (NotFoundException $ex) { diff --git a/lib/Service/DbCacheService.php b/lib/Service/DbCacheService.php index b73cc8a3b..f707579e7 100644 --- a/lib/Service/DbCacheService.php +++ b/lib/Service/DbCacheService.php @@ -37,6 +37,9 @@ class DbCacheService { /** @var NormalizeRecipeFileFilter */ private $normalizeFileFilter; + /** + * @var array + */ private $jsonFiles; private $dbReceipeFiles; private $dbKeywords; @@ -223,7 +226,7 @@ private function compareReceipeLists() { // private function - private function isDbEntryUpToDate($id) { + private function isDbEntryUpToDate(int $id) { $dbEntry = $this->dbReceipeFiles[$id]; $fileEntry = $this->jsonFiles[$id]; diff --git a/lib/Service/HtmlDownloadService.php b/lib/Service/HtmlDownloadService.php index a641a2653..b17042993 100644 --- a/lib/Service/HtmlDownloadService.php +++ b/lib/Service/HtmlDownloadService.php @@ -105,8 +105,11 @@ public function getDom(): ?DOMDocument { /** * Fetch an HTML page from the internet + * * @param string $url The URL of the page to fetch + * * @throws ImportException If the given URL was not fetched + * * @return string The content of the page as a plain string */ private function fetchHtmlPage(string $url): string { diff --git a/lib/Service/RecipeExtractionService.php b/lib/Service/RecipeExtractionService.php index f11a1a4e8..ff80333b5 100644 --- a/lib/Service/RecipeExtractionService.php +++ b/lib/Service/RecipeExtractionService.php @@ -3,7 +3,6 @@ namespace OCA\Cookbook\Service; use OCA\Cookbook\Exception\HtmlParsingException; -use OCA\Cookbook\Helper\HTMLParser\AbstractHtmlParser; use OCA\Cookbook\Helper\HTMLParser\HttpJsonLdParser; use OCA\Cookbook\Helper\HTMLParser\HttpMicrodataParser; use OCP\IL10N; @@ -34,7 +33,6 @@ public function __construct(HttpJsonLdParser $jsonParser, HttpMicrodataParser $m * @return array The data as returned from the parser */ public function parse(\DOMDocument $document, ?string $url): array { - /** @var $parser AbstractHtmlParser */ foreach ($this->parsers as $parser) { try { return $parser->parse($document, $url); diff --git a/lib/Service/RecipeService.php b/lib/Service/RecipeService.php index 9460cdddb..d7a4fe061 100755 --- a/lib/Service/RecipeService.php +++ b/lib/Service/RecipeService.php @@ -14,6 +14,7 @@ use OCA\Cookbook\Helper\ImageService\ImageSize; use OCA\Cookbook\Helper\UserConfigHelper; use OCA\Cookbook\Helper\UserFolderHelper; +use OCP\AppFramework\Db\DoesNotExistException; use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\IRootFolder; @@ -97,7 +98,7 @@ public function __construct( * * @param int $id * - * @return array|null + * @return ?array */ public function getRecipeById(int $id) { $file = $this->getRecipeFileByFolderId($id); @@ -113,10 +114,8 @@ public function getRecipeById(int $id) { * Get a recipe's modification time by its folder id. * * @param int $id - * - * @return int */ - public function getRecipeMTime(int $id) { + public function getRecipeMTime(int $id): ?int { $file = $this->getRecipeFileByFolderId($id); if (!$file) { @@ -184,7 +183,7 @@ public function deleteRecipe(int $id) { $user_folder = $this->userFolder->getFolder(); $recipe_folder = $user_folder->getById($id); - if ($recipe_folder && count($recipe_folder) > 0) { + if ($recipe_folder) { $recipe_folder[0]->delete(); } @@ -462,7 +461,7 @@ public function getRecipesByCategory($category): array { * @param string $keywords Keywords/tags as a comma-separated string. * * @return array - * @throws \OCP\AppFramework\Db\DoesNotExistException + * @throws DoesNotExistException */ public function getRecipesByKeywords($keywords): array { $recipes = $this->db->getRecipesByKeywords($keywords, $this->user_id); @@ -473,11 +472,14 @@ public function getRecipesByKeywords($keywords): array { /** * Search for recipes by keywords * - * @param $keywords_string + * @param string $keywords_string + * * @return array - * @throws \OCP\AppFramework\Db\DoesNotExistException + * + * @throws DoesNotExistException + * */ - public function findRecipesInSearchIndex($keywords_string): array { + public function findRecipesInSearchIndex(string $keywords_string): array { $keywords_string = strtolower($keywords_string); $keywords_array = []; preg_match_all('/[^ ,]+/', $keywords_string, $keywords_array); @@ -535,10 +537,8 @@ public function getVisibleInfoBlocks(): array { * Get recipe file contents as an array * * @param File $file - * - * @return array */ - public function parseRecipeFile($file) { + public function parseRecipeFile($file): ?array { if (!$file) { return null; } diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index 29892bc8c..b08fd8e5e 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -1,121 +1,63 @@ - + - - $e->getMessage() - 'New category name not found in data' + + getMessage()]]> + - - 'OK' - 'Search index rebuilt successfully' + + + - - $e->getMessage() - $e->getMessage() - $e->getMessage() - $e->getMessage() - $e->getMessage() - $file->getParent()->getId() - $file->getParent()->getId() + + getMessage()]]> + getMessage()]]> + getMessage()]]> + getMessage()]]> + getMessage()]]> + getParent()->getId()]]> + getParent()->getId()]]> $id - 'Field "url" is required' - 'Recipe ' . $id . ' deleted successfully' + + - - new JSONResponse($recipes, 200, ['Content-Type' => 'application/json']) - new JSONResponse($recipes, Http::STATUS_OK, ['Content-Type' => 'application/json']) - new JSONResponse($recipes, Http::STATUS_OK, ['Content-Type' => 'application/json']) + + 'application/json'])]]> + 'application/json'])]]> + 'application/json'])]]> - - - $this->impl->create() - $this->impl->destroy($id) - $this->impl->getAllInCategory($category) - $this->impl->getAllWithTags($keywords) - $this->impl->image($id) - $this->impl->search($query) - $this->impl->show($id) - $this->impl->update($id) - - - JSONResponse - JSONResponse - JSONResponse - JSONResponse - JSONResponse - JSONResponse - JSONResponse - JSONResponse|FileDisplayResponse|DataDisplayResponse - - - JSONResponse - JSONResponse - JSONResponse - JSONResponse - JSONResponse - JSONResponse - JSONResponse - JSONResponse|FileDisplayResponse|DataDisplayResponse - - - - $keywords && is_array($keywords) + + is_array($keywords) - - !is_array($ids) - !is_array($pairs) - !is_array($pairs) - !is_array($recipes) - !is_array($recipes) - - - - - int - string - - - $this->content - $this->status - - - - - is_float($json[self::YIELD]) || is_double($json[self::YIELD]) - + + + + $instructions + $newElements + - + $changed - - $this->setJSONValue($json, '@type', 'Recipe') + + setJSONValue($json, '@type', 'Recipe')]]> - - $changed - - - bool - - - $filter->apply($json, $recipeFile) + + apply($json, $recipeFile)]]> - - - $node - - + getAttribute getAttribute getElementsByTagName @@ -123,7 +65,7 @@ - + GenericFileException GenericFileException InvalidPathException @@ -135,30 +77,30 @@ - + array - + $value - - $this->root - $this->root + + root]]> + root]]> IRootFolder IRootFolder - + Provider - + GenericFileException InvalidPathException InvalidThumbnailTypeException @@ -168,33 +110,18 @@ RecipeImageExistsException - - - - $parser - - - - array - int - - - $this->root - $this->root - $this->root - $this->root + + root]]> + root]]> + root]]> + root]]> IRootFolder - - null - null - null - - - !array_key_exists('dateCreated', $json) && method_exists($file, 'getCreationTime') + + - + newFile newFile newFile @@ -202,7 +129,7 @@ - + $img $img $img