diff --git a/.changeset/loud-moons-grow.md b/.changeset/loud-moons-grow.md new file mode 100644 index 00000000..ada9d2f9 --- /dev/null +++ b/.changeset/loud-moons-grow.md @@ -0,0 +1,5 @@ +--- +"codehike": patch +--- + +Better regex parsing in annotation range diff --git a/packages/codehike/src/code/extract-annotations.test.ts b/packages/codehike/src/code/extract-annotations.test.ts new file mode 100644 index 00000000..77a8bd1a --- /dev/null +++ b/packages/codehike/src/code/extract-annotations.test.ts @@ -0,0 +1,60 @@ +import { expect, test } from "vitest" +import { splitAnnotationsAndCode } from "./extract-annotations.js" + +async function t(comment: string) { + const code = `// ${comment} \nvar xyz = "https://example.com"` + const { annotations } = await splitAnnotationsAndCode(code, "javascript", "!") + return annotations[0] +} + +test("extracts basic annotation name", async () => { + const annotation = await t("!foo bar") + expect(annotation.name).toEqual("foo") +}) + +test("extracts name with parentheses range", async () => { + const annotation = await t("!foo(1) bar") + expect(annotation.name).toEqual("foo") +}) + +test("extracts name with brackets range", async () => { + const annotation = await t("!foo[1] bar") + expect(annotation.name).toEqual("foo") +}) + +test("extracts name with simple regex", async () => { + const annotation = await t("!foo[/x/] bar") + expect(annotation.name).toEqual("foo") +}) + +test("extracts name with regex flags", async () => { + const annotation = await t("!foo[/x/gmi] bar") + expect(annotation.name).toEqual("foo") +}) + +test("extracts name with regex containing brackets", async () => { + const annotation = await t(`!foo[/xyz[a-z]*/g] bar`) + expect(annotation.name).toEqual("foo") +}) + +test("extracts name with regex containing parentheses", async () => { + const annotation = await t(`!foo(/(xyz[w]*)/g) bar`) + expect(annotation.name).toEqual("foo") +}) + +test("extracts name with regex containing nested parentheses", async () => { + const annotation = await t(`!foo(/((xyz)[w]*)/g) bar`) + expect(annotation.name).toEqual("foo") +}) + +test("extracts name with regex containing escaped slashes", async () => { + const annotation = await t(`!foo[/https?:\\/\\//g] bar`) + expect(annotation.name).toEqual("foo") +}) + +test("extracts name with complex regex pattern", async () => { + const code = `// !tooltip[/#\\[program\\]/] example \n#[program]` + const { annotations } = await splitAnnotationsAndCode(code, "rust", "!") + const annotation = annotations[0] + expect(annotation.name).toEqual("tooltip") +}) diff --git a/packages/codehike/src/code/extract-annotations.tsx b/packages/codehike/src/code/extract-annotations.tsx index 8bfbfb85..d5ff7929 100644 --- a/packages/codehike/src/code/extract-annotations.tsx +++ b/packages/codehike/src/code/extract-annotations.tsx @@ -26,12 +26,21 @@ async function extractCommentAnnotations( annotationPrefix = "!", ) { const extractor = (comment: string) => { - // const regex = /\s*(!?[\w-]+)?(\([^\)]*\)|\[[^\]]*\])?(.*)$/ + const body = "(?:\\\\.|[^\\\\/])+" + const nestedBracketRegex = new RegExp( + `\\s*(${annotationPrefix}?[\\w-]+)?(\\[\\/${body}\\/[a-zA-Z]*\\])(.*)$`, + ) + const nestedParenRegex = new RegExp( + `\\s*(${annotationPrefix}?[\\w-]+)?(\\(\\/${body}\\/[a-zA-Z]*\\))(.*)$`, + ) const regex = new RegExp( `\\s*(${annotationPrefix}?[\\w-]+)?(\\([^\\)]*\\)|\\[[^\\]]*\\])?(.*)$`, ) - const match = comment.match(regex) + const match = + comment.match(nestedBracketRegex) || + comment.match(nestedParenRegex) || + comment.match(regex) if (!match) { return null }