• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / roku-debug / #2026

02 Nov 2022 02:41PM UTC coverage: 56.194% (+7.0%) from 49.18%
#2026

push

TwitchBronBron
0.17.0

997 of 1898 branches covered (52.53%)

Branch coverage included in aggregate %.

2074 of 3567 relevant lines covered (58.14%)

15.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.53
/src/managers/SourceMapManager.ts
1
import * as fsExtra from 'fs-extra';
1✔
2
import type { RawSourceMap } from 'source-map';
3
import { SourceMapConsumer } from 'source-map';
1✔
4
import { standardizePath as s, fileUtils } from '../FileUtils';
1✔
5
import * as path from 'path';
1✔
6
import type { SourceLocation } from './LocationManager';
7
import { logger } from '../logging';
1✔
8

9
/**
10
 * Unifies access to source files across the whole project
11
 */
12
export class SourceMapManager {
1✔
13

14
    private logger = logger.createLogger(`[${SourceMapManager.name}]`);
107✔
15
    /**
16
    * Store all paths in lower case since Roku is case-insensitive.
17
    * If the file existed, but something failed during parsing, this will be set to null.
18
    * So take that into consideration when deciding to use falsey checking
19
    */
20
    private cache = {} as Record<string, RawSourceMap | null>;
107✔
21

22
    /**
23
     * Does a source map exist at the specified path?
24
     * Checks the local cache first to prevent hitting the file system,
25
     * then falls back to the file system if not in the cache
26
     */
27
    public sourceMapExists(sourceMapPath: string) {
28
        let key = s`${sourceMapPath.toLowerCase()}`;
5✔
29
        let map = this.cache[key];
5✔
30
        if (map !== undefined && map !== null) {
5✔
31
            return true;
1✔
32
        }
33
        let existsOnDisk = fsExtra.pathExistsSync(sourceMapPath);
4✔
34
        return existsOnDisk;
4✔
35
    }
36

37
    /**
38
     * Get a parsed source map, with all of its paths already resolved
39
     */
40
    public async getSourceMap(sourceMapPath: string) {
41
        let key = s`${sourceMapPath.toLowerCase()}`;
17✔
42
        if (this.cache[key] === undefined) {
17✔
43
            if (await fsExtra.pathExists(sourceMapPath)) {
9!
44
                try {
9✔
45
                    let contents = (await fsExtra.readFile(sourceMapPath)).toString();
9✔
46
                    this.set(sourceMapPath, contents);
9✔
47
                } catch (e) {
48
                    this.logger.error(`Error loading or parsing source map for '${sourceMapPath}'`, e);
×
49
                }
50
            }
51
        }
52
        return this.cache[key];
17✔
53
    }
54

55
    /**
56
     * Update the in-memory cache for a specific source map,
57
     * and resolve the sources list to absolute paths
58
     */
59
    public set(sourceMapPath: string, sourceMap: string) {
60
        let key = s`${sourceMapPath.toLowerCase()}`;
19✔
61
        try {
19✔
62
            let parsedSourceMap = JSON.parse(sourceMap) as RawSourceMap;
19✔
63
            //remove the file from cache
64
            delete this.cache[key];
19✔
65
            //standardize the source map paths
66
            parsedSourceMap.sources = parsedSourceMap.sources.map(source => fileUtils.standardizePath(
19✔
67
                path.resolve(
68
                    //use the map's sourceRoot, or the map's folder path (to support relative paths)
69
                    parsedSourceMap.sourceRoot || path.dirname(sourceMapPath),
38✔
70
                    source
71
                )
72
            ));
73
            this.cache[key] = parsedSourceMap;
19✔
74
        } catch (e) {
75
            this.cache[key] = null;
×
76
            throw e;
×
77
        }
78
    }
79

80
    /**
81
     * Get the source location of a position using a source map. If no source map is found, undefined is returned
82
     * @param filePath - the absolute path to the file
83
     * @param currentLineNumber - the 1-based line number of the current location.
84
     * @param currentColumnIndex - the 0-based column number of the current location.
85
     */
86
    public async getOriginalLocation(filePath: string, currentLineNumber: number, currentColumnIndex = 0): Promise<SourceLocation> {
2✔
87
        //look for a source map for this file
88
        let sourceMapPath = `${filePath}.map`;
23✔
89

90
        //if we have a source map, use it
91
        if (await fsExtra.pathExists(sourceMapPath)) {
23✔
92
            let parsedSourceMap = await this.getSourceMap(sourceMapPath);
10✔
93
            if (parsedSourceMap) {
10!
94
                let position = await SourceMapConsumer.with(parsedSourceMap, null, (consumer) => {
10✔
95
                    return consumer.originalPositionFor({
10✔
96
                        line: currentLineNumber,
97
                        column: currentColumnIndex,
98
                        bias: SourceMapConsumer.LEAST_UPPER_BOUND
99
                    });
100
                });
101
                if (position?.source) {
10!
102
                    return {
10✔
103
                        columnIndex: position.column,
104
                        lineNumber: position.line,
105
                        filePath: position.source
106
                    };
107
                }
108
                //if the sourcemap didn't find a valid mapped location,
109
                //try to fallback to the first source referenced in the map
110
                if (parsedSourceMap.sources?.[0]) {
×
111
                    return {
×
112
                        columnIndex: currentColumnIndex,
113
                        lineNumber: currentLineNumber,
114
                        filePath: parsedSourceMap.sources[0]
115
                    };
116
                } else {
117
                    return undefined;
×
118
                }
119
            }
120
        }
121
    }
122

123
    /**
124
     * Given a source location, find the generated location using source maps
125
     */
126
    public async getGeneratedLocations(sourceMapPaths: string[], sourceLocation: SourceLocation) {
127
        let sourcePath = fileUtils.standardizePath(sourceLocation.filePath);
48✔
128
        let locations = [] as SourceLocation[];
48✔
129

130
        //search through every source map async
131
        await Promise.all(sourceMapPaths.map(async (sourceMapPath) => {
48✔
132
            try {
4✔
133
                sourceMapPath = fileUtils.standardizePath(sourceMapPath);
4✔
134
                let parsedSourceMap = await this.getSourceMap(sourceMapPath);
4✔
135

136
                //if the source path was found in the sourceMap, convert the source location into a target location
137
                if (parsedSourceMap?.sources.includes(sourcePath)) {
4!
138
                    let position = await SourceMapConsumer.with(parsedSourceMap, null, (consumer) => {
4✔
139
                        return consumer.generatedPositionFor({
4✔
140
                            line: sourceLocation.lineNumber,
141
                            column: sourceLocation.columnIndex,
142
                            source: fileUtils.standardizePath(sourceLocation.filePath),
143
                            //snap to the NEXT item if the current position could not be found
144
                            bias: SourceMapConsumer.LEAST_UPPER_BOUND
145
                        });
146
                    });
147

148
                    if (position) {
4!
149
                        locations.push({
4✔
150
                            lineNumber: position.line,
151
                            columnIndex: position.column,
152
                            filePath: sourceMapPath.replace(/\.map$/g, '')
153
                        });
154
                    }
155
                }
156
            } catch (error) {
157
                this.logger.error('Error converting source location to staging location', { error });
×
158
            }
159
        }));
160
        return locations;
48✔
161
    }
162
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc