Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/markdown-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,18 @@ export const findContentAfterList = (tokens: Token[], returnAllOnNoList = false)
if (!returnAllOnNoList) return [];
start = tokens.findIndex((t) => t.type === 'heading_close');
}
// `end` is relative to `start`, so offset it back into `tokens` coordinates
const end = tokens.slice(start).findIndex((t) => t.type === 'heading_open');
if (end === -1) return tokens.slice(start + 1);
return tokens.slice(start + 1, end);
return tokens.slice(start + 1, start + end);
};

export const findContentAfterHeadingClose = (tokens: Token[]) => {
const start = tokens.findIndex((t) => t.type === 'heading_close');
// `end` is relative to `start`, so offset it back into `tokens` coordinates
const end = tokens.slice(start).findIndex((t) => t.type === 'heading_open');
if (end === -1) return tokens.slice(start + 1);
return tokens.slice(start + 1, end);
return tokens.slice(start + 1, start + end);
Comment thread
dsanders11 marked this conversation as resolved.
Resolved
};

export type HeadingContent = {
Expand Down
65 changes: 65 additions & 0 deletions tests/DocsParser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,43 @@ A \`string\` property that indicates the current application's name.
expect(appModule.properties[0].name).toBe('name');
});

it('should include a code fence at the end of a module description', async () => {
const moduleContent = `# app

> Control your application's event lifecycle.

Process: [Main](../glossary.md#main-process)

The following example shows how to quit the application when the last window
is closed:

\`\`\`js
const { app } = require('electron')
app.on('window-all-closed', () => {
app.quit()
})
\`\`\`

## Methods

### \`app.quit()\`

Try to close all windows.
`;

const modulePath = path.join(tempDir, 'docs', 'api', 'app.md');
await fs.promises.writeFile(modulePath, moduleContent);

const parser = new DocsParser(tempDir, '1.0.0', [modulePath], [], 'single');
const result = await parser.parse();

const appModule = result.find((m) => m.name === 'app');
assert(appModule?.type === 'Module', 'Parsed module should be of type Module');
expect(appModule.description).toContain('is closed:');
expect(appModule.description).toContain("const { app } = require('electron')");
expect(appModule.description).not.toContain('Try to close all windows');
});

it('should parse a class documentation', async () => {
const classContent = `# BrowserWindow

Expand Down Expand Up @@ -354,6 +391,34 @@ Additional description after the property list.
expect(rectangleStructure.properties[3].name).toBe('height');
});

it('should keep the description when a heading follows the property list', async () => {
const structureContent = `# PrinterInfo Object

* \`name\` string - The name of the printer as understood by the OS.
* \`status\` number - The current status of the printer.

The number represented by \`status\` means different things on different platforms.

## Example

Below is an example of some of the additional options that may be set.
`;

const structurePath = path.join(tempDir, 'docs', 'api', 'structures', 'printer-info.md');
await fs.promises.writeFile(structurePath, structureContent);

const parser = new DocsParser(tempDir, '1.0.0', [], [structurePath], 'single');
const result = await parser.parse();

const printerInfo = result.find((s) => s.name === 'PrinterInfo');
assert(printerInfo?.type === 'Structure', 'Parsed structure should be of type Structure');

expect(printerInfo.properties).toHaveLength(2);
expect(printerInfo.description).toBe(
'The number represented by `status` means different things on different platforms.',
);
});

it('should parse a structure with optional properties', async () => {
const structureContent = `# Options Object

Expand Down
56 changes: 54 additions & 2 deletions tests/markdown-helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,36 @@ After nested list.
const joined = safelyJoinTokens(content);
expect(joined).toContain('After nested list');
});

it('should return all content between the list and the next heading', () => {
const md = `# Heading

* Item 1
* Item 2

First paragraph after the list.

Second paragraph after the list.

\`\`\`js
const code = 'fence'
\`\`\`

## Next Heading

Content that should not be included.
`;
const tokens = getTokens(md);
const content = findContentAfterList(tokens);
expect(content[0].type).toBe('paragraph_open');
expect(content[content.length - 1].type).toBe('fence');
const joined = safelyJoinTokens(content, { parseCodeFences: true });
expect(joined).toContain('First paragraph after the list.');
expect(joined).toContain('Second paragraph after the list.');
expect(joined).toContain("const code = 'fence'");
expect(joined).not.toContain('Next Heading');
expect(joined).not.toContain('should not be included');
});
});

describe('findContentAfterHeadingClose', () => {
Expand All @@ -885,8 +915,30 @@ Content for heading one.
Content for heading two.`;
const tokens = getTokens(md);
const content = findContentAfterHeadingClose(tokens);
// The function returns tokens, check that we got some
expect(content.length).toBeGreaterThan(0);
expect(content[0].type).toBe('paragraph_open');
expect(content[content.length - 1].type).toBe('paragraph_close');
const joined = safelyJoinTokens(content);
expect(joined).toBe('Content for heading one.');
});

it('should not drop a trailing code fence before the next heading', () => {
const md = `# Heading One

Content for heading one.

\`\`\`js
const code = 'fence'
\`\`\`

## Heading Two

Content for heading two.`;
const tokens = getTokens(md);
const content = findContentAfterHeadingClose(tokens);
expect(content[content.length - 1].type).toBe('fence');
const joined = safelyJoinTokens(content, { parseCodeFences: true });
expect(joined).toContain("const code = 'fence'");
expect(joined).not.toContain('Content for heading two.');
});
});

Expand Down
Loading