@miliastry/quasar 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.
Files changed (172) hide show
  1. package/LICENSE +119 -0
  2. package/README.md +45 -0
  3. package/dist/Visuals/lyne.css +1027 -0
  4. package/dist/Visuals/osu.css +257 -0
  5. package/dist/index.d.mts +4563 -0
  6. package/dist/index.d.ts +4563 -0
  7. package/dist/index.js +11291 -0
  8. package/dist/index.mjs +11197 -0
  9. package/package.json +51 -0
  10. package/src/Analysis/Contracts/AnalysisReport.ts +26 -0
  11. package/src/Analysis/Contracts/Contribution.ts +76 -0
  12. package/src/Analysis/Contracts/Pass.ts +76 -0
  13. package/src/Analysis/Contracts/PipelineContext.ts +48 -0
  14. package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +292 -0
  15. package/src/Analysis/Passes/Analysis/MergeableColorAnalyzer.ts +112 -0
  16. package/src/Analysis/Passes/Analysis/RainbowAnalyzer.ts +233 -0
  17. package/src/Analysis/Passes/Analysis/WaveAnalyzer.ts +211 -0
  18. package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +135 -0
  19. package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +84 -0
  20. package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +99 -0
  21. package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +119 -0
  22. package/src/Analysis/Passes/Decision/DefaultDecision.ts +139 -0
  23. package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +179 -0
  24. package/src/Analysis/Passes/Transform/CollapseGradientTransform.ts +176 -0
  25. package/src/Analysis/Passes/Transform/MergeColorsTransform.ts +126 -0
  26. package/src/Analysis/Passes/Transform/RainbowCollapseTransform.ts +83 -0
  27. package/src/Analysis/Passes/Transform/WaveCollapseTransform.ts +88 -0
  28. package/src/Analysis/Passes/Utility/CharacterCountAnalyzer.ts +45 -0
  29. package/src/Analysis/Pipeline/Pipeline.ts +133 -0
  30. package/src/Analysis/Pipeline/PipelineBuilder.ts +55 -0
  31. package/src/Analysis/Pipeline/PipelineStage.ts +19 -0
  32. package/src/Analysis/Utils/color-utils.ts +132 -0
  33. package/src/Analysis/__tests__/Integration.test.ts +162 -0
  34. package/src/Analysis/__tests__/Pipeline.test.ts +133 -0
  35. package/src/Analysis/index.ts +52 -0
  36. package/src/BBCode/BBCodeDocumentModel.ts +175 -0
  37. package/src/BBCode/BBCodeToGreenNode.ts +755 -0
  38. package/src/BBCode/Parser.ts +384 -0
  39. package/src/BBCode/index.ts +12 -0
  40. package/src/Collab/positions.ts +91 -0
  41. package/src/Commands/Command.ts +44 -0
  42. package/src/Commands/CommandRegistry.ts +78 -0
  43. package/src/Commands/DeleteNode.ts +20 -0
  44. package/src/Commands/InsertText.ts +21 -0
  45. package/src/Commands/SplitMerge.ts +28 -0
  46. package/src/Commands/WrapInTag.ts +21 -0
  47. package/src/Commands/index.ts +6 -0
  48. package/src/Diff/TreeDiffer.ts +264 -0
  49. package/src/Diff/__tests__/TreeDiffer.test.ts +65 -0
  50. package/src/Diff/index.ts +2 -0
  51. package/src/Events/EventBus.ts +160 -0
  52. package/src/Events/index.ts +2 -0
  53. package/src/Formatter/Formatter.ts +54 -0
  54. package/src/Formatter/index.ts +2 -0
  55. package/src/HTML/HTMLDocumentModel.ts +35 -0
  56. package/src/HTML/HTMLToGreenNode.ts +290 -0
  57. package/src/Incremental/ChangeTracker.ts +105 -0
  58. package/src/Incremental/IncrementalParser.ts +591 -0
  59. package/src/Incremental/__tests__/IncrementalParser.test.ts +164 -0
  60. package/src/Incremental/index.ts +4 -0
  61. package/src/Lexer/BBCodeLexer.ts +382 -0
  62. package/src/Lexer/Lexer.ts +181 -0
  63. package/src/Lexer/index.ts +10 -0
  64. package/src/Linter/Linter.ts +193 -0
  65. package/src/Linter/index.ts +2 -0
  66. package/src/Markdown/MarkdownAST.ts +112 -0
  67. package/src/Markdown/MarkdownDocumentModel.ts +55 -0
  68. package/src/Markdown/MarkdownLexer.ts +203 -0
  69. package/src/Markdown/MarkdownParser.ts +455 -0
  70. package/src/Markdown/MarkdownToGreenNode.ts +153 -0
  71. package/src/Model/DocumentModel.ts +694 -0
  72. package/src/Model/NodeFactory.ts +117 -0
  73. package/src/Model/TagRegistry.ts +495 -0
  74. package/src/Model/index.ts +5 -0
  75. package/src/Plugins/PluginAPI.ts +119 -0
  76. package/src/Plugins/PluginRegistry.ts +132 -0
  77. package/src/Plugins/index.ts +3 -0
  78. package/src/Queries/QueryEngine.ts +152 -0
  79. package/src/Queries/index.ts +1 -0
  80. package/src/RenderPipeline/RenderPipeline.ts +125 -0
  81. package/src/RenderPipeline/RenderTree.ts +134 -0
  82. package/src/RenderPipeline/index.ts +4 -0
  83. package/src/Semantic/SemanticAnalyzer.ts +506 -0
  84. package/src/Semantic/index.ts +2 -0
  85. package/src/Symbols/SymbolTable.ts +124 -0
  86. package/src/Symbols/index.ts +1 -0
  87. package/src/Syntax/GreenNode.ts +324 -0
  88. package/src/Syntax/GreenNodePool.ts +269 -0
  89. package/src/Syntax/NodeMatcher.ts +370 -0
  90. package/src/Syntax/RedNode.ts +569 -0
  91. package/src/Syntax/RedNodeStore.ts +184 -0
  92. package/src/Syntax/TreeBuilder.ts +214 -0
  93. package/src/Syntax/__tests__/GreenNode.test.ts +33 -0
  94. package/src/Syntax/__tests__/RedNode.test.ts +81 -0
  95. package/src/Syntax/__tests__/RedNodeStore.test.ts +104 -0
  96. package/src/Syntax/greenEdit.ts +110 -0
  97. package/src/Syntax/hash.ts +30 -0
  98. package/src/Syntax/index.ts +12 -0
  99. package/src/Syntax/partition.ts +161 -0
  100. package/src/Syntax/preserveNodeIds.ts +201 -0
  101. package/src/Tests/ASTOptimizerIdempotence.test.ts +77 -0
  102. package/src/Tests/BlockPatcher.test.ts +437 -0
  103. package/src/Tests/BlockPatcherWindowed.test.ts +364 -0
  104. package/src/Tests/BoxDrawer.test.ts +217 -0
  105. package/src/Tests/BoxRichTitle.test.ts +105 -0
  106. package/src/Tests/Chars500kBenchmark.test.ts +151 -0
  107. package/src/Tests/Chars500kEdits.test.ts +321 -0
  108. package/src/Tests/CollabPositions.test.ts +146 -0
  109. package/src/Tests/CompilerPathProfiling.test.ts +186 -0
  110. package/src/Tests/DOMMorpher.test.ts +142 -0
  111. package/src/Tests/DomPatchPerf.test.ts +60 -0
  112. package/src/Tests/EffectSegments.snapshot.json +616 -0
  113. package/src/Tests/EffectSegments.test.ts +68 -0
  114. package/src/Tests/FindNodeAtOffset.test.ts +65 -0
  115. package/src/Tests/Fuzzer.test.ts +166 -0
  116. package/src/Tests/GreenNodePool.test.ts +153 -0
  117. package/src/Tests/Lexer.test.ts +238 -0
  118. package/src/Tests/LyneMode.test.ts +187 -0
  119. package/src/Tests/ModelCoherence.test.ts +180 -0
  120. package/src/Tests/Partition.test.ts +238 -0
  121. package/src/Tests/PluginTags.test.ts +150 -0
  122. package/src/Tests/ProblematicSection.test.ts +46 -0
  123. package/src/Tests/ProblematicSectionHTML.test.ts +58 -0
  124. package/src/Tests/RedReuse.test.ts +134 -0
  125. package/src/Tests/ReproDelete20k.test.ts +62 -0
  126. package/src/Tests/SemanticValidators.test.ts +136 -0
  127. package/src/Tests/StableNodeIds.test.ts +210 -0
  128. package/src/Tests/StudioColorBloat.test.ts +25 -0
  129. package/src/Tests/StudioDebugText.test.ts +27 -0
  130. package/src/Tests/StudioTrailingChar.test.ts +25 -0
  131. package/src/Tests/StudioValidText.test.ts +25 -0
  132. package/src/Tests/UrlImgBug.test.ts +23 -0
  133. package/src/Tests/VisualBuilderFidelity.test.ts +105 -0
  134. package/src/Tests/referenceDocument.ts +119 -0
  135. package/src/Transactions/Transaction.ts +176 -0
  136. package/src/Transactions/UndoManager.ts +111 -0
  137. package/src/Transactions/index.ts +3 -0
  138. package/src/Transformers/ASTOptimizer.ts +315 -0
  139. package/src/Transformers/GradientTransformer.ts +143 -0
  140. package/src/Transformers/GrowTransformer.ts +115 -0
  141. package/src/Transformers/RainbowTransformer.ts +121 -0
  142. package/src/Transformers/SineWaveTransformer.ts +130 -0
  143. package/src/Transformers/Transformer.ts +22 -0
  144. package/src/Types/core.ts +270 -0
  145. package/src/Types/diagnostics.ts +156 -0
  146. package/src/Types/index.ts +23 -0
  147. package/src/Types/operations.ts +180 -0
  148. package/src/Types/queries.ts +121 -0
  149. package/src/Types/symbols.ts +69 -0
  150. package/src/Types/tokens.ts +186 -0
  151. package/src/Utils/BBCodeGenerator.ts +126 -0
  152. package/src/Utils/ColorMath.ts +276 -0
  153. package/src/Utils/color.ts +112 -0
  154. package/src/Utils/dom-to-svg.test.ts +86 -0
  155. package/src/Utils/dom-to-svg.ts +615 -0
  156. package/src/Utils/treeTransformers.ts +717 -0
  157. package/src/Visitors/BBBlocksExporter.ts +69 -0
  158. package/src/Visitors/BBCodeExporter.ts +318 -0
  159. package/src/Visitors/BlockPatcher.ts +963 -0
  160. package/src/Visitors/DOMMorpher.ts +134 -0
  161. package/src/Visitors/HTMLRenderer.ts +1077 -0
  162. package/src/Visitors/JSONExporter.ts +66 -0
  163. package/src/Visitors/MarkdownExporter.ts +99 -0
  164. package/src/Visitors/SVGRenderer.ts +35 -0
  165. package/src/Visitors/TiptapExporter.ts +145 -0
  166. package/src/Visitors/Visitor.ts +48 -0
  167. package/src/Visitors/index.ts +9 -0
  168. package/src/Visuals/BoxDrawer.ts +175 -0
  169. package/src/Visuals/index.ts +42 -0
  170. package/src/Visuals/lyne.css +1027 -0
  171. package/src/Visuals/osu.css +257 -0
  172. package/src/index.ts +197 -0
