Skip to content

Commit 8528155

Browse files
authored
Fix circular links in CodeQL CLI manual pages (#56233)
1 parent 7594c76 commit 8528155

File tree

6 files changed

+228
-9
lines changed

6 files changed

+228
-9
lines changed

src/audit-logs/tests/unit/filter-events.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { describe, expect, test } from 'vitest'
33
import { filterByAllowlistValues, filterAndUpdateGhesDataByAllowlistValues } from '../../lib'
44
import type { RawAuditLogEventT, VersionedAuditLogData } from '../../types'
55

6-
describe('audit log event fitering', () => {
6+
describe('audit log event filtering', () => {
77
test('matches single allowlist value', async () => {
88
const eventsToProcess: RawAuditLogEventT[] = [
99
{

src/codeql-cli/scripts/convert-markdown-for-docs.js

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ const END_SECTION = '\n:::'
2020
const PROGRAM_SECTION = '::: {.program}\n'
2121

2222
// Updates several properties of the Markdown file using the AST
23-
export async function convertContentToDocs(content, frontmatterDefaults = {}) {
23+
export async function convertContentToDocs(
24+
content,
25+
frontmatterDefaults = {},
26+
currentFileName = '',
27+
) {
2428
const ast = fromMarkdown(content)
2529

2630
let depth = 0
@@ -160,11 +164,20 @@ export async function convertContentToDocs(content, frontmatterDefaults = {}) {
160164

161165
// Remove the string {.interpreted-text role="doc"} from this node
162166
node.value = node.value.replace(/\n/g, ' ').replace('{.interpreted-text role="doc"}', '')
163-
// Make the previous sibling node a link
164-
link.type = 'link'
165-
link.url = `${RELATIVE_LINK_PATH}/${linkPath}`
166-
link.children = [{ type: 'text', value: linkText }]
167-
delete link.value
167+
168+
// Check for circular links - if the link points to the same file we're processing
169+
const currentFileBaseName = currentFileName.replace('.md', '')
170+
if (currentFileBaseName && linkPath === currentFileBaseName) {
171+
// Convert circular link to plain text instead of creating a link
172+
link.type = 'text'
173+
link.value = linkText
174+
} else {
175+
// Make the previous sibling node a link
176+
link.type = 'link'
177+
link.url = `${RELATIVE_LINK_PATH}/${linkPath}`
178+
link.children = [{ type: 'text', value: linkText }]
179+
delete link.value
180+
}
168181
}
169182

170183
// Save any nodes that contain aka.ms links so we can convert them later

src/codeql-cli/scripts/sync.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ async function main() {
4343
matchHeading,
4444
matchHeading + '\n### Primary Options\n',
4545
)
46-
const { data, content } = await convertContentToDocs(primaryHeadingSourceContent)
46+
const currentFileName = path.basename(file)
47+
const { data, content } = await convertContentToDocs(
48+
primaryHeadingSourceContent,
49+
{},
50+
currentFileName,
51+
)
4752
await writeFile(file, matter.stringify(content, data))
4853
const targetFilename = path.join(targetDirectory, path.basename(file))
4954
const sourceData = { ...data, ...frontmatterDefaults }
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { convertContentToDocs } from '../scripts/convert-markdown-for-docs'
3+
4+
describe('convertContentToDocs circular link handling', () => {
5+
const testContent = `
6+
# bqrs interpret
7+
8+
[Plumbing] Interpret data in a single BQRS.
9+
10+
## Description
11+
12+
A command that interprets a single BQRS file according to the provided
13+
metadata and generates output in the specified format.
14+
15+
## Options
16+
17+
### Primary Options
18+
19+
This option has no effect when passed to \`codeql bqrs interpret<bqrs-interpret>\`{.interpreted-text role="doc"}.
20+
21+
For more information, see \`codeql database analyze<database-analyze>\`{.interpreted-text role="doc"}.
22+
`
23+
24+
test('converts circular links to plain text', async () => {
25+
const result = await convertContentToDocs(testContent, {}, 'bqrs-interpret.md')
26+
27+
// Should not contain circular link
28+
expect(result.content).not.toContain(
29+
'[codeql bqrs interpret](/code-security/codeql-cli/codeql-cli-manual/bqrs-interpret)',
30+
)
31+
32+
// Should contain plain text instead
33+
expect(result.content).toContain('codeql bqrs interpret')
34+
})
35+
36+
test('preserves non-circular links', async () => {
37+
const result = await convertContentToDocs(testContent, {}, 'bqrs-interpret.md')
38+
39+
// Should preserve valid cross-reference link
40+
expect(result.content).toContain(
41+
'[codeql database analyze](/code-security/codeql-cli/codeql-cli-manual/database-analyze)',
42+
)
43+
})
44+
45+
test('handles edge case: no filename provided', async () => {
46+
const result = await convertContentToDocs(testContent, {}, '')
47+
48+
// Should preserve link when no filename is provided
49+
expect(result.content).toContain(
50+
'[codeql bqrs interpret](/code-security/codeql-cli/codeql-cli-manual/bqrs-interpret)',
51+
)
52+
})
53+
54+
test('handles edge case: different filename', async () => {
55+
const result = await convertContentToDocs(testContent, {}, 'different-file.md')
56+
57+
// Should preserve link when filename is different
58+
expect(result.content).toContain(
59+
'[codeql bqrs interpret](/code-security/codeql-cli/codeql-cli-manual/bqrs-interpret)',
60+
)
61+
})
62+
63+
test('processes both circular and non-circular links correctly in same content', async () => {
64+
const result = await convertContentToDocs(testContent, {}, 'bqrs-interpret.md')
65+
66+
// Circular link should be plain text
67+
expect(result.content).not.toContain(
68+
'[codeql bqrs interpret](/code-security/codeql-cli/codeql-cli-manual/bqrs-interpret)',
69+
)
70+
71+
// Non-circular link should be preserved
72+
expect(result.content).toContain(
73+
'[codeql database analyze](/code-security/codeql-cli/codeql-cli-manual/database-analyze)',
74+
)
75+
76+
// Both should have their text content present
77+
expect(result.content).toContain('codeql bqrs interpret')
78+
expect(result.content).toContain('codeql database analyze')
79+
})
80+
81+
test('returns proper data structure', async () => {
82+
const result = await convertContentToDocs(testContent, {}, 'bqrs-interpret.md')
83+
84+
expect(result).toHaveProperty('content')
85+
expect(result).toHaveProperty('data')
86+
expect(typeof result.content).toBe('string')
87+
expect(typeof result.data).toBe('object')
88+
})
89+
})
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { convertContentToDocs } from '../scripts/convert-markdown-for-docs'
2+
3+
// Test content that simulates a circular link scenario
4+
const testContent = `
5+
# bqrs interpret
6+
7+
[Plumbing] Interpret data in a single BQRS.
8+
9+
## Description
10+
11+
A command that interprets a single BQRS file according to the provided
12+
metadata and generates output in the specified format.
13+
14+
## Options
15+
16+
### Primary Options
17+
18+
This option has no effect when passed to \`codeql bqrs interpret<bqrs-interpret>\`{.interpreted-text role="doc"}.
19+
20+
For more information, see \`codeql database analyze<database-analyze>\`{.interpreted-text role="doc"}.
21+
`
22+
23+
async function testCircularLinkFix() {
24+
console.log('Testing circular link fix...')
25+
26+
try {
27+
// Test with circular link (should convert to plain text)
28+
const result1 = await convertContentToDocs(testContent, {}, 'bqrs-interpret.md')
29+
console.log('✅ Conversion completed successfully')
30+
31+
// Check if circular link was converted to plain text
32+
const hasCircularLink = result1.content.includes(
33+
'[codeql bqrs interpret](/code-security/codeql-cli/codeql-cli-manual/bqrs-interpret)',
34+
)
35+
const hasPlainText = result1.content.includes('codeql bqrs interpret')
36+
37+
if (hasCircularLink) {
38+
console.log('❌ FAIL: Circular link still present in output')
39+
console.log('Content:', result1.content)
40+
return false
41+
} else if (hasPlainText) {
42+
console.log('✅ PASS: Circular link converted to plain text')
43+
} else {
44+
console.log('⚠️ WARNING: Could not find expected text in output')
45+
}
46+
47+
// Check if non-circular link is preserved
48+
const hasValidLink = result1.content.includes(
49+
'[codeql database analyze](/code-security/codeql-cli/codeql-cli-manual/database-analyze)',
50+
)
51+
52+
if (hasValidLink) {
53+
console.log('✅ PASS: Non-circular link preserved correctly')
54+
} else {
55+
console.log('❌ FAIL: Valid cross-reference link was incorrectly removed')
56+
}
57+
58+
console.log('\n--- Generated content preview ---')
59+
console.log(result1.content.substring(0, 800) + '...')
60+
61+
return !hasCircularLink && hasValidLink
62+
} catch (error) {
63+
console.error('❌ Test failed with error:', error)
64+
return false
65+
}
66+
}
67+
68+
async function testEdgeCases() {
69+
console.log('\nTesting edge cases...')
70+
71+
// Test with no filename (should not crash)
72+
const result1 = await convertContentToDocs(testContent, {}, '')
73+
const hasLink1 = result1.content.includes(
74+
'[codeql bqrs interpret](/code-security/codeql-cli/codeql-cli-manual/bqrs-interpret)',
75+
)
76+
if (hasLink1) {
77+
console.log('✅ PASS: No filename provided - link preserved as expected')
78+
} else {
79+
console.log('❌ FAIL: Link incorrectly removed when no filename provided')
80+
return false
81+
}
82+
83+
// Test with different filename (should preserve link)
84+
const result2 = await convertContentToDocs(testContent, {}, 'different-file.md')
85+
const hasLink2 = result2.content.includes(
86+
'[codeql bqrs interpret](/code-security/codeql-cli/codeql-cli-manual/bqrs-interpret)',
87+
)
88+
if (hasLink2) {
89+
console.log('✅ PASS: Different filename - link preserved correctly')
90+
} else {
91+
console.log('❌ FAIL: Link incorrectly removed for different filename')
92+
return false
93+
}
94+
95+
return true
96+
}
97+
98+
// Run all tests
99+
async function runAllTests() {
100+
const test1 = await testCircularLinkFix()
101+
const test2 = await testEdgeCases()
102+
103+
if (test1 && test2) {
104+
console.log('\n🎉 All tests passed!')
105+
process.exit(0)
106+
} else {
107+
console.log('\n💥 Tests failed!')
108+
process.exit(1)
109+
}
110+
}
111+
112+
runAllTests()

src/frame/lib/get-remote-json.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export default async function getRemoteJSON(url, config) {
5959
}
6060
}
6161
} catch (error) {
62-
if (!(error instanceof SyntaxError || error.code === 'ENOENT')) {
62+
if (!(error instanceof SyntaxError || (error instanceof Error && error.code === 'ENOENT'))) {
6363
throw error
6464
}
6565
}

0 commit comments

Comments
 (0)