mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-27 09:12:41 +01:00
Fix project hashtag regexp to stop matching terminal periods
Summary: Fixes T6416. The comment is consistent with intent, but the actual regexp doesn't quite work right. In particular, we incorrectly match `#security.` as `security.` (with a period) instead of `security` (with no period). Since this stuff is a pain to test and I evidently got it wrong in this case in D8703, make it unit testable. Test Plan: Added unit tests. Also: {F227181} Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6416 Differential Revision: https://secure.phabricator.com/D10753
This commit is contained in:
parent
d5b70e2c1c
commit
dc6b988dea
4 changed files with 131 additions and 11 deletions
|
@ -2823,6 +2823,7 @@ phutil_register_library_map(array(
|
||||||
'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php',
|
'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php',
|
||||||
'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php',
|
'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php',
|
||||||
'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php',
|
'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php',
|
||||||
|
'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php',
|
||||||
'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php',
|
'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php',
|
||||||
'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php',
|
'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php',
|
||||||
'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php',
|
'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php',
|
||||||
|
@ -6015,6 +6016,7 @@ phutil_register_library_map(array(
|
||||||
'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability',
|
'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability',
|
||||||
'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod',
|
'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod',
|
||||||
'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||||
|
'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase',
|
||||||
'QueryFormattingTestCase' => 'PhabricatorTestCase',
|
'QueryFormattingTestCase' => 'PhabricatorTestCase',
|
||||||
'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification',
|
'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification',
|
||||||
'ReleephBranch' => array(
|
'ReleephBranch' => array(
|
||||||
|
|
|
@ -30,7 +30,7 @@ final class ProjectRemarkupRule extends PhabricatorObjectRemarkupRule {
|
||||||
// In other contexts, the PhabricatorProjectProjectPHIDType pattern is
|
// In other contexts, the PhabricatorProjectProjectPHIDType pattern is
|
||||||
// controlling and these names should parse correctly.
|
// controlling and these names should parse correctly.
|
||||||
|
|
||||||
return '[^\s.!,:;{}#]*[^\s\d!,:;{}#]+(?:[^\s.!,:;{}#][^\s!,:;{}#]*)*';
|
return '[^\s.\d!,:;{}#]+(?:[^\s!,:;{}#][^\s.!,:;{}#]+)*';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function loadObjects(array $ids) {
|
protected function loadObjects(array $ids) {
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class ProjectRemarkupRuleTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
|
public function testProjectObjectRemarkup() {
|
||||||
|
$cases = array(
|
||||||
|
'I like #ducks.' => array(
|
||||||
|
'embed' => array(),
|
||||||
|
'ref' => array(
|
||||||
|
array(
|
||||||
|
'offset' => 8,
|
||||||
|
'id' => 'ducks',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'We should make a post on #blog.example.com tomorrow.' => array(
|
||||||
|
'embed' => array(),
|
||||||
|
'ref' => array(
|
||||||
|
array(
|
||||||
|
'offset' => 26,
|
||||||
|
'id' => 'blog.example.com',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'We should make a post on #blog.example.com.' => array(
|
||||||
|
'embed' => array(),
|
||||||
|
'ref' => array(
|
||||||
|
array(
|
||||||
|
'offset' => 26,
|
||||||
|
'id' => 'blog.example.com',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'#123' => array(
|
||||||
|
'embed' => array(),
|
||||||
|
'ref' => array(),
|
||||||
|
),
|
||||||
|
'#security#123' => array(
|
||||||
|
'embed' => array(),
|
||||||
|
'ref' => array(
|
||||||
|
array(
|
||||||
|
'offset' => 1,
|
||||||
|
'id' => 'security',
|
||||||
|
'tail' => '123',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($cases as $input => $expect) {
|
||||||
|
$rule = new ProjectRemarkupRule();
|
||||||
|
$matches = $rule->extractReferences($input);
|
||||||
|
$this->assertEqual($expect, $matches, $input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -95,15 +95,33 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function apply($text) {
|
public function apply($text) {
|
||||||
$prefix = $this->getObjectNamePrefix();
|
|
||||||
$prefix = preg_quote($prefix, '@');
|
|
||||||
$id = $this->getObjectIDPattern();
|
|
||||||
|
|
||||||
$text = preg_replace_callback(
|
$text = preg_replace_callback(
|
||||||
'@\B{'.$prefix.'('.$id.')((?:[^}\\\\]|\\\\.)*)}\B@u',
|
$this->getObjectEmbedPattern(),
|
||||||
array($this, 'markupObjectEmbed'),
|
array($this, 'markupObjectEmbed'),
|
||||||
$text);
|
$text);
|
||||||
|
|
||||||
|
$text = preg_replace_callback(
|
||||||
|
$this->getObjectReferencePattern(),
|
||||||
|
array($this, 'markupObjectReference'),
|
||||||
|
$text);
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getObjectEmbedPattern() {
|
||||||
|
$prefix = $this->getObjectNamePrefix();
|
||||||
|
$prefix = preg_quote($prefix);
|
||||||
|
$id = $this->getObjectIDPattern();
|
||||||
|
|
||||||
|
return '(\B{'.$prefix.'('.$id.')((?:[^}\\\\]|\\\\.)*)}\B)u';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getObjectReferencePattern() {
|
||||||
|
$prefix = $this->getObjectNamePrefix();
|
||||||
|
$prefix = preg_quote($prefix);
|
||||||
|
|
||||||
|
$id = $this->getObjectIDPattern();
|
||||||
|
|
||||||
// If the prefix starts with a word character (like "D"), we want to
|
// If the prefix starts with a word character (like "D"), we want to
|
||||||
// require a word boundary so that we don't match "XD1" as "D1". If the
|
// require a word boundary so that we don't match "XD1" as "D1". If the
|
||||||
// prefix does not start with a word character, we want to require no word
|
// prefix does not start with a word character, we want to require no word
|
||||||
|
@ -121,12 +139,55 @@ abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
|
||||||
// The "\b" allows us to link "(abcdef)" or similar without linking things
|
// The "\b" allows us to link "(abcdef)" or similar without linking things
|
||||||
// in the middle of words.
|
// in the middle of words.
|
||||||
|
|
||||||
$text = preg_replace_callback(
|
return '((?<![#-])'.$boundary.$prefix.'('.$id.')(?:#([-\w\d]+))?(?!\w))u';
|
||||||
'((?<![#-])'.$boundary.$prefix.'('.$id.')(?:#([-\w\d]+))?(?!\w))u',
|
}
|
||||||
array($this, 'markupObjectReference'),
|
|
||||||
$text);
|
|
||||||
|
|
||||||
return $text;
|
|
||||||
|
/**
|
||||||
|
* Extract matched object references from a block of text.
|
||||||
|
*
|
||||||
|
* This is intended to make it easy to write unit tests for object remarkup
|
||||||
|
* rules. Production code is not normally expected to call this method.
|
||||||
|
*
|
||||||
|
* @param string Text to match rules against.
|
||||||
|
* @return wild Matches, suitable for writing unit tests against.
|
||||||
|
*/
|
||||||
|
public function extractReferences($text) {
|
||||||
|
$embed_matches = null;
|
||||||
|
preg_match_all(
|
||||||
|
$this->getObjectEmbedPattern(),
|
||||||
|
$text,
|
||||||
|
$embed_matches,
|
||||||
|
PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
|
||||||
|
|
||||||
|
$ref_matches = null;
|
||||||
|
preg_match_all(
|
||||||
|
$this->getObjectReferencePattern(),
|
||||||
|
$text,
|
||||||
|
$ref_matches,
|
||||||
|
PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
|
||||||
|
|
||||||
|
$results = array();
|
||||||
|
$sets = array(
|
||||||
|
'embed' => $embed_matches,
|
||||||
|
'ref' => $ref_matches,
|
||||||
|
);
|
||||||
|
foreach ($sets as $type => $matches) {
|
||||||
|
$formatted = array();
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
$format = array(
|
||||||
|
'offset' => $match[1][1],
|
||||||
|
'id' => $match[1][0],
|
||||||
|
);
|
||||||
|
if (isset($match[2][0])) {
|
||||||
|
$format['tail'] = $match[2][0];
|
||||||
|
}
|
||||||
|
$formatted[] = $format;
|
||||||
|
}
|
||||||
|
$results[$type] = $formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function markupObjectEmbed($matches) {
|
public function markupObjectEmbed($matches) {
|
||||||
|
|
Loading…
Reference in a new issue