-
-
Notifications
You must be signed in to change notification settings - Fork 225
(feat) add svelte2tsx for better ts support #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
752f42d
e5fbf35
f001bc6
a4f272f
ac63301
98287d2
c10a5cf
0488b67
5831254
e1eaaf0
5b2f67a
6c589d1
f7f0cb8
97eaaca
a225f28
53c3d00
a890cb3
ae167e6
f413439
c04c4bb
8c80107
7ced88a
8dbe8a1
909c433
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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, | ||
| }; | ||
| } | ||
| } | ||
| 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 = ''; | ||
|
||
| 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); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