@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Tighten query compiler rules around spaces inside and after operators

Summary:
Ref T13509. Since `title:- cat` is now ambiguous, forbid spaces after operators.

Also, forbid spaces inside operators, although this has no effect today.

Test Plan: Added unit tests, ran unit tests.

Maniphest Tasks: T13509

Differential Revision: https://secure.phabricator.com/D21109

+73 -10
+50 -5
src/applications/search/compiler/PhutilSearchQueryCompiler.php
··· 172 172 } 173 173 174 174 if ($mode == 'operator') { 175 - if (preg_match('/^\s\z/u', $character)) { 176 - continue; 175 + if (!$current_operator) { 176 + if (preg_match('/^\s\z/u', $character)) { 177 + continue; 178 + } 177 179 } 178 180 179 181 if (preg_match('/^'.$operator_characters.'\z/', $character)) { ··· 337 339 'operator' => $operator, 338 340 'quoted' => $is_quoted, 339 341 'value' => $value, 342 + 'raw' => $this->getDisplayToken($token), 340 343 ); 341 344 342 345 if ($enable_functions) { ··· 355 358 356 359 $result['function'] = $function; 357 360 358 - if ($result['quoted']) { 361 + $is_sticky = !$result['quoted']; 362 + switch ($operator) { 363 + case self::OPERATOR_ABSENT: 364 + case self::OPERATOR_PRESENT: 365 + $is_sticky = false; 366 + break; 367 + } 368 + 369 + if ($is_sticky) { 370 + $last_function = $function; 371 + } else { 359 372 $last_function = null; 373 + } 374 + } 375 + 376 + $results[] = $result; 377 + } 378 + 379 + if ($enable_functions) { 380 + // If any function is required to be "absent", there must be no other 381 + // terms which make assertions about it. 382 + 383 + $present_tokens = array(); 384 + $absent_tokens = array(); 385 + foreach ($results as $result) { 386 + $function = $result['function']; 387 + 388 + if ($result['operator'] === self::OPERATOR_ABSENT) { 389 + $absent_tokens[$function][] = $result; 360 390 } else { 361 - $last_function = $function; 391 + $present_tokens[$function][] = $result; 362 392 } 363 393 } 364 394 365 - $results[] = $result; 395 + foreach ($absent_tokens as $function => $tokens) { 396 + $absent_token = head($tokens); 397 + 398 + if (empty($present_tokens[$function])) { 399 + continue; 400 + } 401 + 402 + $present_token = head($present_tokens[$function]); 403 + 404 + throw new PhutilSearchQueryCompilerSyntaxException( 405 + pht( 406 + 'Query field must be absent ("%s") and present ("%s"). This '. 407 + 'is impossible, so the query is not valid.', 408 + $absent_token['raw'], 409 + $present_token['raw'])); 410 + } 366 411 } 367 412 368 413 return $results;
+23 -5
src/applications/search/compiler/__tests__/PhutilSearchQueryCompilerTestCase.php
··· 10 10 'cat -dog' => '+"cat" -"dog"', 11 11 'cat-dog' => '+"cat-dog"', 12 12 13 - // If there are spaces after an operator, the operator applies to the 14 - // next search term. 15 - 'cat - dog' => '+"cat" -"dog"', 16 - 17 13 // Double quotes serve as delimiters even if there is no whitespace 18 14 // between terms. 19 15 '"cat"dog' => '+"cat" +"dog"', ··· 40 36 // Trailing whitespace should be discarded. 41 37 'a b ' => '+"a" +"b"', 42 38 43 - // Functions must have search text. 39 + // Tokens must have search text. 44 40 '""' => false, 45 41 '-' => false, 42 + 43 + // Previously, we permitted spaces to appear inside or after operators. 44 + 45 + // Now that "title:-" is now a valid construction meaning "title is 46 + // absent", this had to be tightened. We want "title:- duck" to mean 47 + // "title is absent, and any other field matches 'duck'". 48 + 'cat - dog' => false, 46 49 ); 47 50 48 51 $this->assertCompileQueries($tests); ··· 171 174 array('title', $op_and, 'x'), 172 175 array(null, $op_and, 'y'), 173 176 ), 177 + 178 + // The "present" and "absent" functions are not sticky. 179 + 'title:~ x' => array( 180 + array('title', $op_present, null), 181 + array(null, $op_and, 'x'), 182 + ), 183 + 'title:- x' => array( 184 + array('title', $op_absent, null), 185 + array(null, $op_and, 'x'), 186 + ), 187 + 188 + // These queries require a field be both present and absent, which is 189 + // impossible. 190 + 'title:- title:x' => false, 191 + 'title:- title:~' => false, 174 192 ); 175 193 176 194 $this->assertCompileFunctionQueries($function_tests);