-
Notifications
You must be signed in to change notification settings - Fork 56
fix(plugin-docs-cli): mask inline code before HTML checks #2840
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import { describe, it, expect } from 'vitest'; | ||
| import { join } from 'node:path'; | ||
| import { mkdtemp, writeFile } from 'node:fs/promises'; | ||
| import { tmpdir } from 'node:os'; | ||
| import { checkStubContent } from './stub-content.js'; | ||
| import { Rule } from '../types.js'; | ||
|
|
||
| const input = (docsPath: string, strict = true) => ({ docsPath, strict }); | ||
|
|
||
| // helper: valid frontmatter markdown file content | ||
| const md = (body = '') => `---\ntitle: Page\ndescription: A page\n---\n${body}`; | ||
|
|
||
| describe('checkStubContent', () => { | ||
| it('should return empty for nonexistent path', async () => { | ||
| const findings = await checkStubContent(input('/nonexistent/path')); | ||
| expect(findings).toHaveLength(0); | ||
| }); | ||
|
|
||
| it('should report a remaining section-brief marker', async () => { | ||
| const tmp = await mkdtemp(join(tmpdir(), 'stub-test-')); | ||
| await writeFile( | ||
| join(tmp, 'index.md'), | ||
| md('## Features\n\n<!-- section-brief:start -->\n\nFill this in.\n\n<!-- section-brief:end -->\n') | ||
| ); | ||
|
|
||
| const findings = await checkStubContent(input(tmp)); | ||
| const finding = findings.find((f) => f.rule === Rule.UnfilledSectionBrief); | ||
| expect(finding).toBeDefined(); | ||
| expect(finding!.severity).toBe('error'); | ||
| }); | ||
|
|
||
| it('should report as warning in non-strict mode', async () => { | ||
| const tmp = await mkdtemp(join(tmpdir(), 'stub-test-')); | ||
| await writeFile(join(tmp, 'index.md'), md('<!-- section-brief:start -->\n')); | ||
|
|
||
| const findings = await checkStubContent(input(tmp, false)); | ||
| const finding = findings.find((f) => f.rule === Rule.UnfilledSectionBrief); | ||
| expect(finding).toBeDefined(); | ||
| expect(finding!.severity).toBe('warning'); | ||
| }); | ||
|
|
||
| it('should include the line number of the marker', async () => { | ||
| const tmp = await mkdtemp(join(tmpdir(), 'stub-test-')); | ||
| await writeFile(join(tmp, 'index.md'), md('\n\n<!-- section-brief:start -->\n')); | ||
|
|
||
| const findings = await checkStubContent(input(tmp)); | ||
| const finding = findings.find((f) => f.rule === Rule.UnfilledSectionBrief); | ||
| expect(finding).toBeDefined(); | ||
| expect(finding!.line).toBeGreaterThan(1); | ||
| }); | ||
|
|
||
| it('should report every remaining marker in a file with multiple sections', async () => { | ||
| const tmp = await mkdtemp(join(tmpdir(), 'stub-test-')); | ||
| await writeFile( | ||
| join(tmp, 'index.md'), | ||
| md( | ||
| '<!-- section-brief:start -->\nFill this in.\n<!-- section-brief:end -->\n\n<!-- section-brief:start -->\nAnd this.\n<!-- section-brief:end -->\n' | ||
| ) | ||
| ); | ||
|
|
||
| const findings = await checkStubContent(input(tmp)); | ||
| expect(findings.filter((f) => f.rule === Rule.UnfilledSectionBrief)).toHaveLength(2); | ||
| }); | ||
|
|
||
| it('should not report a page with no section-brief markers', async () => { | ||
| const tmp = await mkdtemp(join(tmpdir(), 'stub-test-')); | ||
| await writeFile(join(tmp, 'index.md'), md('## Features\n\nThis panel does real things.\n')); | ||
|
|
||
| const findings = await checkStubContent(input(tmp)); | ||
| expect(findings.find((f) => f.rule === Rule.UnfilledSectionBrief)).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('should not report meta files like README.md', async () => { | ||
| const tmp = await mkdtemp(join(tmpdir(), 'stub-test-')); | ||
| await writeFile(join(tmp, 'README.md'), '# Docs\n\n<!-- section-brief:start -->\n'); | ||
|
|
||
| const findings = await checkStubContent(input(tmp)); | ||
| expect(findings.find((f) => f.rule === Rule.UnfilledSectionBrief)).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('should check all markdown files', async () => { | ||
| const tmp = await mkdtemp(join(tmpdir(), 'stub-test-')); | ||
| await writeFile(join(tmp, 'index.md'), md('<!-- section-brief:start -->\n')); | ||
| await writeFile(join(tmp, 'options.md'), md('<!-- section-brief:start -->\n')); | ||
|
|
||
| const findings = await checkStubContent(input(tmp)); | ||
| const files = findings.filter((f) => f.rule === Rule.UnfilledSectionBrief).map((f) => f.file); | ||
| expect(files).toContain('index.md'); | ||
| expect(files).toContain('options.md'); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import { readFile, readdir } from 'node:fs/promises'; | ||
| import type { Dirent } from 'node:fs'; | ||
| import { join, relative } from 'node:path'; | ||
| import { type Diagnostic, type ValidationInput, Rule } from '../types.js'; | ||
| import { isMetaFile } from './utils.js'; | ||
|
|
||
| // matches the opening marker of a section-brief authoring-guidance block, | ||
| // scaffolded by `create-plugin add panel-docs`/`datasource-docs` as a | ||
| // placeholder for the author to replace with real content. | ||
| const SECTION_BRIEF_START_RE = /<!--\s*section-brief:start\s*-->/; | ||
|
|
||
| /** | ||
| * Checks that no page still contains an unfilled `section-brief` block. A | ||
| * remaining marker means the author never replaced the scaffolded guidance | ||
| * with real documentation, so the page shouldn't ship as-is. | ||
| */ | ||
| export async function checkStubContent(input: ValidationInput): Promise<Diagnostic[]> { | ||
| const diagnostics: Diagnostic[] = []; | ||
|
|
||
| let entries: Dirent[] = []; | ||
| try { | ||
| entries = await readdir(input.docsPath, { recursive: true, withFileTypes: true }); | ||
| } catch { | ||
| return diagnostics; | ||
| } | ||
|
|
||
| const mdFiles = entries.filter( | ||
| (e) => | ||
| e.isFile() && | ||
| e.name.endsWith('.md') && | ||
| !isMetaFile(e.name) && | ||
| !e.parentPath.includes('node_modules') && | ||
| !e.parentPath.includes('dist') | ||
| ); | ||
|
|
||
| for (const file of mdFiles) { | ||
| const absolutePath = join(file.parentPath, file.name); | ||
| const relativePath = relative(input.docsPath, absolutePath); | ||
| let raw: string; | ||
| try { | ||
| raw = await readFile(absolutePath, 'utf-8'); | ||
| } catch { | ||
| continue; | ||
| } | ||
|
|
||
| const lines = raw.split('\n'); | ||
| for (let i = 0; i < lines.length; i++) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it any value in running these lines in the |
||
| if (!SECTION_BRIEF_START_RE.test(lines[i])) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we also need to verify that there is an end block? Or will this fail if it is missing? |
||
| continue; | ||
| } | ||
| diagnostics.push({ | ||
| rule: Rule.UnfilledSectionBrief, | ||
| severity: input.strict ? 'error' : 'warning', | ||
| file: relativePath, | ||
| line: i + 1, | ||
| title: 'Unfilled documentation stub', | ||
| detail: | ||
| "This section still has scaffolded authoring guidance (<!-- section-brief:start -->) instead of real content. Replace it with your plugin's actual documentation and remove the marker.", | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| return diagnostics; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.