-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy pathlineOffsetMap.ts
143 lines (125 loc) · 6.13 KB
/
lineOffsetMap.ts
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
import { compare, compareNumbers } from "./core";
import { SourceFile } from "./nodes";
import { RegionMap } from "./regionMap";
import { Position, Range } from "./types";
/* @internal */
export interface SourceLine {
line: number;
file?: string;
}
/* @internal */
export interface LineOffset {
generatedLine: number;
sourceLine: SourceLine | "default";
}
function compareSourceLines(a: SourceLine | "default", b: SourceLine | "default") {
if (a === "default") return b === "default" ? 0 : -1;
if (b === "default") return 1;
return compare(a.file, b.file)
|| compareNumbers(a.line, b.line);
}
/* @internal */
export function compareLineOffsets(a: LineOffset, b: LineOffset): number {
return compareNumbers(a.generatedLine, b.generatedLine)
|| compareSourceLines(a.sourceLine, b.sourceLine);
}
function equateSourceLines(a: SourceLine | "default", b: SourceLine | "default") {
if (a === "default") return b === "default";
if (b === "default") return false;
return a.line === b.line
&& a.file === b.file;
}
/* @internal */
export function equateLineOffsets(a: LineOffset, b: LineOffset): boolean {
return a.generatedLine === b.generatedLine
&& equateSourceLines(a.sourceLine, b.sourceLine);
}
export class LineOffsetMap {
private generatedFilesLineOffsets: RegionMap<SourceLine | "default"> | undefined;
private sourceFilesLineOffsets: RegionMap<SourceLine | "default"> | undefined;
/* @internal */
public addLineOffset(sourceFile: SourceFile | string, line: number, sourceLine: SourceLine | "default") {
const filename = typeof sourceFile === "string" ? sourceFile : sourceFile.filename;
this.generatedFilesLineOffsets ||= new RegionMap(equateSourceLines);
this.generatedFilesLineOffsets.addRegion(sourceFile, line, sourceLine);
// add reverse mapping
this.sourceFilesLineOffsets ||= new RegionMap(equateSourceLines);
const reverseFilename = sourceLine === "default" || sourceLine.file === undefined ? filename : sourceLine.file;
const reverseLine = sourceLine === "default" ? line : sourceLine.line;
const reverseSourceLine = sourceLine === "default" ? "default" : { file: filename, line };
this.sourceFilesLineOffsets.upsertRegion(reverseFilename, reverseLine, old => {
if (reverseSourceLine === "default") return old ?? reverseSourceLine;
return reverseSourceLine;
});
}
/* @internal */
public findLineOffset(sourceFile: SourceFile | string, position: Position) {
const filename = typeof sourceFile === "string" ? sourceFile : sourceFile.filename;
return this.generatedFilesLineOffsets?.findRegion(filename, position.line);
}
/* @internal */
public findRawOffset(filename: string, position: Position) {
return this.sourceFilesLineOffsets?.findRegion(filename, position.line);
}
/* @internal */
public copyFrom(other: LineOffsetMap) {
if (other.generatedFilesLineOffsets) {
this.generatedFilesLineOffsets ||= new RegionMap(equateSourceLines);
this.generatedFilesLineOffsets.copyFrom(other.generatedFilesLineOffsets);
}
}
/**
* Gets the effective filename of a raw position within a source file, taking into account `@line` directives.
*/
public getEffectiveFilenameAtPosition(sourceFile: SourceFile | string, position: Position) {
const filename = typeof sourceFile === "string" ? sourceFile : sourceFile.filename;
const lineOffset = this.findLineOffset(filename, position);
if (lineOffset && lineOffset.value !== "default" && lineOffset.value.file !== undefined) {
return lineOffset.value.file;
}
return filename;
}
/**
* Gets the effective position of a raw position within a source file, taking into account `@line` directives.
*/
public getEffectivePosition(sourceFile: SourceFile | string, position: Position) {
const filename = typeof sourceFile === "string" ? sourceFile : sourceFile.filename;
const lineOffset = this.findLineOffset(filename, position);
if (lineOffset && lineOffset.value !== "default") {
const diff = position.line - lineOffset.line;
const sourceLine = lineOffset.value.line + diff;
return Position.create(sourceLine, position.character);
}
return position;
}
/**
* Gets the effective range of a raw range within a source file, taking into account `@line` directives.
*/
public getEffectiveRange(sourceFile: SourceFile | string, range: Range) {
const filename = typeof sourceFile === "string" ? sourceFile : sourceFile.filename;
const start = this.getEffectivePosition(filename, range.start);
const end = this.getEffectivePosition(filename, range.end);
return start !== range.start || end !== range.end ? Range.create(start, end) : range;
}
public getRawFilenameAtEffectivePosition(filename: string, position: Position) {
const lineOffset = this.findRawOffset(filename, position);
if (lineOffset && lineOffset.value !== "default" && lineOffset.value.file !== undefined) {
return lineOffset.value.file;
}
return filename;
}
public getRawPositionFromEffectivePosition(filename: string, position: Position) {
const lineOffset = this.findRawOffset(filename, position);
if (lineOffset && lineOffset.value !== "default") {
const diff = position.line - lineOffset.line;
const sourceLine = lineOffset.value.line + diff;
return Position.create(sourceLine, position.character);
}
return position;
}
public getRawRangeFromEffectiveRange(filename: string, range: Range) {
const start = this.getRawPositionFromEffectivePosition(filename, range.start);
const end = this.getRawPositionFromEffectivePosition(filename, range.end);
return start !== range.start || end !== range.end ? Range.create(start, end) : range;
}
}