ci: Add psalm CI workflow and composer script

Signed-off-by: Ferdinand Thiessen <rpm@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2023-03-21 16:50:28 +01:00 коммит произвёл Jonas (Rebase PR Action)
Родитель 0c9b41de33
Коммит ea3e6fb470
6 изменённых файлов: 367 добавлений и 9 удалений

39
.github/workflows/psalm.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,39 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
name: Static analysis
on:
pull_request:
push:
branches:
- main
concurrency:
group: psalm-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
static-analysis:
runs-on: ubuntu-latest
name: Nextcloud
steps:
- name: Checkout
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
- name: Set up php
uses: shivammathur/setup-php@1a18b2267f80291a81ca1d33e7c851fe09e7dfc4 # v2
with:
php-version: 8.1
coverage: none
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install dependencies
run: composer i
- name: Run coding standards check
run: composer run psalm

Просмотреть файл

@ -40,6 +40,7 @@ You run several tests by:
- `composer cs:check` for the Nextcloud php coding standard
- `composer lint` for php linting
- `composer test:unit` and `composer test:integration` to run the php functionality tests
- `composer psalm` for static code analysis
## ♥ How to create a pull request

Просмотреть файл

@ -16,13 +16,15 @@
"cs:check": "php-cs-fixer fix --dry-run --diff",
"lint": "find . -name \\*.php -not -path './vendor*/*' -print0 | xargs -0 -n1 php -l",
"test:unit": "phpunit -c tests/phpunit.xml",
"test:integration": "phpunit -c tests/phpunit.integration.xml"
"test:integration": "phpunit -c tests/phpunit.integration.xml",
"psalm": "psalm.phar"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.5",
"guzzlehttp/guzzle": "^7.4",
"nextcloud/ocp": "^25.0.4",
"phpunit/phpunit": "^9",
"guzzlehttp/guzzle": "^7.4"
"psalm/phar": "^5.8"
},
"require": {
"league/csv": "^9.8.0"

49
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": "16eb0d3fc3f2e5293da55cba45f8b9c0",
"content-hash": "9ec06bd6b5594642a5f940f7e07ff85e",
"packages": [
{
"name": "league/csv",
@ -433,16 +433,16 @@
},
{
"name": "guzzlehttp/psr7",
"version": "2.4.1",
"version": "2.4.4",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379"
"reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379",
"reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf",
"reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf",
"shasum": ""
},
"require": {
@ -532,7 +532,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.4.1"
"source": "https://github.com/guzzle/psr7/tree/2.4.4"
},
"funding": [
{
@ -548,7 +548,7 @@
"type": "tidelift"
}
],
"time": "2022-08-28T14:45:39+00:00"
"time": "2023-03-09T13:19:02+00:00"
},
{
"name": "myclabs/deep-copy",
@ -1240,6 +1240,41 @@
],
"time": "2023-03-27T11:43:46+00:00"
},
{
"name": "psalm/phar",
"version": "5.8.0",
"source": {
"type": "git",
"url": "https://github.com/psalm/phar.git",
"reference": "6473ecb1ea109a96e69c98b93310eace0d583cc7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/psalm/phar/zipball/6473ecb1ea109a96e69c98b93310eace0d583cc7",
"reference": "6473ecb1ea109a96e69c98b93310eace0d583cc7",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"conflict": {
"vimeo/psalm": "*"
},
"bin": [
"psalm.phar"
],
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Composer-based Psalm Phar",
"support": {
"issues": "https://github.com/psalm/phar/issues",
"source": "https://github.com/psalm/phar/tree/5.8.0"
},
"time": "2023-03-10T03:53:59+00:00"
},
{
"name": "psr/container",
"version": "1.1.2",

34
psalm.xml Normal file
Просмотреть файл

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<psalm
errorLevel="4"
resolveFromConfigFile="true"
totallyTyped="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorBaseline="tests/psalm-baseline.xml"
>
<projectFiles>
<directory name="lib" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<extraFiles>
<directory name="vendor" />
<ignoreFiles>
<directory name="vendor/phpunit/php-code-coverage" />
<directory name="vendor/psalm" />
</ignoreFiles>
</extraFiles>
<issueHandlers>
<UndefinedDocblockClass>
<errorLevel type="suppress">
<referencedClass name="Doctrine\DBAL\Schema\Schema" />
<referencedClass name="Doctrine\DBAL\Schema\SchemaException" />
<referencedClass name="Doctrine\DBAL\Driver\Statement" />
<referencedClass name="Doctrine\DBAL\Schema\Table" />
</errorLevel>
</UndefinedDocblockClass>
</issueHandlers>
</psalm>

247
tests/psalm-baseline.xml Normal file
Просмотреть файл

@ -0,0 +1,247 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.8.0@9cf4f60a333f779ad3bc704a555920e81d4fdcda">
<file src="lib/Activity/Provider.php">
<InvalidCatch>
<code><![CDATA[try {
// Overwrite formTitle if form is found (i.e. still exists).
$formTitle = $this->formMapper->findbyHash($formHash)->getTitle();
// Append hash and route
$formLink .= $formHash;
if ($route !== '') {
$formLink .= '/' . $route;
}
} catch (IMapperException $e) {
// Ignore if not found, just use stored title
}]]></code>
</InvalidCatch>
</file>
<file src="lib/AppInfo/Application.php">
<InvalidArgument>
<code>UserDeletedListener::class</code>
</InvalidArgument>
</file>
<file src="lib/Controller/ApiController.php">
<InvalidCatch>
<code><![CDATA[try {
$questions[$arrayKey] = $this->questionMapper->findById($questionId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find question. Id:{id}', [
'id' => $questionId
]);
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$form = $this->formMapper->findByHash($hash);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$form = $this->formMapper->findByHash($hash);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$form = $this->formMapper->findByHash($hash);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$form = $this->formMapper->findByHash($hash);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$form = $this->formMapper->findById($formId);
$questions = $this->formsService->getQuestions($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$form = $this->formMapper->findById($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$form = $this->formMapper->findById($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$form = $this->formMapper->findById($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$form = $this->formMapper->findById($id);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$form = $this->formMapper->findById($id);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$form = $this->formsService->getForm($id);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$oldForm = $this->formMapper->findById($id);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}]]></code>
<code><![CDATA[try {
$option = $this->optionMapper->findById($id);
$question = $this->questionMapper->findById($option->getQuestionId());
$form = $this->formMapper->findById($question->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or option');
throw new OCSBadRequestException('Could not find form or option');
}]]></code>
<code><![CDATA[try {
$option = $this->optionMapper->findById($id);
$question = $this->questionMapper->findById($option->getQuestionId());
$form = $this->formMapper->findById($question->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find option, question or form');
throw new OCSBadRequestException('Could not find option, question or form');
}]]></code>
<code><![CDATA[try {
$question = $this->questionMapper->findById($id);
$form = $this->formMapper->findById($question->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or question');
throw new OCSBadRequestException('Could not find form or question');
}]]></code>
<code><![CDATA[try {
$question = $this->questionMapper->findById($id);
$form = $this->formMapper->findById($question->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or question');
throw new OCSBadRequestException('Could not find form or question');
}]]></code>
<code><![CDATA[try {
$question = $this->questionMapper->findById($questionId);
$form = $this->formMapper->findById($question->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or question');
throw new OCSBadRequestException('Could not find form or question');
}]]></code>
<code><![CDATA[try {
$submission = $this->submissionMapper->findById($id);
$form = $this->formMapper->findById($submission->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or submission');
throw new OCSBadRequestException();
}]]></code>
</InvalidCatch>
</file>
<file src="lib/Controller/ShareApiController.php">
<InvalidCatch>
<code><![CDATA[try {
$form = $this->formMapper->findById($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form', ['exception' => $e]);
throw new OCSBadRequestException('Could not find form');
}]]></code>
<code><![CDATA[try {
$share = $this->shareMapper->findById($id);
$form = $this->formMapper->findById($share->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find share', ['exception' => $e]);
throw new OCSBadRequestException('Could not find share');
}]]></code>
<code><![CDATA[try {
$share = $this->shareMapper->findById($id);
$form = $this->formMapper->findById($share->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find share', ['exception' => $e]);
throw new OCSBadRequestException('Could not find share');
}]]></code>
</InvalidCatch>
</file>
<file src="lib/Db/Form.php">
<UndefinedMagicMethod>
<code>getAccessJson</code>
<code>setAccessJson</code>
</UndefinedMagicMethod>
</file>
<file src="lib/Db/Question.php">
<UndefinedMagicMethod>
<code>getExtraSettingsJson</code>
<code>setExtraSettingsJson</code>
</UndefinedMagicMethod>
</file>
<file src="lib/Db/Share.php">
<UndefinedMagicMethod>
<code>getPermissionsJson</code>
<code>setPermissionsJson</code>
</UndefinedMagicMethod>
</file>
<file src="lib/FormsMigrator.php">
<UndefinedClass>
<code>OutputInterface</code>
<code>OutputInterface</code>
</UndefinedClass>
</file>
<file src="lib/Middleware/PublicCorsMiddleware.php">
<NoInterfaceProperties>
<code><![CDATA[$this->request->server]]></code>
</NoInterfaceProperties>
<UndefinedClass>
<code>SecurityException</code>
<code>SecurityException</code>
</UndefinedClass>
<UndefinedDocblockClass>
<code>SecurityException</code>
</UndefinedDocblockClass>
</file>
<file src="lib/Migration/Version020300Date20210403214012.php">
<InvalidOperand>
<code>$result</code>
<code><![CDATA[$this->ensureColumnIsNullable($schema, 'forms_v2_questions', 'mandatory')]]></code>
</InvalidOperand>
</file>
<file src="lib/Migration/Version030000Date20220402100057.php">
<UndefinedClass>
<code>Type</code>
</UndefinedClass>
</file>
<file src="lib/Migration/Version030100Date20230115214242.php">
<InvalidOperand>
<code>$result</code>
<code><![CDATA[$this->ensureColumnIsNullable($schema, 'forms_v2_options', 'text')]]></code>
<code><![CDATA[$this->ensureColumnIsNullable($schema, 'forms_v2_questions', 'order')]]></code>
<code><![CDATA[$this->ensureColumnIsNullable($schema, 'forms_v2_questions', 'text')]]></code>
<code><![CDATA[$this->ensureColumnIsNullable($schema, 'forms_v2_shares', 'share_type')]]></code>
</InvalidOperand>
</file>
<file src="lib/Service/FormsService.php">
<InvalidThrow>
<code>IMapperException</code>
<code>IMapperException</code>
<code>IMapperException</code>
</InvalidThrow>
</file>
<file src="lib/Service/SubmissionService.php">
<MissingDependency>
<code><![CDATA[$this->storage]]></code>
<code>IRootFolder</code>
<code>IRootFolder</code>
</MissingDependency>
</file>
</files>