浏览代码

Skip mentions inside codeblocks

Mark Rousskov 5 年之前
父节点
当前提交
8532c3b30e
共有 3 个文件被更改,包括 95 次插入5 次删除
  1. 3 0
      parser/src/lib.rs
  2. 86 0
      parser/src/mentions.rs
  3. 6 5
      src/handlers/notification.rs

+ 3 - 0
parser/src/lib.rs

@@ -1,4 +1,7 @@
 mod code_block;
 pub mod command;
 pub mod error;
+mod mentions;
 mod token;
+
+pub use mentions::get_mentions;

+ 86 - 0
parser/src/mentions.rs

@@ -0,0 +1,86 @@
+/// This provides a list of usernames or teams that were pinged in the text
+/// provided.
+///
+/// It will appropriately skip mentions just like GitHub, i.e., mentions inside
+/// code blocks will be ignored.
+///
+/// Note that the `@` is skipped in the final output.
+pub fn get_mentions(input: &str) -> Vec<&str> {
+    let code_regions = crate::code_block::ColorCodeBlocks::new(input);
+
+    let mut mentions = Vec::new();
+    for (idx, _) in input.match_indices('@') {
+        if let Some(previous) = input[..idx].chars().next_back() {
+            // A github username must stand apart from other text.
+            //
+            // Oddly enough, english letters do not work, but letters outside
+            // ASCII do work as separators; for now just go with this limited
+            // list.
+            if let 'a'..='z' | 'A'..='Z' = previous {
+                continue;
+            }
+        }
+        let mut saw_slash = false;
+        let username_end = input
+            .get(idx + 1..)
+            .unwrap_or_default()
+            .char_indices()
+            .find(|(_, terminator)| match terminator {
+                // These are the valid characters inside of a GitHub
+                // username
+                'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => false,
+                '/' if !saw_slash => {
+                    saw_slash = true;
+                    false
+                }
+                _ => true,
+            })
+            .map(|(end, _)| idx + 1 + end)
+            .unwrap_or(input.len());
+        let username = input.get(idx + 1..username_end).unwrap_or_default();
+        if username.is_empty() {
+            continue;
+        }
+        if code_regions
+            .overlaps_code(idx..idx + username.len())
+            .is_some()
+        {
+            continue;
+        }
+        mentions.push(username);
+    }
+    mentions
+}
+
+#[test]
+fn mentions_in_code_ignored() {
+    assert_eq!(
+        get_mentions("@rust-lang/libs `@user`"),
+        vec!["rust-lang/libs"]
+    );
+    assert_eq!(get_mentions("@user `@user`"), vec!["user"]);
+    assert_eq!(get_mentions("`@user`"), Vec::<&str>::new());
+}
+
+#[test]
+fn italics() {
+    assert_eq!(get_mentions("*@rust-lang/libs*"), vec!["rust-lang/libs"]);
+}
+
+#[test]
+fn slash() {
+    assert_eq!(
+        get_mentions("@rust-lang/libs/@rust-lang/release"),
+        vec!["rust-lang/libs", "rust-lang/release"]
+    );
+}
+
+#[test]
+fn no_panic_lone() {
+    assert_eq!(get_mentions("@ `@`"), Vec::<&str>::new());
+}
+
+#[test]
+fn no_email() {
+    assert_eq!(get_mentions("user@example.com"), Vec::<&str>::new());
+}

+ 6 - 5
src/handlers/notification.rs

@@ -15,7 +15,6 @@ use std::collections::HashSet;
 use std::convert::{TryFrom, TryInto};
 
 lazy_static::lazy_static! {
-    static ref PING_RE: Regex = Regex::new(r#"@([-\w\d/]+)"#,).unwrap();
     static ref ACKNOWLEDGE_RE: Regex = Regex::new(r#"acknowledge (https?://[^ ]+)"#,).unwrap();
 }
 
@@ -87,9 +86,8 @@ pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> {
         Event::IssueComment(e) => format!("Comment on {}", e.issue.title),
     };
 
-    let caps = PING_RE
-        .captures_iter(body)
-        .map(|c| c.get(1).unwrap().as_str().to_owned())
+    let caps = parser::get_mentions(body)
+        .into_iter()
         .collect::<HashSet<_>>();
     let mut users_notified = HashSet::new();
     // We've implicitly notified the user that is submitting the notification:
@@ -155,7 +153,10 @@ pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> {
                 Some(team.name),
             )
         } else {
-            let user = github::User { login, id: None };
+            let user = github::User {
+                login: login.to_owned(),
+                id: None,
+            };
             let id = user
                 .get_id(&ctx.github)
                 .await