From 83e63aeb078bf0ee4f1a9f9d0eab32bdffc6c7ff Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 4 Sep 2020 17:24:25 -0700 Subject: [PATCH] Allow AAST to extract string literal values from HEREDOCs Summary: Ref T13577. I'd like to `arc lint --everything` to find other bad calls to `pht()` and similar functions, but `n_HEREDOC` nodes currently can not generate a response to "getStringLiteralValue()". Support literal extraction from heredocs. Test Plan: Added a test, made it pass. Will lint everything. Maniphest Tasks: T13577 Differential Revision: https://secure.phabricator.com/D21455 --- .../formatted-string.lint-test | 11 ++++ src/parser/xhpast/api/XHPASTNode.php | 54 +++++++++++++------ 2 files changed, 49 insertions(+), 16 deletions(-) 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 @@ -14,6 +14,16 @@ foobar(null, null, '%s'); pht('x %s y'); pht('x %s y'.'z'); + +pht(<<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)) { @@ -186,31 +188,51 @@ final class XHPASTNode extends AASTNode { } 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_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); + 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) {