From 57ef9a94074b12811e11be5207505897db22a34a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:01:34 +0000 Subject: [PATCH 1/2] Initial plan From d22864f4b25b11181daf732f4b70dfcd8330860e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:12:32 +0000 Subject: [PATCH 2/2] Fix issue #5: Remove other marks when code mark is present Co-authored-by: NoahCardoza <10343470+NoahCardoza@users.noreply.github.com> --- src/parser/markdown-to-adf/ASTBuilder.ts | 35 +++- tests/fixtures/adf/comprehensive-marks.adf | 15 -- .../unit/parser/code-mark-exclusivity.test.ts | 170 ++++++++++++++++++ 3 files changed, 202 insertions(+), 18 deletions(-) create mode 100644 tests/unit/parser/code-mark-exclusivity.test.ts diff --git a/src/parser/markdown-to-adf/ASTBuilder.ts b/src/parser/markdown-to-adf/ASTBuilder.ts index d9e4f9f..08abea2 100644 --- a/src/parser/markdown-to-adf/ASTBuilder.ts +++ b/src/parser/markdown-to-adf/ASTBuilder.ts @@ -570,7 +570,18 @@ export class ASTBuilder { // Apply the mark to all child text nodes return childNodes.map(node => { if (node.type === 'text') { - const marks = node.marks ? [...node.marks] : []; + let marks = node.marks ? [...node.marks] : []; + + // Issue #5: If code mark is being added, remove all other marks + // as it's invalid in ADF to combine code with other marks + if (markType === 'code') { + marks = []; + } + // If code mark already exists, don't add any other marks + else if (marks.some(m => m.type === 'code')) { + return { ...node, marks }; + } + marks.push({ type: markType }); return { ...node, @@ -625,7 +636,14 @@ export class ASTBuilder { // Apply the link mark to all child text nodes return childNodes.map(node => { if (node.type === 'text') { - const marks = node.marks ? [...node.marks] : []; + let marks = node.marks ? [...node.marks] : []; + + // Issue #5: Don't add link mark if code mark exists + // as it's invalid in ADF to combine code with other marks + if (marks.some(m => m.type === 'code')) { + return { ...node, marks }; + } + marks.push({ type: 'link', attrs: linkAttrs }); return { ...node, @@ -1966,7 +1984,18 @@ export class ASTBuilder { return childNodes.map(node => { if (markableNodeTypes.includes(node.type)) { - const marks = node.marks ? [...node.marks] : []; + let marks = node.marks ? [...node.marks] : []; + + // Issue #5: If code mark is being added, remove all other marks + // as it's invalid in ADF to combine code with other marks + if (markType === 'code') { + marks = []; + } + // If code mark already exists, don't add any other marks + else if (marks.some(m => m.type === 'code')) { + return { ...node, marks }; + } + marks.push(mark); return { ...node, diff --git a/tests/fixtures/adf/comprehensive-marks.adf b/tests/fixtures/adf/comprehensive-marks.adf index f2f1f13..444a5d0 100644 --- a/tests/fixtures/adf/comprehensive-marks.adf +++ b/tests/fixtures/adf/comprehensive-marks.adf @@ -166,21 +166,6 @@ "type": "text", "text": "bold italic underlined colored code", "marks": [ - { - "type": "strong" - }, - { - "type": "em" - }, - { - "type": "underline" - }, - { - "type": "textColor", - "attrs": { - "color": "#0066CC" - } - }, { "type": "code" } diff --git a/tests/unit/parser/code-mark-exclusivity.test.ts b/tests/unit/parser/code-mark-exclusivity.test.ts new file mode 100644 index 0000000..4e99466 --- /dev/null +++ b/tests/unit/parser/code-mark-exclusivity.test.ts @@ -0,0 +1,170 @@ +/** + * @file Tests for Issue #5: Code mark exclusivity + * @description Validates that code mark removes all other marks and that other marks cannot be added to code + */ + +import { describe, it, expect } from '@jest/globals'; +import { Parser } from '../../../src/parser/Parser'; + +describe('Code Mark Exclusivity (Issue #5)', () => { + const parser = new Parser(); + + describe('Bold + Code combinations', () => { + it('should only have code mark when bold wraps code', () => { + const markdown = '**`code`**'; + const result = parser.markdownToAdf(markdown); + + const textNode = result.content[0].content[0]; + expect(textNode.type).toBe('text'); + expect(textNode.text).toBe('code'); + expect(textNode.marks).toHaveLength(1); + expect(textNode.marks[0].type).toBe('code'); + }); + + it('should not add bold mark when code mark exists', () => { + const markdown = '`**bold code**`'; + const result = parser.markdownToAdf(markdown); + + const textNode = result.content[0].content[0]; + expect(textNode.type).toBe('text'); + expect(textNode.marks).toHaveLength(1); + expect(textNode.marks[0].type).toBe('code'); + }); + }); + + describe('Italic + Code combinations', () => { + it('should only have code mark when italic wraps code', () => { + const markdown = '*`code`*'; + const result = parser.markdownToAdf(markdown); + + const textNode = result.content[0].content[0]; + expect(textNode.type).toBe('text'); + expect(textNode.text).toBe('code'); + expect(textNode.marks).toHaveLength(1); + expect(textNode.marks[0].type).toBe('code'); + }); + + it('should only have code mark when emphasis wraps code', () => { + const markdown = '_`code`_'; + const result = parser.markdownToAdf(markdown); + + const textNode = result.content[0].content[0]; + expect(textNode.type).toBe('text'); + expect(textNode.marks).toHaveLength(1); + expect(textNode.marks[0].type).toBe('code'); + }); + }); + + describe('Strikethrough + Code combinations', () => { + it('should only have code mark when strikethrough wraps code', () => { + const markdown = '~~`code`~~'; + const result = parser.markdownToAdf(markdown); + + const textNode = result.content[0].content[0]; + expect(textNode.type).toBe('text'); + expect(textNode.marks).toHaveLength(1); + expect(textNode.marks[0].type).toBe('code'); + }); + }); + + describe('Link + Code combinations', () => { + it('should only have code mark when link wraps code', () => { + const markdown = '[`code`](https://example.com)'; + const result = parser.markdownToAdf(markdown); + + const textNode = result.content[0].content[0]; + expect(textNode.type).toBe('text'); + expect(textNode.text).toBe('code'); + expect(textNode.marks).toHaveLength(1); + expect(textNode.marks[0].type).toBe('code'); + // Link mark should not be added + }); + }); + + describe('Multiple marks + Code combinations', () => { + it('should only have code mark when multiple formatting wraps code', () => { + const markdown = '***`code`***'; + const result = parser.markdownToAdf(markdown); + + const textNode = result.content[0].content[0]; + expect(textNode.type).toBe('text'); + expect(textNode.text).toBe('code'); + expect(textNode.marks).toHaveLength(1); + expect(textNode.marks[0].type).toBe('code'); + }); + + it('should only have code mark when bold+italic+strike wraps code', () => { + const markdown = '~~***`code`***~~'; + const result = parser.markdownToAdf(markdown); + + const textNode = result.content[0].content[0]; + expect(textNode.type).toBe('text'); + expect(textNode.marks).toHaveLength(1); + expect(textNode.marks[0].type).toBe('code'); + }); + }); + + describe('Code should stand alone', () => { + it('should preserve only code mark in simple inline code', () => { + const markdown = '`simple code`'; + const result = parser.markdownToAdf(markdown); + + const textNode = result.content[0].content[0]; + expect(textNode.type).toBe('text'); + expect(textNode.text).toBe('simple code'); + expect(textNode.marks).toHaveLength(1); + expect(textNode.marks[0].type).toBe('code'); + }); + + it('should handle multiple code spans in same paragraph', () => { + const markdown = '**bold** and `code` and *italic*'; + const result = parser.markdownToAdf(markdown); + + const content = result.content[0].content; + + // First should be bold + expect(content[0].marks).toHaveLength(1); + expect(content[0].marks[0].type).toBe('strong'); + + // Third should be code (only) + expect(content[2].marks).toHaveLength(1); + expect(content[2].marks[0].type).toBe('code'); + + // Fifth should be italic + expect(content[4].marks).toHaveLength(1); + expect(content[4].marks[0].type).toBe('em'); + }); + }); + + describe('Edge cases', () => { + it('should handle code at the start of bold text', () => { + const markdown = '**`code` and bold**'; + const result = parser.markdownToAdf(markdown); + + const content = result.content[0].content; + + // First node should be code only + expect(content[0].marks).toHaveLength(1); + expect(content[0].marks[0].type).toBe('code'); + + // Second node should be bold + expect(content[1].marks).toHaveLength(1); + expect(content[1].marks[0].type).toBe('strong'); + }); + + it('should handle code at the end of italic text', () => { + const markdown = '*italic and `code`*'; + const result = parser.markdownToAdf(markdown); + + const content = result.content[0].content; + + // First node should be italic + expect(content[0].marks).toHaveLength(1); + expect(content[0].marks[0].type).toBe('em'); + + // Second node should be code only + expect(content[1].marks).toHaveLength(1); + expect(content[1].marks[0].type).toBe('code'); + }); + }); +});