Page MenuHomeSealhub

No OneTemporary

diff --git a/src/lint/linter/xhpast/rules/__tests__/formatted-string/formatted-string.lint-test b/src/lint/linter/xhpast/rules/__tests__/formatted-string/formatted-string.lint-test
index 67ec7746..f9e5a11b 100644
--- a/src/lint/linter/xhpast/rules/__tests__/formatted-string/formatted-string.lint-test
+++ b/src/lint/linter/xhpast/rules/__tests__/formatted-string/formatted-string.lint-test
@@ -1,33 +1,44 @@
<?php
printf();
printf(null);
printf('');
sprintf('%s');
pht('%s', 'foo', 'bar');
fprintf(null, 'x');
queryfx(null, 'x', 'y');
foobar(null, null, '%s');
pht('x %s y');
pht('x %s y'.'z');
+
+pht(<<<HEREDOC
+a b c
+HEREDOC
+ );
+
+pht(<<<HEREDOC
+a %s c
+HEREDOC
+ );
~~~~~~~~~~
error:3:1:XHP54:Formatted String
error:7:1:XHP54:Formatted String
error:8:1:XHP54:Formatted String
error:11:1:XHP54:Formatted String
error:13:1:XHP54:Formatted String
error:15:1:XHP54:Formatted String
error:16:1:XHP54:Formatted String
+error:23:1:XHP54:Formatted String
~~~~~~~~~~
~~~~~~~~~~
{
"config": {
"xhpast.printf-functions": {
"foobar": 2
}
}
}
diff --git a/src/parser/xhpast/api/XHPASTNode.php b/src/parser/xhpast/api/XHPASTNode.php
index 4decaea1..0f685c5e 100644
--- a/src/parser/xhpast/api/XHPASTNode.php
+++ b/src/parser/xhpast/api/XHPASTNode.php
@@ -1,320 +1,342 @@
<?php
final class XHPASTNode extends AASTNode {
public function isStaticScalar() {
return in_array($this->getTypeName(), array(
'n_STRING_SCALAR',
'n_NUMERIC_SCALAR',
));
}
public function getDocblockToken() {
if ($this->l == -1) {
return null;
}
$tokens = $this->tree->getRawTokenStream();
for ($ii = $this->l - 1; $ii >= 0; $ii--) {
if ($tokens[$ii]->getTypeName() == 'T_DOC_COMMENT') {
return $tokens[$ii];
}
if (!$tokens[$ii]->isAnyWhitespace()) {
return null;
}
}
return null;
}
public function evalStatic() {
switch ($this->getTypeName()) {
case 'n_STATEMENT':
return $this->getChildByIndex(0)->evalStatic();
break;
case 'n_STRING_SCALAR':
- return (string)$this->getStringLiteralValue();
+ return phutil_string_cast($this->getStringLiteralValue());
+ case 'n_HEREDOC':
+ return phutil_string_cast($this->getStringLiteralValue());
case 'n_NUMERIC_SCALAR':
$value = $this->getSemanticString();
if (preg_match('/^0x/i', $value)) {
// Hex
$value = base_convert(substr($value, 2), 16, 10);
} else if (preg_match('/^0\d+$/i', $value)) {
// Octal
$value = base_convert(substr($value, 1), 8, 10);
}
return +$value;
case 'n_SYMBOL_NAME':
$value = $this->getSemanticString();
if ($value == 'INF') {
return INF;
}
switch (strtolower($value)) {
case 'true':
return true;
case 'false':
return false;
case 'null':
return null;
default:
throw new Exception(pht('Unrecognized symbol name.'));
}
break;
case 'n_UNARY_PREFIX_EXPRESSION':
$operator = $this->getChildOfType(0, 'n_OPERATOR');
$operand = $this->getChildByIndex(1);
switch ($operator->getSemanticString()) {
case '-':
return -$operand->evalStatic();
break;
case '+':
return $operand->evalStatic();
break;
default:
throw new Exception(
pht('Unexpected operator in static expression.'));
}
break;
case 'n_ARRAY_LITERAL':
$result = array();
$values = $this->getChildOfType(0, 'n_ARRAY_VALUE_LIST');
foreach ($values->getChildren() as $child) {
$key = $child->getChildByIndex(0);
$val = $child->getChildByIndex(1);
if ($key->getTypeName() == 'n_EMPTY') {
$result[] = $val->evalStatic();
} else {
$result[$key->evalStatic()] = $val->evalStatic();
}
}
return $result;
case 'n_CONCATENATION_LIST':
$result = '';
foreach ($this->getChildren() as $child) {
if ($child->getTypeName() == 'n_OPERATOR') {
continue;
}
$result .= $child->evalStatic();
}
return $result;
default:
throw new Exception(
pht(
'Unexpected node during static evaluation, of type: %s',
$this->getTypeName()));
}
}
public function isConstantString() {
return $this->checkIsConstantString();
}
public function isConstantStringWithMagicConstants() {
return $this->checkIsConstantString(array('n_MAGIC_SCALAR'));
}
private function checkIsConstantString(array $additional_types = array()) {
switch ($this->getTypeName()) {
case 'n_HEREDOC':
case 'n_STRING_SCALAR':
return !$this->getStringVariables();
case 'n_CONCATENATION_LIST':
foreach ($this->getChildren() as $child) {
if ($child->getTypeName() == 'n_OPERATOR') {
continue;
}
if (!$child->checkIsConstantString($additional_types)) {
return false;
}
}
return true;
default:
if (in_array($this->getTypeName(), $additional_types)) {
return true;
}
return false;
}
}
public function getStringVariables() {
$value = $this->getConcreteString();
switch ($this->getTypeName()) {
case 'n_HEREDOC':
if (preg_match("/^<<<\s*'/", $value)) { // Nowdoc: <<<'EOT'
return array();
}
break;
case 'n_STRING_SCALAR':
if ($value[0] == "'") {
return array();
}
break;
default:
throw new Exception(pht('Unexpected type %s.', $this->getTypeName()));
}
// We extract just the variable names and ignore properties and array keys.
$re = '/\\\\.|(\$|\{\$|\${)([a-z_\x7F-\xFF][a-z0-9_\x7F-\xFF]*)/i';
$matches = null;
preg_match_all($re, $value, $matches, PREG_OFFSET_CAPTURE);
// NOTE: The result format for this construction changed in PHP 7.4.
// See T13518.
$names = $matches[2];
foreach ($names as $name_idx => $name_match) {
if ($name_match === '') {
unset($names[$name_idx]);
continue;
}
if ($name_match[1] === -1) {
unset($names[$name_idx]);
continue;
}
}
$names = ipull($names, 0, 1);
return $names;
}
public function getStringLiteralValue() {
- if ($this->getTypeName() != 'n_STRING_SCALAR') {
- return null;
+ $type_name = $this->getTypeName();
+
+ if ($type_name === 'n_HEREDOC') {
+ $value = $this->getSemanticString();
+ $value = phutil_split_lines($value);
+ $value = array_slice($value, 1, -1);
+ $value = implode('', $value);
+
+ // Strip the final newline from value, this isn't part of the string
+ // literal.
+ $value = preg_replace('/(\r|\n|\r\n)\z/', '', $value);
+
+ return $this->newStringLiteralFromSemanticString($value);
}
- $value = $this->getSemanticString();
- $type = $value[0];
- $value = preg_replace('/^b?[\'"]|[\'"]$/i', '', $value);
- $esc = false;
- $len = strlen($value);
- $out = '';
-
- if ($type == "'") {
- // Single quoted strings treat everything as a literal except "\\" and
- // "\'".
- return str_replace(
- array('\\\\', '\\\''),
- array('\\', "'"),
- $value);
+ if ($type_name === 'n_STRING_SCALAR') {
+ $value = $this->getSemanticString();
+ $type = $value[0];
+ $value = preg_replace('/^b?[\'"]|[\'"]$/i', '', $value);
+
+ if ($type == "'") {
+ // Single quoted strings treat everything as a literal except "\\" and
+ // "\'".
+ return str_replace(
+ array('\\\\', '\\\''),
+ array('\\', "'"),
+ $value);
+ }
+
+ return $this->newStringLiteralFromSemanticString($value);
}
+ return null;
+ }
+
+ private function newStringLiteralFromSemanticString($value) {
// Double quoted strings treat "\X" as a literal if X isn't specifically
// a character which needs to be escaped -- e.g., "\q" and "\'" are
// literally "\q" and "\'". stripcslashes() is too aggressive, so find
// all these under-escaped backslashes and escape them.
+ $len = strlen($value);
+ $esc = false;
+ $out = '';
+
for ($ii = 0; $ii < $len; $ii++) {
$c = $value[$ii];
if ($esc) {
$esc = false;
switch ($c) {
case 'x':
$u = isset($value[$ii + 1]) ? $value[$ii + 1] : null;
if (!preg_match('/^[a-f0-9]/i', $u)) {
// PHP treats \x followed by anything which is not a hex digit
// as a literal \x.
$out .= '\\\\'.$c;
break;
}
/* fallthrough */
case 'n':
case 'r':
case 'f':
case 'v':
case '"':
case '$':
case 't':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
$out .= '\\'.$c;
break;
case 'e':
// Since PHP 5.4.0, this means "esc". However, stripcslashes() does
// not perform this conversion.
$out .= chr(27);
break;
default:
$out .= '\\\\'.$c;
break;
}
} else if ($c == '\\') {
$esc = true;
} else {
$out .= $c;
}
}
return stripcslashes($out);
}
/**
* Determines the parent namespace for a node.
*
* Traverses the AST upwards from a given node in order to determine the
* namespace in which the node is declared.
*
* To prevent any possible ambiguity, the returned namespace will always be
* prefixed with the namespace separator.
*
* @param XHPASTNode The input node.
* @return string|null The namespace which contains the input node, or
* `null` if no such node exists.
*/
public function getNamespace() {
$namespaces = $this
->getTree()
->getRootNode()
->selectDescendantsOfType('n_NAMESPACE')
->getRawNodes();
foreach (array_reverse($namespaces) as $namespace) {
if ($namespace->isAfter($this)) {
continue;
}
$body = $namespace->getChildByIndex(1);
if ($body->getTypeName() != 'n_EMPTY') {
if (!$body->containsDescendant($this)) {
continue;
}
}
return $namespace->getNamespaceName();
}
return null;
}
/**
* Returns the namespace name from a node of type `n_NAMESPACE`.
*
* @return string|null
*/
private function getNamespaceName() {
if ($this->getTypeName() != 'n_NAMESPACE') {
return null;
}
$namespace_name = $this->getChildByIndex(0);
if ($namespace_name->getTypeName() == 'n_EMPTY') {
return null;
}
return '\\'.$namespace_name->getConcreteString();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jan 23, 21:14 (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
601529
Default Alt Text
(11 KB)

Event Timeline