From af0ef2d634668729caca73c81b027ce63414c2a7 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 15 Feb 2021 18:45:51 +0100 Subject: [PATCH] WIP synchronous mapping 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 (#580) --- packages/language-server/package.json | 1 + .../src/plugins/typescript/DocumentMapper.ts | 112 +++++++++++++++--- .../plugins/typescript/DocumentSnapshot.ts | 6 +- yarn.lock | 2 +- 4 files changed, 101 insertions(+), 20 deletions(-) diff --git a/packages/language-server/package.json b/packages/language-server/package.json index d1543e688..bf5f0854f 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -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": "*", diff --git a/packages/language-server/src/plugins/typescript/DocumentMapper.ts b/packages/language-server/src/plugins/typescript/DocumentMapper.ts index 6f15c0299..91698e0bf 100644 --- a/packages/language-server/src/plugins/typescript/DocumentMapper.ts +++ b/packages/language-server/src/plugins/typescript/DocumentMapper.ts @@ -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; } diff --git a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts index 7214b2613..c869e2939 100644 --- a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts +++ b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts @@ -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); } } diff --git a/yarn.lock b/yarn.lock index 7f3210e7f..c96c966e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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==