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.
Files changed (221) hide show
  1. package/dist/analyzers/ast-analyzer.d.ts +251 -0
  2. package/dist/analyzers/ast-analyzer.d.ts.map +1 -0
  3. package/dist/analyzers/ast-analyzer.js +548 -0
  4. package/dist/analyzers/ast-analyzer.js.map +1 -0
  5. package/dist/analyzers/flow-analyzer.d.ts +241 -0
  6. package/dist/analyzers/flow-analyzer.d.ts.map +1 -0
  7. package/dist/analyzers/flow-analyzer.js +1219 -0
  8. package/dist/analyzers/flow-analyzer.js.map +1 -0
  9. package/dist/analyzers/index.d.ts +18 -0
  10. package/dist/analyzers/index.d.ts.map +1 -0
  11. package/dist/analyzers/index.js +19 -0
  12. package/dist/analyzers/index.js.map +1 -0
  13. package/dist/analyzers/semantic-analyzer.d.ts +252 -0
  14. package/dist/analyzers/semantic-analyzer.d.ts.map +1 -0
  15. package/dist/analyzers/semantic-analyzer.js +1182 -0
  16. package/dist/analyzers/semantic-analyzer.js.map +1 -0
  17. package/dist/analyzers/type-analyzer.d.ts +289 -0
  18. package/dist/analyzers/type-analyzer.d.ts.map +1 -0
  19. package/dist/analyzers/type-analyzer.js +1269 -0
  20. package/dist/analyzers/type-analyzer.js.map +1 -0
  21. package/dist/analyzers/types.d.ts +537 -0
  22. package/dist/analyzers/types.d.ts.map +1 -0
  23. package/dist/analyzers/types.js +11 -0
  24. package/dist/analyzers/types.js.map +1 -0
  25. package/dist/config/config-loader.d.ts +166 -0
  26. package/dist/config/config-loader.d.ts.map +1 -0
  27. package/dist/config/config-loader.js +429 -0
  28. package/dist/config/config-loader.js.map +1 -0
  29. package/dist/config/config-validator.d.ts +204 -0
  30. package/dist/config/config-validator.d.ts.map +1 -0
  31. package/dist/config/config-validator.js +632 -0
  32. package/dist/config/config-validator.js.map +1 -0
  33. package/dist/config/defaults.d.ts +8 -0
  34. package/dist/config/defaults.d.ts.map +1 -0
  35. package/dist/config/defaults.js +26 -0
  36. package/dist/config/defaults.js.map +1 -0
  37. package/dist/config/index.d.ts +10 -0
  38. package/dist/config/index.d.ts.map +1 -0
  39. package/dist/config/index.js +10 -0
  40. package/dist/config/index.js.map +1 -0
  41. package/dist/config/types.d.ts +47 -0
  42. package/dist/config/types.d.ts.map +1 -0
  43. package/dist/config/types.js +7 -0
  44. package/dist/config/types.js.map +1 -0
  45. package/dist/index.d.ts +37 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +39 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/manifest/exporter.d.ts +21 -0
  50. package/dist/manifest/exporter.d.ts.map +1 -0
  51. package/dist/manifest/exporter.js +339 -0
  52. package/dist/manifest/exporter.js.map +1 -0
  53. package/dist/manifest/index.d.ts +14 -0
  54. package/dist/manifest/index.d.ts.map +1 -0
  55. package/dist/manifest/index.js +15 -0
  56. package/dist/manifest/index.js.map +1 -0
  57. package/dist/manifest/manifest-store.d.ts +111 -0
  58. package/dist/manifest/manifest-store.d.ts.map +1 -0
  59. package/dist/manifest/manifest-store.js +418 -0
  60. package/dist/manifest/manifest-store.js.map +1 -0
  61. package/dist/manifest/types.d.ts +238 -0
  62. package/dist/manifest/types.d.ts.map +1 -0
  63. package/dist/manifest/types.js +11 -0
  64. package/dist/manifest/types.js.map +1 -0
  65. package/dist/matcher/confidence-scorer.d.ts +188 -0
  66. package/dist/matcher/confidence-scorer.d.ts.map +1 -0
  67. package/dist/matcher/confidence-scorer.js +302 -0
  68. package/dist/matcher/confidence-scorer.js.map +1 -0
  69. package/dist/matcher/index.d.ts +24 -0
  70. package/dist/matcher/index.d.ts.map +1 -0
  71. package/dist/matcher/index.js +26 -0
  72. package/dist/matcher/index.js.map +1 -0
  73. package/dist/matcher/outlier-detector.d.ts +252 -0
  74. package/dist/matcher/outlier-detector.d.ts.map +1 -0
  75. package/dist/matcher/outlier-detector.js +544 -0
  76. package/dist/matcher/outlier-detector.js.map +1 -0
  77. package/dist/matcher/pattern-matcher.d.ts +169 -0
  78. package/dist/matcher/pattern-matcher.d.ts.map +1 -0
  79. package/dist/matcher/pattern-matcher.js +692 -0
  80. package/dist/matcher/pattern-matcher.js.map +1 -0
  81. package/dist/matcher/types.d.ts +476 -0
  82. package/dist/matcher/types.d.ts.map +1 -0
  83. package/dist/matcher/types.js +36 -0
  84. package/dist/matcher/types.js.map +1 -0
  85. package/dist/parsers/base-parser.d.ts +282 -0
  86. package/dist/parsers/base-parser.d.ts.map +1 -0
  87. package/dist/parsers/base-parser.js +421 -0
  88. package/dist/parsers/base-parser.js.map +1 -0
  89. package/dist/parsers/css-parser.d.ts +225 -0
  90. package/dist/parsers/css-parser.d.ts.map +1 -0
  91. package/dist/parsers/css-parser.js +477 -0
  92. package/dist/parsers/css-parser.js.map +1 -0
  93. package/dist/parsers/index.d.ts +15 -0
  94. package/dist/parsers/index.d.ts.map +1 -0
  95. package/dist/parsers/index.js +15 -0
  96. package/dist/parsers/index.js.map +1 -0
  97. package/dist/parsers/json-parser.d.ts +219 -0
  98. package/dist/parsers/json-parser.d.ts.map +1 -0
  99. package/dist/parsers/json-parser.js +602 -0
  100. package/dist/parsers/json-parser.js.map +1 -0
  101. package/dist/parsers/markdown-parser.d.ts +276 -0
  102. package/dist/parsers/markdown-parser.d.ts.map +1 -0
  103. package/dist/parsers/markdown-parser.js +731 -0
  104. package/dist/parsers/markdown-parser.js.map +1 -0
  105. package/dist/parsers/parser-manager.d.ts +294 -0
  106. package/dist/parsers/parser-manager.d.ts.map +1 -0
  107. package/dist/parsers/parser-manager.js +738 -0
  108. package/dist/parsers/parser-manager.js.map +1 -0
  109. package/dist/parsers/python-parser.d.ts +204 -0
  110. package/dist/parsers/python-parser.d.ts.map +1 -0
  111. package/dist/parsers/python-parser.js +517 -0
  112. package/dist/parsers/python-parser.js.map +1 -0
  113. package/dist/parsers/types.d.ts +43 -0
  114. package/dist/parsers/types.d.ts.map +1 -0
  115. package/dist/parsers/types.js +7 -0
  116. package/dist/parsers/types.js.map +1 -0
  117. package/dist/parsers/typescript-parser.d.ts +264 -0
  118. package/dist/parsers/typescript-parser.d.ts.map +1 -0
  119. package/dist/parsers/typescript-parser.js +658 -0
  120. package/dist/parsers/typescript-parser.js.map +1 -0
  121. package/dist/rules/evaluator.d.ts +305 -0
  122. package/dist/rules/evaluator.d.ts.map +1 -0
  123. package/dist/rules/evaluator.js +579 -0
  124. package/dist/rules/evaluator.js.map +1 -0
  125. package/dist/rules/index.d.ts +13 -0
  126. package/dist/rules/index.d.ts.map +1 -0
  127. package/dist/rules/index.js +13 -0
  128. package/dist/rules/index.js.map +1 -0
  129. package/dist/rules/quick-fix-generator.d.ts +334 -0
  130. package/dist/rules/quick-fix-generator.d.ts.map +1 -0
  131. package/dist/rules/quick-fix-generator.js +1075 -0
  132. package/dist/rules/quick-fix-generator.js.map +1 -0
  133. package/dist/rules/rule-engine.d.ts +241 -0
  134. package/dist/rules/rule-engine.d.ts.map +1 -0
  135. package/dist/rules/rule-engine.js +585 -0
  136. package/dist/rules/rule-engine.js.map +1 -0
  137. package/dist/rules/severity-manager.d.ts +394 -0
  138. package/dist/rules/severity-manager.d.ts.map +1 -0
  139. package/dist/rules/severity-manager.js +619 -0
  140. package/dist/rules/severity-manager.js.map +1 -0
  141. package/dist/rules/types.d.ts +370 -0
  142. package/dist/rules/types.d.ts.map +1 -0
  143. package/dist/rules/types.js +133 -0
  144. package/dist/rules/types.js.map +1 -0
  145. package/dist/rules/variant-manager.d.ts +388 -0
  146. package/dist/rules/variant-manager.d.ts.map +1 -0
  147. package/dist/rules/variant-manager.js +777 -0
  148. package/dist/rules/variant-manager.js.map +1 -0
  149. package/dist/scanner/change-detector.d.ts +164 -0
  150. package/dist/scanner/change-detector.d.ts.map +1 -0
  151. package/dist/scanner/change-detector.js +263 -0
  152. package/dist/scanner/change-detector.js.map +1 -0
  153. package/dist/scanner/dependency-graph.d.ts +270 -0
  154. package/dist/scanner/dependency-graph.d.ts.map +1 -0
  155. package/dist/scanner/dependency-graph.js +436 -0
  156. package/dist/scanner/dependency-graph.js.map +1 -0
  157. package/dist/scanner/file-walker.d.ts +127 -0
  158. package/dist/scanner/file-walker.d.ts.map +1 -0
  159. package/dist/scanner/file-walker.js +526 -0
  160. package/dist/scanner/file-walker.js.map +1 -0
  161. package/dist/scanner/index.d.ts +12 -0
  162. package/dist/scanner/index.d.ts.map +1 -0
  163. package/dist/scanner/index.js +12 -0
  164. package/dist/scanner/index.js.map +1 -0
  165. package/dist/scanner/types.d.ts +218 -0
  166. package/dist/scanner/types.d.ts.map +1 -0
  167. package/dist/scanner/types.js +10 -0
  168. package/dist/scanner/types.js.map +1 -0
  169. package/dist/scanner/worker-pool.d.ts +317 -0
  170. package/dist/scanner/worker-pool.d.ts.map +1 -0
  171. package/dist/scanner/worker-pool.js +571 -0
  172. package/dist/scanner/worker-pool.js.map +1 -0
  173. package/dist/store/cache-manager.d.ts +179 -0
  174. package/dist/store/cache-manager.d.ts.map +1 -0
  175. package/dist/store/cache-manager.js +391 -0
  176. package/dist/store/cache-manager.js.map +1 -0
  177. package/dist/store/history-store.d.ts +314 -0
  178. package/dist/store/history-store.d.ts.map +1 -0
  179. package/dist/store/history-store.js +707 -0
  180. package/dist/store/history-store.js.map +1 -0
  181. package/dist/store/index.d.ts +20 -0
  182. package/dist/store/index.d.ts.map +1 -0
  183. package/dist/store/index.js +26 -0
  184. package/dist/store/index.js.map +1 -0
  185. package/dist/store/lock-file-manager.d.ts +202 -0
  186. package/dist/store/lock-file-manager.d.ts.map +1 -0
  187. package/dist/store/lock-file-manager.js +475 -0
  188. package/dist/store/lock-file-manager.js.map +1 -0
  189. package/dist/store/pattern-store.d.ts +289 -0
  190. package/dist/store/pattern-store.d.ts.map +1 -0
  191. package/dist/store/pattern-store.js +936 -0
  192. package/dist/store/pattern-store.js.map +1 -0
  193. package/dist/store/schema-validator.d.ts +159 -0
  194. package/dist/store/schema-validator.d.ts.map +1 -0
  195. package/dist/store/schema-validator.js +1096 -0
  196. package/dist/store/schema-validator.js.map +1 -0
  197. package/dist/store/types.d.ts +585 -0
  198. package/dist/store/types.d.ts.map +1 -0
  199. package/dist/store/types.js +82 -0
  200. package/dist/store/types.js.map +1 -0
  201. package/dist/types/analysis.d.ts +19 -0
  202. package/dist/types/analysis.d.ts.map +1 -0
  203. package/dist/types/analysis.js +5 -0
  204. package/dist/types/analysis.js.map +1 -0
  205. package/dist/types/common.d.ts +7 -0
  206. package/dist/types/common.d.ts.map +1 -0
  207. package/dist/types/common.js +5 -0
  208. package/dist/types/common.js.map +1 -0
  209. package/dist/types/index.d.ts +12 -0
  210. package/dist/types/index.d.ts.map +1 -0
  211. package/dist/types/index.js +10 -0
  212. package/dist/types/index.js.map +1 -0
  213. package/dist/types/patterns.d.ts +40 -0
  214. package/dist/types/patterns.d.ts.map +1 -0
  215. package/dist/types/patterns.js +7 -0
  216. package/dist/types/patterns.js.map +1 -0
  217. package/dist/types/violations.d.ts +7 -0
  218. package/dist/types/violations.d.ts.map +1 -0
  219. package/dist/types/violations.js +7 -0
  220. package/dist/types/violations.js.map +1 -0
  221. 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: ![alt](url "title")
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.alt}](${image.url})`, 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