diff --git a/src/Psalm/Codebase.php b/src/Psalm/Codebase.php index cb72efa8b76..1de35369467 100644 --- a/src/Psalm/Codebase.php +++ b/src/Psalm/Codebase.php @@ -1669,7 +1669,7 @@ public function getCompletionItemsForPartialSymbol( ) { $file_contents = $this->getFileContents($file_path); - $class_name = preg_replace('/^.*\\\/', '', $fq_class_name); + $class_name = preg_replace('/^.*\\\/', '', $fq_class_name, 1); if ($aliases->uses_end) { $position = self::getPositionFromOffset($aliases->uses_end, $file_contents); diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 9ac69ea7d42..6dd9b63d125 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -1312,7 +1312,7 @@ public function setCustomErrorLevel(string $issue_key, string $error_level): voi private function loadFileExtensions(SimpleXMLElement $extensions): void { foreach ($extensions as $extension) { - $extension_name = preg_replace('/^\.?/', '', (string)$extension['name']); + $extension_name = preg_replace('/^\.?/', '', (string)$extension['name'], 1); $this->file_extensions[] = $extension_name; if (isset($extension['scanner'])) { @@ -1506,7 +1506,7 @@ private function getPluginClassForPath(Codebase $codebase, string $path, string public function shortenFileName(string $to): string { if (!is_file($to)) { - return preg_replace('/^' . preg_quote($this->base_dir, '/') . '/', '', $to); + return preg_replace('/^' . preg_quote($this->base_dir, '/') . '/', '', $to, 1); } $from = $this->base_dir; @@ -1678,7 +1678,7 @@ public static function getParentIssueType(string $issue_type): ?string } if (strpos($issue_type, 'Possibly') === 0) { - $stripped_issue_type = preg_replace('/^Possibly(False|Null)?/', '', $issue_type); + $stripped_issue_type = preg_replace('/^Possibly(False|Null)?/', '', $issue_type, 1); if (strpos($stripped_issue_type, 'Invalid') === false && strpos($stripped_issue_type, 'Un') !== 0) { $stripped_issue_type = 'Invalid' . $stripped_issue_type; @@ -1692,7 +1692,7 @@ public static function getParentIssueType(string $issue_type): ?string } if (preg_match('/^(False|Null)[A-Z]/', $issue_type) && !strpos($issue_type, 'Reference')) { - return preg_replace('/^(False|Null)/', 'Invalid', $issue_type); + return preg_replace('/^(False|Null)/', 'Invalid', $issue_type, 1); } if ($issue_type === 'UndefinedInterfaceMethod') { diff --git a/src/Psalm/Config/Creator.php b/src/Psalm/Config/Creator.php index f67eb5cf3cd..5a541dca888 100644 --- a/src/Psalm/Config/Creator.php +++ b/src/Psalm/Config/Creator.php @@ -242,7 +242,7 @@ private static function getPsr4Or0Paths(string $current_dir, array $composer_jso continue; } - $path = preg_replace('@[\\\\/]$@', '', $path); + $path = preg_replace('@[/\\\]$@', '', $path, 1); if ($path !== 'tests') { $nodes[] = ''; diff --git a/src/Psalm/Config/FileFilter.php b/src/Psalm/Config/FileFilter.php index 76f53d6be2e..de83e148285 100644 --- a/src/Psalm/Config/FileFilter.php +++ b/src/Psalm/Config/FileFilter.php @@ -421,7 +421,7 @@ function (): bool { */ protected static function slashify(string $str): string { - return preg_replace('/\/?$/', DIRECTORY_SEPARATOR, $str); + return rtrim( $str, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR; } public function allows(string $file_name, bool $case_sensitive = false): bool diff --git a/src/Psalm/Context.php b/src/Psalm/Context.php index 73e642af92e..1acfb9d7d37 100644 --- a/src/Psalm/Context.php +++ b/src/Psalm/Context.php @@ -752,7 +752,7 @@ public function hasVariable(string $var_name): bool return false; } - $stripped_var = preg_replace('/(->|\[).*$/', '', $var_name); + $stripped_var = preg_replace('/(->|\[).*$/', '', $var_name, 1); if ($stripped_var !== '$this' || $var_name !== $stripped_var) { $this->referenced_var_ids[$var_name] = true; diff --git a/src/Psalm/DocComment.php b/src/Psalm/DocComment.php index ae3cd6ca32a..f9cbc8287c6 100644 --- a/src/Psalm/DocComment.php +++ b/src/Psalm/DocComment.php @@ -64,9 +64,9 @@ public static function parse(string $docblock, ?int $line_number = null, bool $p { // Strip off comments. $docblock = trim($docblock); - $docblock = preg_replace('@^/\*\*@', '', $docblock); - $docblock = preg_replace('@\*/$@', '', $docblock); - $docblock = preg_replace('@^[ \t]*\*@m', '', $docblock); + $docblock = preg_replace('@^/\*\*@', '', $docblock, 1); + $docblock = preg_replace('@\*/$@', '', $docblock, 1); + $docblock = preg_replace('@^[ \t]*\*@m', '', $docblock, 1); // Normalize multi-line @specials. $lines = explode("\n", $docblock); @@ -157,7 +157,7 @@ public static function parse(string $docblock, ?int $line_number = null, bool $p // Trim any empty lines off the front, but leave the indent level if there // is one. - $docblock = preg_replace('/^\s*\n/', '', $docblock); + $docblock = preg_replace('/^\s*\n/', '', $docblock, 1); foreach ($special as $special_key => $_) { if (strpos($special_key, 'psalm-') === 0) { diff --git a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php index 51387fc2cdf..c3c47b8c0db 100644 --- a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php @@ -231,7 +231,7 @@ public static function checkFullyQualifiedClassLikeName( return null; } - $fq_class_name = preg_replace('/^\\\/', '', $fq_class_name); + $fq_class_name = preg_replace('/^\\\/', '', $fq_class_name, 1); if (in_array($fq_class_name, ['callable', 'iterable', 'self', 'static', 'parent'], true)) { return true; diff --git a/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php b/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php index 06413e6bbbf..a3d658b6e01 100644 --- a/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php @@ -221,7 +221,7 @@ public static function isWithinAny(string $calling_identifier, array $identifier */ public static function getNameSpaceRoot(string $fullyQualifiedClassName): string { - $root_namespace = preg_replace('/^([^\\\]+).*/', '$1', $fullyQualifiedClassName); + $root_namespace = preg_replace('/^([^\\\]+).*/', '$1', $fullyQualifiedClassName, 1); if ($root_namespace === "") { throw new InvalidArgumentException("Invalid classname \"$fullyQualifiedClassName\""); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index d98cf1fc825..96f26a09497 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -735,7 +735,7 @@ private static function getAnalyzeNamedExpression( if (strpos($var_type_part->value, '::')) { $parts = explode('::', strtolower($var_type_part->value)); $fq_class_name = $parts[0]; - $fq_class_name = preg_replace('/^\\\\/', '', $fq_class_name); + $fq_class_name = preg_replace('/^\\\/', '', $fq_class_name, 1); $potential_method_id = new MethodIdentifier($fq_class_name, $parts[1]); } else { $function_call_info->new_function_name = new VirtualFullyQualified( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php index 9cc9d85098e..e5c7942365a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php @@ -522,7 +522,7 @@ public static function getFunctionIdsFromCallableArg( } if ($callable_arg instanceof PhpParser\Node\Scalar\String_) { - $potential_id = preg_replace('/^\\\/', '', $callable_arg->value); + $potential_id = preg_replace('/^\\\/', '', $callable_arg->value, 1); if (preg_match('/^[A-Za-z0-9_]+(\\\[A-Za-z0-9_]+)*(::[A-Za-z0-9_]+)?$/', $potential_id)) { return [$potential_id]; @@ -552,7 +552,7 @@ public static function getFunctionIdsFromCallableArg( } if ($class_arg instanceof PhpParser\Node\Scalar\String_) { - return [preg_replace('/^\\\/', '', $class_arg->value) . '::' . $method_name_arg->value]; + return [preg_replace('/^\\\/', '', $class_arg->value, 1) . '::' . $method_name_arg->value]; } if ($class_arg instanceof PhpParser\Node\Expr\ClassConstFetch diff --git a/src/Psalm/Internal/Cli/LanguageServer.php b/src/Psalm/Internal/Cli/LanguageServer.php index e8689ee968c..634cdee16a4 100644 --- a/src/Psalm/Internal/Cli/LanguageServer.php +++ b/src/Psalm/Internal/Cli/LanguageServer.php @@ -93,7 +93,7 @@ public static function run(array $argv): void array_map( function (string $arg) use ($valid_long_options): void { if (strpos($arg, '--') === 0 && $arg !== '--') { - $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); + $arg_name = preg_replace('/=.*$/', '', substr($arg, 2), 1); if (!in_array($arg_name, $valid_long_options, true) && !in_array($arg_name . ':', $valid_long_options, true) diff --git a/src/Psalm/Internal/Cli/Psalm.php b/src/Psalm/Internal/Cli/Psalm.php index 86031b7b614..a01a47aa05b 100644 --- a/src/Psalm/Internal/Cli/Psalm.php +++ b/src/Psalm/Internal/Cli/Psalm.php @@ -431,7 +431,7 @@ private static function validateCliArguments(array $args): void array_map( function (string $arg): void { if (strpos($arg, '--') === 0 && $arg !== '--') { - $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); + $arg_name = preg_replace('/=.*$/', '', substr($arg, 2), 1); if (!in_array($arg_name, self::LONG_OPTIONS) && !in_array($arg_name . ':', self::LONG_OPTIONS) diff --git a/src/Psalm/Internal/Cli/Psalter.php b/src/Psalm/Internal/Cli/Psalter.php index a29491475cf..7db204c5f61 100644 --- a/src/Psalm/Internal/Cli/Psalter.php +++ b/src/Psalm/Internal/Cli/Psalter.php @@ -432,7 +432,7 @@ private static function validateCliArguments(array $args): void array_map( function (string $arg): void { if (strpos($arg, '--') === 0 && $arg !== '--') { - $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); + $arg_name = preg_replace('/=.*$/', '', substr($arg, 2), 1); if ($arg_name === 'alter') { // valid option for psalm, ignored by psalter diff --git a/src/Psalm/Internal/Cli/Refactor.php b/src/Psalm/Internal/Cli/Refactor.php index 1c87f4e84a2..864e9d4aebd 100644 --- a/src/Psalm/Internal/Cli/Refactor.php +++ b/src/Psalm/Internal/Cli/Refactor.php @@ -82,7 +82,7 @@ public static function run(array $argv): void array_map( function (string $arg) use ($valid_long_options): void { if (strpos($arg, '--') === 0 && $arg !== '--') { - $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); + $arg_name = preg_replace('/=.*$/', '', substr($arg, 2), 1); if ($arg_name === 'refactor') { // valid option for psalm, ignored by psalter diff --git a/src/Psalm/Internal/Codebase/Analyzer.php b/src/Psalm/Internal/Codebase/Analyzer.php index 36c927131d3..a2d06d4eaaa 100644 --- a/src/Psalm/Internal/Codebase/Analyzer.php +++ b/src/Psalm/Internal/Codebase/Analyzer.php @@ -773,7 +773,7 @@ public function loadCachedResults(ProjectAnalyzer $project_analyzer): void $method_param_uses[$member_id] ); - $member_stub = preg_replace('/::.*$/', '::*', $member_id); + $member_stub = preg_replace('/::.*$/', '::*', $member_id, 1); if (isset($all_referencing_methods[$member_stub])) { $newly_invalidated_methods = array_merge( diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php index d19b202fef6..f81e888620f 100644 --- a/src/Psalm/Internal/Codebase/ClassLikes.php +++ b/src/Psalm/Internal/Codebase/ClassLikes.php @@ -185,7 +185,7 @@ private function collectPredefinedClassLikes(): void $predefined_classes = get_declared_classes(); foreach ($predefined_classes as $predefined_class) { - $predefined_class = preg_replace('/^\\\/', '', $predefined_class); + $predefined_class = preg_replace('/^\\\/', '', $predefined_class, 1); /** @psalm-suppress ArgumentTypeCoercion */ $reflection_class = new ReflectionClass($predefined_class); @@ -201,7 +201,7 @@ private function collectPredefinedClassLikes(): void $predefined_interfaces = get_declared_interfaces(); foreach ($predefined_interfaces as $predefined_interface) { - $predefined_interface = preg_replace('/^\\\/', '', $predefined_interface); + $predefined_interface = preg_replace('/^\\\/', '', $predefined_interface, 1); /** @psalm-suppress ArgumentTypeCoercion */ $reflection_class = new ReflectionClass($predefined_interface); diff --git a/src/Psalm/Internal/Codebase/Properties.php b/src/Psalm/Internal/Codebase/Properties.php index 2a0137a5db6..ba5604ea850 100644 --- a/src/Psalm/Internal/Codebase/Properties.php +++ b/src/Psalm/Internal/Codebase/Properties.php @@ -83,8 +83,8 @@ public function propertyExists( ?Context $context = null, ?CodeLocation $code_location = null ): bool { - // remove trailing backslash if it exists - $property_id = preg_replace('/^\\\\/', '', $property_id); + // remove leading backslash if it exists + $property_id = ltrim( $property_id, '\\' ); [$fq_class_name, $property_name] = explode('::$', $property_id); $fq_class_name_lc = strtolower($fq_class_name); @@ -248,8 +248,8 @@ public function getAppearingClassForProperty( public function getStorage(string $property_id): PropertyStorage { - // remove trailing backslash if it exists - $property_id = preg_replace('/^\\\\/', '', $property_id); + // remove leading backslash if it exists + $property_id = ltrim( $property_id, '\\' ); [$fq_class_name, $property_name] = explode('::$', $property_id); @@ -269,8 +269,8 @@ public function getStorage(string $property_id): PropertyStorage public function hasStorage(string $property_id): bool { - // remove trailing backslash if it exists - $property_id = preg_replace('/^\\\\/', '', $property_id); + // remove leading backslash if it exists + $property_id = ltrim( $property_id, '\\' ); [$fq_class_name, $property_name] = explode('::$', $property_id); @@ -291,8 +291,8 @@ public function getPropertyType( ?StatementsSource $source = null, ?Context $context = null ): ?Union { - // remove trailing backslash if it exists - $property_id = preg_replace('/^\\\\/', '', $property_id); + // remove leading backslash if it exists + $property_id = ltrim( $property_id, '\\' ); [$fq_class_name, $property_name] = explode('::$', $property_id); diff --git a/src/Psalm/Internal/MethodIdentifier.php b/src/Psalm/Internal/MethodIdentifier.php index cf4c81ba63d..1abb0ecded1 100644 --- a/src/Psalm/Internal/MethodIdentifier.php +++ b/src/Psalm/Internal/MethodIdentifier.php @@ -56,8 +56,8 @@ public static function fromMethodIdReference(string $method_id): self if (!static::isValidMethodIdReference($method_id)) { throw new InvalidArgumentException('Invalid method id reference provided: ' . $method_id); } - // remove trailing backslash if it exists - $method_id = preg_replace('/^\\\\/', '', $method_id); + // remove leading backslash if it exists + $method_id = ltrim($method_id, '\\'); $method_id_parts = explode('::', $method_id); return new self($method_id_parts[0], strtolower($method_id_parts[1])); } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php index 4572df37da3..aa2adb811c0 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php @@ -506,7 +506,7 @@ protected static function addMagicPropertyToInfo( ) { $line_parts[1] = str_replace('&', '', $line_parts[1]); - $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1], 1); $end = $offset + strlen($line_parts[0]); diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index 91fbfbfee14..656bab44db9 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -1838,8 +1838,8 @@ private static function getTypeAliasesFromCommentLines( $type_string = str_replace("\n", '', implode('', $var_line_parts)); - $type_string = preg_replace('/>[^>^\}]*$/', '>', $type_string); - $type_string = preg_replace('/\}[^>^\}]*$/', '}', $type_string); + $type_string = preg_replace('/>[^>^\}]*$/', '>', $type_string, 1); + $type_string = preg_replace('/\}[^>^\}]*$/', '}', $type_string, 1); try { $type_tokens = TypeTokenizer::getFullyQualifiedTokens( diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php index 341d0f63918..2eb713b9458 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php @@ -76,7 +76,7 @@ public static function parse( ) { $line_parts[1] = str_replace('&', '', $line_parts[1]); - $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1], 1); $end = $offset + strlen($line_parts[0]); @@ -152,7 +152,7 @@ public static function parse( throw new IncorrectDocblockException('Misplaced variable'); } - $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1], 1); $info->params_out[] = [ 'name' => trim($line_parts[1]), @@ -340,7 +340,7 @@ public static function parse( throw new IncorrectDocblockException('Misplaced variable'); } - $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]); + $line_parts[1] = preg_replace('/,$/', '', $line_parts[1], 1); $info->globals[] = [ 'name' => $line_parts[1], diff --git a/src/Psalm/Internal/Scanner/DocblockParser.php b/src/Psalm/Internal/Scanner/DocblockParser.php index 2ddf134e62c..22ae1ed258f 100644 --- a/src/Psalm/Internal/Scanner/DocblockParser.php +++ b/src/Psalm/Internal/Scanner/DocblockParser.php @@ -111,7 +111,7 @@ public static function parse(string $docblock, int $offsetStart): ParsedDocblock // Strip the leading *, if present. $text = $lines[$k]; $text = str_replace("\t", ' ', $text); - $text = preg_replace('/^ *\*/', '', $text); + $text = preg_replace('/^ *\*/', '', $text, 1); $lines[$k] = $text; } @@ -142,7 +142,7 @@ public static function parse(string $docblock, int $offsetStart): ParsedDocblock // Trim any empty lines off the front, but leave the indent level if there // is one. - $docblock = preg_replace('/^\s*\n/', '', $docblock); + $docblock = preg_replace('/^\s*\n/', '', $docblock, 1); $parsed = new ParsedDocblock($docblock, $special, $first_line_padding ?: '');