-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsource-maps.ts
More file actions
151 lines (122 loc) · 4.62 KB
/
Copy pathsource-maps.ts
File metadata and controls
151 lines (122 loc) · 4.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { JsonSourceMap } from 'json-source-map';
import { default as YamlSourceMap, SourceLocation as YamlSourceLocation } from 'js-yaml-source-map';
import { FileType, InMemoryFile } from './entities.js';
import { InternalError } from '../common/errors.js';
import chalk from 'chalk';
import { JsonSourceMapAdapter } from './json/source-map.js';
import { YamlSourceMapAdapter } from './yaml/source-map.js';
type FilePath = string;
interface InContextSourceMap {
file: InMemoryFile;
sourceMap: SourceMap;
}
export class SourceMapCache {
sourceMaps: Map<FilePath, InContextSourceMap> = new Map();
// The source map key follows the following conventions: /file/path#/json/path
// If the key refers to an root object, then /file/path is allowed
// Arrays are indexed as a integer object /file/path#/0
static constructKey(filePath: string, fragment?: string) {
return `${filePath}${ fragment ? `#${fragment}` : ''}`
}
static combineKeys(key1: string, key2: string): string {
if (!key1.includes('#')) {
return `${key1}#${key2}`
}
if (key1.endsWith('/') && key2.startsWith('/')) {
return `${key1.slice(0, -1)}${key2}`
}
if (key1.endsWith('/') || key2.startsWith('/')) {
return `${key1}${key2}`
}
if (key2.endsWith('/')) {
key2 = key2.slice(0, -1)
}
return `${key1}/${key2}`
}
addSourceMap(file: InMemoryFile, sourceMap: JsonSourceMap | YamlSourceMap) {
if (file.fileType === FileType.YAML) {
this.sourceMaps.set(file.filePath, {
file,
sourceMap: new YamlSourceMapAdapter(sourceMap as YamlSourceMap, file),
})
} else {
this.sourceMaps.set(file.filePath, {
file,
sourceMap: new JsonSourceMapAdapter(sourceMap as JsonSourceMap),
})
}
}
getSourceMap(sourceMapKey: string): InContextSourceMap | null {
const [filePath] = sourceMapKey.split('#');
return this.sourceMaps.get(filePath) ?? null;
}
hasSourceMap(sourceMapKey: string): boolean {
const [filePath] = sourceMapKey.split('#');
return this.sourceMaps.has(filePath)
}
lookup(sourceMapKey: string): SourceMapPointer | null {
const sourceMap = this.getSourceMap(sourceMapKey);
if (!sourceMap) {
return null;
}
const [, jsonKey] = sourceMapKey.split('#');
return sourceMap.sourceMap.lookup(this.cleanupJsonKey(jsonKey)) ?? null;
}
getCodeSnippet(
sourceMapKey: string,
addAdditionalContextLines = true,
addLineNumbers = true,
): string | null {
const inContextSourceMap = this.getSourceMap(sourceMapKey);
if (!inContextSourceMap) {
return null;
}
const { file, sourceMap } = inContextSourceMap;
const pointer = this.lookup(sourceMapKey);
if (!pointer) {
return null;
}
const lines = file.contents.split(/\n/g);
const startLine = addAdditionalContextLines
? Math.max(pointer.value.line - 3, 0)
: pointer.value.line
const endLine = addAdditionalContextLines
? Math.min(pointer.valueEnd.line + 4, lines.length)
: pointer.valueEnd.line + 1
const maxLineNumberLength = Math.max(startLine.toString().length, endLine.toString().length);
// Format the string to look good
return chalk.black(`File: ${file.filePath}\n`)
+ lines.slice(startLine, endLine)
.map((l, idx) => {
// Some cool formatting here. First add a carat at the beginning to indicate the first non-additional line
const carat = idx + startLine === pointer.value.line ? chalk.yellow('>') : ' ';
// Add line numbers but pad them so that they are all the same width
const lineNumber = chalk.black((idx + startLine).toString().padStart(maxLineNumberLength))
// Highlight the important lines in green
const line = (idx + startLine >= pointer.value.line && idx + startLine <= pointer.valueEnd.line) ? chalk.green(l) : l
return addLineNumbers ? `${carat} ${lineNumber} ${chalk.black('|')} ${line}` : line
})
.join('\n')
}
private cleanupJsonKey(key: string): string {
if (key.endsWith('/')) {
key = key.slice(0, -1);
}
return key;
}
}
//****************************************************************************************
// Source map wrappers. These adapt different source map implementations to a single interface
//*********************************************************************************
export interface SourceMapPointer {
value: SourceLocation;
valueEnd: SourceLocation;
}
export interface SourceLocation {
line: number;
column: number;
position: number;
}
export interface SourceMap {
lookup(jsonKey: string): SourceMapPointer | null;
}