Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2890591
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
5 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/project/remarkup/ProjectRemarkupRule.php b/src/applications/project/remarkup/ProjectRemarkupRule.php
index 5bdebfa13e..fa0552593f 100644
--- a/src/applications/project/remarkup/ProjectRemarkupRule.php
+++ b/src/applications/project/remarkup/ProjectRemarkupRule.php
@@ -1,62 +1,76 @@
<?php
final class ProjectRemarkupRule extends PhabricatorObjectRemarkupRule {
protected function getObjectNamePrefix() {
return '#';
}
protected function renderObjectRef($object, $handle, $anchor, $id) {
if ($this->getEngine()->isTextMode()) {
return '#'.$id;
}
return $handle->renderTag();
}
protected function getObjectIDPattern() {
// NOTE: This explicitly does not match strings which contain only
// digits, because digit strings like "#123" are used to reference tasks at
// Facebook and are somewhat conventional in general.
// The latter half of this rule matches monograms with internal periods,
// like `#domain.com`, but does not match monograms with terminal periods,
// because they're probably just puncutation.
// Broadly, this will not match every possible project monogram, and we
// accept some false negatives -- like `#1` or `#dot.` -- in order to avoid
// a bunch of false positives on general use of the `#` character.
// In other contexts, the PhabricatorProjectProjectPHIDType pattern is
// controlling and these names should parse correctly.
- return '[^\s.\d!,:;{}#\(\)]+(?:[^\s!,:;{}#\(\)]*[^\s.!,:;{}#\(\)]+)*';
+ // These characters may never appear anywhere in a hashtag.
+ $never = '\s?!,:;{}#\\(\\)"\'';
+
+ // These characters may not appear at the beginning.
+ $never_first = '.\d';
+
+ // These characters may not appear at the end.
+ $never_last = '.';
+
+ return
+ '[^'.$never_first.$never.']+'.
+ '(?:'.
+ '[^'.$never.']*'.
+ '[^'.$never_last.$never.']+'.
+ ')*';
}
protected function loadObjects(array $ids) {
$viewer = $this->getEngine()->getConfig('viewer');
// Put the "#" back on the front of these IDs.
$names = array();
foreach ($ids as $id) {
$names[] = '#'.$id;
}
// Issue a query by object name.
$query = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames($names);
$query->execute();
$projects = $query->getNamedResults();
// Slice the "#" off again.
$result = array();
foreach ($projects as $name => $project) {
$result[substr($name, 1)] = $project;
}
return $result;
}
}
diff --git a/src/applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php b/src/applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php
index 4df2aad3d9..437b025491 100644
--- a/src/applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php
+++ b/src/applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php
@@ -1,93 +1,123 @@
<?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',
),
),
),
// Don't match a terminal parenthesis. This fixes these constructs in
// natural language.
'There is some documentation (see #guides).' => array(
'embed' => array(),
'ref' => array(
array(
'offset' => 34,
'id' => 'guides',
),
),
),
// Don't match internal parentheses either. This makes the terminal
// parenthesis behavior less arbitrary (otherwise, we match open
// parentheses but not closing parentheses, which is surprising).
'#a(b)c' => array(
'embed' => array(),
'ref' => array(
array(
'offset' => 1,
'id' => 'a',
),
),
),
'#s3' => array(
'embed' => array(),
'ref' => array(
array(
'offset' => 1,
'id' => 's3',
),
),
),
+ 'Is this #urgent?' => array(
+ 'embed' => array(),
+ 'ref' => array(
+ array(
+ 'offset' => 9,
+ 'id' => 'urgent',
+ ),
+ ),
+ ),
+
+ 'This is "#urgent".' => array(
+ 'embed' => array(),
+ 'ref' => array(
+ array(
+ 'offset' => 10,
+ 'id' => 'urgent',
+ ),
+ ),
+ ),
+
+ 'This is \'#urgent\'.' => array(
+ 'embed' => array(),
+ 'ref' => array(
+ array(
+ 'offset' => 10,
+ 'id' => 'urgent',
+ ),
+ ),
+ ),
+
);
foreach ($cases as $input => $expect) {
$rule = new ProjectRemarkupRule();
$matches = $rule->extractReferences($input);
$this->assertEqual($expect, $matches, $input);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 13:49 (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125222
Default Alt Text
(5 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment