• 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

92.78
/src/managers/LocationManager.ts
1
import * as fsExtra from 'fs-extra';
1✔
2
import * as path from 'path';
1✔
3
import { standardizePath as s, fileUtils } from '../FileUtils';
1✔
4
import type { SourceMapManager } from './SourceMapManager';
5
import * as glob from 'glob';
1✔
6

7
/**
8
 * Find original source locations based on debugger/staging locations.
9
 */
10
export class LocationManager {
1✔
11
    constructor(
12
        private sourceMapManager: SourceMapManager
72✔
13
    ) {
14

15
    }
16
    /**
17
     * Given a debugger/staging location, convert that to a source location
18
     */
19
    public async getSourceLocation(options: GetSourceLocationOptions): Promise<SourceLocation> {
20
        let rootDir = s`${options.rootDir}`;
19✔
21
        let stagingFolderPath = s`${options.stagingFolderPath}`;
19✔
22
        let currentFilePath = s`${options.stagingFilePath}`;
19✔
23
        let sourceDirs = options.sourceDirs ? options.sourceDirs.map(x => s`${x}`) : [];
19✔
24
        //throw out any sourceDirs pointing the rootDir
25
        sourceDirs = sourceDirs.filter(x => x !== rootDir);
19✔
26

27
        //look for a sourcemap for this file (if source maps are enabled)
28
        if (options?.enableSourceMaps !== false) {
19!
29
            let sourceLocation = await this.sourceMapManager.getOriginalLocation(
19✔
30
                currentFilePath,
31
                options.lineNumber,
32
                options.columnIndex
33
            );
34
            //follow the source map trail backwards another level
35
            if (
19✔
36
                //if the sourcemap points to a new location on disk
37
                sourceLocation?.filePath &&
87✔
38
                //prevent circular dependencies by stopping if we have already seen this path before
39
                !options._sourceChain?.includes(sourceLocation.filePath) &&
18✔
40
                //there is a source map for that new location
41
                this.sourceMapManager.sourceMapExists(`${sourceLocation.filePath}.map`)
42
            ) {
43
                let nextLevelSourceLocation = await this.getSourceLocation({
3✔
44
                    ...options,
45
                    //push current file to the source chain to prevent circular dependencies
46
                    _sourceChain: [
47
                        ...options._sourceChain ?? [],
9✔
48
                        currentFilePath
49
                    ],
50
                    columnIndex: sourceLocation.columnIndex,
51
                    lineNumber: sourceLocation.lineNumber,
52
                    stagingFilePath: sourceLocation.filePath
53
                });
54
                sourceLocation = nextLevelSourceLocation ?? sourceLocation;
3!
55
            }
56

57
            if (sourceLocation) {
19✔
58
                return sourceLocation;
6✔
59
            }
60
        }
61

62
        //if we have sourceDirs, rootDir is the project's OUTPUT folder, so skip looking for files there, and
63
        //instead walk backwards through sourceDirs until we find the file we want
64
        if (sourceDirs.length > 0) {
13✔
65
            let relativeFilePath = fileUtils.getRelativePath(stagingFolderPath, currentFilePath);
3✔
66
            let sourceDirsFilePath = await fileUtils.findFirstRelativeFile(relativeFilePath, sourceDirs);
3✔
67
            //if we found a file in one of the sourceDirs, use that
68
            if (sourceDirsFilePath) {
3!
69
                return {
3✔
70
                    filePath: sourceDirsFilePath,
71
                    lineNumber: options.lineNumber,
72
                    columnIndex: options.columnIndex
73
                };
74
            }
75
        }
76

77
        //no sourceDirs and no sourceMap. assume direct file copy using roku-deploy.
78
        if (!options.fileMappings) {
10!
79
            throw new Error('fileMappings cannot be undefined');
×
80
        }
81
        let lowerFilePathInStaging = currentFilePath.toLowerCase();
10✔
82
        let fileEntry = options.fileMappings.find(x => {
10✔
83
            return fileUtils.standardizePath(x.dest.toLowerCase()) === lowerFilePathInStaging;
9✔
84
        });
85

86
        if (fileEntry && await fsExtra.pathExists(fileEntry.src)) {
10✔
87
            return {
6✔
88
                filePath: fileEntry.src,
89
                lineNumber: options.lineNumber,
90
                columnIndex: options.columnIndex
91
            };
92
        }
93
        return undefined;
4✔
94
    }
95

96
    /**
97
     * Given a source location, compute its locations in staging. You should call this for the main app (rootDir, rootDir+sourceDirs),
98
     * and also once for each component library.
99
     * There is a possibility of a single source location mapping to multiple staging locations (i.e. merging a function into two different files),
100
     * So this will return an array of locations.
101
     */
102
    public async getStagingLocations(
103
        sourceFilePath: string,
104
        sourceLineNumber: number,
105
        sourceColumnIndex: number,
106
        sourceDirs: string[],
107
        stagingFolderPath: string,
108
        fileMappings: Array<{ src: string; dest: string }>
109
    ): Promise<{ type: 'fileMap' | 'sourceDirs' | 'sourceMap'; locations: SourceLocation[] }> {
110

111
        sourceFilePath = s`${sourceFilePath}`;
48✔
112
        sourceDirs = sourceDirs.map(x => s`${x}`);
61✔
113
        stagingFolderPath = s`${stagingFolderPath}`;
48✔
114

115
        //look through the sourcemaps in the staging folder for any instances of this source location
116
        let locations = await this.sourceMapManager.getGeneratedLocations(
48✔
117
            glob.sync('**/*.map', {
118
                cwd: stagingFolderPath,
119
                absolute: true
120
            }),
121
            {
122
                filePath: sourceFilePath,
123
                lineNumber: sourceLineNumber,
124
                columnIndex: sourceColumnIndex
125
            }
126
        );
127

128
        if (locations.length > 0) {
48✔
129
            return {
4✔
130
                type: 'sourceMap',
131
                locations: locations
132
            };
133
        }
134

135
        //no sourcemaps were found that reference this file.
136
        //walk look through each sourceDir in order, computing the relative path for the file, and
137
        //comparing that relative path to the relative path in the staging directory
138
        //so look for a file with the same relative location in the staging folder
139

140
        //compute the relative path for this file
141
        let parentFolderPath = fileUtils.findFirstParent(sourceFilePath, sourceDirs);
44✔
142
        if (parentFolderPath) {
44✔
143
            let relativeFilePath = fileUtils.replaceCaseInsensitive(sourceFilePath, parentFolderPath, '');
21✔
144
            let stagingFilePathAbsolute = path.join(stagingFolderPath, relativeFilePath);
21✔
145
            return {
21✔
146
                type: 'sourceDirs',
147
                locations: [{
148
                    filePath: stagingFilePathAbsolute,
149
                    columnIndex: sourceColumnIndex,
150
                    lineNumber: sourceLineNumber
151
                }]
152
            };
153
        }
154

155
        //look through the files array to see if there are any mappings that reference this file.
156
        //both `src` and `dest` are assumed to already be standardized
157
        for (let fileMapping of fileMappings ?? []) {
23✔
158
            if (fileMapping.src === sourceFilePath) {
1!
159
                return {
1✔
160
                    type: 'fileMap',
161
                    locations: [{
162
                        filePath: fileMapping.dest,
163
                        columnIndex: sourceColumnIndex,
164
                        lineNumber: sourceLineNumber
165
                    }]
166
                };
167
            }
168
        }
169

170
        //return an empty array so the result is still iterable
171
        return {
22✔
172
            type: 'fileMap',
173
            locations: []
174
        };
175
    }
176

177
}
178

