@bernierllc/markdown-parser 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +27 -0
- package/README.md +325 -0
- package/__tests__/MarkdownParser.test.ts +787 -0
- package/__tests__/ast-utils.test.ts +319 -0
- package/__tests__/cache.test.ts +243 -0
- package/__tests__/plugins.test.ts +282 -0
- package/coverage/clover.xml +283 -0
- package/coverage/coverage-final.json +7 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +146 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/src/MarkdownParser.ts.html +1636 -0
- package/coverage/lcov-report/src/index.html +116 -0
- package/coverage/lcov-report/src/plugins/commonmark.ts.html +154 -0
- package/coverage/lcov-report/src/plugins/gfm.ts.html +154 -0
- package/coverage/lcov-report/src/plugins/index.html +146 -0
- package/coverage/lcov-report/src/plugins/index.ts.html +115 -0
- package/coverage/lcov-report/src/utils/ast-utils.ts.html +328 -0
- package/coverage/lcov-report/src/utils/cache.ts.html +403 -0
- package/coverage/lcov-report/src/utils/index.html +131 -0
- package/coverage/lcov.info +457 -0
- package/dist/MarkdownParser.d.ts +46 -0
- package/dist/MarkdownParser.d.ts.map +1 -0
- package/dist/MarkdownParser.js +458 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/plugins/commonmark.d.ts +6 -0
- package/dist/plugins/commonmark.d.ts.map +1 -0
- package/dist/plugins/commonmark.js +23 -0
- package/dist/plugins/gfm.d.ts +6 -0
- package/dist/plugins/gfm.d.ts.map +1 -0
- package/dist/plugins/gfm.js +23 -0
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +25 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/utils/ast-utils.d.ts +22 -0
- package/dist/utils/ast-utils.d.ts.map +1 -0
- package/dist/utils/ast-utils.js +81 -0
- package/dist/utils/cache.d.ts +39 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +91 -0
- package/jest.config.cjs +28 -0
- package/package.json +44 -0
- package/src/MarkdownParser.ts +517 -0
- package/src/index.ts +13 -0
- package/src/plugins/commonmark.ts +23 -0
- package/src/plugins/gfm.ts +23 -0
- package/src/plugins/index.ts +10 -0
- package/src/types.ts +114 -0
- package/src/utils/ast-utils.ts +81 -0
- package/src/utils/cache.ts +106 -0
- package/tsconfig.json +30 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
parser: '@typescript-eslint/parser',
|
|
11
|
+
parserOptions: {
|
|
12
|
+
ecmaVersion: 2020,
|
|
13
|
+
sourceType: 'module',
|
|
14
|
+
},
|
|
15
|
+
extends: [
|
|
16
|
+
'eslint:recommended',
|
|
17
|
+
'plugin:@typescript-eslint/recommended',
|
|
18
|
+
],
|
|
19
|
+
rules: {
|
|
20
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
21
|
+
'@typescript-eslint/explicit-function-return-type': 'error',
|
|
22
|
+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
23
|
+
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
|
24
|
+
'no-regex-spaces': 'off',
|
|
25
|
+
'no-useless-escape': 'off',
|
|
26
|
+
},
|
|
27
|
+
};
|
package/README.md
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# @bernierllc/markdown-parser
|
|
2
|
+
|
|
3
|
+
High-performance markdown parser with CommonMark, GitHub-Flavored Markdown (GFM), and plugin support. Converts markdown text to Abstract Syntax Tree (AST) format for further processing.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @bernierllc/markdown-parser
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **CommonMark Support** - Full CommonMark v0.31.2 specification compliance
|
|
14
|
+
- **GitHub-Flavored Markdown** - Task lists, tables, strikethrough, autolinks
|
|
15
|
+
- **Plugin System** - Extend parser with custom transformations
|
|
16
|
+
- **Caching** - Optional in-memory caching with TTL and size limits
|
|
17
|
+
- **Performance** - Fast parsing with position tracking
|
|
18
|
+
- **TypeScript** - Strict typing with comprehensive type definitions
|
|
19
|
+
- **Zero Dependencies** - Only uses @bernierllc packages
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Basic Parsing
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { MarkdownParser } from '@bernierllc/markdown-parser';
|
|
27
|
+
|
|
28
|
+
const parser = new MarkdownParser();
|
|
29
|
+
|
|
30
|
+
const result = await parser.parse('# Hello World\n\nThis is **bold** text.');
|
|
31
|
+
|
|
32
|
+
if (result.success) {
|
|
33
|
+
console.log('Parsed AST:', result.ast);
|
|
34
|
+
console.log('Node count:', result.metadata.nodeCount);
|
|
35
|
+
console.log('Parse time:', result.metadata.parseTime, 'ms');
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### With Caching
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const parser = new MarkdownParser({
|
|
43
|
+
cache: {
|
|
44
|
+
enabled: true,
|
|
45
|
+
maxSize: 1000, // Maximum 1000 cached documents
|
|
46
|
+
ttl: 3600, // Cache for 1 hour
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// First parse - not cached
|
|
51
|
+
const result1 = await parser.parse('# Document');
|
|
52
|
+
console.log('Cached:', result1.metadata.cached); // false
|
|
53
|
+
|
|
54
|
+
// Second parse - cached
|
|
55
|
+
const result2 = await parser.parse('# Document');
|
|
56
|
+
console.log('Cached:', result2.metadata.cached); // true
|
|
57
|
+
console.log('Faster:', result2.metadata.parseTime < result1.metadata.parseTime); // true
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### With Custom Plugins
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { MarkdownParser, MarkdownPlugin, transformNodes } from '@bernierllc/markdown-parser';
|
|
64
|
+
|
|
65
|
+
// Create a plugin that converts all text to uppercase
|
|
66
|
+
const uppercasePlugin: MarkdownPlugin = {
|
|
67
|
+
name: 'uppercase',
|
|
68
|
+
version: '1.0.0',
|
|
69
|
+
transform: (ast) => {
|
|
70
|
+
return transformNodes(ast, (node) => {
|
|
71
|
+
if (node.type === 'text' && node.value) {
|
|
72
|
+
return { ...node, value: node.value.toUpperCase() };
|
|
73
|
+
}
|
|
74
|
+
return node;
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const parser = new MarkdownParser({
|
|
80
|
+
plugins: [uppercasePlugin],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const result = await parser.parse('# hello world');
|
|
84
|
+
// Result: heading with text "HELLO WORLD"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### GFM Features
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const parser = new MarkdownParser({ gfm: true });
|
|
91
|
+
|
|
92
|
+
// Task lists
|
|
93
|
+
const tasks = await parser.parse(`
|
|
94
|
+
- [ ] Unchecked task
|
|
95
|
+
- [x] Checked task
|
|
96
|
+
`);
|
|
97
|
+
|
|
98
|
+
// Tables
|
|
99
|
+
const table = await parser.parse(`
|
|
100
|
+
| Header 1 | Header 2 |
|
|
101
|
+
| -------- | -------- |
|
|
102
|
+
| Cell 1 | Cell 2 |
|
|
103
|
+
`);
|
|
104
|
+
|
|
105
|
+
// Strikethrough
|
|
106
|
+
const strikethrough = await parser.parse('This is ~~deleted~~ text');
|
|
107
|
+
|
|
108
|
+
// Autolinks
|
|
109
|
+
const autolink = await parser.parse('Visit https://example.com for more');
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### With Logger
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { Logger } from '@bernierllc/logger';
|
|
116
|
+
|
|
117
|
+
const logger = new Logger({ level: 'debug' });
|
|
118
|
+
|
|
119
|
+
const parser = new MarkdownParser({ logger });
|
|
120
|
+
|
|
121
|
+
await parser.parse('# Test');
|
|
122
|
+
// Logs: "Markdown parsed successfully" with metadata
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## API Reference
|
|
126
|
+
|
|
127
|
+
### `MarkdownParser`
|
|
128
|
+
|
|
129
|
+
Main parser class for converting markdown to AST.
|
|
130
|
+
|
|
131
|
+
#### Constructor
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
new MarkdownParser(config?: MarkdownParserConfig)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Config Options:**
|
|
138
|
+
- `gfm?: boolean` - Enable GitHub-Flavored Markdown (default: `true`)
|
|
139
|
+
- `commonmark?: boolean` - Enable CommonMark strict mode (default: `true`)
|
|
140
|
+
- `plugins?: MarkdownPlugin[]` - Custom plugins to apply (default: `[]`)
|
|
141
|
+
- `cache?: CacheConfig` - Caching configuration
|
|
142
|
+
- `enabled: boolean` - Enable caching
|
|
143
|
+
- `maxSize?: number` - Maximum cache entries (default: `1000`)
|
|
144
|
+
- `ttl?: number` - Time to live in seconds (default: `3600`)
|
|
145
|
+
- `logger?: Logger` - Logger instance for debug output
|
|
146
|
+
|
|
147
|
+
#### Methods
|
|
148
|
+
|
|
149
|
+
##### `parse(markdown: string): Promise<ParseResult>`
|
|
150
|
+
|
|
151
|
+
Parse markdown string to AST.
|
|
152
|
+
|
|
153
|
+
**Returns:**
|
|
154
|
+
```typescript
|
|
155
|
+
{
|
|
156
|
+
success: boolean;
|
|
157
|
+
ast?: ASTNode;
|
|
158
|
+
error?: string;
|
|
159
|
+
metadata?: {
|
|
160
|
+
parseTime: number; // Milliseconds
|
|
161
|
+
nodeCount: number; // Total nodes in AST
|
|
162
|
+
cached: boolean; // Whether result was cached
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
##### `clearCache(): void`
|
|
168
|
+
|
|
169
|
+
Clear all cached parse results.
|
|
170
|
+
|
|
171
|
+
##### `getCacheStats(): { size: number; enabled: boolean }`
|
|
172
|
+
|
|
173
|
+
Get current cache statistics.
|
|
174
|
+
|
|
175
|
+
##### `pruneCache(): number`
|
|
176
|
+
|
|
177
|
+
Remove expired cache entries. Returns number of entries removed.
|
|
178
|
+
|
|
179
|
+
### AST Node Types
|
|
180
|
+
|
|
181
|
+
The parser generates nodes with the following structure:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
interface ASTNode {
|
|
185
|
+
type: NodeType;
|
|
186
|
+
value?: string;
|
|
187
|
+
children?: ASTNode[];
|
|
188
|
+
attributes?: Record<string, unknown>;
|
|
189
|
+
position?: Position;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Supported Node Types:**
|
|
194
|
+
- Block: `root`, `heading`, `paragraph`, `blockquote`, `list`, `listItem`, `codeBlock`, `table`, `tableRow`, `tableCell`, `horizontalRule`
|
|
195
|
+
- Inline: `text`, `emphasis`, `strong`, `code`, `link`, `image`, `lineBreak`
|
|
196
|
+
- GFM: `strikethrough`, `taskList`, `taskListItem`, `autolink`
|
|
197
|
+
|
|
198
|
+
### Plugin Interface
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
interface MarkdownPlugin {
|
|
202
|
+
name: string;
|
|
203
|
+
version: string;
|
|
204
|
+
transform: (ast: ASTNode) => ASTNode;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Utility Functions
|
|
209
|
+
|
|
210
|
+
#### `countNodes(node: ASTNode): number`
|
|
211
|
+
|
|
212
|
+
Count total nodes in AST tree.
|
|
213
|
+
|
|
214
|
+
#### `visitNodes(node: ASTNode, visitor: (node: ASTNode) => void): void`
|
|
215
|
+
|
|
216
|
+
Visit each node in AST tree with callback.
|
|
217
|
+
|
|
218
|
+
#### `transformNodes(node: ASTNode, transformer: (node: ASTNode) => ASTNode): ASTNode`
|
|
219
|
+
|
|
220
|
+
Transform AST nodes recursively.
|
|
221
|
+
|
|
222
|
+
#### `findNodes(node: ASTNode, predicate: (node: ASTNode) => boolean): ASTNode[]`
|
|
223
|
+
|
|
224
|
+
Find all nodes matching predicate.
|
|
225
|
+
|
|
226
|
+
#### `cloneNode(node: ASTNode): ASTNode`
|
|
227
|
+
|
|
228
|
+
Create deep copy of AST node.
|
|
229
|
+
|
|
230
|
+
## Examples
|
|
231
|
+
|
|
232
|
+
### Example 1: Parsing Headers
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const parser = new MarkdownParser();
|
|
236
|
+
const result = await parser.parse(`
|
|
237
|
+
# Heading 1
|
|
238
|
+
## Heading 2
|
|
239
|
+
### Heading 3
|
|
240
|
+
`);
|
|
241
|
+
|
|
242
|
+
// Find all headings
|
|
243
|
+
const headings = findNodes(result.ast!, (node) => node.type === 'heading');
|
|
244
|
+
console.log('Found', headings.length, 'headings');
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Example 2: Extract All Links
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const parser = new MarkdownParser();
|
|
251
|
+
const result = await parser.parse('Check [Google](https://google.com) and [GitHub](https://github.com)');
|
|
252
|
+
|
|
253
|
+
const links = findNodes(result.ast!, (node) => node.type === 'link');
|
|
254
|
+
links.forEach((link) => {
|
|
255
|
+
console.log('Link:', link.attributes.href);
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Example 3: Count Code Blocks
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
const parser = new MarkdownParser();
|
|
263
|
+
const result = await parser.parse(`
|
|
264
|
+
\`\`\`javascript
|
|
265
|
+
const x = 1;
|
|
266
|
+
\`\`\`
|
|
267
|
+
|
|
268
|
+
\`\`\`python
|
|
269
|
+
print("hello")
|
|
270
|
+
\`\`\`
|
|
271
|
+
`);
|
|
272
|
+
|
|
273
|
+
const codeBlocks = findNodes(result.ast!, (node) => node.type === 'codeBlock');
|
|
274
|
+
console.log('Found', codeBlocks.length, 'code blocks');
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Example 4: Performance Monitoring
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const parser = new MarkdownParser({
|
|
281
|
+
cache: { enabled: true },
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const markdown = '# Heading\n\n' + 'Paragraph text. '.repeat(1000);
|
|
285
|
+
|
|
286
|
+
// First parse
|
|
287
|
+
const result1 = await parser.parse(markdown);
|
|
288
|
+
console.log('First parse:', result1.metadata.parseTime, 'ms');
|
|
289
|
+
|
|
290
|
+
// Cached parse
|
|
291
|
+
const result2 = await parser.parse(markdown);
|
|
292
|
+
console.log('Cached parse:', result2.metadata.parseTime, 'ms');
|
|
293
|
+
console.log('Speedup:', result1.metadata.parseTime / result2.metadata.parseTime, 'x');
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Integration Status
|
|
297
|
+
|
|
298
|
+
- **Logger**: ✅ Integrated - Uses `@bernierllc/logger` for debug output
|
|
299
|
+
- **Docs-Suite**: ✅ Ready - Complete TypeScript definitions and JSDoc
|
|
300
|
+
- **NeverHub**: ⚠️ Optional - Core package, standalone operation
|
|
301
|
+
|
|
302
|
+
## Performance
|
|
303
|
+
|
|
304
|
+
- **Large documents**: Parses 1000 headings in <1 second
|
|
305
|
+
- **Caching**: 10-100x speedup for repeated parsing
|
|
306
|
+
- **Memory efficient**: Configurable cache size limits
|
|
307
|
+
- **Non-blocking**: Async API for integration with event loops
|
|
308
|
+
|
|
309
|
+
## Related Packages
|
|
310
|
+
|
|
311
|
+
- [@bernierllc/markdown-renderer](../markdown-renderer) - Renders AST to HTML
|
|
312
|
+
- [@bernierllc/logger](../logger) - Structured logging
|
|
313
|
+
- [@bernierllc/crypto-utils](../crypto-utils) - Hash generation for cache keys
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
Copyright (c) 2025 Bernier LLC
|
|
318
|
+
|
|
319
|
+
This file is licensed to the client under a limited-use license. The client may use and modify this code *only within the scope of the project it was delivered for*. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
320
|
+
|
|
321
|
+
## See Also
|
|
322
|
+
|
|
323
|
+
- [CommonMark Specification](https://spec.commonmark.org/)
|
|
324
|
+
- [GitHub-Flavored Markdown Spec](https://github.github.com/gfm/)
|
|
325
|
+
- [Content Management Suite](../../suite/content-management-suite)
|