MCPcopy Create free account
hub / github.com/PHPantom-dev/phpantom_lsp / is_valid_expression

Function is_valid_expression

src/code_actions/extract_variable.rs:56–129  ·  view source on GitHub ↗

Returns `true` when the selected text parses as a valid, self-contained PHP expression. We wrap it in `<?php $__x = ;` and check that the parser produces no errors. This rejects fragments like `save` (bare method name), `$this` when it's part of `$this->foo()`, partial tokens, and other nonsensical selections.

(selected_text: &str)

Source from the content-addressed store, hash-verified

54/// `save` (bare method name), `$this` when it's part of `$this->foo()`,
55/// partial tokens, and other nonsensical selections.
56fn is_valid_expression(selected_text: &str) -> bool {
57 let trimmed = selected_text.trim();
58 if trimmed.is_empty() {
59 return false;
60 }
61
62 // Quick rejects for obvious non-expressions:
63 // - Bare identifiers that aren't `$var`, `self`, `static`, `parent`,
64 // `true`, `false`, `null`, or a numeric/string literal.
65 // e.g. `save`, `getName` — these are method/function names, not
66 // standalone expressions.
67 if !trimmed.starts_with('$')
68 && !trimmed.starts_with('\'')
69 && !trimmed.starts_with('"')
70 && !trimmed.starts_with('[')
71 && !trimmed.starts_with('(')
72 && !trimmed.starts_with("new ")
73 && !trimmed.starts_with("clone ")
74 && !trimmed.starts_with("fn(")
75 && !trimmed.starts_with("fn (")
76 && !trimmed.starts_with("function")
77 && !trimmed.starts_with("match")
78 && !trimmed.starts_with("yield")
79 && !trimmed.starts_with("throw")
80 && !trimmed.starts_with('!')
81 && !trimmed.starts_with('-')
82 && !trimmed.starts_with('~')
83 && !trimmed.starts_with('\\')
84 && !trimmed.starts_with("self::")
85 && !trimmed.starts_with("static::")
86 && !trimmed.starts_with("parent::")
87 {
88 // Could be a numeric literal (0, 1.5, 0x1F, etc.), a constant
89 // (true/false/null/CONST), or a function/static-method call.
90 // Allow those through if they look like a call or known keyword.
91 let first_char = trimmed.as_bytes()[0];
92 let is_numeric = first_char.is_ascii_digit();
93 let is_keyword = matches!(
94 trimmed,
95 "true" | "false" | "null" | "self" | "static" | "parent"
96 );
97 // Allow `ClassName::method(...)`, `func(...)`, `CONST_NAME`.
98 let has_call_parens = trimmed.contains('(');
99 let has_double_colon = trimmed.contains("::");
100 let is_all_upper_const = trimmed.chars().all(|c| c.is_ascii_uppercase() || c == '_');
101
102 if !is_numeric
103 && !is_keyword
104 && !has_call_parens
105 && !has_double_colon
106 && !is_all_upper_const
107 {
108 return false;
109 }
110 }
111
112 // Reject selections that contain a semicolon in a non-trailing
113 // position — this indicates multiple statements (e.g.

Calls 4

containsMethod · 0.80
is_emptyMethod · 0.45
allMethod · 0.45

Tested by

no test coverage detected