diff --git a/.github/workflows/apiref.yml b/.github/workflows/apiref.yml
index f9384b3f..194b1f16 100644
--- a/.github/workflows/apiref.yml
+++ b/.github/workflows/apiref.yml
@@ -5,7 +5,7 @@ name: "Deploy API Reference"
 on:
   push:
     branches:
-      - "2.0.x"
+      - "2.2.x"
 
 concurrency:
   group: "pages"
@@ -16,9 +16,19 @@ jobs:
     name: "Build API References Pages"
     runs-on: "ubuntu-latest"
 
+    strategy:
+      matrix:
+        branch:
+          - "1.23.x"
+          - "2.0.x"
+          - "2.1.x"
+          - "2.2.x"
+
     steps:
       - name: "Checkout"
         uses: actions/checkout@v4
+        with:
+          ref: ${{ matrix.branch }}
 
       - name: "Install PHP"
         uses: "shivammathur/setup-php@v2"
@@ -36,18 +46,36 @@ jobs:
         run: "composer install --no-interaction --no-progress"
 
       - name: "Run ApiGen"
-        run: "apigen/vendor/bin/apigen -c apigen/apigen.neon --output docs/${{ github.ref_name }} -- src"
+        run: "apigen/vendor/bin/apigen -c apigen/apigen.neon --output docs/${{ matrix.branch }} -- src"
 
       - name: "Copy favicon"
         run: "cp apigen/favicon.png docs/favicon.png"
 
+      - uses: actions/upload-artifact@v4
+        with:
+          name: docs-${{ matrix.branch }}
+          path: docs/*
+
+  merge:
+    name: "Merge docs"
+    needs: build
+
+    runs-on: "ubuntu-latest"
+
+    steps:
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: docs-*
+          path: docs
+          merge-multiple: true
+
       - name: Upload artifact
         uses: actions/upload-pages-artifact@v3
         with:
           path: 'docs'
 
   deploy:
-    needs: build
+    needs: merge
 
     # from https://github.com/actions/deploy-pages
 
diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml
index ba1e3130..77e4e399 100644
--- a/.github/workflows/backward-compatibility.yml
+++ b/.github/workflows/backward-compatibility.yml
@@ -6,7 +6,7 @@ on:
   pull_request:
   push:
     branches:
-      - "1.23.x"
+      - "2.2.x"
 
 jobs:
   backward-compatibility:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 01b49f52..f20d4492 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,7 +6,7 @@ on:
   pull_request:
   push:
     branches:
-      - "1.23.x"
+      - "2.2.x"
 
 jobs:
   lint:
@@ -16,8 +16,6 @@ jobs:
     strategy:
       matrix:
         php-version:
-          - "7.2"
-          - "7.3"
           - "7.4"
           - "8.0"
           - "8.1"
@@ -41,10 +39,6 @@ jobs:
       - name: "Install dependencies"
         run: "composer install --no-interaction --no-progress"
 
-      - name: "Downgrade PHPUnit"
-        if: matrix.php-version == '7.2' || matrix.php-version == '7.3'
-        run: "composer require --dev phpunit/phpunit:^7.5.20 --update-with-dependencies"
-
       - name: "Lint"
         run: "make lint"
 
@@ -62,7 +56,7 @@ jobs:
         with:
           repository: "phpstan/build-cs"
           path: "build-cs"
-          ref: "1.x"
+          ref: "2.x"
 
       - name: "Install PHP"
         uses: "shivammathur/setup-php@v2"
@@ -95,8 +89,6 @@ jobs:
       matrix:
         operating-system: [ubuntu-latest, windows-latest]
         php-version:
-          - "7.2"
-          - "7.3"
           - "7.4"
           - "8.0"
           - "8.1"
@@ -125,10 +117,6 @@ jobs:
         if: ${{ matrix.dependencies == 'highest' }}
         run: "composer update --no-interaction --no-progress"
 
-      - name: "Downgrade PHPUnit"
-        if: matrix.php-version == '7.2' || matrix.php-version == '7.3'
-        run: "composer require --dev phpunit/phpunit:^7.5.20 --update-with-dependencies"
-
       - name: "Tests"
         run: "make tests"
 
@@ -140,8 +128,6 @@ jobs:
       fail-fast: false
       matrix:
         php-version:
-          - "7.2"
-          - "7.3"
           - "7.4"
           - "8.0"
           - "8.1"
@@ -164,9 +150,5 @@ jobs:
       - name: "Install dependencies"
         run: "composer update --no-interaction --no-progress"
 
-      - name: "Downgrade PHPUnit"
-        if: matrix.php-version == '7.2' || matrix.php-version == '7.3'
-        run: "composer require --dev phpunit/phpunit:^7.5.20 --update-with-dependencies"
-
       - name: "PHPStan"
         run: "make phpstan"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b1a669a9..b8c96d48 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,7 +18,7 @@ jobs:
 
       - name: Generate changelog
         id: changelog
-        uses: metcalfc/changelog-generator@v4.3.1
+        uses: metcalfc/changelog-generator@v4.6.2
         with:
           myToken: ${{ secrets.PHPSTAN_BOT_TOKEN }}
 
diff --git a/.github/workflows/send-pr.yml b/.github/workflows/send-pr.yml
index ab26eb3e..2503256e 100644
--- a/.github/workflows/send-pr.yml
+++ b/.github/workflows/send-pr.yml
@@ -23,7 +23,7 @@ jobs:
           repository: phpstan/phpstan-src
           path: phpstan-src
           token: ${{ secrets.PHPSTAN_BOT_TOKEN }}
-          ref: 1.12.x
+          ref: 2.1.x
 
       - name: "Install dependencies"
         working-directory: ./phpstan-src
diff --git a/.github/workflows/test-slevomat-coding-standard.yml b/.github/workflows/test-slevomat-coding-standard.yml
index 238fed25..8767f717 100644
--- a/.github/workflows/test-slevomat-coding-standard.yml
+++ b/.github/workflows/test-slevomat-coding-standard.yml
@@ -6,7 +6,7 @@ on:
   pull_request:
   push:
     branches:
-      - "1.23.x"
+      - "2.2.x"
 
 jobs:
   tests:
@@ -17,7 +17,6 @@ jobs:
       fail-fast: false
       matrix:
         php-version:
-          - "7.3"
           - "7.4"
           - "8.0"
           - "8.1"
@@ -34,7 +33,7 @@ jobs:
         with:
           repository: slevomat/coding-standard
           path: slevomat-cs
-          ref: ad4eab6f2cf9aa099c8853e4e3a250a9ae5fb4bd
+          ref: 88602f9ae5450a933133108d052eeb2edee0a4b7
 
       - name: "Install PHP"
         uses: "shivammathur/setup-php@v2"
diff --git a/LICENSE b/LICENSE
index 98a854e4..e5f34e60 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,7 @@
 MIT License
 
 Copyright (c) 2016 Ondřej Mirtes
+Copyright (c) 2025 PHPStan s.r.o.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
+SOFTWARE.
diff --git a/Makefile b/Makefile
index 4399bed1..294ad0c5 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ lint:
 .PHONY: cs-install
 cs-install:
 	git clone https://github.com/phpstan/build-cs.git || true
-	git -C build-cs fetch origin && git -C build-cs reset --hard origin/1.x
+	git -C build-cs fetch origin && git -C build-cs reset --hard origin/2.x
 	composer install --working-dir build-cs
 
 .PHONY: cs
diff --git a/README.md b/README.md
index 706b2a3c..794b47db 100644
--- a/README.md
+++ b/README.md
@@ -13,9 +13,9 @@ For the complete list of supported PHPDoc features check out PHPStan documentati
 
 * [PHPDoc Basics](https://phpstan.org/writing-php-code/phpdocs-basics) (list of PHPDoc tags)
 * [PHPDoc Types](https://phpstan.org/writing-php-code/phpdoc-types) (list of PHPDoc types)
-* [phpdoc-parser API Reference](https://phpstan.github.io/phpdoc-parser/1.23.x/namespace-PHPStan.PhpDocParser.html) with all the AST node types etc.
+* [phpdoc-parser API Reference](https://phpstan.github.io/phpdoc-parser/2.2.x/namespace-PHPStan.PhpDocParser.html) with all the AST node types etc.
 
-This parser also supports parsing [Doctrine Annotations](https://github.com/doctrine/annotations). The AST nodes live in the [PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine namespace](https://phpstan.github.io/phpdoc-parser/1.23.x/namespace-PHPStan.PhpDocParser.Ast.PhpDoc.Doctrine.html). The support needs to be turned on by setting `bool $parseDoctrineAnnotations` to `true` in `Lexer` and `PhpDocParser` class constructors.
+This parser also supports parsing [Doctrine Annotations](https://github.com/doctrine/annotations). The AST nodes live in the [PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine namespace](https://phpstan.github.io/phpdoc-parser/2.1.x/namespace-PHPStan.PhpDocParser.Ast.PhpDoc.Doctrine.html).
 
 ## Installation
 
@@ -34,6 +34,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
 use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
 use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\ParserConfig;
 use PHPStan\PhpDocParser\Parser\ConstExprParser;
 use PHPStan\PhpDocParser\Parser\PhpDocParser;
 use PHPStan\PhpDocParser\Parser\TokenIterator;
@@ -41,10 +42,11 @@ use PHPStan\PhpDocParser\Parser\TypeParser;
 
 // basic setup
 
-$lexer = new Lexer();
-$constExprParser = new ConstExprParser();
-$typeParser = new TypeParser($constExprParser);
-$phpDocParser = new PhpDocParser($typeParser, $constExprParser);
+$config = new ParserConfig(usedAttributes: []);
+$lexer = new Lexer($config);
+$constExprParser = new ConstExprParser($config);
+$typeParser = new TypeParser($config, $constExprParser);
+$phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
 
 // parsing and reading a PHPDoc string
 
@@ -72,6 +74,7 @@ use PHPStan\PhpDocParser\Ast\NodeVisitor\CloningVisitor;
 use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
 use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
 use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\ParserConfig;
 use PHPStan\PhpDocParser\Parser\ConstExprParser;
 use PHPStan\PhpDocParser\Parser\PhpDocParser;
 use PHPStan\PhpDocParser\Parser\TokenIterator;
@@ -80,12 +83,11 @@ use PHPStan\PhpDocParser\Printer\Printer;
 
 // basic setup with enabled required lexer attributes
 
-$usedAttributes = ['lines' => true, 'indexes' => true];
-
-$lexer = new Lexer();
-$constExprParser = new ConstExprParser(true, true, $usedAttributes);
-$typeParser = new TypeParser($constExprParser, true, $usedAttributes);
-$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, $usedAttributes);
+$config = new ParserConfig(usedAttributes: ['lines' => true, 'indexes' => true, 'comments' => true]);
+$lexer = new Lexer($config);
+$constExprParser = new ConstExprParser($config);
+$typeParser = new TypeParser($config, $constExprParser);
+$phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
 
 $tokens = new TokenIterator($lexer->tokenize('/** @param Lorem $a */'));
 $phpDocNode = $phpDocParser->parse($tokens); // PhpDocNode
diff --git a/UPGRADING.md b/UPGRADING.md
new file mode 100644
index 00000000..b673bccf
--- /dev/null
+++ b/UPGRADING.md
@@ -0,0 +1,129 @@
+Upgrading from phpstan/phpdoc-parser 1.x to 2.0
+=================================
+
+### PHP version requirements
+
+phpstan/phpdoc-parser now requires PHP 7.4 or newer to run.
+
+### Changed constructors of parser classes
+
+Instead of different arrays and boolean values passed into class constructors during setup, parser classes now share a common ParserConfig object.
+
+Before:
+
+```php
+use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\Parser\ConstExprParser;
+use PHPStan\PhpDocParser\Parser\TypeParser;
+use PHPStan\PhpDocParser\Parser\PhpDocParser;
+
+$usedAttributes = ['lines' => true, 'indexes' => true];
+
+$lexer = new Lexer();
+$constExprParser = new ConstExprParser(true, true, $usedAttributes);
+$typeParser = new TypeParser($constExprParser, true, $usedAttributes);
+$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, $usedAttributes);
+```
+
+After:
+
+```php
+use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\ParserConfig;
+use PHPStan\PhpDocParser\Parser\ConstExprParser;
+use PHPStan\PhpDocParser\Parser\TypeParser;
+use PHPStan\PhpDocParser\Parser\PhpDocParser;
+
+$config = new ParserConfig(usedAttributes: ['lines' => true, 'indexes' => true]);
+$lexer = new Lexer($config);
+$constExprParser = new ConstExprParser($config);
+$typeParser = new TypeParser($config, $constExprParser);
+$phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
+```
+
+The point of ParserConfig is that over the course of phpstan/phpdoc-parser 2.x development series it's most likely going to gain new optional parameters akin to PHPStan's [bleeding edge](https://phpstan.org/blog/what-is-bleeding-edge). These parameters will allow opting in to new behaviour which will become the default in 3.0.
+
+With ParserConfig object, it's now going to be impossible to configure parser classes inconsistently. Which [happened to users](https://github.com/phpstan/phpdoc-parser/issues/251#issuecomment-2333927959) when they were separate boolean values.
+
+### Support for parsing Doctrine annotations
+
+This parser now supports parsing [Doctrine Annotations](https://github.com/doctrine/annotations). The AST nodes representing Doctrine Annotations live in the [PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine namespace](https://phpstan.github.io/phpdoc-parser/2.0.x/namespace-PHPStan.PhpDocParser.Ast.PhpDoc.Doctrine.html).
+
+### Whitespace before description is required
+
+phpdoc-parser 1.x sometimes silently consumed invalid part of a PHPDoc type as description:
+
+```php
+/** @return \Closure(...int, string): string */
+```
+
+This became `IdentifierTypeNode` of `\Closure` and with `(...int, string): string` as description. (Valid callable syntax is: `\Closure(int ...$u, string): string`.)
+
+Another example:
+
+```php
+/** @return array{foo: int}} */
+```
+
+The extra `}` also became description.
+
+Both of these examples are now InvalidTagValueNode.
+
+If these parts are supposed to be PHPDoc descriptions, you need to put whitespace between the type and the description text:
+
+```php
+/** @return \Closure (...int, string): string */
+/** @return array{foo: int} } */
+```
+
+### Type aliases with invalid types are preserved
+
+In phpdoc-parser 1.x, invalid type alias syntax was represented as [`InvalidTagValueNode`](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Ast.PhpDoc.InvalidTagValueNode.html), losing information about a type alias being present.
+
+```php
+/**
+ * @phpstan-type TypeAlias
+ */
+```
+
+This `@phpstan-type` is missing the actual type to alias. In phpdoc-parser 2.0 this is now represented as [`TypeAliasTagValueNode`](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Ast.PhpDoc.TypeAliasTagValueNode.html) (instead of `InvalidTagValueNode`) with [`InvalidTypeNode`](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Ast.Type.InvalidTypeNode.html) in place of the type.
+
+### Removal of QuoteAwareConstExprStringNode
+
+The class [QuoteAwareConstExprStringNode](https://phpstan.github.io/phpdoc-parser/1.23.x/PHPStan.PhpDocParser.Ast.ConstExpr.QuoteAwareConstExprStringNode.html) has been removed.
+
+Instead, [ConstExprStringNode](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Ast.ConstExpr.ConstExprStringNode.html) gained information about the kind of quotes being used.
+
+### Removed 2nd parameter of `ConstExprParser::parse()` (`$trimStrings`)
+
+`ConstExprStringNode::$value` now contains unescaped values without surrounding `''` or `""` quotes.
+
+Use `ConstExprStringNode::__toString()` or [`Printer`](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Printer.Printer.html) to get the escaped value along with surrounding quotes.
+
+### Text between tags always belongs to description
+
+Multi-line descriptions between tags were previously represented as separate [PhpDocTextNode](https://phpstan.github.io/phpdoc-parser/2.0.x/PHPStan.PhpDocParser.Ast.PhpDoc.PhpDocTextNode.html):
+
+```php
+/**
+ * @param Foo $foo 1st multi world description
+ * some text in the middle
+ * @param Bar $bar 2nd multi world description
+ */
+```
+
+The line with `some text in the middle` in phpdoc-parser 2.0 is now part of the description of the first `@param` tag.
+
+### `ArrayShapeNode` construction changes
+
+`ArrayShapeNode` constructor made private, added public static methods `createSealed()` and `createUnsealed()`.
+
+### Minor BC breaks
+
+* Constructor parameter `$isEquality` in `AssertTag*ValueNode` made required
+* Constructor parameter `$templateTypes` in `MethodTagValueNode` made required
+* Constructor parameter `$isReference` in `ParamTagValueNode` made required
+* Constructor parameter `$isReference` in `TypelessParamTagValueNode` made required
+* Constructor parameter `$templateTypes` in `CallableTypeNode` made required
+* Constructor parameters `$expectedTokenValue` and `$currentTokenLine` in `ParserException` made required
+* `ArrayShapeItemNode` and `ObjectShapeItemNode` are not standalone TypeNode, just Node
diff --git a/composer.json b/composer.json
index f1c648e5..8047c49e 100644
--- a/composer.json
+++ b/composer.json
@@ -3,17 +3,17 @@
 	"description": "PHPDoc parser with support for nullable, intersection and generic types",
 	"license": "MIT",
 	"require": {
-		"php": "^7.2 || ^8.0"
+		"php": "^7.4 || ^8.0"
 	},
 	"require-dev": {
 		"doctrine/annotations": "^2.0",
-		"nikic/php-parser": "^4.15",
+		"nikic/php-parser": "^5.3.0",
 		"php-parallel-lint/php-parallel-lint": "^1.2",
 		"phpstan/extension-installer": "^1.0",
-		"phpstan/phpstan": "^1.5",
-		"phpstan/phpstan-phpunit": "^1.1",
-		"phpstan/phpstan-strict-rules": "^1.0",
-		"phpunit/phpunit": "^9.5",
+		"phpstan/phpstan": "^2.0",
+		"phpstan/phpstan-phpunit": "^2.0",
+		"phpstan/phpstan-strict-rules": "^2.0",
+		"phpunit/phpunit": "^9.6",
 		"symfony/process": "^5.2"
 	},
 	"config": {
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 4596cc77..3bc8ae89 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -1,26 +1,31 @@
 parameters:
 	ignoreErrors:
 		-
-			message: "#^Method PHPStan\\\\PhpDocParser\\\\Ast\\\\ConstExpr\\\\QuoteAwareConstExprStringNode\\:\\:escapeDoubleQuotedString\\(\\) should return string but returns string\\|null\\.$#"
+			message: '#^Method PHPStan\\PhpDocParser\\Ast\\ConstExpr\\ConstExprStringNode\:\:escapeDoubleQuotedString\(\) should return string but returns string\|null\.$#'
+			identifier: return.type
 			count: 1
-			path: src/Ast/ConstExpr/QuoteAwareConstExprStringNode.php
+			path: src/Ast/ConstExpr/ConstExprStringNode.php
 
 		-
-			message: "#^Cannot use array destructuring on array\\<int, array\\<PHPStan\\\\PhpDocParser\\\\Ast\\\\Node\\>\\|int\\|string\\>\\|null\\.$#"
+			message: '#^Cannot use array destructuring on list\<array\<PHPStan\\PhpDocParser\\Ast\\Node\>\|int\|string\>\|null\.$#'
+			identifier: offsetAccess.nonArray
 			count: 1
 			path: src/Ast/NodeTraverser.php
 
 		-
-			message: "#^Variable property access on PHPStan\\\\PhpDocParser\\\\Ast\\\\Node\\.$#"
+			message: '#^Variable property access on PHPStan\\PhpDocParser\\Ast\\Node\.$#'
+			identifier: property.dynamicName
 			count: 1
 			path: src/Ast/NodeTraverser.php
 
 		-
-			message: "#^Method PHPStan\\\\PhpDocParser\\\\Parser\\\\StringUnescaper\\:\\:parseEscapeSequences\\(\\) should return string but returns string\\|null\\.$#"
+			message: '#^Method PHPStan\\PhpDocParser\\Parser\\StringUnescaper\:\:parseEscapeSequences\(\) should return string but returns string\|null\.$#'
+			identifier: return.type
 			count: 1
 			path: src/Parser/StringUnescaper.php
 
 		-
-			message: "#^Variable property access on PHPStan\\\\PhpDocParser\\\\Ast\\\\Node\\.$#"
+			message: '#^Variable property access on PHPStan\\PhpDocParser\\Ast\\Node\.$#'
+			identifier: property.dynamicName
 			count: 2
 			path: src/Printer/Printer.php
diff --git a/phpstan.neon b/phpstan.neon
index 58ab0c1a..92ffe420 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -11,3 +11,6 @@ parameters:
 	level: 8
 	ignoreErrors:
 		- '#^Dynamic call to static method PHPUnit\\Framework\\Assert#'
+		-
+			identifier: return.unusedType
+			path: tests/*
diff --git a/src/Ast/Attribute.php b/src/Ast/Attribute.php
index cd3a0a29..1f770ded 100644
--- a/src/Ast/Attribute.php
+++ b/src/Ast/Attribute.php
@@ -13,4 +13,6 @@ final class Attribute
 
 	public const ORIGINAL_NODE = 'originalNode';
 
+	public const COMMENTS = 'comments';
+
 }
diff --git a/src/Ast/Comment.php b/src/Ast/Comment.php
new file mode 100644
index 00000000..79e24ebb
--- /dev/null
+++ b/src/Ast/Comment.php
@@ -0,0 +1,28 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\PhpDocParser\Ast;
+
+use function trim;
+
+class Comment
+{
+
+	public string $text;
+
+	public int $startLine;
+
+	public int $startIndex;
+
+	public function __construct(string $text, int $startLine = -1, int $startIndex = -1)
+	{
+		$this->text = $text;
+		$this->startLine = $startLine;
+		$this->startIndex = $startIndex;
+	}
+
+	public function getReformattedText(): string
+	{
+		return trim($this->text);
+	}
+
+}
diff --git a/src/Ast/ConstExpr/ConstExprArrayItemNode.php b/src/Ast/ConstExpr/ConstExprArrayItemNode.php
index ef144521..cd067cac 100644
--- a/src/Ast/ConstExpr/ConstExprArrayItemNode.php
+++ b/src/Ast/ConstExpr/ConstExprArrayItemNode.php
@@ -10,11 +10,9 @@ class ConstExprArrayItemNode implements ConstExprNode
 
 	use NodeAttributes;
 
-	/** @var ConstExprNode|null */
-	public $key;
+	public ?ConstExprNode $key = null;
 
-	/** @var ConstExprNode */
-	public $value;
+	public ConstExprNode $value;
 
 	public function __construct(?ConstExprNode $key, ConstExprNode $value)
 	{
diff --git a/src/Ast/ConstExpr/ConstExprArrayNode.php b/src/Ast/ConstExpr/ConstExprArrayNode.php
index 1f9def37..dc7ad4a9 100644
--- a/src/Ast/ConstExpr/ConstExprArrayNode.php
+++ b/src/Ast/ConstExpr/ConstExprArrayNode.php
@@ -11,7 +11,7 @@ class ConstExprArrayNode implements ConstExprNode
 	use NodeAttributes;
 
 	/** @var ConstExprArrayItemNode[] */
-	public $items;
+	public array $items;
 
 	/**
 	 * @param ConstExprArrayItemNode[] $items
diff --git a/src/Ast/ConstExpr/ConstExprFloatNode.php b/src/Ast/ConstExpr/ConstExprFloatNode.php
index a4192fba..30ab41de 100644
--- a/src/Ast/ConstExpr/ConstExprFloatNode.php
+++ b/src/Ast/ConstExpr/ConstExprFloatNode.php
@@ -9,8 +9,7 @@ class ConstExprFloatNode implements ConstExprNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $value;
+	public string $value;
 
 	public function __construct(string $value)
 	{
diff --git a/src/Ast/ConstExpr/ConstExprIntegerNode.php b/src/Ast/ConstExpr/ConstExprIntegerNode.php
index 5339bb5a..9f0285f3 100644
--- a/src/Ast/ConstExpr/ConstExprIntegerNode.php
+++ b/src/Ast/ConstExpr/ConstExprIntegerNode.php
@@ -9,8 +9,7 @@ class ConstExprIntegerNode implements ConstExprNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $value;
+	public string $value;
 
 	public function __construct(string $value)
 	{
diff --git a/src/Ast/ConstExpr/ConstExprStringNode.php b/src/Ast/ConstExpr/ConstExprStringNode.php
index fa44c262..26e5ef4a 100644
--- a/src/Ast/ConstExpr/ConstExprStringNode.php
+++ b/src/Ast/ConstExpr/ConstExprStringNode.php
@@ -3,24 +3,78 @@
 namespace PHPStan\PhpDocParser\Ast\ConstExpr;
 
 use PHPStan\PhpDocParser\Ast\NodeAttributes;
+use function addcslashes;
+use function assert;
+use function dechex;
+use function ord;
+use function preg_replace_callback;
+use function sprintf;
+use function str_pad;
+use function strlen;
+use const STR_PAD_LEFT;
 
 class ConstExprStringNode implements ConstExprNode
 {
 
+	public const SINGLE_QUOTED = 1;
+	public const DOUBLE_QUOTED = 2;
+
 	use NodeAttributes;
 
-	/** @var string */
-	public $value;
+	public string $value;
+
+	/** @var self::SINGLE_QUOTED|self::DOUBLE_QUOTED */
+	public $quoteType;
 
-	public function __construct(string $value)
+	/**
+	 * @param self::SINGLE_QUOTED|self::DOUBLE_QUOTED $quoteType
+	 */
+	public function __construct(string $value, int $quoteType)
 	{
 		$this->value = $value;
+		$this->quoteType = $quoteType;
 	}
 
 
 	public function __toString(): string
 	{
-		return $this->value;
+		if ($this->quoteType === self::SINGLE_QUOTED) {
+			// from https://github.com/nikic/PHP-Parser/blob/0ffddce52d816f72d0efc4d9b02e276d3309ef01/lib/PhpParser/PrettyPrinter/Standard.php#L1007
+			return sprintf("'%s'", addcslashes($this->value, '\'\\'));
+		}
+
+		// from https://github.com/nikic/PHP-Parser/blob/0ffddce52d816f72d0efc4d9b02e276d3309ef01/lib/PhpParser/PrettyPrinter/Standard.php#L1010-L1040
+		return sprintf('"%s"', $this->escapeDoubleQuotedString());
+	}
+
+	private function escapeDoubleQuotedString(): string
+	{
+		$quote = '"';
+		$escaped = addcslashes($this->value, "\n\r\t\f\v$" . $quote . '\\');
+
+		// Escape control characters and non-UTF-8 characters.
+		// Regex based on https://stackoverflow.com/a/11709412/385378.
+		$regex = '/(
+              [\x00-\x08\x0E-\x1F] # Control characters
+            | [\xC0-\xC1] # Invalid UTF-8 Bytes
+            | [\xF5-\xFF] # Invalid UTF-8 Bytes
+            | \xE0(?=[\x80-\x9F]) # Overlong encoding of prior code point
+            | \xF0(?=[\x80-\x8F]) # Overlong encoding of prior code point
+            | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
+            | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
+            | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
+            | (?<=[\x00-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
+            | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
+            | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
+            | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
+            | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
+        )/x';
+		return preg_replace_callback($regex, static function ($matches) {
+			assert(strlen($matches[0]) === 1);
+			$hex = dechex(ord($matches[0]));
+
+			return '\\x' . str_pad($hex, 2, '0', STR_PAD_LEFT);
+		}, $escaped);
 	}
 
 }
diff --git a/src/Ast/ConstExpr/ConstFetchNode.php b/src/Ast/ConstExpr/ConstFetchNode.php
index 3f20363b..51c33989 100644
--- a/src/Ast/ConstExpr/ConstFetchNode.php
+++ b/src/Ast/ConstExpr/ConstFetchNode.php
@@ -10,10 +10,9 @@ class ConstFetchNode implements ConstExprNode
 	use NodeAttributes;
 
 	/** @var string class name for class constants or empty string for non-class constants */
-	public $className;
+	public string $className;
 
-	/** @var string */
-	public $name;
+	public string $name;
 
 	public function __construct(string $className, string $name)
 	{
diff --git a/src/Ast/ConstExpr/DoctrineConstExprStringNode.php b/src/Ast/ConstExpr/DoctrineConstExprStringNode.php
index a503937b..4515e892 100644
--- a/src/Ast/ConstExpr/DoctrineConstExprStringNode.php
+++ b/src/Ast/ConstExpr/DoctrineConstExprStringNode.php
@@ -13,12 +13,11 @@ class DoctrineConstExprStringNode extends ConstExprStringNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $value;
+	public string $value;
 
 	public function __construct(string $value)
 	{
-		parent::__construct($value);
+		parent::__construct($value, self::DOUBLE_QUOTED);
 		$this->value = $value;
 	}
 
diff --git a/src/Ast/ConstExpr/QuoteAwareConstExprStringNode.php b/src/Ast/ConstExpr/QuoteAwareConstExprStringNode.php
deleted file mode 100644
index f2792b1b..00000000
--- a/src/Ast/ConstExpr/QuoteAwareConstExprStringNode.php
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php declare(strict_types = 1);
-
-namespace PHPStan\PhpDocParser\Ast\ConstExpr;
-
-use PHPStan\PhpDocParser\Ast\NodeAttributes;
-use function addcslashes;
-use function assert;
-use function dechex;
-use function ord;
-use function preg_replace_callback;
-use function sprintf;
-use function str_pad;
-use function strlen;
-use const STR_PAD_LEFT;
-
-class QuoteAwareConstExprStringNode extends ConstExprStringNode implements ConstExprNode
-{
-
-	public const SINGLE_QUOTED = 1;
-	public const DOUBLE_QUOTED = 2;
-
-	use NodeAttributes;
-
-	/** @var self::SINGLE_QUOTED|self::DOUBLE_QUOTED */
-	public $quoteType;
-
-	/**
-	 * @param self::SINGLE_QUOTED|self::DOUBLE_QUOTED $quoteType
-	 */
-	public function __construct(string $value, int $quoteType)
-	{
-		parent::__construct($value);
-		$this->quoteType = $quoteType;
-	}
-
-
-	public function __toString(): string
-	{
-		if ($this->quoteType === self::SINGLE_QUOTED) {
-			// from https://github.com/nikic/PHP-Parser/blob/0ffddce52d816f72d0efc4d9b02e276d3309ef01/lib/PhpParser/PrettyPrinter/Standard.php#L1007
-			return sprintf("'%s'", addcslashes($this->value, '\'\\'));
-		}
-
-		// from https://github.com/nikic/PHP-Parser/blob/0ffddce52d816f72d0efc4d9b02e276d3309ef01/lib/PhpParser/PrettyPrinter/Standard.php#L1010-L1040
-		return sprintf('"%s"', $this->escapeDoubleQuotedString());
-	}
-
-	private function escapeDoubleQuotedString(): string
-	{
-		$quote = '"';
-		$escaped = addcslashes($this->value, "\n\r\t\f\v$" . $quote . '\\');
-
-		// Escape control characters and non-UTF-8 characters.
-		// Regex based on https://stackoverflow.com/a/11709412/385378.
-		$regex = '/(
-              [\x00-\x08\x0E-\x1F] # Control characters
-            | [\xC0-\xC1] # Invalid UTF-8 Bytes
-            | [\xF5-\xFF] # Invalid UTF-8 Bytes
-            | \xE0(?=[\x80-\x9F]) # Overlong encoding of prior code point
-            | \xF0(?=[\x80-\x8F]) # Overlong encoding of prior code point
-            | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
-            | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
-            | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
-            | (?<=[\x00-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
-            | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
-            | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
-            | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
-            | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
-        )/x';
-		return preg_replace_callback($regex, static function ($matches) {
-			assert(strlen($matches[0]) === 1);
-			$hex = dechex(ord($matches[0]));
-
-			return '\\x' . str_pad($hex, 2, '0', STR_PAD_LEFT);
-		}, $escaped);
-	}
-
-}
diff --git a/src/Ast/NodeAttributes.php b/src/Ast/NodeAttributes.php
index 3da1779d..4f2ca76d 100644
--- a/src/Ast/NodeAttributes.php
+++ b/src/Ast/NodeAttributes.php
@@ -8,13 +8,17 @@ trait NodeAttributes
 {
 
 	/** @var array<string, mixed> */
-	private $attributes = [];
+	private array $attributes = [];
 
 	/**
 	 * @param mixed $value
 	 */
 	public function setAttribute(string $key, $value): void
 	{
+		if ($value === null) {
+			unset($this->attributes[$key]);
+			return;
+		}
 		$this->attributes[$key] = $value;
 	}
 
diff --git a/src/Ast/NodeTraverser.php b/src/Ast/NodeTraverser.php
index 63b25c37..76c88f0c 100644
--- a/src/Ast/NodeTraverser.php
+++ b/src/Ast/NodeTraverser.php
@@ -62,10 +62,10 @@ final class NodeTraverser
 	public const DONT_TRAVERSE_CURRENT_AND_CHILDREN = 4;
 
 	/** @var list<NodeVisitor> Visitors */
-	private $visitors = [];
+	private array $visitors = [];
 
 	/** @var bool Whether traversal should be stopped */
-	private $stopTraversal;
+	private bool $stopTraversal;
 
 	/**
 	 * @param list<NodeVisitor> $visitors
@@ -151,7 +151,7 @@ private function traverseNode(Node $node): Node
 						break 2;
 					} else {
 						throw new LogicException(
-							'enterNode() returned invalid value of type ' . gettype($return)
+							'enterNode() returned invalid value of type ' . gettype($return),
 						);
 					}
 				}
@@ -176,11 +176,11 @@ private function traverseNode(Node $node): Node
 						} elseif (is_array($return)) {
 							throw new LogicException(
 								'leaveNode() may only return an array ' .
-								'if the parent structure is an array'
+								'if the parent structure is an array',
 							);
 						} else {
 							throw new LogicException(
-								'leaveNode() returned invalid value of type ' . gettype($return)
+								'leaveNode() returned invalid value of type ' . gettype($return),
 							);
 						}
 					}
@@ -237,7 +237,7 @@ private function traverseArray(array $nodes): array
 						break 2;
 					} else {
 						throw new LogicException(
-							'enterNode() returned invalid value of type ' . gettype($return)
+							'enterNode() returned invalid value of type ' . gettype($return),
 						);
 					}
 				}
@@ -267,7 +267,7 @@ private function traverseArray(array $nodes): array
 							break 2;
 						} else {
 							throw new LogicException(
-								'leaveNode() returned invalid value of type ' . gettype($return)
+								'leaveNode() returned invalid value of type ' . gettype($return),
 							);
 						}
 					}
diff --git a/src/Ast/NodeVisitor/CloningVisitor.php b/src/Ast/NodeVisitor/CloningVisitor.php
index 7200f3af..486e2aae 100644
--- a/src/Ast/NodeVisitor/CloningVisitor.php
+++ b/src/Ast/NodeVisitor/CloningVisitor.php
@@ -9,7 +9,7 @@
 final class CloningVisitor extends AbstractNodeVisitor
 {
 
-	public function enterNode(Node $originalNode)
+	public function enterNode(Node $originalNode): Node
 	{
 		$node = clone $originalNode;
 		$node->setAttribute(Attribute::ORIGINAL_NODE, $originalNode);
diff --git a/src/Ast/PhpDoc/AssertTagMethodValueNode.php b/src/Ast/PhpDoc/AssertTagMethodValueNode.php
index cf4f5563..0dfee314 100644
--- a/src/Ast/PhpDoc/AssertTagMethodValueNode.php
+++ b/src/Ast/PhpDoc/AssertTagMethodValueNode.php
@@ -11,25 +11,20 @@ class AssertTagMethodValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
-	/** @var string */
-	public $parameter;
+	public string $parameter;
 
-	/** @var string */
-	public $method;
+	public string $method;
 
-	/** @var bool */
-	public $isNegated;
+	public bool $isNegated;
 
-	/** @var bool */
-	public $isEquality;
+	public bool $isEquality;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
-	public function __construct(TypeNode $type, string $parameter, string $method, bool $isNegated, string $description, bool $isEquality = false)
+	public function __construct(TypeNode $type, string $parameter, string $method, bool $isNegated, string $description, bool $isEquality)
 	{
 		$this->type = $type;
 		$this->parameter = $parameter;
diff --git a/src/Ast/PhpDoc/AssertTagPropertyValueNode.php b/src/Ast/PhpDoc/AssertTagPropertyValueNode.php
index 4fb31807..8bfd1d0e 100644
--- a/src/Ast/PhpDoc/AssertTagPropertyValueNode.php
+++ b/src/Ast/PhpDoc/AssertTagPropertyValueNode.php
@@ -11,25 +11,20 @@ class AssertTagPropertyValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
-	/** @var string */
-	public $parameter;
+	public string $parameter;
 
-	/** @var string */
-	public $property;
+	public string $property;
 
-	/** @var bool */
-	public $isNegated;
+	public bool $isNegated;
 
-	/** @var bool */
-	public $isEquality;
+	public bool $isEquality;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
-	public function __construct(TypeNode $type, string $parameter, string $property, bool $isNegated, string $description, bool $isEquality = false)
+	public function __construct(TypeNode $type, string $parameter, string $property, bool $isNegated, string $description, bool $isEquality)
 	{
 		$this->type = $type;
 		$this->parameter = $parameter;
diff --git a/src/Ast/PhpDoc/AssertTagValueNode.php b/src/Ast/PhpDoc/AssertTagValueNode.php
index d6423f50..5dc9e8c3 100644
--- a/src/Ast/PhpDoc/AssertTagValueNode.php
+++ b/src/Ast/PhpDoc/AssertTagValueNode.php
@@ -11,22 +11,18 @@ class AssertTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
-	/** @var string */
-	public $parameter;
+	public string $parameter;
 
-	/** @var bool */
-	public $isNegated;
+	public bool $isNegated;
 
-	/** @var bool */
-	public $isEquality;
+	public bool $isEquality;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
-	public function __construct(TypeNode $type, string $parameter, bool $isNegated, string $description, bool $isEquality = false)
+	public function __construct(TypeNode $type, string $parameter, bool $isNegated, string $description, bool $isEquality)
 	{
 		$this->type = $type;
 		$this->parameter = $parameter;
diff --git a/src/Ast/PhpDoc/DeprecatedTagValueNode.php b/src/Ast/PhpDoc/DeprecatedTagValueNode.php
index abf2f1a6..facdd2b4 100644
--- a/src/Ast/PhpDoc/DeprecatedTagValueNode.php
+++ b/src/Ast/PhpDoc/DeprecatedTagValueNode.php
@@ -11,7 +11,7 @@ class DeprecatedTagValueNode implements PhpDocTagValueNode
 	use NodeAttributes;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(string $description)
 	{
diff --git a/src/Ast/PhpDoc/Doctrine/DoctrineAnnotation.php b/src/Ast/PhpDoc/Doctrine/DoctrineAnnotation.php
index 3a93f5aa..778b21fa 100644
--- a/src/Ast/PhpDoc/Doctrine/DoctrineAnnotation.php
+++ b/src/Ast/PhpDoc/Doctrine/DoctrineAnnotation.php
@@ -11,11 +11,10 @@ class DoctrineAnnotation implements Node
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $name;
+	public string $name;
 
 	/** @var list<DoctrineArgument> */
-	public $arguments;
+	public array $arguments;
 
 	/**
 	 * @param list<DoctrineArgument> $arguments
diff --git a/src/Ast/PhpDoc/Doctrine/DoctrineArgument.php b/src/Ast/PhpDoc/Doctrine/DoctrineArgument.php
index f30812cf..30fe9879 100644
--- a/src/Ast/PhpDoc/Doctrine/DoctrineArgument.php
+++ b/src/Ast/PhpDoc/Doctrine/DoctrineArgument.php
@@ -15,8 +15,7 @@ class DoctrineArgument implements Node
 
 	use NodeAttributes;
 
-	/** @var IdentifierTypeNode|null */
-	public $key;
+	public ?IdentifierTypeNode $key = null;
 
 	/** @var ValueType */
 	public $value;
diff --git a/src/Ast/PhpDoc/Doctrine/DoctrineArray.php b/src/Ast/PhpDoc/Doctrine/DoctrineArray.php
index e740567d..06686a52 100644
--- a/src/Ast/PhpDoc/Doctrine/DoctrineArray.php
+++ b/src/Ast/PhpDoc/Doctrine/DoctrineArray.php
@@ -12,7 +12,7 @@ class DoctrineArray implements Node
 	use NodeAttributes;
 
 	/** @var list<DoctrineArrayItem> */
-	public $items;
+	public array $items;
 
 	/**
 	 * @param list<DoctrineArrayItem> $items
diff --git a/src/Ast/PhpDoc/Doctrine/DoctrineTagValueNode.php b/src/Ast/PhpDoc/Doctrine/DoctrineTagValueNode.php
index 84f7b18b..3940e24f 100644
--- a/src/Ast/PhpDoc/Doctrine/DoctrineTagValueNode.php
+++ b/src/Ast/PhpDoc/Doctrine/DoctrineTagValueNode.php
@@ -11,11 +11,10 @@ class DoctrineTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var DoctrineAnnotation */
-	public $annotation;
+	public DoctrineAnnotation $annotation;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 
 	public function __construct(
diff --git a/src/Ast/PhpDoc/ExtendsTagValueNode.php b/src/Ast/PhpDoc/ExtendsTagValueNode.php
index 3bf53e13..d9bbeec5 100644
--- a/src/Ast/PhpDoc/ExtendsTagValueNode.php
+++ b/src/Ast/PhpDoc/ExtendsTagValueNode.php
@@ -11,11 +11,10 @@ class ExtendsTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var GenericTypeNode */
-	public $type;
+	public GenericTypeNode $type;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(GenericTypeNode $type, string $description)
 	{
diff --git a/src/Ast/PhpDoc/GenericTagValueNode.php b/src/Ast/PhpDoc/GenericTagValueNode.php
index 026aa153..de77051a 100644
--- a/src/Ast/PhpDoc/GenericTagValueNode.php
+++ b/src/Ast/PhpDoc/GenericTagValueNode.php
@@ -10,7 +10,7 @@ class GenericTagValueNode implements PhpDocTagValueNode
 	use NodeAttributes;
 
 	/** @var string (may be empty) */
-	public $value;
+	public string $value;
 
 	public function __construct(string $value)
 	{
diff --git a/src/Ast/PhpDoc/ImplementsTagValueNode.php b/src/Ast/PhpDoc/ImplementsTagValueNode.php
index 99043d91..34a30b0d 100644
--- a/src/Ast/PhpDoc/ImplementsTagValueNode.php
+++ b/src/Ast/PhpDoc/ImplementsTagValueNode.php
@@ -11,11 +11,10 @@ class ImplementsTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var GenericTypeNode */
-	public $type;
+	public GenericTypeNode $type;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(GenericTypeNode $type, string $description)
 	{
diff --git a/src/Ast/PhpDoc/InvalidTagValueNode.php b/src/Ast/PhpDoc/InvalidTagValueNode.php
index ca7b4f20..7bb20b22 100644
--- a/src/Ast/PhpDoc/InvalidTagValueNode.php
+++ b/src/Ast/PhpDoc/InvalidTagValueNode.php
@@ -17,10 +17,10 @@ class InvalidTagValueNode implements PhpDocTagValueNode
 	use NodeAttributes;
 
 	/** @var string (may be empty) */
-	public $value;
+	public string $value;
 
 	/** @var mixed[] */
-	private $exceptionArgs;
+	private array $exceptionArgs;
 
 	public function __construct(string $value, ParserException $exception)
 	{
diff --git a/src/Ast/PhpDoc/MethodTagValueNode.php b/src/Ast/PhpDoc/MethodTagValueNode.php
index 211510be..223d6e47 100644
--- a/src/Ast/PhpDoc/MethodTagValueNode.php
+++ b/src/Ast/PhpDoc/MethodTagValueNode.php
@@ -12,29 +12,26 @@ class MethodTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var bool */
-	public $isStatic;
+	public bool $isStatic;
 
-	/** @var TypeNode|null */
-	public $returnType;
+	public ?TypeNode $returnType = null;
 
-	/** @var string */
-	public $methodName;
+	public string $methodName;
 
 	/** @var TemplateTagValueNode[] */
-	public $templateTypes;
+	public array $templateTypes;
 
 	/** @var MethodTagValueParameterNode[] */
-	public $parameters;
+	public array $parameters;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	/**
 	 * @param MethodTagValueParameterNode[] $parameters
 	 * @param TemplateTagValueNode[] $templateTypes
 	 */
-	public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description, array $templateTypes = [])
+	public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description, array $templateTypes)
 	{
 		$this->isStatic = $isStatic;
 		$this->returnType = $returnType;
diff --git a/src/Ast/PhpDoc/MethodTagValueParameterNode.php b/src/Ast/PhpDoc/MethodTagValueParameterNode.php
index 7c17e44c..ebf33e32 100644
--- a/src/Ast/PhpDoc/MethodTagValueParameterNode.php
+++ b/src/Ast/PhpDoc/MethodTagValueParameterNode.php
@@ -12,20 +12,15 @@ class MethodTagValueParameterNode implements Node
 
 	use NodeAttributes;
 
-	/** @var TypeNode|null */
-	public $type;
+	public ?TypeNode $type = null;
 
-	/** @var bool */
-	public $isReference;
+	public bool $isReference;
 
-	/** @var bool */
-	public $isVariadic;
+	public bool $isVariadic;
 
-	/** @var string */
-	public $parameterName;
+	public string $parameterName;
 
-	/** @var ConstExprNode|null */
-	public $defaultValue;
+	public ?ConstExprNode $defaultValue = null;
 
 	public function __construct(?TypeNode $type, bool $isReference, bool $isVariadic, string $parameterName, ?ConstExprNode $defaultValue)
 	{
diff --git a/src/Ast/PhpDoc/MixinTagValueNode.php b/src/Ast/PhpDoc/MixinTagValueNode.php
index d9b7d78a..7a4e43ea 100644
--- a/src/Ast/PhpDoc/MixinTagValueNode.php
+++ b/src/Ast/PhpDoc/MixinTagValueNode.php
@@ -11,11 +11,10 @@ class MixinTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(TypeNode $type, string $description)
 	{
diff --git a/src/Ast/PhpDoc/ParamClosureThisTagValueNode.php b/src/Ast/PhpDoc/ParamClosureThisTagValueNode.php
index 0ac2131a..54feff9e 100644
--- a/src/Ast/PhpDoc/ParamClosureThisTagValueNode.php
+++ b/src/Ast/PhpDoc/ParamClosureThisTagValueNode.php
@@ -11,14 +11,12 @@ class ParamClosureThisTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
-	/** @var string */
-	public $parameterName;
+	public string $parameterName;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(TypeNode $type, string $parameterName, string $description)
 	{
diff --git a/src/Ast/PhpDoc/ParamImmediatelyInvokedCallableTagValueNode.php b/src/Ast/PhpDoc/ParamImmediatelyInvokedCallableTagValueNode.php
index 0f480f7a..9a6761f7 100644
--- a/src/Ast/PhpDoc/ParamImmediatelyInvokedCallableTagValueNode.php
+++ b/src/Ast/PhpDoc/ParamImmediatelyInvokedCallableTagValueNode.php
@@ -10,11 +10,10 @@ class ParamImmediatelyInvokedCallableTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $parameterName;
+	public string $parameterName;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(string $parameterName, string $description)
 	{
diff --git a/src/Ast/PhpDoc/ParamLaterInvokedCallableTagValueNode.php b/src/Ast/PhpDoc/ParamLaterInvokedCallableTagValueNode.php
index eab353f9..84db67a9 100644
--- a/src/Ast/PhpDoc/ParamLaterInvokedCallableTagValueNode.php
+++ b/src/Ast/PhpDoc/ParamLaterInvokedCallableTagValueNode.php
@@ -10,11 +10,10 @@ class ParamLaterInvokedCallableTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $parameterName;
+	public string $parameterName;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(string $parameterName, string $description)
 	{
diff --git a/src/Ast/PhpDoc/ParamOutTagValueNode.php b/src/Ast/PhpDoc/ParamOutTagValueNode.php
index 9f374bf1..3e89f9d7 100644
--- a/src/Ast/PhpDoc/ParamOutTagValueNode.php
+++ b/src/Ast/PhpDoc/ParamOutTagValueNode.php
@@ -11,14 +11,12 @@ class ParamOutTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
-	/** @var string */
-	public $parameterName;
+	public string $parameterName;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(TypeNode $type, string $parameterName, string $description)
 	{
diff --git a/src/Ast/PhpDoc/ParamTagValueNode.php b/src/Ast/PhpDoc/ParamTagValueNode.php
index f93af0ea..8d1ef27a 100644
--- a/src/Ast/PhpDoc/ParamTagValueNode.php
+++ b/src/Ast/PhpDoc/ParamTagValueNode.php
@@ -11,22 +11,18 @@ class ParamTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
-	/** @var bool */
-	public $isReference;
+	public bool $isReference;
 
-	/** @var bool */
-	public $isVariadic;
+	public bool $isVariadic;
 
-	/** @var string */
-	public $parameterName;
+	public string $parameterName;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
-	public function __construct(TypeNode $type, bool $isVariadic, string $parameterName, string $description, bool $isReference = false)
+	public function __construct(TypeNode $type, bool $isVariadic, string $parameterName, string $description, bool $isReference)
 	{
 		$this->type = $type;
 		$this->isReference = $isReference;
diff --git a/src/Ast/PhpDoc/PhpDocNode.php b/src/Ast/PhpDoc/PhpDocNode.php
index a08b777f..a3dd3e4c 100644
--- a/src/Ast/PhpDoc/PhpDocNode.php
+++ b/src/Ast/PhpDoc/PhpDocNode.php
@@ -15,7 +15,7 @@ class PhpDocNode implements Node
 	use NodeAttributes;
 
 	/** @var PhpDocChildNode[] */
-	public $children;
+	public array $children;
 
 	/**
 	 * @param PhpDocChildNode[] $children
@@ -31,9 +31,7 @@ public function __construct(array $children)
 	 */
 	public function getTags(): array
 	{
-		return array_filter($this->children, static function (PhpDocChildNode $child): bool {
-			return $child instanceof PhpDocTagNode;
-		});
+		return array_filter($this->children, static fn (PhpDocChildNode $child): bool => $child instanceof PhpDocTagNode);
 	}
 
 
@@ -42,9 +40,7 @@ public function getTags(): array
 	 */
 	public function getTagsByName(string $tagName): array
 	{
-		return array_filter($this->getTags(), static function (PhpDocTagNode $tag) use ($tagName): bool {
-			return $tag->name === $tagName;
-		});
+		return array_filter($this->getTags(), static fn (PhpDocTagNode $tag): bool => $tag->name === $tagName);
 	}
 
 
@@ -55,9 +51,7 @@ public function getVarTagValues(string $tagName = '@var'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof VarTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof VarTagValueNode,
 		);
 	}
 
@@ -69,9 +63,7 @@ public function getParamTagValues(string $tagName = '@param'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof ParamTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof ParamTagValueNode,
 		);
 	}
 
@@ -83,9 +75,7 @@ public function getTypelessParamTagValues(string $tagName = '@param'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof TypelessParamTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof TypelessParamTagValueNode,
 		);
 	}
 
@@ -97,9 +87,7 @@ public function getParamImmediatelyInvokedCallableTagValues(string $tagName = '@
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof ParamImmediatelyInvokedCallableTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof ParamImmediatelyInvokedCallableTagValueNode,
 		);
 	}
 
@@ -111,9 +99,7 @@ public function getParamLaterInvokedCallableTagValues(string $tagName = '@param-
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof ParamLaterInvokedCallableTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof ParamLaterInvokedCallableTagValueNode,
 		);
 	}
 
@@ -125,9 +111,7 @@ public function getParamClosureThisTagValues(string $tagName = '@param-closure-t
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof ParamClosureThisTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof ParamClosureThisTagValueNode,
 		);
 	}
 
@@ -138,9 +122,7 @@ public function getPureUnlessCallableIsImpureTagValues(string $tagName = '@pure-
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof PureUnlessCallableIsImpureTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof PureUnlessCallableIsImpureTagValueNode,
 		);
 	}
 
@@ -151,9 +133,7 @@ public function getTemplateTagValues(string $tagName = '@template'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof TemplateTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof TemplateTagValueNode,
 		);
 	}
 
@@ -165,9 +145,7 @@ public function getExtendsTagValues(string $tagName = '@extends'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof ExtendsTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof ExtendsTagValueNode,
 		);
 	}
 
@@ -179,9 +157,7 @@ public function getImplementsTagValues(string $tagName = '@implements'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof ImplementsTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof ImplementsTagValueNode,
 		);
 	}
 
@@ -193,9 +169,7 @@ public function getUsesTagValues(string $tagName = '@use'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof UsesTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof UsesTagValueNode,
 		);
 	}
 
@@ -207,9 +181,7 @@ public function getReturnTagValues(string $tagName = '@return'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof ReturnTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof ReturnTagValueNode,
 		);
 	}
 
@@ -221,9 +193,7 @@ public function getThrowsTagValues(string $tagName = '@throws'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof ThrowsTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof ThrowsTagValueNode,
 		);
 	}
 
@@ -235,9 +205,7 @@ public function getMixinTagValues(string $tagName = '@mixin'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof MixinTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof MixinTagValueNode,
 		);
 	}
 
@@ -248,9 +216,7 @@ public function getRequireExtendsTagValues(string $tagName = '@phpstan-require-e
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof RequireExtendsTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof RequireExtendsTagValueNode,
 		);
 	}
 
@@ -261,9 +227,18 @@ public function getRequireImplementsTagValues(string $tagName = '@phpstan-requir
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof RequireImplementsTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof RequireImplementsTagValueNode,
+		);
+	}
+
+	/**
+	 * @return SealedTagValueNode[]
+	 */
+	public function getSealedTagValues(string $tagName = '@phpstan-sealed'): array
+	{
+		return array_filter(
+			array_column($this->getTagsByName($tagName), 'value'),
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof SealedTagValueNode,
 		);
 	}
 
@@ -274,9 +249,7 @@ public function getDeprecatedTagValues(): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName('@deprecated'), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof DeprecatedTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof DeprecatedTagValueNode,
 		);
 	}
 
@@ -288,9 +261,7 @@ public function getPropertyTagValues(string $tagName = '@property'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof PropertyTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof PropertyTagValueNode,
 		);
 	}
 
@@ -302,9 +273,7 @@ public function getPropertyReadTagValues(string $tagName = '@property-read'): ar
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof PropertyTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof PropertyTagValueNode,
 		);
 	}
 
@@ -316,9 +285,7 @@ public function getPropertyWriteTagValues(string $tagName = '@property-write'):
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof PropertyTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof PropertyTagValueNode,
 		);
 	}
 
@@ -330,9 +297,7 @@ public function getMethodTagValues(string $tagName = '@method'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof MethodTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof MethodTagValueNode,
 		);
 	}
 
@@ -344,9 +309,7 @@ public function getTypeAliasTagValues(string $tagName = '@phpstan-type'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof TypeAliasTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof TypeAliasTagValueNode,
 		);
 	}
 
@@ -358,9 +321,7 @@ public function getTypeAliasImportTagValues(string $tagName = '@phpstan-import-t
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof TypeAliasImportTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof TypeAliasImportTagValueNode,
 		);
 	}
 
@@ -372,9 +333,7 @@ public function getAssertTagValues(string $tagName = '@phpstan-assert'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof AssertTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof AssertTagValueNode,
 		);
 	}
 
@@ -386,9 +345,7 @@ public function getAssertPropertyTagValues(string $tagName = '@phpstan-assert'):
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof AssertTagPropertyValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof AssertTagPropertyValueNode,
 		);
 	}
 
@@ -400,9 +357,7 @@ public function getAssertMethodTagValues(string $tagName = '@phpstan-assert'): a
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof AssertTagMethodValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof AssertTagMethodValueNode,
 		);
 	}
 
@@ -414,9 +369,7 @@ public function getSelfOutTypeTagValues(string $tagName = '@phpstan-this-out'):
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof SelfOutTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof SelfOutTagValueNode,
 		);
 	}
 
@@ -428,9 +381,7 @@ public function getParamOutTypeTagValues(string $tagName = '@param-out'): array
 	{
 		return array_filter(
 			array_column($this->getTagsByName($tagName), 'value'),
-			static function (PhpDocTagValueNode $value): bool {
-				return $value instanceof ParamOutTagValueNode;
-			}
+			static fn (PhpDocTagValueNode $value): bool => $value instanceof ParamOutTagValueNode,
 		);
 	}
 
@@ -442,7 +393,7 @@ static function (PhpDocChildNode $child): string {
 				$s = (string) $child;
 				return $s === '' ? '' : ' ' . $s;
 			},
-			$this->children
+			$this->children,
 		);
 		return "/**\n *" . implode("\n *", $children) . "\n */";
 	}
diff --git a/src/Ast/PhpDoc/PhpDocTagNode.php b/src/Ast/PhpDoc/PhpDocTagNode.php
index d20746fc..cac2feeb 100644
--- a/src/Ast/PhpDoc/PhpDocTagNode.php
+++ b/src/Ast/PhpDoc/PhpDocTagNode.php
@@ -11,11 +11,9 @@ class PhpDocTagNode implements PhpDocChildNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $name;
+	public string $name;
 
-	/** @var PhpDocTagValueNode */
-	public $value;
+	public PhpDocTagValueNode $value;
 
 	public function __construct(string $name, PhpDocTagValueNode $value)
 	{
diff --git a/src/Ast/PhpDoc/PhpDocTextNode.php b/src/Ast/PhpDoc/PhpDocTextNode.php
index 0bca3c99..97a96894 100644
--- a/src/Ast/PhpDoc/PhpDocTextNode.php
+++ b/src/Ast/PhpDoc/PhpDocTextNode.php
@@ -9,8 +9,7 @@ class PhpDocTextNode implements PhpDocChildNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $text;
+	public string $text;
 
 	public function __construct(string $text)
 	{
diff --git a/src/Ast/PhpDoc/PropertyTagValueNode.php b/src/Ast/PhpDoc/PropertyTagValueNode.php
index 046003d1..cbf622f8 100644
--- a/src/Ast/PhpDoc/PropertyTagValueNode.php
+++ b/src/Ast/PhpDoc/PropertyTagValueNode.php
@@ -11,14 +11,12 @@ class PropertyTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
-	/** @var string */
-	public $propertyName;
+	public string $propertyName;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(TypeNode $type, string $propertyName, string $description)
 	{
diff --git a/src/Ast/PhpDoc/PureUnlessCallableIsImpureTagValueNode.php b/src/Ast/PhpDoc/PureUnlessCallableIsImpureTagValueNode.php
index 4cf0937d..1a0cff89 100644
--- a/src/Ast/PhpDoc/PureUnlessCallableIsImpureTagValueNode.php
+++ b/src/Ast/PhpDoc/PureUnlessCallableIsImpureTagValueNode.php
@@ -10,11 +10,10 @@ class PureUnlessCallableIsImpureTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $parameterName;
+	public string $parameterName;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(string $parameterName, string $description)
 	{
diff --git a/src/Ast/PhpDoc/RequireExtendsTagValueNode.php b/src/Ast/PhpDoc/RequireExtendsTagValueNode.php
index 91c26892..5d8e1d22 100644
--- a/src/Ast/PhpDoc/RequireExtendsTagValueNode.php
+++ b/src/Ast/PhpDoc/RequireExtendsTagValueNode.php
@@ -11,11 +11,10 @@ class RequireExtendsTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(TypeNode $type, string $description)
 	{
diff --git a/src/Ast/PhpDoc/RequireImplementsTagValueNode.php b/src/Ast/PhpDoc/RequireImplementsTagValueNode.php
index 65c9213f..6b7f7bae 100644
--- a/src/Ast/PhpDoc/RequireImplementsTagValueNode.php
+++ b/src/Ast/PhpDoc/RequireImplementsTagValueNode.php
@@ -11,11 +11,10 @@ class RequireImplementsTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(TypeNode $type, string $description)
 	{
diff --git a/src/Ast/PhpDoc/ReturnTagValueNode.php b/src/Ast/PhpDoc/ReturnTagValueNode.php
index d53c8c75..c063bf48 100644
--- a/src/Ast/PhpDoc/ReturnTagValueNode.php
+++ b/src/Ast/PhpDoc/ReturnTagValueNode.php
@@ -11,11 +11,10 @@ class ReturnTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(TypeNode $type, string $description)
 	{
diff --git a/src/Ast/PhpDoc/SealedTagValueNode.php b/src/Ast/PhpDoc/SealedTagValueNode.php
new file mode 100644
index 00000000..230cf3e8
--- /dev/null
+++ b/src/Ast/PhpDoc/SealedTagValueNode.php
@@ -0,0 +1,31 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\PhpDocParser\Ast\PhpDoc;
+
+use PHPStan\PhpDocParser\Ast\NodeAttributes;
+use PHPStan\PhpDocParser\Ast\Type\TypeNode;
+use function trim;
+
+class SealedTagValueNode implements PhpDocTagValueNode
+{
+
+	use NodeAttributes;
+
+	public TypeNode $type;
+
+	/** @var string (may be empty) */
+	public string $description;
+
+	public function __construct(TypeNode $type, string $description)
+	{
+		$this->type = $type;
+		$this->description = $description;
+	}
+
+
+	public function __toString(): string
+	{
+		return trim("{$this->type} {$this->description}");
+	}
+
+}
diff --git a/src/Ast/PhpDoc/SelfOutTagValueNode.php b/src/Ast/PhpDoc/SelfOutTagValueNode.php
index 83169aff..d2377620 100644
--- a/src/Ast/PhpDoc/SelfOutTagValueNode.php
+++ b/src/Ast/PhpDoc/SelfOutTagValueNode.php
@@ -11,11 +11,10 @@ class SelfOutTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(TypeNode $type, string $description)
 	{
diff --git a/src/Ast/PhpDoc/TemplateTagValueNode.php b/src/Ast/PhpDoc/TemplateTagValueNode.php
index 8bc01f6e..ba106825 100644
--- a/src/Ast/PhpDoc/TemplateTagValueNode.php
+++ b/src/Ast/PhpDoc/TemplateTagValueNode.php
@@ -12,19 +12,16 @@ class TemplateTagValueNode implements PhpDocTagValueNode
 	use NodeAttributes;
 
 	/** @var non-empty-string */
-	public $name;
+	public string $name;
 
-	/** @var TypeNode|null */
-	public $bound;
+	public ?TypeNode $bound;
 
-	/** @var TypeNode|null */
-	public $lowerBound;
+	public ?TypeNode $default;
 
-	/** @var TypeNode|null */
-	public $default;
+	public ?TypeNode $lowerBound;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	/**
 	 * @param non-empty-string $name
diff --git a/src/Ast/PhpDoc/ThrowsTagValueNode.php b/src/Ast/PhpDoc/ThrowsTagValueNode.php
index 62d2aed3..dc5521b4 100644
--- a/src/Ast/PhpDoc/ThrowsTagValueNode.php
+++ b/src/Ast/PhpDoc/ThrowsTagValueNode.php
@@ -11,11 +11,10 @@ class ThrowsTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(TypeNode $type, string $description)
 	{
diff --git a/src/Ast/PhpDoc/TypeAliasImportTagValueNode.php b/src/Ast/PhpDoc/TypeAliasImportTagValueNode.php
index ad6b85a5..d0f945d2 100644
--- a/src/Ast/PhpDoc/TypeAliasImportTagValueNode.php
+++ b/src/Ast/PhpDoc/TypeAliasImportTagValueNode.php
@@ -11,14 +11,11 @@ class TypeAliasImportTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $importedAlias;
+	public string $importedAlias;
 
-	/** @var IdentifierTypeNode */
-	public $importedFrom;
+	public IdentifierTypeNode $importedFrom;
 
-	/** @var string|null */
-	public $importedAs;
+	public ?string $importedAs = null;
 
 	public function __construct(string $importedAlias, IdentifierTypeNode $importedFrom, ?string $importedAs)
 	{
@@ -31,7 +28,7 @@ public function __toString(): string
 	{
 		return trim(
 			"{$this->importedAlias} from {$this->importedFrom}"
-			. ($this->importedAs !== null ? " as {$this->importedAs}" : '')
+			. ($this->importedAs !== null ? " as {$this->importedAs}" : ''),
 		);
 	}
 
diff --git a/src/Ast/PhpDoc/TypeAliasTagValueNode.php b/src/Ast/PhpDoc/TypeAliasTagValueNode.php
index 4ccaaac4..ae366b50 100644
--- a/src/Ast/PhpDoc/TypeAliasTagValueNode.php
+++ b/src/Ast/PhpDoc/TypeAliasTagValueNode.php
@@ -11,11 +11,9 @@ class TypeAliasTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $alias;
+	public string $alias;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
 	public function __construct(string $alias, TypeNode $type)
 	{
diff --git a/src/Ast/PhpDoc/TypelessParamTagValueNode.php b/src/Ast/PhpDoc/TypelessParamTagValueNode.php
index 8b982954..bb99e781 100644
--- a/src/Ast/PhpDoc/TypelessParamTagValueNode.php
+++ b/src/Ast/PhpDoc/TypelessParamTagValueNode.php
@@ -10,19 +10,16 @@ class TypelessParamTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var bool */
-	public $isReference;
+	public bool $isReference;
 
-	/** @var bool */
-	public $isVariadic;
+	public bool $isVariadic;
 
-	/** @var string */
-	public $parameterName;
+	public string $parameterName;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
-	public function __construct(bool $isVariadic, string $parameterName, string $description, bool $isReference = false)
+	public function __construct(bool $isVariadic, string $parameterName, string $description, bool $isReference)
 	{
 		$this->isReference = $isReference;
 		$this->isVariadic = $isVariadic;
diff --git a/src/Ast/PhpDoc/UsesTagValueNode.php b/src/Ast/PhpDoc/UsesTagValueNode.php
index cd573d97..b33fff60 100644
--- a/src/Ast/PhpDoc/UsesTagValueNode.php
+++ b/src/Ast/PhpDoc/UsesTagValueNode.php
@@ -11,11 +11,10 @@ class UsesTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var GenericTypeNode */
-	public $type;
+	public GenericTypeNode $type;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(GenericTypeNode $type, string $description)
 	{
diff --git a/src/Ast/PhpDoc/VarTagValueNode.php b/src/Ast/PhpDoc/VarTagValueNode.php
index afb941a8..5b0538c8 100644
--- a/src/Ast/PhpDoc/VarTagValueNode.php
+++ b/src/Ast/PhpDoc/VarTagValueNode.php
@@ -11,14 +11,13 @@ class VarTagValueNode implements PhpDocTagValueNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
 	/** @var string (may be empty) */
-	public $variableName;
+	public string $variableName;
 
 	/** @var string (may be empty) */
-	public $description;
+	public string $description;
 
 	public function __construct(TypeNode $type, string $variableName, string $description)
 	{
diff --git a/src/Ast/Type/ArrayShapeItemNode.php b/src/Ast/Type/ArrayShapeItemNode.php
index 660c6c9d..bed62381 100644
--- a/src/Ast/Type/ArrayShapeItemNode.php
+++ b/src/Ast/Type/ArrayShapeItemNode.php
@@ -4,10 +4,11 @@
 
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
+use PHPStan\PhpDocParser\Ast\Node;
 use PHPStan\PhpDocParser\Ast\NodeAttributes;
 use function sprintf;
 
-class ArrayShapeItemNode implements TypeNode
+class ArrayShapeItemNode implements Node
 {
 
 	use NodeAttributes;
@@ -15,11 +16,9 @@ class ArrayShapeItemNode implements TypeNode
 	/** @var ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|null */
 	public $keyName;
 
-	/** @var bool */
-	public $optional;
+	public bool $optional;
 
-	/** @var TypeNode */
-	public $valueType;
+	public TypeNode $valueType;
 
 	/**
 	 * @param ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|null $keyName
@@ -39,7 +38,7 @@ public function __toString(): string
 				'%s%s: %s',
 				(string) $this->keyName,
 				$this->optional ? '?' : '',
-				(string) $this->valueType
+				(string) $this->valueType,
 			);
 		}
 
diff --git a/src/Ast/Type/ArrayShapeNode.php b/src/Ast/Type/ArrayShapeNode.php
index 73d162de..1d9cf850 100644
--- a/src/Ast/Type/ArrayShapeNode.php
+++ b/src/Ast/Type/ArrayShapeNode.php
@@ -16,32 +16,49 @@ class ArrayShapeNode implements TypeNode
 	use NodeAttributes;
 
 	/** @var ArrayShapeItemNode[] */
-	public $items;
+	public array $items;
 
-	/** @var bool */
-	public $sealed;
+	public bool $sealed;
 
 	/** @var self::KIND_* */
 	public $kind;
 
-	/** @var ArrayShapeUnsealedTypeNode|null */
-	public $unsealedType;
+	public ?ArrayShapeUnsealedTypeNode $unsealedType = null;
 
 	/**
 	 * @param ArrayShapeItemNode[] $items
 	 * @param self::KIND_* $kind
 	 */
-	public function __construct(
+	private function __construct(
 		array $items,
 		bool $sealed = true,
-		string $kind = self::KIND_ARRAY,
-		?ArrayShapeUnsealedTypeNode $unsealedType = null
+		?ArrayShapeUnsealedTypeNode $unsealedType = null,
+		string $kind = self::KIND_ARRAY
 	)
 	{
 		$this->items = $items;
 		$this->sealed = $sealed;
-		$this->kind = $kind;
 		$this->unsealedType = $unsealedType;
+		$this->kind = $kind;
+	}
+
+
+	/**
+	 * @param ArrayShapeItemNode[] $items
+	 * @param self::KIND_* $kind
+	 */
+	public static function createSealed(array $items, string $kind = self::KIND_ARRAY): self
+	{
+		return new self($items, true, null, $kind);
+	}
+
+	/**
+	 * @param ArrayShapeItemNode[] $items
+	 * @param self::KIND_* $kind
+	 */
+	public static function createUnsealed(array $items, ?ArrayShapeUnsealedTypeNode $unsealedType, string $kind = self::KIND_ARRAY): self
+	{
+		return new self($items, false, $unsealedType, $kind);
 	}
 
 
diff --git a/src/Ast/Type/ArrayShapeUnsealedTypeNode.php b/src/Ast/Type/ArrayShapeUnsealedTypeNode.php
index 7ffdf1d2..68d6b36f 100644
--- a/src/Ast/Type/ArrayShapeUnsealedTypeNode.php
+++ b/src/Ast/Type/ArrayShapeUnsealedTypeNode.php
@@ -11,11 +11,9 @@ class ArrayShapeUnsealedTypeNode implements Node
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $valueType;
+	public TypeNode $valueType;
 
-	/** @var TypeNode|null */
-	public $keyType;
+	public ?TypeNode $keyType = null;
 
 	public function __construct(TypeNode $valueType, ?TypeNode $keyType)
 	{
diff --git a/src/Ast/Type/ArrayTypeNode.php b/src/Ast/Type/ArrayTypeNode.php
index d2031032..95c020d8 100644
--- a/src/Ast/Type/ArrayTypeNode.php
+++ b/src/Ast/Type/ArrayTypeNode.php
@@ -9,8 +9,7 @@ class ArrayTypeNode implements TypeNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
 	public function __construct(TypeNode $type)
 	{
diff --git a/src/Ast/Type/CallableTypeNode.php b/src/Ast/Type/CallableTypeNode.php
index 4c913198..0a9e3442 100644
--- a/src/Ast/Type/CallableTypeNode.php
+++ b/src/Ast/Type/CallableTypeNode.php
@@ -11,23 +11,21 @@ class CallableTypeNode implements TypeNode
 
 	use NodeAttributes;
 
-	/** @var IdentifierTypeNode */
-	public $identifier;
+	public IdentifierTypeNode $identifier;
 
 	/** @var TemplateTagValueNode[] */
-	public $templateTypes;
+	public array $templateTypes;
 
 	/** @var CallableTypeParameterNode[] */
-	public $parameters;
+	public array $parameters;
 
-	/** @var TypeNode */
-	public $returnType;
+	public TypeNode $returnType;
 
 	/**
 	 * @param CallableTypeParameterNode[] $parameters
 	 * @param TemplateTagValueNode[]  $templateTypes
 	 */
-	public function __construct(IdentifierTypeNode $identifier, array $parameters, TypeNode $returnType, array $templateTypes = [])
+	public function __construct(IdentifierTypeNode $identifier, array $parameters, TypeNode $returnType, array $templateTypes)
 	{
 		$this->identifier = $identifier;
 		$this->parameters = $parameters;
diff --git a/src/Ast/Type/CallableTypeParameterNode.php b/src/Ast/Type/CallableTypeParameterNode.php
index c78d4c7b..08119670 100644
--- a/src/Ast/Type/CallableTypeParameterNode.php
+++ b/src/Ast/Type/CallableTypeParameterNode.php
@@ -11,20 +11,16 @@ class CallableTypeParameterNode implements Node
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
-	/** @var bool */
-	public $isReference;
+	public bool $isReference;
 
-	/** @var bool */
-	public $isVariadic;
+	public bool $isVariadic;
 
 	/** @var string (may be empty) */
-	public $parameterName;
+	public string $parameterName;
 
-	/** @var bool */
-	public $isOptional;
+	public bool $isOptional;
 
 	public function __construct(TypeNode $type, bool $isReference, bool $isVariadic, string $parameterName, bool $isOptional)
 	{
diff --git a/src/Ast/Type/ConditionalTypeForParameterNode.php b/src/Ast/Type/ConditionalTypeForParameterNode.php
index fbfcae95..4c120d22 100644
--- a/src/Ast/Type/ConditionalTypeForParameterNode.php
+++ b/src/Ast/Type/ConditionalTypeForParameterNode.php
@@ -10,20 +10,15 @@ class ConditionalTypeForParameterNode implements TypeNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $parameterName;
+	public string $parameterName;
 
-	/** @var TypeNode */
-	public $targetType;
+	public TypeNode $targetType;
 
-	/** @var TypeNode */
-	public $if;
+	public TypeNode $if;
 
-	/** @var TypeNode */
-	public $else;
+	public TypeNode $else;
 
-	/** @var bool */
-	public $negated;
+	public bool $negated;
 
 	public function __construct(string $parameterName, TypeNode $targetType, TypeNode $if, TypeNode $else, bool $negated)
 	{
@@ -42,7 +37,7 @@ public function __toString(): string
 			$this->negated ? 'is not' : 'is',
 			$this->targetType,
 			$this->if,
-			$this->else
+			$this->else,
 		);
 	}
 
diff --git a/src/Ast/Type/ConditionalTypeNode.php b/src/Ast/Type/ConditionalTypeNode.php
index bfdb0db1..89c1c63f 100644
--- a/src/Ast/Type/ConditionalTypeNode.php
+++ b/src/Ast/Type/ConditionalTypeNode.php
@@ -10,20 +10,15 @@ class ConditionalTypeNode implements TypeNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $subjectType;
+	public TypeNode $subjectType;
 
-	/** @var TypeNode */
-	public $targetType;
+	public TypeNode $targetType;
 
-	/** @var TypeNode */
-	public $if;
+	public TypeNode $if;
 
-	/** @var TypeNode */
-	public $else;
+	public TypeNode $else;
 
-	/** @var bool */
-	public $negated;
+	public bool $negated;
 
 	public function __construct(TypeNode $subjectType, TypeNode $targetType, TypeNode $if, TypeNode $else, bool $negated)
 	{
@@ -42,7 +37,7 @@ public function __toString(): string
 			$this->negated ? 'is not' : 'is',
 			$this->targetType,
 			$this->if,
-			$this->else
+			$this->else,
 		);
 	}
 
diff --git a/src/Ast/Type/ConstTypeNode.php b/src/Ast/Type/ConstTypeNode.php
index 0096055b..22823e5b 100644
--- a/src/Ast/Type/ConstTypeNode.php
+++ b/src/Ast/Type/ConstTypeNode.php
@@ -10,8 +10,7 @@ class ConstTypeNode implements TypeNode
 
 	use NodeAttributes;
 
-	/** @var ConstExprNode */
-	public $constExpr;
+	public ConstExprNode $constExpr;
 
 	public function __construct(ConstExprNode $constExpr)
 	{
diff --git a/src/Ast/Type/GenericTypeNode.php b/src/Ast/Type/GenericTypeNode.php
index 44e1d16d..4e52c00a 100644
--- a/src/Ast/Type/GenericTypeNode.php
+++ b/src/Ast/Type/GenericTypeNode.php
@@ -16,14 +16,13 @@ class GenericTypeNode implements TypeNode
 
 	use NodeAttributes;
 
-	/** @var IdentifierTypeNode */
-	public $type;
+	public IdentifierTypeNode $type;
 
 	/** @var TypeNode[] */
-	public $genericTypes;
+	public array $genericTypes;
 
 	/** @var (self::VARIANCE_*)[] */
-	public $variances;
+	public array $variances;
 
 	/**
 	 * @param TypeNode[] $genericTypes
diff --git a/src/Ast/Type/IdentifierTypeNode.php b/src/Ast/Type/IdentifierTypeNode.php
index 29bac308..df93fa86 100644
--- a/src/Ast/Type/IdentifierTypeNode.php
+++ b/src/Ast/Type/IdentifierTypeNode.php
@@ -9,8 +9,7 @@ class IdentifierTypeNode implements TypeNode
 
 	use NodeAttributes;
 
-	/** @var string */
-	public $name;
+	public string $name;
 
 	public function __construct(string $name)
 	{
diff --git a/src/Ast/Type/IntersectionTypeNode.php b/src/Ast/Type/IntersectionTypeNode.php
index fd761cf7..b3059cf5 100644
--- a/src/Ast/Type/IntersectionTypeNode.php
+++ b/src/Ast/Type/IntersectionTypeNode.php
@@ -12,7 +12,7 @@ class IntersectionTypeNode implements TypeNode
 	use NodeAttributes;
 
 	/** @var TypeNode[] */
-	public $types;
+	public array $types;
 
 	/**
 	 * @param TypeNode[] $types
diff --git a/src/Ast/Type/InvalidTypeNode.php b/src/Ast/Type/InvalidTypeNode.php
index 1ec47cf6..318176e7 100644
--- a/src/Ast/Type/InvalidTypeNode.php
+++ b/src/Ast/Type/InvalidTypeNode.php
@@ -11,7 +11,7 @@ class InvalidTypeNode implements TypeNode
 	use NodeAttributes;
 
 	/** @var mixed[] */
-	private $exceptionArgs;
+	private array $exceptionArgs;
 
 	public function __construct(ParserException $exception)
 	{
diff --git a/src/Ast/Type/NullableTypeNode.php b/src/Ast/Type/NullableTypeNode.php
index 73f438cd..080a13f2 100644
--- a/src/Ast/Type/NullableTypeNode.php
+++ b/src/Ast/Type/NullableTypeNode.php
@@ -9,8 +9,7 @@ class NullableTypeNode implements TypeNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
 	public function __construct(TypeNode $type)
 	{
diff --git a/src/Ast/Type/ObjectShapeItemNode.php b/src/Ast/Type/ObjectShapeItemNode.php
index 2f012406..f7aa9efb 100644
--- a/src/Ast/Type/ObjectShapeItemNode.php
+++ b/src/Ast/Type/ObjectShapeItemNode.php
@@ -3,10 +3,11 @@
 namespace PHPStan\PhpDocParser\Ast\Type;
 
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
+use PHPStan\PhpDocParser\Ast\Node;
 use PHPStan\PhpDocParser\Ast\NodeAttributes;
 use function sprintf;
 
-class ObjectShapeItemNode implements TypeNode
+class ObjectShapeItemNode implements Node
 {
 
 	use NodeAttributes;
@@ -14,11 +15,9 @@ class ObjectShapeItemNode implements TypeNode
 	/** @var ConstExprStringNode|IdentifierTypeNode */
 	public $keyName;
 
-	/** @var bool */
-	public $optional;
+	public bool $optional;
 
-	/** @var TypeNode */
-	public $valueType;
+	public TypeNode $valueType;
 
 	/**
 	 * @param ConstExprStringNode|IdentifierTypeNode $keyName
@@ -38,7 +37,7 @@ public function __toString(): string
 				'%s%s: %s',
 				(string) $this->keyName,
 				$this->optional ? '?' : '',
-				(string) $this->valueType
+				(string) $this->valueType,
 			);
 		}
 
diff --git a/src/Ast/Type/ObjectShapeNode.php b/src/Ast/Type/ObjectShapeNode.php
index f418bc30..41dc68c3 100644
--- a/src/Ast/Type/ObjectShapeNode.php
+++ b/src/Ast/Type/ObjectShapeNode.php
@@ -11,7 +11,7 @@ class ObjectShapeNode implements TypeNode
 	use NodeAttributes;
 
 	/** @var ObjectShapeItemNode[] */
-	public $items;
+	public array $items;
 
 	/**
 	 * @param ObjectShapeItemNode[] $items
diff --git a/src/Ast/Type/OffsetAccessTypeNode.php b/src/Ast/Type/OffsetAccessTypeNode.php
index c27ec0a3..4bd67d8d 100644
--- a/src/Ast/Type/OffsetAccessTypeNode.php
+++ b/src/Ast/Type/OffsetAccessTypeNode.php
@@ -9,11 +9,9 @@ class OffsetAccessTypeNode implements TypeNode
 
 	use NodeAttributes;
 
-	/** @var TypeNode */
-	public $type;
+	public TypeNode $type;
 
-	/** @var TypeNode */
-	public $offset;
+	public TypeNode $offset;
 
 	public function __construct(TypeNode $type, TypeNode $offset)
 	{
diff --git a/src/Ast/Type/UnionTypeNode.php b/src/Ast/Type/UnionTypeNode.php
index c552dab5..602cb3dd 100644
--- a/src/Ast/Type/UnionTypeNode.php
+++ b/src/Ast/Type/UnionTypeNode.php
@@ -12,7 +12,7 @@ class UnionTypeNode implements TypeNode
 	use NodeAttributes;
 
 	/** @var TypeNode[] */
-	public $types;
+	public array $types;
 
 	/**
 	 * @param TypeNode[] $types
diff --git a/src/Lexer/Lexer.php b/src/Lexer/Lexer.php
index 32539faf..b2669131 100644
--- a/src/Lexer/Lexer.php
+++ b/src/Lexer/Lexer.php
@@ -2,6 +2,7 @@
 
 namespace PHPStan\PhpDocParser\Lexer;
 
+use PHPStan\PhpDocParser\ParserConfig;
 use function implode;
 use function preg_match_all;
 use const PREG_SET_ORDER;
@@ -50,6 +51,8 @@ class Lexer
 	public const TOKEN_NEGATED = 35;
 	public const TOKEN_ARROW = 36;
 
+	public const TOKEN_COMMENT = 37;
+
 	public const TOKEN_LABELS = [
 		self::TOKEN_REFERENCE => '\'&\'',
 		self::TOKEN_UNION => '\'|\'',
@@ -65,6 +68,7 @@ class Lexer
 		self::TOKEN_OPEN_CURLY_BRACKET => '\'{\'',
 		self::TOKEN_CLOSE_CURLY_BRACKET => '\'}\'',
 		self::TOKEN_COMMA => '\',\'',
+		self::TOKEN_COMMENT => '\'//\'',
 		self::TOKEN_COLON => '\':\'',
 		self::TOKEN_VARIADIC => '\'...\'',
 		self::TOKEN_DOUBLE_COLON => '\'::\'',
@@ -94,17 +98,16 @@ class Lexer
 	public const TYPE_OFFSET = 1;
 	public const LINE_OFFSET = 2;
 
-	/** @var bool */
-	private $parseDoctrineAnnotations;
+	private ParserConfig $config; // @phpstan-ignore property.onlyWritten
 
-	/** @var string|null */
-	private $regexp;
+	private ?string $regexp = null;
 
-	public function __construct(bool $parseDoctrineAnnotations = false)
+	public function __construct(ParserConfig $config)
 	{
-		$this->parseDoctrineAnnotations = $parseDoctrineAnnotations;
+		$this->config = $config;
 	}
 
+
 	/**
 	 * @return list<array{string, int, int}>
 	 */
@@ -160,6 +163,7 @@ private function generateRegexp(): string
 			self::TOKEN_CLOSE_CURLY_BRACKET => '\\}',
 
 			self::TOKEN_COMMA => ',',
+			self::TOKEN_COMMENT => '\/\/[^\\r\\n]*(?=\n|\r|\*/)',
 			self::TOKEN_VARIADIC => '\\.\\.\\.',
 			self::TOKEN_DOUBLE_COLON => '::',
 			self::TOKEN_DOUBLE_ARROW => '=>',
@@ -170,23 +174,20 @@ private function generateRegexp(): string
 			self::TOKEN_OPEN_PHPDOC => '/\\*\\*(?=\\s)\\x20?+',
 			self::TOKEN_CLOSE_PHPDOC => '\\*/',
 			self::TOKEN_PHPDOC_TAG => '@(?:[a-z][a-z0-9-\\\\]+:)?[a-z][a-z0-9-\\\\]*+',
+			self::TOKEN_DOCTRINE_TAG => '@[a-z_\\\\][a-z0-9_\:\\\\]*[a-z_][a-z0-9_]*',
 			self::TOKEN_PHPDOC_EOL => '\\r?+\\n[\\x09\\x20]*+(?:\\*(?!/)\\x20?+)?',
 
 			self::TOKEN_FLOAT => '[+\-]?(?:(?:[0-9]++(_[0-9]++)*\\.[0-9]*+(_[0-9]++)*(?:e[+\-]?[0-9]++(_[0-9]++)*)?)|(?:[0-9]*+(_[0-9]++)*\\.[0-9]++(_[0-9]++)*(?:e[+\-]?[0-9]++(_[0-9]++)*)?)|(?:[0-9]++(_[0-9]++)*e[+\-]?[0-9]++(_[0-9]++)*))',
 			self::TOKEN_INTEGER => '[+\-]?(?:(?:0b[0-1]++(_[0-1]++)*)|(?:0o[0-7]++(_[0-7]++)*)|(?:0x[0-9a-f]++(_[0-9a-f]++)*)|(?:[0-9]++(_[0-9]++)*))',
 			self::TOKEN_SINGLE_QUOTED_STRING => '\'(?:\\\\[^\\r\\n]|[^\'\\r\\n\\\\])*+\'',
 			self::TOKEN_DOUBLE_QUOTED_STRING => '"(?:\\\\[^\\r\\n]|[^"\\r\\n\\\\])*+"',
+			self::TOKEN_DOCTRINE_ANNOTATION_STRING => '"(?:""|[^"])*+"',
 
 			self::TOKEN_WILDCARD => '\\*',
-		];
 
-		if ($this->parseDoctrineAnnotations) {
-			$patterns[self::TOKEN_DOCTRINE_TAG] = '@[a-z_\\\\][a-z0-9_\:\\\\]*[a-z_][a-z0-9_]*';
-			$patterns[self::TOKEN_DOCTRINE_ANNOTATION_STRING] = '"(?:""|[^"])*+"';
-		}
-
-		// anything but TOKEN_CLOSE_PHPDOC or TOKEN_HORIZONTAL_WS or TOKEN_EOL
-		$patterns[self::TOKEN_OTHER] = '(?:(?!\\*/)[^\\s])++';
+			// anything but TOKEN_CLOSE_PHPDOC or TOKEN_HORIZONTAL_WS or TOKEN_EOL
+			self::TOKEN_OTHER => '(?:(?!\\*/)[^\\s])++',
+		];
 
 		foreach ($patterns as $type => &$pattern) {
 			$pattern = '(?:' . $pattern . ')(*MARK:' . $type . ')';
diff --git a/src/Parser/ConstExprParser.php b/src/Parser/ConstExprParser.php
index f6a7306e..396b8d7c 100644
--- a/src/Parser/ConstExprParser.php
+++ b/src/Parser/ConstExprParser.php
@@ -4,41 +4,22 @@
 
 use PHPStan\PhpDocParser\Ast;
 use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\ParserConfig;
 use function str_replace;
 use function strtolower;
-use function substr;
 
 class ConstExprParser
 {
 
-	/** @var bool */
-	private $unescapeStrings;
+	private ParserConfig $config;
 
-	/** @var bool */
-	private $quoteAwareConstExprString;
+	private bool $parseDoctrineStrings;
 
-	/** @var bool */
-	private $useLinesAttributes;
-
-	/** @var bool */
-	private $useIndexAttributes;
-
-	/** @var bool */
-	private $parseDoctrineStrings;
-
-	/**
-	 * @param array{lines?: bool, indexes?: bool} $usedAttributes
-	 */
 	public function __construct(
-		bool $unescapeStrings = false,
-		bool $quoteAwareConstExprString = false,
-		array $usedAttributes = []
+		ParserConfig $config
 	)
 	{
-		$this->unescapeStrings = $unescapeStrings;
-		$this->quoteAwareConstExprString = $quoteAwareConstExprString;
-		$this->useLinesAttributes = $usedAttributes['lines'] ?? false;
-		$this->useIndexAttributes = $usedAttributes['indexes'] ?? false;
+		$this->config = $config;
 		$this->parseDoctrineStrings = false;
 	}
 
@@ -47,19 +28,12 @@ public function __construct(
 	 */
 	public function toDoctrine(): self
 	{
-		$self = new self(
-			$this->unescapeStrings,
-			$this->quoteAwareConstExprString,
-			[
-				'lines' => $this->useLinesAttributes,
-				'indexes' => $this->useIndexAttributes,
-			]
-		);
+		$self = new self($this->config);
 		$self->parseDoctrineStrings = true;
 		return $self;
 	}
 
-	public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\ConstExpr\ConstExprNode
+	public function parse(TokenIterator $tokens): Ast\ConstExpr\ConstExprNode
 	{
 		$startLine = $tokens->currentTokenLine();
 		$startIndex = $tokens->currentTokenIndex();
@@ -71,7 +45,7 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
 				$tokens,
 				new Ast\ConstExpr\ConstExprFloatNode(str_replace('_', '', $value)),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		}
 
@@ -83,7 +57,7 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
 				$tokens,
 				new Ast\ConstExpr\ConstExprIntegerNode(str_replace('_', '', $value)),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		}
 
@@ -95,7 +69,7 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
 				$tokens,
 				new Ast\ConstExpr\DoctrineConstExprStringNode(Ast\ConstExpr\DoctrineConstExprStringNode::unescape($value)),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		}
 
@@ -108,7 +82,7 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
 						$tokens->currentTokenOffset(),
 						Lexer::TOKEN_DOUBLE_QUOTED_STRING,
 						null,
-						$tokens->currentTokenLine()
+						$tokens->currentTokenLine(),
 					);
 				}
 
@@ -119,39 +93,24 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
 					$tokens,
 					$this->parseDoctrineString($value, $tokens),
 					$startLine,
-					$startIndex
+					$startIndex,
 				);
 			}
-			$value = $tokens->currentTokenValue();
+
+			$value = StringUnescaper::unescapeString($tokens->currentTokenValue());
 			$type = $tokens->currentTokenType();
-			if ($trimStrings) {
-				if ($this->unescapeStrings) {
-					$value = StringUnescaper::unescapeString($value);
-				} else {
-					$value = substr($value, 1, -1);
-				}
-			}
 			$tokens->next();
 
-			if ($this->quoteAwareConstExprString) {
-				return $this->enrichWithAttributes(
-					$tokens,
-					new Ast\ConstExpr\QuoteAwareConstExprStringNode(
-						$value,
-						$type === Lexer::TOKEN_SINGLE_QUOTED_STRING
-							? Ast\ConstExpr\QuoteAwareConstExprStringNode::SINGLE_QUOTED
-							: Ast\ConstExpr\QuoteAwareConstExprStringNode::DOUBLE_QUOTED
-					),
-					$startLine,
-					$startIndex
-				);
-			}
-
 			return $this->enrichWithAttributes(
 				$tokens,
-				new Ast\ConstExpr\ConstExprStringNode($value),
+				new Ast\ConstExpr\ConstExprStringNode(
+					$value,
+					$type === Lexer::TOKEN_SINGLE_QUOTED_STRING
+						? Ast\ConstExpr\ConstExprStringNode::SINGLE_QUOTED
+						: Ast\ConstExpr\ConstExprStringNode::DOUBLE_QUOTED,
+				),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 
 		} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
@@ -164,21 +123,21 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
 						$tokens,
 						new Ast\ConstExpr\ConstExprTrueNode(),
 						$startLine,
-						$startIndex
+						$startIndex,
 					);
 				case 'false':
 					return $this->enrichWithAttributes(
 						$tokens,
 						new Ast\ConstExpr\ConstExprFalseNode(),
 						$startLine,
-						$startIndex
+						$startIndex,
 					);
 				case 'null':
 					return $this->enrichWithAttributes(
 						$tokens,
 						new Ast\ConstExpr\ConstExprNullNode(),
 						$startLine,
-						$startIndex
+						$startIndex,
 					);
 				case 'array':
 					$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
@@ -220,7 +179,7 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
 					$tokens,
 					new Ast\ConstExpr\ConstFetchNode($identifier, $classConstantName),
 					$startLine,
-					$startIndex
+					$startIndex,
 				);
 
 			}
@@ -229,7 +188,7 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
 				$tokens,
 				new Ast\ConstExpr\ConstFetchNode('', $identifier),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 
 		} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
@@ -242,7 +201,7 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con
 			$tokens->currentTokenOffset(),
 			Lexer::TOKEN_IDENTIFIER,
 			null,
-			$tokens->currentTokenLine()
+			$tokens->currentTokenLine(),
 		);
 	}
 
@@ -264,7 +223,7 @@ private function parseArray(TokenIterator $tokens, int $endToken, int $startInde
 			$tokens,
 			new Ast\ConstExpr\ConstExprArrayNode($items),
 			$startLine,
-			$startIndex
+			$startIndex,
 		);
 	}
 
@@ -306,7 +265,7 @@ private function parseArrayItem(TokenIterator $tokens): Ast\ConstExpr\ConstExprA
 			$tokens,
 			new Ast\ConstExpr\ConstExprArrayItemNode($key, $value),
 			$startLine,
-			$startIndex
+			$startIndex,
 		);
 	}
 
@@ -317,12 +276,12 @@ private function parseArrayItem(TokenIterator $tokens): Ast\ConstExpr\ConstExprA
 	 */
 	private function enrichWithAttributes(TokenIterator $tokens, Ast\ConstExpr\ConstExprNode $node, int $startLine, int $startIndex): Ast\ConstExpr\ConstExprNode
 	{
-		if ($this->useLinesAttributes) {
+		if ($this->config->useLinesAttributes) {
 			$node->setAttribute(Ast\Attribute::START_LINE, $startLine);
 			$node->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
 		}
 
-		if ($this->useIndexAttributes) {
+		if ($this->config->useIndexAttributes) {
 			$node->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
 			$node->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
 		}
diff --git a/src/Parser/ParserException.php b/src/Parser/ParserException.php
index 6ab5cc07..ae72fd9c 100644
--- a/src/Parser/ParserException.php
+++ b/src/Parser/ParserException.php
@@ -14,31 +14,25 @@
 class ParserException extends Exception
 {
 
-	/** @var string */
-	private $currentTokenValue;
+	private string $currentTokenValue;
 
-	/** @var int */
-	private $currentTokenType;
+	private int $currentTokenType;
 
-	/** @var int */
-	private $currentOffset;
+	private int $currentOffset;
 
-	/** @var int */
-	private $expectedTokenType;
+	private int $expectedTokenType;
 
-	/** @var string|null */
-	private $expectedTokenValue;
+	private ?string $expectedTokenValue;
 
-	/** @var int|null */
-	private $currentTokenLine;
+	private ?int $currentTokenLine;
 
 	public function __construct(
 		string $currentTokenValue,
 		int $currentTokenType,
 		int $currentOffset,
 		int $expectedTokenType,
-		?string $expectedTokenValue = null,
-		?int $currentTokenLine = null
+		?string $expectedTokenValue,
+		?int $currentTokenLine
 	)
 	{
 		$this->currentTokenValue = $currentTokenValue;
@@ -54,7 +48,7 @@ public function __construct(
 			Lexer::TOKEN_LABELS[$expectedTokenType],
 			$expectedTokenValue !== null ? sprintf(' (%s)', $this->formatValue($expectedTokenValue)) : '',
 			$currentOffset,
-			$currentTokenLine === null ? '' : sprintf(' on line %d', $currentTokenLine)
+			$currentTokenLine === null ? '' : sprintf(' on line %d', $currentTokenLine),
 		));
 	}
 
diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php
index b6cd85ea..f36b9136 100644
--- a/src/Parser/PhpDocParser.php
+++ b/src/Parser/PhpDocParser.php
@@ -10,9 +10,9 @@
 use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
 use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
 use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\ParserConfig;
 use PHPStan\ShouldNotHappenException;
 use function array_key_exists;
-use function array_values;
 use function count;
 use function rtrim;
 use function str_replace;
@@ -29,55 +29,24 @@ class PhpDocParser
 		Lexer::TOKEN_INTERSECTION,
 	];
 
-	/** @var TypeParser */
-	private $typeParser;
+	private ParserConfig $config;
 
-	/** @var ConstExprParser */
-	private $constantExprParser;
+	private TypeParser $typeParser;
 
-	/** @var ConstExprParser */
-	private $doctrineConstantExprParser;
+	private ConstExprParser $constantExprParser;
 
-	/** @var bool */
-	private $requireWhitespaceBeforeDescription;
+	private ConstExprParser $doctrineConstantExprParser;
 
-	/** @var bool */
-	private $preserveTypeAliasesWithInvalidTypes;
-
-	/** @var bool */
-	private $parseDoctrineAnnotations;
-
-	/** @var bool */
-	private $useLinesAttributes;
-
-	/** @var bool */
-	private $useIndexAttributes;
-
-	/** @var bool */
-	private $textBetweenTagsBelongsToDescription;
-
-	/**
-	 * @param array{lines?: bool, indexes?: bool} $usedAttributes
-	 */
 	public function __construct(
+		ParserConfig $config,
 		TypeParser $typeParser,
-		ConstExprParser $constantExprParser,
-		bool $requireWhitespaceBeforeDescription = false,
-		bool $preserveTypeAliasesWithInvalidTypes = false,
-		array $usedAttributes = [],
-		bool $parseDoctrineAnnotations = false,
-		bool $textBetweenTagsBelongsToDescription = false
+		ConstExprParser $constantExprParser
 	)
 	{
+		$this->config = $config;
 		$this->typeParser = $typeParser;
 		$this->constantExprParser = $constantExprParser;
 		$this->doctrineConstantExprParser = $constantExprParser->toDoctrine();
-		$this->requireWhitespaceBeforeDescription = $requireWhitespaceBeforeDescription;
-		$this->preserveTypeAliasesWithInvalidTypes = $preserveTypeAliasesWithInvalidTypes;
-		$this->parseDoctrineAnnotations = $parseDoctrineAnnotations;
-		$this->useLinesAttributes = $usedAttributes['lines'] ?? false;
-		$this->useIndexAttributes = $usedAttributes['indexes'] ?? false;
-		$this->textBetweenTagsBelongsToDescription = $textBetweenTagsBelongsToDescription;
 	}
 
 
@@ -88,44 +57,35 @@ public function parse(TokenIterator $tokens): Ast\PhpDoc\PhpDocNode
 
 		$children = [];
 
-		if ($this->parseDoctrineAnnotations) {
-			if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
-				$lastChild = $this->parseChild($tokens);
-				$children[] = $lastChild;
-				while (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
-					if (
-						$lastChild instanceof Ast\PhpDoc\PhpDocTagNode
-						&& (
-							$lastChild->value instanceof Doctrine\DoctrineTagValueNode
-							|| $lastChild->value instanceof Ast\PhpDoc\GenericTagValueNode
-						)
-					) {
-						$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
-						if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
-							break;
-						}
-						$lastChild = $this->parseChild($tokens);
-						$children[] = $lastChild;
-						continue;
-					}
-
-					if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
-						break;
-					}
+		if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
+			$lastChild = $this->parseChild($tokens);
+			$children[] = $lastChild;
+			while (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
+				if (
+					$lastChild instanceof Ast\PhpDoc\PhpDocTagNode
+					&& (
+						$lastChild->value instanceof Doctrine\DoctrineTagValueNode
+						|| $lastChild->value instanceof Ast\PhpDoc\GenericTagValueNode
+					)
+				) {
+					$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
 					if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
 						break;
 					}
-
 					$lastChild = $this->parseChild($tokens);
 					$children[] = $lastChild;
+					continue;
 				}
-			}
-		} else {
-			if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
-				$children[] = $this->parseChild($tokens);
-				while ($tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL) && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
-					$children[] = $this->parseChild($tokens);
+
+				if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
+					break;
+				}
+				if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
+					break;
 				}
+
+				$lastChild = $this->parseChild($tokens);
+				$children[] = $lastChild;
 			}
 		}
 
@@ -150,16 +110,26 @@ public function parse(TokenIterator $tokens): Ast\PhpDoc\PhpDocNode
 					$tokens,
 					new Ast\PhpDoc\InvalidTagValueNode($e->getMessage(), $e),
 					$startLine,
-					$startIndex
-				)
+					$startIndex,
+				),
 			);
 
 			$tokens->forwardToTheEnd();
 
+			$comments = $tokens->flushComments();
+			if ($comments !== []) {
+				throw new LogicException('Comments should already be flushed');
+			}
+
 			return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocNode([$this->enrichWithAttributes($tokens, $tag, $startLine, $startIndex)]), 1, 0);
 		}
 
-		return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocNode(array_values($children)), 1, 0);
+		$comments = $tokens->flushComments();
+		if ($comments !== []) {
+			throw new LogicException('Comments should already be flushed');
+		}
+
+		return $this->enrichWithAttributes($tokens, new Ast\PhpDoc\PhpDocNode($children), 1, 0);
 	}
 
 
@@ -187,8 +157,8 @@ private function parseChild(TokenIterator $tokens): Ast\PhpDoc\PhpDocChildNode
 					$tokens,
 					$this->parseDoctrineTagValue($tokens, $tag),
 					$tagStartLine,
-					$tagStartIndex
-				)
+					$tagStartIndex,
+				),
 			), $startLine, $startIndex);
 		}
 
@@ -206,12 +176,12 @@ private function parseChild(TokenIterator $tokens): Ast\PhpDoc\PhpDocChildNode
 	 */
 	private function enrichWithAttributes(TokenIterator $tokens, Ast\Node $tag, int $startLine, int $startIndex): Ast\Node
 	{
-		if ($this->useLinesAttributes) {
+		if ($this->config->useLinesAttributes) {
 			$tag->setAttribute(Ast\Attribute::START_LINE, $startLine);
 			$tag->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
 		}
 
-		if ($this->useIndexAttributes) {
+		if ($this->config->useIndexAttributes) {
 			$tag->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
 			$tag->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
 		}
@@ -224,15 +194,12 @@ private function parseText(TokenIterator $tokens): Ast\PhpDoc\PhpDocTextNode
 	{
 		$text = '';
 
-		$endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
-		if ($this->textBetweenTagsBelongsToDescription) {
-			$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
-		}
+		$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
 
 		$savepoint = false;
 
 		// if the next token is EOL, everything below is skipped and empty string is returned
-		while ($this->textBetweenTagsBelongsToDescription || !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
+		while (true) {
 			$tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, ...$endTokens);
 			$text .= $tmpText;
 
@@ -241,14 +208,12 @@ private function parseText(TokenIterator $tokens): Ast\PhpDoc\PhpDocTextNode
 				break;
 			}
 
-			if ($this->textBetweenTagsBelongsToDescription) {
-				if (!$savepoint) {
-					$tokens->pushSavePoint();
-					$savepoint = true;
-				} elseif ($tmpText !== '') {
-					$tokens->dropSavePoint();
-					$tokens->pushSavePoint();
-				}
+			if (!$savepoint) {
+				$tokens->pushSavePoint();
+				$savepoint = true;
+			} elseif ($tmpText !== '') {
+				$tokens->dropSavePoint();
+				$tokens->pushSavePoint();
 			}
 
 			$tokens->pushSavePoint();
@@ -280,15 +245,12 @@ private function parseOptionalDescriptionAfterDoctrineTag(TokenIterator $tokens)
 	{
 		$text = '';
 
-		$endTokens = [Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
-		if ($this->textBetweenTagsBelongsToDescription) {
-			$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
-		}
+		$endTokens = [Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END];
 
 		$savepoint = false;
 
 		// if the next token is EOL, everything below is skipped and empty string is returned
-		while ($this->textBetweenTagsBelongsToDescription || !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
+		while (true) {
 			$tmpText = $tokens->getSkippedHorizontalWhiteSpaceIfAny() . $tokens->joinUntil(Lexer::TOKEN_PHPDOC_TAG, Lexer::TOKEN_DOCTRINE_TAG, Lexer::TOKEN_PHPDOC_EOL, ...$endTokens);
 			$text .= $tmpText;
 
@@ -327,14 +289,12 @@ private function parseOptionalDescriptionAfterDoctrineTag(TokenIterator $tokens)
 				break;
 			}
 
-			if ($this->textBetweenTagsBelongsToDescription) {
-				if (!$savepoint) {
-					$tokens->pushSavePoint();
-					$savepoint = true;
-				} elseif ($tmpText !== '') {
-					$tokens->dropSavePoint();
-					$tokens->pushSavePoint();
-				}
+			if (!$savepoint) {
+				$tokens->pushSavePoint();
+				$savepoint = true;
+			} elseif ($tmpText !== '') {
+				$tokens->dropSavePoint();
+				$tokens->pushSavePoint();
 			}
 
 			$tokens->pushSavePoint();
@@ -443,6 +403,11 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph
 					$tagValue = $this->parseRequireImplementsTagValue($tokens);
 					break;
 
+				case '@psalm-inheritors':
+				case '@phpstan-sealed':
+					$tagValue = $this->parseSealedTagValue($tokens);
+					break;
+
 				case '@deprecated':
 					$tagValue = $this->parseDeprecatedTagValue($tokens);
 					break;
@@ -481,9 +446,7 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph
 				case '@psalm-template-contravariant':
 					$tagValue = $this->typeParser->parseTemplateTagValue(
 						$tokens,
-						function ($tokens) {
-							return $this->parseOptionalDescription($tokens);
-						}
+						fn ($tokens) => $this->parseOptionalDescription($tokens, true),
 					);
 					break;
 
@@ -544,17 +507,11 @@ function ($tokens) {
 					break;
 
 				default:
-					if ($this->parseDoctrineAnnotations) {
-						if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
-							$tagValue = $this->parseDoctrineTagValue($tokens, $tag);
-						} else {
-							$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescriptionAfterDoctrineTag($tokens));
-						}
-						break;
+					if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
+						$tagValue = $this->parseDoctrineTagValue($tokens, $tag);
+					} else {
+						$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescriptionAfterDoctrineTag($tokens));
 					}
-
-					$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens));
-
 					break;
 			}
 
@@ -562,7 +519,7 @@ function ($tokens) {
 
 		} catch (ParserException $e) {
 			$tokens->rollback();
-			$tagValue = new Ast\PhpDoc\InvalidTagValueNode($this->parseOptionalDescription($tokens), $e);
+			$tagValue = new Ast\PhpDoc\InvalidTagValueNode($this->parseOptionalDescription($tokens, false), $e);
 		}
 
 		return $this->enrichWithAttributes($tokens, $tagValue, $startLine, $startIndex);
@@ -579,9 +536,9 @@ private function parseDoctrineTagValue(TokenIterator $tokens, string $tag): Ast\
 				$tokens,
 				new Doctrine\DoctrineAnnotation($tag, $this->parseDoctrineArguments($tokens, false)),
 				$startLine,
-				$startIndex
+				$startIndex,
 			),
-			$this->parseOptionalDescriptionAfterDoctrineTag($tokens)
+			$this->parseOptionalDescriptionAfterDoctrineTag($tokens),
 		);
 	}
 
@@ -632,7 +589,7 @@ private function parseDoctrineArgument(TokenIterator $tokens): Doctrine\Doctrine
 				$tokens,
 				new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		}
 
@@ -648,7 +605,7 @@ private function parseDoctrineArgument(TokenIterator $tokens): Doctrine\Doctrine
 				$tokens,
 				new IdentifierTypeNode($currentValue),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 			$tokens->consumeTokenType(Lexer::TOKEN_EQUAL);
 
@@ -660,7 +617,7 @@ private function parseDoctrineArgument(TokenIterator $tokens): Doctrine\Doctrine
 				$tokens,
 				new Doctrine\DoctrineArgument($key, $value),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		} catch (ParserException $e) {
 			$tokens->rollback();
@@ -669,7 +626,7 @@ private function parseDoctrineArgument(TokenIterator $tokens): Doctrine\Doctrine
 				$tokens,
 				new Doctrine\DoctrineArgument(null, $this->parseDoctrineArgumentValue($tokens)),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		}
 	}
@@ -691,7 +648,7 @@ private function parseDoctrineArgumentValue(TokenIterator $tokens)
 				$tokens,
 				new Doctrine\DoctrineAnnotation($name, $this->parseDoctrineArguments($tokens, true)),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		}
 
@@ -710,7 +667,7 @@ private function parseDoctrineArgumentValue(TokenIterator $tokens)
 				$tokens,
 				new Doctrine\DoctrineArray($items),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		}
 
@@ -721,7 +678,7 @@ private function parseDoctrineArgumentValue(TokenIterator $tokens)
 				$tokens,
 				new Ast\Type\IdentifierTypeNode($currentTokenValue),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 			if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
 				$tokens->dropSavePoint();
@@ -739,7 +696,7 @@ private function parseDoctrineArgumentValue(TokenIterator $tokens)
 		$currentTokenLine = $tokens->currentTokenLine();
 
 		try {
-			$constExpr = $this->doctrineConstantExprParser->parse($tokens, true);
+			$constExpr = $this->doctrineConstantExprParser->parse($tokens);
 			if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
 				throw new ParserException(
 					$currentTokenValue,
@@ -747,7 +704,7 @@ private function parseDoctrineArgumentValue(TokenIterator $tokens)
 					$currentTokenOffset,
 					Lexer::TOKEN_IDENTIFIER,
 					null,
-					$currentTokenLine
+					$currentTokenLine,
 				);
 			}
 
@@ -759,7 +716,7 @@ private function parseDoctrineArgumentValue(TokenIterator $tokens)
 				$currentTokenOffset,
 				Lexer::TOKEN_IDENTIFIER,
 				null,
-				$currentTokenLine
+				$currentTokenLine,
 			);
 		}
 	}
@@ -788,7 +745,7 @@ private function parseDoctrineArrayItem(TokenIterator $tokens): Doctrine\Doctrin
 				$tokens,
 				new Doctrine\DoctrineArrayItem($key, $value),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		} catch (ParserException $e) {
 			$tokens->rollback();
@@ -797,7 +754,7 @@ private function parseDoctrineArrayItem(TokenIterator $tokens): Doctrine\Doctrin
 				$tokens,
 				new Doctrine\DoctrineArrayItem(null, $this->parseDoctrineArgumentValue($tokens)),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		}
 	}
@@ -816,10 +773,14 @@ private function parseDoctrineArrayKey(TokenIterator $tokens)
 			$tokens->next();
 
 		} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOCTRINE_ANNOTATION_STRING)) {
-			$key = new Ast\ConstExpr\DoctrineConstExprStringNode(Ast\ConstExpr\DoctrineConstExprStringNode::unescape($tokens->currentTokenValue()));
+			$key = $this->doctrineConstantExprParser->parseDoctrineString($tokens->currentTokenValue(), $tokens);
 
 			$tokens->next();
 
+		} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
+			$key = new Ast\ConstExpr\ConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\ConstExprStringNode::SINGLE_QUOTED);
+			$tokens->next();
+
 		} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
 			$value = $tokens->currentTokenValue();
 			$tokens->next();
@@ -836,7 +797,7 @@ private function parseDoctrineArrayKey(TokenIterator $tokens)
 					$tokens->currentTokenOffset(),
 					Lexer::TOKEN_IDENTIFIER,
 					null,
-					$tokens->currentTokenLine()
+					$tokens->currentTokenLine(),
 				);
 			}
 
@@ -847,12 +808,12 @@ private function parseDoctrineArrayKey(TokenIterator $tokens)
 					$tokens,
 					new IdentifierTypeNode($currentTokenValue),
 					$startLine,
-					$startIndex
+					$startIndex,
 				);
 			}
 
 			$tokens->rollback();
-			$constExpr = $this->doctrineConstantExprParser->parse($tokens, true);
+			$constExpr = $this->doctrineConstantExprParser->parse($tokens);
 			if (!$constExpr instanceof Ast\ConstExpr\ConstFetchNode) {
 				throw new ParserException(
 					$tokens->currentTokenValue(),
@@ -860,7 +821,7 @@ private function parseDoctrineArrayKey(TokenIterator $tokens)
 					$tokens->currentTokenOffset(),
 					Lexer::TOKEN_IDENTIFIER,
 					null,
-					$tokens->currentTokenLine()
+					$tokens->currentTokenLine(),
 				);
 			}
 
@@ -887,7 +848,7 @@ private function parseParamTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTag
 		$isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE);
 		$isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC);
 		$parameterName = $this->parseRequiredVariableName($tokens);
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, false);
 
 		if ($type !== null) {
 			return new Ast\PhpDoc\ParamTagValueNode($type, $isVariadic, $parameterName, $description, $isReference);
@@ -900,7 +861,7 @@ private function parseParamTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTag
 	private function parseParamImmediatelyInvokedCallableTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode
 	{
 		$parameterName = $this->parseRequiredVariableName($tokens);
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, false);
 
 		return new Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode($parameterName, $description);
 	}
@@ -909,7 +870,7 @@ private function parseParamImmediatelyInvokedCallableTagValue(TokenIterator $tok
 	private function parseParamLaterInvokedCallableTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode
 	{
 		$parameterName = $this->parseRequiredVariableName($tokens);
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, false);
 
 		return new Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode($parameterName, $description);
 	}
@@ -919,7 +880,7 @@ private function parseParamClosureThisTagValue(TokenIterator $tokens): Ast\PhpDo
 	{
 		$type = $this->typeParser->parse($tokens);
 		$parameterName = $this->parseRequiredVariableName($tokens);
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, false);
 
 		return new Ast\PhpDoc\ParamClosureThisTagValueNode($type, $parameterName, $description);
 	}
@@ -927,7 +888,7 @@ private function parseParamClosureThisTagValue(TokenIterator $tokens): Ast\PhpDo
 	private function parsePureUnlessCallableIsImpureTagValue(TokenIterator $tokens): Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode
 	{
 		$parameterName = $this->parseRequiredVariableName($tokens);
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, false);
 
 		return new Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode($parameterName, $description);
 	}
@@ -977,9 +938,16 @@ private function parseRequireImplementsTagValue(TokenIterator $tokens): Ast\PhpD
 		return new Ast\PhpDoc\RequireImplementsTagValueNode($type, $description);
 	}
 
+	private function parseSealedTagValue(TokenIterator $tokens): Ast\PhpDoc\SealedTagValueNode
+	{
+		$type = $this->typeParser->parse($tokens);
+		$description = $this->parseOptionalDescription($tokens, true);
+		return new Ast\PhpDoc\SealedTagValueNode($type, $description);
+	}
+
 	private function parseDeprecatedTagValue(TokenIterator $tokens): Ast\PhpDoc\DeprecatedTagValueNode
 	{
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, false);
 		return new Ast\PhpDoc\DeprecatedTagValueNode($description);
 	}
 
@@ -988,7 +956,7 @@ private function parsePropertyTagValue(TokenIterator $tokens): Ast\PhpDoc\Proper
 	{
 		$type = $this->typeParser->parse($tokens);
 		$parameterName = $this->parseRequiredVariableName($tokens);
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, false);
 		return new Ast\PhpDoc\PropertyTagValueNode($type, $parameterName, $description);
 	}
 
@@ -1031,7 +999,7 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa
 					$tokens,
 					$this->typeParser->parseTemplateTagValue($tokens),
 					$startLine,
-					$startIndex
+					$startIndex,
 				);
 			} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
 			$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
@@ -1047,7 +1015,7 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa
 		}
 		$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
 
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, false);
 		return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description, $templateTypes);
 	}
 
@@ -1084,7 +1052,7 @@ private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc
 			$tokens,
 			new Ast\PhpDoc\MethodTagValueParameterNode($parameterType, $isReference, $isVariadic, $parameterName, $defaultValue),
 			$startLine,
-			$startIndex
+			$startIndex,
 		);
 	}
 
@@ -1097,10 +1065,10 @@ private function parseExtendsTagValue(string $tagName, TokenIterator $tokens): A
 
 		$type = $this->typeParser->parseGeneric(
 			$tokens,
-			$this->typeParser->enrichWithAttributes($tokens, $baseType, $startLine, $startIndex)
+			$this->typeParser->enrichWithAttributes($tokens, $baseType, $startLine, $startIndex),
 		);
 
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, true);
 
 		switch ($tagName) {
 			case '@extends':
@@ -1122,37 +1090,31 @@ private function parseTypeAliasTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeA
 		// support phan-type/psalm-type syntax
 		$tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL);
 
-		if ($this->preserveTypeAliasesWithInvalidTypes) {
-			$startLine = $tokens->currentTokenLine();
-			$startIndex = $tokens->currentTokenIndex();
-			try {
-				$type = $this->typeParser->parse($tokens);
-				if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
-					if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
-						throw new ParserException(
-							$tokens->currentTokenValue(),
-							$tokens->currentTokenType(),
-							$tokens->currentTokenOffset(),
-							Lexer::TOKEN_PHPDOC_EOL,
-							null,
-							$tokens->currentTokenLine()
-						);
-					}
+		$startLine = $tokens->currentTokenLine();
+		$startIndex = $tokens->currentTokenIndex();
+		try {
+			$type = $this->typeParser->parse($tokens);
+			if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
+				if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
+					throw new ParserException(
+						$tokens->currentTokenValue(),
+						$tokens->currentTokenType(),
+						$tokens->currentTokenOffset(),
+						Lexer::TOKEN_PHPDOC_EOL,
+						null,
+						$tokens->currentTokenLine(),
+					);
 				}
-
-				return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type);
-			} catch (ParserException $e) {
-				$this->parseOptionalDescription($tokens);
-				return new Ast\PhpDoc\TypeAliasTagValueNode(
-					$alias,
-					$this->enrichWithAttributes($tokens, new Ast\Type\InvalidTypeNode($e), $startLine, $startIndex)
-				);
 			}
-		}
-
-		$type = $this->typeParser->parse($tokens);
 
-		return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type);
+			return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type);
+		} catch (ParserException $e) {
+			$this->parseOptionalDescription($tokens, false);
+			return new Ast\PhpDoc\TypeAliasTagValueNode(
+				$alias,
+				$this->enrichWithAttributes($tokens, new Ast\Type\InvalidTypeNode($e), $startLine, $startIndex),
+			);
+		}
 	}
 
 	private function parseTypeAliasImportTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeAliasImportTagValueNode
@@ -1170,7 +1132,7 @@ private function parseTypeAliasImportTagValue(TokenIterator $tokens): Ast\PhpDoc
 			$tokens,
 			new IdentifierTypeNode($importedFrom),
 			$identifierStartLine,
-			$identifierStartIndex
+			$identifierStartIndex,
 		);
 
 		$importedAs = null;
@@ -1191,7 +1153,7 @@ private function parseAssertTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTa
 		$isEquality = $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL);
 		$type = $this->typeParser->parse($tokens);
 		$parameter = $this->parseAssertParameter($tokens);
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, false);
 
 		if (array_key_exists('method', $parameter)) {
 			return new Ast\PhpDoc\AssertTagMethodValueNode($type, $parameter['parameter'], $parameter['method'], $isNegated, $description, $isEquality);
@@ -1236,7 +1198,7 @@ private function parseAssertParameter(TokenIterator $tokens): array
 	private function parseSelfOutTagValue(TokenIterator $tokens): Ast\PhpDoc\SelfOutTagValueNode
 	{
 		$type = $this->typeParser->parse($tokens);
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, true);
 
 		return new Ast\PhpDoc\SelfOutTagValueNode($type, $description);
 	}
@@ -1245,7 +1207,7 @@ private function parseParamOutTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamO
 	{
 		$type = $this->typeParser->parse($tokens);
 		$parameterName = $this->parseRequiredVariableName($tokens);
-		$description = $this->parseOptionalDescription($tokens);
+		$description = $this->parseOptionalDescription($tokens, false);
 
 		return new Ast\PhpDoc\ParamOutTagValueNode($type, $parameterName, $description);
 	}
@@ -1275,7 +1237,10 @@ private function parseRequiredVariableName(TokenIterator $tokens): string
 		return $parameterName;
 	}
 
-	private function parseOptionalDescription(TokenIterator $tokens, bool $limitStartToken = false): string
+	/**
+	 * @param bool $limitStartToken true should be used when the description immediately follows a parsed type
+	 */
+	private function parseOptionalDescription(TokenIterator $tokens, bool $limitStartToken): string
 	{
 		if ($limitStartToken) {
 			foreach (self::DISALLOWED_DESCRIPTION_START_TOKENS as $disallowedStartToken) {
@@ -1287,8 +1252,7 @@ private function parseOptionalDescription(TokenIterator $tokens, bool $limitStar
 			}
 
 			if (
-				$this->requireWhitespaceBeforeDescription
-				&& !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END)
+				!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END)
 				&& !$tokens->isPrecededByHorizontalWhitespace()
 			) {
 				$tokens->consumeTokenType(Lexer::TOKEN_HORIZONTAL_WS); // will throw exception
diff --git a/src/Parser/StringUnescaper.php b/src/Parser/StringUnescaper.php
index a3bbeedd..e8e0a3d6 100644
--- a/src/Parser/StringUnescaper.php
+++ b/src/Parser/StringUnescaper.php
@@ -31,7 +31,7 @@ public static function unescapeString(string $string): string
 			return str_replace(
 				['\\\\', '\\\''],
 				['\\', '\''],
-				substr($string, 1, -1)
+				substr($string, 1, -1),
 			);
 		}
 
@@ -65,7 +65,7 @@ static function ($matches) {
 
 				return chr((int) octdec($str));
 			},
-			$str
+			$str,
 		);
 	}
 
diff --git a/src/Parser/TokenIterator.php b/src/Parser/TokenIterator.php
index 9be7593d..f2be3da4 100644
--- a/src/Parser/TokenIterator.php
+++ b/src/Parser/TokenIterator.php
@@ -3,6 +3,7 @@
 namespace PHPStan\PhpDocParser\Parser;
 
 use LogicException;
+use PHPStan\PhpDocParser\Ast\Comment;
 use PHPStan\PhpDocParser\Lexer\Lexer;
 use function array_pop;
 use function assert;
@@ -15,19 +16,20 @@ class TokenIterator
 {
 
 	/** @var list<array{string, int, int}> */
-	private $tokens;
+	private array $tokens;
 
-	/** @var int */
-	private $index;
+	private int $index;
 
-	/** @var int[] */
-	private $savePoints = [];
+	/** @var list<Comment> */
+	private array $comments = [];
+
+	/** @var list<array{int, list<Comment>}> */
+	private array $savePoints = [];
 
 	/** @var list<int> */
-	private $skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS];
+	private array $skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS];
 
-	/** @var string|null */
-	private $newline = null;
+	private ?string $newline = null;
 
 	/**
 	 * @param list<array{string, int, int}> $tokens
@@ -154,8 +156,7 @@ public function consumeTokenType(int $tokenType): void
 			}
 		}
 
-		$this->index++;
-		$this->skipIrrelevantTokens();
+		$this->next();
 	}
 
 
@@ -168,8 +169,7 @@ public function consumeTokenValue(int $tokenType, string $tokenValue): void
 			$this->throwError($tokenType, $tokenValue);
 		}
 
-		$this->index++;
-		$this->skipIrrelevantTokens();
+		$this->next();
 	}
 
 
@@ -180,12 +180,20 @@ public function tryConsumeTokenValue(string $tokenValue): bool
 			return false;
 		}
 
-		$this->index++;
-		$this->skipIrrelevantTokens();
+		$this->next();
 
 		return true;
 	}
 
+	/**
+	 * @return list<Comment>
+	 */
+	public function flushComments(): array
+	{
+		$res = $this->comments;
+		$this->comments = [];
+		return $res;
+	}
 
 	/** @phpstan-impure */
 	public function tryConsumeTokenType(int $tokenType): bool
@@ -200,13 +208,50 @@ public function tryConsumeTokenType(int $tokenType): bool
 			}
 		}
 
-		$this->index++;
-		$this->skipIrrelevantTokens();
+		$this->next();
 
 		return true;
 	}
 
 
+	/**
+	 * @deprecated Use skipNewLineTokensAndConsumeComments instead (when parsing a type)
+	 */
+	public function skipNewLineTokens(): void
+	{
+		if (!$this->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
+			return;
+		}
+
+		do {
+			$foundNewLine = $this->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		} while ($foundNewLine === true);
+	}
+
+
+	public function skipNewLineTokensAndConsumeComments(): void
+	{
+		if ($this->currentTokenType() === Lexer::TOKEN_COMMENT) {
+			$this->comments[] = new Comment($this->currentTokenValue(), $this->currentTokenLine(), $this->currentTokenIndex());
+			$this->next();
+		}
+
+		if (!$this->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
+			return;
+		}
+
+		do {
+			$foundNewLine = $this->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			if ($this->currentTokenType() !== Lexer::TOKEN_COMMENT) {
+				continue;
+			}
+
+			$this->comments[] = new Comment($this->currentTokenValue(), $this->currentTokenLine(), $this->currentTokenIndex());
+			$this->next();
+		} while ($foundNewLine === true);
+	}
+
+
 	private function detectNewline(): void
 	{
 		$value = $this->currentTokenValue();
@@ -282,7 +327,7 @@ public function forwardToTheEnd(): void
 
 	public function pushSavePoint(): void
 	{
-		$this->savePoints[] = $this->index;
+		$this->savePoints[] = [$this->index, $this->comments];
 	}
 
 
@@ -294,9 +339,9 @@ public function dropSavePoint(): void
 
 	public function rollback(): void
 	{
-		$index = array_pop($this->savePoints);
-		assert($index !== null);
-		$this->index = $index;
+		$savepoint = array_pop($this->savePoints);
+		assert($savepoint !== null);
+		[$this->index, $this->comments] = $savepoint;
 	}
 
 
@@ -311,7 +356,7 @@ private function throwError(int $expectedTokenType, ?string $expectedTokenValue
 			$this->currentTokenOffset(),
 			$expectedTokenType,
 			$expectedTokenValue,
-			$this->currentTokenLine()
+			$this->currentTokenLine(),
 		);
 	}
 
diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php
index 982eba7d..fc225854 100644
--- a/src/Parser/TypeParser.php
+++ b/src/Parser/TypeParser.php
@@ -6,41 +6,27 @@
 use PHPStan\PhpDocParser\Ast;
 use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
 use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\ParserConfig;
 use function in_array;
 use function str_replace;
 use function strlen;
 use function strpos;
 use function substr_compare;
-use function trim;
 
 class TypeParser
 {
 
-	/** @var ConstExprParser|null */
-	private $constExprParser;
+	private ParserConfig $config;
 
-	/** @var bool */
-	private $quoteAwareConstExprString;
+	private ConstExprParser $constExprParser;
 
-	/** @var bool */
-	private $useLinesAttributes;
-
-	/** @var bool */
-	private $useIndexAttributes;
-
-	/**
-	 * @param array{lines?: bool, indexes?: bool} $usedAttributes
-	 */
 	public function __construct(
-		?ConstExprParser $constExprParser = null,
-		bool $quoteAwareConstExprString = false,
-		array $usedAttributes = []
+		ParserConfig $config,
+		ConstExprParser $constExprParser
 	)
 	{
+		$this->config = $config;
 		$this->constExprParser = $constExprParser;
-		$this->quoteAwareConstExprString = $quoteAwareConstExprString;
-		$this->useLinesAttributes = $usedAttributes['lines'] ?? false;
-		$this->useIndexAttributes = $usedAttributes['indexes'] ?? false;
 	}
 
 	/** @phpstan-impure */
@@ -54,17 +40,44 @@ public function parse(TokenIterator $tokens): Ast\Type\TypeNode
 		} else {
 			$type = $this->parseAtomic($tokens);
 
-			if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
-				$type = $this->parseUnion($tokens, $type);
+			$tokens->pushSavePoint();
+			$tokens->skipNewLineTokensAndConsumeComments();
+
+			try {
+				$enrichedType = $this->enrichTypeOnUnionOrIntersection($tokens, $type);
+
+			} catch (ParserException $parserException) {
+				$enrichedType = null;
+			}
 
-			} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
-				$type = $this->parseIntersection($tokens, $type);
+			if ($enrichedType !== null) {
+				$type = $enrichedType;
+				$tokens->dropSavePoint();
+
+			} else {
+				$tokens->rollback();
+				$type = $this->enrichTypeOnUnionOrIntersection($tokens, $type) ?? $type;
 			}
 		}
 
 		return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex);
 	}
 
+	/** @phpstan-impure */
+	private function enrichTypeOnUnionOrIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): ?Ast\Type\TypeNode
+	{
+		if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
+			return $this->parseUnion($tokens, $type);
+
+		}
+
+		if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
+			return $this->parseIntersection($tokens, $type);
+		}
+
+		return null;
+	}
+
 	/**
 	 * @internal
 	 * @template T of Ast\Node
@@ -73,12 +86,17 @@ public function parse(TokenIterator $tokens): Ast\Type\TypeNode
 	 */
 	public function enrichWithAttributes(TokenIterator $tokens, Ast\Node $type, int $startLine, int $startIndex): Ast\Node
 	{
-		if ($this->useLinesAttributes) {
+		if ($this->config->useLinesAttributes) {
 			$type->setAttribute(Ast\Attribute::START_LINE, $startLine);
 			$type->setAttribute(Ast\Attribute::END_LINE, $tokens->currentTokenLine());
 		}
 
-		if ($this->useIndexAttributes) {
+		$comments = $tokens->flushComments();
+		if ($this->config->useCommentsAttributes) {
+			$type->setAttribute(Ast\Attribute::COMMENTS, $comments);
+		}
+
+		if ($this->config->useIndexAttributes) {
 			$type->setAttribute(Ast\Attribute::START_INDEX, $startIndex);
 			$type->setAttribute(Ast\Attribute::END_INDEX, $tokens->endIndexOfLastRelevantToken());
 		}
@@ -104,7 +122,7 @@ private function subParse(TokenIterator $tokens): Ast\Type\TypeNode
 			if ($tokens->isCurrentTokenValue('is')) {
 				$type = $this->parseConditional($tokens, $type);
 			} else {
-				$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+				$tokens->skipNewLineTokensAndConsumeComments();
 
 				if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
 					$type = $this->subParseUnion($tokens, $type);
@@ -126,9 +144,9 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
 		$startIndex = $tokens->currentTokenIndex();
 
 		if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 			$type = $this->subParse($tokens);
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 
 			$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
 
@@ -196,7 +214,7 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
 					if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
 						$type = $this->tryParseArrayOrOffsetAccess(
 							$tokens,
-							$this->enrichWithAttributes($tokens, $type, $startLine, $startIndex)
+							$this->enrichWithAttributes($tokens, $type, $startLine, $startIndex),
 						);
 					}
 				}
@@ -214,19 +232,8 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
 		$currentTokenOffset = $tokens->currentTokenOffset();
 		$currentTokenLine = $tokens->currentTokenLine();
 
-		if ($this->constExprParser === null) {
-			throw new ParserException(
-				$currentTokenValue,
-				$currentTokenType,
-				$currentTokenOffset,
-				Lexer::TOKEN_IDENTIFIER,
-				null,
-				$currentTokenLine
-			);
-		}
-
 		try {
-			$constExpr = $this->constExprParser->parse($tokens, true);
+			$constExpr = $this->constExprParser->parse($tokens);
 			if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
 				throw new ParserException(
 					$currentTokenValue,
@@ -234,7 +241,7 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
 					$currentTokenOffset,
 					Lexer::TOKEN_IDENTIFIER,
 					null,
-					$currentTokenLine
+					$currentTokenLine,
 				);
 			}
 
@@ -242,7 +249,7 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
 				$tokens,
 				new Ast\Type\ConstTypeNode($constExpr),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 			if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
 				$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
@@ -256,7 +263,7 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
 				$currentTokenOffset,
 				Lexer::TOKEN_IDENTIFIER,
 				null,
-				$currentTokenLine
+				$currentTokenLine,
 			);
 		}
 	}
@@ -269,6 +276,14 @@ private function parseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast
 
 		while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
 			$types[] = $this->parseAtomic($tokens);
+			$tokens->pushSavePoint();
+			$tokens->skipNewLineTokensAndConsumeComments();
+			if (!$tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
+				$tokens->rollback();
+				break;
+			}
+
+			$tokens->dropSavePoint();
 		}
 
 		return new Ast\Type\UnionTypeNode($types);
@@ -281,9 +296,9 @@ private function subParseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type):
 		$types = [$type];
 
 		while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 			$types[] = $this->parseAtomic($tokens);
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 		}
 
 		return new Ast\Type\UnionTypeNode($types);
@@ -297,6 +312,14 @@ private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $typ
 
 		while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
 			$types[] = $this->parseAtomic($tokens);
+			$tokens->pushSavePoint();
+			$tokens->skipNewLineTokensAndConsumeComments();
+			if (!$tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
+				$tokens->rollback();
+				break;
+			}
+
+			$tokens->dropSavePoint();
 		}
 
 		return new Ast\Type\IntersectionTypeNode($types);
@@ -309,9 +332,9 @@ private function subParseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $
 		$types = [$type];
 
 		while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 			$types[] = $this->parseAtomic($tokens);
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 		}
 
 		return new Ast\Type\IntersectionTypeNode($types);
@@ -331,15 +354,15 @@ private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subj
 
 		$targetType = $this->parse($tokens);
 
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 		$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 
 		$ifType = $this->parse($tokens);
 
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 		$tokens->consumeTokenType(Lexer::TOKEN_COLON);
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 
 		$elseType = $this->subParse($tokens);
 
@@ -360,15 +383,15 @@ private function parseConditionalForParameter(TokenIterator $tokens, string $par
 
 		$targetType = $this->parse($tokens);
 
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 		$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 
 		$ifType = $this->parse($tokens);
 
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 		$tokens->consumeTokenType(Lexer::TOKEN_COLON);
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 
 		$elseType = $this->subParse($tokens);
 
@@ -427,6 +450,7 @@ public function isHtml(TokenIterator $tokens): bool
 	public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType): Ast\Type\GenericTypeNode
 	{
 		$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
+		$tokens->skipNewLineTokensAndConsumeComments();
 
 		$startLine = $baseType->getAttribute(Ast\Attribute::START_LINE);
 		$startIndex = $baseType->getAttribute(Ast\Attribute::START_INDEX);
@@ -434,8 +458,11 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode
 		$variances = [];
 
 		$isFirst = true;
-		while ($isFirst || $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		while (
+			$isFirst
+			|| $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)
+		) {
+			$tokens->skipNewLineTokensAndConsumeComments();
 
 			// trailing comma case
 			if (!$isFirst && $tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
@@ -444,7 +471,7 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode
 			$isFirst = false;
 
 			[$genericTypes[], $variances[]] = $this->parseGenericTypeArgument($tokens);
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 		}
 
 		$type = new Ast\Type\GenericTypeNode($baseType, $genericTypes, $variances);
@@ -535,19 +562,19 @@ private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNod
 			: [];
 
 		$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 
 		$parameters = [];
 		if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
 			$parameters[] = $this->parseCallableParameter($tokens);
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 			while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
-				$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+				$tokens->skipNewLineTokensAndConsumeComments();
 				if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
 					break;
 				}
 				$parameters[] = $this->parseCallableParameter($tokens);
-				$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+				$tokens->skipNewLineTokensAndConsumeComments();
 			}
 		}
 
@@ -575,7 +602,7 @@ private function parseCallableTemplates(TokenIterator $tokens): array
 
 		$isFirst = true;
 		while ($isFirst || $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 
 			// trailing comma case
 			if (!$isFirst && $tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
@@ -584,7 +611,7 @@ private function parseCallableTemplates(TokenIterator $tokens): array
 			$isFirst = false;
 
 			$templates[] = $this->parseCallableTemplateArgument($tokens);
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 		}
 
 		$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
@@ -602,7 +629,7 @@ private function parseCallableTemplateArgument(TokenIterator $tokens): Ast\PhpDo
 			$tokens,
 			$this->parseTemplateTagValue($tokens),
 			$startLine,
-			$startIndex
+			$startIndex,
 		);
 	}
 
@@ -629,7 +656,7 @@ private function parseCallableParameter(TokenIterator $tokens): Ast\Type\Callabl
 			$tokens,
 			new Ast\Type\CallableTypeParameterNode($type, $isReference, $isVariadic, $parameterName, $isOptional),
 			$startLine,
-			$startIndex
+			$startIndex,
 		);
 	}
 
@@ -657,7 +684,7 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
 					$tokens,
 					$type,
 					$startLine,
-					$startIndex
+					$startIndex,
 				));
 			}
 
@@ -676,15 +703,15 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
 								$tokens,
 								$type,
 								$startLine,
-								$startIndex
-							)
+								$startIndex,
+							),
 						);
 						if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
 							$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
 								$tokens,
 								$type,
 								$startLine,
-								$startIndex
+								$startIndex,
 							));
 						}
 
@@ -693,7 +720,7 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
 							$tokens,
 							$type,
 							$startLine,
-							$startIndex
+							$startIndex,
 						));
 
 					} elseif (in_array($type->name, [
@@ -710,7 +737,7 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
 								$tokens,
 								$type,
 								$startLine,
-								$startIndex
+								$startIndex,
 							), $type->name);
 						}
 
@@ -719,7 +746,7 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
 								$tokens,
 								$type,
 								$startLine,
-								$startIndex
+								$startIndex,
 							));
 						}
 					}
@@ -738,19 +765,8 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
 		$currentTokenOffset = $tokens->currentTokenOffset();
 		$currentTokenLine = $tokens->currentTokenLine();
 
-		if ($this->constExprParser === null) {
-			throw new ParserException(
-				$currentTokenValue,
-				$currentTokenType,
-				$currentTokenOffset,
-				Lexer::TOKEN_IDENTIFIER,
-				null,
-				$currentTokenLine
-			);
-		}
-
 		try {
-			$constExpr = $this->constExprParser->parse($tokens, true);
+			$constExpr = $this->constExprParser->parse($tokens);
 			if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
 				throw new ParserException(
 					$currentTokenValue,
@@ -758,7 +774,7 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
 					$currentTokenOffset,
 					Lexer::TOKEN_IDENTIFIER,
 					null,
-					$currentTokenLine
+					$currentTokenLine,
 				);
 			}
 
@@ -766,7 +782,7 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
 				$tokens,
 				new Ast\Type\ConstTypeNode($constExpr),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 			if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
 				$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
@@ -780,7 +796,7 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
 				$currentTokenOffset,
 				Lexer::TOKEN_IDENTIFIER,
 				null,
-				$currentTokenLine
+				$currentTokenLine,
 			);
 		}
 	}
@@ -826,7 +842,7 @@ private function tryParseArrayOrOffsetAccess(TokenIterator $tokens, Ast\Type\Typ
 							$tokens,
 							$type,
 							$startLine,
-							$startIndex
+							$startIndex,
 						);
 					}
 				} else {
@@ -839,7 +855,7 @@ private function tryParseArrayOrOffsetAccess(TokenIterator $tokens, Ast\Type\Typ
 							$tokens,
 							$type,
 							$startLine,
-							$startIndex
+							$startIndex,
 						);
 					}
 				}
@@ -865,24 +881,26 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
 		$sealed = true;
 		$unsealedType = null;
 
+		$done = false;
+
 		do {
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 
 			if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
-				return new Ast\Type\ArrayShapeNode($items, true, $kind);
+				return Ast\Type\ArrayShapeNode::createSealed($items, $kind);
 			}
 
 			if ($tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC)) {
 				$sealed = false;
 
-				$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+				$tokens->skipNewLineTokensAndConsumeComments();
 				if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
 					if ($kind === Ast\Type\ArrayShapeNode::KIND_ARRAY) {
 						$unsealedType = $this->parseArrayShapeUnsealedType($tokens);
 					} else {
 						$unsealedType = $this->parseListShapeUnsealedType($tokens);
 					}
-					$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+					$tokens->skipNewLineTokensAndConsumeComments();
 				}
 
 				$tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA);
@@ -890,14 +908,26 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
 			}
 
 			$items[] = $this->parseArrayShapeItem($tokens);
+			$tokens->skipNewLineTokensAndConsumeComments();
+			if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
+				$done = true;
+			}
+			if ($tokens->currentTokenType() !== Lexer::TOKEN_COMMENT) {
+				continue;
+			}
 
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
-		} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
+			$tokens->next();
+
+		} while (!$done);
 
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 		$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
 
-		return new Ast\Type\ArrayShapeNode($items, $sealed, $kind, $unsealedType);
+		if ($sealed) {
+			return Ast\Type\ArrayShapeNode::createSealed($items, $kind);
+		}
+
+		return Ast\Type\ArrayShapeNode::createUnsealed($items, $unsealedType, $kind);
 	}
 
 
@@ -906,19 +936,24 @@ private function parseArrayShapeItem(TokenIterator $tokens): Ast\Type\ArrayShape
 	{
 		$startLine = $tokens->currentTokenLine();
 		$startIndex = $tokens->currentTokenIndex();
+
+		// parse any comments above the item
+		$tokens->skipNewLineTokensAndConsumeComments();
+
 		try {
 			$tokens->pushSavePoint();
 			$key = $this->parseArrayShapeKey($tokens);
 			$optional = $tokens->tryConsumeTokenType(Lexer::TOKEN_NULLABLE);
 			$tokens->consumeTokenType(Lexer::TOKEN_COLON);
 			$value = $this->parse($tokens);
+
 			$tokens->dropSavePoint();
 
 			return $this->enrichWithAttributes(
 				$tokens,
 				new Ast\Type\ArrayShapeItemNode($key, $optional, $value),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		} catch (ParserException $e) {
 			$tokens->rollback();
@@ -928,7 +963,7 @@ private function parseArrayShapeItem(TokenIterator $tokens): Ast\Type\ArrayShape
 				$tokens,
 				new Ast\Type\ArrayShapeItemNode(null, false, $value),
 				$startLine,
-				$startIndex
+				$startIndex,
 			);
 		}
 	}
@@ -947,19 +982,11 @@ private function parseArrayShapeKey(TokenIterator $tokens)
 			$tokens->next();
 
 		} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
-			if ($this->quoteAwareConstExprString) {
-				$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::SINGLE_QUOTED);
-			} else {
-				$key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), "'"));
-			}
+			$key = new Ast\ConstExpr\ConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\ConstExprStringNode::SINGLE_QUOTED);
 			$tokens->next();
 
 		} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
-			if ($this->quoteAwareConstExprString) {
-				$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::DOUBLE_QUOTED);
-			} else {
-				$key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), '"'));
-			}
+			$key = new Ast\ConstExpr\ConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\ConstExprStringNode::DOUBLE_QUOTED);
 
 			$tokens->next();
 
@@ -972,7 +999,7 @@ private function parseArrayShapeKey(TokenIterator $tokens)
 			$tokens,
 			$key,
 			$startLine,
-			$startIndex
+			$startIndex,
 		);
 	}
 
@@ -985,18 +1012,18 @@ private function parseArrayShapeUnsealedType(TokenIterator $tokens): Ast\Type\Ar
 		$startIndex = $tokens->currentTokenIndex();
 
 		$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 
 		$valueType = $this->parse($tokens);
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 
 		$keyType = null;
 		if ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 
 			$keyType = $valueType;
 			$valueType = $this->parse($tokens);
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 		}
 
 		$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
@@ -1005,7 +1032,7 @@ private function parseArrayShapeUnsealedType(TokenIterator $tokens): Ast\Type\Ar
 			$tokens,
 			new Ast\Type\ArrayShapeUnsealedTypeNode($valueType, $keyType),
 			$startLine,
-			$startIndex
+			$startIndex,
 		);
 	}
 
@@ -1018,10 +1045,10 @@ private function parseListShapeUnsealedType(TokenIterator $tokens): Ast\Type\Arr
 		$startIndex = $tokens->currentTokenIndex();
 
 		$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 
 		$valueType = $this->parse($tokens);
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 
 		$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
 
@@ -1029,7 +1056,7 @@ private function parseListShapeUnsealedType(TokenIterator $tokens): Ast\Type\Arr
 			$tokens,
 			new Ast\Type\ArrayShapeUnsealedTypeNode($valueType, null),
 			$startLine,
-			$startIndex
+			$startIndex,
 		);
 	}
 
@@ -1043,7 +1070,7 @@ private function parseObjectShape(TokenIterator $tokens): Ast\Type\ObjectShapeNo
 		$items = [];
 
 		do {
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 
 			if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
 				return new Ast\Type\ObjectShapeNode($items);
@@ -1051,10 +1078,10 @@ private function parseObjectShape(TokenIterator $tokens): Ast\Type\ObjectShapeNo
 
 			$items[] = $this->parseObjectShapeItem($tokens);
 
-			$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+			$tokens->skipNewLineTokensAndConsumeComments();
 		} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
 
-		$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
+		$tokens->skipNewLineTokensAndConsumeComments();
 		$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
 
 		return new Ast\Type\ObjectShapeNode($items);
@@ -1066,12 +1093,19 @@ private function parseObjectShapeItem(TokenIterator $tokens): Ast\Type\ObjectSha
 		$startLine = $tokens->currentTokenLine();
 		$startIndex = $tokens->currentTokenIndex();
 
+		$tokens->skipNewLineTokensAndConsumeComments();
+
 		$key = $this->parseObjectShapeKey($tokens);
 		$optional = $tokens->tryConsumeTokenType(Lexer::TOKEN_NULLABLE);
 		$tokens->consumeTokenType(Lexer::TOKEN_COLON);
 		$value = $this->parse($tokens);
 
-		return $this->enrichWithAttributes($tokens, new Ast\Type\ObjectShapeItemNode($key, $optional, $value), $startLine, $startIndex);
+		return $this->enrichWithAttributes(
+			$tokens,
+			new Ast\Type\ObjectShapeItemNode($key, $optional, $value),
+			$startLine,
+			$startIndex,
+		);
 	}
 
 	/**
@@ -1084,19 +1118,11 @@ private function parseObjectShapeKey(TokenIterator $tokens)
 		$startIndex = $tokens->currentTokenIndex();
 
 		if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
-			if ($this->quoteAwareConstExprString) {
-				$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::SINGLE_QUOTED);
-			} else {
-				$key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), "'"));
-			}
+			$key = new Ast\ConstExpr\ConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\ConstExprStringNode::SINGLE_QUOTED);
 			$tokens->next();
 
 		} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
-			if ($this->quoteAwareConstExprString) {
-				$key = new Ast\ConstExpr\QuoteAwareConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\QuoteAwareConstExprStringNode::DOUBLE_QUOTED);
-			} else {
-				$key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), '"'));
-			}
+			$key = new Ast\ConstExpr\ConstExprStringNode(StringUnescaper::unescapeString($tokens->currentTokenValue()), Ast\ConstExpr\ConstExprStringNode::DOUBLE_QUOTED);
 			$tokens->next();
 
 		} else {
diff --git a/src/ParserConfig.php b/src/ParserConfig.php
new file mode 100644
index 00000000..0c4377ad
--- /dev/null
+++ b/src/ParserConfig.php
@@ -0,0 +1,24 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\PhpDocParser;
+
+class ParserConfig
+{
+
+	public bool $useLinesAttributes;
+
+	public bool $useIndexAttributes;
+
+	public bool $useCommentsAttributes;
+
+	/**
+	 * @param array{lines?: bool, indexes?: bool, comments?: bool} $usedAttributes
+	 */
+	public function __construct(array $usedAttributes)
+	{
+		$this->useLinesAttributes = $usedAttributes['lines'] ?? false;
+		$this->useIndexAttributes = $usedAttributes['indexes'] ?? false;
+		$this->useCommentsAttributes = $usedAttributes['comments'] ?? false;
+	}
+
+}
diff --git a/src/Printer/Differ.php b/src/Printer/Differ.php
index ab10be59..c60fd4ad 100644
--- a/src/Printer/Differ.php
+++ b/src/Printer/Differ.php
@@ -180,7 +180,7 @@ private function coalesceReplacements(array $diff): array
 					$newDiff[] = new DiffElem(
 						DiffElem::TYPE_REPLACE,
 						$diff[$i + $n]->old,
-						$diff[$j + $n]->new
+						$diff[$j + $n]->new,
 					);
 				}
 			} else {
diff --git a/src/Printer/Printer.php b/src/Printer/Printer.php
index f6665987..36f6ebe1 100644
--- a/src/Printer/Printer.php
+++ b/src/Printer/Printer.php
@@ -4,6 +4,7 @@
 
 use LogicException;
 use PHPStan\PhpDocParser\Ast\Attribute;
+use PHPStan\PhpDocParser\Ast\Comment;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode;
 use PHPStan\PhpDocParser\Ast\Node;
@@ -35,6 +36,7 @@
 use PHPStan\PhpDocParser\Ast\PhpDoc\RequireExtendsTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\RequireImplementsTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
+use PHPStan\PhpDocParser\Ast\PhpDoc\SealedTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\SelfOutTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
@@ -66,6 +68,7 @@
 use PHPStan\PhpDocParser\Parser\TokenIterator;
 use function array_keys;
 use function array_map;
+use function assert;
 use function count;
 use function get_class;
 use function get_object_vars;
@@ -74,6 +77,7 @@
 use function is_array;
 use function preg_match_all;
 use function sprintf;
+use function str_replace;
 use function strlen;
 use function strpos;
 use function trim;
@@ -89,7 +93,7 @@ final class Printer
 {
 
 	/** @var Differ<Node> */
-	private $differ;
+	private Differ $differ;
 
 	/**
 	 * Map From "{$class}->{$subNode}" to string that should be inserted
@@ -97,7 +101,7 @@ final class Printer
 	 *
 	 * @var array<string, string>
 	 */
-	private $listInsertionMap = [
+	private array $listInsertionMap = [
 		PhpDocNode::class . '->children' => "\n * ",
 		UnionTypeNode::class . '->types' => '|',
 		IntersectionTypeNode::class . '->types' => '&',
@@ -117,7 +121,7 @@ final class Printer
 	 *
 	 * @var array<string, array{string|null, string, string}>
 	 */
-	private $emptyListInsertionMap = [
+	private array $emptyListInsertionMap = [
 		CallableTypeNode::class . '->parameters' => ['(', '', ''],
 		ArrayShapeNode::class . '->items' => ['{', '', ''],
 		ObjectShapeNode::class . '->items' => ['{', '', ''],
@@ -126,7 +130,7 @@ final class Printer
 	];
 
 	/** @var array<string, list<class-string<TypeNode>>> */
-	private $parenthesesMap = [
+	private array $parenthesesMap = [
 		CallableTypeNode::class . '->returnType' => [
 			CallableTypeNode::class,
 			UnionTypeNode::class,
@@ -148,7 +152,7 @@ final class Printer
 	];
 
 	/** @var array<string, list<class-string<TypeNode>>> */
-	private $parenthesesListMap = [
+	private array $parenthesesListMap = [
 		IntersectionTypeNode::class . '->types' => [
 			IntersectionTypeNode::class,
 			UnionTypeNode::class,
@@ -178,7 +182,7 @@ public function printFormatPreserving(PhpDocNode $node, PhpDocNode $originalNode
 			$originalTokens,
 			$tokenIndex,
 			PhpDocNode::class,
-			'children'
+			'children',
 		);
 		if ($result !== null) {
 			return $result . $originalTokens->getContentBetween($tokenIndex, $originalTokens->getTokenCount());
@@ -195,7 +199,7 @@ function (PhpDocChildNode $child): string {
 					$s = $this->print($child);
 					return $s === '' ? '' : ' ' . $s;
 				},
-				$node->children
+				$node->children,
 			)) . "\n */";
 		}
 		if ($node instanceof PhpDocTextNode) {
@@ -249,6 +253,30 @@ function (PhpDocChildNode $child): string {
 		if ($node instanceof DoctrineArrayItem) {
 			return (string) $node;
 		}
+		if ($node instanceof ArrayShapeItemNode) {
+			if ($node->keyName !== null) {
+				return sprintf(
+					'%s%s: %s',
+					$this->print($node->keyName),
+					$node->optional ? '?' : '',
+					$this->printType($node->valueType),
+				);
+			}
+
+			return $this->printType($node->valueType);
+		}
+		if ($node instanceof ObjectShapeItemNode) {
+			if ($node->keyName !== null) {
+				return sprintf(
+					'%s%s: %s',
+					$this->print($node->keyName),
+					$node->optional ? '?' : '',
+					$this->printType($node->valueType),
+				);
+			}
+
+			return $this->printType($node->valueType);
+		}
 
 		throw new LogicException(sprintf('Unknown node type %s', get_class($node)));
 	}
@@ -283,13 +311,9 @@ private function printTagValue(PhpDocTagValueNode $node): string
 		if ($node instanceof MethodTagValueNode) {
 			$static = $node->isStatic ? 'static ' : '';
 			$returnType = $node->returnType !== null ? $this->printType($node->returnType) . ' ' : '';
-			$parameters = implode(', ', array_map(function (MethodTagValueParameterNode $parameter): string {
-				return $this->print($parameter);
-			}, $node->parameters));
+			$parameters = implode(', ', array_map(fn (MethodTagValueParameterNode $parameter): string => $this->print($parameter), $node->parameters));
 			$description = $node->description !== '' ? " {$node->description}" : '';
-			$templateTypes = count($node->templateTypes) > 0 ? '<' . implode(', ', array_map(function (TemplateTagValueNode $templateTag): string {
-				return $this->print($templateTag);
-			}, $node->templateTypes)) . '>' : '';
+			$templateTypes = count($node->templateTypes) > 0 ? '<' . implode(', ', array_map(fn (TemplateTagValueNode $templateTag): string => $this->print($templateTag), $node->templateTypes)) . '>' : '';
 			return "{$static}{$returnType}{$node->methodName}{$templateTypes}({$parameters}){$description}";
 		}
 		if ($node instanceof MixinTagValueNode) {
@@ -304,6 +328,10 @@ private function printTagValue(PhpDocTagValueNode $node): string
 			$type = $this->printType($node->type);
 			return trim("{$type} {$node->description}");
 		}
+		if ($node instanceof SealedTagValueNode) {
+			$type = $this->printType($node->type);
+			return trim("{$type} {$node->description}");
+		}
 		if ($node instanceof ParamOutTagValueNode) {
 			$type = $this->printType($node->type);
 			return trim("{$type} {$node->parameterName} {$node->description}");
@@ -351,7 +379,7 @@ private function printTagValue(PhpDocTagValueNode $node): string
 		if ($node instanceof TypeAliasImportTagValueNode) {
 			return trim(
 				"{$node->importedAlias} from " . $this->printType($node->importedFrom)
-				. ($node->importedAs !== null ? " as {$node->importedAs}" : '')
+				. ($node->importedAs !== null ? " as {$node->importedAs}" : ''),
 			);
 		}
 		if ($node instanceof TypeAliasTagValueNode) {
@@ -373,9 +401,7 @@ private function printTagValue(PhpDocTagValueNode $node): string
 	private function printType(TypeNode $node): string
 	{
 		if ($node instanceof ArrayShapeNode) {
-			$items = array_map(function (ArrayShapeItemNode $item): string {
-				return $this->printType($item);
-			}, $node->items);
+			$items = array_map(fn (ArrayShapeItemNode $item): string => $this->print($item), $node->items);
 
 			if (! $node->sealed) {
 				$items[] = '...' . ($node->unsealedType === null ? '' : $this->print($node->unsealedType));
@@ -383,18 +409,6 @@ private function printType(TypeNode $node): string
 
 			return $node->kind . '{' . implode(', ', $items) . '}';
 		}
-		if ($node instanceof ArrayShapeItemNode) {
-			if ($node->keyName !== null) {
-				return sprintf(
-					'%s%s: %s',
-					$this->print($node->keyName),
-					$node->optional ? '?' : '',
-					$this->printType($node->valueType)
-				);
-			}
-
-			return $this->printType($node->valueType);
-		}
 		if ($node instanceof ArrayTypeNode) {
 			return $this->printOffsetAccessType($node->type) . '[]';
 		}
@@ -405,13 +419,9 @@ private function printType(TypeNode $node): string
 				$returnType = $this->printType($node->returnType);
 			}
 			$template = $node->templateTypes !== []
-				? '<' . implode(', ', array_map(function (TemplateTagValueNode $templateNode): string {
-					return $this->print($templateNode);
-				}, $node->templateTypes)) . '>'
+				? '<' . implode(', ', array_map(fn (TemplateTagValueNode $templateNode): string => $this->print($templateNode), $node->templateTypes)) . '>'
 				: '';
-			$parameters = implode(', ', array_map(function (CallableTypeParameterNode $parameterNode): string {
-				return $this->print($parameterNode);
-			}, $node->parameters));
+			$parameters = implode(', ', array_map(fn (CallableTypeParameterNode $parameterNode): string => $this->print($parameterNode), $node->parameters));
 			return "{$node->identifier}{$template}({$parameters}): {$returnType}";
 		}
 		if ($node instanceof ConditionalTypeForParameterNode) {
@@ -421,7 +431,7 @@ private function printType(TypeNode $node): string
 				$node->negated ? 'is not' : 'is',
 				$this->printType($node->targetType),
 				$this->printType($node->if),
-				$this->printType($node->else)
+				$this->printType($node->else),
 			);
 		}
 		if ($node instanceof ConditionalTypeNode) {
@@ -431,7 +441,7 @@ private function printType(TypeNode $node): string
 				$node->negated ? 'is not' : 'is',
 				$this->printType($node->targetType),
 				$this->printType($node->if),
-				$this->printType($node->else)
+				$this->printType($node->else),
 			);
 		}
 		if ($node instanceof ConstTypeNode) {
@@ -484,24 +494,10 @@ private function printType(TypeNode $node): string
 			return '?' . $this->printType($node->type);
 		}
 		if ($node instanceof ObjectShapeNode) {
-			$items = array_map(function (ObjectShapeItemNode $item): string {
-				return $this->printType($item);
-			}, $node->items);
+			$items = array_map(fn (ObjectShapeItemNode $item): string => $this->print($item), $node->items);
 
 			return 'object{' . implode(', ', $items) . '}';
 		}
-		if ($node instanceof ObjectShapeItemNode) {
-			if ($node->keyName !== null) {
-				return sprintf(
-					'%s%s: %s',
-					$this->print($node->keyName),
-					$node->optional ? '?' : '',
-					$this->printType($node->valueType)
-				);
-			}
-
-			return $this->printType($node->valueType);
-		}
 		if ($node instanceof OffsetAccessTypeNode) {
 			return $this->printOffsetAccessType($node->type) . '[' . $this->printType($node->offset) . ']';
 		}
@@ -559,19 +555,30 @@ private function printArrayFormatPreserving(array $nodes, array $originalNodes,
 
 		foreach ($diff as $i => $diffElem) {
 			$diffType = $diffElem->type;
-			$newNode = $diffElem->new;
-			$originalNode = $diffElem->old;
+			$arrItem = $diffElem->new;
+			$origArrayItem = $diffElem->old;
 			if ($diffType === DiffElem::TYPE_KEEP || $diffType === DiffElem::TYPE_REPLACE) {
 				$beforeFirstKeepOrReplace = false;
-				if (!$newNode instanceof Node || !$originalNode instanceof Node) {
+				if (!$arrItem instanceof Node || !$origArrayItem instanceof Node) {
 					return null;
 				}
-				$itemStartPos = $originalNode->getAttribute(Attribute::START_INDEX);
-				$itemEndPos = $originalNode->getAttribute(Attribute::END_INDEX);
+
+				/** @var int $itemStartPos */
+				$itemStartPos = $origArrayItem->getAttribute(Attribute::START_INDEX);
+
+				/** @var int $itemEndPos */
+				$itemEndPos = $origArrayItem->getAttribute(Attribute::END_INDEX);
+
 				if ($itemStartPos < 0 || $itemEndPos < 0 || $itemStartPos < $tokenIndex) {
 					throw new LogicException();
 				}
 
+				$comments = $arrItem->getAttribute(Attribute::COMMENTS) ?? [];
+				$origComments = $origArrayItem->getAttribute(Attribute::COMMENTS) ?? [];
+
+				$commentStartPos = count($origComments) > 0 ? $origComments[0]->startIndex : $itemStartPos;
+				assert($commentStartPos >= 0);
+
 				$result .= $originalTokens->getContentBetween($tokenIndex, $itemStartPos);
 
 				if (count($delayedAdd) > 0) {
@@ -581,6 +588,15 @@ private function printArrayFormatPreserving(array $nodes, array $originalNodes,
 						if ($parenthesesNeeded) {
 							$result .= '(';
 						}
+
+						if ($insertNewline) {
+							$delayedAddComments = $delayedAddNode->getAttribute(Attribute::COMMENTS) ?? [];
+							if (count($delayedAddComments) > 0) {
+								$result .= $this->printComments($delayedAddComments, $beforeAsteriskIndent, $afterAsteriskIndent);
+								$result .= sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
+							}
+						}
+
 						$result .= $this->printNodeFormatPreserving($delayedAddNode, $originalTokens);
 						if ($parenthesesNeeded) {
 							$result .= ')';
@@ -597,14 +613,21 @@ private function printArrayFormatPreserving(array $nodes, array $originalNodes,
 				}
 
 				$parenthesesNeeded = isset($this->parenthesesListMap[$mapKey])
-					&& in_array(get_class($newNode), $this->parenthesesListMap[$mapKey], true)
-					&& !in_array(get_class($originalNode), $this->parenthesesListMap[$mapKey], true);
+					&& in_array(get_class($arrItem), $this->parenthesesListMap[$mapKey], true)
+					&& !in_array(get_class($origArrayItem), $this->parenthesesListMap[$mapKey], true);
 				$addParentheses = $parenthesesNeeded && !$originalTokens->hasParentheses($itemStartPos, $itemEndPos);
 				if ($addParentheses) {
 					$result .= '(';
 				}
 
-				$result .= $this->printNodeFormatPreserving($newNode, $originalTokens);
+				if ($comments !== $origComments) {
+					if (count($comments) > 0) {
+						$result .= $this->printComments($comments, $beforeAsteriskIndent, $afterAsteriskIndent);
+						$result .= sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
+					}
+				}
+
+				$result .= $this->printNodeFormatPreserving($arrItem, $originalTokens);
 				if ($addParentheses) {
 					$result .= ')';
 				}
@@ -614,35 +637,42 @@ private function printArrayFormatPreserving(array $nodes, array $originalNodes,
 				if ($insertStr === null) {
 					return null;
 				}
-				if (!$newNode instanceof Node) {
+				if (!$arrItem instanceof Node) {
 					return null;
 				}
 
-				if ($insertStr === ', ' && $isMultiline) {
+				if ($insertStr === ', ' && $isMultiline || count($arrItem->getAttribute(Attribute::COMMENTS) ?? []) > 0) {
 					$insertStr = ',';
 					$insertNewline = true;
 				}
 
 				if ($beforeFirstKeepOrReplace) {
 					// Will be inserted at the next "replace" or "keep" element
-					$delayedAdd[] = $newNode;
+					$delayedAdd[] = $arrItem;
 					continue;
 				}
 
+				/** @var int $itemEndPos */
 				$itemEndPos = $tokenIndex - 1;
 				if ($insertNewline) {
-					$result .= $insertStr . sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
+					$comments = $arrItem->getAttribute(Attribute::COMMENTS) ?? [];
+					$result .= $insertStr;
+					if (count($comments) > 0) {
+						$result .= sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
+						$result .= $this->printComments($comments, $beforeAsteriskIndent, $afterAsteriskIndent);
+					}
+					$result .= sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
 				} else {
 					$result .= $insertStr;
 				}
 
 				$parenthesesNeeded = isset($this->parenthesesListMap[$mapKey])
-					&& in_array(get_class($newNode), $this->parenthesesListMap[$mapKey], true);
+					&& in_array(get_class($arrItem), $this->parenthesesListMap[$mapKey], true);
 				if ($parenthesesNeeded) {
 					$result .= '(';
 				}
 
-				$result .= $this->printNodeFormatPreserving($newNode, $originalTokens);
+				$result .= $this->printNodeFormatPreserving($arrItem, $originalTokens);
 				if ($parenthesesNeeded) {
 					$result .= ')';
 				}
@@ -650,12 +680,15 @@ private function printArrayFormatPreserving(array $nodes, array $originalNodes,
 				$tokenIndex = $itemEndPos + 1;
 
 			} elseif ($diffType === DiffElem::TYPE_REMOVE) {
-				if (!$originalNode instanceof Node) {
+				if (!$origArrayItem instanceof Node) {
 					return null;
 				}
 
-				$itemStartPos = $originalNode->getAttribute(Attribute::START_INDEX);
-				$itemEndPos = $originalNode->getAttribute(Attribute::END_INDEX);
+				/** @var int $itemStartPos */
+				$itemStartPos = $origArrayItem->getAttribute(Attribute::START_INDEX);
+
+				/** @var int $itemEndPos */
+				$itemEndPos = $origArrayItem->getAttribute(Attribute::END_INDEX);
 				if ($itemStartPos < 0 || $itemEndPos < 0) {
 					throw new LogicException();
 				}
@@ -714,7 +747,21 @@ private function printArrayFormatPreserving(array $nodes, array $originalNodes,
 	}
 
 	/**
-	 * @param Node[] $nodes
+	 * @param list<Comment> $comments
+	 */
+	private function printComments(array $comments, string $beforeAsteriskIndent, string $afterAsteriskIndent): string
+	{
+		$formattedComments = [];
+
+		foreach ($comments as $comment) {
+			$formattedComments[] = str_replace("\n", "\n" . $beforeAsteriskIndent . '*' . $afterAsteriskIndent, $comment->getReformattedText());
+		}
+
+		return implode("\n$beforeAsteriskIndent*$afterAsteriskIndent", $formattedComments);
+	}
+
+	/**
+	 * @param array<Node|null> $nodes
 	 * @return array{bool, string, string}
 	 */
 	private function isMultiline(int $initialIndex, array $nodes, TokenIterator $originalTokens): array
@@ -742,7 +789,7 @@ private function isMultiline(int $initialIndex, array $nodes, TokenIterator $ori
 
 		$c = preg_match_all('~\n(?<before>[\\x09\\x20]*)\*(?<after>\\x20*)~', $allText, $matches, PREG_SET_ORDER);
 		if ($c === 0) {
-			return [$isMultiline, '', ''];
+			return [$isMultiline, ' ', '  '];
 		}
 
 		$before = '';
@@ -758,6 +805,9 @@ private function isMultiline(int $initialIndex, array $nodes, TokenIterator $ori
 			$after = $match['after'];
 		}
 
+		$before = strlen($before) === 0 ? ' ' : $before;
+		$after = strlen($after) === 0 ? '  ' : $after;
+
 		return [$isMultiline, $before, $after];
 	}
 
@@ -804,7 +854,7 @@ private function printNodeFormatPreserving(Node $node, TokenIterator $originalTo
 						$originalTokens,
 						$pos,
 						$class,
-						$subNodeName
+						$subNodeName,
 					);
 
 					if ($listResult === null) {
diff --git a/tests/PHPStan/Ast/Attributes/AttributesTest.php b/tests/PHPStan/Ast/Attributes/AttributesTest.php
index a20747ec..f513b1fa 100644
--- a/tests/PHPStan/Ast/Attributes/AttributesTest.php
+++ b/tests/PHPStan/Ast/Attributes/AttributesTest.php
@@ -8,20 +8,22 @@
 use PHPStan\PhpDocParser\Parser\PhpDocParser;
 use PHPStan\PhpDocParser\Parser\TokenIterator;
 use PHPStan\PhpDocParser\Parser\TypeParser;
+use PHPStan\PhpDocParser\ParserConfig;
 use PHPUnit\Framework\TestCase;
 
 final class AttributesTest extends TestCase
 {
 
-	/** @var PhpDocNode */
-	private $phpDocNode;
+	private PhpDocNode $phpDocNode;
 
 	protected function setUp(): void
 	{
 		parent::setUp();
-		$lexer = new Lexer();
-		$constExprParser = new ConstExprParser();
-		$phpDocParser = new PhpDocParser(new TypeParser($constExprParser), $constExprParser);
+
+		$config = new ParserConfig([]);
+		$lexer = new Lexer($config);
+		$constExprParser = new ConstExprParser($config);
+		$phpDocParser = new PhpDocParser($config, new TypeParser($config, $constExprParser), $constExprParser);
 
 		$input = '/** @var string */';
 		$tokens = new TokenIterator($lexer->tokenize($input));
diff --git a/tests/PHPStan/Ast/NodeTraverserTest.php b/tests/PHPStan/Ast/NodeTraverserTest.php
index 74089ed2..49f3646b 100644
--- a/tests/PHPStan/Ast/NodeTraverserTest.php
+++ b/tests/PHPStan/Ast/NodeTraverserTest.php
@@ -163,7 +163,7 @@ public function testMerge(): void
 
 		$this->assertEquals(
 			[$strStart, $strR1, $strR2, $strEnd],
-			$traverser->traverse([$strStart, $strMiddle, $strEnd])
+			$traverser->traverse([$strStart, $strMiddle, $strEnd]),
 		);
 	}
 
@@ -393,7 +393,7 @@ public function provideTestInvalidReturn(): array
 			['enterNode', $expr, new ConstExprIntegerNode('42')],
 		]);
 		$visitor8 = new NodeVisitorForTesting([
-			['enterNode', $num, new ReturnTagValueNode(new ConstTypeNode(new ConstExprStringNode('foo')), '')],
+			['enterNode', $num, new ReturnTagValueNode(new ConstTypeNode(new ConstExprStringNode('foo', ConstExprStringNode::SINGLE_QUOTED)), '')],
 		]);
 
 		return [
diff --git a/tests/PHPStan/Ast/NodeVisitorForTesting.php b/tests/PHPStan/Ast/NodeVisitorForTesting.php
index f3ca0ac0..fa5dd26d 100644
--- a/tests/PHPStan/Ast/NodeVisitorForTesting.php
+++ b/tests/PHPStan/Ast/NodeVisitorForTesting.php
@@ -15,13 +15,12 @@ class NodeVisitorForTesting implements NodeVisitor
 {
 
 	/** @var list<array{string, Node|Node[]}> */
-	public $trace = [];
+	public array $trace = [];
 
 	/** @var list<list<mixed>> */
-	private $returns;
+	private array $returns;
 
-	/** @var int */
-	private $returnsPos;
+	private int $returnsPos;
 
 	/**
 	 * @param list<list<mixed>> $returns
diff --git a/tests/PHPStan/Ast/ToString/ConstExprToStringTest.php b/tests/PHPStan/Ast/ToString/ConstExprToStringTest.php
index fe8be67c..b7568879 100644
--- a/tests/PHPStan/Ast/ToString/ConstExprToStringTest.php
+++ b/tests/PHPStan/Ast/ToString/ConstExprToStringTest.php
@@ -44,16 +44,17 @@ public static function provideConstExprCases(): Generator
 			['false', new ConstExprFalseNode()],
 			['8', new ConstExprIntegerNode('8')],
 			['21.37', new ConstExprFloatNode('21.37')],
-			['foo', new ConstExprStringNode('foo')],
+			['\'foo\'', new ConstExprStringNode('foo', ConstExprStringNode::SINGLE_QUOTED)],
+			['"foo"', new ConstExprStringNode('foo', ConstExprStringNode::DOUBLE_QUOTED)],
 			['FooBar', new ConstFetchNode('', 'FooBar')],
 			['Foo\\Bar::Baz', new ConstFetchNode('Foo\\Bar', 'Baz')],
 			['[]', new ConstExprArrayNode([])],
 			[
-				'[foo, 4 => foo, bar => baz]',
+				"['foo', 4 => 'foo', 'bar' => 'baz']",
 				new ConstExprArrayNode([
-					new ConstExprArrayItemNode(null, new ConstExprStringNode('foo')),
-					new ConstExprArrayItemNode(new ConstExprIntegerNode('4'), new ConstExprStringNode('foo')),
-					new ConstExprArrayItemNode(new ConstExprStringNode('bar'), new ConstExprStringNode('baz')),
+					new ConstExprArrayItemNode(null, new ConstExprStringNode('foo', ConstExprStringNode::SINGLE_QUOTED)),
+					new ConstExprArrayItemNode(new ConstExprIntegerNode('4'), new ConstExprStringNode('foo', ConstExprStringNode::SINGLE_QUOTED)),
+					new ConstExprArrayItemNode(new ConstExprStringNode('bar', ConstExprStringNode::SINGLE_QUOTED), new ConstExprStringNode('baz', ConstExprStringNode::SINGLE_QUOTED)),
 				]),
 			],
 		];
diff --git a/tests/PHPStan/Ast/ToString/PhpDocToStringTest.php b/tests/PHPStan/Ast/ToString/PhpDocToStringTest.php
index 74d257ab..47a25db3 100644
--- a/tests/PHPStan/Ast/ToString/PhpDocToStringTest.php
+++ b/tests/PHPStan/Ast/ToString/PhpDocToStringTest.php
@@ -137,7 +137,7 @@ public static function provideOtherCases(): Generator
 			'#desc',
 			new InvalidTagValueNode(
 				'#desc',
-				new ParserException('#desc', Lexer::TOKEN_OTHER, 11, Lexer::TOKEN_IDENTIFIER)
+				new ParserException('#desc', Lexer::TOKEN_OTHER, 11, Lexer::TOKEN_IDENTIFIER, null, null),
 			),
 		];
 
@@ -283,8 +283,8 @@ public static function provideClassCases(): Generator
 				new MethodTagValueParameterNode($string, true, false, 'foo', null),
 			],
 			[
-				'string &foo = bar',
-				new MethodTagValueParameterNode($string, true, false, 'foo', new ConstExprStringNode('bar')),
+				'string &foo = \'bar\'',
+				new MethodTagValueParameterNode($string, true, false, 'foo', new ConstExprStringNode('bar', ConstExprStringNode::SINGLE_QUOTED)),
 			],
 			[
 				'&...foo',
@@ -382,7 +382,7 @@ public static function provideDoctrineCases(): iterable
 			'@ORM\Entity()',
 			new PhpDocTagNode('@ORM\Entity', new DoctrineTagValueNode(
 				new DoctrineAnnotation('@ORM\Entity', []),
-				''
+				'',
 			)),
 		];
 
@@ -390,7 +390,7 @@ public static function provideDoctrineCases(): iterable
 			'@ORM\Entity() test',
 			new PhpDocTagNode('@ORM\Entity', new DoctrineTagValueNode(
 				new DoctrineAnnotation('@ORM\Entity', []),
-				'test'
+				'test',
 			)),
 		];
 
@@ -401,7 +401,7 @@ public static function provideDoctrineCases(): iterable
 					new DoctrineArgument(null, new ConstExprIntegerNode('1')),
 					new DoctrineArgument(new IdentifierTypeNode('b'), new ConstExprIntegerNode('2')),
 				]),
-				''
+				'',
 			),
 		];
 
@@ -411,10 +411,10 @@ public static function provideDoctrineCases(): iterable
 		];
 
 		yield [
-			'{1, a=2}',
+			'{1, \'a\'=2}',
 			new DoctrineArray([
 				new DoctrineArrayItem(null, new ConstExprIntegerNode('1')),
-				new DoctrineArrayItem(new ConstExprStringNode('a'), new ConstExprIntegerNode('2')),
+				new DoctrineArrayItem(new ConstExprStringNode('a', ConstExprStringNode::SINGLE_QUOTED), new ConstExprIntegerNode('2')),
 			]),
 		];
 
@@ -424,8 +424,8 @@ public static function provideDoctrineCases(): iterable
 		];
 
 		yield [
-			'a=2',
-			new DoctrineArrayItem(new ConstExprStringNode('a'), new ConstExprIntegerNode('2')),
+			'\'a\'=2',
+			new DoctrineArrayItem(new ConstExprStringNode('a', ConstExprStringNode::SINGLE_QUOTED), new ConstExprIntegerNode('2')),
 		];
 
 		yield [
diff --git a/tests/PHPStan/Ast/ToString/TypeToStringTest.php b/tests/PHPStan/Ast/ToString/TypeToStringTest.php
index 9cf7ebf6..6716e433 100644
--- a/tests/PHPStan/Ast/ToString/TypeToStringTest.php
+++ b/tests/PHPStan/Ast/ToString/TypeToStringTest.php
@@ -58,31 +58,31 @@ public static function provideArrayCases(): Generator
 		];
 
 		yield from [
-			['array{}', new ArrayShapeNode([])],
-			['array{...}', new ArrayShapeNode([], false)],
+			['array{}', ArrayShapeNode::createSealed([])],
+			['array{...}', ArrayShapeNode::createUnsealed([], null)],
 			[
 				'array{string, int, ...}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createUnsealed([
 					new ArrayShapeItemNode(null, false, new IdentifierTypeNode('string')),
 					new ArrayShapeItemNode(null, false, new IdentifierTypeNode('int')),
-				], false),
+				], null),
 			],
 			[
-				'array{foo: Foo, bar?: Bar, 1: Baz}',
-				new ArrayShapeNode([
-					new ArrayShapeItemNode(new ConstExprStringNode('foo'), false, new IdentifierTypeNode('Foo')),
-					new ArrayShapeItemNode(new ConstExprStringNode('bar'), true, new IdentifierTypeNode('Bar')),
+				'array{\'foo\': Foo, \'bar\'?: Bar, 1: Baz}',
+				ArrayShapeNode::createSealed([
+					new ArrayShapeItemNode(new ConstExprStringNode('foo', ConstExprStringNode::SINGLE_QUOTED), false, new IdentifierTypeNode('Foo')),
+					new ArrayShapeItemNode(new ConstExprStringNode('bar', ConstExprStringNode::SINGLE_QUOTED), true, new IdentifierTypeNode('Bar')),
 					new ArrayShapeItemNode(new ConstExprIntegerNode('1'), false, new IdentifierTypeNode('Baz')),
 				]),
 			],
-			['list{}', new ArrayShapeNode([], true, 'list')],
-			['list{...}', new ArrayShapeNode([], false, 'list')],
+			['list{}', ArrayShapeNode::createSealed([], 'list')],
+			['list{...}', ArrayShapeNode::createUnsealed([], null, 'list')],
 			[
 				'list{string, int, ...}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createUnsealed([
 					new ArrayShapeItemNode(null, false, new IdentifierTypeNode('string')),
 					new ArrayShapeItemNode(null, false, new IdentifierTypeNode('int')),
-				], false, 'list'),
+				], null, 'list'),
 			],
 		];
 	}
@@ -92,34 +92,34 @@ public static function provideCallableCases(): Generator
 		yield from [
 			[
 				'\\Closure(): string',
-				new CallableTypeNode(new IdentifierTypeNode('\Closure'), [], new IdentifierTypeNode('string')),
+				new CallableTypeNode(new IdentifierTypeNode('\Closure'), [], new IdentifierTypeNode('string'), []),
 			],
 			[
 				'callable(int, int $foo): void',
 				new CallableTypeNode(new IdentifierTypeNode('callable'), [
 					new CallableTypeParameterNode(new IdentifierTypeNode('int'), false, false, '', false),
 					new CallableTypeParameterNode(new IdentifierTypeNode('int'), false, false, '$foo', false),
-				], new IdentifierTypeNode('void')),
+				], new IdentifierTypeNode('void'), []),
 			],
 			[
 				'callable(int=, int $foo=): void',
 				new CallableTypeNode(new IdentifierTypeNode('callable'), [
 					new CallableTypeParameterNode(new IdentifierTypeNode('int'), false, false, '', true),
 					new CallableTypeParameterNode(new IdentifierTypeNode('int'), false, false, '$foo', true),
-				], new IdentifierTypeNode('void')),
+				], new IdentifierTypeNode('void'), []),
 			],
 			[
 				'callable(int &, int &$foo): void',
 				new CallableTypeNode(new IdentifierTypeNode('callable'), [
 					new CallableTypeParameterNode(new IdentifierTypeNode('int'), true, false, '', false),
 					new CallableTypeParameterNode(new IdentifierTypeNode('int'), true, false, '$foo', false),
-				], new IdentifierTypeNode('void')),
+				], new IdentifierTypeNode('void'), []),
 			],
 			[
 				'callable(string ...$foo): void',
 				new CallableTypeNode(new IdentifierTypeNode('callable'), [
 					new CallableTypeParameterNode(new IdentifierTypeNode('string'), false, true, '$foo', false),
-				], new IdentifierTypeNode('void')),
+				], new IdentifierTypeNode('void'), []),
 			],
 		];
 	}
@@ -136,7 +136,7 @@ public static function provideGenericCases(): Generator
 				new GenericTypeNode(
 					new IdentifierTypeNode('array'),
 					[new IdentifierTypeNode('string'), new IdentifierTypeNode('int')],
-					[GenericTypeNode::VARIANCE_INVARIANT, GenericTypeNode::VARIANCE_BIVARIANT]
+					[GenericTypeNode::VARIANCE_INVARIANT, GenericTypeNode::VARIANCE_BIVARIANT],
 				),
 			],
 			[
@@ -144,7 +144,7 @@ public static function provideGenericCases(): Generator
 				new GenericTypeNode(
 					new IdentifierTypeNode('Foo\\Bar'),
 					[new IdentifierTypeNode('string'), new IdentifierTypeNode('int')],
-					[GenericTypeNode::VARIANCE_COVARIANT, GenericTypeNode::VARIANCE_CONTRAVARIANT]
+					[GenericTypeNode::VARIANCE_COVARIANT, GenericTypeNode::VARIANCE_CONTRAVARIANT],
 				),
 			],
 		];
@@ -160,7 +160,7 @@ public static function provideConditionalCases(): Generator
 					new IdentifierTypeNode('int'),
 					new GenericTypeNode(new IdentifierTypeNode('list'), [new IdentifierTypeNode('int')]),
 					new GenericTypeNode(new IdentifierTypeNode('list'), [new IdentifierTypeNode('string')]),
-					false
+					false,
 				),
 			],
 			[
@@ -170,7 +170,7 @@ public static function provideConditionalCases(): Generator
 					new IdentifierTypeNode('array'),
 					new IdentifierTypeNode('int'),
 					new ArrayTypeNode(new IdentifierTypeNode('int')),
-					true
+					true,
 				),
 			],
 			[
@@ -180,7 +180,7 @@ public static function provideConditionalCases(): Generator
 					new IdentifierTypeNode('Exception'),
 					new IdentifierTypeNode('never'),
 					new IdentifierTypeNode('string'),
-					false
+					false,
 				),
 			],
 			[
@@ -190,7 +190,7 @@ public static function provideConditionalCases(): Generator
 					new IdentifierTypeNode('Exception'),
 					new IdentifierTypeNode('string'),
 					new IdentifierTypeNode('never'),
-					true
+					true,
 				),
 			],
 		];
diff --git a/tests/PHPStan/Parser/ConstExprParserTest.php b/tests/PHPStan/Parser/ConstExprParserTest.php
index f3427431..18922e86 100644
--- a/tests/PHPStan/Parser/ConstExprParserTest.php
+++ b/tests/PHPStan/Parser/ConstExprParserTest.php
@@ -16,22 +16,22 @@
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
 use PHPStan\PhpDocParser\Ast\NodeTraverser;
 use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\ParserConfig;
 use PHPUnit\Framework\TestCase;
 
 class ConstExprParserTest extends TestCase
 {
 
-	/** @var Lexer */
-	private $lexer;
+	private Lexer $lexer;
 
-	/** @var ConstExprParser */
-	private $constExprParser;
+	private ConstExprParser $constExprParser;
 
 	protected function setUp(): void
 	{
 		parent::setUp();
-		$this->lexer = new Lexer();
-		$this->constExprParser = new ConstExprParser(true);
+		$config = new ParserConfig([]);
+		$this->lexer = new Lexer($config);
+		$this->constExprParser = new ConstExprParser($config);
 	}
 
 
@@ -65,16 +65,15 @@ public function testParse(string $input, ConstExprNode $expectedExpr, int $nextT
 	 * @dataProvider provideStringNodeParseData
 	 * @dataProvider provideArrayNodeParseData
 	 * @dataProvider provideFetchNodeParseData
-	 *
-	 * @dataProvider provideWithTrimStringsStringNodeParseData
 	 */
 	public function testVerifyAttributes(string $input): void
 	{
 		$tokens = new TokenIterator($this->lexer->tokenize($input));
-		$constExprParser = new ConstExprParser(true, true, [
+		$config = new ParserConfig([
 			'lines' => true,
 			'indexes' => true,
 		]);
+		$constExprParser = new ConstExprParser($config);
 		$visitor = new NodeCollectingVisitor();
 		$traverser = new NodeTraverser([$visitor]);
 		$traverser->traverse([$constExprParser->parse($tokens)]);
@@ -312,22 +311,32 @@ public function provideStringNodeParseData(): Iterator
 	{
 		yield [
 			'"foo"',
-			new ConstExprStringNode('"foo"'),
+			new ConstExprStringNode('foo', ConstExprStringNode::DOUBLE_QUOTED),
 		];
 
 		yield [
 			'"Foo \\n\\"\\r Bar"',
-			new ConstExprStringNode('"Foo \\n\\"\\r Bar"'),
+			new ConstExprStringNode("Foo \n\"\r Bar", ConstExprStringNode::DOUBLE_QUOTED),
 		];
 
 		yield [
 			'\'bar\'',
-			new ConstExprStringNode('\'bar\''),
+			new ConstExprStringNode('bar', ConstExprStringNode::SINGLE_QUOTED),
 		];
 
 		yield [
 			'\'Foo \\\' Bar\'',
-			new ConstExprStringNode('\'Foo \\\' Bar\''),
+			new ConstExprStringNode('Foo \' Bar', ConstExprStringNode::SINGLE_QUOTED),
+		];
+
+		yield [
+			'"\u{1f601}"',
+			new ConstExprStringNode("\u{1f601}", ConstExprStringNode::DOUBLE_QUOTED),
+		];
+
+		yield [
+			'"\u{ffffffff}"',
+			new ConstExprStringNode("\u{fffd}", ConstExprStringNode::DOUBLE_QUOTED),
 		];
 	}
 
@@ -344,7 +353,7 @@ public function provideArrayNodeParseData(): Iterator
 			new ConstExprArrayNode([
 				new ConstExprArrayItemNode(
 					null,
-					new ConstExprIntegerNode('123')
+					new ConstExprIntegerNode('123'),
 				),
 			]),
 		];
@@ -354,15 +363,15 @@ public function provideArrayNodeParseData(): Iterator
 			new ConstExprArrayNode([
 				new ConstExprArrayItemNode(
 					null,
-					new ConstExprIntegerNode('1')
+					new ConstExprIntegerNode('1'),
 				),
 				new ConstExprArrayItemNode(
 					null,
-					new ConstExprIntegerNode('2')
+					new ConstExprIntegerNode('2'),
 				),
 				new ConstExprArrayItemNode(
 					null,
-					new ConstExprIntegerNode('3')
+					new ConstExprIntegerNode('3'),
 				),
 			]),
 		];
@@ -372,15 +381,15 @@ public function provideArrayNodeParseData(): Iterator
 			new ConstExprArrayNode([
 				new ConstExprArrayItemNode(
 					null,
-					new ConstExprIntegerNode('1')
+					new ConstExprIntegerNode('1'),
 				),
 				new ConstExprArrayItemNode(
 					null,
-					new ConstExprIntegerNode('2')
+					new ConstExprIntegerNode('2'),
 				),
 				new ConstExprArrayItemNode(
 					null,
-					new ConstExprIntegerNode('3')
+					new ConstExprIntegerNode('3'),
 				),
 			]),
 		];
@@ -390,7 +399,7 @@ public function provideArrayNodeParseData(): Iterator
 			new ConstExprArrayNode([
 				new ConstExprArrayItemNode(
 					new ConstExprIntegerNode('1'),
-					new ConstExprIntegerNode('2')
+					new ConstExprIntegerNode('2'),
 				),
 			]),
 		];
@@ -400,11 +409,11 @@ public function provideArrayNodeParseData(): Iterator
 			new ConstExprArrayNode([
 				new ConstExprArrayItemNode(
 					new ConstExprIntegerNode('1'),
-					new ConstExprIntegerNode('2')
+					new ConstExprIntegerNode('2'),
 				),
 				new ConstExprArrayItemNode(
 					null,
-					new ConstExprIntegerNode('3')
+					new ConstExprIntegerNode('3'),
 				),
 			]),
 		];
@@ -414,20 +423,20 @@ public function provideArrayNodeParseData(): Iterator
 			new ConstExprArrayNode([
 				new ConstExprArrayItemNode(
 					null,
-					new ConstExprIntegerNode('1')
+					new ConstExprIntegerNode('1'),
 				),
 				new ConstExprArrayItemNode(
 					null,
 					new ConstExprArrayNode([
 						new ConstExprArrayItemNode(
 							null,
-							new ConstExprIntegerNode('2')
+							new ConstExprIntegerNode('2'),
 						),
 						new ConstExprArrayItemNode(
 							null,
-							new ConstExprIntegerNode('3')
+							new ConstExprIntegerNode('3'),
 						),
-					])
+					]),
 				),
 			]),
 		];
@@ -457,50 +466,4 @@ public function provideFetchNodeParseData(): Iterator
 		];
 	}
 
-	/**
-	 * @dataProvider provideWithTrimStringsStringNodeParseData
-	 */
-	public function testParseWithTrimStrings(string $input, ConstExprNode $expectedExpr, int $nextTokenType = Lexer::TOKEN_END): void
-	{
-		$tokens = new TokenIterator($this->lexer->tokenize($input));
-		$exprNode = $this->constExprParser->parse($tokens, true);
-
-		$this->assertSame((string) $expectedExpr, (string) $exprNode);
-		$this->assertEquals($expectedExpr, $exprNode);
-		$this->assertSame($nextTokenType, $tokens->currentTokenType());
-	}
-
-	public function provideWithTrimStringsStringNodeParseData(): Iterator
-	{
-		yield [
-			'"foo"',
-			new ConstExprStringNode('foo'),
-		];
-
-		yield [
-			'"Foo \\n\\"\\r Bar"',
-			new ConstExprStringNode("Foo \n\"\r Bar"),
-		];
-
-		yield [
-			'\'bar\'',
-			new ConstExprStringNode('bar'),
-		];
-
-		yield [
-			'\'Foo \\\' Bar\'',
-			new ConstExprStringNode('Foo \' Bar'),
-		];
-
-		yield [
-			'"\u{1f601}"',
-			new ConstExprStringNode("\u{1f601}"),
-		];
-
-		yield [
-			'"\u{ffffffff}"',
-			new ConstExprStringNode("\u{fffd}"),
-		];
-	}
-
 }
diff --git a/tests/PHPStan/Parser/Doctrine/ApiResource.php b/tests/PHPStan/Parser/Doctrine/ApiResource.php
index 21a958c5..9134705f 100644
--- a/tests/PHPStan/Parser/Doctrine/ApiResource.php
+++ b/tests/PHPStan/Parser/Doctrine/ApiResource.php
@@ -13,22 +13,16 @@
 final class ApiResource
 {
 
-	/** @var string */
-	public $shortName;
+	public string $shortName;
 
-	/** @var string */
-	public $description;
+	public string $description;
 
-	/** @var string */
-	public $iri;
+	public string $iri;
 
-	/** @var array */
-	public $itemOperations;
+	public array $itemOperations;
 
-	/** @var array */
-	public $collectionOperations;
+	public array $collectionOperations;
 
-	/** @var array */
-	public $attributes = [];
+	public array $attributes = [];
 
 }
diff --git a/tests/PHPStan/Parser/FuzzyTest.php b/tests/PHPStan/Parser/FuzzyTest.php
index 336083b2..9491bf69 100644
--- a/tests/PHPStan/Parser/FuzzyTest.php
+++ b/tests/PHPStan/Parser/FuzzyTest.php
@@ -4,6 +4,7 @@
 
 use Iterator;
 use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\ParserConfig;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Process\Process;
 use function file_get_contents;
@@ -19,21 +20,19 @@
 class FuzzyTest extends TestCase
 {
 
-	/** @var Lexer */
-	private $lexer;
+	private Lexer $lexer;
 
-	/** @var TypeParser */
-	private $typeParser;
+	private TypeParser $typeParser;
 
-	/** @var ConstExprParser */
-	private $constExprParser;
+	private ConstExprParser $constExprParser;
 
 	protected function setUp(): void
 	{
 		parent::setUp();
-		$this->lexer = new Lexer();
-		$this->typeParser = new TypeParser(new ConstExprParser());
-		$this->constExprParser = new ConstExprParser();
+		$config = new ParserConfig([]);
+		$this->lexer = new Lexer($config);
+		$this->typeParser = new TypeParser($config, new ConstExprParser($config));
+		$this->constExprParser = new ConstExprParser($config);
 	}
 
 	/**
@@ -47,7 +46,7 @@ public function testTypeParser(string $input): void
 		$this->assertSame(
 			Lexer::TOKEN_END,
 			$tokens->currentTokenType(),
-			sprintf('Failed to parse input %s', $input)
+			sprintf('Failed to parse input %s', $input),
 		);
 	}
 
@@ -67,7 +66,7 @@ public function testConstExprParser(string $input): void
 		$this->assertSame(
 			Lexer::TOKEN_END,
 			$tokens->currentTokenType(),
-			sprintf('Failed to parse input %s', $input)
+			sprintf('Failed to parse input %s', $input),
 		);
 	}
 
diff --git a/tests/PHPStan/Parser/NodeCollectingVisitor.php b/tests/PHPStan/Parser/NodeCollectingVisitor.php
index aa01e559..dbce9dfc 100644
--- a/tests/PHPStan/Parser/NodeCollectingVisitor.php
+++ b/tests/PHPStan/Parser/NodeCollectingVisitor.php
@@ -9,7 +9,7 @@ class NodeCollectingVisitor extends AbstractNodeVisitor
 {
 
 	/** @var list<Node> */
-	public $nodes = [];
+	public array $nodes = [];
 
 	public function enterNode(Node $node)
 	{
diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php
index 1017fd31..b2857b2b 100644
--- a/tests/PHPStan/Parser/PhpDocParserTest.php
+++ b/tests/PHPStan/Parser/PhpDocParserTest.php
@@ -42,6 +42,7 @@
 use PHPStan\PhpDocParser\Ast\PhpDoc\RequireExtendsTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\RequireImplementsTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
+use PHPStan\PhpDocParser\Ast\PhpDoc\SealedTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\SelfOutTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
@@ -60,40 +61,36 @@
 use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
 use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
 use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
 use PHPStan\PhpDocParser\Ast\Type\InvalidTypeNode;
 use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode;
+use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode;
 use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
 use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
 use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\ParserConfig;
 use PHPUnit\Framework\TestCase;
 use function count;
 use function sprintf;
+use const DIRECTORY_SEPARATOR;
 use const PHP_EOL;
 
 class PhpDocParserTest extends TestCase
 {
 
-	/** @var Lexer */
-	private $lexer;
+	private Lexer $lexer;
 
-	/** @var PhpDocParser */
-	private $phpDocParser;
-
-	/** @var PhpDocParser */
-	private $phpDocParserWithRequiredWhitespaceBeforeDescription;
-
-	/** @var PhpDocParser */
-	private $phpDocParserWithPreserveTypeAliasesWithInvalidTypes;
+	private PhpDocParser $phpDocParser;
 
 	protected function setUp(): void
 	{
 		parent::setUp();
-		$this->lexer = new Lexer(true);
-		$constExprParser = new ConstExprParser();
-		$typeParser = new TypeParser($constExprParser);
-		$this->phpDocParser = new PhpDocParser($typeParser, $constExprParser, false, false, [], true);
-		$this->phpDocParserWithRequiredWhitespaceBeforeDescription = new PhpDocParser($typeParser, $constExprParser, true, false, [], true);
-		$this->phpDocParserWithPreserveTypeAliasesWithInvalidTypes = new PhpDocParser($typeParser, $constExprParser, true, true, [], true);
+		$config = new ParserConfig([]);
+		$this->lexer = new Lexer($config);
+		$constExprParser = new ConstExprParser($config);
+		$typeParser = new TypeParser($config, $constExprParser);
+		$this->phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
 	}
 
 
@@ -112,6 +109,7 @@ protected function setUp(): void
 	 * @dataProvider provideMixinTagsData
 	 * @dataProvider provideRequireExtendsTagsData
 	 * @dataProvider provideRequireImplementsTagsData
+	 * @dataProvider provideSealedTagsData
 	 * @dataProvider provideDeprecatedTagsData
 	 * @dataProvider providePropertyTagsData
 	 * @dataProvider provideMethodTagsData
@@ -130,34 +128,19 @@ protected function setUp(): void
 	 * @dataProvider provideDoctrineData
 	 * @dataProvider provideDoctrineWithoutDoctrineCheckData
 	 * @dataProvider provideCommentLikeDescriptions
+	 * @dataProvider provideInlineTags
 	 */
 	public function testParse(
 		string $label,
 		string $input,
-		PhpDocNode $expectedPhpDocNode,
-		?PhpDocNode $withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode = null,
-		?PhpDocNode $withPreserveTypeAliasesWithInvalidTypesExpectedPhpDocNode = null
+		PhpDocNode $expectedPhpDocNode
 	): void
 	{
 		$this->executeTestParse(
 			$this->phpDocParser,
 			$label,
 			$input,
-			$expectedPhpDocNode
-		);
-
-		$this->executeTestParse(
-			$this->phpDocParserWithRequiredWhitespaceBeforeDescription,
-			$label,
-			$input,
-			$withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode ?? $expectedPhpDocNode
-		);
-
-		$this->executeTestParse(
-			$this->phpDocParserWithPreserveTypeAliasesWithInvalidTypes,
-			$label,
-			$input,
-			$withPreserveTypeAliasesWithInvalidTypesExpectedPhpDocNode ?? $withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode ?? $expectedPhpDocNode
+			$expectedPhpDocNode,
 		);
 	}
 
@@ -185,8 +168,9 @@ public function provideParamTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						false,
 						'$foo',
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -201,8 +185,9 @@ public function provideParamTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						false,
 						'$foo',
-						'optional description'
-					)
+						'optional description',
+						false,
+					),
 				),
 			]),
 		];
@@ -217,8 +202,9 @@ public function provideParamTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						true,
 						'$foo',
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -233,8 +219,9 @@ public function provideParamTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						true,
 						'$foo',
-						'optional description'
-					)
+						'optional description',
+						false,
+					),
 				),
 			]),
 		];
@@ -250,8 +237,8 @@ public function provideParamTagsData(): Iterator
 						false,
 						'$foo',
 						'',
-						true
-					)
+						true,
+					),
 				),
 			]),
 		];
@@ -267,8 +254,8 @@ public function provideParamTagsData(): Iterator
 						false,
 						'$foo',
 						'optional description',
-						true
-					)
+						true,
+					),
 				),
 			]),
 		];
@@ -284,8 +271,8 @@ public function provideParamTagsData(): Iterator
 						true,
 						'$foo',
 						'',
-						true
-					)
+						true,
+					),
 				),
 			]),
 		];
@@ -301,8 +288,8 @@ public function provideParamTagsData(): Iterator
 						true,
 						'$foo',
 						'optional description',
-						true
-					)
+						true,
+					),
 				),
 			]),
 		];
@@ -317,8 +304,9 @@ public function provideParamTagsData(): Iterator
 						new ConstTypeNode(new ConstFetchNode('self', '*')),
 						false,
 						'$foo',
-						'optional description'
-					)
+						'optional description',
+						false,
+					),
 				),
 			]),
 		];
@@ -337,9 +325,9 @@ public function provideParamTagsData(): Iterator
 							11,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -358,9 +346,9 @@ public function provideParamTagsData(): Iterator
 							11,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -379,9 +367,9 @@ public function provideParamTagsData(): Iterator
 							16,
 							Lexer::TOKEN_CLOSE_PARENTHESES,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -400,9 +388,9 @@ public function provideParamTagsData(): Iterator
 							16,
 							Lexer::TOKEN_CLOSE_PARENTHESES,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -421,9 +409,9 @@ public function provideParamTagsData(): Iterator
 							19,
 							Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -442,9 +430,9 @@ public function provideParamTagsData(): Iterator
 							16,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -463,9 +451,9 @@ public function provideParamTagsData(): Iterator
 							15,
 							Lexer::TOKEN_VARIABLE,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -486,9 +474,9 @@ public function provideParamTagsData(): Iterator
 							21,
 							Lexer::TOKEN_VARIABLE,
 							null,
-							2
-						)
-					)
+							2,
+						),
+					),
 				),
 			]),
 		];
@@ -507,9 +495,9 @@ public function provideParamTagsData(): Iterator
 							15,
 							Lexer::TOKEN_VARIABLE,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -534,8 +522,9 @@ public function provideParamTagsData(): Iterator
 						false,
 						'$parameters',
 						'{' . PHP_EOL .
-						'    Optional. Parameters for filtering the list of user assignments. Default empty array.'
-					)
+						'    Optional. Parameters for filtering the list of user assignments. Default empty array.',
+						false,
+					),
 				),
 				new PhpDocTextNode(''),
 				new PhpDocTagNode('@type', new GenericTagValueNode('bool $is_active                Pass `true` to only return active user assignments and `false` to' . PHP_EOL .
@@ -558,8 +547,9 @@ public function provideTypelessParamTagsData(): Iterator
 					new TypelessParamTagValueNode(
 						false,
 						'$foo',
-						'description'
-					)
+						'description',
+						false,
+					),
 				),
 			]),
 		];
@@ -574,8 +564,8 @@ public function provideTypelessParamTagsData(): Iterator
 						false,
 						'$foo',
 						'description',
-						true
-					)
+						true,
+					),
 				),
 			]),
 		];
@@ -589,8 +579,9 @@ public function provideTypelessParamTagsData(): Iterator
 					new TypelessParamTagValueNode(
 						true,
 						'$foo',
-						'description'
-					)
+						'description',
+						false,
+					),
 				),
 			]),
 		];
@@ -605,8 +596,8 @@ public function provideTypelessParamTagsData(): Iterator
 						true,
 						'$foo',
 						'description',
-						true
-					)
+						true,
+					),
 				),
 			]),
 		];
@@ -621,8 +612,8 @@ public function provideTypelessParamTagsData(): Iterator
 						false,
 						'$foo',
 						'',
-						false
-					)
+						false,
+					),
 				),
 			]),
 		];
@@ -638,8 +629,8 @@ public function provideParamImmediatelyInvokedCallableTagsData(): Iterator
 					'@param-immediately-invoked-callable',
 					new ParamImmediatelyInvokedCallableTagValueNode(
 						'$foo',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -652,8 +643,8 @@ public function provideParamImmediatelyInvokedCallableTagsData(): Iterator
 					'@param-immediately-invoked-callable',
 					new ParamImmediatelyInvokedCallableTagValueNode(
 						'$foo',
-						'test two three'
-					)
+						'test two three',
+					),
 				),
 			]),
 		];
@@ -669,8 +660,8 @@ public function provideParamLaterInvokedCallableTagsData(): Iterator
 					'@param-later-invoked-callable',
 					new ParamLaterInvokedCallableTagValueNode(
 						'$foo',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -683,8 +674,8 @@ public function provideParamLaterInvokedCallableTagsData(): Iterator
 					'@param-later-invoked-callable',
 					new ParamLaterInvokedCallableTagValueNode(
 						'$foo',
-						'test two three'
-					)
+						'test two three',
+					),
 				),
 			]),
 		];
@@ -701,8 +692,8 @@ public function provideParamClosureThisTagsData(): Iterator
 					new ParamClosureThisTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$a',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -716,8 +707,8 @@ public function provideParamClosureThisTagsData(): Iterator
 					new ParamClosureThisTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$a',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -731,8 +722,8 @@ public function provideParamClosureThisTagsData(): Iterator
 					new ParamClosureThisTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$a',
-						'test'
-					)
+						'test',
+					),
 				),
 			]),
 		];
@@ -748,8 +739,8 @@ public function providePureUnlessCallableIsImpureTagsData(): Iterator
 					'@pure-unless-callable-is-impure',
 					new PureUnlessCallableIsImpureTagValueNode(
 						'$foo',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -762,8 +753,8 @@ public function providePureUnlessCallableIsImpureTagsData(): Iterator
 					'@pure-unless-callable-is-impure',
 					new PureUnlessCallableIsImpureTagValueNode(
 						'$foo',
-						'test two three'
-					)
+						'test two three',
+					),
 				),
 			]),
 		];
@@ -780,8 +771,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -795,8 +786,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$foo',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -810,8 +801,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$foo',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -825,8 +816,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'',
-						'optional description'
-					)
+						'optional description',
+					),
 				),
 			]),
 		];
@@ -839,11 +830,11 @@ public function provideVarTagsData(): Iterator
 					'@var',
 					new VarTagValueNode(
 						new ArrayTypeNode(
-							new IdentifierTypeNode('callable')
+							new IdentifierTypeNode('callable'),
 						),
 						'',
-						'function (Configurator $sender, DI\Compiler $compiler); Occurs after the compiler is created'
-					)
+						'function (Configurator $sender, DI\Compiler $compiler); Occurs after the compiler is created',
+					),
 				),
 			]),
 		];
@@ -857,8 +848,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'',
-						'@inject'
-					)
+						'@inject',
+					),
 				),
 			]),
 		];
@@ -872,8 +863,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'',
-						'(Bar)'
-					)
+						'(Bar)',
+					),
 				),
 			]),
 		];
@@ -887,8 +878,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$foo',
-						'optional description'
-					)
+						'optional description',
+					),
 				),
 			]),
 		];
@@ -902,8 +893,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$this',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -917,8 +908,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$this',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -932,8 +923,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$this',
-						'Testing'
-					)
+						'Testing',
+					),
 				),
 			]),
 		];
@@ -947,8 +938,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$this',
-						'Testing'
-					)
+						'Testing',
+					),
 				),
 			]),
 		];
@@ -962,18 +953,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$foo',
-						'#desc'
-					)
-				),
-			]),
-			new PhpDocNode([
-				new PhpDocTagNode(
-					'@var',
-					new VarTagValueNode(
-						new IdentifierTypeNode('Foo'),
-						'$foo',
-						'#desc'
-					)
+						'#desc',
+					),
 				),
 			]),
 		];
@@ -987,8 +968,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new ConstTypeNode(new ConstFetchNode('self', '*')),
 						'$foo',
-						'optional description'
-					)
+						'optional description',
+					),
 				),
 			]),
 		];
@@ -1002,8 +983,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$var',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1017,8 +998,8 @@ public function provideVarTagsData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1037,9 +1018,9 @@ public function provideVarTagsData(): Iterator
 							9,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1058,9 +1039,9 @@ public function provideVarTagsData(): Iterator
 							9,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1079,9 +1060,9 @@ public function provideVarTagsData(): Iterator
 							14,
 							Lexer::TOKEN_CLOSE_PARENTHESES,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1100,9 +1081,9 @@ public function provideVarTagsData(): Iterator
 							14,
 							Lexer::TOKEN_CLOSE_PARENTHESES,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1121,9 +1102,9 @@ public function provideVarTagsData(): Iterator
 							17,
 							Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1142,9 +1123,9 @@ public function provideVarTagsData(): Iterator
 							14,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1152,23 +1133,6 @@ public function provideVarTagsData(): Iterator
 		yield [
 			'invalid object shape',
 			'/** @psalm-type PARTSTRUCTURE_PARAM = objecttt{attribute:string, value?:string} */',
-			new PhpDocNode([
-				new PhpDocTagNode(
-					'@psalm-type',
-					new InvalidTagValueNode(
-						'Unexpected token "{", expected \'*/\' at offset 46 on line 1',
-						new ParserException(
-							'{',
-							Lexer::TOKEN_OPEN_CURLY_BRACKET,
-							46,
-							Lexer::TOKEN_CLOSE_PHPDOC,
-							null,
-							1
-						)
-					)
-				),
-			]),
-			null,
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@psalm-type',
@@ -1181,10 +1145,10 @@ public function provideVarTagsData(): Iterator
 								46,
 								Lexer::TOKEN_PHPDOC_EOL,
 								null,
-								1
-							)
-						)
-					)
+								1,
+							),
+						),
+					),
 				),
 			]),
 		];
@@ -1202,8 +1166,8 @@ public function providePropertyTagsData(): Iterator
 					new PropertyTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$foo',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1217,8 +1181,8 @@ public function providePropertyTagsData(): Iterator
 					new PropertyTagValueNode(
 						new IdentifierTypeNode('Foo'),
 						'$foo',
-						'optional description'
-					)
+						'optional description',
+					),
 				),
 			]),
 		];
@@ -1237,9 +1201,9 @@ public function providePropertyTagsData(): Iterator
 							14,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1258,9 +1222,9 @@ public function providePropertyTagsData(): Iterator
 							14,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1279,9 +1243,9 @@ public function providePropertyTagsData(): Iterator
 							19,
 							Lexer::TOKEN_CLOSE_PARENTHESES,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1300,9 +1264,9 @@ public function providePropertyTagsData(): Iterator
 							19,
 							Lexer::TOKEN_CLOSE_PARENTHESES,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1321,9 +1285,9 @@ public function providePropertyTagsData(): Iterator
 							22,
 							Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1342,9 +1306,9 @@ public function providePropertyTagsData(): Iterator
 							19,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1363,9 +1327,9 @@ public function providePropertyTagsData(): Iterator
 							18,
 							Lexer::TOKEN_VARIABLE,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1384,9 +1348,9 @@ public function providePropertyTagsData(): Iterator
 							18,
 							Lexer::TOKEN_VARIABLE,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1403,8 +1367,8 @@ public function provideReturnTagsData(): Iterator
 					'@return',
 					new ReturnTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1417,8 +1381,8 @@ public function provideReturnTagsData(): Iterator
 					'@return',
 					new ReturnTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						'optional description'
-					)
+						'optional description',
+					),
 				),
 			]),
 		];
@@ -1431,8 +1395,8 @@ public function provideReturnTagsData(): Iterator
 					'@return',
 					new ReturnTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						'[Bar]'
-					)
+						'[Bar]',
+					),
 				),
 			]),
 		];
@@ -1446,10 +1410,10 @@ public function provideReturnTagsData(): Iterator
 					new ReturnTagValueNode(
 						new OffsetAccessTypeNode(
 							new IdentifierTypeNode('Foo'),
-							new IdentifierTypeNode('Bar')
+							new IdentifierTypeNode('Bar'),
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1462,8 +1426,8 @@ public function provideReturnTagsData(): Iterator
 					'@return',
 					new ReturnTagValueNode(
 						new IdentifierTypeNode('MongoCollection'),
-						'<p>Returns a collection object representing the new collection.</p>'
-					)
+						'<p>Returns a collection object representing the new collection.</p>',
+					),
 				),
 			]),
 		];
@@ -1482,9 +1446,9 @@ public function provideReturnTagsData(): Iterator
 							12,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1503,9 +1467,9 @@ public function provideReturnTagsData(): Iterator
 							12,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1524,9 +1488,9 @@ public function provideReturnTagsData(): Iterator
 							18,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1545,9 +1509,9 @@ public function provideReturnTagsData(): Iterator
 							18,
 							Lexer::TOKEN_OTHER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1566,9 +1530,9 @@ public function provideReturnTagsData(): Iterator
 							18,
 							Lexer::TOKEN_OTHER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1587,9 +1551,9 @@ public function provideReturnTagsData(): Iterator
 							24,
 							Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1610,11 +1574,11 @@ public function provideReturnTagsData(): Iterator
 								],
 								[
 									GenericTypeNode::VARIANCE_INVARIANT,
-								]
+								],
 							),
 						]),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1627,8 +1591,8 @@ public function provideReturnTagsData(): Iterator
 					'@return',
 					new ReturnTagValueNode(
 						new ConstTypeNode(new ConstFetchNode('self', '*')),
-						'example description'
-					)
+						'example description',
+					),
 				),
 			]),
 		];
@@ -1645,10 +1609,10 @@ public function provideReturnTagsData(): Iterator
 							new IdentifierTypeNode('Bar'),
 							new IdentifierTypeNode('never'),
 							new IdentifierTypeNode('int'),
-							false
+							false,
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1665,10 +1629,10 @@ public function provideReturnTagsData(): Iterator
 							new IdentifierTypeNode('Bar'),
 							new IdentifierTypeNode('never'),
 							new IdentifierTypeNode('int'),
-							true
+							true,
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1691,12 +1655,12 @@ public function provideReturnTagsData(): Iterator
 								new ConstTypeNode(new ConstFetchNode('self', 'TYPE_INT')),
 								new IdentifierTypeNode('int'),
 								new IdentifierTypeNode('bool'),
-								false
+								false,
 							),
-							false
+							false,
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1709,8 +1673,8 @@ public function provideReturnTagsData(): Iterator
 					'@return',
 					new ReturnTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						'is not Bar ? never : int'
-					)
+						'is not Bar ? never : int',
+					),
 				),
 			]),
 		];
@@ -1727,10 +1691,10 @@ public function provideReturnTagsData(): Iterator
 							new IdentifierTypeNode('Bar'),
 							new IdentifierTypeNode('never'),
 							new IdentifierTypeNode('int'),
-							false
+							false,
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1747,10 +1711,10 @@ public function provideReturnTagsData(): Iterator
 							new IdentifierTypeNode('Bar'),
 							new IdentifierTypeNode('never'),
 							new IdentifierTypeNode('int'),
-							true
+							true,
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1773,12 +1737,12 @@ public function provideReturnTagsData(): Iterator
 								new ConstTypeNode(new ConstFetchNode('self', 'TYPE_INT')),
 								new IdentifierTypeNode('int'),
 								new IdentifierTypeNode('bool'),
-								false
+								false,
 							),
-							false
+							false,
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1797,9 +1761,9 @@ public function provideReturnTagsData(): Iterator
 							12,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1819,20 +1783,21 @@ public function provideReturnTagsData(): Iterator
 									false,
 									true,
 									'$u',
-									false
+									false,
 								),
 								new CallableTypeParameterNode(
 									new IdentifierTypeNode('string'),
 									false,
 									false,
 									'',
-									false
+									false,
 								),
 							],
-							new IdentifierTypeNode('string')
+							new IdentifierTypeNode('string'),
+							[],
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1840,15 +1805,6 @@ public function provideReturnTagsData(): Iterator
 		yield [
 			'invalid variadic callable',
 			'/** @return \Closure(...int, string): string */',
-			new PhpDocNode([
-				new PhpDocTagNode(
-					'@return',
-					new ReturnTagValueNode(
-						new IdentifierTypeNode('\Closure'),
-						'(...int, string): string'
-					)
-				),
-			]),
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@return',
@@ -1860,61 +1816,124 @@ public function provideReturnTagsData(): Iterator
 							20,
 							Lexer::TOKEN_HORIZONTAL_WS,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
-	}
 
-
-	public function provideThrowsTagsData(): Iterator
-	{
 		yield [
-			'OK without description',
-			'/** @throws Foo */',
+			'valid CallableTypeNode without space after "callable"',
+			'/** @return callable(int, string): void */',
 			new PhpDocNode([
 				new PhpDocTagNode(
-					'@throws',
-					new ThrowsTagValueNode(
-						new IdentifierTypeNode('Foo'),
-						''
-					)
+					'@return',
+					new ReturnTagValueNode(
+						new CallableTypeNode(new IdentifierTypeNode('callable'), [
+							new CallableTypeParameterNode(new IdentifierTypeNode('int'), false, false, '', false),
+							new CallableTypeParameterNode(new IdentifierTypeNode('string'), false, false, '', false),
+						], new IdentifierTypeNode('void'), []),
+						'',
+					),
 				),
 			]),
 		];
 
 		yield [
-			'OK with description',
-			'/** @throws Foo optional description */',
+			'valid CallableTypeNode with space after "callable"',
+			'/** @return callable (int, string): void */',
 			new PhpDocNode([
 				new PhpDocTagNode(
-					'@throws',
-					new ThrowsTagValueNode(
-						new IdentifierTypeNode('Foo'),
-						'optional description'
-					)
+					'@return',
+					new ReturnTagValueNode(
+						new CallableTypeNode(new IdentifierTypeNode('callable'), [
+							new CallableTypeParameterNode(new IdentifierTypeNode('int'), false, false, '', false),
+							new CallableTypeParameterNode(new IdentifierTypeNode('string'), false, false, '', false),
+						], new IdentifierTypeNode('void'), []),
+						'',
+					),
 				),
 			]),
 		];
 
 		yield [
-			'OK with description that starts with TOKEN_OPEN_SQUARE_BRACKET',
-			'/** @throws Foo [Bar] */',
+			'valid IdentifierTypeNode with space after "callable" turns the rest to description',
+			'/** @return callable (int, string) */',
 			new PhpDocNode([
 				new PhpDocTagNode(
-					'@throws',
-					new ThrowsTagValueNode(
-						new IdentifierTypeNode('Foo'),
-						'[Bar]'
-					)
+					'@return',
+					new ReturnTagValueNode(new IdentifierTypeNode('callable'), '(int, string)'),
 				),
 			]),
 		];
 
 		yield [
-			'invalid without type and description',
+			'invalid CallableTypeNode without space after "callable"',
+			'/** @return callable(int, string) */',
+			new PhpDocNode([
+				new PhpDocTagNode(
+					'@return',
+					new InvalidTagValueNode('callable(int, string)', new ParserException(
+						'(',
+						4,
+						20,
+						27,
+						null,
+						1,
+					)),
+				),
+			]),
+		];
+	}
+
+
+	public function provideThrowsTagsData(): Iterator
+	{
+		yield [
+			'OK without description',
+			'/** @throws Foo */',
+			new PhpDocNode([
+				new PhpDocTagNode(
+					'@throws',
+					new ThrowsTagValueNode(
+						new IdentifierTypeNode('Foo'),
+						'',
+					),
+				),
+			]),
+		];
+
+		yield [
+			'OK with description',
+			'/** @throws Foo optional description */',
+			new PhpDocNode([
+				new PhpDocTagNode(
+					'@throws',
+					new ThrowsTagValueNode(
+						new IdentifierTypeNode('Foo'),
+						'optional description',
+					),
+				),
+			]),
+		];
+
+		yield [
+			'OK with description that starts with TOKEN_OPEN_SQUARE_BRACKET',
+			'/** @throws Foo [Bar] */',
+			new PhpDocNode([
+				new PhpDocTagNode(
+					'@throws',
+					new ThrowsTagValueNode(
+						new IdentifierTypeNode('Foo'),
+						'[Bar]',
+					),
+				),
+			]),
+		];
+
+		yield [
+			'invalid without type and description',
 			'/** @throws */',
 			new PhpDocNode([
 				new PhpDocTagNode(
@@ -1927,9 +1946,9 @@ public function provideThrowsTagsData(): Iterator
 							12,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1948,9 +1967,9 @@ public function provideThrowsTagsData(): Iterator
 							18,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -1966,8 +1985,8 @@ public function provideMixinTagsData(): Iterator
 					'@mixin',
 					new MixinTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -1980,8 +1999,8 @@ public function provideMixinTagsData(): Iterator
 					'@mixin',
 					new MixinTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						'optional description'
-					)
+						'optional description',
+					),
 				),
 			]),
 		];
@@ -1994,8 +2013,8 @@ public function provideMixinTagsData(): Iterator
 					'@mixin',
 					new MixinTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						'[Bar]'
-					)
+						'[Bar]',
+					),
 				),
 			]),
 		];
@@ -2014,9 +2033,9 @@ public function provideMixinTagsData(): Iterator
 							11,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -2035,9 +2054,9 @@ public function provideMixinTagsData(): Iterator
 							17,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -2054,8 +2073,8 @@ public function provideMixinTagsData(): Iterator
 						], [
 							GenericTypeNode::VARIANCE_INVARIANT,
 						]),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -2071,8 +2090,8 @@ public function provideRequireExtendsTagsData(): Iterator
 					'@phpstan-require-extends',
 					new RequireExtendsTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -2085,8 +2104,8 @@ public function provideRequireExtendsTagsData(): Iterator
 					'@phpstan-require-extends',
 					new RequireExtendsTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						'optional description'
-					)
+						'optional description',
+					),
 				),
 			]),
 		];
@@ -2099,8 +2118,8 @@ public function provideRequireExtendsTagsData(): Iterator
 					'@psalm-require-extends',
 					new RequireExtendsTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						'optional description'
-					)
+						'optional description',
+					),
 				),
 			]),
 		];
@@ -2119,9 +2138,9 @@ public function provideRequireExtendsTagsData(): Iterator
 							29,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -2137,8 +2156,8 @@ public function provideRequireImplementsTagsData(): Iterator
 					'@phpstan-require-implements',
 					new RequireImplementsTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -2151,8 +2170,8 @@ public function provideRequireImplementsTagsData(): Iterator
 					'@phpstan-require-implements',
 					new RequireImplementsTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						'optional description'
-					)
+						'optional description',
+					),
 				),
 			]),
 		];
@@ -2165,8 +2184,8 @@ public function provideRequireImplementsTagsData(): Iterator
 					'@psalm-require-implements',
 					new RequireImplementsTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						'optional description'
-					)
+						'optional description',
+					),
 				),
 			]),
 		];
@@ -2185,9 +2204,84 @@ public function provideRequireImplementsTagsData(): Iterator
 							32,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
+				),
+			]),
+		];
+	}
+
+	public function provideSealedTagsData(): Iterator
+	{
+		yield [
+			'OK without description',
+			'/** @phpstan-sealed Foo|Bar */',
+			new PhpDocNode([
+				new PhpDocTagNode(
+					'@phpstan-sealed',
+					new SealedTagValueNode(
+						new UnionTypeNode([
+							new IdentifierTypeNode('Foo'),
+							new IdentifierTypeNode('Bar'),
+						]),
+						'',
+					),
+				),
+			]),
+		];
+
+		yield [
+			'OK with description',
+			'/** @phpstan-sealed Foo|Bar optional description */',
+			new PhpDocNode([
+				new PhpDocTagNode(
+					'@phpstan-sealed',
+					new SealedTagValueNode(
+						new UnionTypeNode([
+							new IdentifierTypeNode('Foo'),
+							new IdentifierTypeNode('Bar'),
+						]),
+						'optional description',
+					),
+				),
+			]),
+		];
+
+		yield [
+			'OK with psalm-prefix description',
+			'/** @psalm-inheritors Foo|Bar optional description */',
+			new PhpDocNode([
+				new PhpDocTagNode(
+					'@psalm-inheritors',
+					new SealedTagValueNode(
+						new UnionTypeNode([
+							new IdentifierTypeNode('Foo'),
+							new IdentifierTypeNode('Bar'),
+						]),
+						'optional description',
+					),
+				),
+			]),
+		];
+
+		yield [
+			'invalid without type and description',
+			'/** @phpstan-sealed */',
+			new PhpDocNode([
+				new PhpDocTagNode(
+					'@phpstan-sealed',
+					new InvalidTagValueNode(
+						'',
+						new ParserException(
+							'*/',
+							Lexer::TOKEN_CLOSE_PHPDOC,
+							20,
+							Lexer::TOKEN_IDENTIFIER,
+							null,
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -2201,7 +2295,7 @@ public function provideDeprecatedTagsData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@deprecated',
-					new DeprecatedTagValueNode('')
+					new DeprecatedTagValueNode(''),
 				),
 			]),
 		];
@@ -2212,7 +2306,7 @@ public function provideDeprecatedTagsData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@deprecated',
-					new DeprecatedTagValueNode('text string')
+					new DeprecatedTagValueNode('text string'),
 				),
 			]),
 		];
@@ -2225,12 +2319,12 @@ public function provideDeprecatedTagsData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@deprecated',
-					new DeprecatedTagValueNode('text first')
+					new DeprecatedTagValueNode('text first'),
 				),
 				new PhpDocTextNode(''),
 				new PhpDocTagNode(
 					'@deprecated',
-					new DeprecatedTagValueNode('text second')
+					new DeprecatedTagValueNode('text second'),
 				),
 			]),
 		];
@@ -2243,11 +2337,11 @@ public function provideDeprecatedTagsData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@deprecated',
-					new DeprecatedTagValueNode('text first')
+					new DeprecatedTagValueNode('text first'),
 				),
 				new PhpDocTagNode(
 					'@deprecated',
-					new DeprecatedTagValueNode('text second')
+					new DeprecatedTagValueNode('text second'),
 				),
 			]),
 		];
@@ -2264,7 +2358,7 @@ public function provideDeprecatedTagsData(): Iterator
 					new DeprecatedTagValueNode('in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In
   Drupal 9 there will be no way to set the status and in Drupal 8 this
   ability has been removed because mb_*() functions are supplied using
-  Symfony\'s polyfill.')
+  Symfony\'s polyfill.'),
 				),
 			]),
 		];
@@ -2285,7 +2379,7 @@ public function provideDeprecatedTagsData(): Iterator
 				new PhpDocTextNode(''),
 				new PhpDocTagNode(
 					'@author',
-					new GenericTagValueNode('Foo Baz <foo@baz.com>')
+					new GenericTagValueNode('Foo Baz <foo@baz.com>'),
 				),
 				new PhpDocTextNode(''),
 				new PhpDocTagNode(
@@ -2293,7 +2387,7 @@ public function provideDeprecatedTagsData(): Iterator
 					new DeprecatedTagValueNode('in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In
   Drupal 9 there will be no way to set the status and in Drupal 8 this
   ability has been removed because mb_*() functions are supplied using
-  Symfony\'s polyfill.')
+  Symfony\'s polyfill.'),
 				),
 			]),
 		];
@@ -2312,8 +2406,9 @@ public function provideMethodTagsData(): Iterator
 						null,
 						'foo',
 						[],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2329,8 +2424,9 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						'foo',
 						[],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2346,8 +2442,9 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('static'),
 						'foo',
 						[],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2363,8 +2460,9 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						'foo',
 						[],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2380,8 +2478,9 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('static'),
 						'foo',
 						[],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2397,8 +2496,9 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						'foo',
 						[],
-						'optional description'
-					)
+						'optional description',
+						[],
+					),
 				),
 			]),
 		];
@@ -2419,11 +2519,12 @@ public function provideMethodTagsData(): Iterator
 								false,
 								false,
 								'$a',
-								null
+								null,
 							),
 						],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2444,11 +2545,12 @@ public function provideMethodTagsData(): Iterator
 								false,
 								false,
 								'$a',
-								null
+								null,
 							),
 						],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2469,11 +2571,12 @@ public function provideMethodTagsData(): Iterator
 								true,
 								false,
 								'$a',
-								null
+								null,
 							),
 						],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2494,11 +2597,12 @@ public function provideMethodTagsData(): Iterator
 								false,
 								true,
 								'$a',
-								null
+								null,
 							),
 						],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2519,11 +2623,12 @@ public function provideMethodTagsData(): Iterator
 								true,
 								true,
 								'$a',
-								null
+								null,
 							),
 						],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2544,11 +2649,12 @@ public function provideMethodTagsData(): Iterator
 								false,
 								false,
 								'$a',
-								new ConstExprIntegerNode('123')
+								new ConstExprIntegerNode('123'),
 							),
 						],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2569,11 +2675,12 @@ public function provideMethodTagsData(): Iterator
 								true,
 								true,
 								'$a',
-								new ConstExprIntegerNode('123')
+								new ConstExprIntegerNode('123'),
 							),
 						],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2594,25 +2701,26 @@ public function provideMethodTagsData(): Iterator
 								false,
 								false,
 								'$a',
-								null
+								null,
 							),
 							new MethodTagValueParameterNode(
 								null,
 								false,
 								false,
 								'$b',
-								null
+								null,
 							),
 							new MethodTagValueParameterNode(
 								null,
 								false,
 								false,
 								'$c',
-								null
+								null,
 							),
 						],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2631,9 +2739,9 @@ public function provideMethodTagsData(): Iterator
 							16,
 							Lexer::TOKEN_OPEN_PARENTHESES,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -2652,9 +2760,9 @@ public function provideMethodTagsData(): Iterator
 							23,
 							Lexer::TOKEN_OPEN_PARENTHESES,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -2673,9 +2781,9 @@ public function provideMethodTagsData(): Iterator
 							17,
 							Lexer::TOKEN_VARIABLE,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -2701,7 +2809,7 @@ public function provideMethodTagsData(): Iterator
 									[
 										GenericTypeNode::VARIANCE_INVARIANT,
 										GenericTypeNode::VARIANCE_INVARIANT,
-									]
+									],
 								),
 								false,
 								false,
@@ -2709,13 +2817,13 @@ public function provideMethodTagsData(): Iterator
 								new ConstExprArrayNode([
 									new ConstExprArrayItemNode(
 										null,
-										new ConstExprStringNode('\'a\'')
+										new ConstExprStringNode('a', ConstExprStringNode::SINGLE_QUOTED),
 									),
 									new ConstExprArrayItemNode(
 										null,
-										new ConstExprStringNode('\'b\'')
+										new ConstExprStringNode('b', ConstExprStringNode::SINGLE_QUOTED),
 									),
-								])
+								]),
 							),
 						],
 						'',
@@ -2724,10 +2832,10 @@ public function provideMethodTagsData(): Iterator
 								'T',
 								null,
 								'',
-								new IdentifierTypeNode('string')
+								new IdentifierTypeNode('string'),
 							),
-						]
-					)
+						],
+					),
 				),
 			]),
 		];
@@ -2748,21 +2856,21 @@ public function provideMethodTagsData(): Iterator
 								false,
 								false,
 								'$t1',
-								null
+								null,
 							),
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('T2'),
 								false,
 								false,
 								'$t2',
-								null
+								null,
 							),
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('T3'),
 								false,
 								false,
 								'$t3',
-								null
+								null,
 							),
 						],
 						'',
@@ -2770,8 +2878,8 @@ public function provideMethodTagsData(): Iterator
 							new TemplateTagValueNode('T1', null, ''),
 							new TemplateTagValueNode('T2', new IdentifierTypeNode('Bar'), ''),
 							new TemplateTagValueNode('T3', new IdentifierTypeNode('Baz'), ''),
-						]
-					)
+						],
+					),
 				),
 			]),
 		];
@@ -2790,8 +2898,9 @@ public function provideMethodTagsData(): Iterator
 						]),
 						'foo',
 						[],
-						''
-					)
+						'',
+						[],
+					),
 				),
 			]),
 		];
@@ -2811,7 +2920,7 @@ public function provideSingleLinePhpDocData(): Iterator
 			'/** /**/',
 			new PhpDocNode([
 				new PhpDocTextNode(
-					'/*'
+					'/*',
 				),
 			]),
 		];
@@ -2821,7 +2930,7 @@ public function provideSingleLinePhpDocData(): Iterator
 			'/** text */',
 			new PhpDocNode([
 				new PhpDocTextNode(
-					'text'
+					'text',
 				),
 			]),
 		];
@@ -2831,7 +2940,7 @@ public function provideSingleLinePhpDocData(): Iterator
 			'/** text @foo bar */',
 			new PhpDocNode([
 				new PhpDocTextNode(
-					'text @foo bar'
+					'text @foo bar',
 				),
 			]),
 		];
@@ -2842,7 +2951,7 @@ public function provideSingleLinePhpDocData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@foo',
-					new GenericTagValueNode('')
+					new GenericTagValueNode(''),
 				),
 			]),
 		];
@@ -2853,7 +2962,7 @@ public function provideSingleLinePhpDocData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@foo',
-					new GenericTagValueNode('lorem ipsum')
+					new GenericTagValueNode('lorem ipsum'),
 				),
 			]),
 		];
@@ -2864,11 +2973,11 @@ public function provideSingleLinePhpDocData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@foo',
-					new GenericTagValueNode('lorem')
+					new GenericTagValueNode('lorem'),
 				),
 				new PhpDocTagNode(
 					'@bar',
-					new GenericTagValueNode('ipsum')
+					new GenericTagValueNode('ipsum'),
 				),
 			]),
 		];
@@ -2880,8 +2989,8 @@ public function provideSingleLinePhpDocData(): Iterator
 				new PhpDocTagNode(
 					'@varFoo',
 					new GenericTagValueNode(
-						'$foo'
-					)
+						'$foo',
+					),
 				),
 			]),
 		];
@@ -2896,11 +3005,9 @@ public function provideSingleLinePhpDocData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@example',
-					new GenericTagValueNode('')
-				),
-				new PhpDocTextNode(
-					'entity_managers:' . PHP_EOL .
-					'    default:'
+					new GenericTagValueNode(PHP_EOL .
+						'  entity_managers:' . PHP_EOL .
+						'    default:'),
 				),
 			]),
 		];
@@ -2917,11 +3024,12 @@ public function provideSingleLinePhpDocData(): Iterator
 							[
 								new CallableTypeParameterNode(new IdentifierTypeNode('int'), false, false, '', false),
 							],
-							new IdentifierTypeNode('void')
+							new IdentifierTypeNode('void'),
+							[],
 						),
 						'',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -2935,8 +3043,8 @@ public function provideSingleLinePhpDocData(): Iterator
 					new VarTagValueNode(
 						new IdentifierTypeNode('callable'),
 						'',
-						'(int)'
-					)
+						'(int)',
+					),
 				),
 			]),
 		];
@@ -2944,16 +3052,6 @@ public function provideSingleLinePhpDocData(): Iterator
 		yield [
 			'callable with incomplete signature without return type',
 			'/** @var callable(int) */',
-			new PhpDocNode([
-				new PhpDocTagNode(
-					'@var',
-					new VarTagValueNode(
-						new IdentifierTypeNode('callable'),
-						'',
-						'(int)'
-					)
-				),
-			]),
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@var',
@@ -2965,9 +3063,9 @@ public function provideSingleLinePhpDocData(): Iterator
 							17,
 							Lexer::TOKEN_HORIZONTAL_WS,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -2992,8 +3090,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('Foo'),
 							false,
 							'$foo',
-							'1st multi world description'
-						)
+							'1st multi world description',
+							false,
+						),
 					),
 					new PhpDocTagNode(
 						'@param',
@@ -3001,8 +3100,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('Bar'),
 							false,
 							'$bar',
-							'2nd multi world description'
-						)
+							'2nd multi world description',
+							false,
+						),
 					),
 				]),
 			],
@@ -3021,8 +3121,9 @@ public function provideMultiLinePhpDocData(): iterable
 							false,
 							'$foo',
 							'1st multi world description
-some text in the middle'
-						)
+some text in the middle',
+							false,
+						),
 					),
 					new PhpDocTagNode(
 						'@param',
@@ -3030,8 +3131,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('Bar'),
 							false,
 							'$bar',
-							'2nd multi world description'
-						)
+							'2nd multi world description',
+							false,
+						),
 					),
 				]),
 			],
@@ -3060,26 +3162,29 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('Foo'),
 							false,
 							'$foo',
-							'1st multi world description with empty lines'
-						)
+							'1st multi world description with empty lines
+
+
+some text in the middle',
+							false,
+						),
 					),
 					new PhpDocTextNode(''),
 					new PhpDocTextNode(''),
-					new PhpDocTextNode('some text in the middle'),
-					new PhpDocTextNode(''),
-					new PhpDocTextNode(''),
 					new PhpDocTagNode(
 						'@param',
 						new ParamTagValueNode(
 							new IdentifierTypeNode('Bar'),
 							false,
 							'$bar',
-							'2nd multi world description with empty lines'
-						)
+							'2nd multi world description with empty lines
+
+
+test',
+							false,
+						),
 					),
-					new PhpDocTextNode(''),
-					new PhpDocTextNode(''),
-					new PhpDocTextNode('test'),
+
 				]),
 			],
 			[
@@ -3107,8 +3212,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('int'),
 							false,
 							'$foo',
-							'@param string $bar'
-						)
+							'@param string $bar',
+							false,
+						),
 					),
 				]),
 			],
@@ -3166,18 +3272,19 @@ public function provideMultiLinePhpDocData(): iterable
 									false,
 									false,
 									'$a',
-									null
+									null,
 								),
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
 									false,
 									false,
 									'$b',
-									null
+									null,
 								),
 							],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3191,18 +3298,19 @@ public function provideMultiLinePhpDocData(): iterable
 									false,
 									false,
 									'$a',
-									null
+									null,
 								),
 								new MethodTagValueParameterNode(
 									null,
 									false,
 									false,
 									'$b',
-									null
+									null,
 								),
 							],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3214,8 +3322,9 @@ public function provideMultiLinePhpDocData(): iterable
 							]),
 							'getFooOrBar',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3224,8 +3333,9 @@ public function provideMultiLinePhpDocData(): iterable
 							null,
 							'methodWithNoReturnType',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3239,18 +3349,19 @@ public function provideMultiLinePhpDocData(): iterable
 									false,
 									false,
 									'$a',
-									null
+									null,
 								),
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
 									false,
 									false,
 									'$b',
-									null
+									null,
 								),
 							],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3264,18 +3375,19 @@ public function provideMultiLinePhpDocData(): iterable
 									false,
 									false,
 									'$a',
-									null
+									null,
 								),
 								new MethodTagValueParameterNode(
 									null,
 									false,
 									false,
 									'$b',
-									null
+									null,
 								),
 							],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3287,8 +3399,9 @@ public function provideMultiLinePhpDocData(): iterable
 							]),
 							'getFooOrBarStatically',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3297,8 +3410,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('static'),
 							'methodWithNoReturnTypeStatically',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3312,18 +3426,19 @@ public function provideMultiLinePhpDocData(): iterable
 									false,
 									false,
 									'$a',
-									null
+									null,
 								),
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
 									false,
 									false,
 									'$b',
-									null
+									null,
 								),
 							],
-							'Get an integer with a description.'
-						)
+							'Get an integer with a description.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3337,18 +3452,19 @@ public function provideMultiLinePhpDocData(): iterable
 									false,
 									false,
 									'$a',
-									null
+									null,
 								),
 								new MethodTagValueParameterNode(
 									null,
 									false,
 									false,
 									'$b',
-									null
+									null,
 								),
 							],
-							'Do something with a description.'
-						)
+							'Do something with a description.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3360,8 +3476,9 @@ public function provideMultiLinePhpDocData(): iterable
 							]),
 							'getFooOrBarWithDescription',
 							[],
-							'Get a Foo or a Bar with a description.'
-						)
+							'Get a Foo or a Bar with a description.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3370,8 +3487,9 @@ public function provideMultiLinePhpDocData(): iterable
 							null,
 							'methodWithNoReturnTypeWithDescription',
 							[],
-							'Do something with a description but what, who knows!'
-						)
+							'Do something with a description but what, who knows!',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3385,18 +3503,19 @@ public function provideMultiLinePhpDocData(): iterable
 									false,
 									false,
 									'$a',
-									null
+									null,
 								),
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
 									false,
 									false,
 									'$b',
-									null
+									null,
 								),
 							],
-							'Get an integer with a description statically.'
-						)
+							'Get an integer with a description statically.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3410,18 +3529,19 @@ public function provideMultiLinePhpDocData(): iterable
 									false,
 									false,
 									'$a',
-									null
+									null,
 								),
 								new MethodTagValueParameterNode(
 									null,
 									false,
 									false,
 									'$b',
-									null
+									null,
 								),
 							],
-							'Do something with a description statically.'
-						)
+							'Do something with a description statically.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3433,8 +3553,9 @@ public function provideMultiLinePhpDocData(): iterable
 							]),
 							'getFooOrBarStaticallyWithDescription',
 							[],
-							'Get a Foo or a Bar with a description statically.'
-						)
+							'Get a Foo or a Bar with a description statically.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3443,8 +3564,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('static'),
 							'methodWithNoReturnTypeStaticallyWithDescription',
 							[],
-							'Do something with a description statically, but what, who knows!'
-						)
+							'Do something with a description statically, but what, who knows!',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3453,8 +3575,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('bool'),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClass',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3463,8 +3586,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('string'),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescription',
 							[],
-							'A Description.'
-						)
+							'A Description.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3473,8 +3597,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('int'),
 							'getIntegerNoParams',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3483,8 +3608,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('void'),
 							'doSomethingNoParams',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3496,8 +3622,9 @@ public function provideMultiLinePhpDocData(): iterable
 							]),
 							'getFooOrBarNoParams',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3506,8 +3633,9 @@ public function provideMultiLinePhpDocData(): iterable
 							null,
 							'methodWithNoReturnTypeNoParams',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3516,8 +3644,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('int'),
 							'getIntegerStaticallyNoParams',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3526,8 +3655,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('void'),
 							'doSomethingStaticallyNoParams',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3539,8 +3669,9 @@ public function provideMultiLinePhpDocData(): iterable
 							]),
 							'getFooOrBarStaticallyNoParams',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3549,8 +3680,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('static'),
 							'methodWithNoReturnTypeStaticallyNoParams',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3559,8 +3691,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('int'),
 							'getIntegerWithDescriptionNoParams',
 							[],
-							'Get an integer with a description.'
-						)
+							'Get an integer with a description.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3569,8 +3702,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('void'),
 							'doSomethingWithDescriptionNoParams',
 							[],
-							'Do something with a description.'
-						)
+							'Do something with a description.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3582,8 +3716,9 @@ public function provideMultiLinePhpDocData(): iterable
 							]),
 							'getFooOrBarWithDescriptionNoParams',
 							[],
-							'Get a Foo or a Bar with a description.'
-						)
+							'Get a Foo or a Bar with a description.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3592,8 +3727,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('int'),
 							'getIntegerStaticallyWithDescriptionNoParams',
 							[],
-							'Get an integer with a description statically.'
-						)
+							'Get an integer with a description statically.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3602,8 +3738,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('void'),
 							'doSomethingStaticallyWithDescriptionNoParams',
 							[],
-							'Do something with a description statically.'
-						)
+							'Do something with a description statically.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3615,8 +3752,9 @@ public function provideMultiLinePhpDocData(): iterable
 							]),
 							'getFooOrBarStaticallyWithDescriptionNoParams',
 							[],
-							'Get a Foo or a Bar with a description statically.'
-						)
+							'Get a Foo or a Bar with a description statically.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3628,8 +3766,9 @@ public function provideMultiLinePhpDocData(): iterable
 							]),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClassNoParams',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3641,8 +3780,9 @@ public function provideMultiLinePhpDocData(): iterable
 							]),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams',
 							[],
-							'A Description.'
-						)
+							'A Description.',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3656,11 +3796,12 @@ public function provideMultiLinePhpDocData(): iterable
 									false,
 									false,
 									'$args',
-									null
+									null,
 								),
 							],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3674,18 +3815,19 @@ public function provideMultiLinePhpDocData(): iterable
 									true,
 									true,
 									'$angle',
-									new ConstExprArrayNode([])
+									new ConstExprArrayNode([]),
 								),
 								new MethodTagValueParameterNode(
 									null,
 									false,
 									false,
 									'$backgroundColor',
-									null
+									null,
 								),
 							],
-							''
-						)
+							'',
+							[],
+						),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3694,8 +3836,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('Foo'),
 							'overridenMethod',
 							[],
-							''
-						)
+							'',
+							[],
+						),
 					),
 				]),
 			],
@@ -3709,11 +3852,11 @@ public function provideMultiLinePhpDocData(): iterable
 				new PhpDocNode([
 					new PhpDocTagNode(
 						'@template',
-						new TemplateTagValueNode('TKey', new IdentifierTypeNode('array-key'), '')
+						new TemplateTagValueNode('TKey', new IdentifierTypeNode('array-key'), ''),
 					),
 					new PhpDocTagNode(
 						'@template',
-						new TemplateTagValueNode('TValue', null, '')
+						new TemplateTagValueNode('TValue', null, ''),
 					),
 					new PhpDocTagNode(
 						'@method',
@@ -3730,11 +3873,12 @@ public function provideMultiLinePhpDocData(): iterable
 									false,
 									false,
 									'$v',
-									null
+									null,
 								),
 							],
-							'find index of $v'
-						)
+							'find index of $v',
+							[],
+						),
 					),
 				]),
 			],
@@ -3756,11 +3900,11 @@ public function provideMultiLinePhpDocData(): iterable
 				new PhpDocNode([
 					new PhpDocTagNode(
 						'@template',
-						new TemplateTagValueNode('TRandKey', new IdentifierTypeNode('array-key'), '')
+						new TemplateTagValueNode('TRandKey', new IdentifierTypeNode('array-key'), ''),
 					),
 					new PhpDocTagNode(
 						'@template',
-						new TemplateTagValueNode('TRandVal', null, '')
+						new TemplateTagValueNode('TRandVal', null, ''),
 					),
 					new PhpDocTagNode(
 						'@template',
@@ -3776,7 +3920,7 @@ public function provideMultiLinePhpDocData(): iterable
 									[
 										GenericTypeNode::VARIANCE_INVARIANT,
 										GenericTypeNode::VARIANCE_INVARIANT,
-									]
+									],
 								),
 								new GenericTypeNode(
 									new IdentifierTypeNode('XIterator'),
@@ -3787,7 +3931,7 @@ public function provideMultiLinePhpDocData(): iterable
 									[
 										GenericTypeNode::VARIANCE_INVARIANT,
 										GenericTypeNode::VARIANCE_INVARIANT,
-									]
+									],
 								),
 								new GenericTypeNode(
 									new IdentifierTypeNode('Traversable'),
@@ -3798,11 +3942,11 @@ public function provideMultiLinePhpDocData(): iterable
 									[
 										GenericTypeNode::VARIANCE_INVARIANT,
 										GenericTypeNode::VARIANCE_INVARIANT,
-									]
+									],
 								),
 							]),
-							''
-						)
+							'',
+						),
 					),
 					new PhpDocTextNode(''),
 					new PhpDocTagNode(
@@ -3811,8 +3955,9 @@ public function provideMultiLinePhpDocData(): iterable
 							new IdentifierTypeNode('TRandList'),
 							false,
 							'$list',
-							''
-						)
+							'',
+							false,
+						),
 					),
 					new PhpDocTextNode(''),
 					new PhpDocTagNode(
@@ -3830,7 +3975,7 @@ public function provideMultiLinePhpDocData(): iterable
 									[
 										GenericTypeNode::VARIANCE_INVARIANT,
 										GenericTypeNode::VARIANCE_INVARIANT,
-									]
+									],
 								),
 								new ConditionalTypeNode(
 									new IdentifierTypeNode('TRandList'),
@@ -3844,7 +3989,7 @@ public function provideMultiLinePhpDocData(): iterable
 										[
 											GenericTypeNode::VARIANCE_INVARIANT,
 											GenericTypeNode::VARIANCE_INVARIANT,
-										]
+										],
 									),
 									new UnionTypeNode([
 										new GenericTypeNode(
@@ -3856,7 +4001,7 @@ public function provideMultiLinePhpDocData(): iterable
 											[
 												GenericTypeNode::VARIANCE_INVARIANT,
 												GenericTypeNode::VARIANCE_INVARIANT,
-											]
+											],
 										),
 										new GenericTypeNode(
 											new IdentifierTypeNode('LimitIterator'),
@@ -3867,15 +4012,15 @@ public function provideMultiLinePhpDocData(): iterable
 											[
 												GenericTypeNode::VARIANCE_INVARIANT,
 												GenericTypeNode::VARIANCE_INVARIANT,
-											]
+											],
 										),
 									]),
-									false
+									false,
 								),
-								false
+								false,
 							),
-							''
-						)
+							'',
+						),
 					),
 				]),
 			],
@@ -3891,7 +4036,7 @@ public function provideMultiLinePhpDocData(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', '')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', '', false)),
 				new PhpDocTextNode(''),
 				new PhpDocTextNode(''),
 			]),
@@ -3908,10 +4053,19 @@ public function provideMultiLinePhpDocData(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', '')),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode('test'),
+				new PhpDocTagNode(
+					'@param',
+					new ParamTagValueNode(
+						new IdentifierTypeNode('int'),
+						false,
+						'$a',
+						PHP_EOL
+						. PHP_EOL
+						. PHP_EOL
+						. 'test',
+						false,
+					),
+				),
 			]),
 		];
 
@@ -3929,31 +4083,423 @@ public function provideMultiLinePhpDocData(): iterable
 			' *' . PHP_EOL .
 			' */',
 			new PhpDocNode([
-				new PhpDocTextNode(''),
 				new PhpDocTextNode(
+					PHP_EOL .
 					'MultiLine' . PHP_EOL .
-					'description'
+					'description',
 				),
 				new PhpDocTagNode('@param', new ParamTagValueNode(
 					new IdentifierTypeNode('bool'),
 					false,
 					'$a',
 					'',
-					false
+					false,
 				)),
 				new PhpDocTextNode(''),
 				new PhpDocTagNode('@return', new ReturnTagValueNode(
 					new IdentifierTypeNode('void'),
-					''
+					'',
 				)),
 				new PhpDocTextNode(''),
 				new PhpDocTagNode('@throws', new ThrowsTagValueNode(
 					new IdentifierTypeNode('\Exception'),
-					''
+					'',
 				)),
 				new PhpDocTextNode(''),
 			]),
 		];
+
+		yield [
+			'Multiline PHPDoc with new line across generic type declaration',
+			'/**' . PHP_EOL .
+			' * @param array<string, array{' . PHP_EOL .
+			' *     foo: int,' . PHP_EOL .
+			' *     bar?: array<string,' . PHP_EOL .
+			' *         array{foo1: int, bar1?: true}' . PHP_EOL .
+			' *         | array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
+			' *     >,' . PHP_EOL .
+			' * }> $a' . PHP_EOL .
+			' */',
+			new PhpDocNode([
+				new PhpDocTagNode('@param', new ParamTagValueNode(
+					new GenericTypeNode(
+						new IdentifierTypeNode('array'),
+						[
+							new IdentifierTypeNode('string'),
+							ArrayShapeNode::createSealed([
+								new ArrayShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
+								new ArrayShapeItemNode(new IdentifierTypeNode('bar'), true, new GenericTypeNode(
+									new IdentifierTypeNode('array'),
+									[
+										new IdentifierTypeNode('string'),
+										new UnionTypeNode([
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
+											]),
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('true')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
+											]),
+										]),
+									],
+									[
+										GenericTypeNode::VARIANCE_INVARIANT,
+										GenericTypeNode::VARIANCE_INVARIANT,
+									],
+								)),
+							]),
+						],
+						[
+							GenericTypeNode::VARIANCE_INVARIANT,
+							GenericTypeNode::VARIANCE_INVARIANT,
+						],
+					),
+					false,
+					'$a',
+					'',
+					false,
+				)),
+			]),
+		];
+
+		yield [
+			'Multiline PHPDoc with new line within type declaration',
+			'/**' . PHP_EOL .
+			' * @param array<string, array{' . PHP_EOL .
+			' *     foo: int,' . PHP_EOL .
+			' * ' . PHP_EOL .
+			' * ' . PHP_EOL .
+			' *     bar?: array<string,' . PHP_EOL .
+			' * ' . PHP_EOL .
+			' *         array{foo1: int, bar1?: true}' . PHP_EOL .
+			' * ' . PHP_EOL .
+			' *         | array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
+			' *     >,' . PHP_EOL .
+			' * }> $a' . PHP_EOL .
+			' */',
+			new PhpDocNode([
+				new PhpDocTagNode('@param', new ParamTagValueNode(
+					new GenericTypeNode(
+						new IdentifierTypeNode('array'),
+						[
+							new IdentifierTypeNode('string'),
+							ArrayShapeNode::createSealed([
+								new ArrayShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
+								new ArrayShapeItemNode(new IdentifierTypeNode('bar'), true, new GenericTypeNode(
+									new IdentifierTypeNode('array'),
+									[
+										new IdentifierTypeNode('string'),
+										new UnionTypeNode([
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
+											]),
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('true')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
+											]),
+										]),
+									],
+									[
+										GenericTypeNode::VARIANCE_INVARIANT,
+										GenericTypeNode::VARIANCE_INVARIANT,
+									],
+								)),
+							]),
+						],
+						[
+							GenericTypeNode::VARIANCE_INVARIANT,
+							GenericTypeNode::VARIANCE_INVARIANT,
+						],
+					),
+					false,
+					'$a',
+					'',
+					false,
+				)),
+			]),
+		];
+
+		yield [
+			'Multiline PHPDoc with new line within type declaration including usage of braces',
+			'/**' . PHP_EOL .
+			' * @phpstan-type FactoriesConfigurationType = array<' . PHP_EOL .
+			' *      string,' . PHP_EOL .
+			' *      (class-string<Factory\FactoryInterface>|Factory\FactoryInterface)' . PHP_EOL .
+			' *      |callable(ContainerInterface,?string,array<mixed>|null):object' . PHP_EOL .
+			' * >' . PHP_EOL .
+			' */',
+			new PhpDocNode([
+				new PhpDocTagNode('@phpstan-type', new TypeAliasTagValueNode(
+					'FactoriesConfigurationType',
+					new GenericTypeNode(
+						new IdentifierTypeNode('array'),
+						[
+							new IdentifierTypeNode('string'),
+							new UnionTypeNode([
+								new UnionTypeNode([
+									new GenericTypeNode(
+										new IdentifierTypeNode('class-string'),
+										[new IdentifierTypeNode('Factory\\FactoryInterface')],
+										[GenericTypeNode::VARIANCE_INVARIANT],
+									),
+									new IdentifierTypeNode('Factory\\FactoryInterface'),
+								]),
+								new CallableTypeNode(
+									new IdentifierTypeNode('callable'),
+									[
+										new CallableTypeParameterNode(new IdentifierTypeNode('ContainerInterface'), false, false, '', false),
+										new CallableTypeParameterNode(
+											new NullableTypeNode(
+												new IdentifierTypeNode('string'),
+											),
+											false,
+											false,
+											'',
+											false,
+										),
+										new CallableTypeParameterNode(
+											new UnionTypeNode([
+												new GenericTypeNode(
+													new IdentifierTypeNode('array'),
+													[new IdentifierTypeNode('mixed')],
+													[GenericTypeNode::VARIANCE_INVARIANT],
+												),
+												new IdentifierTypeNode('null'),
+											]),
+											false,
+											false,
+											'',
+											false,
+										),
+									],
+									new IdentifierTypeNode('object'),
+									[],
+								),
+							]),
+						],
+						[
+							GenericTypeNode::VARIANCE_INVARIANT,
+							GenericTypeNode::VARIANCE_INVARIANT,
+						],
+					),
+				)),
+			]),
+		];
+
+		yield [
+			'Multiline PHPDoc with multiple new line within union type declaration',
+			'/**' . PHP_EOL .
+			' * @param array<string, array{' . PHP_EOL .
+			' *     foo: int,' . PHP_EOL .
+			' *     bar?: array<string,' . PHP_EOL .
+			' *         array{foo1: int, bar1?: true}' . PHP_EOL .
+			' *         | array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
+			' *         | array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
+			' *         | array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
+			' *     >,' . PHP_EOL .
+			' * }> $a' . PHP_EOL .
+			' */',
+			new PhpDocNode([
+				new PhpDocTagNode('@param', new ParamTagValueNode(
+					new GenericTypeNode(
+						new IdentifierTypeNode('array'),
+						[
+							new IdentifierTypeNode('string'),
+							ArrayShapeNode::createSealed([
+								new ArrayShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
+								new ArrayShapeItemNode(new IdentifierTypeNode('bar'), true, new GenericTypeNode(
+									new IdentifierTypeNode('array'),
+									[
+										new IdentifierTypeNode('string'),
+										new UnionTypeNode([
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
+											]),
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('true')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
+											]),
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('bool')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar2'), true, new IdentifierTypeNode('true')),
+											]),
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('true')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar3'), true, new IdentifierTypeNode('false')),
+											]),
+										]),
+									],
+									[
+										GenericTypeNode::VARIANCE_INVARIANT,
+										GenericTypeNode::VARIANCE_INVARIANT,
+									],
+								)),
+							]),
+						],
+						[
+							GenericTypeNode::VARIANCE_INVARIANT,
+							GenericTypeNode::VARIANCE_INVARIANT,
+						],
+					),
+					false,
+					'$a',
+					'',
+					false,
+				)),
+			]),
+		];
+
+		yield [
+			'Multiline PHPDoc with multiple new line within intersection type declaration',
+			'/**' . PHP_EOL .
+			' * @param array<string, array{' . PHP_EOL .
+			' *     foo: int,' . PHP_EOL .
+			' *     bar?: array<string,' . PHP_EOL .
+			' *         array{foo1: int, bar1?: true}' . PHP_EOL .
+			' *         & array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
+			' *         & array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
+			' *         & array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
+			' *     >,' . PHP_EOL .
+			' * }> $a' . PHP_EOL .
+			' */',
+			new PhpDocNode([
+				new PhpDocTagNode('@param', new ParamTagValueNode(
+					new GenericTypeNode(
+						new IdentifierTypeNode('array'),
+						[
+							new IdentifierTypeNode('string'),
+							ArrayShapeNode::createSealed([
+								new ArrayShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
+								new ArrayShapeItemNode(new IdentifierTypeNode('bar'), true, new GenericTypeNode(
+									new IdentifierTypeNode('array'),
+									[
+										new IdentifierTypeNode('string'),
+										new IntersectionTypeNode([
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
+											]),
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('true')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
+											]),
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('bool')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar2'), true, new IdentifierTypeNode('true')),
+											]),
+											ArrayShapeNode::createSealed([
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('true')),
+												new ArrayShapeItemNode(new IdentifierTypeNode('bar3'), true, new IdentifierTypeNode('false')),
+											]),
+										]),
+									],
+									[
+										GenericTypeNode::VARIANCE_INVARIANT,
+										GenericTypeNode::VARIANCE_INVARIANT,
+									],
+								)),
+							]),
+						],
+						[
+							GenericTypeNode::VARIANCE_INVARIANT,
+							GenericTypeNode::VARIANCE_INVARIANT,
+						],
+					),
+					false,
+					'$a',
+					'',
+					false,
+				)),
+			]),
+		];
+
+		yield [
+			'Multiline PHPDoc with multiple new line being invalid due to union and intersection type declaration',
+			'/**' . PHP_EOL .
+			' * @param array<string, array{' . PHP_EOL .
+			' *     foo: int,' . PHP_EOL .
+			' *     bar?: array<string,' . PHP_EOL .
+			' *         array{foo1: int, bar1?: true}' . PHP_EOL .
+			' *         & array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
+			' *         | array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
+			' *         & array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
+			' *     >,' . PHP_EOL .
+			' * }> $a' . PHP_EOL .
+			' */',
+			new PhpDocNode([
+				new PhpDocTagNode('@param', new InvalidTagValueNode(
+					'array<string, array{' . PHP_EOL .
+					'    foo: int,' . PHP_EOL .
+					'    bar?: array<string,' . PHP_EOL .
+					'        array{foo1: int, bar1?: true}' . PHP_EOL .
+					'        & array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
+					'        | array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
+					'        & array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
+					'    >,' . PHP_EOL .
+					'}> $a',
+					new ParserException(
+						'?',
+						Lexer::TOKEN_NULLABLE,
+						DIRECTORY_SEPARATOR === '\\' ? 65 : 62,
+						Lexer::TOKEN_CLOSE_CURLY_BRACKET,
+						null,
+						4,
+					),
+				)),
+			]),
+		];
+
+		/**
+		 * @return object{
+		 *   a: int,
+		 *
+		 *   b: int,
+		 * }
+		 */
+
+		yield [
+			'Multiline PHPDoc with new line within object type declaration',
+			'/**' . PHP_EOL .
+			' * @return object{' . PHP_EOL .
+			' *   a: int,' . PHP_EOL .
+			' *' . PHP_EOL .
+			' *   b: int,' . PHP_EOL .
+			' * }' . PHP_EOL .
+			' */',
+			new PhpDocNode([
+				new PhpDocTagNode(
+					'@return',
+					new ReturnTagValueNode(
+						new ObjectShapeNode(
+							[
+								new ObjectShapeItemNode(
+									new IdentifierTypeNode('a'),
+									false,
+									new IdentifierTypeNode('int'),
+								),
+								new ObjectShapeItemNode(
+									new IdentifierTypeNode('b'),
+									false,
+									new IdentifierTypeNode('int'),
+								),
+							],
+						),
+						'',
+					),
+				),
+			]),
+		];
 	}
 
 	public function provideTemplateTagsData(): Iterator
@@ -3967,8 +4513,8 @@ public function provideTemplateTagsData(): Iterator
 					new TemplateTagValueNode(
 						'T',
 						null,
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -3982,8 +4528,8 @@ public function provideTemplateTagsData(): Iterator
 					new TemplateTagValueNode(
 						'T',
 						null,
-						'the value type'
-					)
+						'the value type',
+					),
 				),
 			]),
 		];
@@ -3997,8 +4543,8 @@ public function provideTemplateTagsData(): Iterator
 					new TemplateTagValueNode(
 						'T',
 						new IdentifierTypeNode('DateTime'),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -4012,8 +4558,8 @@ public function provideTemplateTagsData(): Iterator
 					new TemplateTagValueNode(
 						'T',
 						new IdentifierTypeNode('DateTime'),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -4027,8 +4573,8 @@ public function provideTemplateTagsData(): Iterator
 					new TemplateTagValueNode(
 						'T',
 						new IdentifierTypeNode('DateTime'),
-						'the value type'
-					)
+						'the value type',
+					),
 				),
 			]),
 		];
@@ -4044,8 +4590,8 @@ public function provideTemplateTagsData(): Iterator
 						null,
 						'the value type',
 						null,
-						new IdentifierTypeNode('DateTimeImmutable')
-					)
+						new IdentifierTypeNode('DateTimeImmutable'),
+					),
 				),
 			]),
 		];
@@ -4061,8 +4607,8 @@ public function provideTemplateTagsData(): Iterator
 						new IdentifierTypeNode('DateTimeInterface'),
 						'the value type',
 						null,
-						new IdentifierTypeNode('DateTimeImmutable')
-					)
+						new IdentifierTypeNode('DateTimeImmutable'),
+					),
 				),
 			]),
 		];
@@ -4081,9 +4627,9 @@ public function provideTemplateTagsData(): Iterator
 							14,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -4102,9 +4648,9 @@ public function provideTemplateTagsData(): Iterator
 							14,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -4118,8 +4664,8 @@ public function provideTemplateTagsData(): Iterator
 					new TemplateTagValueNode(
 						'T',
 						null,
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -4133,8 +4679,8 @@ public function provideTemplateTagsData(): Iterator
 					new TemplateTagValueNode(
 						'T',
 						null,
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -4149,8 +4695,8 @@ public function provideTemplateTagsData(): Iterator
 						'T',
 						null,
 						'',
-						new IdentifierTypeNode('string')
-					)
+						new IdentifierTypeNode('string'),
+					),
 				),
 			]),
 		];
@@ -4165,8 +4711,8 @@ public function provideTemplateTagsData(): Iterator
 						'T',
 						null,
 						'the value type',
-						new IdentifierTypeNode('string')
-					)
+						new IdentifierTypeNode('string'),
+					),
 				),
 			]),
 		];
@@ -4181,8 +4727,8 @@ public function provideTemplateTagsData(): Iterator
 						'T',
 						new IdentifierTypeNode('string'),
 						'the value type',
-						new ConstTypeNode(new ConstExprStringNode(''))
-					)
+						new ConstTypeNode(new ConstExprStringNode('', ConstExprStringNode::SINGLE_QUOTED)),
+					),
 				),
 			]),
 		];
@@ -4204,10 +4750,10 @@ public function provideExtendsTagsData(): Iterator
 							],
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -4228,10 +4774,10 @@ public function provideExtendsTagsData(): Iterator
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -4252,10 +4798,10 @@ public function provideExtendsTagsData(): Iterator
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -4276,10 +4822,10 @@ public function provideExtendsTagsData(): Iterator
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -4294,10 +4840,10 @@ public function provideExtendsTagsData(): Iterator
 						new GenericTypeNode(
 							new IdentifierTypeNode('Foo'),
 							[new IdentifierTypeNode('A')],
-							[GenericTypeNode::VARIANCE_INVARIANT]
+							[GenericTypeNode::VARIANCE_INVARIANT],
 						),
-						'extends foo'
-					)
+						'extends foo',
+					),
 				),
 			]),
 		];
@@ -4316,9 +4862,9 @@ public function provideExtendsTagsData(): Iterator
 							13,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -4337,9 +4883,9 @@ public function provideExtendsTagsData(): Iterator
 							17,
 							Lexer::TOKEN_OPEN_ANGLE_BRACKET,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -4352,8 +4898,8 @@ public function provideExtendsTagsData(): Iterator
 					'@return',
 					new ReturnTagValueNode(
 						new IdentifierTypeNode('class-string'),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -4366,8 +4912,8 @@ public function provideExtendsTagsData(): Iterator
 					'@return',
 					new ReturnTagValueNode(
 						new IdentifierTypeNode('class-string'),
-						'some description'
-					)
+						'some description',
+					),
 				),
 			]),
 		];
@@ -4382,8 +4928,9 @@ public function provideExtendsTagsData(): Iterator
 						new IdentifierTypeNode('class-string'),
 						false,
 						'$test',
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -4398,8 +4945,9 @@ public function provideExtendsTagsData(): Iterator
 						new IdentifierTypeNode('class-string'),
 						false,
 						'$test',
-						'some description'
-					)
+						'some description',
+						false,
+					),
 				),
 			]),
 		];
@@ -4418,8 +4966,8 @@ public function provideTypeAliasTagsData(): Iterator
 						new UnionTypeNode([
 							new IdentifierTypeNode('string'),
 							new IdentifierTypeNode('int'),
-						])
-					)
+						]),
+					),
 				),
 			]),
 		];
@@ -4435,8 +4983,8 @@ public function provideTypeAliasTagsData(): Iterator
 						new UnionTypeNode([
 							new IdentifierTypeNode('string'),
 							new IdentifierTypeNode('int'),
-						])
-					)
+						]),
+					),
 				),
 			]),
 		];
@@ -4444,23 +4992,6 @@ public function provideTypeAliasTagsData(): Iterator
 		yield [
 			'invalid without type',
 			'/** @phpstan-type TypeAlias */',
-			new PhpDocNode([
-				new PhpDocTagNode(
-					'@phpstan-type',
-					new InvalidTagValueNode(
-						'TypeAlias',
-						new ParserException(
-							'*/',
-							Lexer::TOKEN_CLOSE_PHPDOC,
-							28,
-							Lexer::TOKEN_IDENTIFIER,
-							null,
-							1
-						)
-					)
-				),
-			]),
-			null,
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@phpstan-type',
@@ -4472,9 +5003,9 @@ public function provideTypeAliasTagsData(): Iterator
 							28,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						))
-					)
+							1,
+						)),
+					),
 				),
 			]),
 		];
@@ -4484,23 +5015,6 @@ public function provideTypeAliasTagsData(): Iterator
 			'/**
 			  * @phpstan-type TypeAlias
 			  */',
-			new PhpDocNode([
-				new PhpDocTagNode(
-					'@phpstan-type',
-					new InvalidTagValueNode(
-						'TypeAlias',
-						new ParserException(
-							"\n\t\t\t  ",
-							Lexer::TOKEN_PHPDOC_EOL,
-							34,
-							Lexer::TOKEN_IDENTIFIER,
-							null,
-							2
-						)
-					)
-				),
-			]),
-			null,
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@phpstan-type',
@@ -4512,9 +5026,9 @@ public function provideTypeAliasTagsData(): Iterator
 							34,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							2
-						))
-					)
+							2,
+						)),
+					),
 				),
 			]),
 		];
@@ -4525,30 +5039,6 @@ public function provideTypeAliasTagsData(): Iterator
 			  * @phpstan-type TypeAlias
 			  * @mixin T
 			  */',
-			new PhpDocNode([
-				new PhpDocTagNode(
-					'@phpstan-type',
-					new InvalidTagValueNode(
-						'TypeAlias',
-						new ParserException(
-							"\n\t\t\t  * ",
-							Lexer::TOKEN_PHPDOC_EOL,
-							34,
-							Lexer::TOKEN_IDENTIFIER,
-							null,
-							2
-						)
-					)
-				),
-				new PhpDocTagNode(
-					'@mixin',
-					new MixinTagValueNode(
-						new IdentifierTypeNode('T'),
-						''
-					)
-				),
-			]),
-			null,
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@phpstan-type',
@@ -4560,16 +5050,16 @@ public function provideTypeAliasTagsData(): Iterator
 							34,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							2
-						))
-					)
+							2,
+						)),
+					),
 				),
 				new PhpDocTagNode(
 					'@mixin',
 					new MixinTagValueNode(
 						new IdentifierTypeNode('T'),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -4580,30 +5070,13 @@ public function provideTypeAliasTagsData(): Iterator
  * @phpstan-type Foo array{}
  * @phpstan-type InvalidFoo what{}
  */',
-			new PhpDocNode([
-				new PhpDocTagNode(
-					'@phpstan-type',
-					new InvalidTagValueNode(
-						"Unexpected token \"{\", expected '*/' at offset 65 on line 3",
-						new ParserException(
-							'{',
-							Lexer::TOKEN_OPEN_CURLY_BRACKET,
-							65,
-							Lexer::TOKEN_CLOSE_PHPDOC,
-							null,
-							3
-						)
-					)
-				),
-			]),
-			null,
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@phpstan-type',
 					new TypeAliasTagValueNode(
 						'Foo',
-						new ArrayShapeNode([])
-					)
+						ArrayShapeNode::createSealed([]),
+					),
 				),
 				new PhpDocTagNode(
 					'@phpstan-type',
@@ -4615,9 +5088,9 @@ public function provideTypeAliasTagsData(): Iterator
 							65,
 							Lexer::TOKEN_PHPDOC_EOL,
 							null,
-							3
-						))
-					)
+							3,
+						)),
+					),
 				),
 			]),
 		];
@@ -4629,30 +5102,13 @@ public function provideTypeAliasTagsData(): Iterator
  * @phpstan-type InvalidFoo what{}
  * @phpstan-type Bar array{}
  */',
-			new PhpDocNode([
-				new PhpDocTagNode(
-					'@phpstan-type',
-					new InvalidTagValueNode(
-						"Unexpected token \"{\", expected '*/' at offset 65 on line 3",
-						new ParserException(
-							'{',
-							Lexer::TOKEN_OPEN_CURLY_BRACKET,
-							65,
-							Lexer::TOKEN_CLOSE_PHPDOC,
-							null,
-							3
-						)
-					)
-				),
-			]),
-			null,
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@phpstan-type',
 					new TypeAliasTagValueNode(
 						'Foo',
-						new ArrayShapeNode([])
-					)
+						ArrayShapeNode::createSealed([]),
+					),
 				),
 				new PhpDocTagNode(
 					'@phpstan-type',
@@ -4664,16 +5120,16 @@ public function provideTypeAliasTagsData(): Iterator
 							65,
 							Lexer::TOKEN_PHPDOC_EOL,
 							null,
-							3
-						))
-					)
+							3,
+						)),
+					),
 				),
 				new PhpDocTagNode(
 					'@phpstan-type',
 					new TypeAliasTagValueNode(
 						'Bar',
-						new ArrayShapeNode([])
-					)
+						ArrayShapeNode::createSealed([]),
+					),
 				),
 			]),
 		];
@@ -4692,9 +5148,9 @@ public function provideTypeAliasTagsData(): Iterator
 							18,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -4711,8 +5167,8 @@ public function provideTypeAliasImportTagsData(): Iterator
 					new TypeAliasImportTagValueNode(
 						'TypeAlias',
 						new IdentifierTypeNode('AnotherClass'),
-						null
-					)
+						null,
+					),
 				),
 			]),
 		];
@@ -4726,8 +5182,8 @@ public function provideTypeAliasImportTagsData(): Iterator
 					new TypeAliasImportTagValueNode(
 						'TypeAlias',
 						new IdentifierTypeNode('AnotherClass'),
-						'DifferentAlias'
-					)
+						'DifferentAlias',
+					),
 				),
 			]),
 		];
@@ -4746,9 +5202,9 @@ public function provideTypeAliasImportTagsData(): Iterator
 							40,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -4767,9 +5223,9 @@ public function provideTypeAliasImportTagsData(): Iterator
 							52,
 							Lexer::TOKEN_CLOSE_PHPDOC,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -4788,9 +5244,9 @@ public function provideTypeAliasImportTagsData(): Iterator
 							35,
 							Lexer::TOKEN_IDENTIFIER,
 							'from',
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -4809,9 +5265,9 @@ public function provideTypeAliasImportTagsData(): Iterator
 							35,
 							Lexer::TOKEN_IDENTIFIER,
 							'from',
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -4830,9 +5286,9 @@ public function provideTypeAliasImportTagsData(): Iterator
 							25,
 							Lexer::TOKEN_IDENTIFIER,
 							null,
-							1
-						)
-					)
+							1,
+						),
+					),
 				),
 			]),
 		];
@@ -4850,8 +5306,9 @@ public function provideAssertTagsData(): Iterator
 						new IdentifierTypeNode('Type'),
 						'$var',
 						false,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -4866,8 +5323,9 @@ public function provideAssertTagsData(): Iterator
 						new IdentifierTypeNode('Type'),
 						'$var',
 						false,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -4882,8 +5340,9 @@ public function provideAssertTagsData(): Iterator
 						new IdentifierTypeNode('Type'),
 						'$var',
 						false,
-						'assert Type to $var'
-					)
+						'assert Type to $var',
+						false,
+					),
 				),
 			]),
 		];
@@ -4901,8 +5360,9 @@ public function provideAssertTagsData(): Iterator
 						]),
 						'$var',
 						false,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -4918,8 +5378,9 @@ public function provideAssertTagsData(): Iterator
 						'$var',
 						'method',
 						false,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -4935,8 +5396,9 @@ public function provideAssertTagsData(): Iterator
 						'$var',
 						'property',
 						false,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -4951,8 +5413,9 @@ public function provideAssertTagsData(): Iterator
 						new IdentifierTypeNode('Type'),
 						'$this',
 						false,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -4967,8 +5430,9 @@ public function provideAssertTagsData(): Iterator
 						new IdentifierTypeNode('Type'),
 						'$this',
 						false,
-						'assert Type to $this'
-					)
+						'assert Type to $this',
+						false,
+					),
 				),
 			]),
 		];
@@ -4987,12 +5451,13 @@ public function provideAssertTagsData(): Iterator
 							],
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
 						'$this',
 						false,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -5008,8 +5473,9 @@ public function provideAssertTagsData(): Iterator
 						'$this',
 						'method',
 						false,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -5025,8 +5491,9 @@ public function provideAssertTagsData(): Iterator
 						'$this',
 						'property',
 						false,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -5041,8 +5508,9 @@ public function provideAssertTagsData(): Iterator
 						new IdentifierTypeNode('Type'),
 						'$var',
 						false,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -5057,8 +5525,9 @@ public function provideAssertTagsData(): Iterator
 						new IdentifierTypeNode('Type'),
 						'$var',
 						false,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -5073,8 +5542,9 @@ public function provideAssertTagsData(): Iterator
 						new IdentifierTypeNode('Type'),
 						'$var',
 						true,
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -5090,8 +5560,8 @@ public function provideAssertTagsData(): Iterator
 						'$var',
 						false,
 						'',
-						true
-					)
+						true,
+					),
 				),
 			]),
 		];
@@ -5107,8 +5577,8 @@ public function provideAssertTagsData(): Iterator
 						'$var',
 						true,
 						'',
-						true
-					)
+						true,
+					),
 				),
 			]),
 		];
@@ -5129,13 +5599,13 @@ public function providerDebug(): Iterator
 			'OK class line',
 			$sample,
 			new PhpDocNode([
-				new PhpDocTextNode('Returns the schema for the field.'),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode('This method is static because the field schema information is needed on
+				new PhpDocTextNode('Returns the schema for the field.
+
+This method is static because the field schema information is needed on
 creation of the field. FieldItemInterface objects instantiated at that
-time are not reliable as field settings might be missing.'),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode('Computed fields having no schema should return an empty array.'),
+time are not reliable as field settings might be missing.
+
+Computed fields having no schema should return an empty array.'),
 			]),
 		];
 	}
@@ -5183,13 +5653,13 @@ public function provideRealWorldExampleData(): Iterator
 			'OK FieldItemInterface::schema',
 			$sample,
 			new PhpDocNode([
-				new PhpDocTextNode('Returns the schema for the field.'),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode('This method is static because the field schema information is needed on
+				new PhpDocTextNode('Returns the schema for the field.
+
+This method is static because the field schema information is needed on
 creation of the field. FieldItemInterface objects instantiated at that
-time are not reliable as field settings might be missing.'),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode('Computed fields having no schema should return an empty array.'),
+time are not reliable as field settings might be missing.
+
+Computed fields having no schema should return an empty array.'),
 				new PhpDocTextNode(''),
 				new PhpDocTagNode(
 					'@param',
@@ -5197,19 +5667,18 @@ public function provideRealWorldExampleData(): Iterator
 						new IdentifierTypeNode('\Drupal\Core\Field\FieldStorageDefinitionInterface'),
 						false,
 						'$field_definition',
-						''
-					)
+						'
+  The field definition.',
+						false,
+					),
 				),
-				new PhpDocTextNode('The field definition.'),
 				new PhpDocTextNode(''),
 				new PhpDocTagNode(
 					'@return',
 					new ReturnTagValueNode(
 						new IdentifierTypeNode('array'),
-						''
-					)
-				),
-				new PhpDocTextNode("An empty array if there is no schema, or an associative array with the
+						"
+  An empty array if there is no schema, or an associative array with the
   following key/value pairs:
   - columns: An array of Schema API column specifications, keyed by column
     name. The columns need to be a subset of the properties defined in
@@ -5231,7 +5700,9 @@ public function provideRealWorldExampleData(): Iterator
     definitions. Note, however, that the field data is not necessarily
     stored in SQL. Also, the possible usage is limited, as you cannot
     specify another field as related, only existing SQL tables,
-    such as {taxonomy_term_data}."),
+    such as {taxonomy_term_data}.",
+					),
+				),
 			]),
 		];
 
@@ -5256,9 +5727,9 @@ public function provideRealWorldExampleData(): Iterator
 			'OK AbstractChunkedController::parseChunkedRequest',
 			$sample,
 			new PhpDocNode([
-				new PhpDocTextNode('Parses a chunked request and return relevant information.'),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode('This function must return an array containing the following
+				new PhpDocTextNode('Parses a chunked request and return relevant information.
+
+ This function must return an array containing the following
  keys and their corresponding values:
    - last: Wheter this is the last chunk of the uploaded file
    - uuid: A unique id which distinguishes two uploaded files
@@ -5274,16 +5745,17 @@ public function provideRealWorldExampleData(): Iterator
 						new IdentifierTypeNode('Request'),
 						false,
 						'$request',
-						'- The request object'
-					)
+						'- The request object',
+						false,
+					),
 				),
 				new PhpDocTextNode(''),
 				new PhpDocTagNode(
 					'@return',
 					new ReturnTagValueNode(
 						new IdentifierTypeNode('array'),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -5301,9 +5773,9 @@ public function provideRealWorldExampleData(): Iterator
 			  * </code>
 			  */",
 			new PhpDocNode([
-				new PhpDocTextNode('Finder allows searching through directory trees using iterator.'),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode("<code>
+				new PhpDocTextNode("Finder allows searching through directory trees using iterator.
+
+<code>
 Finder::findFiles('*.php')
     ->size('> 10kB')
     ->from('.')
@@ -5320,11 +5792,11 @@ public function provideRealWorldExampleData(): Iterator
 					'@return',
 					new ReturnTagValueNode(
 						new UnionTypeNode([
-							new ConstTypeNode(new ConstExprStringNode('foo')),
-							new ConstTypeNode(new ConstExprStringNode('bar')),
+							new ConstTypeNode(new ConstExprStringNode('foo', ConstExprStringNode::SINGLE_QUOTED)),
+							new ConstTypeNode(new ConstExprStringNode('bar', ConstExprStringNode::SINGLE_QUOTED)),
 						]),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -5357,10 +5829,10 @@ public function provideRealWorldExampleData(): Iterator
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -5386,10 +5858,10 @@ public function provideRealWorldExampleData(): Iterator
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -5415,10 +5887,10 @@ public function provideRealWorldExampleData(): Iterator
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -5440,12 +5912,14 @@ public function provideRealWorldExampleData(): Iterator
 								new CallableTypeParameterNode(new IdentifierTypeNode('A'), false, false, '', false),
 								new CallableTypeParameterNode(new IdentifierTypeNode('B'), false, false, '', false),
 							],
-							new IdentifierTypeNode('void')
+							new IdentifierTypeNode('void'),
+							[],
 						),
 						false,
 						'$foo',
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -5468,12 +5942,14 @@ public function provideRealWorldExampleData(): Iterator
 								new CallableTypeParameterNode(new IdentifierTypeNode('A'), false, false, '', false),
 								new CallableTypeParameterNode(new IdentifierTypeNode('B'), false, false, '', false),
 							],
-							new IdentifierTypeNode('void')
+							new IdentifierTypeNode('void'),
+							[],
 						),
 						false,
 						'$foo',
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -5496,12 +5972,14 @@ public function provideRealWorldExampleData(): Iterator
 								new CallableTypeParameterNode(new IdentifierTypeNode('A'), false, false, '', false),
 								new CallableTypeParameterNode(new IdentifierTypeNode('B'), false, false, '', false),
 							],
-							new IdentifierTypeNode('void')
+							new IdentifierTypeNode('void'),
+							[],
 						),
 						false,
 						'$foo',
-						''
-					)
+						'',
+						false,
+					),
 				),
 			]),
 		];
@@ -5541,10 +6019,10 @@ public function provideRealWorldExampleData(): Iterator
 								GenericTypeNode::VARIANCE_INVARIANT,
 								GenericTypeNode::VARIANCE_INVARIANT,
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
-						''
-					)
+						'',
+					),
 				),
 				new PhpDocTextNode(''),
 				new PhpDocTagNode(
@@ -5553,8 +6031,9 @@ public function provideRealWorldExampleData(): Iterator
 						new IdentifierTypeNode('string'),
 						false,
 						'$pattern',
-						''
-					)
+						'',
+						false,
+					),
 				),
 				new PhpDocTagNode(
 					'@param',
@@ -5562,8 +6041,9 @@ public function provideRealWorldExampleData(): Iterator
 						new IdentifierTypeNode('string'),
 						false,
 						'$subject',
-						''
-					)
+						'',
+						false,
+					),
 				),
 				new PhpDocTagNode(
 					'@param',
@@ -5571,8 +6051,9 @@ public function provideRealWorldExampleData(): Iterator
 						new IdentifierTypeNode('mixed'),
 						false,
 						'$matches',
-						''
-					)
+						'',
+						false,
+					),
 				),
 				new PhpDocTagNode(
 					'@param',
@@ -5580,8 +6061,9 @@ public function provideRealWorldExampleData(): Iterator
 						new IdentifierTypeNode('TFlags'),
 						false,
 						'$flags',
-						''
-					)
+						'',
+						false,
+					),
 				),
 				new PhpDocTagNode(
 					'@param-out',
@@ -5594,7 +6076,7 @@ public function provideRealWorldExampleData(): Iterator
 								[
 									new IdentifierTypeNode('array-key'),
 									new UnionTypeNode([
-										new ArrayShapeNode([
+										ArrayShapeNode::createSealed([
 											new ArrayShapeItemNode(null, false, new IdentifierTypeNode('string')),
 											new ArrayShapeItemNode(
 												null,
@@ -5602,11 +6084,11 @@ public function provideRealWorldExampleData(): Iterator
 												new UnionTypeNode([
 													new ConstTypeNode(new ConstExprIntegerNode('0')),
 													new IdentifierTypeNode('positive-int'),
-												])
+												]),
 											),
 										]),
-										new ArrayShapeNode([
-											new ArrayShapeItemNode(null, false, new ConstTypeNode(new ConstExprStringNode(''))),
+										ArrayShapeNode::createSealed([
+											new ArrayShapeItemNode(null, false, new ConstTypeNode(new ConstExprStringNode('', ConstExprStringNode::SINGLE_QUOTED))),
 											new ArrayShapeItemNode(null, false, new ConstTypeNode(new ConstExprIntegerNode('-1'))),
 										]),
 									]),
@@ -5614,7 +6096,7 @@ public function provideRealWorldExampleData(): Iterator
 								[
 									GenericTypeNode::VARIANCE_INVARIANT,
 									GenericTypeNode::VARIANCE_INVARIANT,
-								]
+								],
 							),
 							new ConditionalTypeNode(
 								new IdentifierTypeNode('TFlags'),
@@ -5631,7 +6113,7 @@ public function provideRealWorldExampleData(): Iterator
 									[
 										GenericTypeNode::VARIANCE_INVARIANT,
 										GenericTypeNode::VARIANCE_INVARIANT,
-									]
+									],
 								),
 								new ConditionalTypeNode(
 									new IdentifierTypeNode('TFlags'),
@@ -5641,7 +6123,7 @@ public function provideRealWorldExampleData(): Iterator
 										[
 											new IdentifierTypeNode('array-key'),
 											new UnionTypeNode([
-												new ArrayShapeNode([
+												ArrayShapeNode::createSealed([
 													new ArrayShapeItemNode(null, false, new IdentifierTypeNode('string')),
 													new ArrayShapeItemNode(
 														null,
@@ -5649,10 +6131,10 @@ public function provideRealWorldExampleData(): Iterator
 														new UnionTypeNode([
 															new ConstTypeNode(new ConstExprIntegerNode('0')),
 															new IdentifierTypeNode('positive-int'),
-														])
+														]),
 													),
 												]),
-												new ArrayShapeNode([
+												ArrayShapeNode::createSealed([
 													new ArrayShapeItemNode(null, false, new IdentifierTypeNode('null')),
 													new ArrayShapeItemNode(null, false, new ConstTypeNode(new ConstExprIntegerNode('-1'))),
 												]),
@@ -5661,7 +6143,7 @@ public function provideRealWorldExampleData(): Iterator
 										[
 											GenericTypeNode::VARIANCE_INVARIANT,
 											GenericTypeNode::VARIANCE_INVARIANT,
-										]
+										],
 									),
 									new GenericTypeNode(
 										new IdentifierTypeNode('array'),
@@ -5672,17 +6154,17 @@ public function provideRealWorldExampleData(): Iterator
 										[
 											GenericTypeNode::VARIANCE_INVARIANT,
 											GenericTypeNode::VARIANCE_INVARIANT,
-										]
+										],
 									),
-									false
+									false,
 								),
-								false
+								false,
 							),
-							false
+							false,
 						),
 						'$matches',
-						''
-					)
+						'',
+					),
 				),
 				new PhpDocTagNode(
 					'@return',
@@ -5692,8 +6174,8 @@ public function provideRealWorldExampleData(): Iterator
 							new ConstTypeNode(new ConstExprIntegerNode('0')),
 							new IdentifierTypeNode('false'),
 						]),
-						''
-					)
+						'',
+					),
 				),
 				new PhpDocTagNode('@psalm-ignore-falsable-return', new GenericTagValueNode('')),
 			]),
@@ -5712,8 +6194,8 @@ public function provideDescriptionWithOrWithoutHtml(): Iterator
 					'@return',
 					new ReturnTagValueNode(
 						new IdentifierTypeNode('Foo'),
-						'<strong>Important <i>description</i></strong>'
-					)
+						'<strong>Important <i>description</i></strong>',
+					),
 				),
 			]),
 		];
@@ -5728,8 +6210,8 @@ public function provideDescriptionWithOrWithoutHtml(): Iterator
 					'@throws',
 					new ThrowsTagValueNode(
 						new IdentifierTypeNode('FooException'),
-						'<strong>Important <em>description</em> etc</strong>'
-					)
+						'<strong>Important <em>description</em> etc</strong>',
+					),
 				),
 			]),
 		];
@@ -5744,8 +6226,8 @@ public function provideDescriptionWithOrWithoutHtml(): Iterator
 					'@mixin',
 					new MixinTagValueNode(
 						new IdentifierTypeNode('Mixin'),
-						'<strong>Important description</strong>'
-					)
+						'<strong>Important description</strong>',
+					),
 				),
 			]),
 		];
@@ -5755,23 +6237,6 @@ public function provideDescriptionWithOrWithoutHtml(): Iterator
 			'/**' . PHP_EOL .
 			' * @return Foo <strong>Important description' . PHP_EOL .
 			' */',
-			new PhpDocNode([
-				new PhpDocTagNode(
-					'@return',
-					new ReturnTagValueNode(
-						new GenericTypeNode(
-							new IdentifierTypeNode('Foo'),
-							[
-								new IdentifierTypeNode('strong'),
-							],
-							[
-								GenericTypeNode::VARIANCE_INVARIANT,
-							]
-						),
-						'Important description'
-					)
-				),
-			]),
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@return',
@@ -5783,9 +6248,9 @@ public function provideDescriptionWithOrWithoutHtml(): Iterator
 							PHP_EOL === "\n" ? 27 : 28,
 							Lexer::TOKEN_HORIZONTAL_WS,
 							null,
-							2
-						)
-					)
+							2,
+						),
+					),
 				),
 			]),
 		];
@@ -5804,7 +6269,8 @@ public function dataParseTagValue(): array
 					new ConstTypeNode(new ConstFetchNode('DateTimeImmutable', 'ATOM')),
 					false,
 					'$a',
-					''
+					'',
+					false,
 				),
 			],
 			[
@@ -5818,8 +6284,8 @@ public function dataParseTagValue(): array
 						0,
 						Lexer::TOKEN_IDENTIFIER,
 						null,
-						1
-					)
+						1,
+					),
 				),
 			],
 		];
@@ -5833,7 +6299,7 @@ public function provideTagsWithNumbers(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@special3',
-					new GenericTagValueNode('Foo')
+					new GenericTagValueNode('Foo'),
 				),
 			]),
 		];
@@ -5847,7 +6313,7 @@ public function provideTagsWithBackslash(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@ORM\Mapping\Entity',
-					new GenericTagValueNode('User')
+					new GenericTagValueNode('User'),
 				),
 			]),
 		];
@@ -5861,7 +6327,7 @@ public function provideTagsWithBackslash(): Iterator
 					new DoctrineTagValueNode(new DoctrineAnnotation('@ORM\Mapping\JoinColumn', [
 						new DoctrineArgument(new IdentifierTypeNode('name'), new DoctrineConstExprStringNode('column_id')),
 						new DoctrineArgument(new IdentifierTypeNode('referencedColumnName'), new DoctrineConstExprStringNode('id')),
-					]), '')
+					]), ''),
 				),
 			]),
 		];
@@ -5877,8 +6343,8 @@ public function provideSelfOutTagsData(): Iterator
 					'@phpstan-self-out',
 					new SelfOutTagValueNode(
 						new GenericTypeNode(new IdentifierTypeNode('self'), [new IdentifierTypeNode('T')], [GenericTypeNode::VARIANCE_INVARIANT]),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -5891,8 +6357,8 @@ public function provideSelfOutTagsData(): Iterator
 					'@phpstan-this-out',
 					new SelfOutTagValueNode(
 						new GenericTypeNode(new IdentifierTypeNode('self'), [new IdentifierTypeNode('T')], [GenericTypeNode::VARIANCE_INVARIANT]),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -5905,8 +6371,8 @@ public function provideSelfOutTagsData(): Iterator
 					'@psalm-self-out',
 					new SelfOutTagValueNode(
 						new GenericTypeNode(new IdentifierTypeNode('self'), [new IdentifierTypeNode('T')], [GenericTypeNode::VARIANCE_INVARIANT]),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -5919,8 +6385,8 @@ public function provideSelfOutTagsData(): Iterator
 					'@psalm-this-out',
 					new SelfOutTagValueNode(
 						new GenericTypeNode(new IdentifierTypeNode('self'), [new IdentifierTypeNode('T')], [GenericTypeNode::VARIANCE_INVARIANT]),
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -5933,8 +6399,8 @@ public function provideSelfOutTagsData(): Iterator
 					'@phpstan-self-out',
 					new SelfOutTagValueNode(
 						new GenericTypeNode(new IdentifierTypeNode('self'), [new IdentifierTypeNode('T')], [GenericTypeNode::VARIANCE_INVARIANT]),
-						'description'
-					)
+						'description',
+					),
 				),
 			]),
 		];
@@ -5950,11 +6416,55 @@ public function provideCommentLikeDescriptions(): Iterator
 					new IdentifierTypeNode('int'),
 					false,
 					'$a',
-					'// this is a description'
+					'// this is a description',
+					false,
+				)),
+			]),
+		];
+
+		yield [
+			'Comment after @param with https://',
+			'/** @param int $a https://phpstan.org/ */',
+			new PhpDocNode([
+				new PhpDocTagNode('@param', new ParamTagValueNode(
+					new IdentifierTypeNode('int'),
+					false,
+					'$a',
+					'https://phpstan.org/',
+					false,
+				)),
+			]),
+		];
+
+		yield [
+			'Comment after @param with https:// in // comment',
+			'/** @param int $a // comment https://phpstan.org/ */',
+			new PhpDocNode([
+				new PhpDocTagNode('@param', new ParamTagValueNode(
+					new IdentifierTypeNode('int'),
+					false,
+					'$a',
+					'// comment https://phpstan.org/',
+					false,
 				)),
 			]),
 		];
 
+		yield [
+			'Comment in PHPDoc tag outside of type',
+			'/** @param // comment */',
+			new PhpDocNode([
+				new PhpDocTagNode('@param', new InvalidTagValueNode('// comment', new ParserException(
+					'// comment ',
+					37,
+					11,
+					24,
+					null,
+					1,
+				))),
+			]),
+		];
+
 		yield [
 			'Comment on a separate line',
 			'/**' . PHP_EOL .
@@ -5966,9 +6476,9 @@ public function provideCommentLikeDescriptions(): Iterator
 					new IdentifierTypeNode('int'),
 					false,
 					'$a',
-					''
+					PHP_EOL . '// this is a comment',
+					false,
 				)),
-				new PhpDocTextNode('// this is a comment'),
 			]),
 		];
 		yield [
@@ -5983,10 +6493,9 @@ public function provideCommentLikeDescriptions(): Iterator
 					new IdentifierTypeNode('int'),
 					false,
 					'$a',
-					''
+					PHP_EOL . PHP_EOL . '// this is a comment',
+					false,
 				)),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode('// this is a comment'),
 			]),
 		];
 		yield [
@@ -6002,7 +6511,7 @@ public function provideCommentLikeDescriptions(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@\ORM\Doctrine', new DoctrineTagValueNode(
 					new DoctrineAnnotation('@\ORM\Doctrine', []),
-					'// this is a description'
+					'// this is a description',
 				)),
 			]),
 		];
@@ -6012,7 +6521,7 @@ public function provideCommentLikeDescriptions(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@\ORM\Doctrine', new DoctrineTagValueNode(
 					new DoctrineAnnotation('@\ORM\Doctrine', []),
-					'// this is a description'
+					'// this is a description',
 				)),
 			]),
 		];
@@ -6022,16 +6531,37 @@ public function provideCommentLikeDescriptions(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@\ORM\Doctrine', new DoctrineTagValueNode(
 					new DoctrineAnnotation('@\ORM\Doctrine', []),
-					''
+					'',
 				)),
 				new PhpDocTagNode('@\ORM\Entity', new DoctrineTagValueNode(
 					new DoctrineAnnotation('@\ORM\Entity', []),
-					'// this is a description'
+					'// this is a description',
 				)),
 			]),
 		];
 	}
 
+	public function provideInlineTags(): Iterator
+	{
+		yield [
+			'Inline @link tag in @copyright',
+			'/**' . PHP_EOL .
+			' * Unit tests for stored_progress_bar_cleanup' . PHP_EOL .
+			' *' . PHP_EOL .
+			' * @package   core' . PHP_EOL .
+			' * @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}' . PHP_EOL .
+			' * @\ORM\Entity() 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}' . PHP_EOL .
+			' */',
+			new PhpDocNode([
+				new PhpDocTextNode('Unit tests for stored_progress_bar_cleanup'),
+				new PhpDocTextNode(''),
+				new PhpDocTagNode('@package', new GenericTagValueNode('core')),
+				new PhpDocTagNode('@copyright', new GenericTagValueNode('2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}')),
+				new PhpDocTagNode('@\ORM\Entity', new DoctrineTagValueNode(new DoctrineAnnotation('@\ORM\Entity', []), '2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}')),
+			]),
+		];
+	}
+
 	public function provideParamOutTagsData(): Iterator
 	{
 		yield [
@@ -6043,8 +6573,8 @@ public function provideParamOutTagsData(): Iterator
 					new ParamOutTagValueNode(
 						new IdentifierTypeNode('string'),
 						'$s',
-						''
-					)
+						'',
+					),
 				),
 			]),
 		];
@@ -6058,8 +6588,8 @@ public function provideParamOutTagsData(): Iterator
 					new ParamOutTagValueNode(
 						new IdentifierTypeNode('string'),
 						'$s',
-						'description'
-					)
+						'description',
+					),
 				),
 			]),
 		];
@@ -6077,12 +6607,10 @@ public function provideDoctrineData(): Iterator
 					'@X',
 					new DoctrineTagValueNode(
 						new DoctrineAnnotation('@X', []),
-						'Content'
-					)
+						'Content',
+					),
 				),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6100,12 +6628,10 @@ public function provideDoctrineData(): Iterator
 						new DoctrineAnnotation('@X', [
 							new DoctrineArgument(null, new DoctrineAnnotation('@Z', [])),
 						]),
-						'Content'
-					)
+						'Content',
+					),
 				),
 			]),
-			null,
-			null,
 			[$xWithZ],
 		];
 
@@ -6121,12 +6647,10 @@ public function provideDoctrineData(): Iterator
 						new DoctrineAnnotation('@X', [
 							new DoctrineArgument(new IdentifierTypeNode('a'), new DoctrineAnnotation('@Z', [])),
 						]),
-						'Content'
-					)
+						'Content',
+					),
 				),
 			]),
-			null,
-			null,
 			[$xWithZ],
 		];
 
@@ -6142,12 +6666,10 @@ public function provideDoctrineData(): Iterator
 						new DoctrineAnnotation('@X', [
 							new DoctrineArgument(null, new DoctrineAnnotation('@\PHPStan\PhpDocParser\Parser\Doctrine\Z', [])),
 						]),
-						'Content'
-					)
+						'Content',
+					),
 				),
 			]),
-			null,
-			null,
 			[$xWithZ],
 		];
 
@@ -6163,12 +6685,10 @@ public function provideDoctrineData(): Iterator
 						new DoctrineAnnotation('@X', [
 							new DoctrineArgument(new IdentifierTypeNode('a'), new DoctrineAnnotation('@\PHPStan\PhpDocParser\Parser\Doctrine\Z', [])),
 						]),
-						'Content'
-					)
+						'Content',
+					),
 				),
 			]),
-			null,
-			null,
 			[$xWithZ],
 		];
 
@@ -6183,12 +6703,10 @@ public function provideDoctrineData(): Iterator
 					'@X',
 					new DoctrineTagValueNode(
 						new DoctrineAnnotation('@X', []),
-						'Content'
-					)
+						'Content',
+					),
 				),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6203,13 +6721,11 @@ public function provideDoctrineData(): Iterator
 					'@X',
 					new DoctrineTagValueNode(
 						new DoctrineAnnotation('@X', []),
-						''
-					)
+						PHP_EOL .
+						'Content',
+					),
 				),
-				new PhpDocTextNode('Content'),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6223,12 +6739,10 @@ public function provideDoctrineData(): Iterator
 					'@\PHPStan\PhpDocParser\Parser\Doctrine\X',
 					new DoctrineTagValueNode(
 						new DoctrineAnnotation('@\PHPStan\PhpDocParser\Parser\Doctrine\X', []),
-						'Content'
-					)
+						'Content',
+					),
 				),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6242,12 +6756,10 @@ public function provideDoctrineData(): Iterator
 					'@\PHPStan\PhpDocParser\Parser\Doctrine\X',
 					new DoctrineTagValueNode(
 						new DoctrineAnnotation('@\PHPStan\PhpDocParser\Parser\Doctrine\X', []),
-						'Content'
-					)
+						'Content',
+					),
 				),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6282,14 +6794,12 @@ public function provideDoctrineData(): Iterator
 										new DoctrineArgument(new IdentifierTypeNode('code'), new ConstExprIntegerNode('123')),
 									])),
 								])),
-							]
+							],
 						),
-						'Content'
-					)
+						'Content',
+					),
 				),
 			]),
-			null,
-			null,
 			[$x],
 		];
 
@@ -6320,14 +6830,12 @@ public function provideDoctrineData(): Iterator
 										new DoctrineArgument(new IdentifierTypeNode('code'), new ConstExprIntegerNode('123')),
 									])),
 								])),
-							]
+							],
 						),
-						'Content'
-					)
+						'Content',
+					),
 				),
 			]),
-			null,
-			null,
 			[$x],
 		];
 
@@ -6339,11 +6847,9 @@ public function provideDoctrineData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@X', new DoctrineTagValueNode(new DoctrineAnnotation(
 					'@X',
-					[]
+					[],
 				), 'test')),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6356,11 +6862,9 @@ public function provideDoctrineData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@X', new DoctrineTagValueNode(new DoctrineAnnotation(
 					'@X',
-					[]
+					[],
 				), 'test' . PHP_EOL . 'test2')),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 		yield [
@@ -6373,13 +6877,10 @@ public function provideDoctrineData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@X', new DoctrineTagValueNode(new DoctrineAnnotation(
 					'@X',
-					[]
-				), 'test')),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode('test2'),
+					[],
+				), 'test' . PHP_EOL .
+					PHP_EOL . 'test2')),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 		yield [
@@ -6393,17 +6894,14 @@ public function provideDoctrineData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@X', new DoctrineTagValueNode(new DoctrineAnnotation(
 					'@X',
-					[]
-				), 'test')),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode('test2'),
+					[],
+				), 'test' . PHP_EOL .
+					PHP_EOL . 'test2')),
 				new PhpDocTagNode('@Z', new DoctrineTagValueNode(new DoctrineAnnotation(
 					'@Z',
-					[]
+					[],
 				), '')),
 			]),
-			null,
-			null,
 			[new Doctrine\X(), new Doctrine\Z()],
 		];
 
@@ -6415,8 +6913,6 @@ public function provideDoctrineData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@X', new GenericTagValueNode('test')),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6429,8 +6925,6 @@ public function provideDoctrineData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@X', new GenericTagValueNode('test' . PHP_EOL . 'test2')),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 		yield [
@@ -6441,12 +6935,10 @@ public function provideDoctrineData(): Iterator
 			' * test2' . PHP_EOL .
 			' */',
 			new PhpDocNode([
-				new PhpDocTagNode('@X', new GenericTagValueNode('test')),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode('test2'),
+				new PhpDocTagNode('@X', new GenericTagValueNode('test' . PHP_EOL .
+					PHP_EOL .
+					'test2')),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 		yield [
@@ -6458,13 +6950,11 @@ public function provideDoctrineData(): Iterator
 			' * @Z' . PHP_EOL .
 			' */',
 			new PhpDocNode([
-				new PhpDocTagNode('@X', new GenericTagValueNode('test')),
-				new PhpDocTextNode(''),
-				new PhpDocTextNode('test2'),
+				new PhpDocTagNode('@X', new GenericTagValueNode('test' . PHP_EOL .
+					PHP_EOL .
+					'test2')),
 				new PhpDocTagNode('@Z', new GenericTagValueNode('')),
 			]),
-			null,
-			null,
 			[new Doctrine\X(), new Doctrine\Z()],
 		];
 
@@ -6475,8 +6965,6 @@ public function provideDoctrineData(): Iterator
 				new PhpDocTagNode('@X', new DoctrineTagValueNode(new DoctrineAnnotation('@X', []), '')),
 				new PhpDocTagNode('@Z', new DoctrineTagValueNode(new DoctrineAnnotation('@Z', []), '')),
 			]),
-			null,
-			null,
 			[new Doctrine\X(), new Doctrine\Z()],
 		];
 
@@ -6487,8 +6975,6 @@ public function provideDoctrineData(): Iterator
 				new PhpDocTagNode('@X', new DoctrineTagValueNode(new DoctrineAnnotation('@X', []), 'test')),
 				new PhpDocTagNode('@Z', new DoctrineTagValueNode(new DoctrineAnnotation('@Z', []), '')),
 			]),
-			null,
-			null,
 			[new Doctrine\X(), new Doctrine\Z()],
 		];
 
@@ -6499,8 +6985,6 @@ public function provideDoctrineData(): Iterator
 				new PhpDocTagNode('@X', new GenericTagValueNode('test')),
 				new PhpDocTagNode('@Z', new DoctrineTagValueNode(new DoctrineAnnotation('@Z', []), '')),
 			]),
-			null,
-			null,
 			[new Doctrine\X(), new Doctrine\Z()],
 		];
 
@@ -6510,8 +6994,6 @@ public function provideDoctrineData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@X', new GenericTagValueNode('@phpstan-param int $z')),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6521,8 +7003,6 @@ public function provideDoctrineData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@X', new GenericTagValueNode('@phpstan-param |int $z')),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6532,8 +7012,6 @@ public function provideDoctrineData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@X', new DoctrineTagValueNode(new DoctrineAnnotation('@X', []), '@phpstan-param int $z')),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6543,8 +7021,6 @@ public function provideDoctrineData(): Iterator
 			new PhpDocNode([
 				new PhpDocTagNode('@X', new DoctrineTagValueNode(new DoctrineAnnotation('@X', []), '@phpstan-param |int $z')),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6586,7 +7062,7 @@ public function provideDoctrineData(): Iterator
 										new DoctrineConstExprStringNode('is_granted(' . PHP_EOL .
 											"constant('REDACTED')," . PHP_EOL .
 											'object' . PHP_EOL .
-											')')
+											')'),
 									),
 									new DoctrineArrayItem(
 										new DoctrineConstExprStringNode('normalization_context'),
@@ -6595,19 +7071,17 @@ public function provideDoctrineData(): Iterator
 												new DoctrineConstExprStringNode('groups'),
 												new DoctrineArray([
 													new DoctrineArrayItem(null, new DoctrineConstExprStringNode('Redacted:read')),
-												])
+												]),
 											),
-										])
+										]),
 									),
-								])
+								]),
 							),
 						])),
 					]),
-					''
+					'',
 				)),
 			]),
-			null,
-			null,
 			[$apiResource],
 		];
 
@@ -6621,11 +7095,9 @@ public function provideDoctrineData(): Iterator
 					new DoctrineAnnotation('@X', [
 						new DoctrineArgument(new IdentifierTypeNode('a'), new DoctrineConstExprStringNode($xWithString->a)),
 					]),
-					''
+					'',
 				)),
 			]),
-			null,
-			null,
 			[$xWithString],
 		];
 
@@ -6639,11 +7111,9 @@ public function provideDoctrineData(): Iterator
 					new DoctrineAnnotation('@X', [
 						new DoctrineArgument(new IdentifierTypeNode('a'), new DoctrineConstExprStringNode($xWithString2->a)),
 					]),
-					''
+					'',
 				)),
 			]),
-			null,
-			null,
 			[$xWithString2],
 		];
 
@@ -6657,11 +7127,9 @@ public function provideDoctrineData(): Iterator
 					new DoctrineAnnotation('@X', [
 						new DoctrineArgument(new IdentifierTypeNode('a'), new DoctrineConstExprStringNode($xWithString3->a)),
 					]),
-					''
+					'',
 				)),
 			]),
-			null,
-			null,
 			[$xWithString3],
 		];
 
@@ -6675,11 +7143,9 @@ public function provideDoctrineData(): Iterator
 					new DoctrineAnnotation('@X', [
 						new DoctrineArgument(new IdentifierTypeNode('a'), new DoctrineConstExprStringNode($xWithString4->a)),
 					]),
-					''
+					'',
 				)),
 			]),
-			null,
-			null,
 			[$xWithString4],
 		];
 	}
@@ -6694,7 +7160,7 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					new DoctrineAnnotation('@DummyAnnotation', [
 						new DoctrineArgument(new IdentifierTypeNode('dummyValue'), new DoctrineConstExprStringNode('hello')),
 					]),
-					''
+					'',
 				)),
 			]),
 		];
@@ -6724,7 +7190,7 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 							])),
 						])),
 					]),
-					''
+					'',
 				)),
 			]),
 		];
@@ -6737,7 +7203,7 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					new DoctrineAnnotation('@AnnotationTargetAll', [
 						new DoctrineArgument(null, new DoctrineAnnotation('@AnnotationTargetAnnotation', [])),
 					]),
-					''
+					'',
 				)),
 			]),
 		];
@@ -6750,7 +7216,7 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					new DoctrineAnnotation('@DummyAnnotation', [
 						new DoctrineArgument(new IdentifierTypeNode('dummyValue'), new DoctrineConstExprStringNode('bar')),
 					]),
-					''
+					'',
 				)),
 			]),
 		];
@@ -6767,7 +7233,7 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					new DoctrineAnnotation('@DummyColumn', [
 						new DoctrineArgument(new IdentifierTypeNode('type'), new DoctrineConstExprStringNode('integer')),
 					]),
-					''
+					'',
 				)),
 				new PhpDocTagNode('@DummyGeneratedValue', new GenericTagValueNode('')),
 				new PhpDocTagNode('@var', new VarTagValueNode(new IdentifierTypeNode('int'), '', '')),
@@ -6790,7 +7256,7 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					new DoctrineAnnotation('@AnnotationWithConstants', [
 						new DoctrineArgument(null, new IdentifierTypeNode('PHP_EOL')),
 					]),
-					''
+					'',
 				)),
 			]),
 		];
@@ -6807,12 +7273,12 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 							new DoctrineArrayItem(null, new DoctrineArray([
 								new DoctrineArrayItem(new DoctrineConstExprStringNode('key'), new DoctrineAnnotation(
 									'@Name',
-									[]
+									[],
 								)),
 							])),
 						])),
 					]),
-					''
+					'',
 				)),
 			]),
 		];
@@ -6825,7 +7291,7 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					new DoctrineAnnotation('@AnnotationWithConstants', [
 						new DoctrineArgument(null, new ConstFetchNode('Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithConstants', 'FLOAT')),
 					]),
-					''
+					'',
 				)),
 			]),
 		];
@@ -6838,7 +7304,7 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					new DoctrineAnnotation('@AnnotationWithConstants', [
 						new DoctrineArgument(null, new ConstFetchNode('\Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithConstants', 'FLOAT')),
 					]),
-					''
+					'',
 				)),
 			]),
 		];
@@ -6854,11 +7320,11 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 						new DoctrineArgument(null, new DoctrineArray([
 							new DoctrineArrayItem(
 								new ConstFetchNode('Doctrine\Tests\Common\Annotations\Fixtures\InterfaceWithConstants', 'SOME_KEY'),
-								new ConstFetchNode('AnnotationWithConstants', 'INTEGER')
+								new ConstFetchNode('AnnotationWithConstants', 'INTEGER'),
 							),
 						])),
 					]),
-					''
+					'',
 				)),
 			]),
 		];
@@ -6873,7 +7339,7 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 							new DoctrineArrayItem(new DoctrineConstExprStringNode('foo'), new DoctrineConstExprStringNode('bar')),
 						])),
 					]),
-					''
+					'',
 				)),
 			]),
 		];
@@ -6889,11 +7355,9 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					19,
 					5,
 					null,
-					1
+					1,
 				))),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6908,11 +7372,9 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					21,
 					5,
 					null,
-					1
+					1,
 				))),
 			]),
-			null,
-			null,
 			[new Doctrine\X()],
 		];
 
@@ -6924,7 +7386,8 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					new IdentifierTypeNode('int'),
 					false,
 					'$z',
-					'@X()'
+					'@X()',
+					false,
 				)),
 			]),
 		];
@@ -6937,7 +7400,8 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					new IdentifierTypeNode('int'),
 					false,
 					'$z',
-					'@\X\Y()'
+					'@\X\Y()',
+					false,
 				)),
 			]),
 		];
@@ -6950,7 +7414,8 @@ public function provideDoctrineWithoutDoctrineCheckData(): Iterator
 					new IdentifierTypeNode('int'),
 					false,
 					'$z',
-					'@X'
+					'@X',
+					false,
 				)),
 			]),
 		];
@@ -6983,8 +7448,8 @@ public function provideSpecializedTags(): Iterator
 				new PhpDocTagNode(
 					'@special:param',
 					new GenericTagValueNode(
-						'this is special'
-					)
+						'this is special',
+					),
 				),
 			]),
 		];
@@ -6997,7 +7462,6 @@ public function provideSpecializedTags(): Iterator
 	public function testParseTagValue(string $tag, string $phpDoc, Node $expectedPhpDocNode): void
 	{
 		$this->executeTestParseTagValue($this->phpDocParser, $tag, $phpDoc, $expectedPhpDocNode);
-		$this->executeTestParseTagValue($this->phpDocParserWithRequiredWhitespaceBeforeDescription, $tag, $phpDoc, $expectedPhpDocNode);
 	}
 
 	private function executeTestParseTagValue(PhpDocParser $phpDocParser, string $tag, string $phpDoc, Node $expectedPhpDocNode): void
@@ -7145,13 +7609,13 @@ public function dataLinesAndIndexes(): iterable
 	public function testLinesAndIndexes(string $phpDoc, array $childrenLines): void
 	{
 		$tokens = new TokenIterator($this->lexer->tokenize($phpDoc));
-		$usedAttributes = [
+		$config = new ParserConfig([
 			'lines' => true,
 			'indexes' => true,
-		];
-		$constExprParser = new ConstExprParser(true, true, $usedAttributes);
-		$typeParser = new TypeParser($constExprParser, true, $usedAttributes);
-		$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, $usedAttributes);
+		]);
+		$constExprParser = new ConstExprParser($config);
+		$typeParser = new TypeParser($config, $constExprParser);
+		$phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
 		$phpDocNode = $phpDocParser->parse($tokens);
 		$children = $phpDocNode->children;
 		$this->assertCount(count($childrenLines), $children);
@@ -7229,13 +7693,13 @@ public function dataDeepNodesLinesAndIndexes(): iterable
 	public function testDeepNodesLinesAndIndexes(string $phpDoc, array $nodeAttributes): void
 	{
 		$tokens = new TokenIterator($this->lexer->tokenize($phpDoc));
-		$usedAttributes = [
+		$config = new ParserConfig([
 			'lines' => true,
 			'indexes' => true,
-		];
-		$constExprParser = new ConstExprParser(true, true, $usedAttributes);
-		$typeParser = new TypeParser($constExprParser, true, $usedAttributes);
-		$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, $usedAttributes, true);
+		]);
+		$constExprParser = new ConstExprParser($config);
+		$typeParser = new TypeParser($config, $constExprParser);
+		$phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
 		$visitor = new NodeCollectingVisitor();
 		$traverser = new NodeTraverser([$visitor]);
 		$traverser->traverse([$phpDocParser->parse($tokens)]);
@@ -7302,13 +7766,13 @@ public function dataReturnTypeLinesAndIndexes(): iterable
 	public function testReturnTypeLinesAndIndexes(string $phpDoc, array $lines): void
 	{
 		$tokens = new TokenIterator($this->lexer->tokenize($phpDoc));
-		$usedAttributes = [
+		$config = new ParserConfig([
 			'lines' => true,
 			'indexes' => true,
-		];
-		$constExprParser = new ConstExprParser(true, true, $usedAttributes);
-		$typeParser = new TypeParser($constExprParser, true, $usedAttributes);
-		$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, $usedAttributes);
+		]);
+		$constExprParser = new ConstExprParser($config);
+		$typeParser = new TypeParser($config, $constExprParser);
+		$phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
 		$phpDocNode = $phpDocParser->parse($tokens);
 		$returnTag = $phpDocNode->getReturnTagValues()[0];
 		$type = $returnTag->type;
@@ -7352,10 +7816,13 @@ public function testReturnTypeLinesAndIndexes(string $phpDoc, array $lines): voi
 	 */
 	public function testVerifyAttributes(string $label, string $input): void
 	{
-		$usedAttributes = ['lines' => true, 'indexes' => true];
-		$constExprParser = new ConstExprParser(true, true, $usedAttributes);
-		$typeParser = new TypeParser($constExprParser, true, $usedAttributes);
-		$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, $usedAttributes);
+		$config = new ParserConfig([
+			'lines' => true,
+			'indexes' => true,
+		]);
+		$constExprParser = new ConstExprParser($config);
+		$typeParser = new TypeParser($config, $constExprParser);
+		$phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
 		$tokens = new TokenIterator($this->lexer->tokenize($input));
 
 		$visitor = new NodeCollectingVisitor();
@@ -7378,8 +7845,6 @@ public function testDoctrine(
 		string $label,
 		string $input,
 		PhpDocNode $expectedPhpDocNode,
-		?PhpDocNode $withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode = null,
-		?PhpDocNode $withPreserveTypeAliasesWithInvalidTypesExpectedPhpDocNode = null,
 		array $expectedAnnotations = []
 	): void
 	{
@@ -7403,8 +7868,8 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			  ' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . '  paramA description')),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$b', PHP_EOL . '  paramB description')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . '  paramA description', false)),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$b', PHP_EOL . '  paramB description', false)),
 			]),
 		];
 
@@ -7417,9 +7882,9 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', '')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', '', false)),
 				new PhpDocTextNode(''),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$b', '')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$b', '', false)),
 			]),
 		];
 
@@ -7433,7 +7898,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', 'aaaa' . PHP_EOL . '  bbbb' . PHP_EOL . PHP_EOL . 'ccc')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', 'aaaa' . PHP_EOL . '  bbbb' . PHP_EOL . PHP_EOL . 'ccc', false)),
 			]),
 		];
 
@@ -7477,7 +7942,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
 				new PhpDocTagNode('@ORM\Column', new DoctrineTagValueNode(new DoctrineAnnotation('@ORM\Column', []), 'aaaa' . PHP_EOL . '  bbbb' . PHP_EOL . PHP_EOL . 'ccc')),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$b', '')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$b', '', false)),
 			]),
 		];
 
@@ -7490,7 +7955,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', '')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', '', false)),
 				new PhpDocTextNode(''),
 				new PhpDocTextNode(''),
 			]),
@@ -7506,7 +7971,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . PHP_EOL . PHP_EOL . 'test')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . PHP_EOL . PHP_EOL . 'test', false)),
 			]),
 		];
 
@@ -7518,7 +7983,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', 'test')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', 'test', false)),
 				new PhpDocTextNode(''),
 			]),
 		];
@@ -7532,7 +7997,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', 'test')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', 'test', false)),
 				new PhpDocTextNode(''),
 				new PhpDocTextNode(''),
 			]),
@@ -7548,7 +8013,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . ' test')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . ' test', false)),
 				new PhpDocTextNode(''),
 				new PhpDocTextNode(''),
 			]),
@@ -7565,7 +8030,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . ' test')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . ' test', false)),
 				new PhpDocTextNode(''),
 				new PhpDocTextNode(''),
 				new PhpDocTextNode(''),
@@ -7583,7 +8048,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . ' test' . PHP_EOL . PHP_EOL . 'test 2')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . ' test' . PHP_EOL . PHP_EOL . 'test 2', false)),
 				new PhpDocTextNode(''),
 			]),
 		];
@@ -7599,7 +8064,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 			' */',
 			new PhpDocNode([
 				new PhpDocTextNode('Real description'),
-				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . ' test' . PHP_EOL . PHP_EOL . 'test 2')),
+				new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('int'), false, '$a', PHP_EOL . ' test' . PHP_EOL . PHP_EOL . 'test 2', false)),
 				new PhpDocTextNode(''),
 				new PhpDocTextNode(''),
 			]),
@@ -7651,7 +8116,7 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 					new DeprecatedTagValueNode('in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In
   Drupal 9 there will be no way to set the status and in Drupal 8 this
   ability has been removed because mb_*() functions are supplied using
-  Symfony\'s polyfill.')
+  Symfony\'s polyfill.'),
 				),
 			]),
 		];
@@ -7669,8 +8134,8 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 						'in Drupal 8.6.0 and will be removed before Drupal 9.0.0. In
   Drupal 9 there will be no way to set the status and in Drupal 8 this
   ability has been removed because mb_*() functions are supplied using
-  Symfony\'s polyfill.'
-					)
+  Symfony\'s polyfill.',
+					),
 				),
 			]),
 		];
@@ -7691,24 +8156,24 @@ public function dataTextBetweenTagsBelongsToDescription(): iterable
 				new PhpDocTextNode(
 					PHP_EOL .
 					'MultiLine' . PHP_EOL .
-					'description'
+					'description',
 				),
 				new PhpDocTagNode('@param', new ParamTagValueNode(
 					new IdentifierTypeNode('bool'),
 					false,
 					'$a',
 					'',
-					false
+					false,
 				)),
 				new PhpDocTextNode(''),
 				new PhpDocTagNode('@return', new ReturnTagValueNode(
 					new IdentifierTypeNode('void'),
-					''
+					'',
 				)),
 				new PhpDocTextNode(''),
 				new PhpDocTagNode('@throws', new ThrowsTagValueNode(
 					new IdentifierTypeNode('\Exception'),
-					''
+					'',
 				)),
 				new PhpDocTextNode(''),
 			]),
@@ -7723,9 +8188,10 @@ public function testTextBetweenTagsBelongsToDescription(
 		PhpDocNode $expectedPhpDocNode
 	): void
 	{
-		$constExprParser = new ConstExprParser();
-		$typeParser = new TypeParser($constExprParser);
-		$phpDocParser = new PhpDocParser($typeParser, $constExprParser, true, true, [], true, true);
+		$config = new ParserConfig([]);
+		$constExprParser = new ConstExprParser($config);
+		$typeParser = new TypeParser($config, $constExprParser);
+		$phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
 
 		$tokens = new TokenIterator($this->lexer->tokenize($input));
 		$actualPhpDocNode = $phpDocParser->parse($tokens);
diff --git a/tests/PHPStan/Parser/TokenIteratorTest.php b/tests/PHPStan/Parser/TokenIteratorTest.php
index 6f526029..3cc51fc1 100644
--- a/tests/PHPStan/Parser/TokenIteratorTest.php
+++ b/tests/PHPStan/Parser/TokenIteratorTest.php
@@ -3,6 +3,7 @@
 namespace PHPStan\PhpDocParser\Parser;
 
 use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\ParserConfig;
 use PHPUnit\Framework\TestCase;
 use const PHP_EOL;
 
@@ -46,11 +47,12 @@ public function dataGetDetectedNewline(): iterable
 	 */
 	public function testGetDetectedNewline(string $phpDoc, ?string $expectedNewline): void
 	{
-		$lexer = new Lexer(true);
+		$config = new ParserConfig([]);
+		$lexer = new Lexer($config);
 		$tokens = new TokenIterator($lexer->tokenize($phpDoc));
-		$constExprParser = new ConstExprParser();
-		$typeParser = new TypeParser($constExprParser);
-		$phpDocParser = new PhpDocParser($typeParser, $constExprParser);
+		$constExprParser = new ConstExprParser($config);
+		$typeParser = new TypeParser($config, $constExprParser);
+		$phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser);
 		$phpDocParser->parse($tokens);
 		$this->assertSame($expectedNewline, $tokens->getDetectedNewline());
 	}
diff --git a/tests/PHPStan/Parser/TypeParserTest.php b/tests/PHPStan/Parser/TypeParserTest.php
index 5ff5dd83..8fe96f5f 100644
--- a/tests/PHPStan/Parser/TypeParserTest.php
+++ b/tests/PHPStan/Parser/TypeParserTest.php
@@ -3,13 +3,17 @@
 namespace PHPStan\PhpDocParser\Parser;
 
 use Exception;
+use PHPStan\PhpDocParser\Ast\AbstractNodeVisitor;
 use PHPStan\PhpDocParser\Ast\Attribute;
+use PHPStan\PhpDocParser\Ast\Comment;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
+use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
-use PHPStan\PhpDocParser\Ast\ConstExpr\QuoteAwareConstExprStringNode;
 use PHPStan\PhpDocParser\Ast\Node;
 use PHPStan\PhpDocParser\Ast\NodeTraverser;
+use PHPStan\PhpDocParser\Ast\NodeVisitor\CloningVisitor;
+use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
 use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
 use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
@@ -31,6 +35,7 @@
 use PHPStan\PhpDocParser\Ast\Type\TypeNode;
 use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
 use PHPStan\PhpDocParser\Lexer\Lexer;
+use PHPStan\PhpDocParser\ParserConfig;
 use PHPStan\PhpDocParser\Printer\Printer;
 use PHPUnit\Framework\TestCase;
 use function get_class;
@@ -40,17 +45,16 @@
 class TypeParserTest extends TestCase
 {
 
-	/** @var Lexer */
-	private $lexer;
+	private Lexer $lexer;
 
-	/** @var TypeParser */
-	private $typeParser;
+	private TypeParser $typeParser;
 
 	protected function setUp(): void
 	{
 		parent::setUp();
-		$this->lexer = new Lexer();
-		$this->typeParser = new TypeParser(new ConstExprParser(true, true), true);
+		$config = new ParserConfig([]);
+		$this->lexer = new Lexer($config);
+		$this->typeParser = new TypeParser($config, new ConstExprParser($config));
 	}
 
 
@@ -67,10 +71,11 @@ public function testParse(string $input, $expectedResult, int $nextTokenType = L
 
 		$tokens = new TokenIterator($this->lexer->tokenize($input));
 		$typeNode = $this->typeParser->parse($tokens);
+		$this->assertInstanceOf(TypeNode::class, $expectedResult);
 
 		$this->assertSame((string) $expectedResult, (string) $typeNode);
 		$this->assertInstanceOf(get_class($expectedResult), $typeNode);
-		$this->assertEquals($expectedResult, $typeNode);
+		$this->assertEquals($this->unsetAllAttributes($expectedResult), $this->unsetAllAttributes($typeNode));
 		$this->assertSame($nextTokenType, $tokens->currentTokenType(), Lexer::TOKEN_LABELS[$nextTokenType]);
 
 		if (strpos((string) $expectedResult, '$ref') !== false) {
@@ -117,13 +122,16 @@ public function testVerifyAttributes(string $input, $expectedResult): void
 			$this->expectExceptionMessage($expectedResult->getMessage());
 		}
 
-		$usedAttributes = ['lines' => true, 'indexes' => true];
-		$typeParser = new TypeParser(new ConstExprParser(true, true, $usedAttributes), true, $usedAttributes);
+		$config = new ParserConfig(['lines' => true, 'indexes' => true, 'comments' => true]);
+		$typeParser = new TypeParser($config, new ConstExprParser($config));
 		$tokens = new TokenIterator($this->lexer->tokenize($input));
 
+		$typeNode = $typeParser->parse($tokens);
+		$this->assertInstanceOf(TypeNode::class, $expectedResult);
+
 		$visitor = new NodeCollectingVisitor();
 		$traverser = new NodeTraverser([$visitor]);
-		$traverser->traverse([$typeParser->parse($tokens)]);
+		$traverser->traverse([$typeNode]);
 
 		foreach ($visitor->nodes as $node) {
 			$this->assertNotNull($node->getAttribute(Attribute::START_LINE), (string) $node);
@@ -131,6 +139,84 @@ public function testVerifyAttributes(string $input, $expectedResult): void
 			$this->assertNotNull($node->getAttribute(Attribute::START_INDEX), (string) $node);
 			$this->assertNotNull($node->getAttribute(Attribute::END_INDEX), (string) $node);
 		}
+
+		$this->assertEquals(
+			$this->unsetAllAttributesButComments($expectedResult),
+			$this->unsetAllAttributesButComments($typeNode),
+		);
+	}
+
+
+	private function unsetAllAttributes(Node $node): Node
+	{
+		$visitor = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				$node->setAttribute(Attribute::START_LINE, null);
+				$node->setAttribute(Attribute::END_LINE, null);
+				$node->setAttribute(Attribute::START_INDEX, null);
+				$node->setAttribute(Attribute::END_INDEX, null);
+				$node->setAttribute(Attribute::ORIGINAL_NODE, null);
+				$node->setAttribute(Attribute::COMMENTS, null);
+
+				return $node;
+			}
+
+		};
+
+		$cloningTraverser = new NodeTraverser([new CloningVisitor()]);
+		$newNodes = $cloningTraverser->traverse([$node]);
+
+		$traverser = new NodeTraverser([$visitor]);
+
+		/** @var PhpDocNode */
+		return $traverser->traverse($newNodes)[0];
+	}
+
+
+	private function unsetAllAttributesButComments(Node $node): Node
+	{
+		$visitor = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				$node->setAttribute(Attribute::START_LINE, null);
+				$node->setAttribute(Attribute::END_LINE, null);
+				$node->setAttribute(Attribute::START_INDEX, null);
+				$node->setAttribute(Attribute::END_INDEX, null);
+				$node->setAttribute(Attribute::ORIGINAL_NODE, null);
+
+				if ($node->getAttribute(Attribute::COMMENTS) === []) {
+					$node->setAttribute(Attribute::COMMENTS, null);
+				}
+
+				return $node;
+			}
+
+		};
+
+		$cloningTraverser = new NodeTraverser([new CloningVisitor()]);
+		$newNodes = $cloningTraverser->traverse([$node]);
+
+		$traverser = new NodeTraverser([$visitor]);
+
+		/** @var PhpDocNode */
+		return $traverser->traverse($newNodes)[0];
+	}
+
+
+	/**
+	 * @template TNode of Node
+	 * @param TNode $node
+	 * @return TNode
+	 */
+	public static function withComment(Node $node, string $comment, int $startLine, int $startIndex): Node
+	{
+		$comments = $node->getAttribute(Attribute::COMMENTS) ?? [];
+		$comments[] = new Comment($comment, $startLine, $startIndex);
+		$node->setAttribute(Attribute::COMMENTS, $comments);
+		return $node;
 	}
 
 
@@ -140,6 +226,100 @@ public function testVerifyAttributes(string $input, $expectedResult): void
 	public function provideParseData(): array
 	{
 		return [
+			[
+				'array{
+					// a is for apple
+					a: int,
+				}',
+				ArrayShapeNode::createSealed([
+					new ArrayShapeItemNode(
+						self::withComment(new IdentifierTypeNode('a'), '// a is for apple', 2, 3),
+						false,
+						new IdentifierTypeNode('int'),
+					),
+				]),
+			],
+			[
+				'array{
+					// a is for // apple
+					a: int,
+				}',
+				ArrayShapeNode::createSealed([
+					new ArrayShapeItemNode(
+						self::withComment(new IdentifierTypeNode('a'), '// a is for // apple', 2, 3),
+						false,
+						new IdentifierTypeNode('int'),
+					),
+				]),
+			],
+			[
+				'array{
+					// a is for * apple
+					a: int,
+				}',
+				ArrayShapeNode::createSealed([
+					new ArrayShapeItemNode(
+						self::withComment(new IdentifierTypeNode('a'), '// a is for * apple', 2, 3),
+						false,
+						new IdentifierTypeNode('int'),
+					),
+				]),
+			],
+			[
+				'array{
+					// a is for http://www.apple.com/
+					a: int,
+				}',
+				ArrayShapeNode::createSealed([
+					new ArrayShapeItemNode(
+						self::withComment(new IdentifierTypeNode('a'), '// a is for http://www.apple.com/', 2, 3),
+						false,
+						new IdentifierTypeNode('int'),
+					),
+				]),
+			],
+			[
+				'array{
+					// a is for apple
+					// a is also for awesome
+					a: int,
+				}',
+				ArrayShapeNode::createSealed([
+					new ArrayShapeItemNode(
+						self::withComment(self::withComment(new IdentifierTypeNode('a'), '// a is for apple', 2, 3), '// a is also for awesome', 3, 5),
+						false,
+						new IdentifierTypeNode('int'),
+					),
+				]),
+			],
+			[
+				'string',
+				new IdentifierTypeNode('string'),
+			],
+			[
+				'  string  ',
+				new IdentifierTypeNode('string'),
+			],
+			[
+				' ( string ) ',
+				new IdentifierTypeNode('string'),
+			],
+			[
+				'( ( string ) )',
+				new IdentifierTypeNode('string'),
+			],
+			[
+				'\\Foo\Bar\\Baz',
+				new IdentifierTypeNode('\\Foo\Bar\\Baz'),
+			],
+			[
+				'  \\Foo\Bar\\Baz  ',
+				new IdentifierTypeNode('\\Foo\Bar\\Baz'),
+			],
+			[
+				' ( \\Foo\Bar\\Baz ) ',
+				new IdentifierTypeNode('\\Foo\Bar\\Baz'),
+			],
 			[
 				'string',
 				new IdentifierTypeNode('string'),
@@ -280,13 +460,13 @@ public function provideParseData(): array
 			[
 				'string[]',
 				new ArrayTypeNode(
-					new IdentifierTypeNode('string')
+					new IdentifierTypeNode('string'),
 				),
 			],
 			[
 				'string [  ] ',
 				new ArrayTypeNode(
-					new IdentifierTypeNode('string')
+					new IdentifierTypeNode('string'),
 				),
 			],
 			[
@@ -296,7 +476,7 @@ public function provideParseData(): array
 						new IdentifierTypeNode('string'),
 						new IdentifierTypeNode('int'),
 						new IdentifierTypeNode('float'),
-					])
+					]),
 				),
 			],
 			[
@@ -304,9 +484,9 @@ public function provideParseData(): array
 				new ArrayTypeNode(
 					new ArrayTypeNode(
 						new ArrayTypeNode(
-							new IdentifierTypeNode('string')
-						)
-					)
+							new IdentifierTypeNode('string'),
+						),
+					),
 				),
 			],
 			[
@@ -314,9 +494,9 @@ public function provideParseData(): array
 				new ArrayTypeNode(
 					new ArrayTypeNode(
 						new ArrayTypeNode(
-							new IdentifierTypeNode('string')
-						)
-					)
+							new IdentifierTypeNode('string'),
+						),
+					),
 				),
 			],
 			[
@@ -328,9 +508,9 @@ public function provideParseData(): array
 								new IdentifierTypeNode('string'),
 								new IdentifierTypeNode('int'),
 								new IdentifierTypeNode('float'),
-							])
-						)
-					)
+							]),
+						),
+					),
 				),
 			],
 			[
@@ -340,7 +520,7 @@ public function provideParseData(): array
 			[
 				'?int',
 				new NullableTypeNode(
-					new IdentifierTypeNode('int')
+					new IdentifierTypeNode('int'),
 				),
 			],
 			[
@@ -353,8 +533,8 @@ public function provideParseData(): array
 						],
 						[
 							GenericTypeNode::VARIANCE_INVARIANT,
-						]
-					)
+						],
+					),
 				),
 			],
 			[
@@ -368,7 +548,25 @@ public function provideParseData(): array
 					[
 						GenericTypeNode::VARIANCE_INVARIANT,
 						GenericTypeNode::VARIANCE_INVARIANT,
-					]
+					],
+				),
+			],
+			[
+				'array<
+					// index with an int
+					int,
+					Foo\\Bar
+				>',
+				new GenericTypeNode(
+					new IdentifierTypeNode('array'),
+					[
+						new IdentifierTypeNode('int'),
+						new IdentifierTypeNode('Foo\\Bar'),
+					],
+					[
+						GenericTypeNode::VARIANCE_INVARIANT,
+						GenericTypeNode::VARIANCE_INVARIANT,
+					],
 				),
 			],
 			[
@@ -379,153 +577,154 @@ public function provideParseData(): array
 
 			[
 				'array{a: int}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
 			[
 				'array{a: ?int}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
 						new NullableTypeNode(
-							new IdentifierTypeNode('int')
-						)
+							new IdentifierTypeNode('int'),
+						),
 					),
 				]),
 			],
 			[
 				'array{a?: ?int}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						true,
 						new NullableTypeNode(
-							new IdentifierTypeNode('int')
-						)
+							new IdentifierTypeNode('int'),
+						),
 					),
 				]),
 			],
 			[
 				'array{0: int}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new ConstExprIntegerNode('0'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
 			[
 				'array{0?: int}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new ConstExprIntegerNode('0'),
 						true,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
 			[
 				'array{int, int}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						null,
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ArrayShapeItemNode(
 						null,
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
 			[
 				'array{a: int, b: string}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 				]),
 			],
 			[
 				'array{a?: int, b: string, 0: int, 1?: DateTime, hello: string}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						true,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 					new ArrayShapeItemNode(
 						new ConstExprIntegerNode('0'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ArrayShapeItemNode(
 						new ConstExprIntegerNode('1'),
 						true,
-						new IdentifierTypeNode('DateTime')
+						new IdentifierTypeNode('DateTime'),
 					),
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('hello'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 				]),
 			],
 			[
 				'array{a: int, b: array{c: callable(): int}}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new ArrayShapeNode([
+						ArrayShapeNode::createSealed([
 							new ArrayShapeItemNode(
 								new IdentifierTypeNode('c'),
 								false,
 								new CallableTypeNode(
 									new IdentifierTypeNode('callable'),
 									[],
-									new IdentifierTypeNode('int')
-								)
+									new IdentifierTypeNode('int'),
+									[],
+								),
 							),
-						])
+						]),
 					),
 				]),
 			],
 			[
 				'?array{a: int}',
 				new NullableTypeNode(
-					new ArrayShapeNode([
+					ArrayShapeNode::createSealed([
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('a'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
-					])
+					]),
 				),
 			],
 			[
@@ -534,7 +733,9 @@ public function provideParseData(): array
 					'',
 					Lexer::TOKEN_END,
 					6,
-					Lexer::TOKEN_IDENTIFIER
+					Lexer::TOKEN_IDENTIFIER,
+					null,
+					null,
 				),
 			],
 			[
@@ -543,46 +744,48 @@ public function provideParseData(): array
 					'=>',
 					Lexer::TOKEN_OTHER,
 					8,
-					Lexer::TOKEN_CLOSE_CURLY_BRACKET
+					Lexer::TOKEN_CLOSE_CURLY_BRACKET,
+					null,
+					null,
 				),
 			],
 			[
 				'array{"a": int}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
-						new QuoteAwareConstExprStringNode('a', QuoteAwareConstExprStringNode::DOUBLE_QUOTED),
+						new ConstExprStringNode('a', ConstExprStringNode::DOUBLE_QUOTED),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
 			[
 				'array{\'a\': int}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
-						new QuoteAwareConstExprStringNode('a', QuoteAwareConstExprStringNode::SINGLE_QUOTED),
+						new ConstExprStringNode('a', ConstExprStringNode::SINGLE_QUOTED),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
 			[
 				'array{\'$ref\': int}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
-						new QuoteAwareConstExprStringNode('$ref', QuoteAwareConstExprStringNode::SINGLE_QUOTED),
+						new ConstExprStringNode('$ref', ConstExprStringNode::SINGLE_QUOTED),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
 			[
 				'array{"$ref": int}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
-						new QuoteAwareConstExprStringNode('$ref', QuoteAwareConstExprStringNode::DOUBLE_QUOTED),
+						new ConstExprStringNode('$ref', ConstExprStringNode::DOUBLE_QUOTED),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
@@ -590,11 +793,11 @@ public function provideParseData(): array
 				'array{
 				 *	a: int
 				 *}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
@@ -602,11 +805,11 @@ public function provideParseData(): array
 				'array{
 				 	a: int,
 				 }',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
@@ -615,16 +818,16 @@ public function provideParseData(): array
 				 	a: int,
 				 	b: string,
 				 }',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 				]),
 			],
@@ -634,21 +837,21 @@ public function provideParseData(): array
 				 	, b: string
 				 	, c: string
 				 }',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('c'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 				]),
 			],
@@ -657,78 +860,78 @@ public function provideParseData(): array
 				 	a: int,
 				 	b: string
 				 }',
-				new ArrayShapeNode([
+				ArrayShapeNode::createSealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 				]),
 			],
 			[
 				'array{a: int, b: int, ...}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createUnsealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
-				], false),
+				], null),
 			],
 			[
 				'array{int, string, ...}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createUnsealed([
 					new ArrayShapeItemNode(
 						null,
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ArrayShapeItemNode(
 						null,
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
-				], false),
+				], null),
 			],
 			[
 				'array{...}',
-				new ArrayShapeNode([], false),
+				ArrayShapeNode::createUnsealed([], null),
 			],
 			[
 				'array{
 				 *	a: int,
 				 *	...
 				 *}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createUnsealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
-				], false),
+				], null),
 			],
 			[
 				'array{
 					a: int,
 					...,
 				}',
-				new ArrayShapeNode([
+				ArrayShapeNode::createUnsealed([
 					new ArrayShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
-				], false),
+				], null),
 			],
 			[
 				'array{int, ..., string}',
@@ -736,7 +939,9 @@ public function provideParseData(): array
 					'string',
 					Lexer::TOKEN_IDENTIFIER,
 					16,
-					Lexer::TOKEN_CLOSE_CURLY_BRACKET
+					Lexer::TOKEN_CLOSE_CURLY_BRACKET,
+					null,
+					null,
 				),
 			],
 			[
@@ -744,21 +949,20 @@ public function provideParseData(): array
 				 	int,
 				 	string
 				 }',
-				new ArrayShapeNode(
+				ArrayShapeNode::createSealed(
 					[
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('string')
+							new IdentifierTypeNode('string'),
 						),
 					],
-					true,
-					ArrayShapeNode::KIND_LIST
+					ArrayShapeNode::KIND_LIST,
 				),
 			],
 			[
@@ -766,139 +970,131 @@ public function provideParseData(): array
 				 	int,
 				 	string
 				 }',
-				new ArrayShapeNode(
+				ArrayShapeNode::createSealed(
 					[
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('string')
+							new IdentifierTypeNode('string'),
 						),
 					],
-					true,
-					ArrayShapeNode::KIND_NON_EMPTY_ARRAY
+					ArrayShapeNode::KIND_NON_EMPTY_ARRAY,
 				),
 			],
 			[
 				'callable(): non-empty-array{int, string}',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayShapeNode(
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], ArrayShapeNode::createSealed(
 					[
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('string')
+							new IdentifierTypeNode('string'),
 						),
 					],
-					true,
-					ArrayShapeNode::KIND_NON_EMPTY_ARRAY
-				)),
+					ArrayShapeNode::KIND_NON_EMPTY_ARRAY,
+				), []),
 			],
 			[
 				'callable(): non-empty-list{int, string}',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayShapeNode(
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], ArrayShapeNode::createSealed(
 					[
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('string')
+							new IdentifierTypeNode('string'),
 						),
 					],
-					true,
-					ArrayShapeNode::KIND_NON_EMPTY_LIST
-				)),
+					ArrayShapeNode::KIND_NON_EMPTY_LIST,
+				), []),
 			],
 			[
 				'non-empty-list{
 				 	int,
 				 	string
 				 }',
-				new ArrayShapeNode(
+				ArrayShapeNode::createSealed(
 					[
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('string')
+							new IdentifierTypeNode('string'),
 						),
 					],
-					true,
-					ArrayShapeNode::KIND_NON_EMPTY_LIST
+					ArrayShapeNode::KIND_NON_EMPTY_LIST,
 				),
 			],
 			[
 				'array{...<string>}',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[],
-					false,
-					ArrayShapeNode::KIND_ARRAY,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						null
-					)
+						null,
+					),
+					ArrayShapeNode::KIND_ARRAY,
 				),
 			],
 			[
 				'array{a: int, b?: int, ...<string>}',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('a'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('b'),
 							true,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_ARRAY,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						null
-					)
+						null,
+					),
 				),
 			],
 			[
 				'array{a:int,b?:int,...<string>}',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('a'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('b'),
 							true,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_ARRAY,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						null
-					)
+						null,
+					),
+					ArrayShapeNode::KIND_ARRAY,
 				),
 			],
 			[
@@ -908,83 +1104,76 @@ public function provideParseData(): array
 				. '  >  ' . PHP_EOL
 				. '  ,  ' . PHP_EOL
 				. ' }',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('a'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('b'),
 							true,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_ARRAY,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						null
-					)
+						null,
+					),
 				),
 			],
 			[
 				'array{...<int, string>}',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[],
-					false,
-					ArrayShapeNode::KIND_ARRAY,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						new IdentifierTypeNode('int')
-					)
+						new IdentifierTypeNode('int'),
+					),
+					ArrayShapeNode::KIND_ARRAY,
 				),
 			],
 			[
 				'array{a: int, b?: int, ...<int, string>}',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('a'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('b'),
 							true,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_ARRAY,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						new IdentifierTypeNode('int')
-					)
+						new IdentifierTypeNode('int'),
+					),
 				),
 			],
 			[
 				'array{a:int,b?:int,...<int,string>}',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('a'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('b'),
 							true,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_ARRAY,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						new IdentifierTypeNode('int')
-					)
+						new IdentifierTypeNode('int'),
+					),
 				),
 			],
 			[
@@ -996,83 +1185,78 @@ public function provideParseData(): array
 				. '  >  ' . PHP_EOL
 				. '  ,  ' . PHP_EOL
 				. '  }',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('a'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('b'),
 							true,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_ARRAY,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						new IdentifierTypeNode('int')
-					)
+						new IdentifierTypeNode('int'),
+					),
 				),
 			],
 			[
 				'list{...<string>}',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[],
-					false,
-					ArrayShapeNode::KIND_LIST,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						null
-					)
+						null,
+					),
+					ArrayShapeNode::KIND_LIST,
 				),
 			],
 			[
 				'list{int, int, ...<string>}',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_LIST,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						null
-					)
+						null,
+					),
+					ArrayShapeNode::KIND_LIST,
 				),
 			],
 			[
 				'list{int,int,...<string>}',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_LIST,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						null
-					)
+						null,
+					),
+					ArrayShapeNode::KIND_LIST,
 				),
 			],
 			[
@@ -1082,71 +1266,68 @@ public function provideParseData(): array
 				. '  >  ' . PHP_EOL
 				. '  ,  ' . PHP_EOL
 				. '  }',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_LIST,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						null
-					)
+						null,
+					),
+					ArrayShapeNode::KIND_LIST,
 				),
 			],
 			[
 				'list{0: int, 1?: int, ...<string>}',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							new ConstExprIntegerNode('0'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							new ConstExprIntegerNode('1'),
 							true,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_LIST,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						null
-					)
+						null,
+					),
+					ArrayShapeNode::KIND_LIST,
 				),
 			],
 			[
 				'list{0:int,1?:int,...<string>}',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							new ConstExprIntegerNode('0'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							new ConstExprIntegerNode('1'),
 							true,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_LIST,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						null
-					)
+						null,
+					),
+					ArrayShapeNode::KIND_LIST,
 				),
 			],
 			[
@@ -1156,25 +1337,24 @@ public function provideParseData(): array
 				. '  >  ' . PHP_EOL
 				. '  ,  ' . PHP_EOL
 				. '  }',
-				new ArrayShapeNode(
+				ArrayShapeNode::createUnsealed(
 					[
 						new ArrayShapeItemNode(
 							new ConstExprIntegerNode('0'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 						new ArrayShapeItemNode(
 							new ConstExprIntegerNode('1'),
 							true,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
 					],
-					false,
-					ArrayShapeNode::KIND_LIST,
 					new ArrayShapeUnsealedTypeNode(
 						new IdentifierTypeNode('string'),
-						null
-					)
+						null,
+					),
+					ArrayShapeNode::KIND_LIST,
 				),
 			],
 			[
@@ -1183,7 +1363,9 @@ public function provideParseData(): array
 					'>',
 					Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
 					10,
-					Lexer::TOKEN_IDENTIFIER
+					Lexer::TOKEN_IDENTIFIER,
+					null,
+					null,
 				),
 			],
 			[
@@ -1192,7 +1374,9 @@ public function provideParseData(): array
 					'>',
 					Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
 					14,
-					Lexer::TOKEN_IDENTIFIER
+					Lexer::TOKEN_IDENTIFIER,
+					null,
+					null,
 				),
 			],
 			[
@@ -1201,7 +1385,9 @@ public function provideParseData(): array
 					',',
 					Lexer::TOKEN_COMMA,
 					21,
-					Lexer::TOKEN_CLOSE_ANGLE_BRACKET
+					Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
+					null,
+					null,
 				),
 			],
 			[
@@ -1210,7 +1396,9 @@ public function provideParseData(): array
 					',',
 					Lexer::TOKEN_COMMA,
 					21,
-					Lexer::TOKEN_CLOSE_ANGLE_BRACKET
+					Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
+					null,
+					null,
 				),
 			],
 			[
@@ -1219,7 +1407,9 @@ public function provideParseData(): array
 					'>',
 					Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
 					9,
-					Lexer::TOKEN_IDENTIFIER
+					Lexer::TOKEN_IDENTIFIER,
+					null,
+					null,
 				),
 			],
 			[
@@ -1228,7 +1418,9 @@ public function provideParseData(): array
 					',',
 					Lexer::TOKEN_COMMA,
 					12,
-					Lexer::TOKEN_CLOSE_ANGLE_BRACKET
+					Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
+					null,
+					null,
 				),
 			],
 			[
@@ -1237,7 +1429,9 @@ public function provideParseData(): array
 					',',
 					Lexer::TOKEN_COMMA,
 					12,
-					Lexer::TOKEN_CLOSE_ANGLE_BRACKET
+					Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
+					null,
+					null,
 				),
 			],
 			[
@@ -1245,7 +1439,8 @@ public function provideParseData(): array
 				new CallableTypeNode(
 					new IdentifierTypeNode('callable'),
 					[],
-					new IdentifierTypeNode('Foo')
+					new IdentifierTypeNode('Foo'),
+					[],
 				),
 			],
 			[
@@ -1253,7 +1448,8 @@ public function provideParseData(): array
 				new CallableTypeNode(
 					new IdentifierTypeNode('pure-callable'),
 					[],
-					new IdentifierTypeNode('Foo')
+					new IdentifierTypeNode('Foo'),
+					[],
 				),
 			],
 			[
@@ -1261,7 +1457,8 @@ public function provideParseData(): array
 				new CallableTypeNode(
 					new IdentifierTypeNode('pure-Closure'),
 					[],
-					new IdentifierTypeNode('Foo')
+					new IdentifierTypeNode('Foo'),
+					[],
 				),
 			],
 			[
@@ -1270,8 +1467,9 @@ public function provideParseData(): array
 					new IdentifierTypeNode('callable'),
 					[],
 					new NullableTypeNode(
-						new IdentifierTypeNode('Foo')
-					)
+						new IdentifierTypeNode('Foo'),
+					),
+					[],
 				),
 			],
 			[
@@ -1286,8 +1484,9 @@ public function provideParseData(): array
 						],
 						[
 							GenericTypeNode::VARIANCE_INVARIANT,
-						]
-					)
+						],
+					),
+					[],
 				),
 			],
 			[
@@ -1302,8 +1501,9 @@ public function provideParseData(): array
 						],
 						[
 							GenericTypeNode::VARIANCE_INVARIANT,
-						]
-					))
+						],
+					)),
+					[],
 				),
 			],
 			[
@@ -1312,7 +1512,8 @@ public function provideParseData(): array
 					new CallableTypeNode(
 						new IdentifierTypeNode('callable'),
 						[],
-						new IdentifierTypeNode('Foo')
+						new IdentifierTypeNode('Foo'),
+						[],
 					),
 					new IdentifierTypeNode('Bar'),
 				]),
@@ -1323,7 +1524,8 @@ public function provideParseData(): array
 					new CallableTypeNode(
 						new IdentifierTypeNode('callable'),
 						[],
-						new IdentifierTypeNode('Foo')
+						new IdentifierTypeNode('Foo'),
+						[],
 					),
 					new IdentifierTypeNode('Bar'),
 				]),
@@ -1336,7 +1538,8 @@ public function provideParseData(): array
 					new UnionTypeNode([
 						new IdentifierTypeNode('Foo'),
 						new IdentifierTypeNode('Bar'),
-					])
+					]),
+					[],
 				),
 			],
 			[
@@ -1347,7 +1550,8 @@ public function provideParseData(): array
 					new IntersectionTypeNode([
 						new IdentifierTypeNode('Foo'),
 						new IdentifierTypeNode('Bar'),
-					])
+					]),
+					[],
 				),
 			],
 			[
@@ -1355,13 +1559,14 @@ public function provideParseData(): array
 				new CallableTypeNode(
 					new IdentifierTypeNode('callable'),
 					[],
-					new ArrayShapeNode([
+					ArrayShapeNode::createSealed([
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('a'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
-					])
+					]),
+					[],
 				),
 			],
 			[
@@ -1374,24 +1579,25 @@ public function provideParseData(): array
 							true,
 							true,
 							'$a',
-							true
+							true,
 						),
 						new CallableTypeParameterNode(
 							new IdentifierTypeNode('B'),
 							true,
 							true,
 							'',
-							true
+							true,
 						),
 						new CallableTypeParameterNode(
 							new IdentifierTypeNode('C'),
 							false,
 							false,
 							'',
-							false
+							false,
 						),
 					],
-					new IdentifierTypeNode('Foo')
+					new IdentifierTypeNode('Foo'),
+					[],
 				),
 			],
 			[
@@ -1404,13 +1610,13 @@ public function provideParseData(): array
 							false,
 							false,
 							'',
-							false
+							false,
 						),
 					],
 					new IdentifierTypeNode('C'),
 					[
 						new TemplateTagValueNode('A', null, ''),
-					]
+					],
 				),
 			],
 			[
@@ -1419,7 +1625,9 @@ public function provideParseData(): array
 					'>',
 					Lexer::TOKEN_END,
 					9,
-					Lexer::TOKEN_IDENTIFIER
+					Lexer::TOKEN_IDENTIFIER,
+					null,
+					null,
 				),
 			],
 			[
@@ -1432,14 +1640,14 @@ public function provideParseData(): array
 							false,
 							false,
 							'',
-							false
+							false,
 						),
 						new CallableTypeParameterNode(
 							new IdentifierTypeNode('int'),
 							false,
 							false,
 							'',
-							false
+							false,
 						),
 					],
 					new UnionTypeNode([
@@ -1448,7 +1656,7 @@ public function provideParseData(): array
 					]),
 					[
 						new TemplateTagValueNode('T', new IdentifierTypeNode('Model'), ''),
-					]
+					],
 				),
 			],
 			[
@@ -1461,26 +1669,26 @@ public function provideParseData(): array
 							false,
 							false,
 							'',
-							false
+							false,
 						),
 						new CallableTypeParameterNode(
 							new IdentifierTypeNode('Ty'),
 							false,
 							false,
 							'',
-							false
+							false,
 						),
 					],
-					new ArrayShapeNode([
+					ArrayShapeNode::createSealed([
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('Ty')
+							new IdentifierTypeNode('Ty'),
 						),
 						new ArrayShapeItemNode(
 							null,
 							false,
-							new IdentifierTypeNode('Tx')
+							new IdentifierTypeNode('Tx'),
 						),
 					]),
 					[
@@ -1489,7 +1697,7 @@ public function provideParseData(): array
 							new IdentifierTypeNode('Z'),
 						]), ''),
 						new TemplateTagValueNode('Ty', new IdentifierTypeNode('Y'), ''),
-					]
+					],
 				),
 			],
 			[
@@ -1507,7 +1715,7 @@ public function provideParseData(): array
 								[
 									GenericTypeNode::VARIANCE_INVARIANT,
 									GenericTypeNode::VARIANCE_INVARIANT,
-								]
+								],
 							),
 							new UnionTypeNode([
 								new IdentifierTypeNode('int'),
@@ -1520,17 +1728,17 @@ public function provideParseData(): array
 											],
 											[
 												GenericTypeNode::VARIANCE_INVARIANT,
-											]
+											],
 										),
 										new IdentifierTypeNode('bar'),
-									])
+									]),
 								),
 							]),
 						],
 						[
 							GenericTypeNode::VARIANCE_INVARIANT,
 							GenericTypeNode::VARIANCE_INVARIANT,
-						]
+						],
 					),
 					new IdentifierTypeNode('Lorem'),
 				]),
@@ -1544,20 +1752,20 @@ public function provideParseData(): array
 				'array[ int ]',
 				new OffsetAccessTypeNode(
 					new IdentifierTypeNode('array'),
-					new IdentifierTypeNode('int')
+					new IdentifierTypeNode('int'),
 				),
 			],
 			[
 				'self::TYPES[ int ]',
 				new OffsetAccessTypeNode(
 					new ConstTypeNode(new ConstFetchNode('self', 'TYPES')),
-					new IdentifierTypeNode('int')
+					new IdentifierTypeNode('int'),
 				),
 			],
 			[
 				"?\t\xA009", // edge-case with \h
 				new NullableTypeNode(
-					new IdentifierTypeNode("\xA009")
+					new IdentifierTypeNode("\xA009"),
 				),
 			],
 			[
@@ -1572,8 +1780,8 @@ public function provideParseData(): array
 						[
 							GenericTypeNode::VARIANCE_INVARIANT,
 							GenericTypeNode::VARIANCE_INVARIANT,
-						]
-					)
+						],
+					),
 				),
 			],
 			[
@@ -1590,21 +1798,21 @@ public function provideParseData(): array
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
-						)
+							],
+						),
 					),
 				]),
 			],
 			[
 				'array{foo: int}[]',
 				new ArrayTypeNode(
-					new ArrayShapeNode([
+					ArrayShapeNode::createSealed([
 						new ArrayShapeItemNode(
 							new IdentifierTypeNode('foo'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
-					])
+					]),
 				),
 			],
 			[
@@ -1612,20 +1820,20 @@ public function provideParseData(): array
 				new UnionTypeNode([
 					new IdentifierTypeNode('int'),
 					new ArrayTypeNode(
-						new ArrayShapeNode([
+						ArrayShapeNode::createSealed([
 							new ArrayShapeItemNode(
 								new IdentifierTypeNode('foo'),
 								false,
-								new IdentifierTypeNode('int')
+								new IdentifierTypeNode('int'),
 							),
-						])
+						]),
 					),
 				]),
 			],
 			[
 				'$this[]',
 				new ArrayTypeNode(
-					new ThisTypeNode()
+					new ThisTypeNode(),
 				),
 			],
 			[
@@ -1633,7 +1841,7 @@ public function provideParseData(): array
 				new UnionTypeNode([
 					new IdentifierTypeNode('int'),
 					new ArrayTypeNode(
-						new ThisTypeNode()
+						new ThisTypeNode(),
 					),
 				]),
 			],
@@ -1643,29 +1851,30 @@ public function provideParseData(): array
 					new IdentifierTypeNode('callable'),
 					[],
 					new ArrayTypeNode(
-						new IdentifierTypeNode('int')
-					)
+						new IdentifierTypeNode('int'),
+					),
+					[],
 				),
 			],
 			[
 				'?int[]',
 				new NullableTypeNode(
 					new ArrayTypeNode(
-						new IdentifierTypeNode('int')
-					)
+						new IdentifierTypeNode('int'),
+					),
 				),
 			],
 			[
 				'callable(mixed...): TReturn',
 				new CallableTypeNode(new IdentifierTypeNode('callable'), [
 					new CallableTypeParameterNode(new IdentifierTypeNode('mixed'), false, true, '', false),
-				], new IdentifierTypeNode('TReturn')),
+				], new IdentifierTypeNode('TReturn'), []),
 			],
 			[
 				"'foo'|'bar'",
 				new UnionTypeNode([
-					new ConstTypeNode(new QuoteAwareConstExprStringNode('foo', QuoteAwareConstExprStringNode::SINGLE_QUOTED)),
-					new ConstTypeNode(new QuoteAwareConstExprStringNode('bar', QuoteAwareConstExprStringNode::SINGLE_QUOTED)),
+					new ConstTypeNode(new ConstExprStringNode('foo', ConstExprStringNode::SINGLE_QUOTED)),
+					new ConstTypeNode(new ConstExprStringNode('bar', ConstExprStringNode::SINGLE_QUOTED)),
 				]),
 			],
 			[
@@ -1714,7 +1923,7 @@ public function provideParseData(): array
 			],
 			[
 				'"bar"',
-				new ConstTypeNode(new QuoteAwareConstExprStringNode('bar', QuoteAwareConstExprStringNode::DOUBLE_QUOTED)),
+				new ConstTypeNode(new ConstExprStringNode('bar', ConstExprStringNode::DOUBLE_QUOTED)),
 			],
 			[
 				'Foo::FOO_*',
@@ -1752,7 +1961,7 @@ public function provideParseData(): array
 			[
 				'( "foo" | Foo::FOO_* )',
 				new UnionTypeNode([
-					new ConstTypeNode(new QuoteAwareConstExprStringNode('foo', QuoteAwareConstExprStringNode::DOUBLE_QUOTED)),
+					new ConstTypeNode(new ConstExprStringNode('foo', ConstExprStringNode::DOUBLE_QUOTED)),
 					new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*')),
 				]),
 			],
@@ -1789,7 +1998,7 @@ public function provideParseData(): array
 					],
 					[
 						GenericTypeNode::VARIANCE_INVARIANT,
-					]
+					],
 				),
 			],
 			[
@@ -1806,7 +2015,7 @@ public function provideParseData(): array
 					[
 						GenericTypeNode::VARIANCE_INVARIANT,
 						GenericTypeNode::VARIANCE_INVARIANT,
-					]
+					],
 				),
 			],
 			[
@@ -1822,7 +2031,7 @@ public function provideParseData(): array
 					[
 						GenericTypeNode::VARIANCE_INVARIANT,
 						GenericTypeNode::VARIANCE_INVARIANT,
-					]
+					],
 				),
 			],
 			[
@@ -1843,13 +2052,13 @@ public function provideParseData(): array
 							],
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
 					],
 					[
 						GenericTypeNode::VARIANCE_INVARIANT,
 						GenericTypeNode::VARIANCE_INVARIANT,
-					]
+					],
 				),
 			],
 			[
@@ -1870,26 +2079,26 @@ public function provideParseData(): array
 							],
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
 					],
 					[
 						GenericTypeNode::VARIANCE_INVARIANT,
 						GenericTypeNode::VARIANCE_INVARIANT,
-					]
+					],
 				),
 			],
 			[
 				'array{}',
-				new ArrayShapeNode([]),
+				ArrayShapeNode::createSealed([]),
 			],
 			[
 				'array{}|int',
-				new UnionTypeNode([new ArrayShapeNode([]), new IdentifierTypeNode('int')]),
+				new UnionTypeNode([ArrayShapeNode::createSealed([]), new IdentifierTypeNode('int')]),
 			],
 			[
 				'int|array{}',
-				new UnionTypeNode([new IdentifierTypeNode('int'), new ArrayShapeNode([])]),
+				new UnionTypeNode([new IdentifierTypeNode('int'), ArrayShapeNode::createSealed([])]),
 			],
 			[
 				'callable(' . PHP_EOL .
@@ -1900,7 +2109,8 @@ public function provideParseData(): array
 					[
 						new CallableTypeParameterNode(new IdentifierTypeNode('Foo'), false, false, '', false),
 					],
-					new IdentifierTypeNode('void')
+					new IdentifierTypeNode('void'),
+					[],
 				),
 			],
 			[
@@ -1914,7 +2124,8 @@ public function provideParseData(): array
 						new CallableTypeParameterNode(new IdentifierTypeNode('Foo'), false, false, '', false),
 						new CallableTypeParameterNode(new IdentifierTypeNode('Bar'), false, false, '', false),
 					],
-					new IdentifierTypeNode('void')
+					new IdentifierTypeNode('void'),
+					[],
 				),
 			],
 			[
@@ -1927,7 +2138,8 @@ public function provideParseData(): array
 						new CallableTypeParameterNode(new IdentifierTypeNode('Foo'), false, false, '', false),
 						new CallableTypeParameterNode(new IdentifierTypeNode('Bar'), false, false, '', false),
 					],
-					new IdentifierTypeNode('void')
+					new IdentifierTypeNode('void'),
+					[],
 				),
 			],
 			[
@@ -1947,15 +2159,17 @@ public function provideParseData(): array
 								[
 									new CallableTypeParameterNode(new IdentifierTypeNode('Bar'), false, false, '', false),
 								],
-								new IdentifierTypeNode('void')
+								new IdentifierTypeNode('void'),
+								[],
 							),
 							false,
 							false,
 							'',
-							false
+							false,
 						),
 					],
-					new IdentifierTypeNode('void')
+					new IdentifierTypeNode('void'),
+					[],
 				),
 			],
 			[
@@ -1975,15 +2189,17 @@ public function provideParseData(): array
 								[
 									new CallableTypeParameterNode(new IdentifierTypeNode('Bar'), false, false, '', false),
 								],
-								new IdentifierTypeNode('void')
+								new IdentifierTypeNode('void'),
+								[],
 							),
 							false,
 							false,
 							'',
-							false
+							false,
 						),
 					],
-					new IdentifierTypeNode('void')
+					new IdentifierTypeNode('void'),
+					[],
 				),
 			],
 			[
@@ -1993,7 +2209,23 @@ public function provideParseData(): array
 					new IdentifierTypeNode('Bar'),
 					new IdentifierTypeNode('never'),
 					new IdentifierTypeNode('int'),
-					false
+					false,
+				),
+			],
+			[
+				'(
+					Foo is Bar
+					?
+					// never, I say
+					never
+					:
+					int)',
+				new ConditionalTypeNode(
+					new IdentifierTypeNode('Foo'),
+					new IdentifierTypeNode('Bar'),
+					new IdentifierTypeNode('never'),
+					new IdentifierTypeNode('int'),
+					false,
 				),
 			],
 			[
@@ -2003,7 +2235,7 @@ public function provideParseData(): array
 					new IdentifierTypeNode('Bar'),
 					new IdentifierTypeNode('never'),
 					new IdentifierTypeNode('int'),
-					true
+					true,
 				),
 			],
 			[
@@ -2017,9 +2249,9 @@ public function provideParseData(): array
 						new ConstTypeNode(new ConstFetchNode('self', 'TYPE_INT')),
 						new IdentifierTypeNode('int'),
 						new IdentifierTypeNode('bool'),
-						false
+						false,
 					),
-					false
+					false,
 				),
 			],
 			[
@@ -2035,7 +2267,7 @@ public function provideParseData(): array
 						new IdentifierTypeNode('int'),
 						new IdentifierTypeNode('string'),
 					]),
-					false
+					false,
 				),
 			],
 			[
@@ -2056,7 +2288,7 @@ public function provideParseData(): array
 						[
 							GenericTypeNode::VARIANCE_INVARIANT,
 							GenericTypeNode::VARIANCE_INVARIANT,
-						]
+						],
 					),
 					new ConditionalTypeNode(
 						new IdentifierTypeNode('TRandList'),
@@ -2070,7 +2302,7 @@ public function provideParseData(): array
 							[
 								GenericTypeNode::VARIANCE_INVARIANT,
 								GenericTypeNode::VARIANCE_INVARIANT,
-							]
+							],
 						),
 						new UnionTypeNode([
 							new GenericTypeNode(
@@ -2082,7 +2314,7 @@ public function provideParseData(): array
 								[
 									GenericTypeNode::VARIANCE_INVARIANT,
 									GenericTypeNode::VARIANCE_INVARIANT,
-								]
+								],
 							),
 							new GenericTypeNode(
 								new IdentifierTypeNode('LimitIterator'),
@@ -2093,12 +2325,12 @@ public function provideParseData(): array
 								[
 									GenericTypeNode::VARIANCE_INVARIANT,
 									GenericTypeNode::VARIANCE_INVARIANT,
-								]
+								],
 							),
 						]),
-						false
+						false,
 					),
-					false
+					false,
 				),
 			],
 			[
@@ -2114,7 +2346,7 @@ public function provideParseData(): array
 						new IdentifierTypeNode('int'),
 						new IdentifierTypeNode('string'),
 					]),
-					false
+					false,
 				),
 			],
 			[
@@ -2134,7 +2366,7 @@ public function provideParseData(): array
 						new IdentifierTypeNode('int'),
 						new IdentifierTypeNode('string'),
 					]),
-					false
+					false,
 				),
 			],
 			[
@@ -2143,9 +2375,9 @@ public function provideParseData(): array
 					new ConstTypeNode(
 						new ConstFetchNode(
 							'Currency',
-							'CURRENCY_*'
-						)
-					)
+							'CURRENCY_*',
+						),
+					),
 				),
 			],
 			[
@@ -2159,9 +2391,9 @@ public function provideParseData(): array
 						new IdentifierTypeNode('Bar'),
 						new IdentifierTypeNode('false'),
 						new IdentifierTypeNode('null'),
-						false
+						false,
 					),
-					false
+					false,
 				),
 			],
 			[
@@ -2170,7 +2402,9 @@ public function provideParseData(): array
 					'is',
 					Lexer::TOKEN_IDENTIFIER,
 					14,
-					Lexer::TOKEN_COLON
+					Lexer::TOKEN_COLON,
+					null,
+					null,
 				),
 			],
 			[
@@ -2184,9 +2418,9 @@ public function provideParseData(): array
 						new IdentifierTypeNode('Bar'),
 						new IdentifierTypeNode('false'),
 						new IdentifierTypeNode('null'),
-						false
+						false,
 					),
-					false
+					false,
 				),
 			],
 			[
@@ -2195,7 +2429,9 @@ public function provideParseData(): array
 					'$foo',
 					Lexer::TOKEN_VARIABLE,
 					15,
-					Lexer::TOKEN_IDENTIFIER
+					Lexer::TOKEN_IDENTIFIER,
+					null,
+					null,
 				),
 			],
 			[
@@ -2209,7 +2445,7 @@ public function provideParseData(): array
 					[
 						GenericTypeNode::VARIANCE_COVARIANT,
 						GenericTypeNode::VARIANCE_INVARIANT,
-					]
+					],
 				),
 			],
 			[
@@ -2223,7 +2459,7 @@ public function provideParseData(): array
 					[
 						GenericTypeNode::VARIANCE_INVARIANT,
 						GenericTypeNode::VARIANCE_CONTRAVARIANT,
-					]
+					],
 				),
 			],
 			[
@@ -2232,7 +2468,9 @@ public function provideParseData(): array
 					'>',
 					Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
 					13,
-					Lexer::TOKEN_IDENTIFIER
+					Lexer::TOKEN_IDENTIFIER,
+					null,
+					null,
 				),
 			],
 			[
@@ -2241,7 +2479,9 @@ public function provideParseData(): array
 					'Bar',
 					Lexer::TOKEN_IDENTIFIER,
 					16,
-					Lexer::TOKEN_CLOSE_ANGLE_BRACKET
+					Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
+					null,
+					null,
 				),
 			],
 			[
@@ -2255,7 +2495,7 @@ public function provideParseData(): array
 					[
 						GenericTypeNode::VARIANCE_INVARIANT,
 						GenericTypeNode::VARIANCE_BIVARIANT,
-					]
+					],
 				),
 			],
 			[
@@ -2264,7 +2504,7 @@ public function provideParseData(): array
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
@@ -2275,8 +2515,8 @@ public function provideParseData(): array
 						new IdentifierTypeNode('a'),
 						false,
 						new NullableTypeNode(
-							new IdentifierTypeNode('int')
-						)
+							new IdentifierTypeNode('int'),
+						),
 					),
 				]),
 			],
@@ -2287,8 +2527,8 @@ public function provideParseData(): array
 						new IdentifierTypeNode('a'),
 						true,
 						new NullableTypeNode(
-							new IdentifierTypeNode('int')
-						)
+							new IdentifierTypeNode('int'),
+						),
 					),
 				]),
 			],
@@ -2298,12 +2538,12 @@ public function provideParseData(): array
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 				]),
 			],
@@ -2313,22 +2553,23 @@ public function provideParseData(): array
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new ArrayShapeNode([
+						ArrayShapeNode::createSealed([
 							new ArrayShapeItemNode(
 								new IdentifierTypeNode('c'),
 								false,
 								new CallableTypeNode(
 									new IdentifierTypeNode('callable'),
 									[],
-									new IdentifierTypeNode('int')
-								)
+									new IdentifierTypeNode('int'),
+									[],
+								),
 							),
-						])
+						]),
 					),
 				]),
 			],
@@ -2338,7 +2579,7 @@ public function provideParseData(): array
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('b'),
@@ -2350,10 +2591,11 @@ public function provideParseData(): array
 								new CallableTypeNode(
 									new IdentifierTypeNode('callable'),
 									[],
-									new IdentifierTypeNode('int')
-								)
+									new IdentifierTypeNode('int'),
+									[],
+								),
 							),
-						])
+						]),
 					),
 				]),
 			],
@@ -2364,9 +2606,9 @@ public function provideParseData(): array
 						new ObjectShapeItemNode(
 							new IdentifierTypeNode('a'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
-					])
+					]),
 				),
 			],
 			[
@@ -2375,7 +2617,9 @@ public function provideParseData(): array
 					'',
 					Lexer::TOKEN_END,
 					7,
-					Lexer::TOKEN_IDENTIFIER
+					Lexer::TOKEN_IDENTIFIER,
+					null,
+					null,
 				),
 			],
 			[
@@ -2384,7 +2628,9 @@ public function provideParseData(): array
 					'=>',
 					Lexer::TOKEN_OTHER,
 					9,
-					Lexer::TOKEN_COLON
+					Lexer::TOKEN_COLON,
+					null,
+					null,
 				),
 			],
 			[
@@ -2393,7 +2639,9 @@ public function provideParseData(): array
 					'}',
 					Lexer::TOKEN_CLOSE_CURLY_BRACKET,
 					10,
-					Lexer::TOKEN_COLON
+					Lexer::TOKEN_COLON,
+					null,
+					null,
 				),
 			],
 			[
@@ -2402,7 +2650,9 @@ public function provideParseData(): array
 					'0',
 					Lexer::TOKEN_END,
 					7,
-					Lexer::TOKEN_IDENTIFIER
+					Lexer::TOKEN_IDENTIFIER,
+					null,
+					null,
 				),
 			],
 			[
@@ -2411,16 +2661,18 @@ public function provideParseData(): array
 					'0',
 					Lexer::TOKEN_END,
 					7,
-					Lexer::TOKEN_IDENTIFIER
+					Lexer::TOKEN_IDENTIFIER,
+					null,
+					null,
 				),
 			],
 			[
 				'object{"a": int}',
 				new ObjectShapeNode([
 					new ObjectShapeItemNode(
-						new QuoteAwareConstExprStringNode('a', QuoteAwareConstExprStringNode::DOUBLE_QUOTED),
+						new ConstExprStringNode('a', ConstExprStringNode::DOUBLE_QUOTED),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
@@ -2428,9 +2680,9 @@ public function provideParseData(): array
 				'object{\'a\': int}',
 				new ObjectShapeNode([
 					new ObjectShapeItemNode(
-						new QuoteAwareConstExprStringNode('a', QuoteAwareConstExprStringNode::SINGLE_QUOTED),
+						new ConstExprStringNode('a', ConstExprStringNode::SINGLE_QUOTED),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
@@ -2438,9 +2690,9 @@ public function provideParseData(): array
 				'object{\'$ref\': int}',
 				new ObjectShapeNode([
 					new ObjectShapeItemNode(
-						new QuoteAwareConstExprStringNode('$ref', QuoteAwareConstExprStringNode::SINGLE_QUOTED),
+						new ConstExprStringNode('$ref', ConstExprStringNode::SINGLE_QUOTED),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
@@ -2448,9 +2700,9 @@ public function provideParseData(): array
 				'object{"$ref": int}',
 				new ObjectShapeNode([
 					new ObjectShapeItemNode(
-						new QuoteAwareConstExprStringNode('$ref', QuoteAwareConstExprStringNode::DOUBLE_QUOTED),
+						new ConstExprStringNode('$ref', ConstExprStringNode::DOUBLE_QUOTED),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
@@ -2462,7 +2714,7 @@ public function provideParseData(): array
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
@@ -2474,7 +2726,20 @@ public function provideParseData(): array
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
+					),
+				]),
+			],
+			[
+				'object{
+					// a is for apple
+				 	a: int,
+				 }',
+				new ObjectShapeNode([
+					new ObjectShapeItemNode(
+						self::withComment(new IdentifierTypeNode('a'), '// a is for apple', 2, 3),
+						false,
+						new IdentifierTypeNode('int'),
 					),
 				]),
 			],
@@ -2487,12 +2752,12 @@ public function provideParseData(): array
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 				]),
 			],
@@ -2506,17 +2771,17 @@ public function provideParseData(): array
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('c'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 				]),
 			],
@@ -2529,12 +2794,12 @@ public function provideParseData(): array
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('a'),
 						false,
-						new IdentifierTypeNode('int')
+						new IdentifierTypeNode('int'),
 					),
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('b'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 				]),
 			],
@@ -2545,9 +2810,9 @@ public function provideParseData(): array
 						new ObjectShapeItemNode(
 							new IdentifierTypeNode('foo'),
 							false,
-							new IdentifierTypeNode('int')
+							new IdentifierTypeNode('int'),
 						),
-					])
+					]),
 				),
 			],
 			[
@@ -2559,9 +2824,9 @@ public function provideParseData(): array
 							new ObjectShapeItemNode(
 								new IdentifierTypeNode('foo'),
 								false,
-								new IdentifierTypeNode('int')
+								new IdentifierTypeNode('int'),
 							),
-						])
+						]),
 					),
 				]),
 			],
@@ -2583,12 +2848,12 @@ public function provideParseData(): array
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('attribute'),
 						false,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 					new ObjectShapeItemNode(
 						new IdentifierTypeNode('value'),
 						true,
-						new IdentifierTypeNode('string')
+						new IdentifierTypeNode('string'),
 					),
 				]),
 			],
@@ -2604,19 +2869,21 @@ public function provideParseData(): array
 						[
 							new CallableTypeParameterNode(new IdentifierTypeNode('Foo'), false, false, '', false),
 						],
-						new IdentifierTypeNode('Bar')
-					)
+						new IdentifierTypeNode('Bar'),
+						[],
+					),
+					[],
 				),
 			],
 			[
 				'callable(): ?int',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new IdentifierTypeNode('int'))),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new IdentifierTypeNode('int')), []),
 			],
 			[
 				'callable(): object{foo: int}',
 				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ObjectShapeNode([
 					new ObjectShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
-				])),
+				]), []),
 			],
 			[
 				'callable(): object{foo: int}[]',
@@ -2626,33 +2893,34 @@ public function provideParseData(): array
 					new ArrayTypeNode(
 						new ObjectShapeNode([
 							new ObjectShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
-						])
-					)
+						]),
+					),
+					[],
 				),
 			],
 			[
 				'callable(): int[][][]',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new ArrayTypeNode(new ArrayTypeNode(new IdentifierTypeNode('int'))))),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new ArrayTypeNode(new ArrayTypeNode(new IdentifierTypeNode('int')))), []),
 			],
 			[
 				'callable(): (int[][][])',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new ArrayTypeNode(new ArrayTypeNode(new IdentifierTypeNode('int'))))),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new ArrayTypeNode(new ArrayTypeNode(new IdentifierTypeNode('int')))), []),
 			],
 			[
 				'(callable(): int[])[][]',
 				new ArrayTypeNode(
 					new ArrayTypeNode(
-						new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new IdentifierTypeNode('int')))
-					)
+						new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new IdentifierTypeNode('int')), []),
+					),
 				),
 			],
 			[
 				'callable(): $this',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ThisTypeNode()),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ThisTypeNode(), []),
 			],
 			[
 				'callable(): $this[]',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new ThisTypeNode())),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new ThisTypeNode()), []),
 			],
 			[
 				'2.5|3',
@@ -2663,29 +2931,29 @@ public function provideParseData(): array
 			],
 			[
 				'callable(): 3.5',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstExprFloatNode('3.5'))),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstExprFloatNode('3.5')), []),
 			],
 			[
 				'callable(): 3.5[]',
 				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(
-					new ConstTypeNode(new ConstExprFloatNode('3.5'))
-				)),
+					new ConstTypeNode(new ConstExprFloatNode('3.5')),
+				), []),
 			],
 			[
 				'callable(): Foo',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new IdentifierTypeNode('Foo')),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new IdentifierTypeNode('Foo'), []),
 			],
 			[
 				'callable(): (Foo)[]',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new IdentifierTypeNode('Foo'))),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new IdentifierTypeNode('Foo')), []),
 			],
 			[
 				'callable(): Foo::BAR',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstFetchNode('Foo', 'BAR'))),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstFetchNode('Foo', 'BAR')), []),
 			],
 			[
 				'callable(): Foo::*',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstFetchNode('Foo', '*'))),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstFetchNode('Foo', '*')), []),
 			],
 			[
 				'?Foo[]',
@@ -2693,11 +2961,11 @@ public function provideParseData(): array
 			],
 			[
 				'callable(): ?Foo',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new IdentifierTypeNode('Foo'))),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new IdentifierTypeNode('Foo')), []),
 			],
 			[
 				'callable(): ?Foo[]',
-				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new ArrayTypeNode(new IdentifierTypeNode('Foo')))),
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new ArrayTypeNode(new IdentifierTypeNode('Foo'))), []),
 			],
 			[
 				'?(Foo|Bar)',
@@ -2765,8 +3033,8 @@ public function provideParseData(): array
 					new GenericTypeNode(new IdentifierTypeNode('class-string'), [new IdentifierTypeNode('TService')], ['invariant']),
 					new IdentifierTypeNode('TService'),
 					new IdentifierTypeNode('mixed'),
-					false
-				)),
+					false,
+				), []),
 			],
 			[
 				'MongoCollection <p>Returns a collection object representing the new collection.</p>',
@@ -2785,9 +3053,7 @@ public function dataLinesAndIndexes(): iterable
 			'int | object{foo: int}[]',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'int | object{foo: int}[]',
 					1,
 					1,
@@ -2795,9 +3061,7 @@ static function (TypeNode $typeNode): TypeNode {
 					12,
 				],
 				[
-					static function (UnionTypeNode $typeNode): TypeNode {
-						return $typeNode->types[0];
-					},
+					static fn (UnionTypeNode $typeNode): TypeNode => $typeNode->types[0],
 					'int',
 					1,
 					1,
@@ -2805,9 +3069,7 @@ static function (UnionTypeNode $typeNode): TypeNode {
 					0,
 				],
 				[
-					static function (UnionTypeNode $typeNode): TypeNode {
-						return $typeNode->types[1];
-					},
+					static fn (UnionTypeNode $typeNode): TypeNode => $typeNode->types[1],
 					'object{foo: int}[]',
 					1,
 					1,
@@ -2821,9 +3083,7 @@ static function (UnionTypeNode $typeNode): TypeNode {
 			'int | object{foo: int}[]    ',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'int | object{foo: int}[]',
 					1,
 					1,
@@ -2831,9 +3091,7 @@ static function (TypeNode $typeNode): TypeNode {
 					12,
 				],
 				[
-					static function (UnionTypeNode $typeNode): TypeNode {
-						return $typeNode->types[0];
-					},
+					static fn (UnionTypeNode $typeNode): TypeNode => $typeNode->types[0],
 					'int',
 					1,
 					1,
@@ -2841,9 +3099,7 @@ static function (UnionTypeNode $typeNode): TypeNode {
 					0,
 				],
 				[
-					static function (UnionTypeNode $typeNode): TypeNode {
-						return $typeNode->types[1];
-					},
+					static fn (UnionTypeNode $typeNode): TypeNode => $typeNode->types[1],
 					'object{foo: int}[]',
 					1,
 					1,
@@ -2860,9 +3116,7 @@ static function (UnionTypeNode $typeNode): TypeNode {
 			 }',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'array{
 				a: int,
 				b: string
@@ -2879,9 +3133,7 @@ static function (TypeNode $typeNode): TypeNode {
 			'callable(Foo, Bar): void',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'callable(Foo, Bar): void',
 					1,
 					1,
@@ -2889,9 +3141,7 @@ static function (TypeNode $typeNode): TypeNode {
 					9,
 				],
 				[
-					static function (CallableTypeNode $typeNode): TypeNode {
-						return $typeNode->identifier;
-					},
+					static fn (CallableTypeNode $typeNode): TypeNode => $typeNode->identifier,
 					'callable',
 					1,
 					1,
@@ -2899,9 +3149,7 @@ static function (CallableTypeNode $typeNode): TypeNode {
 					0,
 				],
 				[
-					static function (CallableTypeNode $typeNode): Node {
-						return $typeNode->parameters[0];
-					},
+					static fn (CallableTypeNode $typeNode): Node => $typeNode->parameters[0],
 					'Foo',
 					1,
 					1,
@@ -2909,9 +3157,7 @@ static function (CallableTypeNode $typeNode): Node {
 					2,
 				],
 				[
-					static function (CallableTypeNode $typeNode): TypeNode {
-						return $typeNode->returnType;
-					},
+					static fn (CallableTypeNode $typeNode): TypeNode => $typeNode->returnType,
 					'void',
 					1,
 					1,
@@ -2925,9 +3171,7 @@ static function (CallableTypeNode $typeNode): TypeNode {
 			'$this',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'$this',
 					1,
 					1,
@@ -2941,9 +3185,7 @@ static function (TypeNode $typeNode): TypeNode {
 			'array{foo: int}',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'array{foo: int}',
 					1,
 					1,
@@ -2951,9 +3193,7 @@ static function (TypeNode $typeNode): TypeNode {
 					6,
 				],
 				[
-					static function (ArrayShapeNode $typeNode): TypeNode {
-						return $typeNode->items[0];
-					},
+					static fn (ArrayShapeNode $typeNode): Node => $typeNode->items[0],
 					'foo: int',
 					1,
 					1,
@@ -2967,9 +3207,7 @@ static function (ArrayShapeNode $typeNode): TypeNode {
 			'array{}',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'array{}',
 					1,
 					1,
@@ -2983,9 +3221,7 @@ static function (TypeNode $typeNode): TypeNode {
 			'object{foo: int}',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'object{foo: int}',
 					1,
 					1,
@@ -2993,9 +3229,7 @@ static function (TypeNode $typeNode): TypeNode {
 					6,
 				],
 				[
-					static function (ObjectShapeNode $typeNode): TypeNode {
-						return $typeNode->items[0];
-					},
+					static fn (ObjectShapeNode $typeNode): Node => $typeNode->items[0],
 					'foo: int',
 					1,
 					1,
@@ -3009,9 +3243,7 @@ static function (ObjectShapeNode $typeNode): TypeNode {
 			'object{}',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'object{}',
 					1,
 					1,
@@ -3025,9 +3257,7 @@ static function (TypeNode $typeNode): TypeNode {
 			'object{}[]',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'object{}[]',
 					1,
 					1,
@@ -3041,9 +3271,7 @@ static function (TypeNode $typeNode): TypeNode {
 			'int[][][]',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'int[][][]',
 					1,
 					1,
@@ -3051,9 +3279,7 @@ static function (TypeNode $typeNode): TypeNode {
 					6,
 				],
 				[
-					static function (ArrayTypeNode $typeNode): TypeNode {
-						return $typeNode->type;
-					},
+					static fn (ArrayTypeNode $typeNode): TypeNode => $typeNode->type,
 					'int[][]',
 					1,
 					1,
@@ -3098,9 +3324,7 @@ static function (ArrayTypeNode $typeNode): TypeNode {
 			'int[foo][bar][baz]',
 			[
 				[
-					static function (TypeNode $typeNode): TypeNode {
-						return $typeNode;
-					},
+					static fn (TypeNode $typeNode): TypeNode => $typeNode,
 					'int[foo][bar][baz]',
 					1,
 					1,
@@ -3108,9 +3332,7 @@ static function (TypeNode $typeNode): TypeNode {
 					9,
 				],
 				[
-					static function (OffsetAccessTypeNode $typeNode): TypeNode {
-						return $typeNode->type;
-					},
+					static fn (OffsetAccessTypeNode $typeNode): TypeNode => $typeNode->type,
 					'int[foo][bar]',
 					1,
 					1,
@@ -3118,9 +3340,7 @@ static function (OffsetAccessTypeNode $typeNode): TypeNode {
 					6,
 				],
 				[
-					static function (OffsetAccessTypeNode $typeNode): TypeNode {
-						return $typeNode->offset;
-					},
+					static fn (OffsetAccessTypeNode $typeNode): TypeNode => $typeNode->offset,
 					'baz',
 					1,
 					1,
@@ -3201,11 +3421,11 @@ public function testLinesAndIndexes(string $input, array $assertions): void
 	{
 		$tokensArray = $this->lexer->tokenize($input);
 		$tokens = new TokenIterator($tokensArray);
-		$usedAttributes = [
+		$config = new ParserConfig([
 			'lines' => true,
 			'indexes' => true,
-		];
-		$typeParser = new TypeParser(new ConstExprParser(true, true), true, $usedAttributes);
+		]);
+		$typeParser = new TypeParser($config, new ConstExprParser($config));
 		$typeNode = $typeParser->parse($tokens);
 
 		foreach ($assertions as [$callable, $expectedContent, $startLine, $endLine, $startIndex, $endIndex]) {
diff --git a/tests/PHPStan/Printer/DifferTest.php b/tests/PHPStan/Printer/DifferTest.php
index b06f1692..cca1f61c 100644
--- a/tests/PHPStan/Printer/DifferTest.php
+++ b/tests/PHPStan/Printer/DifferTest.php
@@ -44,9 +44,7 @@ private function formatDiffString(array $diff): string
 	 */
 	public function testDiff(string $oldStr, string $newStr, string $expectedDiffStr): void
 	{
-		$differ = new Differ(static function ($a, $b) {
-			return $a === $b;
-		});
+		$differ = new Differ(static fn ($a, $b) => $a === $b);
 		$diff = $differ->diff(str_split($oldStr), str_split($newStr));
 		$this->assertSame($expectedDiffStr, $this->formatDiffString($diff));
 	}
@@ -72,9 +70,7 @@ public function provideTestDiff(): array
 	 */
 	public function testDiffWithReplacements(string $oldStr, string $newStr, string $expectedDiffStr): void
 	{
-		$differ = new Differ(static function ($a, $b) {
-			return $a === $b;
-		});
+		$differ = new Differ(static fn ($a, $b) => $a === $b);
 		$diff = $differ->diffWithReplacements(str_split($oldStr), str_split($newStr));
 		$this->assertSame($expectedDiffStr, $this->formatDiffString($diff));
 	}
diff --git a/tests/PHPStan/Printer/IntegrationPrinterWithPhpParserTest.php b/tests/PHPStan/Printer/IntegrationPrinterWithPhpParserTest.php
index 74d05072..045156e8 100644
--- a/tests/PHPStan/Printer/IntegrationPrinterWithPhpParserTest.php
+++ b/tests/PHPStan/Printer/IntegrationPrinterWithPhpParserTest.php
@@ -2,13 +2,15 @@
 
 namespace PHPStan\PhpDocParser\Printer;
 
+use LogicException;
 use PhpParser\Comment\Doc;
-use PhpParser\Lexer\Emulative;
+use PhpParser\Internal\TokenStream;
 use PhpParser\Node as PhpNode;
 use PhpParser\NodeTraverser as PhpParserNodeTraverser;
 use PhpParser\NodeVisitor\CloningVisitor as PhpParserCloningVisitor;
 use PhpParser\NodeVisitorAbstract;
-use PhpParser\Parser\Php7;
+use PhpParser\ParserFactory;
+use PhpParser\PrettyPrinter\Standard;
 use PHPStan\PhpDocParser\Ast\AbstractNodeVisitor;
 use PHPStan\PhpDocParser\Ast\Node;
 use PHPStan\PhpDocParser\Ast\NodeTraverser;
@@ -22,12 +24,16 @@
 use PHPStan\PhpDocParser\Parser\PhpDocParser;
 use PHPStan\PhpDocParser\Parser\TokenIterator;
 use PHPStan\PhpDocParser\Parser\TypeParser;
+use PHPStan\PhpDocParser\ParserConfig;
 use PHPUnit\Framework\TestCase;
 use function file_get_contents;
+use function str_repeat;
 
 class IntegrationPrinterWithPhpParserTest extends TestCase
 {
 
+	private const TAB_WIDTH = 4;
+
 	/**
 	 * @return iterable<array{string, string, NodeVisitor}>
 	 */
@@ -42,7 +48,8 @@ public function enterNode(Node $node)
 						new IdentifierTypeNode('Bar'),
 						false,
 						'$b',
-						''
+						'',
+						false,
 					));
 				}
 				return $node;
@@ -66,32 +73,31 @@ public function enterNode(Node $node)
 	 */
 	public function testPrint(string $file, string $expectedFile, NodeVisitor $visitor): void
 	{
-		$lexer = new Emulative([
-			'usedAttributes' => [
-				'comments',
-				'startLine', 'endLine',
-				'startTokenPos', 'endTokenPos',
-			],
-		]);
-		$phpParser = new Php7($lexer);
+		$phpParserFactory = new ParserFactory();
+		$phpParser = $phpParserFactory->createForNewestSupportedVersion();
 		$phpTraverser = new PhpParserNodeTraverser();
 		$phpTraverser->addVisitor(new PhpParserCloningVisitor());
 
-		$printer = new PhpPrinter();
 		$fileContents = file_get_contents($file);
 		if ($fileContents === false) {
 			$this->fail('Could not read ' . $file);
 		}
 
-		/** @var PhpNode[] $oldStmts */
 		$oldStmts = $phpParser->parse($fileContents);
-		$oldTokens = $lexer->getTokens();
+		if ($oldStmts === null) {
+			throw new LogicException();
+		}
+		$oldTokens = $phpParser->getTokens();
+
+		$phpTraverserIndent = new PhpParserNodeTraverser();
+		$indentDetector = new PhpPrinterIndentationDetectorVisitor(new TokenStream($oldTokens, self::TAB_WIDTH));
+		$phpTraverserIndent->addVisitor($indentDetector);
+		$phpTraverserIndent->traverse($oldStmts);
 
 		$phpTraverser2 = new PhpParserNodeTraverser();
 		$phpTraverser2->addVisitor(new class ($visitor) extends NodeVisitorAbstract {
 
-			/** @var NodeVisitor */
-			private $visitor;
+			private NodeVisitor $visitor;
 
 			public function __construct(NodeVisitor $visitor)
 			{
@@ -106,16 +112,14 @@ public function enterNode(PhpNode $phpNode)
 
 				$phpDoc = $phpNode->getDocComment()->getText();
 
-				$usedAttributes = ['lines' => true, 'indexes' => true];
-				$constExprParser = new ConstExprParser(true, true, $usedAttributes);
+				$config = new ParserConfig(['lines' => true, 'indexes' => true]);
+				$constExprParser = new ConstExprParser($config);
 				$phpDocParser = new PhpDocParser(
-					new TypeParser($constExprParser, true, $usedAttributes),
+					$config,
+					new TypeParser($config, $constExprParser),
 					$constExprParser,
-					true,
-					true,
-					$usedAttributes
 				);
-				$lexer = new Lexer();
+				$lexer = new Lexer($config);
 				$tokens = new TokenIterator($lexer->tokenize($phpDoc));
 				$phpDocNode = $phpDocParser->parse($tokens);
 				$cloningTraverser = new NodeTraverser([new NodeVisitor\CloningVisitor()]);
@@ -139,6 +143,7 @@ public function enterNode(PhpNode $phpNode)
 		$newStmts = $phpTraverser->traverse($oldStmts);
 		$newStmts = $phpTraverser2->traverse($newStmts);
 
+		$printer = new Standard(['indent' => str_repeat($indentDetector->indentCharacter, $indentDetector->indentSize)]);
 		$newCode = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens);
 		$this->assertStringEqualsFile($expectedFile, $newCode);
 	}
diff --git a/tests/PHPStan/Printer/PhpPrinter.php b/tests/PHPStan/Printer/PhpPrinter.php
deleted file mode 100644
index d691a981..00000000
--- a/tests/PHPStan/Printer/PhpPrinter.php
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php declare(strict_types = 1);
-
-namespace PHPStan\PhpDocParser\Printer;
-
-use PhpParser\NodeTraverser;
-use PhpParser\PrettyPrinter\Standard;
-use function str_repeat;
-
-class PhpPrinter extends Standard
-{
-
-	/** @var string */
-	private $indentCharacter = ' ';
-
-	/** @var int */
-	private $indentSize = 4;
-
-	protected function resetState(): void
-	{
-		parent::resetState();
-		$this->indentCharacter = ' ';
-		$this->indentSize = 4;
-	}
-
-	protected function preprocessNodes(array $nodes): void
-	{
-		parent::preprocessNodes($nodes);
-		if ($this->origTokens === null) {
-			return;
-		}
-
-		$traverser = new NodeTraverser();
-
-		$visitor = new PhpPrinterIndentationDetectorVisitor($this->origTokens);
-		$traverser->addVisitor($visitor);
-		$traverser->traverse($nodes);
-
-		$this->indentCharacter = $visitor->indentCharacter;
-		$this->indentSize = $visitor->indentSize;
-	}
-
-	protected function setIndentLevel(int $level): void
-	{
-		$this->indentLevel = $level;
-		$this->nl = "\n" . str_repeat($this->indentCharacter, $level);
-	}
-
-	protected function indent(): void
-	{
-		$this->indentLevel += $this->indentSize;
-		$this->nl = "\n" . str_repeat($this->indentCharacter, $this->indentLevel);
-	}
-
-	protected function outdent(): void
-	{
-		$this->indentLevel -= $this->indentSize;
-		$this->nl = "\n" . str_repeat($this->indentCharacter, $this->indentLevel);
-	}
-
-}
diff --git a/tests/PHPStan/Printer/PhpPrinterIndentationDetectorVisitor.php b/tests/PHPStan/Printer/PhpPrinterIndentationDetectorVisitor.php
index 58a807eb..49987eb3 100644
--- a/tests/PHPStan/Printer/PhpPrinterIndentationDetectorVisitor.php
+++ b/tests/PHPStan/Printer/PhpPrinterIndentationDetectorVisitor.php
@@ -4,7 +4,7 @@
 
 use PhpParser\Internal\TokenStream;
 use PhpParser\Node;
-use PhpParser\NodeTraverser;
+use PhpParser\NodeVisitor;
 use PhpParser\NodeVisitorAbstract;
 use function count;
 use function preg_match;
@@ -16,14 +16,11 @@
 class PhpPrinterIndentationDetectorVisitor extends NodeVisitorAbstract
 {
 
-	/** @var string */
-	public $indentCharacter = ' ';
+	public string $indentCharacter = ' ';
 
-	/** @var int */
-	public $indentSize = 4;
+	public int $indentSize = 4;
 
-	/** @var TokenStream */
-	private $origTokens;
+	private TokenStream $origTokens;
 
 	public function __construct(TokenStream $origTokens)
 	{
@@ -74,7 +71,7 @@ public function enterNode(Node $node)
 			$this->indentCharacter = $char;
 			$this->indentSize = $size;
 
-			return NodeTraverser::STOP_TRAVERSAL;
+			return NodeVisitor::STOP_TRAVERSAL;
 		}
 
 		return null;
diff --git a/tests/PHPStan/Printer/PrinterTest.php b/tests/PHPStan/Printer/PrinterTest.php
index e4effc31..464b7234 100644
--- a/tests/PHPStan/Printer/PrinterTest.php
+++ b/tests/PHPStan/Printer/PrinterTest.php
@@ -4,11 +4,12 @@
 
 use PHPStan\PhpDocParser\Ast\AbstractNodeVisitor;
 use PHPStan\PhpDocParser\Ast\Attribute;
+use PHPStan\PhpDocParser\Ast\Comment;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayItemNode;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
+use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
-use PHPStan\PhpDocParser\Ast\ConstExpr\QuoteAwareConstExprStringNode;
 use PHPStan\PhpDocParser\Ast\Node;
 use PHPStan\PhpDocParser\Ast\NodeTraverser;
 use PHPStan\PhpDocParser\Ast\NodeVisitor;
@@ -48,36 +49,41 @@
 use PHPStan\PhpDocParser\Parser\PhpDocParser;
 use PHPStan\PhpDocParser\Parser\TokenIterator;
 use PHPStan\PhpDocParser\Parser\TypeParser;
+use PHPStan\PhpDocParser\ParserConfig;
 use PHPUnit\Framework\TestCase;
+use function array_map;
 use function array_pop;
+use function array_slice;
 use function array_splice;
 use function array_unshift;
 use function array_values;
 use function assert;
 use function count;
+use function implode;
+use function preg_match;
+use function preg_replace_callback;
+use function preg_split;
+use function str_repeat;
+use function str_replace;
+use function strlen;
 use const PHP_EOL;
 
 class PrinterTest extends TestCase
 {
 
-	/** @var TypeParser */
-	private $typeParser;
+	private TypeParser $typeParser;
 
-	/** @var PhpDocParser */
-	private $phpDocParser;
+	private PhpDocParser $phpDocParser;
 
 	protected function setUp(): void
 	{
-		$usedAttributes = ['lines' => true, 'indexes' => true];
-		$constExprParser = new ConstExprParser(true, true, $usedAttributes);
-		$this->typeParser = new TypeParser($constExprParser, true, $usedAttributes);
+		$config = new ParserConfig(['lines' => true, 'indexes' => true, 'comments' => true]);
+		$constExprParser = new ConstExprParser($config);
+		$this->typeParser = new TypeParser($config, $constExprParser);
 		$this->phpDocParser = new PhpDocParser(
+			$config,
 			$this->typeParser,
 			$constExprParser,
-			true,
-			true,
-			$usedAttributes,
-			true
 		);
 	}
 
@@ -100,12 +106,14 @@ public function dataPrintFormatPreserving(): iterable
 			$noopVisitor,
 		];
 		yield [
-			'/**
- * @param Foo $foo
- */',
-			'/**
- * @param Foo $foo
- */',
+			self::nowdoc('
+				/**
+				 * @param Foo $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param Foo $foo
+				 */'),
 			$noopVisitor,
 		];
 
@@ -147,33 +155,39 @@ public function enterNode(Node $node)
 		];
 
 		yield [
-			'/**
- * @param Foo $foo
- */',
-			'/**
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 */'),
+			self::nowdoc('
+			/**
+			 */'),
 			$removeFirst,
 		];
 
 		yield [
-			'/**
- * @param Foo $foo
- * @param Bar $bar
- */',
-			'/**
- * @param Bar $bar
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Bar $bar
+			 */'),
 			$removeFirst,
 		];
 
 		yield [
-			'/**
-     * @param Foo $foo
-     * @param Bar $bar
-     */',
-			'/**
-     * @param Bar $bar
-     */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Bar $bar
+			 */'),
 			$removeFirst,
 		];
 
@@ -193,13 +207,15 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @param Foo $foo
- * @param Bar $bar
- */',
-			'/**
- * @param Foo $foo
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 */'),
 			$removeLast,
 		];
 
@@ -220,39 +236,45 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @param Foo $foo
- * @param Bar $bar
- */',
-			'/**
- * @param Foo $foo
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 */'),
 			$removeSecond,
 		];
 
 		yield [
-			'/**
- * @param Foo $foo
- * @param Bar $bar
- * @param Baz $baz
- */',
-			'/**
- * @param Foo $foo
- * @param Baz $baz
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 * @param Baz $baz
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Baz $baz
+			 */'),
 			$removeSecond,
 		];
 
 		yield [
-			'/**
-     * @param Foo $foo
-     * @param Bar $bar
-     * @param Baz $baz
-     */',
-			'/**
-     * @param Foo $foo
-     * @param Baz $baz
-     */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 * @param Baz $baz
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Baz $baz
+			 */'),
 			$removeSecond,
 		];
 
@@ -284,58 +306,66 @@ public function enterNode(Node $node)
 		];
 
 		yield [
-			'/**
-* @return Foo
-* @param Foo $foo
-* @param Bar $bar
-*/',
-			'/**
-* @return Bar
-* @param Foo $foo
-* @param Bar $bar
-*/',
+			self::nowdoc('
+			/**
+			 * @return Foo
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return Bar
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
 			$changeReturnType,
 		];
 
 		yield [
-			'/**
-* @param Foo $foo
-* @return Foo
-* @param Bar $bar
-*/',
-			'/**
-* @param Foo $foo
-* @return Bar
-* @param Bar $bar
-*/',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @return Foo
+			 * @param Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @return Bar
+			 * @param Bar $bar
+			 */'),
 			$changeReturnType,
 		];
 
 		yield [
-			'/**
-* @return Foo
-* @param Foo $foo
-* @param Bar $bar
-*/',
-			'/**
-* @return Bar
-* @param Foo $foo
-* @param Bar $bar
-*/',
+			self::nowdoc('
+			/**
+			 * @return Foo
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return Bar
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
 			$changeReturnType,
 		];
 
 		yield [
-			'/**
-* @param Foo $foo Foo description
-* @return Foo Foo return description
-* @param Bar $bar Bar description
-*/',
-			'/**
-* @param Foo $foo Foo description
-* @return Bar Foo return description
-* @param Bar $bar Bar description
-*/',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo Foo description
+			 * @return Foo Foo return description
+			 * @param Bar $bar Bar description
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo $foo Foo description
+			 * @return Bar Foo return description
+			 * @param Bar $bar Bar description
+			 */'),
 			$changeReturnType,
 		];
 
@@ -344,7 +374,7 @@ public function enterNode(Node $node)
 			public function enterNode(Node $node)
 			{
 				if ($node instanceof PhpDocNode) {
-					$node->children[0] = new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('Baz'), false, '$a', ''));
+					$node->children[0] = new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('Baz'), false, '$a', '', false));
 					return $node;
 				}
 
@@ -360,22 +390,26 @@ public function enterNode(Node $node)
 		];
 
 		yield [
-			'/**
- * @param Foo $foo
- */',
-			'/**
- * @param Baz $a
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Baz $a
+			 */'),
 			$replaceFirst,
 		];
 
 		yield [
-			'/**
-     * @param Foo $foo
-     */',
-			'/**
-     * @param Baz $a
-     */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Baz $a
+			 */'),
 			$replaceFirst,
 		];
 
@@ -384,7 +418,7 @@ public function enterNode(Node $node)
 			public function enterNode(Node $node)
 			{
 				if ($node instanceof PhpDocNode) {
-					array_unshift($node->children, new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('Baz'), false, '$a', '')));
+					array_unshift($node->children, new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('Baz'), false, '$a', '', false)));
 
 					return $node;
 				}
@@ -395,24 +429,28 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @param Foo $foo
- */',
-			'/**
- * @param Baz $a
- * @param Foo $foo
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Baz $a
+			 * @param Foo $foo
+			 */'),
 			$insertFirst,
 		];
 
 		yield [
-			'/**
-     * @param Foo $foo
-     */',
-			'/**
-     * @param Baz $a
-     * @param Foo $foo
-     */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Baz $a
+			 * @param Foo $foo
+			 */'),
 			$insertFirst,
 		];
 
@@ -422,7 +460,7 @@ public function enterNode(Node $node)
 			{
 				if ($node instanceof PhpDocNode) {
 					array_splice($node->children, 1, 0, [
-						new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('Baz'), false, '$a', '')),
+						new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('Baz'), false, '$a', '', false)),
 					]);
 
 					return $node;
@@ -434,52 +472,60 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @param Foo $foo
- */',
-			'/**
- * @param Foo $foo
- * @param Baz $a
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Baz $a
+			 */'),
 			$insertSecond,
 		];
 
 		yield [
-			'/**
- * @param Foo $foo
- * @param Bar $bar
- */',
-			'/**
- * @param Foo $foo
- * @param Baz $a
- * @param Bar $bar
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Baz $a
+			 * @param Bar $bar
+			 */'),
 			$insertSecond,
 		];
 
 		yield [
-			'/**
-     * @param Foo $foo
-     * @param Bar $bar
-     */',
-			'/**
-     * @param Foo $foo
-     * @param Baz $a
-     * @param Bar $bar
-     */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Baz $a
+			 * @param Bar $bar
+			 */'),
 			$insertSecond,
 		];
 
 		yield [
-			'/**
-	 * @param Foo $foo
-	 * @param Bar $bar
-	 */',
-			'/**
-	 * @param Foo $foo
-	 * @param Baz $a
-	 * @param Bar $bar
-	 */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Baz $a
+			 * @param Bar $bar
+			 */'),
 			$insertSecond,
 		];
 
@@ -488,7 +534,7 @@ public function enterNode(Node $node)
 			public function enterNode(Node $node)
 			{
 				if ($node instanceof PhpDocNode) {
-					$node->children[count($node->children) - 1] = new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('Baz'), false, '$a', ''));
+					$node->children[count($node->children) - 1] = new PhpDocTagNode('@param', new ParamTagValueNode(new IdentifierTypeNode('Baz'), false, '$a', '', false));
 					return $node;
 				}
 
@@ -498,24 +544,28 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @param Foo $foo
- */',
-			'/**
- * @param Baz $a
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Baz $a
+			 */'),
 			$replaceLast,
 		];
 
 		yield [
-			'/**
- * @param Foo $foo
- * @param Bar $bar
- */',
-			'/**
- * @param Foo $foo
- * @param Baz $a
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo $foo
+			 * @param Baz $a
+			 */'),
 			$replaceLast,
 		];
 
@@ -533,24 +583,28 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @param Bar|Baz $foo
- */',
-			'/**
- * @param Foo|Bar|Baz $foo
- */',
+			self::nowdoc('
+			/**
+			 * @param Bar|Baz $foo
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo|Bar|Baz $foo
+			 */'),
 			$insertFirstTypeInUnionType,
 		];
 
 		yield [
-			'/**
- * @param Bar|Baz $foo
- * @param Foo $bar
- */',
-			'/**
- * @param Foo|Bar|Baz $foo
- * @param Foo $bar
- */',
+			self::nowdoc('
+			/**
+			 * @param Bar|Baz $foo
+			 * @param Foo $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo|Bar|Baz $foo
+			 * @param Foo $bar
+			 */'),
 			$insertFirstTypeInUnionType,
 		];
 
@@ -571,12 +625,14 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @param Foo|Bar $bar
- */',
-			'/**
- * @param Lorem|Ipsum $bar
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo|Bar $bar
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Lorem|Ipsum $bar
+			 */'),
 			$replaceTypesInUnionType,
 		];
 
@@ -604,7 +660,7 @@ public function enterNode(Node $node)
 					$node->templateTypes[] = new TemplateTagValueNode(
 						'T',
 						new IdentifierTypeNode('int'),
-						''
+						'',
 					);
 				}
 
@@ -626,12 +682,14 @@ public function enterNode(Node $node)
 		];
 
 		yield [
-			'/**
- * @param callable(): void $cb
- */',
-			'/**
- * @param callable(Foo $foo, Bar $bar): void $cb
- */',
+			self::nowdoc('
+			/**
+			 * @param callable(): void $cb
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param callable(Foo $foo, Bar $bar): void $cb
+			 */'),
 			$replaceParametersInCallableType,
 		];
 
@@ -649,12 +707,14 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @param callable(Foo $foo, Bar $bar): void $cb
- */',
-			'/**
- * @param callable(): void $cb
- */',
+			self::nowdoc('
+			/**
+			 * @param callable(Foo $foo, Bar $bar): void $cb
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param callable(): void $cb
+			 */'),
 			$removeParametersInCallableType,
 		];
 
@@ -672,14 +732,16 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @param callable(Foo $foo, Bar $bar): void $cb
- * @param callable(): void $cb2
- */',
-			'/**
- * @param Closure(Foo $foo, Bar $bar): void $cb
- * @param Closure(): void $cb2
- */',
+			self::nowdoc('
+			/**
+			 * @param callable(Foo $foo, Bar $bar): void $cb
+			 * @param callable(): void $cb2
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Closure(Foo $foo, Bar $bar): void $cb
+			 * @param Closure(): void $cb2
+			 */'),
 			$changeCallableTypeIdentifier,
 		];
 
@@ -710,120 +772,134 @@ public function enterNode(Node $node)
 		];
 
 		yield [
-			'/**
- * @return array{float, Foo}
- */',
-			'/**
- * @return array{float, int, Foo, string}
- */',
+			self::nowdoc('
+			/**
+			 * @return array{float, Foo}
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return array{float, int, Foo, string}
+			 */'),
 			$addItemsToArrayShape,
 		];
 
 		yield [
-			'/**
- * @return array{
- *   float,
- *   Foo,
- * }
- */',
-			'/**
- * @return array{
- *   float,
- *   int,
- *   Foo,
- *   string,
- * }
- */',
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *   float,
+			 *   Foo,
+			 * }
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *   float,
+			 *   int,
+			 *   Foo,
+			 *   string,
+			 * }
+			 */'),
 			$addItemsToArrayShape,
 		];
 
 		yield [
-			'/**
- * @return array{
- *   float,
- *   Foo
- * }
- */',
-			'/**
- * @return array{
- *   float,
- *   int,
- *   Foo,
- *   string
- * }
- */',
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *   float,
+			 *   Foo
+			 * }
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *   float,
+			 *   int,
+			 *   Foo,
+			 *   string
+			 * }
+			 */'),
 			$addItemsToArrayShape,
 		];
 
 		yield [
-			'/**
- * @return array{
- *     float,
- *     Foo
- * }
- */',
-			'/**
- * @return array{
- *     float,
- *     int,
- *     Foo,
- *     string
- * }
- */',
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *     float,
+			 *     Foo
+			 * }
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *     float,
+			 *     int,
+			 *     Foo,
+			 *     string
+			 * }
+			 */'),
 			$addItemsToArrayShape,
 		];
 
 		yield [
-			'/**
-	 * @return array{
-	 *   float,
-	 *   Foo,
-	 * }
-	 */',
-			'/**
-	 * @return array{
-	 *   float,
-	 *   int,
-	 *   Foo,
-	 *   string,
-	 * }
-	 */',
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *   float,
+			 *   Foo,
+			 * }
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *   float,
+			 *   int,
+			 *   Foo,
+			 *   string,
+			 * }
+			 */'),
 			$addItemsToArrayShape,
 		];
 
 		yield [
-			'/**
-     * @return array{
-     *   float,
-     *   Foo
-     * }
-     */',
-			'/**
-     * @return array{
-     *   float,
-     *   int,
-     *   Foo,
-     *   string
-     * }
-     */',
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *   float,
+			 *   Foo
+			 * }
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *   float,
+			 *   int,
+			 *   Foo,
+			 *   string
+			 * }
+			 */'),
 			$addItemsToArrayShape,
 		];
 
 		yield [
-			'/**
-	 * @return array{
-	 *     float,
-	 *     Foo
-	 * }
-	 */',
-			'/**
-	 * @return array{
-	 *     float,
-	 *     int,
-	 *     Foo,
-	 *     string
-	 * }
-	 */',
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *     float,
+			 *     Foo
+			 * }
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *     float,
+			 *     int,
+			 *     Foo,
+			 *     string
+			 * }
+			 */'),
 			$addItemsToArrayShape,
 		];
 
@@ -841,25 +917,254 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @return object{}
- */',
-			'/**
- * @return object{foo: int}
- */',
+			self::nowdoc('
+				/**
+				 * @return object{}
+				 */'),
+			self::nowdoc('
+				/**
+				 * @return object{foo: int}
+				 */'),
 			$addItemsToObjectShape,
 		];
 
 		yield [
-			'/**
- * @return object{bar: string}
- */',
-			'/**
- * @return object{bar: string, foo: int}
- */',
+			self::nowdoc('
+				/**
+				 * @return object{bar: string}
+				 */'),
+			self::nowdoc('
+				/**
+				 * @return object{bar: string, foo: int}
+				 */'),
+			$addItemsToObjectShape,
+		];
+
+		yield [
+			self::nowdoc('
+			/**
+			 * @return object{bar: string}
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return object{bar: string, foo: int}
+			 */'),
 			$addItemsToObjectShape,
 		];
 
+		$addItemsWithCommentsToMultilineArrayShape = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				if ($node instanceof ArrayShapeNode) {
+					$commentedNode = new ArrayShapeItemNode(new IdentifierTypeNode('b'), false, new IdentifierTypeNode('int'));
+					$commentedNode->setAttribute(Attribute::COMMENTS, [new Comment('// bar')]);
+					array_splice($node->items, 1, 0, [
+						$commentedNode,
+					]);
+					$commentedNode = new ArrayShapeItemNode(new IdentifierTypeNode('d'), false, new IdentifierTypeNode('string'));
+					$commentedNode->setAttribute(Attribute::COMMENTS, [new Comment(
+						PrinterTest::nowdoc('
+						// first comment'),
+					)]);
+					$node->items[] = $commentedNode;
+
+					$commentedNode = new ArrayShapeItemNode(new IdentifierTypeNode('e'), false, new IdentifierTypeNode('string'));
+					$commentedNode->setAttribute(Attribute::COMMENTS, [new Comment(
+						PrinterTest::nowdoc('
+						// second comment'),
+					)]);
+					$node->items[] = $commentedNode;
+
+					$commentedNode = new ArrayShapeItemNode(new IdentifierTypeNode('f'), false, new IdentifierTypeNode('string'));
+					$commentedNode->setAttribute(Attribute::COMMENTS, [
+						new Comment('// third comment'),
+						new Comment('// fourth comment'),
+					]);
+					$node->items[] = $commentedNode;
+				}
+
+				return $node;
+			}
+
+		};
+
+		yield [
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *  // foo
+			 *	a: int,
+			 *	c: string
+			 * }
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *  // foo
+			 *	a: int,
+			 *  // bar
+			 *  b: int,
+			 *	c: string,
+			 *  // first comment
+			 *  d: string,
+			 *  // second comment
+			 *  e: string,
+			 *  // third comment
+			 *  // fourth comment
+			 *  f: string
+			 * }
+			 */'),
+			$addItemsWithCommentsToMultilineArrayShape,
+		];
+
+		$prependItemsWithCommentsToMultilineArrayShape = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				if ($node instanceof ArrayShapeNode) {
+					$commentedNode = new ArrayShapeItemNode(new IdentifierTypeNode('a'), false, new IdentifierTypeNode('int'));
+					$commentedNode->setAttribute(Attribute::COMMENTS, [new Comment('// first item')]);
+					array_splice($node->items, 0, 0, [
+						$commentedNode,
+					]);
+				}
+
+				return $node;
+			}
+
+		};
+
+		yield [
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *  b: int,
+			 * }
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *  // first item
+			 *  a: int,
+			 *  b: int,
+			 * }
+			 */'),
+			$prependItemsWithCommentsToMultilineArrayShape,
+		];
+
+		$changeCommentOnArrayShapeItem = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				if ($node instanceof ArrayShapeItemNode) {
+					$node->setAttribute(Attribute::COMMENTS, [new Comment('// puppies')]);
+				}
+
+				return $node;
+			}
+
+		};
+
+		yield [
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *   a: int,
+			 * }
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *   // puppies
+			 *   a: int,
+			 * }
+			 */'),
+			$changeCommentOnArrayShapeItem,
+		];
+
+		$addItemsWithCommentsToObjectShape = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				if ($node instanceof ObjectShapeNode) {
+					$item = new ObjectShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int'));
+					$item->setAttribute(Attribute::COMMENTS, [new Comment('// favorite foo')]);
+					$node->items[] = $item;
+				}
+
+				return $node;
+			}
+
+		};
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @return object{
+				 *   // your favorite bar
+				 *   bar: string
+				 * }
+				 */'),
+			self::nowdoc('
+				/**
+				 * @return object{
+				 *   // your favorite bar
+				 *   bar: string,
+				 *	 // favorite foo
+				 *	 foo: int
+				 * }
+				 */'),
+			$addItemsWithCommentsToObjectShape,
+		];
+
+		$removeItemWithComment = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				if (!$node instanceof ArrayShapeNode) {
+					return null;
+				}
+
+				foreach ($node->items as $i => $item) {
+					if ($item->keyName === null) {
+						continue;
+					}
+
+					$comments = $item->keyName->getAttribute(Attribute::COMMENTS);
+					if ($comments === null) {
+						continue;
+					}
+					if ($comments === []) {
+						continue;
+					}
+
+					unset($node->items[$i]);
+				}
+
+				return $node;
+			}
+
+		};
+
+		yield [
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *  a: string,
+			 *  // b comment
+			 *  b: int,
+			 * }
+			 */'),
+			self::nowdoc('
+			/**
+			 * @return array{
+			 *  a: string,
+			 * }
+			 */'),
+			$removeItemWithComment,
+		];
+
 		$addItemsToConstExprArray = new class extends AbstractNodeVisitor {
 
 			public function enterNode(Node $node)
@@ -1028,7 +1333,7 @@ public function enterNode(Node $node)
 			public function enterNode(Node $node)
 			{
 				if ($node instanceof ArrayShapeItemNode) {
-					$node->keyName = new QuoteAwareConstExprStringNode('test', QuoteAwareConstExprStringNode::SINGLE_QUOTED);
+					$node->keyName = new ConstExprStringNode('test', ConstExprStringNode::SINGLE_QUOTED);
 				}
 
 				return $node;
@@ -1100,36 +1405,42 @@ public function enterNode(Node $node)
 		];
 
 		yield [
-			'/**
- * @param int $a
- */',
-			'/**
- * @param int $bz
- */',
+			self::nowdoc('
+			/**
+			 * @param int $a
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param int $bz
+			 */'),
 			$changeParameterName,
 		];
 
 		yield [
-			'/**
- * @param int $a
- * @return string
- */',
-			'/**
- * @param int $bz
- * @return string
- */',
+			self::nowdoc('
+			/**
+			 * @param int $a
+			 * @return string
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param int $bz
+			 * @return string
+			 */'),
 			$changeParameterName,
 		];
 
 		yield [
-			'/**
- * @param int $a haha description
- * @return string
- */',
-			'/**
- * @param int $bz haha description
- * @return string
- */',
+			self::nowdoc('
+			/**
+			 * @param int $a haha description
+			 * @return string
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param int $bz haha description
+			 * @return string
+			 */'),
 			$changeParameterName,
 		];
 
@@ -1165,12 +1476,14 @@ public function enterNode(Node $node)
 		];
 
 		yield [
-			'/**
- * @param int $a haha
- */',
-			'/**
- * @param int $a hehe
- */',
+			self::nowdoc('
+			/**
+			 * @param int $a haha
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param int $a hehe
+			 */'),
 			$changeParameterDescription,
 		];
 
@@ -1188,12 +1501,14 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @param Foo[awesome] $a haha
- */',
-			'/**
- * @param Foo[baz] $a haha
- */',
+			self::nowdoc('
+			/**
+			 * @param Foo[awesome] $a haha
+			 */'),
+			self::nowdoc('
+			/**
+			 * @param Foo[baz] $a haha
+			 */'),
 			$changeOffsetAccess,
 		];
 
@@ -1211,12 +1526,14 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @phpstan-import-type TypeAlias from AnotherClass as DifferentAlias
- */',
-			'/**
- * @phpstan-import-type TypeAlias from AnotherClass as Ciao
- */',
+			self::nowdoc('
+			/**
+			 * @phpstan-import-type TypeAlias from AnotherClass as DifferentAlias
+			 */'),
+			self::nowdoc('
+			/**
+			 * @phpstan-import-type TypeAlias from AnotherClass as Ciao
+			 */'),
 			$changeTypeAliasImportAs,
 		];
 
@@ -1234,12 +1551,14 @@ public function enterNode(Node $node)
 		};
 
 		yield [
-			'/**
- * @phpstan-import-type TypeAlias from AnotherClass as DifferentAlias
- */',
-			'/**
- * @phpstan-import-type TypeAlias from AnotherClass
- */',
+			self::nowdoc('
+			/**
+			 * @phpstan-import-type TypeAlias from AnotherClass as DifferentAlias
+			 */'),
+			self::nowdoc('
+			/**
+			 * @phpstan-import-type TypeAlias from AnotherClass
+			 */'),
 			$removeImportAs,
 		];
 
@@ -1251,7 +1570,7 @@ public function enterNode(Node $node)
 					$node->templateTypes[] = new TemplateTagValueNode(
 						'T',
 						new IdentifierTypeNode('int'),
-						''
+						'',
 					);
 				}
 
@@ -1272,27 +1591,24 @@ public function enterNode(Node $node)
 			$addMethodTemplateType,
 		];
 
-		$changeCallableReturnTypeFactory = function (TypeNode $type): NodeVisitor {
-			return new class ($type) extends AbstractNodeVisitor {
-
-				/** @var TypeNode */
-				private $type;
+		$changeCallableReturnTypeFactory = static fn (TypeNode $type): NodeVisitor => new class ($type) extends AbstractNodeVisitor {
 
-				public function __construct(TypeNode $type)
-				{
-					$this->type = $type;
-				}
+			private TypeNode $type;
 
-				public function enterNode(Node $node)
-				{
-					if ($node instanceof CallableTypeNode) {
-						$node->returnType = $this->type;
-					}
+			public function __construct(TypeNode $type)
+			{
+				$this->type = $type;
+			}
 
-					return $node;
+			public function enterNode(Node $node)
+			{
+				if ($node instanceof CallableTypeNode) {
+					$node->returnType = $this->type;
 				}
 
-			};
+				return $node;
+			}
+
 		};
 
 		yield [
@@ -1325,44 +1641,38 @@ public function enterNode(Node $node)
 			])),
 		];
 
-		$changeCallableReturnTypeCallbackFactory = function (callable $callback): NodeVisitor {
-			return new class ($callback) extends AbstractNodeVisitor {
+		$changeCallableReturnTypeCallbackFactory = fn (callable $callback): NodeVisitor => new class ($callback) extends AbstractNodeVisitor {
 
-				/** @var callable(TypeNode): TypeNode */
-				private $callback;
+			/** @var callable(TypeNode): TypeNode */
+			private $callback;
 
-				public function __construct(callable $callback)
-				{
-					$this->callback = $callback;
-				}
-
-				public function enterNode(Node $node)
-				{
-					if ($node instanceof CallableTypeNode) {
-						$cb = $this->callback;
-						$node->returnType = $cb($node->returnType);
-					}
+			public function __construct(callable $callback)
+			{
+				$this->callback = $callback;
+			}
 
-					return $node;
+			public function enterNode(Node $node)
+			{
+				if ($node instanceof CallableTypeNode) {
+					$cb = $this->callback;
+					$node->returnType = $cb($node->returnType);
 				}
 
-			};
+				return $node;
+			}
+
 		};
 
 		yield [
 			'/** @param callable(): int $a */',
 			'/** @param callable(): string $a */',
-			$changeCallableReturnTypeCallbackFactory(static function (TypeNode $typeNode): TypeNode {
-				return new IdentifierTypeNode('string');
-			}),
+			$changeCallableReturnTypeCallbackFactory(static fn (TypeNode $typeNode) => new IdentifierTypeNode('string')),
 		];
 
 		yield [
 			'/** @param callable(): (int) $a */',
 			'/** @param callable(): string $a */',
-			$changeCallableReturnTypeCallbackFactory(static function (TypeNode $typeNode): TypeNode {
-				return new IdentifierTypeNode('string');
-			}),
+			$changeCallableReturnTypeCallbackFactory(static fn (TypeNode $typeNode) => new IdentifierTypeNode('string')),
 		];
 
 		yield [
@@ -1651,15 +1961,19 @@ public function enterNode(Node $node)
 		];
 
 		yield [
-			'/** @Foo(
-			  *     1,
-			  *     2,
-			  *  ) */',
-			'/** @Foo(
-			  *     1,
-			  *     2,
-			  *     3,
-			  *  ) */',
+			self::nowdoc('
+			/** @Foo(
+			 *     1,
+			 *     2,
+			 *  )
+			 */'),
+			self::nowdoc('
+			/** @Foo(
+			 *     1,
+			 *     2,
+			 *     3,
+			 *  )
+			 */'),
 			new class extends AbstractNodeVisitor {
 
 				public function enterNode(Node $node)
@@ -1939,6 +2253,377 @@ public function enterNode(Node $node)
 
 			},
 		];
+
+		$singleCommentLineAddFront = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				if ($node instanceof ArrayShapeNode) {
+					array_unshift($node->items, PrinterTest::withComment(
+						new ArrayShapeItemNode(null, false, new IdentifierTypeNode('float')),
+						'// A fractional number',
+					));
+				}
+
+				return $node;
+			}
+
+		};
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param array{} $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param array{float} $foo
+				 */'),
+			$singleCommentLineAddFront,
+		];
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param array{string} $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param array{// A fractional number
+				 *  float,
+				 *  string} $foo
+				 */'),
+			$singleCommentLineAddFront,
+		];
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param array{
+				 *   string,int} $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param array{
+				 *   // A fractional number
+				 *   float,
+				 *   string,int} $foo
+				 */'),
+			$singleCommentLineAddFront,
+		];
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param array{
+				 *   string,
+				 *	 int
+				 * } $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param array{
+				 *   // A fractional number
+				 *   float,
+				 *   string,
+				 *   int
+				 * } $foo
+				 */'),
+			$singleCommentLineAddFront,
+		];
+
+		$singleCommentLineAddMiddle = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				$newItem = PrinterTest::withComment(
+					new ArrayShapeItemNode(null, false, new IdentifierTypeNode('float')),
+					'// A fractional number',
+				);
+
+				if ($node instanceof ArrayShapeNode) {
+					if (count($node->items) === 0) {
+						$node->items[] = $newItem;
+					} else {
+						array_splice($node->items, 1, 0, [$newItem]);
+					}
+				}
+
+				return $node;
+			}
+
+		};
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param array{} $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param array{float} $foo
+				 */'),
+			$singleCommentLineAddMiddle,
+		];
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param array{string} $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param array{string,
+				 *  // A fractional number
+				 *  float} $foo
+				 */'),
+			$singleCommentLineAddMiddle,
+		];
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param array{
+				 *   string,int} $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param array{
+				 *   string,
+				 *   // A fractional number
+				 *   float,int} $foo
+				 */'),
+			$singleCommentLineAddMiddle,
+		];
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param array{
+				 *   string,
+				 *	 int
+				 * } $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param array{
+				 *   string,
+				 *   // A fractional number
+				 *   float,
+				 *   int
+				 * } $foo
+				 */'),
+			$singleCommentLineAddMiddle,
+		];
+
+		$addCommentToCallableParamsFront = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				if ($node instanceof CallableTypeNode) {
+					array_unshift($node->parameters, PrinterTest::withComment(
+						new CallableTypeParameterNode(new IdentifierTypeNode('Foo'), false, false, '$foo', false),
+						'// never pet a burning dog',
+					));
+				}
+
+				return $node;
+			}
+
+		};
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param callable(Bar $bar): int $a
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param callable(// never pet a burning dog
+				 *  Foo $foo,
+				 *  Bar $bar): int $a
+				 */'),
+			$addCommentToCallableParamsFront,
+		];
+
+		$addCommentToCallableParamsMiddle = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				if ($node instanceof CallableTypeNode) {
+					$node->parameters[] = PrinterTest::withComment(
+						new CallableTypeParameterNode(new IdentifierTypeNode('Bar'), false, false, '$bar', false),
+						'// never pet a burning dog',
+					);
+				}
+
+				return $node;
+			}
+
+		};
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param callable(Foo $foo): int $a
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param callable(Foo $foo,
+				 *  // never pet a burning dog
+				 *  Bar $bar): int $a
+				 */'),
+			$addCommentToCallableParamsMiddle,
+		];
+
+		$addCommentToObjectShapeItemFront = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				if ($node instanceof ObjectShapeNode) {
+					array_unshift($node->items, PrinterTest::withComment(
+						new ObjectShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('float')),
+						'// A fractional number',
+					));
+				}
+
+				return $node;
+			}
+
+		};
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param object{bar: string} $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param object{// A fractional number
+				 *  foo: float,
+				 *  bar: string} $foo
+				 */'),
+			$addCommentToObjectShapeItemFront,
+		];
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param object{
+				 *   bar:string,naz:int} $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param object{
+				 *   // A fractional number
+				 *   foo: float,
+				 *   bar:string,naz:int} $foo
+				 */'),
+			$addCommentToObjectShapeItemFront,
+		];
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param object{
+				 *   bar:string,
+				 *	 naz:int
+				 * } $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param object{
+				 *   // A fractional number
+				 *   foo: float,
+				 *   bar:string,
+				 *   naz:int
+				 * } $foo
+				 */'),
+			$addCommentToObjectShapeItemFront,
+		];
+
+		$addCommentToObjectShapeItemMiddle = new class extends AbstractNodeVisitor {
+
+			public function enterNode(Node $node)
+			{
+				if ($node instanceof ObjectShapeNode) {
+					$newItem = PrinterTest::withComment(
+						new ObjectShapeItemNode(new IdentifierTypeNode('bar'), false, new IdentifierTypeNode('float')),
+						'// A fractional number',
+					);
+					if (count($node->items) === 0) {
+						$node->items[] = $newItem;
+					} else {
+						array_splice($node->items, 1, 0, [$newItem]);
+					}
+				}
+
+				return $node;
+			}
+
+		};
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param object{} $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param object{bar: float} $foo
+				 */'),
+			$addCommentToObjectShapeItemMiddle,
+		];
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param object{foo:string} $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param object{foo:string,
+				 *  // A fractional number
+				 *  bar: float} $foo
+				 */'),
+			$addCommentToObjectShapeItemMiddle,
+		];
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param object{
+				 *   foo:string,naz:int} $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param object{
+				 *   foo:string,
+				 *   // A fractional number
+				 *   bar: float,naz:int} $foo
+				 */'),
+			$addCommentToObjectShapeItemMiddle,
+		];
+
+		yield [
+			self::nowdoc('
+				/**
+				 * @param object{
+				 *   foo:string,
+				 *	 naz:int
+				 * } $foo
+				 */'),
+			self::nowdoc('
+				/**
+				 * @param object{
+				 *   foo:string,
+				 *   // A fractional number
+				 *   bar: float,
+				 *   naz:int
+				 * } $foo
+				 */'),
+			$addCommentToObjectShapeItemMiddle,
+		];
 	}
 
 	/**
@@ -1946,7 +2631,8 @@ public function enterNode(Node $node)
 	 */
 	public function testPrintFormatPreserving(string $phpDoc, string $expectedResult, NodeVisitor $visitor): void
 	{
-		$lexer = new Lexer(true);
+		$config = new ParserConfig(['lines' => true, 'indexes' => true, 'comments' => true]);
+		$lexer = new Lexer($config);
 		$tokens = new TokenIterator($lexer->tokenize($phpDoc));
 		$phpDocNode = $this->phpDocParser->parse($tokens);
 		$cloningTraverser = new NodeTraverser([new NodeVisitor\CloningVisitor()]);
@@ -1963,7 +2649,7 @@ public function testPrintFormatPreserving(string $phpDoc, string $expectedResult
 
 		$this->assertEquals(
 			$this->unsetAttributes($newNode),
-			$this->unsetAttributes($this->phpDocParser->parse(new TokenIterator($lexer->tokenize($newPhpDoc))))
+			$this->unsetAttributes($this->phpDocParser->parse(new TokenIterator($lexer->tokenize($newPhpDoc)))),
 		);
 	}
 
@@ -1978,6 +2664,7 @@ public function enterNode(Node $node)
 				$node->setAttribute(Attribute::START_INDEX, null);
 				$node->setAttribute(Attribute::END_INDEX, null);
 				$node->setAttribute(Attribute::ORIGINAL_NODE, null);
+				$node->setAttribute(Attribute::COMMENTS, null);
 
 				return $node;
 			}
@@ -2019,7 +2706,7 @@ public function dataPrintType(): iterable
 				[
 					GenericTypeNode::VARIANCE_INVARIANT,
 					GenericTypeNode::VARIANCE_INVARIANT,
-				]
+				],
 			),
 			'array<int, int|string>',
 		];
@@ -2027,18 +2714,18 @@ public function dataPrintType(): iterable
 			new CallableTypeNode(new IdentifierTypeNode('callable'), [], new UnionTypeNode([
 				new IdentifierTypeNode('int'),
 				new IdentifierTypeNode('string'),
-			])),
+			]), []),
 			'callable(): (int|string)',
 		];
 		yield [
-			new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new ArrayTypeNode(new ArrayTypeNode(new IdentifierTypeNode('int'))))),
+			new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new ArrayTypeNode(new ArrayTypeNode(new IdentifierTypeNode('int')))), []),
 			'callable(): int[][][]',
 		];
 		yield [
 			new ArrayTypeNode(
 				new ArrayTypeNode(
-					new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new IdentifierTypeNode('int')))
-				)
+					new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new IdentifierTypeNode('int')), []),
+				),
 			),
 			'(callable(): int[])[][]',
 		];
@@ -2081,21 +2768,21 @@ public function dataPrintType(): iterable
 		yield [
 			new OffsetAccessTypeNode(
 				new ConstTypeNode(new ConstFetchNode('self', 'TYPES')),
-				new IdentifierTypeNode('int')
+				new IdentifierTypeNode('int'),
 			),
 			'self::TYPES[int]',
 		];
 		yield [
-			new ArrayShapeNode([
+			ArrayShapeNode::createSealed([
 				new ArrayShapeItemNode(
 					new IdentifierTypeNode('name'),
 					false,
-					new IdentifierTypeNode('string')
+					new IdentifierTypeNode('string'),
 				),
 				new ArrayShapeItemNode(
-					new QuoteAwareConstExprStringNode('Full Name', QuoteAwareConstExprStringNode::SINGLE_QUOTED),
+					new ConstExprStringNode('Full Name', ConstExprStringNode::SINGLE_QUOTED),
 					false,
-					new IdentifierTypeNode('string')
+					new IdentifierTypeNode('string'),
 				),
 			]),
 			"array{name: string, 'Full Name': string}",
@@ -2105,12 +2792,12 @@ public function dataPrintType(): iterable
 				new ObjectShapeItemNode(
 					new IdentifierTypeNode('name'),
 					false,
-					new IdentifierTypeNode('string')
+					new IdentifierTypeNode('string'),
 				),
 				new ObjectShapeItemNode(
-					new QuoteAwareConstExprStringNode('Full Name', QuoteAwareConstExprStringNode::SINGLE_QUOTED),
+					new ConstExprStringNode('Full Name', ConstExprStringNode::SINGLE_QUOTED),
 					false,
-					new IdentifierTypeNode('string')
+					new IdentifierTypeNode('string'),
 				),
 			]),
 			"object{name: string, 'Full Name': string}",
@@ -2126,10 +2813,11 @@ public function testPrintType(TypeNode $node, string $expectedResult): void
 		$phpDoc = $printer->print($node);
 		$this->assertSame($expectedResult, $phpDoc);
 
-		$lexer = new Lexer();
+		$config = new ParserConfig([]);
+		$lexer = new Lexer($config);
 		$this->assertEquals(
 			$this->unsetAttributes($node),
-			$this->unsetAttributes($this->typeParser->parse(new TokenIterator($lexer->tokenize($phpDoc))))
+			$this->unsetAttributes($this->typeParser->parse(new TokenIterator($lexer->tokenize($phpDoc)))),
 		);
 	}
 
@@ -2144,7 +2832,8 @@ public function dataPrintPhpDocNode(): iterable
 					new IdentifierTypeNode('int'),
 					false,
 					'$a',
-					''
+					'',
+					false,
 				)),
 			]),
 			'/**
@@ -2162,11 +2851,59 @@ public function testPrintPhpDocNode(PhpDocNode $node, string $expectedResult): v
 		$phpDoc = $printer->print($node);
 		$this->assertSame($expectedResult, $phpDoc);
 
-		$lexer = new Lexer();
+		$config = new ParserConfig([]);
+		$lexer = new Lexer($config);
 		$this->assertEquals(
 			$this->unsetAttributes($node),
-			$this->unsetAttributes($this->phpDocParser->parse(new TokenIterator($lexer->tokenize($phpDoc))))
+			$this->unsetAttributes($this->phpDocParser->parse(new TokenIterator($lexer->tokenize($phpDoc)))),
 		);
 	}
 
+	/**
+	 * @template TNode of Node
+	 * @param TNode $node
+	 * @return TNode
+	 */
+	public static function withComment(Node $node, string $comment): Node
+	{
+		$node->setAttribute(Attribute::COMMENTS, [new Comment($comment)]);
+		return $node;
+	}
+
+	public static function nowdoc(string $str): string
+	{
+		$lines = preg_split('/\\n/', $str);
+
+		if ($lines === false) {
+			return '';
+		}
+
+		if (count($lines) < 2) {
+			return '';
+		}
+
+		// Toss out the first line
+		$lines = array_slice($lines, 1, count($lines) - 1);
+
+		// normalize any tabs to spaces
+		$lines = array_map(static fn ($line) => preg_replace_callback('/(\t+)/m', static function ($matches) {
+			$fixed = str_repeat('  ', strlen($matches[1]));
+			return $fixed;
+		}, $line), $lines);
+
+		// take the ws from the first line and subtract them from all lines
+		$matches = [];
+
+		if (preg_match('/(^[ \t]+)/', $lines[0] ?? '', $matches) !== 1) {
+			return '';
+		}
+
+		$numLines = count($lines);
+		for ($i = 0; $i < $numLines; ++$i) {
+			$lines[$i] = str_replace($matches[0], '', $lines[$i] ?? '');
+		}
+
+		return implode("\n", $lines);
+	}
+
 }
