Browse Source

:octocat: switch phan for phpstan (see #277)

smiley 1 year ago
parent
commit
89cdd09044
72 changed files with 539 additions and 217 deletions
  1. 21 18
      .gitattributes
  2. 3 1
      .github/workflows/ci.yml
  3. 1 0
      .gitignore
  4. 0 60
      .phan/config.php
  5. 0 9
      .phan/stubs/misc.php
  6. 5 3
      composer.json
  7. 2 0
      examples/imagickImageAsBackground.php
  8. 2 0
      examples/imagickWithLogo.php
  9. 9 11
      examples/qrcode-interactive.php
  10. 4 0
      examples/svgMeltedModules.php
  11. 7 1
      examples/svgRandomColoredDots.php
  12. 7 0
      examples/svgRoundQuietzone.php
  13. 4 0
      examples/svgWithLogo.php
  14. 4 0
      examples/svgWithLogoAndCustomShapes.php
  15. 141 0
      phpstan-baseline.neon
  16. 24 0
      phpstan.dist.neon
  17. 6 2
      src/Common/BitBuffer.php
  18. 2 1
      src/Common/GenericGFPoly.php
  19. 1 0
      src/Common/LuminanceSourceAbstract.php
  20. 2 2
      src/Common/LuminanceSourceInterface.php
  21. 11 3
      src/Common/MaskPattern.php
  22. 1 1
      src/Common/Mode.php
  23. 22 19
      src/Data/AlphaNum.php
  24. 15 20
      src/Data/Number.php
  25. 3 1
      src/Data/QRData.php
  26. 6 3
      src/Data/QRMatrix.php
  27. 5 3
      src/Data/ReedSolomonEncoder.php
  28. 4 0
      src/Decoder/Binarizer.php
  29. 1 1
      src/Decoder/BitMatrix.php
  30. 2 0
      src/Decoder/DecoderResult.php
  31. 11 4
      src/Decoder/ReedSolomonDecoder.php
  32. 1 0
      src/Detector/AlignmentPatternFinder.php
  33. 0 2
      src/Detector/FinderPatternFinder.php
  34. 1 0
      src/Detector/GridSampler.php
  35. 4 1
      src/Detector/PerspectiveTransform.php
  36. 8 6
      src/Output/CssColorModuleValueTrait.php
  37. 2 0
      src/Output/QREps.php
  38. 2 2
      src/Output/QRFpdf.php
  39. 1 1
      src/Output/QRGdImage.php
  40. 14 10
      src/Output/QRMarkupXML.php
  41. 17 4
      src/Output/QROutputAbstract.php
  42. 9 0
      src/Output/QRStringJSON.php
  43. 4 0
      src/Output/QRStringText.php
  44. 11 6
      src/Output/RGBArrayModuleValueTrait.php
  45. 8 2
      src/QRCode.php
  46. 5 0
      src/QRCodeReaderOptionsTrait.php
  47. 57 0
      src/QROptionsTrait.php
  48. 3 0
      tests/Common/BitBufferTest.php
  49. 7 1
      tests/Common/ECICharsetTest.php
  50. 1 1
      tests/Common/EccLevelTest.php
  51. 1 1
      tests/Common/MaskPatternTest.php
  52. 4 2
      tests/Common/ModeTest.php
  53. 1 1
      tests/Common/VersionTest.php
  54. 2 0
      tests/Data/AlphaNumTest.php
  55. 2 0
      tests/Data/ByteTest.php
  56. 7 2
      tests/Data/DataInterfaceTestAbstract.php
  57. 13 3
      tests/Data/ECITest.php
  58. 2 0
      tests/Data/HanziTest.php
  59. 2 0
      tests/Data/KanjiTest.php
  60. 2 0
      tests/Data/NumberTest.php
  61. 0 3
      tests/Output/CssColorModuleValueProviderTrait.php
  62. 1 0
      tests/Output/QREpsTest.php
  63. 1 0
      tests/Output/QRFpdfTest.php
  64. 1 2
      tests/Output/QRGdImageAVIFTest.php
  65. 1 0
      tests/Output/QRGdImageTestAbstract.php
  66. 1 0
      tests/Output/QRImagickTest.php
  67. 1 0
      tests/Output/QRInterventionImageTest.php
  68. 3 0
      tests/Output/QROutputTestAbstract.php
  69. 3 0
      tests/Output/QRStringTextTest.php
  70. 3 0
      tests/QRCodeReaderImagickTest.php
  71. 4 1
      tests/QRCodeReaderTestAbstract.php
  72. 3 3
      tests/QROptionsTest.php

+ 21 - 18
.gitattributes

@@ -1,20 +1,23 @@
-/.build            export-ignore
-/.github           export-ignore
-/.idea             export-ignore
-/.phan             export-ignore
-/.phpdoc           export-ignore
-/benchmark         export-ignore
-/docs              export-ignore
-/examples          export-ignore
-/tests             export-ignore
-/.editorconfig     export-ignore
-/.gitattributes    export-ignore
-/.gitignore        export-ignore
-/.readthedocs.yml  export-ignore
-/phpbench.json     export-ignore
-/phpcs.xml.dist    export-ignore
-/phpdoc.xml.dist   export-ignore
-/phpmd.xml.dist    export-ignore
-/phpunit.xml.dist  export-ignore
+/.build                 export-ignore
+/.github                export-ignore
+/.idea                  export-ignore
+/.phan                  export-ignore
+/.phpdoc                export-ignore
+/benchmark              export-ignore
+/docs                   export-ignore
+/examples               export-ignore
+/tests                  export-ignore
+/.editorconfig          export-ignore
+/.gitattributes         export-ignore
+/.gitignore             export-ignore
+/.readthedocs.yml       export-ignore
+/composer.lock          export-ignore
+/phpbench.json          export-ignore
+/phpcs.xml.dist         export-ignore
+/phpdoc.xml.dist        export-ignore
+/phpmd.xml.dist         export-ignore
+/phpunit.xml.dist       export-ignore
+/phpstan.dist.neon      export-ignore
+/phpstan-baseline.neon  export-ignore
 
 *.php diff=php

+ 3 - 1
.github/workflows/ci.yml

@@ -40,7 +40,7 @@ jobs:
         uses: shivammathur/setup-php@v2
         with:
           php-version: ${{ matrix.php-version }}
-          extensions: ast, ${{ env.PHP_EXTENSIONS }}
+          extensions: ${{ env.PHP_EXTENSIONS }}
           ini-values: ${{ env.PHP_INI_VALUES }}
           coverage: none
 
@@ -49,6 +49,8 @@ jobs:
 
       - name: "Run phan"
         run: php vendor/bin/phan --target-php-version=${{ matrix.php-version }}
+      - name: "Run PHPStan"
+        run: php vendor/bin/phpstan
 
 
   tests:

+ 1 - 0
.gitignore

@@ -18,4 +18,5 @@ phpcs.xml
 phpdoc.xml
 phpmd.xml
 phpunit.xml
+phpstan.neon
 tests/Performance/*.json

+ 0 - 60
.phan/config.php

@@ -1,60 +0,0 @@
-<?php
-/**
- * This configuration will be read and overlaid on top of the
- * default configuration. Command-line arguments will be applied
- * after this file is read.
- */
-return [
-	// Supported values: `'5.6'`, `'7.0'`, `'7.1'`, `'7.2'`, `'7.3'`,
-	// `'7.4'`, `null`.
-	// If this is set to `null`,
-	// then Phan assumes the PHP version which is closest to the minor version
-	// of the php executable used to execute Phan.
-	//
-	// Note that the **only** effect of choosing `'5.6'` is to infer
-	// that functions removed in php 7.0 exist.
-	// (See `backward_compatibility_checks` for additional options)
-	'target_php_version'              => null,
-	'minimum_target_php_version'      => '8.2',
-
-	// A list of directories that should be parsed for class and
-	// method information. After excluding the directories
-	// defined in exclude_analysis_directory_list, the remaining
-	// files will be statically analyzed for errors.
-	//
-	// Thus, both first-party and third-party code being used by
-	// your application should be included in this list.
-	'directory_list'                  => [
-		'benchmark',
-		'examples',
-		'src',
-		'tests',
-		'vendor',
-		'.phan/stubs',
-	],
-
-	// A regex used to match every file name that you want to
-	// exclude from parsing. Actual value will exclude every
-	// "test", "tests", "Test" and "Tests" folders found in
-	// "vendor/" directory.
-	'exclude_file_regex'              => '@^vendor/.*/(tests?|Tests?)/@',
-
-	// A directory list that defines files that will be excluded
-	// from static analysis, but whose class and method
-	// information should be included.
-	//
-	// Generally, you'll want to include the directories for
-	// third-party code (such as "vendor/") in this list.
-	//
-	// n.b.: If you'd like to parse but not analyze 3rd
-	//       party code, directories containing that code
-	//       should be added to both the `directory_list`
-	//       and `exclude_analysis_directory_list` arrays.
-	'exclude_analysis_directory_list' => [
-		'vendor/',
-		'.phan/stubs',
-	],
-	'suppress_issue_types'            => [
-		'PhanAccessMethodInternal',
-	],
-];

+ 0 - 9
.phan/stubs/misc.php

@@ -1,9 +0,0 @@
-<?php
-/**
- * Miscellaneous stubs for phan
- *
- * @created      25.01.2021
- * @author       smiley <smiley@chillerlan.net>
- * @copyright    2021 smiley
- * @license      MIT
- */

+ 5 - 3
composer.json

@@ -55,9 +55,10 @@
 		"chillerlan/php-authenticator": "^5.2.1",
 		"intervention/image": "^3.7",
 		"phpbench/phpbench": "^1.2.15",
-		"phan/phan": "^5.4",
 		"phpunit/phpunit": "^11.2",
 		"phpmd/phpmd": "^2.15",
+		"phpstan/phpstan": "^1.11",
+		"phpstan/phpstan-deprecation-rules": "^1.2",
 		"setasign/fpdf": "^1.8.2",
 		"squizlabs/php_codesniffer": "^3.10"
 	},
