@wire-dsl/engine 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,3 +1,99 @@
1
+ /**
2
+ * SourceMap Types for Wire DSL
3
+ *
4
+ * Provides bidirectional mapping between code positions and AST nodes
5
+ * for features like code-canvas selection, visual editing, and error reporting.
6
+ */
7
+
8
+ /**
9
+ * Position in source code (1-based line, 0-based column)
10
+ */
11
+ interface Position {
12
+ line: number;
13
+ column: number;
14
+ offset?: number;
15
+ }
16
+ /**
17
+ * Range in source code
18
+ */
19
+ interface CodeRange {
20
+ start: Position;
21
+ end: Position;
22
+ }
23
+ /**
24
+ * SourceMap for a single property
25
+ * Captures ranges for precise property editing
26
+ */
27
+ interface PropertySourceMap {
28
+ name: string;
29
+ value: any;
30
+ range: CodeRange;
31
+ nameRange: CodeRange;
32
+ valueRange: CodeRange;
33
+ }
34
+ /**
35
+ * Types of nodes in Wire DSL
36
+ */
37
+ type SourceMapNodeType = 'project' | 'screen' | 'layout' | 'component' | 'component-definition' | 'cell' | 'theme' | 'mocks' | 'colors';
38
+ /**
39
+ * Main SourceMap entry - represents one node in the AST
40
+ */
41
+ interface SourceMapEntry {
42
+ nodeId: string;
43
+ type: SourceMapNodeType;
44
+ range: CodeRange;
45
+ filePath: string;
46
+ parentId: string | null;
47
+ name?: string;
48
+ layoutType?: string;
49
+ componentType?: string;
50
+ indexInParent?: number;
51
+ isUserDefined?: boolean;
52
+ keywordRange?: CodeRange;
53
+ nameRange?: CodeRange;
54
+ bodyRange?: CodeRange;
55
+ properties?: Record<string, PropertySourceMap>;
56
+ insertionPoint?: InsertionPoint;
57
+ }
58
+ /**
59
+ * Insertion point for new children (FASE 3)
60
+ */
61
+ interface InsertionPoint {
62
+ line: number;
63
+ column: number;
64
+ indentation: string;
65
+ after?: string;
66
+ }
67
+ /**
68
+ * Parse result with SourceMap
69
+ */
70
+ interface ParseResult {
71
+ ast: AST;
72
+ sourceMap: SourceMapEntry[];
73
+ errors: ParseError[];
74
+ }
75
+ /**
76
+ * Parse error with optional nodeId reference
77
+ */
78
+ interface ParseError {
79
+ message: string;
80
+ range: CodeRange;
81
+ severity: 'error' | 'warning';
82
+ nodeId?: string;
83
+ }
84
+ /**
85
+ * Captured tokens from Chevrotain parser (internal use)
86
+ * These are NOT stored in AST, only used temporarily during SourceMap building
87
+ */
88
+ interface CapturedTokens {
89
+ keyword?: any;
90
+ name?: any;
91
+ paramList?: any;
92
+ properties?: any[];
93
+ body?: any;
94
+ children?: any[];
95
+ }
96
+
1
97
  interface AST {
2
98
  type: 'project';
3
99
  name: string;
@@ -6,35 +102,81 @@ interface AST {
6
102
  colors: Record<string, string>;
7
103
  definedComponents: ASTDefinedComponent[];
8
104
  screens: ASTScreen[];
105
+ _meta?: {
106
+ nodeId: string;
107
+ };
9
108
  }
10
109
  interface ASTDefinedComponent {
11
110
  type: 'definedComponent';
12
111
  name: string;
13
112
  body: ASTLayout | ASTComponent;
113
+ _meta?: {
114
+ nodeId: string;
115
+ };
14
116
  }
15
117
  interface ASTScreen {
16
118
  type: 'screen';
17
119
  name: string;
18
120
  params: Record<string, string | number>;
19
121
  layout: ASTLayout;
122
+ _meta?: {
123
+ nodeId: string;
124
+ };
20
125
  }
21
126
  interface ASTLayout {
22
127
  type: 'layout';
23
128
  layoutType: string;
24
129
  params: Record<string, string | number>;
25
130
  children: (ASTComponent | ASTLayout | ASTCell)[];
131
+ _meta?: {
132
+ nodeId: string;
133
+ };
26
134
  }
27
135
  interface ASTCell {
28
136
  type: 'cell';
29
137
  props: Record<string, string | number>;
30
138
  children: (ASTComponent | ASTLayout)[];
139
+ _meta?: {
140
+ nodeId: string;
141
+ };
31
142
  }
32
143
  interface ASTComponent {
33
144
  type: 'component';
34
145
  componentType: string;
35
146
  props: Record<string, string | number>;
147
+ _meta?: {
148
+ nodeId: string;
149
+ };
36
150
  }
37
151
  declare function parseWireDSL(input: string): AST;
152
+ /**
153
+ * Parse Wire DSL with SourceMap generation
154
+ *
155
+ * Returns both AST and SourceMap for bidirectional code-canvas mapping
156
+ * Useful for:
157
+ * - Visual editors (Wire Studio)
158
+ * - Code navigation (click canvas → jump to code)
159
+ * - Error reporting with precise locations
160
+ * - Component inspection and manipulation
161
+ *
162
+ * @param input - Wire DSL source code
163
+ * @param filePath - Optional file path (default: "<input>") - used for stable nodeIds
164
+ * @returns ParseResult with AST, SourceMap, and errors
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * const { ast, sourceMap } = parseWireDSLWithSourceMap(code, 'screens/Main.wire');
169
+ *
170
+ * // Find node by position
171
+ * const node = sourceMap.find(e =>
172
+ * e.range.start.line === 5 && e.range.start.column === 4
173
+ * );
174
+ *
175
+ * // Access AST node
176
+ * console.log(node.astNode.type); // 'component'
177
+ * ```
178
+ */
179
+ declare function parseWireDSLWithSourceMap(input: string, filePath?: string): ParseResult;
38
180
  interface ParsedWireframe {
39
181
  name: string;
40
182
  components: ParsedComponent[];
@@ -116,6 +258,7 @@ interface IRStyle {
116
258
  }
117
259
  interface IRMeta {
118
260
  source?: string;
261
+ nodeId?: string;
119
262
  }
120
263
  declare class IRGenerator {
121
264
  private idGen;
@@ -302,11 +445,342 @@ declare class SVGRenderer {
302
445
  private extractSvgContent;
303
446
  private resolveSpacing;
304
447
  private escapeXml;
448
+ /**
449
+ * Get data-node-id attribute string for SVG elements
450
+ * Enables bidirectional selection between code and canvas
451
+ */
452
+ private getDataNodeId;
305
453
  }
306
454
  declare function renderToSVG(ir: IRContract, layout: LayoutResult, options?: SVGRenderOptions): string;
307
455
  declare function createSVGElement(tag: string, attrs: Record<string, string | number>, children?: string[]): string;
308
456
  declare function buildSVG(component: SVGComponent): string;
309
457
 
458
+ /**
459
+ * Content-based hash generation for stable NodeIds
460
+ *
461
+ * Browser-safe implementation (no crypto/node dependencies)
462
+ * Generates stable IDs that persist across parses if code hasn't changed
463
+ *
464
+ * **Uniqueness Strategy:**
465
+ * Includes `indexInParent` in the hash to ensure identical components
466
+ * in the same parent get unique IDs. This prevents nodeId collisions for:
467
+ *
468
+ * ```wire
469
+ * layout stack {
470
+ * component Button text: "Click" // index 0
471
+ * component Button text: "Click" // index 1
472
+ * component Button text: "Click" // index 2
473
+ * }
474
+ * ```
475
+ *
476
+ * Each button will have a different nodeId despite being identical.
477
+ */
478
+
479
+ /**
480
+ * Generate a stable, content-based node ID
481
+ *
482
+ * The ID is based on:
483
+ * - File path (distinguishes nodes across files)
484
+ * - Line and column (unique position in file)
485
+ * - Node type (component, layout, screen, etc.)
486
+ * - Index in parent array (distinguishes identical siblings)
487
+ * - Optional name (for named nodes like screens, defined components)
488
+ *
489
+ * Examples:
490
+ * - "node-k7m2p9-component" (Icon component at Main.wire:5:4, index 0)
491
+ * - "node-abc123-screen" (Main screen at Main.wire:2:0, index 0)
492
+ * - "node-xyz789-layout" (stack layout at Main.wire:3:2, index 0)
493
+ *
494
+ * @param type - Type of AST node
495
+ * @param filePath - Source file path
496
+ * @param line - Line number (1-based)
497
+ * @param column - Column number (0-based)
498
+ * @param indexInParent - Index in parent's child array (0-based)
499
+ * @param name - Optional name (for screens, defined components)
500
+ * @returns Stable node ID
501
+ */
502
+ declare function generateStableNodeId(type: SourceMapNodeType, filePath: string, line: number, column: number, indexInParent: number, name?: string): string;
503
+ /**
504
+ * Validate if a string looks like a valid node ID
505
+ *
506
+ * @param id - String to validate
507
+ * @returns true if it matches the node ID pattern
508
+ */
509
+ declare function isValidNodeId(id: string): boolean;
510
+ /**
511
+ * Extract node type from a node ID
512
+ *
513
+ * @param nodeId - Node ID to parse
514
+ * @returns The node type, or null if invalid
515
+ */
516
+ declare function getTypeFromNodeId(nodeId: string): SourceMapNodeType | null;
517
+
518
+ /**
519
+ * SourceMapBuilder
520
+ *
521
+ * Constructs SourceMap during AST traversal using semantic node IDs.
522
+ *
523
+ * **ID Strategy:**
524
+ * - Uses human-readable, semantic IDs: `{type}-{subtype}-{counter}`
525
+ * - Counter is per type-subtype to keep indices small
526
+ * - Examples: `component-button-0`, `layout-stack-1`, `screen-0`
527
+ *
528
+ * **Stability:**
529
+ * - IDs remain stable when editing properties ✅
530
+ * - IDs change when reordering nodes ⚠️
531
+ * - Ideal for editor UX where property editing is frequent
532
+ *
533
+ * **Generated IDs:**
534
+ * - `project` - Single project (no counter)
535
+ * - `screen-{n}` - Screens by index (0-based)
536
+ * - `component-{type}-{n}` - Components by subtype (button-0, input-0, etc.)
537
+ * - `layout-{type}-{n}` - Layouts by subtype (stack-0, grid-1, etc.)
538
+ * - `cell-{n}` - Grid cells by index
539
+ * - `define-{name}` - Component definitions by name
540
+ */
541
+
542
+ /**
543
+ * Builder for constructing SourceMap entries during parsing
544
+ * Uses semantic IDs based on type-subtype-counter (e.g., component-button-0)
545
+ */
546
+ declare class SourceMapBuilder {
547
+ private entries;
548
+ private filePath;
549
+ private sourceCode;
550
+ private parentStack;
551
+ private counters;
552
+ constructor(filePath?: string, sourceCode?: string);
553
+ /**
554
+ * Add a node to the SourceMap
555
+ * Generates semantic IDs like: project, screen-0, component-button-1, layout-stack-0
556
+ *
557
+ * @param type - Type of AST node
558
+ * @param tokens - Captured tokens from parser
559
+ * @param metadata - Optional metadata (name, layoutType, componentType)
560
+ * @returns Generated nodeId
561
+ */
562
+ addNode(type: SourceMapNodeType, tokens: CapturedTokens, metadata?: {
563
+ name?: string;
564
+ layoutType?: string;
565
+ componentType?: string;
566
+ isUserDefined?: boolean;
567
+ }): string;
568
+ /**
569
+ * Generate semantic node ID based on type and subtype
570
+ * Format: {type}-{subtype}-{counter} or {type}-{counter}
571
+ *
572
+ * Examples:
573
+ * - project → "project"
574
+ * - theme → "theme"
575
+ * - mocks → "mocks"
576
+ * - colors → "colors"
577
+ * - screen → "screen-0", "screen-1"
578
+ * - component Button → "component-button-0", "component-button-1"
579
+ * - layout stack → "layout-stack-0", "layout-stack-1"
580
+ * - cell → "cell-0", "cell-1"
581
+ * - component-definition → "define-MyButton"
582
+ */
583
+ private generateNodeId;
584
+ /**
585
+ * Add a property to an existing node in the SourceMap
586
+ * Captures precise ranges for property name and value for surgical editing
587
+ *
588
+ * @param nodeId - ID of the node that owns this property
589
+ * @param propertyName - Name of the property (e.g., "text", "direction")
590
+ * @param propertyValue - Parsed value of the property
591
+ * @param tokens - Captured tokens for the property
592
+ * @returns The PropertySourceMap entry created
593
+ */
594
+ addProperty(nodeId: string, propertyName: string, propertyValue: any, tokens: {
595
+ name?: any;
596
+ value?: any;
597
+ separator?: any;
598
+ full?: any;
599
+ }): PropertySourceMap;
600
+ /**
601
+ * Push a parent onto the stack (when entering a container node)
602
+ */
603
+ pushParent(nodeId: string): void;
604
+ /**
605
+ * Pop a parent from the stack (when exiting a container node)
606
+ */
607
+ popParent(): void;
608
+ /**
609
+ * Get the current parent nodeId (or null if at root)
610
+ */
611
+ getCurrentParent(): string | null;
612
+ /**
613
+ * Build and return the final SourceMap
614
+ */
615
+ build(): SourceMapEntry[];
616
+ /**
617
+ * Calculate insertionPoints for all container nodes
618
+ * Container nodes: project, screen, layout, cell, component-definition
619
+ */
620
+ private calculateAllInsertionPoints;
621
+ /**
622
+ * Calculate CodeRange from captured tokens
623
+ * Finds the earliest start and latest end among all tokens
624
+ */
625
+ private calculateRange;
626
+ /**
627
+ * Convert a single token to CodeRange
628
+ */
629
+ private tokenToRange;
630
+ /**
631
+ * Calculate body range from closing brace token
632
+ * Body range typically spans from opening brace to closing brace
633
+ */
634
+ private calculateBodyRange;
635
+ /**
636
+ * Extract the first real token from a CST node (earliest by offset)
637
+ * Recursively searches through children to find the token with smallest offset
638
+ */
639
+ private getFirstToken;
640
+ /**
641
+ * Extract the last real token from a CST node (latest by offset)
642
+ * Recursively searches through children to find the token with largest offset
643
+ */
644
+ private getLastToken;
645
+ /**
646
+ * Extract start position from a Chevrotain token or CST node
647
+ */
648
+ private getTokenStart;
649
+ /**
650
+ * Extract end position from a Chevrotain token or CST node
651
+ */
652
+ private getTokenEnd;
653
+ /**
654
+ * Reset the builder (for reuse)
655
+ */
656
+ reset(filePath?: string, sourceCode?: string): void;
657
+ /**
658
+ * Calculate insertion point for adding new children to a container node
659
+ *
660
+ * Strategy:
661
+ * - If node has children: insert after last child, preserve indentation
662
+ * - If node is empty: insert inside body, use parent indentation + 2 spaces
663
+ *
664
+ * @param nodeId - ID of the container node
665
+ * @returns InsertionPoint with line, column, indentation, and optional after
666
+ */
667
+ calculateInsertionPoint(nodeId: string): {
668
+ line: number;
669
+ column: number;
670
+ indentation: string;
671
+ after?: string;
672
+ } | undefined;
673
+ /**
674
+ * Extract indentation (leading whitespace) from a line
675
+ */
676
+ private extractIndentation;
677
+ }
678
+
679
+ /**
680
+ * SourceMapResolver
681
+ *
682
+ * Provides query APIs for bidirectional code↔canvas selection
683
+ *
684
+ * **Use Cases:**
685
+ * 1. Canvas → Code: Click SVG element with data-node-id → find code location
686
+ * 2. Code → Canvas: Click code position → find corresponding node
687
+ *
688
+ * **Performance:**
689
+ * - getNodeById: O(1) with Map index
690
+ * - getNodeByPosition: O(n) linear search (could be optimized with interval tree)
691
+ * - getChildren/getParent: O(1) with indexes
692
+ */
693
+
694
+ /**
695
+ * Query result for position-based lookups
696
+ */
697
+ interface PositionQueryResult extends SourceMapEntry {
698
+ depth: number;
699
+ }
700
+ /**
701
+ * SourceMap query and navigation API
702
+ */
703
+ declare class SourceMapResolver {
704
+ private nodeMap;
705
+ private childrenMap;
706
+ private positionIndex;
707
+ constructor(sourceMap: SourceMapEntry[]);
708
+ /**
709
+ * Find node by ID (Canvas → Code)
710
+ *
711
+ * @example
712
+ * // User clicks SVG element with data-node-id="component-button-0"
713
+ * const node = resolver.getNodeById("component-button-0");
714
+ * editor.revealRange(node.range); // Jump to code
715
+ */
716
+ getNodeById(nodeId: string): SourceMapEntry | null;
717
+ /**
718
+ * Find node at position (Code → Canvas)
719
+ * Returns the most specific (deepest) node containing the position
720
+ *
721
+ * @example
722
+ * // User clicks code at line 5, column 10
723
+ * const node = resolver.getNodeByPosition(5, 10);
724
+ * canvas.highlightElement(node.nodeId); // Highlight in canvas
725
+ */
726
+ getNodeByPosition(line: number, column: number): SourceMapEntry | null;
727
+ /**
728
+ * Get all child nodes of a parent
729
+ *
730
+ * @example
731
+ * const children = resolver.getChildren("layout-stack-0");
732
+ * // Returns: [component-button-0, component-input-0, ...]
733
+ */
734
+ getChildren(nodeId: string): SourceMapEntry[];
735
+ /**
736
+ * Get parent node
737
+ *
738
+ * @example
739
+ * const parent = resolver.getParent("component-button-0");
740
+ * // Returns: layout-stack-0
741
+ */
742
+ getParent(nodeId: string): SourceMapEntry | null;
743
+ /**
744
+ * Get all nodes in the SourceMap
745
+ */
746
+ getAllNodes(): SourceMapEntry[];
747
+ /**
748
+ * Get all nodes of a specific type
749
+ *
750
+ * @example
751
+ * const buttons = resolver.getNodesByType("component", "Button");
752
+ */
753
+ getNodesByType(type: SourceMapEntry['type'], subtype?: string): SourceMapEntry[];
754
+ /**
755
+ * Get siblings of a node (nodes with same parent)
756
+ */
757
+ getSiblings(nodeId: string): SourceMapEntry[];
758
+ /**
759
+ * Get path from root to node (breadcrumb)
760
+ *
761
+ * @example
762
+ * const path = resolver.getPath("component-button-0");
763
+ * // Returns: [project, screen-0, layout-stack-0, component-button-0]
764
+ */
765
+ getPath(nodeId: string): SourceMapEntry[];
766
+ /**
767
+ * Check if a position is within a node's range
768
+ */
769
+ private containsPosition;
770
+ /**
771
+ * Calculate depth of a node in the tree (0 = root)
772
+ */
773
+ private calculateDepth;
774
+ /**
775
+ * Get statistics about the SourceMap
776
+ */
777
+ getStats(): {
778
+ totalNodes: number;
779
+ byType: Record<string, number>;
780
+ maxDepth: number;
781
+ };
782
+ }
783
+
310
784
  declare const version = "0.0.1";
311
785
 
312
- export { type AST, type ASTCell, type ASTComponent, type ASTDefinedComponent, type ASTLayout, type ASTScreen, type IRComponent, type IRComponentNode, type IRContainerNode, type IRContract, IRGenerator, type IRLayout, type IRMeta, type IRMetadata, type IRNode, type IRProject, type IRScreen, type IRStyle, type IRTheme, type IRWireframe, LayoutEngine, type LayoutPosition, type LayoutResult, type ParsedComponent, type ParsedWireframe, type SVGComponent, type SVGRenderOptions, SVGRenderer, buildSVG, calculateLayout, createSVGElement, generateIR, parseWireDSL, renderToSVG, resolveGridPosition, version };
786
+ export { type AST, type ASTCell, type ASTComponent, type ASTDefinedComponent, type ASTLayout, type ASTScreen, type CapturedTokens, type CodeRange, type IRComponent, type IRComponentNode, type IRContainerNode, type IRContract, IRGenerator, type IRLayout, type IRMeta, type IRMetadata, type IRNode, type IRProject, type IRScreen, type IRStyle, type IRTheme, type IRWireframe, type InsertionPoint, LayoutEngine, type LayoutPosition, type LayoutResult, type ParseError, type ParseResult, type ParsedComponent, type ParsedWireframe, type Position, type PositionQueryResult, type PropertySourceMap, type SVGComponent, type SVGRenderOptions, SVGRenderer, SourceMapBuilder, type SourceMapEntry, type SourceMapNodeType, SourceMapResolver, buildSVG, calculateLayout, createSVGElement, generateIR, generateStableNodeId, getTypeFromNodeId, isValidNodeId, parseWireDSL, parseWireDSLWithSourceMap, renderToSVG, resolveGridPosition, version };