public function getProperties(): array
    {
        // Check cache
        if (null === $this->properties) {
            $tokens = $this->getTokenStream();

            // Create empty set, to denote that
            // we parsed all the properties
            $this->properties = [];

            // Start parsing from the class name location
            // and trigger ClassDefinitionNotFoundException when called
            // on a file not containing a class.
            $vis_loc = $this->getClassNameLocation();

            // Scan for public, protected and private because
            // these keywords denote the start of a property.
            // var is excluded because its use is deprecated.
            while ($vis_loc = $tokens->scan($vis_loc, [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_VAR])) {
                // Seek forward, skipping static, if it was found and check if we
                // really have a property here, otherwise confine and try to find more
                // properties.

                // We also skip final, to improve error handling and consistent behaviour,
                // otherwise final private $foo would be parsed and private final $bar
                // would not be parsed.
                $var_loc = $tokens->next($vis_loc, [T_COMMENT, T_WHITESPACE, T_STATIC, T_FINAL]);
                if ($tokens->type($var_loc) !== T_VARIABLE) {
                    continue;
                }

                $doc_comment = $this->parseDocComment($vis_loc);           // doc comment
                $modifiers   = $this->parsePropertyModifiers($vis_loc);    // public, protected, private, static
                $name        = substr($tokens->value($var_loc), 1);  // property name
                $default     = $this->parseDefaultValue($var_loc);         // default value
                $property    = new ReflectionProperty($name, $modifiers, $default, $doc_comment, $this);

                $this->properties[] = $property;
            }
        }

        return $this->properties;
    }