179
export interface GetSourceLocationOptions {
180
    /**
181
     * The absolute path to the staging folder
182
     */
183
    stagingFolderPath: string;
184

185
    /**
186
     * The absolute path to the file in the staging folder
187
     */
188
    stagingFilePath: string;
189

190
    /**
191
     * The absolute path to the root directory
192
     */
193
    rootDir: string;
194
    /**
195
     *  An array of sourceDir paths
196
     */
197
    sourceDirs?: string[];
198
    /**
199
     * The result of rokuDeploy.getFilePaths(). This is passed in so it can be cached on the outside in order to improve performance
200
     */
201
    fileMappings: { src: string; dest: string }[];
202
    /**
203
     * The debugger line number (1-based)
204
     */
205
    lineNumber: number;
206
    /**
207
     * The debugger column index (0-based)
208
     */
209
    columnIndex: number;
210
    /**
211
     * If true, then use source maps as part of the process
212
     */
213
    enableSourceMaps: boolean;
214
    /**
215
     * Used to prevent circular references. This is set by the function so do not set this value yourself
216
     */
217
    _sourceChain?: string[];
218
}
219

220
export interface SourceLocation {
221
    /**
222
     * The path to the file in the source location
223
     */
224
    filePath: string;
225
    /**
226
     * 1-based line number
227
     */
228
    lineNumber: number;
229
    /**
230
     * 0-based column index
231
     */
232
    columnIndex: number;
233
}
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