Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/language-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"prettier-plugin-svelte": "0.7.0",
"source-map": "^0.7.3",
"svelte": "3.19.2",
"svelte2tsx": "~0.1.4",
"typescript": "*",
"vscode-css-languageservice": "4.1.0",
"vscode-emmet-helper": "1.2.17",
Expand Down
64 changes: 64 additions & 0 deletions packages/language-server/src/plugins/typescript/DocumentMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Position } from 'vscode-languageserver';
import { SourceMapConsumer } from 'source-map';

export interface DocumentMapper {
getOriginalPosition(generatedPosition: Position): Position;
getGeneratedPosition(originalPosition: Position): Position;
}

export class IdentityMapper implements DocumentMapper {
getOriginalPosition(generatedPosition: Position): Position {
return generatedPosition;
}

getGeneratedPosition(originalPosition: Position): Position {
return originalPosition;
}
}

export class ConsumerDocumentMapper implements DocumentMapper {
consumer: SourceMapConsumer;
sourceUri: string;

constructor(consumer: SourceMapConsumer, sourceUri: string) {
this.consumer = consumer;
this.sourceUri = sourceUri;
}

getOriginalPosition(generatedPosition: Position): Position {
const mapped = this.consumer.originalPositionFor({
line: generatedPosition.line + 1,
column: generatedPosition.character,
});

if (!mapped) {
return { line: -1, character: -1 };
}

if (mapped.line === 0) {
console.warn('Got 0 mapped line from', generatedPosition, 'col was', mapped.column);
}

return {
line: (mapped.line || 0) - 1,
character: mapped.column || 0,
};
}

getGeneratedPosition(originalPosition: Position): Position {
const mapped = this.consumer.generatedPositionFor({
line: originalPosition.line + 1,
column: originalPosition.character,
source: this.sourceUri,
});

if (!mapped) {
return { line: -1, character: -1 };
}

return {
line: (mapped.line || 0) - 1,
character: mapped.column || 0,
};
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

135 changes: 118 additions & 17 deletions packages/language-server/src/plugins/typescript/DocumentSnapshot.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,125 @@
import ts from 'typescript';
import { getScriptKindFromAttributes } from './utils';
import { TypescriptDocument } from './TypescriptDocument';
import { getScriptKindFromAttributes, isSvelteFilePath, getScriptKindFromFileName } from './utils';
import { Fragment, positionAt, offsetAt, Document, extractTag } from '../../lib/documents';
import { DocumentMapper, IdentityMapper, ConsumerDocumentMapper } from './DocumentMapper';
import { Position } from 'vscode-languageserver';
import { SourceMapConsumer, RawSourceMap } from 'source-map';
import { pathToUrl } from '../../utils';
import svelte2tsx from 'svelte2tsx';

export interface DocumentSnapshot extends ts.IScriptSnapshot {
version: number;
scriptKind: ts.ScriptKind;
export const INITIAL_VERSION = 0;

export class DocumentSnapshot implements ts.IScriptSnapshot {
private fragment?: SnapshotFragment;

static fromDocument(document: Document) {
const { tsxMap, text } = DocumentSnapshot.preprocessIfIsSvelteFile(
document.uri,
document.getText(),
);

return new DocumentSnapshot(
document.version,
getScriptKindFromAttributes(extractTag(document.getText(), 'script')?.attributes ?? {}),
document.getFilePath() || '',
text,
tsxMap,
);
}

static fromFilePath(filePath: string) {
const { text, tsxMap } = DocumentSnapshot.preprocessIfIsSvelteFile(
pathToUrl(filePath),
ts.sys.readFile(filePath) ?? '',
);

return new DocumentSnapshot(
INITIAL_VERSION + 1, // ensure it's greater than initial build
getScriptKindFromFileName(filePath),
filePath,
text,
tsxMap,
);
}

private static preprocessIfIsSvelteFile(uri: string, text: string) {
let tsxMap: RawSourceMap | undefined;
if (isSvelteFilePath(uri)) {
try {
const tsx = svelte2tsx(text);
text = tsx.code;
tsxMap = tsx.map;
if (tsxMap) {
tsxMap.sources = [uri];
}
} catch (e) {
text = '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had this approach and found that it gave too man angry red squiggles when the document is in a state where it won't parse.

https://github.com/halfnelson/svelte-type-checker-vscode/blob/svelte-native-support/server/src/DocumentSnapshot.ts#L50
In my plugin I stash the parse error so that when I get a call to get the diagnostics, I can check for the existence of the error and return the parse error (instead of treating the file as blank). Is the intention to let the svelte plugin point out the location of an parse errors?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. At the moment there is no error at all shown if there are parsing errors. With your strategy this could be pointing to parser errors instead, as you said. I will look into it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

console.error(`Couldn't convert ${uri} to tsx`, e);
}
}
return { tsxMap, text };
}

private constructor(
public version: number,
public scriptKind: ts.ScriptKind,
public filePath: string,
private text: string,
private tsxMap?: RawSourceMap,
) {}

getText(start: number, end: number) {
return this.text.substring(start, end);
}

getLength() {
return this.text.length;
}

getChangeRange() {
return undefined;
}

positionAt(offset: number) {
return positionAt(offset, this.text);
}

async getFragment() {
if (!this.fragment) {
const uri = pathToUrl(this.filePath);
const mapper = !this.tsxMap
? new IdentityMapper()
: new ConsumerDocumentMapper(await new SourceMapConsumer(this.tsxMap), uri);
this.fragment = new SnapshotFragment(mapper, this.text, uri);
}
return this.fragment;
}
}

export const INITIAL_VERSION = 0;
export class SnapshotFragment implements Fragment {
constructor(private mapper: DocumentMapper, private text: string, private url: string) {}

positionInParent(pos: Position): Position {
return this.mapper.getOriginalPosition(pos)!;
}

positionInFragment(pos: Position): Position {
return this.mapper.getGeneratedPosition(pos)!;
}

isInFragment(): boolean {
return true;
}

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

positionAt(offset: number) {
return positionAt(offset, this.text);
}

export namespace DocumentSnapshot {
export function fromDocument(document: TypescriptDocument): DocumentSnapshot {
const text = document.getText();
const length = document.getTextLength();
return {
version: document.version,
scriptKind: getScriptKindFromAttributes(document.getAttributes()),
getText: (start, end) => text.substring(start, end),
getLength: () => length,
getChangeRange: () => undefined,
};
offsetAt(position: Position) {
return offsetAt(position, this.text);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export class SnapshotManager {
delete(fileName: string) {
return this.documents.delete(fileName);
}

getFileNames() {
return Array.from(this.documents.keys());
}
}
}
Loading