@@ -79,13 +80,14 @@
 		}
 	},
 	"scripts": {
-		"phan": "@php vendor/bin/phan",
 		"phpcs": "@php vendor/bin/phpcs",
 		"phpbench":[
 			"Composer\\Config::disableProcessTimeout",
 			"@php vendor/bin/phpbench run"
 		],
-		"phpunit": "@php vendor/bin/phpunit"
+		"phpunit": "@php vendor/bin/phpunit",
+		"phpstan": "@php vendor/bin/phpstan",
+		"phpstan-baseline": "@php vendor/bin/phpstan --generate-baseline"
 	},
 	"config": {
 		"lock": false,

+ 2 - 0
examples/imagickImageAsBackground.php

@@ -56,6 +56,8 @@ class QRImagickImageAsBackground extends QRImagick{
 
 /**
  * augment the QROptions class
+ *
+ * @property string $background
  */
 class ImageAsBackgroundOptions extends QROptions{
 

+ 2 - 0
examples/imagickWithLogo.php

@@ -65,6 +65,8 @@ class QRImagickWithLogo extends QRImagick{
 
 /**
  * augment the QROptions class
+ *
+ * @property string $pngLogo
  */
 class ImagickWithLogoOptions extends QROptions{
 

+ 9 - 11
examples/qrcode-interactive.php

@@ -13,6 +13,15 @@ use chillerlan\QRCode\Data\QRMatrix;
 
 require_once '../vendor/autoload.php';
 
+/**
+ * @param array<string, mixed> $response
+ */
+function sendResponse(array $response):never{
+	header('Content-type: application/json;charset=utf-8;');
+	echo json_encode($response);
+	exit;
+}
+
 try{
 
 	$moduleValues = [
@@ -89,14 +98,3 @@ catch(Throwable $e){
 	header('HTTP/1.1 500 Internal Server Error');
 	sendResponse(['error' => $e->getMessage()]);
 }
-
-exit;
-
-/**
- * @param array $response
- */
-function sendResponse(array $response){
-	header('Content-type: application/json;charset=utf-8;');
-	echo json_encode($response);
-	exit;
-}

+ 4 - 0
examples/svgMeltedModules.php

@@ -217,6 +217,10 @@ class MeltedSVGQRCodeOutput extends QRMarkupSVG{
 
 /**
  * the augmented options class
+ *
+ * @property bool  $melt
+ * @property bool  $inverseMelt
+ * @property float $meltRadius
  */
 class MeltedOutputOptions extends QROptions{
 

+ 7 - 1
examples/svgRandomColoredDots.php

@@ -81,13 +81,19 @@ class RandomDotsSVGOutput extends QRMarkupSVG{
 
 }
 
-// the extended options with the $dotColors option
+
+/**
+ * the extended options with the $dotColors option
+ *
+ * @property array<int, string> $dotColors
+ */
 class RandomDotsOptions extends QROptions{
 
 	/**
 	 * a map of $M_TYPE_LAYER => color
 	 *
 	 * @see \array_rand()
+	 * @var array<int, string>
 	 */
 	protected array $dotColors = [];
 

+ 7 - 0
examples/svgRoundQuietzone.php

@@ -210,6 +210,12 @@ class RoundQuietzoneSVGoutput extends QRMarkupSVG{
 
 /**
  * the augmented options class
+ *
+ * @property int $additionalModules
+ * @property array<int, string> $dotColors
+ * @property string $svgLogo
+ * @property float  $svgLogoScale
+ * @property string $svgLogoCssClass
  */
 class RoundQuietzoneOptions extends QROptions{
 
@@ -231,6 +237,7 @@ class RoundQuietzoneOptions extends QROptions{
 	 * a map of $M_TYPE_LAYER => color
 	 *
 	 * @see \array_rand()
+	 * @var array<int, string>
 	 */
 	protected array $dotColors = [];
 

+ 4 - 0
examples/svgWithLogo.php

@@ -72,6 +72,10 @@ class QRSvgWithLogo extends QRMarkupSVG{
 
 /**
  * augment the QROptions class
+ *
+ * @property string $svgLogo
+ * @property float  $svgLogoScale
+ * @property string $svgLogoCssClass
  */
 class SVGWithLogoOptions extends QROptions{
 	// path to svg logo

+ 4 - 0
examples/svgWithLogoAndCustomShapes.php

@@ -130,6 +130,10 @@ class QRSvgWithLogoAndCustomShapes extends QRMarkupSVG{
 
 /**
  * augment the QROptions class
+ *
+ * @property string $svgLogo
+ * @property float $svgLogoScale
+ * @property string $svgLogoCssClass
  */
 class SVGWithLogoAndCustomShapesOptions extends QROptions{
 	// path to svg logo

+ 141 - 0
phpstan-baseline.neon

@@ -0,0 +1,141 @@
+parameters:
+	ignoreErrors:
+		-
+			message: "#^Property chillerlan\\\\QRCode\\\\QROptions\\:\\:\\$eccLevel \\(int\\) does not accept string\\.$#"
+			count: 1
+			path: examples/custom_output.php
+
+		-
+			message: "#^Parameter \\#2 \\$color of function imagecolortransparent expects int\\|null, int\\<0, max\\>\\|false given\\.$#"
+			count: 1
+			path: examples/imageWithText.php
+
+		-
+			message: "#^Parameter \\#6 \\$color of function imagechar expects int, int\\<0, max\\>\\|false given\\.$#"
+			count: 1
+			path: examples/imageWithText.php
+
+		-
+			message: "#^Parameter \\#6 \\$color of function imagefilledrectangle expects int, int\\<0, max\\>\\|false given\\.$#"
+			count: 1
+			path: examples/imageWithText.php
+
+		-
+			message: "#^Parameter \\#1 \\(Intervention\\\\Image\\\\Interfaces\\\\ImageInterface\\|string\\) of echo cannot be converted to string\\.$#"
+			count: 1
+			path: examples/intervention-image.php
+
+		-
+			message: "#^Method RandomDotsSVGOutput\\:\\:collectModules\\(\\) should return array\\<int, mixed\\> but returns array\\<int\\|string, array\\<int, mixed\\>\\>\\.$#"
+			count: 1
+			path: examples/svgRandomColoredDots.php
+
+		-
+			message: "#^Method RoundQuietzoneSVGoutput\\:\\:collectModules\\(\\) should return array\\<int, mixed\\> but returns array\\<int\\|string, array\\<int, mixed\\>\\>\\.$#"
+			count: 1
+			path: examples/svgRoundQuietzone.php
+
+		-
+			message: "#^Parameter \\#1 \\$data of function imagecreatefromstring expects string, string\\|false given\\.$#"
+			count: 1
+			path: src/Common/GDLuminanceSource.php
+
+		-
+			message: "#^Parameter \\#1 \\$gdImage of class chillerlan\\\\QRCode\\\\Common\\\\GDLuminanceSource constructor expects GdImage, GdImage\\|false given\\.$#"
+			count: 2
+			path: src/Common/GDLuminanceSource.php
+
+		-
+			message: "#^Parameter \\#2 \\$color of function imagecolorsforindex expects int, int\\<0, max\\>\\|false given\\.$#"
+			count: 1
+			path: src/Common/GDLuminanceSource.php
+
+		-
+			message: "#^Parameter \\#1 \\$maskPattern of class chillerlan\\\\QRCode\\\\Common\\\\MaskPattern constructor expects int, int\\|false given\\.$#"
+			count: 1
+			path: src/Common/MaskPattern.php
+
+		-
+			message: "#^Method chillerlan\\\\QRCode\\\\Common\\\\Version\\:\\:getRSBlocks\\(\\) return type has no value type specified in iterable type array\\.$#"
+			count: 1
+			path: src/Common/Version.php
+
+		-
+			message: "#^Parameter \\#1 \\$encoding of function mb_detect_order expects non\\-empty\\-list\\<non\\-falsy\\-string\\>\\|non\\-falsy\\-string\\|null, array\\{string, 'UTF\\-8', 'GB2312', 'GB18030', 'CP936', 'EUC\\-CN', 'HZ'\\} given\\.$#"
+			count: 1
+			path: src/Data/Hanzi.php
+
+		-
+			message: "#^Parameter \\#1 \\$encoding of function mb_detect_order expects non\\-empty\\-list\\<non\\-falsy\\-string\\>\\|non\\-falsy\\-string\\|null, array\\{string, 'UTF\\-8', 'SJIS', 'SJIS\\-2004'\\} given\\.$#"
+			count: 1
+			path: src/Data/Kanji.php
+
+		-
+			message: "#^Parameter \\#1 \\$version of class chillerlan\\\\QRCode\\\\Data\\\\ReedSolomonEncoder constructor expects chillerlan\\\\QRCode\\\\Common\\\\Version, chillerlan\\\\QRCode\\\\Common\\\\Version\\|null given\\.$#"
+			count: 1
+			path: src/Data/QRMatrix.php
+
+		-
+			message: "#^Parameter \\#2 \\$eccLevel of class chillerlan\\\\QRCode\\\\Data\\\\ReedSolomonEncoder constructor expects chillerlan\\\\QRCode\\\\Common\\\\EccLevel, chillerlan\\\\QRCode\\\\Common\\\\EccLevel\\|null given\\.$#"
+			count: 1
+			path: src/Data/QRMatrix.php
+
+		-
+			message: "#^Parameter \\#1 \\$maskPattern of method chillerlan\\\\QRCode\\\\Data\\\\QRMatrix\\:\\:mask\\(\\) expects chillerlan\\\\QRCode\\\\Common\\\\MaskPattern, chillerlan\\\\QRCode\\\\Common\\\\MaskPattern\\|null given\\.$#"
+			count: 1
+			path: src/Decoder/BitMatrix.php
+
+		-
+			message: "#^Parameter \\#1 \\$version of class chillerlan\\\\QRCode\\\\Data\\\\QRMatrix constructor expects chillerlan\\\\QRCode\\\\Common\\\\Version, chillerlan\\\\QRCode\\\\Common\\\\Version\\|null given\\.$#"
+			count: 1
+			path: src/Decoder/BitMatrix.php
+
+		-
+			message: "#^Parameter \\#2 \\$b of method chillerlan\\\\QRCode\\\\Decoder\\\\BitMatrix\\:\\:numBitsDiffering\\(\\) expects int, int\\|null given\\.$#"
+			count: 1
+			path: src/Decoder/BitMatrix.php
+
+		-
+			message: "#^Parameter \\#2 \\$eccLevel of class chillerlan\\\\QRCode\\\\Data\\\\QRMatrix constructor expects chillerlan\\\\QRCode\\\\Common\\\\EccLevel, chillerlan\\\\QRCode\\\\Common\\\\EccLevel\\|null given\\.$#"
+			count: 1
+			path: src/Decoder/BitMatrix.php
+
+		-
+			message: "#^Property chillerlan\\\\QRCode\\\\Decoder\\\\Decoder\\:\\:\\$options is never read, only written\\.$#"
+			count: 1
+			path: src/Decoder/Decoder.php
+
+		-
+			message: "#^Method chillerlan\\\\QRCode\\\\Decoder\\\\ReedSolomonDecoder\\:\\:deinterleaveRawBytes\\(\\) return type has no value type specified in iterable type array\\.$#"
+			count: 1
+			path: src/Decoder/ReedSolomonDecoder.php
+
+		-
+			message: "#^Method chillerlan\\\\QRCodeTest\\\\Common\\\\MaskPatternTest\\:\\:assertMask\\(\\) has parameter \\$expected with no value type specified in iterable type array\\.$#"
+			count: 1
+			path: tests/Common/MaskPatternTest.php
+
+		-
+			message: "#^Method chillerlan\\\\QRCodeTest\\\\Common\\\\MaskPatternTest\\:\\:maskPatternProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
+			count: 1
+			path: tests/Common/MaskPatternTest.php
+
+		-
+			message: "#^Method chillerlan\\\\QRCodeTest\\\\Common\\\\MaskPatternTest\\:\\:testMask\\(\\) has parameter \\$expected with no value type specified in iterable type array\\.$#"
+			count: 1
+			path: tests/Common/MaskPatternTest.php
+
+		-
+			message: "#^Parameter \\#1 \\$string of static method chillerlan\\\\QRCode\\\\Data\\\\QRDataModeInterface\\:\\:validateString\\(\\) expects string, string\\|false given\\.$#"
+			count: 1
+			path: tests/Data/DataInterfaceTestAbstract.php
+
+		-
+			message: "#^Parameter \\#2 \\$to_encoding of function mb_convert_encoding expects string, string\\|null given\\.$#"
+			count: 1
+			path: tests/Data/ECITest.php
+
+		-
+			message: "#^Parameter \\#1 \\$blob of method chillerlan\\\\QRCode\\\\QRCode\\:\\:readFromBlob\\(\\) expects string, GdImage\\|string given\\.$#"
+			count: 1
+			path: tests/Data/QRDataTest.php

+ 24 - 0
phpstan.dist.neon

@@ -0,0 +1,24 @@
+# https://phpstan.org/config-reference
+
+parameters:
+	level: 8
+	tmpDir: .build/phpstan-cache
+	paths:
+		- examples
+		- src
+		- tests
+
+	treatPhpDocTypesAsCertain: false
+
+	ignoreErrors:
+		# the only place where these instances *may* be null is in the Decoder\BitMatrix class
+		- message: "#^Cannot call method [\\w]+\\(\\) on chillerlan\\\\QRCode\\\\Common\\\\EccLevel\\|null\\.$#"
+		- message: "#^Cannot call method [\\w]+\\(\\) on chillerlan\\\\QRCode\\\\Common\\\\MaskPattern\\|null\\.$#"
+		- message: "#^Cannot call method [\\w]+\\(\\) on chillerlan\\\\QRCode\\\\Common\\\\Version\\|null\\.$#"
+
+
+includes:
+	- phpstan-baseline.neon
+	- vendor/phpstan/phpstan/conf/bleedingEdge.neon
+	- vendor/phpstan/phpstan-deprecation-rules/rules.neon
+	- vendor/chillerlan/php-settings-container/rules-magic-access.neon

+ 6 - 2
src/Common/BitBuffer.php

@@ -42,6 +42,8 @@ final class BitBuffer{
 
 	/**
 	 * BitBuffer constructor.
+	 *
+	 * @param int[] $bytes
 	 */
 	public function __construct(array $bytes = []){
 		$this->buffer = $bytes;
@@ -89,14 +91,16 @@ final class BitBuffer{
 	/**
 	 * returns the buffer content
 	 *
-	 * to debug: array_map(fn($v) => sprintf('%08b', $v), $bitBuffer->getBuffer())
+	 * to debug: `array_map(fn($v) => sprintf('%08b', $v), $bitBuffer->getBuffer())`
+	 *
+	 * @return int[]
 	 */
 	public function getBuffer():array{
 		return $this->buffer;
 	}
 
 	/**
-	 * @return int number of bits that can be read successfully
+	 * Returns the number of bits that can be read successfully
 	 */
 	public function available():int{
 		return ((8 * ($this->length - $this->bytesRead)) - $this->bitsRead);

+ 2 - 1
src/Common/GenericGFPoly.php

@@ -25,10 +25,11 @@ use function array_fill, array_slice, array_splice, count;
  */
 final class GenericGFPoly{
 
+	/** @var int[] */
 	private array $coefficients;
 
 	/**
-	 * @param array      $coefficients array coefficients as ints representing elements of GF(size), arranged
+	 * @param int[]      $coefficients array coefficients as ints representing elements of GF(size), arranged
 	 *                                 from most significant (highest-power term) coefficient to the least significant
 	 * @param int|null   $degree
 	 *

+ 1 - 0
src/Common/LuminanceSourceAbstract.php

@@ -26,6 +26,7 @@ use function array_slice, array_splice, file_exists, is_file, is_readable, realp
 abstract class LuminanceSourceAbstract implements LuminanceSourceInterface{
 
 	protected SettingsContainerInterface|QROptions $options;
+	/** @var int[] */
 	protected array $luminances;
 	protected int   $width;
 	protected int   $height;

+ 2 - 2
src/Common/LuminanceSourceInterface.php

@@ -21,7 +21,7 @@ interface LuminanceSourceInterface{
 	 * Fetches luminance data for the underlying bitmap. Values should be fetched using:
 	 * `int luminance = array[y * width + x] & 0xff`
 	 *
-	 * @return array A row-major 2D array of luminance values. Do not use result $length as it may be
+	 * @return int[] A row-major 2D array of luminance values. Do not use result $length as it may be
 	 *         larger than $width * $height bytes on some platforms. Do not modify the contents
 	 *         of the result.
 	 */
@@ -46,7 +46,7 @@ interface LuminanceSourceInterface{
 	 *
 	 * @param int $y  The row to fetch, which must be in [0,getHeight())
 	 *
-	 * @return array An array containing the luminance data.
+	 * @return int[] An array containing the luminance data.
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
 	 */
 	public function getRow(int $y):array;

+ 11 - 3
src/Common/MaskPattern.php

@@ -139,6 +139,8 @@ final class MaskPattern{
 	/**
 	 * Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and
 	 * give penalty to them. Example: 00000 or 11111.
+	 *
+	 * @param bool[][] $matrix
 	 */
 	public static function testRule1(array $matrix, int $height, int $width):int{
 		$penalty = 0;
@@ -157,7 +159,7 @@ final class MaskPattern{
 	}
 
 	/**
-	 *
+	 * @param bool[] $rc
 	 */
 	private static function applyRule1(array $rc):int{
 		$penalty         = 0;
@@ -191,6 +193,8 @@ final class MaskPattern{
 	 * Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
 	 * penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a
 	 * penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block.
+	 *
+	 * @param bool[][] $matrix
 	 */
 	public static function testRule2(array $matrix, int $height, int $width):int{
 		$penalty = 0;
@@ -224,6 +228,8 @@ final class MaskPattern{
 	 * Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4
 	 * starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them.  If we
 	 * find patterns like 000010111010000, we give penalty once.
+	 *
+	 * @param bool[][] $matrix
 	 */
 	public static function testRule3(array $matrix, int $height, int $width):int{
 		$penalties = 0;
@@ -272,7 +278,7 @@ final class MaskPattern{
 	}
 
 	/**
-	 *
+	 * @param bool[] $row
 	 */
 	private static function isWhiteHorizontal(array $row, int $width, int $from, int $to):bool{
 
@@ -290,7 +296,7 @@ final class MaskPattern{
 	}
 
 	/**
-	 *
+	 * @param bool[][] $matrix
 	 */
 	private static function isWhiteVertical(array $matrix, int $height, int $x, int $from, int $to):bool{
 
@@ -310,6 +316,8 @@ final class MaskPattern{
 	/**
 	 * Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give
 	 * penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance.
+	 *
+	 * @param bool[][] $matrix
 	 */
 	public static function testRule4(array $matrix, int $height, int $width):int{
 		$darkCells  = 0;

+ 1 - 1
src/Common/Mode.php

@@ -58,7 +58,7 @@ final class Mode{
 	/**
 	 * Map of data mode => interface (detection order)
 	 *
-	 * @var string[]
+	 * @var array<int, (\chillerlan\QRCode\Data\QRDataModeInterface|string)>
 	 */
 	public const INTERFACES = [
 		self::NUMBER   => Number::class,

+ 22 - 19
src/Data/AlphaNum.php

@@ -11,7 +11,7 @@
 namespace chillerlan\QRCode\Data;
 
 use chillerlan\QRCode\Common\{BitBuffer, Mode};
-use function array_flip, ceil, intdiv, str_split;
+use function ceil, intdiv, str_split;
 
 /**
  * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / :
@@ -35,6 +35,18 @@ final class AlphaNum extends QRDataModeAbstract{
 		'+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44,
 	];
 
+	/**
+	 * @var string[]
+	 */
+	private const ORD_TO_CHAR = [
+		'0', '1', '2', '3', '4', '5', '6', '7',
+		'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+		'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+		'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+		'W', 'X', 'Y', 'Z', ' ', '$', '%', '*',
+		'+', '-', '.', '/', ':',
+	];
+
 	/**
 	 * @inheritDoc
 	 */
@@ -78,7 +90,10 @@ final class AlphaNum extends QRDataModeAbstract{
 
 		// encode 2 characters in 11 bits
 		for($i = 0; ($i + 1) < $len; $i += 2){
-			$bitBuffer->put((self::CHAR_TO_ORD[$this->data[$i]] * 45 + self::CHAR_TO_ORD[$this->data[($i + 1)]]), 11);
+			$bitBuffer->put(
+				(self::CHAR_TO_ORD[$this->data[$i]] * 45 + self::CHAR_TO_ORD[$this->data[($i + 1)]]),
+				11,
+			);
 		}
 
 		// encode a remaining character in 6 bits
@@ -95,19 +110,7 @@ final class AlphaNum extends QRDataModeAbstract{
 	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
 	 */
 	public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
-		$length  = $bitBuffer->read(self::getLengthBits($versionNumber));
-		$charmap = array_flip(self::CHAR_TO_ORD);
-
-		// @todo
-		$toAlphaNumericChar = function(int $ord) use ($charmap):string{
-
-			if(isset($charmap[$ord])){
-				return $charmap[$ord];
-			}
-
-			throw new QRCodeDataException('invalid character value: '.$ord);
-		};
-
+		$length = $bitBuffer->read(self::getLengthBits($versionNumber));
 		$result = '';
 		// Read two characters at a time
 		while($length > 1){
@@ -116,9 +119,9 @@ final class AlphaNum extends QRDataModeAbstract{
 				throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
 			}
 
-			$nextTwoCharsBits = $bitBuffer->read(11);
-			$result           .= $toAlphaNumericChar(intdiv($nextTwoCharsBits, 45));
-			$result           .= $toAlphaNumericChar($nextTwoCharsBits % 45);
+			$nextTwoCharsBits  = $bitBuffer->read(11);
+			$result           .= self::ORD_TO_CHAR[intdiv($nextTwoCharsBits, 45)];
+			$result           .= self::ORD_TO_CHAR[($nextTwoCharsBits % 45)];
 			$length           -= 2;
 		}
 
@@ -128,7 +131,7 @@ final class AlphaNum extends QRDataModeAbstract{
 				throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
 			}
 
-			$result .= $toAlphaNumericChar($bitBuffer->read(6));
+			$result .= self::ORD_TO_CHAR[$bitBuffer->read(6)];
 		}
 
 		return $result;

+ 15 - 20
src/Data/Number.php

@@ -11,7 +11,7 @@
 namespace chillerlan\QRCode\Data;
 
 use chillerlan\QRCode\Common\{BitBuffer, Mode};
-use function array_flip, ceil, intdiv, str_split, substr, unpack;
+use function ceil, intdiv, str_split, substr, unpack;
 
 /**
  * Numeric mode: decimal digits 0 to 9
@@ -28,6 +28,13 @@ final class Number extends QRDataModeAbstract{
 		'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
 	];
 
+	/**
+	 * @var string[]
+	 */
+	private const ORD_TO_NUMBER = [
+		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+	];
+
 	/**
 	 * @inheritDoc
 	 */
@@ -112,19 +119,7 @@ final class Number extends QRDataModeAbstract{
 	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
 	 */
 	public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
-		$length  = $bitBuffer->read(self::getLengthBits($versionNumber));
-		$charmap = array_flip(self::NUMBER_TO_ORD);
-
-		// @todo
-		$toNumericChar = function(int $ord) use ($charmap):string{
-
-			if(isset($charmap[$ord])){
-				return $charmap[$ord];
-			}
-
-			throw new QRCodeDataException('invalid character value: '.$ord);
-		};
-
+		$length = $bitBuffer->read(self::getLengthBits($versionNumber));
 		$result = '';
 		// Read three digits at a time
 		while($length >= 3){
@@ -139,9 +134,9 @@ final class Number extends QRDataModeAbstract{
 				throw new QRCodeDataException('error decoding numeric value');
 			}
 
-			$result .= $toNumericChar(intdiv($threeDigitsBits, 100));
-			$result .= $toNumericChar(intdiv($threeDigitsBits, 10) % 10);
-			$result .= $toNumericChar($threeDigitsBits % 10);
+			$result .= self::ORD_TO_NUMBER[intdiv($threeDigitsBits, 100)];
+			$result .= self::ORD_TO_NUMBER[(intdiv($threeDigitsBits, 10) % 10)];
+			$result .= self::ORD_TO_NUMBER[($threeDigitsBits % 10)];
 
 			$length -= 3;
 		}
@@ -158,8 +153,8 @@ final class Number extends QRDataModeAbstract{
 				throw new QRCodeDataException('error decoding numeric value');
 			}
 
-			$result .= $toNumericChar(intdiv($twoDigitsBits, 10));
-			$result .= $toNumericChar($twoDigitsBits % 10);
+			$result .= self::ORD_TO_NUMBER[intdiv($twoDigitsBits, 10)];
+			$result .= self::ORD_TO_NUMBER[($twoDigitsBits % 10)];
 		}
 		elseif($length === 1){
 			// One digit left over to read
@@ -173,7 +168,7 @@ final class Number extends QRDataModeAbstract{
 				throw new QRCodeDataException('error decoding numeric value');
 			}
 
-			$result .= $toNumericChar($digitBits);
+			$result .= self::ORD_TO_NUMBER[$digitBits];
 		}
 
 		return $result;

+ 3 - 1
src/Data/QRData.php

@@ -54,6 +54,8 @@ final class QRData{
 
 	/**
 	 * QRData constructor.
+	 *
+	 * @param \chillerlan\QRCode\Data\QRDataModeInterface[] $dataSegments
 	 */
 	public function __construct(SettingsContainerInterface|QROptions $options, array $dataSegments = []){
 		$this->options       = $options;
@@ -146,7 +148,7 @@ final class QRData{
 		}
 
 		$provisionalVersion = null;
-
+		/** @var int $version */
 		foreach($this->maxBitsForEcc as $version => $maxBits){
 
 			if($length <= $maxBits){

+ 6 - 3
src/Data/QRMatrix.php

@@ -94,7 +94,7 @@ class QRMatrix{
 	 *
 	 * @see \chillerlan\QRCode\Data\QRMatrix::checkNeighbours()
 	 *
-	 * @var array
+	 * @var int[][]
 	 */
 	protected const neighbours = [
 		0b00000001 => [-1, -1],
@@ -150,6 +150,8 @@ class QRMatrix{
 
 	/**
 	 * Creates a 2-dimensional array (square) of the given $size
+	 *
+	 * @return int[][]
 	 */
 	protected function createMatrix(int $size, int $value):array{
 		return array_fill(0, $size, array_fill(0, $size, $value));
@@ -173,7 +175,7 @@ class QRMatrix{
 	/**
 	 * Returns the data matrix, returns a pure boolean representation if $boolean is set to true
 	 *
-	 * @return int[][]|bool[][]
+	 * @return int[][]
 	 */
 	public function getMatrix(bool|null $boolean = null):array{
 
@@ -299,6 +301,8 @@ class QRMatrix{
 	/**
 	 * Checks whether the module at ($x, $y) is in the given array of $M_TYPES,
 	 * returns true if a match is found, otherwise false.
+	 *
+	 * @param int[] $M_TYPES
 	 */
 	public function checkTypeIn(int $x, int $y, array $M_TYPES):bool{
 
@@ -583,7 +587,6 @@ class QRMatrix{
 	 * Rotates the matrix by 90 degrees clock wise
 	 */
 	public function rotate90():static{
-		/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
 		$this->matrix = array_map((fn(int ...$a):array => array_reverse($a)), ...$this->matrix);
 
 		return $this;

+ 5 - 3
src/Data/ReedSolomonEncoder.php

@@ -22,7 +22,7 @@ final class ReedSolomonEncoder{
 
 	private Version  $version;
 	private EccLevel $eccLevel;
-
+	/** @var int[] */
 	private array    $interleavedData;
 	private int      $interleavedDataIndex;
 
@@ -37,6 +37,7 @@ final class ReedSolomonEncoder{
 	/**
 	 * ECC encoding and interleaving
 	 *
+	 * @return int[]
 	 * @throws \chillerlan\QRCode\QRCodeException
 	 */
 	public function interleaveEcBytes(BitBuffer $bitBuffer):array{
@@ -80,7 +81,8 @@ final class ReedSolomonEncoder{
 	}
 
 	/**
-	 *
+	 * @param  int[] $dataBytes
+	 * @return int[]
 	 */
 	private function encode(array $dataBytes, int $ecByteCount):array{
 		$rsPoly = new GenericGFPoly([1]);
@@ -112,7 +114,7 @@ final class ReedSolomonEncoder{
 	}
 
 	/**
-	 *
+	 * @param int[][] $byteArray
 	 */
 	private function interleave(array $byteArray, int $maxBytes, int $numRsBlocks):void{
 		for($x = 0; $x < $maxBytes; $x++){

+ 4 - 0
src/Decoder/Binarizer.php

@@ -47,6 +47,7 @@ final class Binarizer{
 	private const LUMINANCE_BUCKETS = 32;
 
 	private LuminanceSourceInterface $source;
+	/** @var int[] */
 	private array                    $luminances;
 
 	/**
@@ -58,6 +59,7 @@ final class Binarizer{
 	}
 
 	/**
+	 * @param int[] $buckets
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
 	 */
 	private function estimateBlackPoint(array $buckets):int{
@@ -204,6 +206,8 @@ final class Binarizer{
 	 * See the following thread for a discussion of this algorithm:
 	 *
 	 * @see http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0
+	 *
+	 * @return float[][]
 	 */
 	private function calculateBlackPoints(int $subWidth, int $subHeight, int $width, int $height):array{
 		$blackPoints = array_fill(0, $subHeight, array_fill(0, $subWidth, 0));

+ 1 - 1
src/Decoder/BitMatrix.php

@@ -105,6 +105,7 @@ final class BitMatrix extends QRMatrix{
 	 * correct order in order to reconstruct the codewords bytes contained within the
 	 * QR Code. Throws if the exact number of bytes expected is not read.
 	 *
+	 * @return int[]
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
 	 */
 	public function readCodewords():array{
@@ -362,7 +363,6 @@ final class BitMatrix extends QRMatrix{
 
 			// Otherwise see if this is the closest to a real version info bit string
 			// we have seen so far
-			/** @phan-suppress-next-line PhanTypeMismatchArgumentNullable ($targetVersionPattern is never null here) */
 			$bitsDifference = $this->numBitsDiffering($versionBits, $targetVersionPattern);
 
 			if($bitsDifference < $bestDifference){

+ 2 - 0
src/Decoder/DecoderResult.php

@@ -40,6 +40,8 @@ final class DecoderResult{
 
 	/**
 	 * DecoderResult constructor.
+	 *
+	 * @phpstan-param array<string, mixed> $properties
 	 */
 	public function __construct(iterable|null $properties = null){
 

+ 11 - 4
src/Decoder/ReedSolomonDecoder.php

@@ -47,6 +47,8 @@ final class ReedSolomonDecoder{
 
 	/**
 	 * Error-correct and copy data blocks together into a stream of bytes
+	 *
+	 * @param int[] $rawCodewords
 	 */
 	public function decode(array $rawCodewords):BitBuffer{
 		$dataBlocks  = $this->deinterleaveRawBytes($rawCodewords);
@@ -69,6 +71,8 @@ final class ReedSolomonDecoder{
 	 * method will separate the data into original blocks.
 	 *
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+	 *
+	 * @param int[] $rawCodewords
 	 */
 	private function deinterleaveRawBytes(array $rawCodewords):array{
 		// Figure out the number and size of data blocks used by this version and
@@ -87,7 +91,6 @@ final class ReedSolomonDecoder{
 
 		// All blocks have the same amount of data, except that the last n
 		// (where n may be 0) have 1 more byte. Figure out where these start.
-		/** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */
 		$shorterBlocksTotalCodewords = count($result[0][1]);
 		$longerBlocksStartAt         = (count($result) - 1);
 
@@ -120,7 +123,6 @@ final class ReedSolomonDecoder{
 		}
 
 		// Now add in error correction blocks
-		/** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */
 		$max = count($result[0][1]);
 
 		for($i = $shorterBlocksNumDataCodewords; $i < $max; $i++){
@@ -137,6 +139,9 @@ final class ReedSolomonDecoder{
 	/**
 	 * Given data and error-correction codewords received, possibly corrupted by errors, attempts to
 	 * correct the errors in-place using Reed-Solomon error correction.
+	 *
+	 * @param  int[] $codewordBytes
+	 * @return int[]
 	 */
 	private function correctErrors(array $codewordBytes, int $numDataCodewords):array{
 		// First read into an array of ints
@@ -162,7 +167,7 @@ final class ReedSolomonDecoder{
 	 * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place,
 	 * in the input.
 	 *
-	 * @param array $received        data and error-correction codewords
+	 * @param int[] $received        data and error-correction codewords
 	 * @param int   $numEccCodewords number of error-correction codewords available
 	 *
 	 * @return int[]
@@ -255,6 +260,7 @@ final class ReedSolomonDecoder{
 	}
 
 	/**
+	 * @return int[]
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
 	 */
 	private function findErrorLocations(GenericGFPoly $errorLocator):array{
@@ -283,7 +289,8 @@ final class ReedSolomonDecoder{
 	}
 
 	/**
-	 *
+	 * @param  int[] $errorLocations
+	 * @return int[]
 	 */
 	private function findErrorMagnitudes(GenericGFPoly $errorEvaluator, array $errorLocations):array{
 		// This is directly applying Forney's Formula

+ 1 - 0
src/Detector/AlignmentPatternFinder.php

@@ -61,6 +61,7 @@ final class AlignmentPatternFinder{
 	public function find(int $startX, int $startY, int $width, int $height):AlignmentPattern|null{
 		$maxJ       = ($startX + $width);
 		$middleI    = ($startY + ($height / 2));
+		/** @var int[] $stateCount */
 		$stateCount = [];
 
 		// We are looking for black/white/black modules in 1:1:1 ratio;

+ 0 - 2
src/Detector/FinderPatternFinder.php

@@ -7,8 +7,6 @@
  * @author       Smiley <smiley@chillerlan.net>
  * @copyright    2021 Smiley
  * @license      Apache-2.0
- *
- * @phan-file-suppress PhanTypePossiblyInvalidDimOffset
  */
 
 namespace chillerlan\QRCode\Detector;

+ 1 - 0
src/Detector/GridSampler.php

@@ -30,6 +30,7 @@ use function array_fill, count, intdiv, sprintf;
  */
 final class GridSampler{
 
+	/** @var float[] */
 	private array $points;
 
 	/**

+ 4 - 1
src/Detector/PerspectiveTransform.php

@@ -150,7 +150,10 @@ final class PerspectiveTransform{
 	}
 
 	/**
-	 * @return array[] [$xValues, $yValues]
+	 * @param float[]      $xValues
+	 * @param float[]|null $yValues
+	 *
+	 * @return float[][] [$xValues, $yValues]
 	 */
 	public function transformPoints(array $xValues, array|null $yValues = null):array{
 		$max = count($xValues);

+ 8 - 6
src/Output/CssColorModuleValueTrait.php

@@ -21,11 +21,13 @@ trait CssColorModuleValueTrait{
 	 * note: we're not necessarily validating the several values, just checking the general syntax
 	 * note: css4 colors are not included
 	 *
+	 * implements \chillerlan\QRCode\Output\QROutputInterface::moduleValueIsValid()
+	 *
 	 * @todo: XSS proof
 	 *
 	 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
-	 * @implements \chillerlan\QRCode\Output\QROutputInterface::moduleValueIsValid()
-	 * @inheritDoc
+	 *
+	 * @param string $value
 	 */
 	public static function moduleValueIsValid(mixed $value):bool{
 
@@ -56,16 +58,16 @@ trait CssColorModuleValueTrait{
 	}
 
 	/**
-	 * @implements \chillerlan\QRCode\Output\QROutputAbstract::prepareModuleValue()
-	 * @inheritDoc
+	 * implements \chillerlan\QRCode\Output\QROutputAbstract::prepareModuleValue()
+	 *
+	 * @param string $value
 	 */
 	protected function prepareModuleValue(mixed $value):string{
 		return trim(strip_tags($value), " '\"\r\n\t");
 	}
 
 	/**
-	 * @implements \chillerlan\QRCode\Output\QROutputAbstract::getDefaultModuleValue()
-	 * @inheritDoc
+	 * implements \chillerlan\QRCode\Output\QROutputAbstract::getDefaultModuleValue()
 	 */
 	protected function getDefaultModuleValue(bool $isDark):string{
 		return ($isDark) ? '#000' : '#fff';

+ 2 - 0
src/Output/QREps.php

@@ -81,6 +81,8 @@ class QREps extends QROutputAbstract{
 	 * 4 values in the color array will be interpreted as CMYK, 3 as RGB
 	 *
 	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+	 *
+	 * @param float[] $values
 	 */
 	protected function formatColor(array $values):string{
 		$count = count($values);

+ 2 - 2
src/Output/QRFpdf.php

@@ -30,6 +30,8 @@ class QRFpdf extends QROutputAbstract{
 	final public const MIME_TYPE = 'application/pdf';
 
 	protected FPDF       $fpdf;
+
+	/** @var int[]  */
 	protected array|null $prevColor = null;
 
 	/**
@@ -71,7 +73,6 @@ class QRFpdf extends QROutputAbstract{
 			$bgColor          = $this->prepareModuleValue($this->options->bgColor);
 			[$width, $height] = $this->getOutputDimensions();
 
-			/** @phan-suppress-next-line PhanParamTooFewUnpack */
 			$this->fpdf->SetFillColor(...$bgColor);
 			$this->fpdf->Rect(0, 0, $width, $height, 'F');
 		}
@@ -111,7 +112,6 @@ class QRFpdf extends QROutputAbstract{
 		$color = $this->getModuleValue($M_TYPE);
 
 		if($color !== null && $color !== $this->prevColor){
-			/** @phan-suppress-next-line PhanParamTooFewUnpack */
 			$this->fpdf->SetFillColor(...$color);
 			$this->prevColor = $color;
 		}

+ 1 - 1
src/Output/QRGdImage.php

@@ -122,7 +122,6 @@ abstract class QRGdImage extends QROutputAbstract{
 			$values[] = max(0, min(255, intval($val)));
 		}
 
-		/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
 		$color = imagecolorallocate($this->image, ...$values);
 
 		if($color === false){
@@ -328,6 +327,7 @@ abstract class QRGdImage extends QROutputAbstract{
 			throw new QRCodeOutputException($exception->getMessage());
 		}
 
+		/** @var string $imageData */
 		return $imageData;
 	}
 

+ 14 - 10
src/Output/QRMarkupXML.php

@@ -8,7 +8,6 @@
  * @license      MIT
  *
  * @noinspection PhpComposerExtensionStubsInspection
- * @phan-file-suppress PhanTypeMismatchArgumentInternal
  */
 
 namespace chillerlan\QRCode\Output;
@@ -29,6 +28,8 @@ class QRMarkupXML extends QRMarkup{
 
 	/**
 	 * @inheritDoc
+	 *
+	 * @return int[]
 	 */
 	protected function getOutputDimensions():array{
 		return [$this->moduleCount, $this->moduleCount];
@@ -53,8 +54,8 @@ class QRMarkupXML extends QRMarkup{
 
 		$root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
 		$root->setAttribute('xsi:noNamespaceSchemaLocation', $this::SCHEMA);
-		$root->setAttribute('version', $this->matrix->getVersion());
-		$root->setAttribute('eccLevel', $this->matrix->getEccLevel());
+		$root->setAttribute('version', (string)$this->matrix->getVersion());
+		$root->setAttribute('eccLevel', (string)$this->matrix->getEccLevel());
 		$root->appendChild($this->createMatrix());
 
 		$this->dom->appendChild($root);
@@ -72,11 +73,11 @@ class QRMarkupXML extends QRMarkup{
 		$matrix           = $this->dom->createElement('matrix');
 		$dimension        = $this->matrix->getVersion()->getDimension();
 
-		$matrix->setAttribute('size', $dimension);
-		$matrix->setAttribute('quietzoneSize', (int)(($this->moduleCount - $dimension) / 2));
-		$matrix->setAttribute('maskPattern', $this->matrix->getMaskPattern()->getPattern());
-		$matrix->setAttribute('width', $width);
-		$matrix->setAttribute('height', $height);
+		$matrix->setAttribute('size', (string)$dimension);
+		$matrix->setAttribute('quietzoneSize', (string)(int)(($this->moduleCount - $dimension) / 2));
+		$matrix->setAttribute('maskPattern', (string)$this->matrix->getMaskPattern()->getPattern());
+		$matrix->setAttribute('width', (string)$width);
+		$matrix->setAttribute('height', (string)$height);
 
 		foreach($this->matrix->getMatrix() as $y => $row){
 			$matrixRow = $this->row($y, $row);
@@ -91,11 +92,13 @@ class QRMarkupXML extends QRMarkup{
 
 	/**
 	 * Creates a DOM element for a matrix row
+	 *
+	 * @param int[] $row
 	 */
 	protected function row(int $y, array $row):DOMElement|null{
 		$matrixRow = $this->dom->createElement('row');
 
-		$matrixRow->setAttribute('y', $y);
+		$matrixRow->setAttribute('y', (string)$y);
 
 		foreach($row as $x => $M_TYPE){
 			$module = $this->module($x, $y, $M_TYPE);
@@ -128,8 +131,9 @@ class QRMarkupXML extends QRMarkup{
 
 		$module->setAttribute('x', $x);
 		$module->setAttribute('dark', ($isDark ? 'true' : 'false'));
+		$module->setAttribute('x', (string)$x);
 		$module->setAttribute('layer', ($this::LAYERNAMES[$M_TYPE] ?? ''));
-		$module->setAttribute('value', $this->getModuleValue($M_TYPE));
+		$module->setAttribute('value', (string)$this->getModuleValue($M_TYPE));
 
 		return $module;
 	}

+ 17 - 4
src/Output/QROutputAbstract.php

@@ -35,6 +35,8 @@ abstract class QROutputAbstract implements QROutputInterface{
 
 	/**
 	 * an (optional) array of color values for the several QR matrix parts
+	 *
+	 * @phpstan-var array<int, mixed>
 	 */
 	protected array $moduleValues;
 
@@ -48,20 +50,26 @@ abstract class QROutputAbstract implements QROutputInterface{
 	 */
 	protected SettingsContainerInterface|QROptions $options;
 
+	/**
+	 * @see \chillerlan\QRCode\QROptions::$excludeFromConnect
+	 * @var int[]
+	 */
+	protected array $excludeFromConnect;
+	/**
+	 * @see \chillerlan\QRCode\QROptions::$keepAsSquare
+	 * @var int[]
+	 */
+	protected array $keepAsSquare;
 	/** @see \chillerlan\QRCode\QROptions::$scale */
 	protected int $scale;
 	/** @see \chillerlan\QRCode\QROptions::$connectPaths */
 	protected bool $connectPaths;
-	/** @see \chillerlan\QRCode\QROptions::$excludeFromConnect */
-	protected array $excludeFromConnect;
 	/** @see \chillerlan\QRCode\QROptions::$eol */
 	protected string $eol;
 	/** @see \chillerlan\QRCode\QROptions::$drawLightModules */
 	protected bool $drawLightModules;
 	/** @see \chillerlan\QRCode\QROptions::$drawCircularModules */
 	protected bool $drawCircularModules;
-	/** @see \chillerlan\QRCode\QROptions::$keepAsSquare */
-	protected array $keepAsSquare;
 	/** @see \chillerlan\QRCode\QROptions::$circleRadius */
 	protected float $circleRadius;
 	protected float $circleDiameter;
@@ -122,6 +130,8 @@ abstract class QROutputAbstract implements QROutputInterface{
 	 * Returns a 2 element array with the current output width and height
 	 *
 	 * The type and units of the values depend on the output class. The default value is the current module count * scale.
+	 *
+	 * @return int[]
 	 */
 	protected function getOutputDimensions():array{
 		return [$this->length, $this->length];
@@ -138,6 +148,7 @@ abstract class QROutputAbstract implements QROutputInterface{
 		}
 
 		// now loop over the options values to replace defaults and add extra values
+		/** @var int $M_TYPE */
 		foreach($this->options->moduleValues as $M_TYPE => $value){
 			if($this::moduleValueIsValid($value)){
 				$this->moduleValues[$M_TYPE] = $this->prepareModuleValue($value);
@@ -217,6 +228,8 @@ abstract class QROutputAbstract implements QROutputInterface{
 	 *   $y            - current row
 	 *   $M_TYPE       - field value
 	 *   $M_TYPE_LAYER - (possibly modified) field value that acts as layer id
+	 *
+	 * @return array<int, mixed>
 	 */
 	protected function collectModules(Closure $transform):array{
 		$paths = [];

+ 9 - 0
src/Output/QRStringJSON.php

@@ -15,7 +15,9 @@ namespace chillerlan\QRCode\Output;
 use function json_encode;
 
 /**
+ * @method string getModuleValue(int $M_TYPE)
  *
+ * @phpstan-type Module array{x: int, dark: bool, layer: string, value: string}
  */
 class QRStringJSON extends QROutputAbstract{
 	use CssColorModuleValueTrait;
@@ -25,6 +27,8 @@ class QRStringJSON extends QROutputAbstract{
 
 	/**
 	 * @inheritDoc
+	 *
+	 * @return int[]
 	 */
 	protected function getOutputDimensions():array{
 		return [$this->moduleCount, $this->moduleCount];
@@ -72,6 +76,9 @@ class QRStringJSON extends QROutputAbstract{
 
 	/**
 	 * Creates an array element for a matrix row
+	 *
+	 * @param  int[] $row
+	 * @phpstan-return array{y: int, modules: array<int, Module>}
 	 */
 	protected function row(int $y, array $row):array|null{
 		$matrixRow = ['y' => $y, 'modules' => []];
@@ -94,6 +101,8 @@ class QRStringJSON extends QROutputAbstract{
 
 	/**
 	 * Creates an array element for a single module
+	 *
+	 * @phpstan-return Module
 	 */
 	protected function module(int $x, int $y, int $M_TYPE):array|null{
 		$isDark = $this->matrix->isDark($M_TYPE);

+ 4 - 0
src/Output/QRStringText.php

@@ -21,6 +21,8 @@ class QRStringText extends QROutputAbstract{
 
 	/**
 	 * @inheritDoc
+	 *
+	 * @param string $value
 	 */
 	public static function moduleValueIsValid(mixed $value):bool{
 		return is_string($value);
@@ -28,6 +30,8 @@ class QRStringText extends QROutputAbstract{
 
 	/**
 	 * @inheritDoc
+	 *
+	 * @param string $value
 	 */
 	protected function prepareModuleValue(mixed $value):string{
 		return $value;

+ 11 - 6
src/Output/RGBArrayModuleValueTrait.php

@@ -18,8 +18,9 @@ use function array_values, count, intval, is_array, is_numeric, max, min;
 trait RGBArrayModuleValueTrait{
 
 	/**
-	 * @implements \chillerlan\QRCode\Output\QROutputInterface::moduleValueIsValid()
-	 * @inheritDoc
+	 * implements \chillerlan\QRCode\Output\QROutputInterface::moduleValueIsValid()
+	 *
+	 * @param int[] $value
 	 */
 	public static function moduleValueIsValid(mixed $value):bool{
 
@@ -44,8 +45,11 @@ trait RGBArrayModuleValueTrait{
 	}
 
 	/**
-	 * @implements \chillerlan\QRCode\Output\QROutputAbstract::prepareModuleValue()
-	 * @inheritDoc
+	 * implements \chillerlan\QRCode\Output\QROutputAbstract::prepareModuleValue()
+	 *
+	 * @param  int[] $value
+	 * @return int[]
+	 *
 	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
 	 */
 	protected function prepareModuleValue(mixed $value):array{
@@ -68,8 +72,9 @@ trait RGBArrayModuleValueTrait{
 	}
 
 	/**
-	 * @implements \chillerlan\QRCode\Output\QROutputAbstract::getDefaultModuleValue()
-	 * @inheritDoc
+	 * implements \chillerlan\QRCode\Output\QROutputAbstract::getDefaultModuleValue()
+	 *
+	 * @return int[]
 	 */
 	protected function getDefaultModuleValue(bool $isDark):array{
 		return ($isDark) ? [0, 0, 0] : [255, 255, 255];

+ 8 - 2
src/QRCode.php

@@ -51,6 +51,8 @@ class QRCode{
 
 	/**
 	 * QRCode constructor.
+	 *
+	 * @phpstan-param array<string, mixed> $options
 	 */
 	public function __construct(SettingsContainerInterface|QROptions|iterable $options = new QROptions){
 		$this->setOptions($options);
@@ -58,6 +60,8 @@ class QRCode{
 
 	/**
 	 * Sets an options instance
+	 *
+	 * @phpstan-param array<string, mixed> $options
 	 */
 	public function setOptions(SettingsContainerInterface|QROptions|iterable $options):static{
 
@@ -80,7 +84,6 @@ class QRCode{
 	public function render(string|null $data = null, string|null $file = null):mixed{
 
 		if($data !== null){
-			/** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */
 			foreach(Mode::INTERFACES as $dataInterface){
 
 				if($dataInterface::validateString($data)){
@@ -157,7 +160,10 @@ class QRCode{
 			throw new QRCodeOutputException('output class does not implement QROutputInterface');
 		}
 
-		return new $outputInterface($this->options, $matrix);
+		/** @var \chillerlan\QRCode\Output\QROutputInterface $instance */
+		$instance = new $outputInterface($this->options, $matrix);
+
+		return $instance;
 	}
 
 	/**

+ 5 - 0
src/QRCodeReaderOptionsTrait.php

@@ -14,6 +14,11 @@ use function extension_loaded;
 
 /**
  * Trait QRCodeReaderOptionsTrait
+ *
+ * @property bool $readerUseImagickIfAvailable
+ * @property bool $readerGrayscale
+ * @property bool $readerInvertColors
+ * @property bool $readerIncreaseContrast
  */
 trait QRCodeReaderOptionsTrait{
 

+ 57 - 0
src/QROptionsTrait.php

@@ -22,6 +22,48 @@ use const JSON_THROW_ON_ERROR, JSON_UNESCAPED_SLASHES, PHP_EOL;
 
 /**
  * The QRCode plug-in settings & setter functionality
+ *
+ * @property int         $version
+ * @property int         $versionMin
+ * @property int         $versionMax
+ * @property int         $eccLevel
+ * @property int         $maskPattern
+ * @property bool        $addQuietzone
+ * @property int         $quietzoneSize
+ * @property string      $outputInterface
+ * @property bool        $returnResource
+ * @property string|null $cachefile
+ * @property bool        $outputBase64
+ * @property string      $eol
+ * @property mixed       $bgColor
+ * @property bool        $invertMatrix
+ * @property bool        $drawLightModules
+ * @property bool        $drawCircularModules
+ * @property float       $circleRadius
+ * @property array       $keepAsSquare
+ * @property bool        $connectPaths
+ * @property array       $excludeFromConnect
+ * @property array       $moduleValues
+ * @property bool        $addLogoSpace
+ * @property int|null    $logoSpaceWidth
+ * @property int|null    $logoSpaceHeight
+ * @property int|null    $logoSpaceStartX
+ * @property int|null    $logoSpaceStartY
+ * @property int         $scale
+ * @property bool        $imageTransparent
+ * @property mixed       $transparencyColor
+ * @property int         $quality
+ * @property bool        $gdImageUseUpscale
+ * @property string      $imagickFormat
+ * @property string      $cssClass
+ * @property bool        $svgAddXmlHeader
+ * @property string      $svgDefs
+ * @property string      $svgPreserveAspectRatio
+ * @property bool        $svgUseFillAttributes
+ * @property string      $textLineStart
+ * @property int         $jsonFlags
+ * @property string      $fpdfMeasureUnit
+ * @property string|null $xmlStylesheet
  */
 trait QROptionsTrait{
 
@@ -143,6 +185,7 @@ trait QROptionsTrait{
 	 */
 	protected string $eol = PHP_EOL;
 
+
 	/*
 	 * Common visual modifications
 	 */
@@ -199,6 +242,8 @@ trait QROptionsTrait{
 	 * Specifies which module types to exclude when `QROptions::$drawCircularModules` is set to `true`
 	 *
 	 * (default: `[]`)
+	 *
+	 * @var int[]
 	 */
 	protected array $keepAsSquare = [];
 
@@ -222,6 +267,8 @@ trait QROptionsTrait{
 	 * Specify which paths/patterns to exclude from connecting if `QROptions::$connectPaths` is set to `true`
 	 *
 	 * @see \chillerlan\QRCode\QROptionsTrait::$connectPaths
+	 *
+	 * @var int[]
 	 */
 	protected array $excludeFromConnect = [];
 
@@ -233,6 +280,8 @@ trait QROptionsTrait{
 	 * - `QREps`: `[C, M, Y, K]` // 0-255
 	 *
 	 * @see \chillerlan\QRCode\Output\QROutputAbstract::setModuleValues()
+	 *
+	 * @var array<int, mixed>
 	 */
 	protected array $moduleValues = [];
 
@@ -321,6 +370,7 @@ trait QROptionsTrait{
 	 */
 	protected int $quality = -1;
 
+
 	/*
 	 * QRGdImage settings
 	 */
@@ -334,6 +384,7 @@ trait QROptionsTrait{
 	 */
 	protected bool $gdImageUseUpscale = true;
 
+
 	/*
 	 * QRImagick settings
 	 */
@@ -356,6 +407,7 @@ trait QROptionsTrait{
 	 */
 	protected string $cssClass = 'qrcode';
 
+
 	/*
 	 * QRMarkupSVG settings
 	 */
@@ -391,6 +443,7 @@ trait QROptionsTrait{
 	 */
 	protected bool $svgUseFillAttributes = true;
 
+
 	/*
 	 * QRStringText settings
 	 */
@@ -400,6 +453,7 @@ trait QROptionsTrait{
 	 */
 	protected string $textLineStart = '';
 
+
 	/*
 	 * QRStringJSON settings
 	 */
@@ -411,6 +465,7 @@ trait QROptionsTrait{
 	 */
 	protected int $jsonFlags = JSON_THROW_ON_ERROR|JSON_UNESCAPED_SLASHES;
 
+
 	/*
 	 * QRFpdf settings
 	 */
@@ -422,6 +477,7 @@ trait QROptionsTrait{
 	 */
 	protected string $fpdfMeasureUnit = 'pt';
 
+
 	/*
 	 * QRMarkupXML settings
 	 */
@@ -485,6 +541,7 @@ trait QROptionsTrait{
 			$eccLevel = constant(EccLevel::class.'::'.$ecc);
 		}
 
+		/** @var int $eccLevel */
 		if((0b11 & $eccLevel) !== $eccLevel){
 			throw new QRCodeException(sprintf('Invalid ECC level: "%s"', $eccLevel));
 		}

+ 3 - 0
tests/Common/BitBufferTest.php

@@ -26,6 +26,9 @@ final class BitBufferTest extends TestCase{
 		$this->bitBuffer = new BitBuffer;
 	}
 
+	/**
+	 * @phpstan-return array<string, array{0: int, 1: int}>
+	 */
 	public static function bitProvider():array{
 		return [
 			'number'   => [Mode::NUMBER, 16],

+ 7 - 1
tests/Common/ECICharsetTest.php

@@ -17,6 +17,9 @@ use PHPUnit\Framework\TestCase;
 
 final class ECICharsetTest extends TestCase{
 
+	/**
+	 * @phpstan-return array<int, array{0: int}>
+	 */
 	public static function invalidIdProvider():array{
 		return [[-1], [1000000]];
 	}
@@ -25,10 +28,13 @@ final class ECICharsetTest extends TestCase{
 	public function testInvalidDataException(int $id):void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid charset id:');
-		/** @phan-suppress-next-line PhanNoopNew */
+
 		new ECICharset($id);
 	}
 
+	/**
+	 * @phpstan-return array<int, array{0: int, 1: (string|null)}>
+	 */
 	public static function encodingProvider():array{
 		$params = [];
 

+ 1 - 1
tests/Common/EccLevelTest.php

@@ -22,7 +22,7 @@ final class EccLevelTest extends TestCase{
 	public function testConstructInvalidEccException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid ECC level');
-		/** @phan-suppress-next-line PhanNoopNew */
+
 		new EccLevel(69);
 	}
 

+ 1 - 1
tests/Common/MaskPatternTest.php

@@ -123,7 +123,7 @@ final class MaskPatternTest extends TestCase{
 	public function testInvalidMaskPatternException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid mask pattern');
-		/** @phan-suppress-next-line PhanNoopNew */
+
 		new MaskPattern(42);
 	}
 

+ 4 - 2
tests/Common/ModeTest.php

@@ -22,6 +22,8 @@ final class ModeTest extends TestCase{
 
 	/**
 	 * version breakpoints for numeric mode
+	 *
+	 * @phpstan-return array<int, array{0: int, 1: int}>
 	 */
 	public static function versionProvider():array{
 		return [
@@ -42,14 +44,14 @@ final class ModeTest extends TestCase{
 	public function testGetLengthBitsForVersionInvalidModeException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid mode given');
-		/** @phan-suppress-next-line PhanNoopNew */
+
 		Mode::getLengthBitsForVersion(42, 69);
 	}
 
 	public function testGetLengthBitsForVersionInvalidVersionException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid version number');
-		/** @phan-suppress-next-line PhanNoopNew */
+
 		Mode::getLengthBitsForVersion(Mode::BYTE, 69);
 	}
 

+ 1 - 1
tests/Common/VersionTest.php

@@ -58,7 +58,7 @@ final class VersionTest extends TestCase{
 	public function testConstructInvalidVersion():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('invalid version given');
-		/** @phan-suppress-next-line PhanNoopNew */
+
 		new Version(69);
 	}
 

+ 2 - 0
tests/Data/AlphaNumTest.php

@@ -26,6 +26,8 @@ final class AlphaNumTest extends DataInterfaceTestAbstract{
 
 	/**
 	 * isAlphaNum() should pass on the 45 defined characters and fail on anything else (e.g. lowercase)
+	 *
+	 * @phpstan-return array<int, array{0: string, 1: bool}>
 	 */
 	public static function stringValidateProvider():array{
 		return [

+ 2 - 0
tests/Data/ByteTest.php

@@ -26,6 +26,8 @@ final class ByteTest extends DataInterfaceTestAbstract{
 
 	/**
 	 * isByte() passses any binary string and only fails on empty strings
+	 *
+	 * @phpstan-return array<int, array{0: string, 1: bool}>
 	 */
 	public static function stringValidateProvider():array{
 		return [

+ 7 - 2
tests/Data/DataInterfaceTestAbstract.php

@@ -60,6 +60,9 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 		$this::assertSame($pattern, $matrix->getMaskPattern()->getPattern());
 	}
 
+	/**
+	 * @phpstan-return array<int, array{0: string, 1: bool}>
+	 */
 	abstract public static function stringValidateProvider():array;
 
 	/**
@@ -81,6 +84,8 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 
 	/**
 	 * returns versions within the version breakpoints 1-9, 10-26 and 27-40
+	 *
+	 * @phpstan-return array<string, array{0: int}>
 	 */
 	public static function versionBreakpointProvider():array{
 		return ['1-9' => [7], '10-26' => [15], '27-40' => [30]];
@@ -118,7 +123,7 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 	public static function maxLengthProvider():Generator{
 		$eccLevels = array_map(fn(int $ecc):EccLevel => new EccLevel($ecc), [EccLevel::L, EccLevel::M, EccLevel::Q, EccLevel::H]);
 		$str       = str_repeat(static::testData, 1000);
-		/** @phan-suppress-next-line PhanAbstractStaticMethodCallInStatic */
+
 		$dataMode  = static::getDataModeInterface(static::testData)::DATAMODE;
 		$mb        = ($dataMode === Mode::KANJI || $dataMode === Mode::HANZI);
 
@@ -203,7 +208,7 @@ abstract class DataInterfaceTestAbstract extends TestCase{
 		$options->version  = $version->getVersionNumber();
 		$options->eccLevel = $eccLevel->getLevel();
 
-		/** @phan-suppress-next-line PhanNoopNew */
+
 		new QRData($options, [static::getDataModeInterface($str1)]);
 	}
 

+ 13 - 3
tests/Data/ECITest.php

@@ -30,10 +30,12 @@ final class ECITest extends TestCase{
 		$this->QRData = new QRData(new QROptions);
 	}
 
+	/**
+	 * @phpstan-return array{0: ECI, 1: Byte}
+	 */
 	private function getDataSegments():array{
 		return [
 			new ECI($this->testCharset),
-			/** @phan-suppress-next-line PhanParamSuspiciousOrder */
 			new Byte(mb_convert_encoding(self::testData, ECICharset::MB_ENCODINGS[$this->testCharset], mb_internal_encoding())),
 		];
 	}
@@ -46,6 +48,8 @@ final class ECITest extends TestCase{
 
 	/**
 	 * returns versions within the version breakpoints 1-9, 10-26 and 27-40
+	 *
+	 * @phpstan-return array<string, array{0: int}>
 	 */
 	public static function versionBreakpointProvider():array{
 		return ['1-9' => [7], '10-26' => [15], '27-40' => [30]];
@@ -72,7 +76,7 @@ final class ECITest extends TestCase{
 	public function testInvalidDataException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('invalid encoding id:');
-		/** @phan-suppress-next-line PhanNoopNew */
+
 		new ECI(-1);
 	}
 
@@ -83,10 +87,13 @@ final class ECITest extends TestCase{
 	public function testInvalidDataOnEmptyException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('invalid encoding id:');
-		/** @phan-suppress-next-line PhanNoopNew */
+
 		new ECI(1000000);
 	}
 
+	/**
+	 * @phpstan-return array<int, array{0: int, 1: int}>
+	 */
 	public static function eciCharsetIdProvider():array{
 		return [
 			[     0,  8],
@@ -129,6 +136,9 @@ final class ECITest extends TestCase{
 		ECI::decodeSegment($bitBuffer, $options->version);
 	}
 
+	/**
+	 * @phpstan-return array<string, array{0: int, 1: string}>
+	 */
 	public static function unknownEncodingDataProvider():array{
 		return [
 			'CP437'              => [0, "\x41\x42\x43"],

+ 2 - 0
tests/Data/HanziTest.php

@@ -30,6 +30,8 @@ final class HanziTest extends DataInterfaceTestAbstract{
 
 	/**
 	 * isGB2312() should pass on Hanzi/GB2312 characters and fail on everything else
+	 *
+	 * @phpstan-return array<int, array{0: string, 1: bool}>
 	 */
 	public static function stringValidateProvider():array{
 		return [

+ 2 - 0
tests/Data/KanjiTest.php

@@ -30,6 +30,8 @@ final class KanjiTest extends DataInterfaceTestAbstract{
 
 	/**
 	 * isKanji() should pass on Kanji/SJIS characters and fail on everything else
+	 *
+	 * @phpstan-return array<int, array{0: string, 1: bool}>
 	 */
 	public static function stringValidateProvider():array{
 		return [

+ 2 - 0
tests/Data/NumberTest.php

@@ -26,6 +26,8 @@ final class NumberTest extends DataInterfaceTestAbstract{
 
 	/**
 	 * isNumber() should pass on any number and fail on anything else
+	 *
+	 * @phpstan-return array<int, array{0: string, 1: bool}>
 	 */
 	public static function stringValidateProvider():array{
 		return [

+ 0 - 3
tests/Output/CssColorModuleValueProviderTrait.php

@@ -17,9 +17,6 @@ namespace chillerlan\QRCodeTest\Output;
  */
 trait CssColorModuleValueProviderTrait{
 
-	/**
-	 * @implements \chillerlan\QRCodeTest\Output\QROutputTestAbstract::moduleValueProvider()
-	 */
 	public static function moduleValueProvider():array{
 		return [
 			'invalid: wrong type'            => [[], false],

+ 1 - 0
tests/Output/QREpsTest.php

@@ -50,6 +50,7 @@ class QREpsTest extends QROutputTestAbstract{
 		$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
 		$this->outputInterface->dump();
 
+		/** @phpstan-ignore-next-line */
 		$this::assertTrue(true); // tricking the code coverage
 	}
 

+ 1 - 0
tests/Output/QRFpdfTest.php

@@ -56,6 +56,7 @@ final class QRFpdfTest extends QROutputTestAbstract{
 		$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
 		$this->outputInterface->dump();
 
+		/** @phpstan-ignore-next-line */
 		$this::assertTrue(true); // tricking the code coverage
 	}
 

+ 1 - 2
tests/Output/QRGdImageAVIFTest.php

@@ -8,11 +8,10 @@
  * @license      MIT
  */
 
-namespace Output;
+namespace chillerlan\QRCodeTest\Output;
 
 use chillerlan\QRCode\QROptions;
 use chillerlan\QRCode\Data\QRMatrix;
-use chillerlan\QRCodeTest\Output\QRGdImageTestAbstract;
 use chillerlan\QRCode\Output\{QRGdImageAVIF, QROutputInterface};
 use chillerlan\Settings\SettingsContainerInterface;
 

+ 1 - 0
tests/Output/QRGdImageTestAbstract.php

@@ -48,6 +48,7 @@ abstract class QRGdImageTestAbstract extends QROutputTestAbstract{
 		$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
 		$this->outputInterface->dump();
 
+		/** @phpstan-ignore-next-line */
 		$this::assertTrue(true); // tricking the code coverage
 	}
 

+ 1 - 0
tests/Output/QRImagickTest.php

@@ -83,6 +83,7 @@ final class QRImagickTest extends QROutputTestAbstract{
 		$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
 		$this->outputInterface->dump();
 
+		/** @phpstan-ignore-next-line */
 		$this::assertTrue(true); // tricking the code coverage
 	}
 

+ 1 - 0
tests/Output/QRInterventionImageTest.php

@@ -57,6 +57,7 @@ class QRInterventionImageTest extends QROutputTestAbstract{
 		$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
 		$this->outputInterface->dump();
 
+		/** @phpstan-ignore-next-line */
 		$this::assertTrue(true); // tricking the code coverage
 	}
 

+ 3 - 0
tests/Output/QROutputTestAbstract.php

@@ -58,6 +58,9 @@ abstract class QROutputTestAbstract extends TestCase{
 		$this->outputInterface->dump('/foo/bar.test');
 	}
 
+	/**
+	 * @phpstan-return array<string, array{0: mixed, 1: bool}>
+	 */
 	abstract public static function moduleValueProvider():array;
 
 	#[DataProvider('moduleValueProvider')]

+ 3 - 0
tests/Output/QRStringTextTest.php

@@ -27,6 +27,9 @@ final class QRStringTextTest extends QROutputTestAbstract{
 		return new QRStringText($options, $matrix);
 	}
 
+	/**
+	 * @phpstan-return array<string, array{0: mixed, 1: bool}>
+	 */
 	public static function moduleValueProvider():array{
 		return [
 			'invalid: wrong type'       => [[], false],

+ 3 - 0
tests/QRCodeReaderImagickTest.php

@@ -41,6 +41,9 @@ final class QRCodeReaderImagickTest extends QRCodeReaderTestAbstract{
 		return IMagickLuminanceSource::fromFile($file, $options);
 	}
 
+	/**
+	 * @phpstan-return array<string, array{0: string, 1: string}>
+	 */
 	public static function vectorQRCodeProvider():array{
 		return [
 			// SVG convert only works on windows (Warning: Option --export-png= is deprecated)

+ 4 - 1
tests/QRCodeReaderTestAbstract.php

@@ -51,6 +51,9 @@ abstract class QRCodeReaderTestAbstract extends TestCase{
 		$this->options->readerUseImagickIfAvailable = false;
 	}
 
+	/**
+	 * @phpstan-return array<string, array{0: string, 1: string, 2: bool}>
+	 */
 	public static function qrCodeProvider():array{
 		return [
 			'helloworld' => ['hello_world.png', 'Hello world!', false],
@@ -129,7 +132,7 @@ abstract class QRCodeReaderTestAbstract extends TestCase{
 
 		/**
 		 * @noinspection PhpUndefinedConstantInspection - see phpunit.xml.dist
-		 * @phan-suppress-next-next-line PhanUndeclaredConstant
+		 * @phpstan-ignore-next-line
 		 */
 		for($v = 1; $v <= READER_TEST_MAX_VERSION; $v++){
 			$version = new Version($v);

+ 3 - 3
tests/QROptionsTest.php

@@ -74,7 +74,7 @@ final class QROptionsTest extends TestCase{
 		$o = new QROptions(['eccLevel' => EccLevel::H]);
 
 		$this::assertSame(EccLevel::H, $o->eccLevel);
-
+		/** @phpstan-ignore-next-line */
 		$o->eccLevel = 'q';
 
 		$this::assertSame(EccLevel::Q, $o->eccLevel);
@@ -86,7 +86,7 @@ final class QROptionsTest extends TestCase{
 	public function testSetEccLevelFromIntException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('Invalid ECC level: "42"');
-		/** @phan-suppress-next-line PhanNoopNew */
+
 		new QROptions(['eccLevel' => 42]);
 	}
 
@@ -96,7 +96,7 @@ final class QROptionsTest extends TestCase{
 	public function testSetEccLevelFromStringException():void{
 		$this->expectException(QRCodeException::class);
 		$this->expectExceptionMessage('Invalid ECC level: "FOO"');
-		/** @phan-suppress-next-line PhanNoopNew */
+
 		new QROptions(['eccLevel' => 'foo']);
 	}