@@ -0,0 +1,270 @@
1
+ /**
2
+ * DocumentEngine — Core Types
3
+ *
4
+ * The foundational types for the entire Language Platform.
5
+ * Every layer builds on these.
6
+ */
7
+
8
+ import type { Diagnostic } from './diagnostics'
9
+ import type { Range } from './tokens'
10
+
11
+ // ─── Node Identity ─────────────────────────────────────────────
12
+
13
+ /** Stable identifier that survives incremental re-parses */
14
+ export type NodeId = string & { readonly __brand: 'NodeId' }
15
+
16
+ /**
17
+ * Monotonic counter backing `createNodeId`.
18
+ *
19
+ * Deliberately NOT a UUID. Node IDs are process-local and ephemeral — they are
20
+ * regenerated on every parse and never restored from persistence — so the
21
+ * randomness of `crypto.randomUUID()` bought nothing while costing on two axes:
22
+ * it is ~7x slower per call (and a full document parse mints one per node), and
23
+ * its 36 characters are echoed into *every* element as `data-node-id`, inflating
24
+ * the emitted HTML and the work the DOM morpher has to diff.
25
+ *
26
+ * A counter is unique within the process, which is the only scope where node IDs
27
+ * are ever compared.
28
+ */
29
+ let _nodeIdCounter = 0
30
+
31
+ export function createNodeId(): NodeId {
32
+ return `n${_nodeIdCounter++}` as NodeId
33
+ }
34
+
35
+ /**
36
+ * Reset the node ID counter. Test-only: lets a suite assert on stable IDs.
37
+ * Never call this while a document tree is alive — reusing IDs across live
38
+ * trees would break every `Map<NodeId, …>` in the engine.
39
+ */
40
+ export function __resetNodeIdCounter(): void {
41
+ _nodeIdCounter = 0
42
+ }
43
+
44
+ // ─── Node Kind ─────────────────────────────────────────────────
45
+
46
+ /**
47
+ * The semantic kind of a node in the document tree.
48
+ *
49
+ * Language-agnostic at the core — BBCode tags, Markdown headings,
50
+ * HTML elements, etc. all map to these.
51
+ */
52
+ export type NodeKind =
53
+ | 'document'
54
+ | 'paragraph'
55
+ | 'text'
56
+ | 'bold'
57
+ | 'italic'
58
+ | 'underline'
59
+ | 'strikethrough'
60
+ | 'color'
61
+ | 'font_size'
62
+ | 'font'
63
+ | 'code'
64
+ | 'inline_code'
65
+ | 'spoiler'
66
+ | 'heading'
67
+ | 'center'
68
+ | 'right'
69
+ | 'left'
70
+ | 'url'
71
+ | 'email'
72
+ | 'profile'
73
+ | 'image'
74
+ | 'video'
75
+ | 'audio'
76
+ | 'imagemap'
77
+ | 'imagemap_area'
78
+ | 'svg'
79
+ | 'quote'
80
+ | 'notice'
81
+ | 'spoilerbox'
82
+ | 'box'
83
+ | 'boxw'
84
+ | 'list'
85
+ | 'list_item'
86
+ | 'zalgo'
87
+ | 'aesthetic'
88
+ | 'sparkle'
89
+ | 'bubble'
90
+ | 'flower'
91
+ | 'gradient'
92
+ | 'grow'
93
+ | 'sinewave'
94
+ | 'rainbow'
95
+ | 'spacing'
96
+ | 'empty_line'
97
+ | 'group'
98
+ | 'wnotice'
99
+ | 'align'
100
+ | 'tables'
101
+ | 'table_row'
102
+ | 'table_col'
103
+ | 'table_th'
104
+ | 'gallery'
105
+ | 'columns'
106
+ | 'separator'
107
+ | 'scroll'
108
+ | 'sup'
109
+ | 'sub'
110
+ | 'abbr'
111
+ | 'mark'
112
+ | 'kbd'
113
+ | 'tooltip'
114
+ | 'flip'
115
+ | 'raw'
116
+ | 'plain'
117
+ | 'effect'
118
+ | 'anim'
119
+ | 'container'
120
+ | 'style_tag'
121
+ | 'guild'
122
+ | 'map'
123
+ | 'custom'
124
+ | 'error'
125
+ | 'unknown'
126
+
127
+ // ─── Attributes ────────────────────────────────────────────────
128
+
129
+ export type NodeAttributes = Record<string, string | number | boolean | null | undefined>
130
+
131
+ // ─── Metadata ──────────────────────────────────────────────────
132
+
133
+ export interface NodeMetadata {
134
+ /** Arbitrary extensions data (plugins can add here) */
135
+ [key: string]: unknown
136
+ }
137
+
138
+ // ─── Source Range ──────────────────────────────────────────────
139
+
140
+ export interface SourceRange {
141
+ start: number
142
+ end: number
143
+ }
144
+
145
+ // ─── Document Node ─────────────────────────────────────────────
146
+
147
+ /**
148
+ * The primary user-facing node in the Document Model.
149
+ *
150
+ * This is the Red Tree node — mutable, with identity.
151
+ * It wraps/holds reference to the Green Tree node for structural info.
152
+ */
153
+ export interface DocumentNode {
154
+ /** Stable ID that survives incremental re-parses */
155
+ id: NodeId
156
+ /** Monotonically increasing version number for change tracking */
157
+ version: number
158
+ /** Semantic kind */
159
+ kind: NodeKind
160
+ /** Raw text content (for text leaves) */
161
+ text: string
162
+ /** Tag-level attributes (e.g. color=#ff0000, size=150) */
163
+ attributes: NodeAttributes
164
+ /** Arbitrary metadata (extensible by plugins) */
165
+ metadata: NodeMetadata
166
+ /** Child nodes */
167
+ children: DocumentNode[]
168
+ /** Diagnostics attached to this specific node */
169
+ diagnostics: Diagnostic[]
170
+ /** Source position in the original text (for mapping back) */
171
+ sourceRange: SourceRange | null
172
+ /** Reference back to parent (null for root) */
173
+ parentId: NodeId | null
174
+ /** Whether this node is "synthetic" (not from source, e.g. auto-generated) */
175
+ isSynthetic: boolean
176
+ }
177
+
178
+ // ─── Document ──────────────────────────────────────────────────
179
+
180
+ export interface DocumentSnapshot {
181
+ root: DocumentNode
182
+ source: string
183
+ language: string
184
+ version: number
185
+ createdAt: number
186
+ }
187
+
188
+ export type DocumentChangeKind =
189
+ | 'node_inserted'
190
+ | 'node_deleted'
191
+ | 'node_updated'
192
+ | 'node_moved'
193
+ | 'attribute_changed'
194
+ | 'text_changed'
195
+ | 'source_changed'
196
+ | 'full_rebuild'
197
+
198
+ export interface DocumentChangeEvent {
199
+ kind: DocumentChangeKind
200
+ nodeId: NodeId
201
+ /** The affected node (after the change) */
202
+ node: DocumentNode | null
203
+ /** Previous version of the node (before the change) */
204
+ previousNode: DocumentNode | null
205
+ /** Additional context data */
206
+ metadata?: Record<string, unknown>
207
+ }
208
+
209
+ // ─── Helpers ───────────────────────────────────────────────────
210
+
211
+ export function createDocumentNode(
212
+ kind: NodeKind,
213
+ text: string = '',
214
+ attributes: NodeAttributes = {},
215
+ children: DocumentNode[] = [],
216
+ ): DocumentNode {
217
+ return {
218
+ id: createNodeId(),
219
+ version: 1,
220
+ kind,
221
+ text,
222
+ attributes,
223
+ metadata: {},
224
+ children,
225
+ diagnostics: [],
226
+ sourceRange: null,
227
+ parentId: null,
228
+ isSynthetic: false,
229
+ }
230
+ }
231
+
232
+ export function cloneNode(node: DocumentNode, deep: boolean = false): DocumentNode {
233
+ return {
234
+ ...node,
235
+ id: node.id,
236
+ version: node.version + 1,
237
+ children: deep ? node.children.map(c => cloneNode(c, true)) : [...node.children],
238
+ diagnostics: [...node.diagnostics],
239
+ }
240
+ }
241
+
242
+ /** Get the text content of a node and all its descendants */
243
+ export function getNodeText(node: DocumentNode): string {
244
+ if (node.children.length === 0) return node.text
245
+ return node.children.map(getNodeText).join('')
246
+ }
247
+
248
+ /** Walk all descendants in pre-order */
249
+ export function walkPreOrder(
250
+ node: DocumentNode,
251
+ visitor: (n: DocumentNode, depth: number) => void | 'skip',
252
+ depth: number = 0,
253
+ ): void {
254
+ const result = visitor(node, depth)
255
+ if (result !== 'skip') {
256
+ for (const child of node.children) {
257
+ walkPreOrder(child, visitor, depth + 1)
258
+ }
259
+ }
260
+ }
261
+
262
+ /** Find a node by ID */
263
+ export function findById(root: DocumentNode, id: NodeId): DocumentNode | null {
264
+ if (root.id === id) return root
265
+ for (const child of root.children) {
266
+ const found = findById(child, id)
267
+ if (found) return found
268
+ }
269
+ return null
270
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * DocumentEngine — Diagnostic Types
3
+ *
4
+ * Diagnostics are messages attached to specific nodes or ranges
5
+ * in the document tree. Inspired by VSCode's diagnostic model.
6
+ */
7
+
8
+ import type { Range } from './tokens'
9
+ import type { NodeId, NodeKind } from './core'
10
+
11
+ // ─── Severity ──────────────────────────────────────────────────
12
+
13
+ export type DiagnosticSeverity = 'hint' | 'info' | 'warning' | 'error'
14
+
15
+ export const DIAGNOSTIC_SEVERITY: Record<DiagnosticSeverity, number> = {
16
+ hint: 0,
17
+ info: 1,
18
+ warning: 2,
19
+ error: 3,
20
+ }
21
+
22
+ // ─── Tags ──────────────────────────────────────────────────────
23
+
24
+ export type DiagnosticTag =
25
+ | 'unnecessary'
26
+ | 'deprecated'
27
+ | 'unused'
28
+ | 'redundant'
29
+
30
+ // ─── Diagnostic ────────────────────────────────────────────────
31
+
32
+ export interface Diagnostic {
33
+ // There is deliberately no `id` field. Each diagnostic used to mint a
34
+ // crypto.randomUUID() that no reader in the engine or the app ever
35
+ // consumed — measured at 14% of analyze() on error-heavy documents. A
36
+ // diagnostic is identified by what it says and where: (code, range, nodeId).
37
+ /** Machine-readable code (e.g. 'invalid-color', 'missing-close-tag') */
38
+ code: string
39
+ /** Human-readable message */
40
+ message: string
41
+ /** Severity level */
42
+ severity: DiagnosticSeverity
43
+ /** Optional tags for additional categorization */
44
+ tags: DiagnosticTag[]
45
+ /** Source range in the original text */
46
+ range: Range | null
47
+ /** Node ID this diagnostic is attached to */
48
+ nodeId: NodeId | null
49
+ /** Node kind for context */
50
+ nodeKind: NodeKind | null
51
+ /** Source of this diagnostic (built-in, plugin name, etc.) */
52
+ source: string
53
+ /** Optional related information */
54
+ related?: DiagnosticRelatedInfo[]
55
+ /** Optional fix suggestions */
56
+ fixes?: DiagnosticFix[]
57
+ }
58
+
59
+ export interface DiagnosticRelatedInfo {
60
+ message: string
61
+ range: Range | null
62
+ nodeId: NodeId | null
63
+ }
64
+
65
+ export interface DiagnosticFix {
66
+ description: string
67
+ /** Whether this fix is automatic or requires user confirmation */
68
+ isAutomatic: boolean
69
+ /** The operations to apply as a fix */
70
+ operations: FixOperation[]
71
+ }
72
+
73
+ export type FixOperation =
74
+ | { kind: 'replace_text'; range: Range; newText: string }
75
+ | { kind: 'insert_text'; position: number; text: string }
76
+ | { kind: 'delete_range'; range: Range }
77
+ | { kind: 'wrap_in_tag'; tagName: string; range: Range }
78
+
79
+ // ─── Diagnostic Collection ─────────────────────────────────────
80
+
81
+ export interface DiagnosticCollection {
82
+ /** All diagnostics */
83
+ items: Diagnostic[]
84
+ /** Count by severity */
85
+ errorCount: number
86
+ warningCount: number
87
+ infoCount: number
88
+ hintCount: number
89
+ /** Quick access: only errors */
90
+ errors: Diagnostic[]
91
+ /** Quick access: only warnings */
92
+ warnings: Diagnostic[]
93
+ }
94
+
95
+ export function createDiagnosticCollection(): DiagnosticCollection {
96
+ return {
97
+ items: [],
98
+ errorCount: 0,
99
+ warningCount: 0,
100
+ infoCount: 0,
101
+ hintCount: 0,
102
+ errors: [],
103
+ warnings: [],
104
+ }
105
+ }
106
+
107
+ export function createDiagnostic(
108
+ code: string,
109
+ message: string,
110
+ severity: DiagnosticSeverity,
111
+ options?: {
112
+ nodeId?: NodeId | null
113
+ nodeKind?: NodeKind | null
114
+ range?: Range | null
115
+ tags?: DiagnosticTag[]
116
+ source?: string
117
+ related?: DiagnosticRelatedInfo[]
118
+ fixes?: DiagnosticFix[]
119
+ },
120
+ ): Diagnostic {
121
+ return {
122
+ code,
123
+ message,
124
+ severity,
125
+ tags: options?.tags ?? [],
126
+ range: options?.range ?? null,
127
+ nodeId: options?.nodeId ?? null,
128
+ nodeKind: options?.nodeKind ?? null,
129
+ source: options?.source ?? 'document-engine',
130
+ related: options?.related,
131
+ fixes: options?.fixes,
132
+ }
133
+ }
134
+
135
+ export function addDiagnostic(
136
+ collection: DiagnosticCollection,
137
+ diagnostic: Diagnostic,
138
+ ): void {
139
+ collection.items.push(diagnostic)
140
+ switch (diagnostic.severity) {
141
+ case 'error':
142
+ collection.errorCount++
143
+ collection.errors.push(diagnostic)
144
+ break
145
+ case 'warning':
146
+ collection.warningCount++
147
+ collection.warnings.push(diagnostic)
148
+ break
149
+ case 'info':
150
+ collection.infoCount++
151
+ break
152
+ case 'hint':
153
+ collection.hintCount++
154
+ break
155
+ }
156
+ }
@@ -0,0 +1,23 @@
1
+ export type { Token, TokenKind, TokenStream, Trivia, TriviaKind, Range } from './tokens'
2
+ export { createToken, createTrivia, range, rangeLength, rangeContains, ArrayTokenStream } from './tokens'
3
+ export type {
4
+ DocumentNode, NodeId, NodeKind, NodeAttributes, NodeMetadata,
5
+ DocumentSnapshot, DocumentChangeEvent, DocumentChangeKind, SourceRange,
6
+ } from './core'
7
+ export { createNodeId, createDocumentNode, cloneNode, getNodeText, walkPreOrder, findById } from './core'
8
+ export type {
9
+ Diagnostic, DiagnosticSeverity, DiagnosticTag, DiagnosticCollection,
10
+ DiagnosticRelatedInfo, DiagnosticFix, FixOperation,
11
+ } from './diagnostics'
12
+ export { createDiagnosticCollection, createDiagnostic, addDiagnostic, DIAGNOSTIC_SEVERITY } from './diagnostics'
13
+ export type {
14
+ Operation, OperationKind,
15
+ InsertNodeOperation, DeleteNodeOperation, ReplaceNodeOperation,
16
+ MoveNodeOperation, UpdateAttributesOperation, SetTextOperation,
17
+ InsertTextOperation, DeleteTextOperation, ReplaceTextOperation,
18
+ WrapInTagOperation, UnwrapNodeOperation, SplitNodeOperation, MergeNodesOperation,
19
+ } from './operations'
20
+ export { createOperationId, operationLabel } from './operations'
21
+ export type { Query, QuerySelector, QueryCombinator, QueryStep, QueryMatch, QueryResult } from './queries'
22
+ export { query } from './queries'
23
+ export type { SymbolInfo, SymbolKind, Reference, SymbolTableData, SymbolSearchResult } from './symbols'
@@ -0,0 +1,180 @@
1
+ /**
2
+ * DocumentEngine — Operation & Transaction Types
3
+ *
4
+ * Operations are the atomic units of change in the document.
5
+ * Every modification goes through operations, never direct mutation.
6
+ *
7
+ * Inspired by ProseMirror and Roslyn.
8
+ */
9
+
10
+ import type { NodeId, NodeKind, NodeAttributes } from './core'
11
+ import type { Range } from './tokens'
12
+ import type { RedNode } from '../Syntax/RedNode'
13
+
14
+ // ─── Operation ─────────────────────────────────────────────────
15
+
16
+ export type OperationKind =
17
+ | 'insert_node'
18
+ | 'delete_node'
19
+ | 'replace_node'
20
+ | 'move_node'
21
+ | 'update_attributes'
22
+ | 'set_text'
23
+ | 'insert_text'
24
+ | 'delete_text'
25
+ | 'replace_text'
26
+ | 'wrap_in_tag'
27
+ | 'unwrap_node'
28
+ | 'split_node'
29
+ | 'merge_nodes'
30
+
31
+ export interface BaseOperation {
32
+ kind: OperationKind
33
+ /** Stable ID for the operation (for undo/redo pairing) */
34
+ id: string
35
+ /** Whether this operation can be undone */
36
+ undoable: boolean
37
+ /** Timestamp */
38
+ timestamp: number
39
+ }
40
+
41
+ export interface InsertNodeOperation extends BaseOperation {
42
+ kind: 'insert_node'
43
+ parentId: NodeId
44
+ index: number
45
+ node: RedNode
46
+ }
47
+
48
+ export interface DeleteNodeOperation extends BaseOperation {
49
+ kind: 'delete_node'
50
+ nodeId: NodeId
51
+ /** The deleted node (stored for undo) */
52
+ node: RedNode
53
+ parentId: NodeId
54
+ index: number
55
+ }
56
+
57
+ export interface ReplaceNodeOperation extends BaseOperation {
58
+ kind: 'replace_node'
59
+ nodeId: NodeId
60
+ newNode: RedNode
61
+ oldNode: RedNode
62
+ }
63
+
64
+ export interface MoveNodeOperation extends BaseOperation {
65
+ kind: 'move_node'
66
+ nodeId: NodeId
67
+ fromParentId: NodeId
68
+ fromIndex: number
69
+ toParentId: NodeId
70
+ toIndex: number
71
+ }
72
+
73
+ export interface UpdateAttributesOperation extends BaseOperation {
74
+ kind: 'update_attributes'
75
+ nodeId: NodeId
76
+ newAttributes: NodeAttributes
77
+ oldAttributes: NodeAttributes
78
+ }
79
+
80
+ export interface SetTextOperation extends BaseOperation {
81
+ kind: 'set_text'
82
+ nodeId: NodeId
83
+ newText: string
84
+ oldText: string
85
+ }
86
+
87
+ export interface InsertTextOperation extends BaseOperation {
88
+ kind: 'insert_text'
89
+ nodeId: NodeId
90
+ position: number
91
+ text: string
92
+ }
93
+
94
+ export interface DeleteTextOperation extends BaseOperation {
95
+ kind: 'delete_text'
96
+ nodeId: NodeId
97
+ position: number
98
+ length: number
99
+ text: string
100
+ }
101
+
102
+ export interface ReplaceTextOperation extends BaseOperation {
103
+ kind: 'replace_text'
104
+ nodeId: NodeId
105
+ position: number
106
+ length: number
107
+ newText: string
108
+ oldText: string
109
+ }
110
+
111
+ export interface WrapInTagOperation extends BaseOperation {
112
+ kind: 'wrap_in_tag'
113
+ nodeId: NodeId
114
+ tagName: string
115
+ attributes: NodeAttributes
116
+ }
117
+
118
+ export interface UnwrapNodeOperation extends BaseOperation {
119
+ kind: 'unwrap_node'
120
+ nodeId: NodeId
121
+ parentId: NodeId
122
+ children: RedNode[]
123
+ }
124
+
125
+ export interface SplitNodeOperation extends BaseOperation {
126
+ kind: 'split_node'
127
+ nodeId: NodeId
128
+ position: number
129
+ leftNode: RedNode
130
+ rightNode: RedNode
131
+ }
132
+
133
+ export interface MergeNodesOperation extends BaseOperation {
134
+ kind: 'merge_nodes'
135
+ leftNodeId: NodeId
136
+ rightNodeId: NodeId
137
+ mergedNode: RedNode
138
+ }
139
+
140
+ export type Operation =
141
+ | InsertNodeOperation
142
+ | DeleteNodeOperation
143
+ | ReplaceNodeOperation
144
+ | MoveNodeOperation
145
+ | UpdateAttributesOperation
146
+ | SetTextOperation
147
+ | InsertTextOperation
148
+ | DeleteTextOperation
149
+ | ReplaceTextOperation
150
+ | WrapInTagOperation
151
+ | UnwrapNodeOperation
152
+ | SplitNodeOperation
153
+ | MergeNodesOperation
154
+
155
+ // ─── Operation Helpers ─────────────────────────────────────────
156
+
157
+ let opCounter = 0
158
+
159
+ export function createOperationId(): string {
160
+ return `op-${Date.now()}-${++opCounter}`
161
+ }
162
+
163
+ export function operationLabel(op: Operation): string {
164
+ const labels: Record<OperationKind, string> = {
165
+ insert_node: 'Insert Node',
166
+ delete_node: 'Delete Node',
167
+ replace_node: 'Replace Node',
168
+ move_node: 'Move Node',
169
+ update_attributes: 'Update Attributes',
170
+ set_text: 'Set Text',
171
+ insert_text: 'Insert Text',
172
+ delete_text: 'Delete Text',
173
+ replace_text: 'Replace Text',
174
+ wrap_in_tag: 'Wrap in Tag',
175
+ unwrap_node: 'Unwrap Node',
176
+ split_node: 'Split Node',
177
+ merge_nodes: 'Merge Nodes',
178
+ }
179
+ return labels[op.kind]
180
+ }