Skip to content

Commit

Permalink
WIP synchronous mapping
Browse files Browse the repository at this point in the history
This replaces asynchronous `source-map` with a synchronous mapping implementation using `sourcemap-codec`. According to the performance test there is no noticeable difference at file lenghts like these - `source-map` is better at really large files (thousands of lines), which is not the case for 99,9% of all Svelte files the user will edit with this. A further (according to tests not noticeable) performance improvment would be to return the decoded form of the source map from svelte2tsx (it used sourcemap-coded, too).

The mapping implementation could also serve as a reference implementation for synchronous source mapping for TS plugin (sveltejs#580)
  • Loading branch information
Simon Holthausen committed Feb 15, 2021
1 parent 319c8cf commit af0ef2d
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 20 deletions.
1 change: 1 addition & 0 deletions packages/language-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"prettier": "2.2.1",
"prettier-plugin-svelte": "~2.1.0",
"source-map": "^0.7.3",
"sourcemap-codec": "^1.4.8",
"svelte": "~3.32.1",
"svelte-preprocess": "~4.6.1",
"svelte2tsx": "*",
Expand Down
112 changes: 98 additions & 14 deletions packages/language-server/src/plugins/typescript/DocumentMapper.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,113 @@
import { decode, SourceMapMappings, SourceMapSegment } from 'sourcemap-codec';
import { Position } from 'vscode-languageserver';
import { SourceMapConsumer } from 'source-map';
import { SourceMapDocumentMapper } from '../../lib/documents';
import { DocumentMapper } from '../../lib/documents';

export class ConsumerDocumentMapper extends SourceMapDocumentMapper {
constructor(consumer: SourceMapConsumer, sourceUri: string, private nrPrependesLines: number) {
super(consumer, sourceUri);
}
export class ConsumerDocumentMapper implements DocumentMapper {
private decoded?: SourceMapMappings;

constructor(
private rawMap: string,
private sourceUri: string,
private nrPrependesLines: number
) {}

getOriginalPosition(generatedPosition: Position): Position {
return super.getOriginalPosition(
Position.create(
generatedPosition.line - this.nrPrependesLines,
generatedPosition.character
)
if (generatedPosition.line < 0) {
return Position.create(-1, -1);
}

if (!this.decoded) {
this.decoded = decode(this.rawMap);
}

const position = Position.create(
generatedPosition.line - this.nrPrependesLines,
generatedPosition.character
);

if (position.line >= this.decoded.length) {
return Position.create(-1, -1);
}

const lineMatch = this.decoded[position.line];
if (!lineMatch.length) {
return Position.create(-1, -1);
}

const character = position.character;
const characterMatch = lineMatch.find(
(col, idx) =>
idx + 1 === lineMatch.length ||
(col[0] <= character && lineMatch[idx + 1][0] > character)
);
if (!isValidSegment(characterMatch)) {
return Position.create(-1, -1);
}

return Position.create(characterMatch[2], characterMatch[3]);
}

getGeneratedPosition(originalPosition: Position): Position {
const result = super.getGeneratedPosition(originalPosition);
result.line += this.nrPrependesLines;
return result;
if (!this.decoded) {
this.decoded = decode(this.rawMap);
}

const lineMatches: [number, [number, number, number, number]][] = [];
for (let line = 0; line < this.decoded.length; line++) {
for (let column = 0; column < this.decoded[line].length; column++) {
const entry = this.decoded[line][column];
if (isSegmentOnSameLine(entry, originalPosition.line)) {
lineMatches.push([line, entry]);
if (entry[3] === originalPosition.character) {
return Position.create(line + this.nrPrependesLines, entry[0]);
}
}
}
}

if (!lineMatches.length) {
return Position.create(-1, -1);
}

// Since the mappings are high resolution, we should never get to this point,
// but we'll deal with it regardless.
lineMatches.sort((m1, m2) => {
const lineDiff = m1[0] - m2[0];
if (lineDiff !== 0) {
return lineDiff;
}
return m1[1][3] - m2[1][3];
});
const match = lineMatches.find(
(match, idx) =>
idx + 1 === lineMatches.length ||
(match[1][3] <= originalPosition.character &&
lineMatches[idx + 1][1][3] > originalPosition.character)
)!;
return Position.create(match[1][2] + this.nrPrependesLines, match[0]);
}

isInGenerated(): boolean {
// always return true and map outliers case by case
return true;
}

getURL(): string {
return this.sourceUri;
}

destroy() {}
}

function isSegmentOnSameLine(
segment: SourceMapSegment,
line: number
): segment is [number, number, number, number] {
return segment[2] === line;
}

function isValidSegment(
segment: SourceMapSegment | undefined
): segment is [number, number, number, number] {
return !!segment && segment.length > 1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,7 @@ export class SvelteDocumentSnapshot implements DocumentSnapshot {
if (!this.tsxMap) {
return new FragmentMapper(this.parent.getText(), scriptInfo, uri);
}
return new ConsumerDocumentMapper(
await new SourceMapConsumer(this.tsxMap),
uri,
this.nrPrependedLines
);
return new ConsumerDocumentMapper(this.tsxMap.mappings, uri, this.nrPrependedLines);
}
}

Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2339,7 +2339,7 @@ source-map@^0.6.0, source-map@^0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==

sourcemap-codec@^1.4.4:
sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
Expand Down

0 comments on commit af0ef2d

Please sign in to comment.