From bb6579bd5af212a922d8a9b1a3a878ed07ffb2cb Mon Sep 17 00:00:00 2001 From: zy95-12 <135135297+zy95-12@users.noreply.github.com> Date: Wed, 4 Nov 2026 18:09:45 +0000 Subject: [PATCH] fix(issue-ref): do not read a URL fragment as owner/repo#12 The repository-qualified shape had no lookbehind in front of it, so any link whose path carried a segment before the `#` matched it as `/#12`: `https://example.com/page#123` named issue #123, and the dispatch wrote a card for an issue nobody meant. The qualified shape now needs the same boundary as the bare number, so a URL fragment stays a fragment while `owner/repo#12`, a bare `#12` and a GitHub issue/pull URL still name the issue. Test: core/test/issue-ref-url-fragment.test.js (red on the base: 4/4). Docs-not-needed: the shape list in docs/subsystems/core-host.md is unchanged; this closes a hole in the rule the module and its tests already state (#1392), with no new module, contract or owned-path change. --- core/src/shared/issue-ref.js | 6 ++-- core/test/issue-ref-url-fragment.test.js | 29 +++++++++++++++++++ patches/ds-bot/files/src/shared/issue-ref.js | 6 ++-- .../files/test/issue-ref-url-fragment.test.js | 29 +++++++++++++++++++ 4 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 core/test/issue-ref-url-fragment.test.js create mode 100644 patches/ds-bot/files/test/issue-ref-url-fragment.test.js diff --git a/core/src/shared/issue-ref.js b/core/src/shared/issue-ref.js index 015f85595..7eb2dbf50 100644 --- a/core/src/shared/issue-ref.js +++ b/core/src/shared/issue-ref.js @@ -6,8 +6,10 @@ // front makes it part of an identifier, a version, a hex colour or a URL fragment (`C#7`, `1.2#3`, // `#3366ff`, `help/#123`), and a word character behind means the number keeps going (`#1360abc`). // `owner/repo#12` is its own shape, tried separately, so this separation cannot swallow it: there -// the `#` follows a repo name rather than a space. -const ISSUE_REF = /(?/#12` — `https://example.com/page#123` named issue #123 — and the dispatch wrote +// a card for an issue nobody meant (#1360). +import assert from 'node:assert/strict' +import { test } from 'node:test' +import { issueRefs, namesIssue } from '../src/shared/issue-ref.js' + +const fragments = [ + 'see https://example.com/page#123 for details', + 'https://example.com/docs/guide#12', + 'read https://docs.example.org/a/b#42 then', +] + +for (const text of fragments) { + test(`a URL fragment is not an issue reference: ${text}`, () => { + assert.deepEqual(issueRefs(text), [], text) + }) +} + +test('the shapes that name an issue keep working beside a URL', () => { + assert.deepEqual(issueRefs('owner/repo#1360'), ['#1360']) + assert.deepEqual(issueRefs('see owner/repo#1360 for the fix'), ['#1360']) + assert.deepEqual(issueRefs('please rebase #1360 onto main'), ['#1360']) + assert.deepEqual(issueRefs('https://github.com/o/r/issues/1360'), ['#1360']) + assert.deepEqual(issueRefs('@Dev-2 rebase owner/repo#1360'), ['#1360']) + assert.equal(namesIssue('https://example.com/docs/guide#12', 12), false) + assert.equal(namesIssue('owner/repo#1360', 1360), true) +}) diff --git a/patches/ds-bot/files/src/shared/issue-ref.js b/patches/ds-bot/files/src/shared/issue-ref.js index 015f85595..7eb2dbf50 100644 --- a/patches/ds-bot/files/src/shared/issue-ref.js +++ b/patches/ds-bot/files/src/shared/issue-ref.js @@ -6,8 +6,10 @@ // front makes it part of an identifier, a version, a hex colour or a URL fragment (`C#7`, `1.2#3`, // `#3366ff`, `help/#123`), and a word character behind means the number keeps going (`#1360abc`). // `owner/repo#12` is its own shape, tried separately, so this separation cannot swallow it: there -// the `#` follows a repo name rather than a space. -const ISSUE_REF = /(?/#12` — `https://example.com/page#123` named issue #123 — and the dispatch wrote +// a card for an issue nobody meant (#1360). +import assert from 'node:assert/strict' +import { test } from 'node:test' +import { issueRefs, namesIssue } from '../src/shared/issue-ref.js' + +const fragments = [ + 'see https://example.com/page#123 for details', + 'https://example.com/docs/guide#12', + 'read https://docs.example.org/a/b#42 then', +] + +for (const text of fragments) { + test(`a URL fragment is not an issue reference: ${text}`, () => { + assert.deepEqual(issueRefs(text), [], text) + }) +} + +test('the shapes that name an issue keep working beside a URL', () => { + assert.deepEqual(issueRefs('owner/repo#1360'), ['#1360']) + assert.deepEqual(issueRefs('see owner/repo#1360 for the fix'), ['#1360']) + assert.deepEqual(issueRefs('please rebase #1360 onto main'), ['#1360']) + assert.deepEqual(issueRefs('https://github.com/o/r/issues/1360'), ['#1360']) + assert.deepEqual(issueRefs('@Dev-2 rebase owner/repo#1360'), ['#1360']) + assert.equal(namesIssue('https://example.com/docs/guide#12', 12), false) + assert.equal(namesIssue('owner/repo#1360', 1360), true) +})