Skip to content

Commit 883cac3

Browse files
authored
feat: add CodeQL CLI transformer for Article API (#58771)
1 parent 1f9d789 commit 883cac3

File tree

9 files changed

+273
-0
lines changed

9 files changed

+273
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Strips HTML comments from markdown content.
3+
* Removes single-line HTML comments like <!-- markdownlint-disable GHD053 -->
4+
* while preserving other content.
5+
*
6+
* @param content - The markdown content to process
7+
* @returns The content with HTML comments removed
8+
*/
9+
export function stripHtmlComments(content: string): string {
10+
// Remove single-line HTML comments (<!-- ... -->)
11+
// This matches comments that are on their own line or inline
12+
return content.replace(/<!--.*?-->/g, '').trim()
13+
}
14+
15+
/**
16+
* Strips HTML comments and cleans up extra blank lines.
17+
* Useful for cleaning up rendered markdown content where HTML comments
18+
* were on separate lines and their removal creates gaps.
19+
*
20+
* @param content - The markdown content to process
21+
* @returns The content with HTML comments removed and blank lines normalized
22+
*/
23+
export function stripHtmlCommentsAndNormalizeWhitespace(content: string): string {
24+
// Remove HTML comments
25+
let cleaned = stripHtmlComments(content)
26+
27+
// Normalize multiple consecutive blank lines to at most 2 blank lines
28+
cleaned = cleaned.replace(/\n{3,}/g, '\n\n')
29+
30+
return cleaned.trim()
31+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { describe, expect, test, beforeAll } from 'vitest'
2+
import { get } from '@/tests/helpers/e2etest'
3+
4+
const makeURL = (pathname: string): string =>
5+
`/api/article/body?${new URLSearchParams({ pathname })}`
6+
7+
describe('codeql cli article body api', () => {
8+
beforeAll(() => {
9+
if (!process.env.ROOT) {
10+
console.warn(
11+
'WARNING: The CodeQL CLI transformer tests require the ROOT environment variable to be set to the fixture root',
12+
)
13+
}
14+
})
15+
16+
test('database-analyze page', async () => {
17+
const res = await get(
18+
makeURL('/en/code-security/codeql-cli/codeql-cli-manual/database-analyze'),
19+
)
20+
21+
expect(res.statusCode).toBe(200)
22+
expect(res.headers['content-type']).toContain('text/markdown')
23+
24+
// Check for title injection
25+
expect(res.body).toContain('# database analyze')
26+
27+
// Check for intro injection
28+
expect(res.body).toContain(
29+
'Analyze a database, producing meaningful results in the context of the source code.',
30+
)
31+
32+
// Check for content
33+
expect(res.body).toContain('## Synopsis')
34+
expect(res.body).toContain('## Description')
35+
expect(res.body).toContain('## Options')
36+
37+
// Verify HTML comments are stripped
38+
expect(res.body).not.toContain('<!-- markdownlint-disable GHD053 -->')
39+
expect(res.body).not.toContain('<!-- markdownlint-disable GHD030 -->')
40+
expect(res.body).not.toContain('<!-- Content after this section is automatically generated -->')
41+
})
42+
43+
test('canTransform returns false for non-codeql-cli pages', async () => {
44+
const res = await get(makeURL('/en/get-started/start-your-journey/hello-world'))
45+
// This should not be transformed by CodeQL CLI transformer
46+
expect(res.statusCode).toBe(200)
47+
})
48+
})
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { describe, expect, test } from 'vitest'
2+
import {
3+
stripHtmlComments,
4+
stripHtmlCommentsAndNormalizeWhitespace,
5+
} from '@/article-api/lib/strip-html-comments'
6+
7+
describe('stripHtmlComments', () => {
8+
test('removes single-line HTML comments', () => {
9+
const input = '<!-- markdownlint-disable GHD053 -->'
10+
const expected = ''
11+
expect(stripHtmlComments(input)).toBe(expected)
12+
})
13+
14+
test('removes multiple HTML comments', () => {
15+
const input = '<!-- comment 1 -->\nSome content\n<!-- comment 2 -->'
16+
const expected = 'Some content'
17+
expect(stripHtmlComments(input)).toBe(expected)
18+
})
19+
20+
test('removes inline HTML comments', () => {
21+
const input = 'Text before <!-- comment --> text after'
22+
const expected = 'Text before text after'
23+
expect(stripHtmlComments(input)).toBe(expected)
24+
})
25+
26+
test('preserves content without HTML comments', () => {
27+
const input = 'Just regular markdown content'
28+
const expected = 'Just regular markdown content'
29+
expect(stripHtmlComments(input)).toBe(expected)
30+
})
31+
32+
test('handles multiple comments on same line', () => {
33+
const input = '<!-- comment 1 --> text <!-- comment 2 -->'
34+
const expected = 'text'
35+
expect(stripHtmlComments(input)).toBe(expected)
36+
})
37+
})
38+
39+
describe('stripHtmlCommentsAndNormalizeWhitespace', () => {
40+
test('removes HTML comments and normalizes blank lines', () => {
41+
const input = `<!-- markdownlint-disable GHD053 -->
42+
43+
<!-- markdownlint-disable GHD030 -->
44+
45+
Content here`
46+
const expected = 'Content here'
47+
expect(stripHtmlCommentsAndNormalizeWhitespace(input)).toBe(expected)
48+
})
49+
50+
test('normalizes multiple blank lines to at most two', () => {
51+
const input = `Line 1
52+
53+
54+
55+
Line 2`
56+
const expected = `Line 1
57+
58+
Line 2`
59+
expect(stripHtmlCommentsAndNormalizeWhitespace(input)).toBe(expected)
60+
})
61+
62+
test('handles real CodeQL CLI content pattern', () => {
63+
const input = `# database analyze
64+
65+
Analyze a database, producing meaningful results
66+
67+
<!-- markdownlint-disable GHD053 -->
68+
69+
<!-- markdownlint-disable GHD030 -->
70+
71+
<!-- Content after this section is automatically generated -->
72+
73+
## Synopsis
74+
75+
\`\`\`shell
76+
codeql database analyze
77+
\`\`\``
78+
79+
const expected = `# database analyze
80+
81+
Analyze a database, producing meaningful results
82+
83+
## Synopsis
84+
85+
\`\`\`shell
86+
codeql database analyze
87+
\`\`\``
88+
89+
expect(stripHtmlCommentsAndNormalizeWhitespace(input)).toBe(expected)
90+
})
91+
92+
test('preserves intentional double line breaks', () => {
93+
const input = `# Title
94+
95+
Paragraph 1
96+
97+
Paragraph 2`
98+
const expected = `# Title
99+
100+
Paragraph 1
101+
102+
Paragraph 2`
103+
expect(stripHtmlCommentsAndNormalizeWhitespace(input)).toBe(expected)
104+
})
105+
})
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { Context, Page } from '@/types'
2+
import type { PageTransformer } from './types'
3+
import { stripHtmlCommentsAndNormalizeWhitespace } from '@/article-api/lib/strip-html-comments'
4+
5+
/**
6+
* Transformer for CodeQL CLI reference pages.
7+
* Renders autogenerated CodeQL CLI documentation pages as markdown.
8+
* Sets `markdownRequested` to true in the context to ensure the page is rendered as markdown,
9+
* bypassing the default article type check.
10+
*/
11+
export class CodeQLCliTransformer implements PageTransformer {
12+
canTransform(page: Page): boolean {
13+
return page.autogenerated === 'codeql-cli'
14+
}
15+
16+
async transform(page: Page, _pathname: string, context: Context): Promise<string> {
17+
// CodeQL CLI pages are fully generated markdown files in the repo.
18+
// We render them with markdownRequested=true to get the markdown output,
19+
// similar to how regular articles are rendered but through the transformer pattern.
20+
context.markdownRequested = true
21+
const content = await page.render(context)
22+
23+
const intro = page.intro ? await page.renderProp('intro', context, { textOnly: true }) : ''
24+
25+
const result = `# ${page.title}\n\n${intro}\n\n${content}`
26+
27+
// Strip HTML comments (e.g., markdownlint-disable comments) from the output
28+
return stripHtmlCommentsAndNormalizeWhitespace(result)
29+
}
30+
}

src/article-api/transformers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { TransformerRegistry } from './types'
22
import { RestTransformer } from './rest-transformer'
3+
import { CodeQLCliTransformer } from './codeql-cli-transformer'
34
import { AuditLogsTransformer } from './audit-logs-transformer'
45
import { GraphQLTransformer } from './graphql-transformer'
56

@@ -10,6 +11,7 @@ import { GraphQLTransformer } from './graphql-transformer'
1011
export const transformerRegistry = new TransformerRegistry()
1112

1213
transformerRegistry.register(new RestTransformer())
14+
transformerRegistry.register(new CodeQLCliTransformer())
1315
transformerRegistry.register(new AuditLogsTransformer())
1416
transformerRegistry.register(new GraphQLTransformer())
1517

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
title: database analyze
3+
intro: Analyze a database, producing meaningful results in the context of the source code.
4+
versions:
5+
fpt: '*'
6+
ghec: '*'
7+
ghes: '*'
8+
type: reference
9+
product: Code Security
10+
autogenerated: codeql-cli
11+
---
12+
13+
<!-- markdownlint-disable GHD053 -->
14+
15+
<!-- markdownlint-disable GHD030 -->
16+
17+
<!-- Content after this section is automatically generated -->
18+
19+
## Synopsis
20+
21+
```shell copy
22+
codeql database analyze --format=<format> --output=<output> [--threads=<num>] [--ram=<MB>] <options>... -- <database> <query|dir|suite|pack>...
23+
```
24+
25+
## Description
26+
27+
Analyze a database, producing meaningful results in the context of the
28+
source code.
29+
30+
## Options
31+
32+
### Primary Options
33+
34+
#### `<database>`
35+
36+
[Mandatory] Path to the CodeQL database to query.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
title: CodeQL CLI Manual
3+
intro: Manual for CodeQL CLI
4+
versions:
5+
fpt: '*'
6+
ghes: '*'
7+
ghec: '*'
8+
children:
9+
- /database-analyze
10+
---
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
title: CodeQL CLI
3+
intro: CodeQL CLI documentation
4+
versions:
5+
fpt: '*'
6+
ghes: '*'
7+
ghec: '*'
8+
children:
9+
- /codeql-cli-manual
10+
---

src/fixtures/fixtures/content/code-security/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ versions:
1414
children:
1515
- /getting-started
1616
- /guides
17+
- /codeql-cli
1718
---

0 commit comments

Comments
 (0)