Get retrieves a source map from cache or loads it from disk. Path translation: goFilePath (.go) → dingoPath (.dingo) → .dmap/ .dmap Handles shadow builds: build/foo.go → foo.dingo (not build/foo.dingo)
(goFilePath string)
| 62 | // Path translation: goFilePath (.go) → dingoPath (.dingo) → .dmap/<relPath>.dmap |
| 63 | // Handles shadow builds: build/foo.go → foo.dingo (not build/foo.dingo) |
| 64 | func (c *SourceMapCache) Get(goFilePath string) (*dmap.Reader, error) { |
| 65 | // CRITICAL: Copy config under read lock to avoid data race with SetConfig |
| 66 | c.mu.RLock() |
| 67 | cfg := c.config |
| 68 | c.mu.RUnlock() |
| 69 | |
| 70 | // Use config-aware path translation to handle shadow builds |
| 71 | // This properly converts build/foo.go -> foo.dingo (strips build/ prefix) |
| 72 | dingoPath, err := transpiler.GoPathToDingoPath(goFilePath, cfg) |
| 73 | if err != nil { |
| 74 | // Fallback to simple suffix replacement for non-shadow builds |
| 75 | dingoPath = strings.TrimSuffix(goFilePath, ".go") + ".dingo" |
| 76 | c.logger.Debugf("[SourceMapCache] GoPathToDingoPath failed, using fallback: %v", err) |
| 77 | } |
| 78 | |
| 79 | c.logger.Debugf("[SourceMapCache] Get called: goFilePath=%s -> dingoPath=%s", goFilePath, dingoPath) |
| 80 | |
| 81 | // Calculate .dmap path in project root .dmap/ folder |
| 82 | dmapPath, err := calculateDmapPathWithConfig(dingoPath, cfg) |
| 83 | if err != nil { |
| 84 | c.logger.Debugf("[SourceMapCache] Failed to calculate dmap path: %v", err) |
| 85 | return nil, fmt.Errorf("failed to calculate dmap path: %w", err) |
| 86 | } |
| 87 | c.logger.Debugf("[SourceMapCache] Calculated dmapPath=%s", dmapPath) |
| 88 | |
| 89 | // CRITICAL FIX C3: Simplified locking (correctness over optimization) |
| 90 | // Hold write lock during entire operation to avoid memory model issues |
| 91 | c.mu.Lock() |
| 92 | defer c.mu.Unlock() |
| 93 | |
| 94 | // Check cache under write lock |
| 95 | if reader, ok := c.maps[dingoPath]; ok { |
| 96 | c.logger.Debugf("Source map cache hit: %s", dmapPath) |
| 97 | return reader, nil |
| 98 | } |
| 99 | |
| 100 | // Load binary .dmap file (still holding lock) |
| 101 | c.logger.Debugf("[SourceMapCache] Opening dmap file: %s", dmapPath) |
| 102 | reader, err := dmap.Open(dmapPath) |
| 103 | if err != nil { |
| 104 | if os.IsNotExist(err) { |
| 105 | c.logger.Debugf("[SourceMapCache] File does not exist: %s", dmapPath) |
| 106 | return nil, fmt.Errorf("source map not found: %s (transpile .dingo file first: dingo build)", dmapPath) |
| 107 | } |
| 108 | c.logger.Debugf("[SourceMapCache] Failed to open dmap: %v", err) |
| 109 | return nil, fmt.Errorf("failed to read source map %s: %w", dmapPath, err) |
| 110 | } |
| 111 | c.logger.Debugf("[SourceMapCache] Successfully opened dmap with %d line mappings", reader.LineMappingCount()) |
| 112 | |
| 113 | // Store with consistent key (dingoPath) |
| 114 | c.maps[dingoPath] = reader |
| 115 | c.logger.Infof("Source map loaded: %s (%d line mappings)", dmapPath, reader.LineMappingCount()) |
| 116 | |
| 117 | return reader, nil |
| 118 | } |
| 119 | |
| 120 | // Invalidate removes a source map from cache (called after file changes) |
| 121 | func (c *SourceMapCache) Invalidate(goFilePath string) { |