@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.
- package/LICENSE +119 -0
- package/README.md +45 -0
- package/dist/Visuals/lyne.css +1027 -0
- package/dist/Visuals/osu.css +257 -0
- package/dist/index.d.mts +4563 -0
- package/dist/index.d.ts +4563 -0
- package/dist/index.js +11291 -0
- package/dist/index.mjs +11197 -0
- package/package.json +51 -0
- package/src/Analysis/Contracts/AnalysisReport.ts +26 -0
- package/src/Analysis/Contracts/Contribution.ts +76 -0
- package/src/Analysis/Contracts/Pass.ts +76 -0
- package/src/Analysis/Contracts/PipelineContext.ts +48 -0
- package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +292 -0
- package/src/Analysis/Passes/Analysis/MergeableColorAnalyzer.ts +112 -0
- package/src/Analysis/Passes/Analysis/RainbowAnalyzer.ts +233 -0
- package/src/Analysis/Passes/Analysis/WaveAnalyzer.ts +211 -0
- package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +135 -0
- package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +84 -0
- package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +99 -0
- package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +119 -0
- package/src/Analysis/Passes/Decision/DefaultDecision.ts +139 -0
- package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +179 -0
- package/src/Analysis/Passes/Transform/CollapseGradientTransform.ts +176 -0
- package/src/Analysis/Passes/Transform/MergeColorsTransform.ts +126 -0
- package/src/Analysis/Passes/Transform/RainbowCollapseTransform.ts +83 -0
- package/src/Analysis/Passes/Transform/WaveCollapseTransform.ts +88 -0
- package/src/Analysis/Passes/Utility/CharacterCountAnalyzer.ts +45 -0
- package/src/Analysis/Pipeline/Pipeline.ts +133 -0
- package/src/Analysis/Pipeline/PipelineBuilder.ts +55 -0
- package/src/Analysis/Pipeline/PipelineStage.ts +19 -0
- package/src/Analysis/Utils/color-utils.ts +132 -0
- package/src/Analysis/__tests__/Integration.test.ts +162 -0
- package/src/Analysis/__tests__/Pipeline.test.ts +133 -0
- package/src/Analysis/index.ts +52 -0
- package/src/BBCode/BBCodeDocumentModel.ts +175 -0
- package/src/BBCode/BBCodeToGreenNode.ts +755 -0
- package/src/BBCode/Parser.ts +384 -0
- package/src/BBCode/index.ts +12 -0
- package/src/Collab/positions.ts +91 -0
- package/src/Commands/Command.ts +44 -0
- package/src/Commands/CommandRegistry.ts +78 -0
- package/src/Commands/DeleteNode.ts +20 -0
- package/src/Commands/InsertText.ts +21 -0
- package/src/Commands/SplitMerge.ts +28 -0
- package/src/Commands/WrapInTag.ts +21 -0
- package/src/Commands/index.ts +6 -0
- package/src/Diff/TreeDiffer.ts +264 -0
- package/src/Diff/__tests__/TreeDiffer.test.ts +65 -0
- package/src/Diff/index.ts +2 -0
- package/src/Events/EventBus.ts +160 -0
- package/src/Events/index.ts +2 -0
- package/src/Formatter/Formatter.ts +54 -0
- package/src/Formatter/index.ts +2 -0
- package/src/HTML/HTMLDocumentModel.ts +35 -0
- package/src/HTML/HTMLToGreenNode.ts +290 -0
- package/src/Incremental/ChangeTracker.ts +105 -0
- package/src/Incremental/IncrementalParser.ts +591 -0
- package/src/Incremental/__tests__/IncrementalParser.test.ts +164 -0
- package/src/Incremental/index.ts +4 -0
- package/src/Lexer/BBCodeLexer.ts +382 -0
- package/src/Lexer/Lexer.ts +181 -0
- package/src/Lexer/index.ts +10 -0
- package/src/Linter/Linter.ts +193 -0
- package/src/Linter/index.ts +2 -0
- package/src/Markdown/MarkdownAST.ts +112 -0
- package/src/Markdown/MarkdownDocumentModel.ts +55 -0
- package/src/Markdown/MarkdownLexer.ts +203 -0
- package/src/Markdown/MarkdownParser.ts +455 -0
- package/src/Markdown/MarkdownToGreenNode.ts +153 -0
- package/src/Model/DocumentModel.ts +694 -0
- package/src/Model/NodeFactory.ts +117 -0
- package/src/Model/TagRegistry.ts +495 -0
- package/src/Model/index.ts +5 -0
- package/src/Plugins/PluginAPI.ts +119 -0
- package/src/Plugins/PluginRegistry.ts +132 -0
- package/src/Plugins/index.ts +3 -0
- package/src/Queries/QueryEngine.ts +152 -0
- package/src/Queries/index.ts +1 -0
- package/src/RenderPipeline/RenderPipeline.ts +125 -0
- package/src/RenderPipeline/RenderTree.ts +134 -0
- package/src/RenderPipeline/index.ts +4 -0
- package/src/Semantic/SemanticAnalyzer.ts +506 -0
- package/src/Semantic/index.ts +2 -0
- package/src/Symbols/SymbolTable.ts +124 -0
- package/src/Symbols/index.ts +1 -0
- package/src/Syntax/GreenNode.ts +324 -0
- package/src/Syntax/GreenNodePool.ts +269 -0
- package/src/Syntax/NodeMatcher.ts +370 -0
- package/src/Syntax/RedNode.ts +569 -0
- package/src/Syntax/RedNodeStore.ts +184 -0
- package/src/Syntax/TreeBuilder.ts +214 -0
- package/src/Syntax/__tests__/GreenNode.test.ts +33 -0
- package/src/Syntax/__tests__/RedNode.test.ts +81 -0
- package/src/Syntax/__tests__/RedNodeStore.test.ts +104 -0
- package/src/Syntax/greenEdit.ts +110 -0
- package/src/Syntax/hash.ts +30 -0
- package/src/Syntax/index.ts +12 -0
- package/src/Syntax/partition.ts +161 -0
- package/src/Syntax/preserveNodeIds.ts +201 -0
- package/src/Tests/ASTOptimizerIdempotence.test.ts +77 -0
- package/src/Tests/BlockPatcher.test.ts +437 -0
- package/src/Tests/BlockPatcherWindowed.test.ts +364 -0
- package/src/Tests/BoxDrawer.test.ts +217 -0
- package/src/Tests/BoxRichTitle.test.ts +105 -0
- package/src/Tests/Chars500kBenchmark.test.ts +151 -0
- package/src/Tests/Chars500kEdits.test.ts +321 -0
- package/src/Tests/CollabPositions.test.ts +146 -0
- package/src/Tests/CompilerPathProfiling.test.ts +186 -0
- package/src/Tests/DOMMorpher.test.ts +142 -0
- package/src/Tests/DomPatchPerf.test.ts +60 -0
- package/src/Tests/EffectSegments.snapshot.json +616 -0
- package/src/Tests/EffectSegments.test.ts +68 -0
- package/src/Tests/FindNodeAtOffset.test.ts +65 -0
- package/src/Tests/Fuzzer.test.ts +166 -0
- package/src/Tests/GreenNodePool.test.ts +153 -0
- package/src/Tests/Lexer.test.ts +238 -0
- package/src/Tests/LyneMode.test.ts +187 -0
- package/src/Tests/ModelCoherence.test.ts +180 -0
- package/src/Tests/Partition.test.ts +238 -0
- package/src/Tests/PluginTags.test.ts +150 -0
- package/src/Tests/ProblematicSection.test.ts +46 -0
- package/src/Tests/ProblematicSectionHTML.test.ts +58 -0
- package/src/Tests/RedReuse.test.ts +134 -0
- package/src/Tests/ReproDelete20k.test.ts +62 -0
- package/src/Tests/SemanticValidators.test.ts +136 -0
- package/src/Tests/StableNodeIds.test.ts +210 -0
- package/src/Tests/StudioColorBloat.test.ts +25 -0
- package/src/Tests/StudioDebugText.test.ts +27 -0
- package/src/Tests/StudioTrailingChar.test.ts +25 -0
- package/src/Tests/StudioValidText.test.ts +25 -0
- package/src/Tests/UrlImgBug.test.ts +23 -0
- package/src/Tests/VisualBuilderFidelity.test.ts +105 -0
- package/src/Tests/referenceDocument.ts +119 -0
- package/src/Transactions/Transaction.ts +176 -0
- package/src/Transactions/UndoManager.ts +111 -0
- package/src/Transactions/index.ts +3 -0
- package/src/Transformers/ASTOptimizer.ts +315 -0
- package/src/Transformers/GradientTransformer.ts +143 -0
- package/src/Transformers/GrowTransformer.ts +115 -0
- package/src/Transformers/RainbowTransformer.ts +121 -0
- package/src/Transformers/SineWaveTransformer.ts +130 -0
- package/src/Transformers/Transformer.ts +22 -0
- package/src/Types/core.ts +270 -0
- package/src/Types/diagnostics.ts +156 -0
- package/src/Types/index.ts +23 -0
- package/src/Types/operations.ts +180 -0
- package/src/Types/queries.ts +121 -0
- package/src/Types/symbols.ts +69 -0
- package/src/Types/tokens.ts +186 -0
- package/src/Utils/BBCodeGenerator.ts +126 -0
- package/src/Utils/ColorMath.ts +276 -0
- package/src/Utils/color.ts +112 -0
- package/src/Utils/dom-to-svg.test.ts +86 -0
- package/src/Utils/dom-to-svg.ts +615 -0
- package/src/Utils/treeTransformers.ts +717 -0
- package/src/Visitors/BBBlocksExporter.ts +69 -0
- package/src/Visitors/BBCodeExporter.ts +318 -0
- package/src/Visitors/BlockPatcher.ts +963 -0
- package/src/Visitors/DOMMorpher.ts +134 -0
- package/src/Visitors/HTMLRenderer.ts +1077 -0
- package/src/Visitors/JSONExporter.ts +66 -0
- package/src/Visitors/MarkdownExporter.ts +99 -0
- package/src/Visitors/SVGRenderer.ts +35 -0
- package/src/Visitors/TiptapExporter.ts +145 -0
- package/src/Visitors/Visitor.ts +48 -0
- package/src/Visitors/index.ts +9 -0
- package/src/Visuals/BoxDrawer.ts +175 -0
- package/src/Visuals/index.ts +42 -0
- package/src/Visuals/lyne.css +1027 -0
- package/src/Visuals/osu.css +257 -0
- 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
|
+
}
|