fix: restore raw URL helper semantics

Why: Task 5 centralized URL formatting but accidentally changed shared Markdown segmentation and the legacy replaceURLs compatibility path.

What: remove angle-bracket autolink protection from shared segmentation, keep angle-bracket and trailing-punctuation handling inside formatURLsInText, and restore replaceURLs to a direct regex replace with linear:// support.
This commit is contained in:
Kodai Nakamura 2026-06-22 03:33:12 +09:00
parent 621b15a7a1
commit 3ddcf08538
6 changed files with 122 additions and 16 deletions

View file

@ -74,12 +74,12 @@ describe("mapMarkdownProse", () => {
expect(result).toBe("TS `TypeScript` [[TypeScript]]")
})
it("treats angle-bracket autolinks as protected segments", () => {
it("does not globally protect angle-bracket autolinks", () => {
const result = mapMarkdownProse(
"<https://example.com> https://example.com",
text => text.replace(/https:\/\/example\.com/g, "URL"),
)
expect(result).toBe("<https://example.com> URL")
expect(result).toBe("<URL> URL")
})
})

View file

@ -132,7 +132,6 @@ const collectFencedCodeRanges = (text: string): ProtectedRange[] => {
const buildProtectedPattern = (protectUrls: boolean): RegExp => {
const parts = [
"`[^`]*`",
"<(?:https?:\\/\\/|linear:\\/\\/)[^>]+>",
"\\[\\[[^\\]]+\\]\\]",
"\\[[^\\]]+\\]\\([^)]+\\)",
"\\[[^\\]]+\\]",
@ -197,7 +196,6 @@ export const segmentMarkdown = (
): MarkdownSegment[] => {
const mayContainProtectedMarkdown = text.includes("`")
|| text.includes("~")
|| text.includes("<")
|| text.includes("[")
|| (options.protectHeadings && text.includes("#"))
|| (options.protectCallouts && text.includes(">"))

View file

@ -14,8 +14,7 @@ describe("replace-urls", () => {
}
it("should replace GitHub URLs", () => {
const input
= "[[github/kdnk/obsidian-automatic-linker]] [🔗](https://github.com/kdnk/obsidian-automatic-linker)"
const input = "https://github.com/kdnk/obsidian-automatic-linker"
const expected
= "[[github/kdnk/obsidian-automatic-linker]] [🔗](https://github.com/kdnk/obsidian-automatic-linker)"
expect(replaceURLs(input, baseSettings, formatGitHubURL)).toBe(
@ -39,4 +38,24 @@ describe("replace-urls", () => {
),
).toBe(expected)
})
it("keeps raw helper semantics inside Markdown links and angle autolinks", () => {
const input = [
"[label](https://github.com/kdnk/obsidian-automatic-linker)",
"<linear://workspace/issue/ACME-123>",
].join(" ")
const expected = [
"[label](converted:https://github.com/kdnk/obsidian-automatic-linker)",
"<converted:linear://workspace/issue/ACME-123>",
].join(" ")
expect(
replaceURLs(
input,
baseSettings,
url => `converted:${url}`,
),
).toBe(expected)
})
})

View file

@ -60,6 +60,18 @@ describe("formatURLsInText", () => {
].join("\n"))
})
it("does not format GitHub URLs wrapped in angle brackets", () => {
const result = formatURLsInText({
text: "<https://github.com/owner/repo/issues/123>",
settings: {
...DEFAULT_SETTINGS,
formatGitHubURLs: true,
},
})
expect(result).toBe("<https://github.com/owner/repo/issues/123>")
})
it("leaves disabled adapter URLs unchanged", () => {
const result = formatURLsInText({
text: "https://linear.app/team/issue/BUG-789/title",
@ -72,6 +84,35 @@ describe("formatURLsInText", () => {
expect(result).toBe("https://linear.app/team/issue/BUG-789/title")
})
it("leaves Jira URLs with query strings unchanged", () => {
const result = formatURLsInText({
text: "https://jira.company.com/browse/ABC-456?focusedCommentId=12345",
settings: {
...DEFAULT_SETTINGS,
jiraURLs: ["jira.company.com"],
formatJiraURLs: true,
},
})
expect(result).toBe(
"https://jira.company.com/browse/ABC-456?focusedCommentId=12345",
)
})
it("keeps closing parens and trailing punctuation outside formatted URLs", () => {
const result = formatURLsInText({
text: "See (https://github.com/owner/repo/issues/123).",
settings: {
...DEFAULT_SETTINGS,
formatGitHubURLs: true,
},
})
expect(result).toBe(
"See ([[github/owner/repo/issues/123]] [🔗](https://github.com/owner/repo/issues/123)).",
)
})
it("does not format URLs inside protected Markdown segments", () => {
const result = formatURLsInText({
text: [

View file

@ -1,4 +1,3 @@
import { mapMarkdownProse } from "../markdown-segments"
import { AutomaticLinkerSettings } from "../settings/settings-info"
const URL_PATTERN = /(?:https?:\/\/|linear:\/\/)[^\s<>\]]+/g
@ -7,8 +6,4 @@ export const replaceURLs = (
fileContent: string,
settings: AutomaticLinkerSettings,
formatter: (url: string, settings: AutomaticLinkerSettings) => string,
) =>
mapMarkdownProse(
fileContent,
prose => prose.replace(URL_PATTERN, match => formatter(match, settings)),
)
) => fileContent.replace(URL_PATTERN, match => formatter(match, settings))

View file

@ -26,6 +26,48 @@ const formatJiraURLIfEnabled: UrlFormatter = (url, settings) =>
const formatLinearURLIfEnabled: UrlFormatter = (url, settings) =>
settings.formatLinearURLs ? formatLinearURL(url, settings) : url
const TRAILING_PUNCTUATION = new Set([".", ",", ";", "!", "?", "]", "}"])
const countCharacter = (text: string, character: string): number => {
let count = 0
for (const currentCharacter of text) {
if (currentCharacter === character) {
count += 1
}
}
return count
}
const splitTrailingBoundary = (match: string): { url: string, suffix: string } => {
let url = match
let suffix = ""
while (url.length > 0) {
const lastCharacter = url[url.length - 1]
if (TRAILING_PUNCTUATION.has(lastCharacter)) {
suffix = lastCharacter + suffix
url = url.slice(0, -1)
continue
}
if (
lastCharacter === ")"
&& countCharacter(url, "(") < countCharacter(url, ")")
) {
suffix = lastCharacter + suffix
url = url.slice(0, -1)
continue
}
break
}
return { url, suffix }
}
export const DEFAULT_URL_FORMATTERS: readonly UrlFormatter[] = [
formatGitHubURLIfEnabled,
formatJiraURLIfEnabled,
@ -54,8 +96,19 @@ export const formatURLsInText = ({
}: FormatURLsInTextOptions): string =>
mapMarkdownProse(
text,
prose =>
prose.replace(URL_PATTERN, match =>
formatURLWithAdapters(match, settings, formatters),
),
(prose, segment) =>
prose.replace(URL_PATTERN, (match, offset) => {
const precedingCharacter = offset > 0
? segment.text[offset - 1]
: undefined
const followingCharacter = segment.text[offset + match.length]
if (precedingCharacter === "<" && followingCharacter === ">") {
return match
}
const { url, suffix } = splitTrailingBoundary(match)
return formatURLWithAdapters(url, settings, formatters) + suffix
}),
)