driftdetect-core 0.1.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/dist/analyzers/ast-analyzer.d.ts +251 -0
- package/dist/analyzers/ast-analyzer.d.ts.map +1 -0
- package/dist/analyzers/ast-analyzer.js +548 -0
- package/dist/analyzers/ast-analyzer.js.map +1 -0
- package/dist/analyzers/flow-analyzer.d.ts +241 -0
- package/dist/analyzers/flow-analyzer.d.ts.map +1 -0
- package/dist/analyzers/flow-analyzer.js +1219 -0
- package/dist/analyzers/flow-analyzer.js.map +1 -0
- package/dist/analyzers/index.d.ts +18 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +19 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/semantic-analyzer.d.ts +252 -0
- package/dist/analyzers/semantic-analyzer.d.ts.map +1 -0
- package/dist/analyzers/semantic-analyzer.js +1182 -0
- package/dist/analyzers/semantic-analyzer.js.map +1 -0
- package/dist/analyzers/type-analyzer.d.ts +289 -0
- package/dist/analyzers/type-analyzer.d.ts.map +1 -0
- package/dist/analyzers/type-analyzer.js +1269 -0
- package/dist/analyzers/type-analyzer.js.map +1 -0
- package/dist/analyzers/types.d.ts +537 -0
- package/dist/analyzers/types.d.ts.map +1 -0
- package/dist/analyzers/types.js +11 -0
- package/dist/analyzers/types.js.map +1 -0
- package/dist/config/config-loader.d.ts +166 -0
- package/dist/config/config-loader.d.ts.map +1 -0
- package/dist/config/config-loader.js +429 -0
- package/dist/config/config-loader.js.map +1 -0
- package/dist/config/config-validator.d.ts +204 -0
- package/dist/config/config-validator.d.ts.map +1 -0
- package/dist/config/config-validator.js +632 -0
- package/dist/config/config-validator.js.map +1 -0
- package/dist/config/defaults.d.ts +8 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +26 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +10 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +47 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +7 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest/exporter.d.ts +21 -0
- package/dist/manifest/exporter.d.ts.map +1 -0
- package/dist/manifest/exporter.js +339 -0
- package/dist/manifest/exporter.js.map +1 -0
- package/dist/manifest/index.d.ts +14 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +15 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/manifest/manifest-store.d.ts +111 -0
- package/dist/manifest/manifest-store.d.ts.map +1 -0
- package/dist/manifest/manifest-store.js +418 -0
- package/dist/manifest/manifest-store.js.map +1 -0
- package/dist/manifest/types.d.ts +238 -0
- package/dist/manifest/types.d.ts.map +1 -0
- package/dist/manifest/types.js +11 -0
- package/dist/manifest/types.js.map +1 -0
- package/dist/matcher/confidence-scorer.d.ts +188 -0
- package/dist/matcher/confidence-scorer.d.ts.map +1 -0
- package/dist/matcher/confidence-scorer.js +302 -0
- package/dist/matcher/confidence-scorer.js.map +1 -0
- package/dist/matcher/index.d.ts +24 -0
- package/dist/matcher/index.d.ts.map +1 -0
- package/dist/matcher/index.js +26 -0
- package/dist/matcher/index.js.map +1 -0
- package/dist/matcher/outlier-detector.d.ts +252 -0
- package/dist/matcher/outlier-detector.d.ts.map +1 -0
- package/dist/matcher/outlier-detector.js +544 -0
- package/dist/matcher/outlier-detector.js.map +1 -0
- package/dist/matcher/pattern-matcher.d.ts +169 -0
- package/dist/matcher/pattern-matcher.d.ts.map +1 -0
- package/dist/matcher/pattern-matcher.js +692 -0
- package/dist/matcher/pattern-matcher.js.map +1 -0
- package/dist/matcher/types.d.ts +476 -0
- package/dist/matcher/types.d.ts.map +1 -0
- package/dist/matcher/types.js +36 -0
- package/dist/matcher/types.js.map +1 -0
- package/dist/parsers/base-parser.d.ts +282 -0
- package/dist/parsers/base-parser.d.ts.map +1 -0
- package/dist/parsers/base-parser.js +421 -0
- package/dist/parsers/base-parser.js.map +1 -0
- package/dist/parsers/css-parser.d.ts +225 -0
- package/dist/parsers/css-parser.d.ts.map +1 -0
- package/dist/parsers/css-parser.js +477 -0
- package/dist/parsers/css-parser.js.map +1 -0
- package/dist/parsers/index.d.ts +15 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +15 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/json-parser.d.ts +219 -0
- package/dist/parsers/json-parser.d.ts.map +1 -0
- package/dist/parsers/json-parser.js +602 -0
- package/dist/parsers/json-parser.js.map +1 -0
- package/dist/parsers/markdown-parser.d.ts +276 -0
- package/dist/parsers/markdown-parser.d.ts.map +1 -0
- package/dist/parsers/markdown-parser.js +731 -0
- package/dist/parsers/markdown-parser.js.map +1 -0
- package/dist/parsers/parser-manager.d.ts +294 -0
- package/dist/parsers/parser-manager.d.ts.map +1 -0
- package/dist/parsers/parser-manager.js +738 -0
- package/dist/parsers/parser-manager.js.map +1 -0
- package/dist/parsers/python-parser.d.ts +204 -0
- package/dist/parsers/python-parser.d.ts.map +1 -0
- package/dist/parsers/python-parser.js +517 -0
- package/dist/parsers/python-parser.js.map +1 -0
- package/dist/parsers/types.d.ts +43 -0
- package/dist/parsers/types.d.ts.map +1 -0
- package/dist/parsers/types.js +7 -0
- package/dist/parsers/types.js.map +1 -0
- package/dist/parsers/typescript-parser.d.ts +264 -0
- package/dist/parsers/typescript-parser.d.ts.map +1 -0
- package/dist/parsers/typescript-parser.js +658 -0
- package/dist/parsers/typescript-parser.js.map +1 -0
- package/dist/rules/evaluator.d.ts +305 -0
- package/dist/rules/evaluator.d.ts.map +1 -0
- package/dist/rules/evaluator.js +579 -0
- package/dist/rules/evaluator.js.map +1 -0
- package/dist/rules/index.d.ts +13 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +13 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/quick-fix-generator.d.ts +334 -0
- package/dist/rules/quick-fix-generator.d.ts.map +1 -0
- package/dist/rules/quick-fix-generator.js +1075 -0
- package/dist/rules/quick-fix-generator.js.map +1 -0
- package/dist/rules/rule-engine.d.ts +241 -0
- package/dist/rules/rule-engine.d.ts.map +1 -0
- package/dist/rules/rule-engine.js +585 -0
- package/dist/rules/rule-engine.js.map +1 -0
- package/dist/rules/severity-manager.d.ts +394 -0
- package/dist/rules/severity-manager.d.ts.map +1 -0
- package/dist/rules/severity-manager.js +619 -0
- package/dist/rules/severity-manager.js.map +1 -0
- package/dist/rules/types.d.ts +370 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +133 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/variant-manager.d.ts +388 -0
- package/dist/rules/variant-manager.d.ts.map +1 -0
- package/dist/rules/variant-manager.js +777 -0
- package/dist/rules/variant-manager.js.map +1 -0
- package/dist/scanner/change-detector.d.ts +164 -0
- package/dist/scanner/change-detector.d.ts.map +1 -0
- package/dist/scanner/change-detector.js +263 -0
- package/dist/scanner/change-detector.js.map +1 -0
- package/dist/scanner/dependency-graph.d.ts +270 -0
- package/dist/scanner/dependency-graph.d.ts.map +1 -0
- package/dist/scanner/dependency-graph.js +436 -0
- package/dist/scanner/dependency-graph.js.map +1 -0
- package/dist/scanner/file-walker.d.ts +127 -0
- package/dist/scanner/file-walker.d.ts.map +1 -0
- package/dist/scanner/file-walker.js +526 -0
- package/dist/scanner/file-walker.js.map +1 -0
- package/dist/scanner/index.d.ts +12 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +12 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/types.d.ts +218 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +10 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/scanner/worker-pool.d.ts +317 -0
- package/dist/scanner/worker-pool.d.ts.map +1 -0
- package/dist/scanner/worker-pool.js +571 -0
- package/dist/scanner/worker-pool.js.map +1 -0
- package/dist/store/cache-manager.d.ts +179 -0
- package/dist/store/cache-manager.d.ts.map +1 -0
- package/dist/store/cache-manager.js +391 -0
- package/dist/store/cache-manager.js.map +1 -0
- package/dist/store/history-store.d.ts +314 -0
- package/dist/store/history-store.d.ts.map +1 -0
- package/dist/store/history-store.js +707 -0
- package/dist/store/history-store.js.map +1 -0
- package/dist/store/index.d.ts +20 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +26 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/lock-file-manager.d.ts +202 -0
- package/dist/store/lock-file-manager.d.ts.map +1 -0
- package/dist/store/lock-file-manager.js +475 -0
- package/dist/store/lock-file-manager.js.map +1 -0
- package/dist/store/pattern-store.d.ts +289 -0
- package/dist/store/pattern-store.d.ts.map +1 -0
- package/dist/store/pattern-store.js +936 -0
- package/dist/store/pattern-store.js.map +1 -0
- package/dist/store/schema-validator.d.ts +159 -0
- package/dist/store/schema-validator.d.ts.map +1 -0
- package/dist/store/schema-validator.js +1096 -0
- package/dist/store/schema-validator.js.map +1 -0
- package/dist/store/types.d.ts +585 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +82 -0
- package/dist/store/types.js.map +1 -0
- package/dist/types/analysis.d.ts +19 -0
- package/dist/types/analysis.d.ts.map +1 -0
- package/dist/types/analysis.js +5 -0
- package/dist/types/analysis.js.map +1 -0
- package/dist/types/common.d.ts +7 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +5 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/patterns.d.ts +40 -0
- package/dist/types/patterns.d.ts.map +1 -0
- package/dist/types/patterns.js +7 -0
- package/dist/types/patterns.js.map +1 -0
- package/dist/types/violations.d.ts +7 -0
- package/dist/types/violations.d.ts.map +1 -0
- package/dist/types/violations.js +7 -0
- package/dist/types/violations.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Parser - Markdown/MDX parsing for AST extraction
|
|
3
|
+
*
|
|
4
|
+
* Extracts headings, code blocks, links, images, lists, blockquotes,
|
|
5
|
+
* and front matter from Markdown files using regex-based parsing.
|
|
6
|
+
*
|
|
7
|
+
* @requirements 3.2
|
|
8
|
+
*/
|
|
9
|
+
import { BaseParser } from './base-parser.js';
|
|
10
|
+
/**
|
|
11
|
+
* Markdown/MDX parser using regex-based parsing.
|
|
12
|
+
*
|
|
13
|
+
* Provides AST parsing and extraction of headings, code blocks,
|
|
14
|
+
* links, images, lists, blockquotes, and front matter from
|
|
15
|
+
* Markdown source files.
|
|
16
|
+
*
|
|
17
|
+
* @requirements 3.2 - Support Markdown parsing
|
|
18
|
+
* @requirements 3.3 - Graceful degradation on parse errors
|
|
19
|
+
*/
|
|
20
|
+
export class MarkdownParser extends BaseParser {
|
|
21
|
+
language = 'markdown';
|
|
22
|
+
extensions = ['.md', '.mdx', '.markdown'];
|
|
23
|
+
/**
|
|
24
|
+
* Parse Markdown source code into an AST.
|
|
25
|
+
*
|
|
26
|
+
* @param source - The source code to parse
|
|
27
|
+
* @param filePath - Optional file path for error reporting
|
|
28
|
+
* @returns MarkdownParseResult containing the AST and extracted information
|
|
29
|
+
*
|
|
30
|
+
* @requirements 3.2, 3.3
|
|
31
|
+
*/
|
|
32
|
+
parse(source, _filePath) {
|
|
33
|
+
try {
|
|
34
|
+
const lines = source.split('\n');
|
|
35
|
+
const rootChildren = [];
|
|
36
|
+
// Extract front matter first (must be at the start)
|
|
37
|
+
const frontMatter = this.extractFrontMatter(source, lines);
|
|
38
|
+
// Extract semantic information
|
|
39
|
+
const headings = this.extractHeadings(source, lines);
|
|
40
|
+
const codeBlocks = this.extractCodeBlocks(source, lines);
|
|
41
|
+
const links = this.extractLinks(source, lines);
|
|
42
|
+
const images = this.extractImages(source, lines);
|
|
43
|
+
const lists = this.extractLists(source, lines);
|
|
44
|
+
const blockquotes = this.extractBlockquotes(source, lines);
|
|
45
|
+
// Build AST nodes for front matter
|
|
46
|
+
if (frontMatter) {
|
|
47
|
+
const frontMatterNode = this.createFrontMatterNode(frontMatter);
|
|
48
|
+
rootChildren.push(frontMatterNode);
|
|
49
|
+
}
|
|
50
|
+
// Build AST nodes for headings
|
|
51
|
+
for (const heading of headings) {
|
|
52
|
+
const headingNode = this.createHeadingNode(heading);
|
|
53
|
+
rootChildren.push(headingNode);
|
|
54
|
+
}
|
|
55
|
+
// Build AST nodes for code blocks
|
|
56
|
+
for (const codeBlock of codeBlocks) {
|
|
57
|
+
const codeBlockNode = this.createCodeBlockNode(codeBlock);
|
|
58
|
+
rootChildren.push(codeBlockNode);
|
|
59
|
+
}
|
|
60
|
+
// Build AST nodes for links
|
|
61
|
+
for (const link of links) {
|
|
62
|
+
const linkNode = this.createLinkNode(link);
|
|
63
|
+
rootChildren.push(linkNode);
|
|
64
|
+
}
|
|
65
|
+
// Build AST nodes for images
|
|
66
|
+
for (const image of images) {
|
|
67
|
+
const imageNode = this.createImageNode(image);
|
|
68
|
+
rootChildren.push(imageNode);
|
|
69
|
+
}
|
|
70
|
+
// Build AST nodes for lists
|
|
71
|
+
for (const list of lists) {
|
|
72
|
+
const listNode = this.createListNode(list);
|
|
73
|
+
rootChildren.push(listNode);
|
|
74
|
+
}
|
|
75
|
+
// Build AST nodes for blockquotes
|
|
76
|
+
for (const blockquote of blockquotes) {
|
|
77
|
+
const blockquoteNode = this.createBlockquoteNode(blockquote);
|
|
78
|
+
rootChildren.push(blockquoteNode);
|
|
79
|
+
}
|
|
80
|
+
// Create root node
|
|
81
|
+
const endPosition = lines.length > 0
|
|
82
|
+
? { row: lines.length - 1, column: lines[lines.length - 1]?.length ?? 0 }
|
|
83
|
+
: { row: 0, column: 0 };
|
|
84
|
+
const rootNode = this.createNode('Document', source, { row: 0, column: 0 }, endPosition, rootChildren);
|
|
85
|
+
const ast = this.createAST(rootNode, source);
|
|
86
|
+
return {
|
|
87
|
+
...this.createSuccessResult(ast),
|
|
88
|
+
headings,
|
|
89
|
+
codeBlocks,
|
|
90
|
+
links,
|
|
91
|
+
images,
|
|
92
|
+
lists,
|
|
93
|
+
blockquotes,
|
|
94
|
+
frontMatter,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown parse error';
|
|
99
|
+
return {
|
|
100
|
+
...this.createFailureResult([this.createError(errorMessage, { row: 0, column: 0 })]),
|
|
101
|
+
headings: [],
|
|
102
|
+
codeBlocks: [],
|
|
103
|
+
links: [],
|
|
104
|
+
images: [],
|
|
105
|
+
lists: [],
|
|
106
|
+
blockquotes: [],
|
|
107
|
+
frontMatter: null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Query the AST for nodes matching a pattern.
|
|
113
|
+
*
|
|
114
|
+
* Supports querying by node type (e.g., 'Heading', 'CodeBlock', 'Link', 'Image').
|
|
115
|
+
*
|
|
116
|
+
* @param ast - The AST to query
|
|
117
|
+
* @param pattern - The node type to search for
|
|
118
|
+
* @returns Array of matching AST nodes
|
|
119
|
+
*
|
|
120
|
+
* @requirements 3.5
|
|
121
|
+
*/
|
|
122
|
+
query(ast, pattern) {
|
|
123
|
+
return this.findNodesByType(ast, pattern);
|
|
124
|
+
}
|
|
125
|
+
// ============================================
|
|
126
|
+
// Front Matter Extraction
|
|
127
|
+
// ============================================
|
|
128
|
+
/**
|
|
129
|
+
* Extract YAML front matter from the start of the document.
|
|
130
|
+
*/
|
|
131
|
+
extractFrontMatter(source, lines) {
|
|
132
|
+
// Front matter must start at the very beginning with ---
|
|
133
|
+
if (!source.startsWith('---')) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
// Find the closing ---
|
|
137
|
+
const endMatch = source.slice(3).match(/\n---(?:\n|$)/);
|
|
138
|
+
if (!endMatch || endMatch.index === undefined) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const endIndex = endMatch.index + 3; // +3 for initial ---
|
|
142
|
+
const raw = source.slice(4, endIndex).trim(); // Skip opening --- and newline
|
|
143
|
+
// Parse YAML content (simple key: value parsing)
|
|
144
|
+
const data = this.parseSimpleYaml(raw);
|
|
145
|
+
// Calculate end position
|
|
146
|
+
let endRow = 0;
|
|
147
|
+
let charCount = 0;
|
|
148
|
+
for (let i = 0; i < lines.length; i++) {
|
|
149
|
+
charCount += (lines[i]?.length ?? 0) + 1; // +1 for newline
|
|
150
|
+
if (charCount > endIndex + 4) { // +4 for closing ---\n
|
|
151
|
+
endRow = i;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
raw,
|
|
157
|
+
data,
|
|
158
|
+
startPosition: { row: 0, column: 0 },
|
|
159
|
+
endPosition: { row: endRow, column: 3 },
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Parse simple YAML key-value pairs.
|
|
164
|
+
*/
|
|
165
|
+
parseSimpleYaml(yaml) {
|
|
166
|
+
const data = {};
|
|
167
|
+
const lines = yaml.split('\n');
|
|
168
|
+
for (const line of lines) {
|
|
169
|
+
const trimmed = line.trim();
|
|
170
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
171
|
+
continue;
|
|
172
|
+
const colonIndex = trimmed.indexOf(':');
|
|
173
|
+
if (colonIndex > 0) {
|
|
174
|
+
const key = trimmed.slice(0, colonIndex).trim();
|
|
175
|
+
let value = trimmed.slice(colonIndex + 1).trim();
|
|
176
|
+
// Parse value type
|
|
177
|
+
if (value === 'true')
|
|
178
|
+
value = true;
|
|
179
|
+
else if (value === 'false')
|
|
180
|
+
value = false;
|
|
181
|
+
else if (value === 'null' || value === '')
|
|
182
|
+
value = null;
|
|
183
|
+
else if (!isNaN(Number(value)) && value !== '')
|
|
184
|
+
value = Number(value);
|
|
185
|
+
else if (typeof value === 'string' && value.startsWith('"') && value.endsWith('"')) {
|
|
186
|
+
value = value.slice(1, -1);
|
|
187
|
+
}
|
|
188
|
+
else if (typeof value === 'string' && value.startsWith("'") && value.endsWith("'")) {
|
|
189
|
+
value = value.slice(1, -1);
|
|
190
|
+
}
|
|
191
|
+
else if (typeof value === 'string' && value.startsWith('[') && value.endsWith(']')) {
|
|
192
|
+
// Simple array parsing
|
|
193
|
+
value = value.slice(1, -1).split(',').map(v => v.trim());
|
|
194
|
+
}
|
|
195
|
+
data[key] = value;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return data;
|
|
199
|
+
}
|
|
200
|
+
// ============================================
|
|
201
|
+
// Heading Extraction
|
|
202
|
+
// ============================================
|
|
203
|
+
/**
|
|
204
|
+
* Extract all headings from Markdown source.
|
|
205
|
+
*/
|
|
206
|
+
extractHeadings(source, lines) {
|
|
207
|
+
const headings = [];
|
|
208
|
+
// Match ATX-style headings: # Heading
|
|
209
|
+
const atxHeadingRegex = /^(#{1,6})\s+(.+?)(?:\s+#*)?$/gm;
|
|
210
|
+
let match;
|
|
211
|
+
while ((match = atxHeadingRegex.exec(source)) !== null) {
|
|
212
|
+
const hashes = match[1] ?? '';
|
|
213
|
+
const level = hashes.length;
|
|
214
|
+
const text = (match[2] ?? '').trim();
|
|
215
|
+
const lineNumber = this.getLineNumber(source, match.index);
|
|
216
|
+
headings.push({
|
|
217
|
+
level,
|
|
218
|
+
text,
|
|
219
|
+
slug: this.generateSlug(text),
|
|
220
|
+
startPosition: { row: lineNumber, column: 0 },
|
|
221
|
+
endPosition: { row: lineNumber, column: (lines[lineNumber]?.length ?? 0) },
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
// Match Setext-style headings (underlined with = or -)
|
|
225
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
226
|
+
const currentLine = lines[i]?.trim() ?? '';
|
|
227
|
+
const nextLine = lines[i + 1]?.trim() ?? '';
|
|
228
|
+
if (currentLine && /^=+$/.test(nextLine)) {
|
|
229
|
+
// H1 with ===
|
|
230
|
+
headings.push({
|
|
231
|
+
level: 1,
|
|
232
|
+
text: currentLine,
|
|
233
|
+
slug: this.generateSlug(currentLine),
|
|
234
|
+
startPosition: { row: i, column: 0 },
|
|
235
|
+
endPosition: { row: i + 1, column: (lines[i + 1]?.length ?? 0) },
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else if (currentLine && /^-+$/.test(nextLine) && nextLine.length >= 2) {
|
|
239
|
+
// H2 with ---
|
|
240
|
+
headings.push({
|
|
241
|
+
level: 2,
|
|
242
|
+
text: currentLine,
|
|
243
|
+
slug: this.generateSlug(currentLine),
|
|
244
|
+
startPosition: { row: i, column: 0 },
|
|
245
|
+
endPosition: { row: i + 1, column: (lines[i + 1]?.length ?? 0) },
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Sort by position
|
|
250
|
+
headings.sort((a, b) => a.startPosition.row - b.startPosition.row);
|
|
251
|
+
return headings;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Generate a URL-friendly slug from heading text.
|
|
255
|
+
*/
|
|
256
|
+
generateSlug(text) {
|
|
257
|
+
return text
|
|
258
|
+
.toLowerCase()
|
|
259
|
+
.replace(/[^\w\s-]/g, '') // Remove special characters
|
|
260
|
+
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
261
|
+
.replace(/-+/g, '-') // Replace multiple hyphens with single
|
|
262
|
+
.replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
|
|
263
|
+
}
|
|
264
|
+
// ============================================
|
|
265
|
+
// Code Block Extraction
|
|
266
|
+
// ============================================
|
|
267
|
+
/**
|
|
268
|
+
* Extract all code blocks from Markdown source.
|
|
269
|
+
*/
|
|
270
|
+
extractCodeBlocks(source, lines) {
|
|
271
|
+
const codeBlocks = [];
|
|
272
|
+
// Match fenced code blocks with ``` or ~~~
|
|
273
|
+
const fencedCodeRegex = /^(```|~~~)(\w*)?(?:\s+(.+))?\n([\s\S]*?)^\1$/gm;
|
|
274
|
+
let match;
|
|
275
|
+
while ((match = fencedCodeRegex.exec(source)) !== null) {
|
|
276
|
+
const language = match[2] || null;
|
|
277
|
+
const meta = match[3] || null;
|
|
278
|
+
const content = match[4] ?? '';
|
|
279
|
+
const lineNumber = this.getLineNumber(source, match.index);
|
|
280
|
+
const endLineNumber = this.getLineNumber(source, match.index + (match[0]?.length ?? 0));
|
|
281
|
+
codeBlocks.push({
|
|
282
|
+
language,
|
|
283
|
+
content: content.trimEnd(),
|
|
284
|
+
isInline: false,
|
|
285
|
+
meta,
|
|
286
|
+
startPosition: { row: lineNumber, column: 0 },
|
|
287
|
+
endPosition: { row: endLineNumber, column: (lines[endLineNumber]?.length ?? 0) },
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
// Match indented code blocks (4 spaces or 1 tab)
|
|
291
|
+
let inCodeBlock = false;
|
|
292
|
+
let codeBlockStart = -1;
|
|
293
|
+
let codeBlockContent = [];
|
|
294
|
+
for (let i = 0; i < lines.length; i++) {
|
|
295
|
+
const line = lines[i] ?? '';
|
|
296
|
+
const isIndented = line.startsWith(' ') || line.startsWith('\t');
|
|
297
|
+
const isEmpty = line.trim() === '';
|
|
298
|
+
if (isIndented && !inCodeBlock) {
|
|
299
|
+
// Start of indented code block
|
|
300
|
+
inCodeBlock = true;
|
|
301
|
+
codeBlockStart = i;
|
|
302
|
+
codeBlockContent = [line.slice(4) || line.slice(1)]; // Remove indent
|
|
303
|
+
}
|
|
304
|
+
else if (inCodeBlock && (isIndented || isEmpty)) {
|
|
305
|
+
// Continue code block
|
|
306
|
+
codeBlockContent.push(isIndented ? (line.slice(4) || line.slice(1)) : '');
|
|
307
|
+
}
|
|
308
|
+
else if (inCodeBlock && !isIndented && !isEmpty) {
|
|
309
|
+
// End of indented code block
|
|
310
|
+
// Remove trailing empty lines
|
|
311
|
+
while (codeBlockContent.length > 0 && codeBlockContent[codeBlockContent.length - 1]?.trim() === '') {
|
|
312
|
+
codeBlockContent.pop();
|
|
313
|
+
}
|
|
314
|
+
if (codeBlockContent.length > 0) {
|
|
315
|
+
codeBlocks.push({
|
|
316
|
+
language: null,
|
|
317
|
+
content: codeBlockContent.join('\n'),
|
|
318
|
+
isInline: false,
|
|
319
|
+
meta: null,
|
|
320
|
+
startPosition: { row: codeBlockStart, column: 0 },
|
|
321
|
+
endPosition: { row: i - 1, column: (lines[i - 1]?.length ?? 0) },
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
inCodeBlock = false;
|
|
325
|
+
codeBlockContent = [];
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Handle code block at end of file
|
|
329
|
+
if (inCodeBlock && codeBlockContent.length > 0) {
|
|
330
|
+
while (codeBlockContent.length > 0 && codeBlockContent[codeBlockContent.length - 1]?.trim() === '') {
|
|
331
|
+
codeBlockContent.pop();
|
|
332
|
+
}
|
|
333
|
+
if (codeBlockContent.length > 0) {
|
|
334
|
+
codeBlocks.push({
|
|
335
|
+
language: null,
|
|
336
|
+
content: codeBlockContent.join('\n'),
|
|
337
|
+
isInline: false,
|
|
338
|
+
meta: null,
|
|
339
|
+
startPosition: { row: codeBlockStart, column: 0 },
|
|
340
|
+
endPosition: { row: lines.length - 1, column: (lines[lines.length - 1]?.length ?? 0) },
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Match inline code spans
|
|
345
|
+
const inlineCodeRegex = /`([^`\n]+)`/g;
|
|
346
|
+
while ((match = inlineCodeRegex.exec(source)) !== null) {
|
|
347
|
+
const content = match[1] ?? '';
|
|
348
|
+
const lineNumber = this.getLineNumber(source, match.index);
|
|
349
|
+
const column = this.getColumnNumber(source, match.index);
|
|
350
|
+
codeBlocks.push({
|
|
351
|
+
language: null,
|
|
352
|
+
content,
|
|
353
|
+
isInline: true,
|
|
354
|
+
meta: null,
|
|
355
|
+
startPosition: { row: lineNumber, column },
|
|
356
|
+
endPosition: { row: lineNumber, column: column + (match[0]?.length ?? 0) },
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// Sort by position
|
|
360
|
+
codeBlocks.sort((a, b) => {
|
|
361
|
+
if (a.startPosition.row !== b.startPosition.row) {
|
|
362
|
+
return a.startPosition.row - b.startPosition.row;
|
|
363
|
+
}
|
|
364
|
+
return a.startPosition.column - b.startPosition.column;
|
|
365
|
+
});
|
|
366
|
+
return codeBlocks;
|
|
367
|
+
}
|
|
368
|
+
// ============================================
|
|
369
|
+
// Link Extraction
|
|
370
|
+
// ============================================
|
|
371
|
+
/**
|
|
372
|
+
* Extract all links from Markdown source.
|
|
373
|
+
*/
|
|
374
|
+
extractLinks(source, _lines) {
|
|
375
|
+
const links = [];
|
|
376
|
+
// Match inline links: [text](url "title")
|
|
377
|
+
const inlineLinkRegex = /\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g;
|
|
378
|
+
let match;
|
|
379
|
+
while ((match = inlineLinkRegex.exec(source)) !== null) {
|
|
380
|
+
const text = match[1] ?? '';
|
|
381
|
+
const url = match[2] ?? '';
|
|
382
|
+
const title = match[3] || null;
|
|
383
|
+
const lineNumber = this.getLineNumber(source, match.index);
|
|
384
|
+
const column = this.getColumnNumber(source, match.index);
|
|
385
|
+
links.push({
|
|
386
|
+
text,
|
|
387
|
+
url,
|
|
388
|
+
title,
|
|
389
|
+
type: 'inline',
|
|
390
|
+
referenceId: null,
|
|
391
|
+
startPosition: { row: lineNumber, column },
|
|
392
|
+
endPosition: { row: lineNumber, column: column + (match[0]?.length ?? 0) },
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
// Match reference-style links: [text][ref] or [text][]
|
|
396
|
+
const refLinkRegex = /\[([^\]]+)\]\[([^\]]*)\]/g;
|
|
397
|
+
while ((match = refLinkRegex.exec(source)) !== null) {
|
|
398
|
+
const text = match[1] ?? '';
|
|
399
|
+
const refId = match[2] || text; // If empty, use text as reference
|
|
400
|
+
const lineNumber = this.getLineNumber(source, match.index);
|
|
401
|
+
const column = this.getColumnNumber(source, match.index);
|
|
402
|
+
// Find the reference definition
|
|
403
|
+
const refDef = this.findLinkReference(source, refId);
|
|
404
|
+
links.push({
|
|
405
|
+
text,
|
|
406
|
+
url: refDef?.url ?? '',
|
|
407
|
+
title: refDef?.title ?? null,
|
|
408
|
+
type: 'reference',
|
|
409
|
+
referenceId: refId,
|
|
410
|
+
startPosition: { row: lineNumber, column },
|
|
411
|
+
endPosition: { row: lineNumber, column: column + (match[0]?.length ?? 0) },
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
// Match autolinks: <url> or <email>
|
|
415
|
+
const autolinkRegex = /<(https?:\/\/[^>]+|[^@\s>]+@[^@\s>]+\.[^@\s>]+)>/g;
|
|
416
|
+
while ((match = autolinkRegex.exec(source)) !== null) {
|
|
417
|
+
const url = match[1] ?? '';
|
|
418
|
+
const lineNumber = this.getLineNumber(source, match.index);
|
|
419
|
+
const column = this.getColumnNumber(source, match.index);
|
|
420
|
+
links.push({
|
|
421
|
+
text: url,
|
|
422
|
+
url: url.includes('@') && !url.startsWith('http') ? `mailto:${url}` : url,
|
|
423
|
+
title: null,
|
|
424
|
+
type: 'autolink',
|
|
425
|
+
referenceId: null,
|
|
426
|
+
startPosition: { row: lineNumber, column },
|
|
427
|
+
endPosition: { row: lineNumber, column: column + (match[0]?.length ?? 0) },
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return links;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Find a link reference definition.
|
|
434
|
+
*/
|
|
435
|
+
findLinkReference(source, refId) {
|
|
436
|
+
// Match reference definitions: [ref]: url "title"
|
|
437
|
+
const escapedRefId = refId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
438
|
+
const refDefRegex = new RegExp(`^\\[${escapedRefId}\\]:\\s*(\\S+)(?:\\s+"([^"]*)")?`, 'im');
|
|
439
|
+
const match = refDefRegex.exec(source);
|
|
440
|
+
if (match) {
|
|
441
|
+
return {
|
|
442
|
+
url: match[1] ?? '',
|
|
443
|
+
title: match[2] || null,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
// ============================================
|
|
449
|
+
// Image Extraction
|
|
450
|
+
// ============================================
|
|
451
|
+
/**
|
|
452
|
+
* Extract all images from Markdown source.
|
|
453
|
+
*/
|
|
454
|
+
extractImages(source, _lines) {
|
|
455
|
+
const images = [];
|
|
456
|
+
// Match inline images: 
|
|
457
|
+
const imageRegex = /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g;
|
|
458
|
+
let match;
|
|
459
|
+
while ((match = imageRegex.exec(source)) !== null) {
|
|
460
|
+
const alt = match[1] ?? '';
|
|
461
|
+
const url = match[2] ?? '';
|
|
462
|
+
const title = match[3] || null;
|
|
463
|
+
const lineNumber = this.getLineNumber(source, match.index);
|
|
464
|
+
const column = this.getColumnNumber(source, match.index);
|
|
465
|
+
images.push({
|
|
466
|
+
alt,
|
|
467
|
+
url,
|
|
468
|
+
title,
|
|
469
|
+
startPosition: { row: lineNumber, column },
|
|
470
|
+
endPosition: { row: lineNumber, column: column + (match[0]?.length ?? 0) },
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
// Match reference-style images: ![alt][ref]
|
|
474
|
+
const refImageRegex = /!\[([^\]]*)\]\[([^\]]*)\]/g;
|
|
475
|
+
while ((match = refImageRegex.exec(source)) !== null) {
|
|
476
|
+
const alt = match[1] ?? '';
|
|
477
|
+
const refId = match[2] || alt;
|
|
478
|
+
const lineNumber = this.getLineNumber(source, match.index);
|
|
479
|
+
const column = this.getColumnNumber(source, match.index);
|
|
480
|
+
// Find the reference definition
|
|
481
|
+
const refDef = this.findLinkReference(source, refId);
|
|
482
|
+
images.push({
|
|
483
|
+
alt,
|
|
484
|
+
url: refDef?.url ?? '',
|
|
485
|
+
title: refDef?.title ?? null,
|
|
486
|
+
startPosition: { row: lineNumber, column },
|
|
487
|
+
endPosition: { row: lineNumber, column: column + (match[0]?.length ?? 0) },
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return images;
|
|
491
|
+
}
|
|
492
|
+
// ============================================
|
|
493
|
+
// List Extraction
|
|
494
|
+
// ============================================
|
|
495
|
+
/**
|
|
496
|
+
* Extract all lists from Markdown source.
|
|
497
|
+
*/
|
|
498
|
+
extractLists(_source, lines) {
|
|
499
|
+
const lists = [];
|
|
500
|
+
let currentList = null;
|
|
501
|
+
for (let i = 0; i < lines.length; i++) {
|
|
502
|
+
const line = lines[i] ?? '';
|
|
503
|
+
const trimmed = line.trim();
|
|
504
|
+
// Check for unordered list item: - item, * item, + item
|
|
505
|
+
const unorderedMatch = line.match(/^(\s*)([-*+])\s+(.*)$/);
|
|
506
|
+
// Check for ordered list item: 1. item, 2) item
|
|
507
|
+
const orderedMatch = line.match(/^(\s*)(\d+)[.)]\s+(.*)$/);
|
|
508
|
+
if (unorderedMatch || orderedMatch) {
|
|
509
|
+
const match = unorderedMatch || orderedMatch;
|
|
510
|
+
const indent = match?.[1]?.length ?? 0;
|
|
511
|
+
const itemText = match?.[3] ?? '';
|
|
512
|
+
const listType = unorderedMatch ? 'unordered' : 'ordered';
|
|
513
|
+
const depth = Math.floor(indent / 2);
|
|
514
|
+
// Check for task list item
|
|
515
|
+
const taskMatch = itemText.match(/^\[([ xX])\]\s*(.*)$/);
|
|
516
|
+
const isTask = !!taskMatch;
|
|
517
|
+
const isChecked = taskMatch ? taskMatch[1]?.toLowerCase() === 'x' : null;
|
|
518
|
+
const text = taskMatch ? (taskMatch[2] ?? '') : itemText;
|
|
519
|
+
const listItem = {
|
|
520
|
+
text,
|
|
521
|
+
isTask,
|
|
522
|
+
isChecked,
|
|
523
|
+
startPosition: { row: i, column: indent },
|
|
524
|
+
endPosition: { row: i, column: line.length },
|
|
525
|
+
};
|
|
526
|
+
// Check if we need to start a new list
|
|
527
|
+
if (!currentList || currentList.type !== listType || currentList.depth !== depth) {
|
|
528
|
+
// Save previous list if exists
|
|
529
|
+
if (currentList && currentList.items.length > 0) {
|
|
530
|
+
currentList.endPosition = { row: i - 1, column: (lines[i - 1]?.length ?? 0) };
|
|
531
|
+
lists.push(currentList);
|
|
532
|
+
}
|
|
533
|
+
// Start new list
|
|
534
|
+
currentList = {
|
|
535
|
+
type: listType,
|
|
536
|
+
items: [listItem],
|
|
537
|
+
depth,
|
|
538
|
+
startPosition: { row: i, column: indent },
|
|
539
|
+
endPosition: { row: i, column: line.length },
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
// Add to current list
|
|
544
|
+
currentList.items.push(listItem);
|
|
545
|
+
currentList.endPosition = { row: i, column: line.length };
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
else if (trimmed === '' && currentList) {
|
|
549
|
+
// Empty line might end the list
|
|
550
|
+
// Check if next non-empty line continues the list
|
|
551
|
+
let nextNonEmpty = i + 1;
|
|
552
|
+
while (nextNonEmpty < lines.length && lines[nextNonEmpty]?.trim() === '') {
|
|
553
|
+
nextNonEmpty++;
|
|
554
|
+
}
|
|
555
|
+
const nextLine = lines[nextNonEmpty] ?? '';
|
|
556
|
+
const continuesList = nextLine.match(/^(\s*)([-*+]|\d+[.)])\s+/);
|
|
557
|
+
if (!continuesList) {
|
|
558
|
+
// End the list
|
|
559
|
+
currentList.endPosition = { row: i - 1, column: (lines[i - 1]?.length ?? 0) };
|
|
560
|
+
lists.push(currentList);
|
|
561
|
+
currentList = null;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
else if (currentList && !trimmed.match(/^\s/) && trimmed !== '') {
|
|
565
|
+
// Non-indented, non-list content ends the list
|
|
566
|
+
currentList.endPosition = { row: i - 1, column: (lines[i - 1]?.length ?? 0) };
|
|
567
|
+
lists.push(currentList);
|
|
568
|
+
currentList = null;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// Don't forget the last list
|
|
572
|
+
if (currentList && currentList.items.length > 0) {
|
|
573
|
+
currentList.endPosition = { row: lines.length - 1, column: (lines[lines.length - 1]?.length ?? 0) };
|
|
574
|
+
lists.push(currentList);
|
|
575
|
+
}
|
|
576
|
+
return lists;
|
|
577
|
+
}
|
|
578
|
+
// ============================================
|
|
579
|
+
// Blockquote Extraction
|
|
580
|
+
// ============================================
|
|
581
|
+
/**
|
|
582
|
+
* Extract all blockquotes from Markdown source.
|
|
583
|
+
*/
|
|
584
|
+
extractBlockquotes(_source, lines) {
|
|
585
|
+
const blockquotes = [];
|
|
586
|
+
let currentBlockquote = null;
|
|
587
|
+
for (let i = 0; i < lines.length; i++) {
|
|
588
|
+
const line = lines[i] ?? '';
|
|
589
|
+
// Match blockquote lines: > content or >> nested
|
|
590
|
+
const blockquoteMatch = line.match(/^(>+)\s?(.*)$/);
|
|
591
|
+
if (blockquoteMatch) {
|
|
592
|
+
const depth = blockquoteMatch[1]?.length ?? 1;
|
|
593
|
+
const content = blockquoteMatch[2] ?? '';
|
|
594
|
+
if (!currentBlockquote || currentBlockquote.depth !== depth) {
|
|
595
|
+
// Save previous blockquote if exists
|
|
596
|
+
if (currentBlockquote) {
|
|
597
|
+
blockquotes.push({
|
|
598
|
+
content: currentBlockquote.content.join('\n'),
|
|
599
|
+
depth: currentBlockquote.depth,
|
|
600
|
+
startPosition: { row: currentBlockquote.startLine, column: 0 },
|
|
601
|
+
endPosition: { row: i - 1, column: (lines[i - 1]?.length ?? 0) },
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
// Start new blockquote
|
|
605
|
+
currentBlockquote = {
|
|
606
|
+
content: [content],
|
|
607
|
+
depth,
|
|
608
|
+
startLine: i,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
// Continue current blockquote
|
|
613
|
+
currentBlockquote.content.push(content);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
else if (currentBlockquote) {
|
|
617
|
+
// Non-blockquote line ends the blockquote
|
|
618
|
+
blockquotes.push({
|
|
619
|
+
content: currentBlockquote.content.join('\n'),
|
|
620
|
+
depth: currentBlockquote.depth,
|
|
621
|
+
startPosition: { row: currentBlockquote.startLine, column: 0 },
|
|
622
|
+
endPosition: { row: i - 1, column: (lines[i - 1]?.length ?? 0) },
|
|
623
|
+
});
|
|
624
|
+
currentBlockquote = null;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// Don't forget the last blockquote
|
|
628
|
+
if (currentBlockquote) {
|
|
629
|
+
blockquotes.push({
|
|
630
|
+
content: currentBlockquote.content.join('\n'),
|
|
631
|
+
depth: currentBlockquote.depth,
|
|
632
|
+
startPosition: { row: currentBlockquote.startLine, column: 0 },
|
|
633
|
+
endPosition: { row: lines.length - 1, column: (lines[lines.length - 1]?.length ?? 0) },
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
return blockquotes;
|
|
637
|
+
}
|
|
638
|
+
// ============================================
|
|
639
|
+
// AST Node Creation
|
|
640
|
+
// ============================================
|
|
641
|
+
/**
|
|
642
|
+
* Create an AST node for front matter.
|
|
643
|
+
*/
|
|
644
|
+
createFrontMatterNode(frontMatter) {
|
|
645
|
+
return this.createNode('FrontMatter', frontMatter.raw, frontMatter.startPosition, frontMatter.endPosition, []);
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Create an AST node for a heading.
|
|
649
|
+
*/
|
|
650
|
+
createHeadingNode(heading) {
|
|
651
|
+
return this.createNode(`Heading${heading.level}`, heading.text, heading.startPosition, heading.endPosition, []);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Create an AST node for a code block.
|
|
655
|
+
*/
|
|
656
|
+
createCodeBlockNode(codeBlock) {
|
|
657
|
+
const nodeType = codeBlock.isInline ? 'InlineCode' : 'CodeBlock';
|
|
658
|
+
const children = [];
|
|
659
|
+
// Add language node if present
|
|
660
|
+
if (codeBlock.language) {
|
|
661
|
+
children.push(this.createNode('Language', codeBlock.language, codeBlock.startPosition, codeBlock.startPosition, []));
|
|
662
|
+
}
|
|
663
|
+
return this.createNode(nodeType, codeBlock.content, codeBlock.startPosition, codeBlock.endPosition, children);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Create an AST node for a link.
|
|
667
|
+
*/
|
|
668
|
+
createLinkNode(link) {
|
|
669
|
+
const children = [
|
|
670
|
+
this.createNode('LinkText', link.text, link.startPosition, link.endPosition, []),
|
|
671
|
+
this.createNode('LinkUrl', link.url, link.startPosition, link.endPosition, []),
|
|
672
|
+
];
|
|
673
|
+
if (link.title) {
|
|
674
|
+
children.push(this.createNode('LinkTitle', link.title, link.startPosition, link.endPosition, []));
|
|
675
|
+
}
|
|
676
|
+
return this.createNode('Link', `[${link.text}](${link.url})`, link.startPosition, link.endPosition, children);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Create an AST node for an image.
|
|
680
|
+
*/
|
|
681
|
+
createImageNode(image) {
|
|
682
|
+
const children = [
|
|
683
|
+
this.createNode('ImageAlt', image.alt, image.startPosition, image.endPosition, []),
|
|
684
|
+
this.createNode('ImageUrl', image.url, image.startPosition, image.endPosition, []),
|
|
685
|
+
];
|
|
686
|
+
if (image.title) {
|
|
687
|
+
children.push(this.createNode('ImageTitle', image.title, image.startPosition, image.endPosition, []));
|
|
688
|
+
}
|
|
689
|
+
return this.createNode('Image', ``, image.startPosition, image.endPosition, children);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Create an AST node for a list.
|
|
693
|
+
*/
|
|
694
|
+
createListNode(list) {
|
|
695
|
+
const nodeType = list.type === 'ordered' ? 'OrderedList' : 'UnorderedList';
|
|
696
|
+
const children = list.items.map((item) => this.createNode(item.isTask ? 'TaskListItem' : 'ListItem', item.text, item.startPosition, item.endPosition, []));
|
|
697
|
+
return this.createNode(nodeType, list.items.map((i) => i.text).join('\n'), list.startPosition, list.endPosition, children);
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Create an AST node for a blockquote.
|
|
701
|
+
*/
|
|
702
|
+
createBlockquoteNode(blockquote) {
|
|
703
|
+
return this.createNode('Blockquote', blockquote.content, blockquote.startPosition, blockquote.endPosition, []);
|
|
704
|
+
}
|
|
705
|
+
// ============================================
|
|
706
|
+
// Utility Methods
|
|
707
|
+
// ============================================
|
|
708
|
+
/**
|
|
709
|
+
* Get the line number for a character offset.
|
|
710
|
+
*/
|
|
711
|
+
getLineNumber(source, offset) {
|
|
712
|
+
let line = 0;
|
|
713
|
+
for (let i = 0; i < offset && i < source.length; i++) {
|
|
714
|
+
if (source[i] === '\n') {
|
|
715
|
+
line++;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return line;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Get the column number for a character offset.
|
|
722
|
+
*/
|
|
723
|
+
getColumnNumber(source, offset) {
|
|
724
|
+
let column = 0;
|
|
725
|
+
for (let i = offset - 1; i >= 0 && source[i] !== '\n'; i--) {
|
|
726
|
+
column++;
|
|
727
|
+
}
|
|
728
|
+
return column;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
//# sourceMappingURL=markdown-parser.js.map
|