import { RawSourceMap , SourceMapConsumer } from 'source-map' ; import { svelte2tsx , IExportedNames } from 'svelte2tsx' ; import ts from 'typescript' ; import { Position , Range , TextDocumentContentChangeEvent } from 'vscode-languageserver' ; import { Document , DocumentMapper , FragmentMapper , IdentityMapper , offsetAt , positionAt , TagInformation , isInTag , getLineOffsets } from '../../lib/documents' ; import { pathToUrl } from '../../utils' ; import { ConsumerDocumentMapper } from './DocumentMapper' ; import { getScriptKindFromAttributes , getScriptKindFromFileName , isSvelteFilePath , getTsCheckComment } from './utils' ; /**
 * An error which occured while trying to parse/preprocess the svelte file contents.
 */ export interface ParserError { message : string ; range : Range ; code : number ; } /**
 * Initial version of snapshots.
 */ export const INITIAL_VERSION = 0 ; /**
 * A document snapshot suitable for the ts language service and the plugin.
 * Can be a svelte or ts/js file.
 */ export interface DocumentSnapshot extends ts . IScriptSnapshot { version : number ; filePath : string ; scriptKind : ts . ScriptKind ; positionAt ( offset ) : Position ; /**
     * Instantiates a source mapper.
     * `destroyFragment` needs to be called when
     * it's no longer needed / the class should be cleaned up
     * in order to prevent memory leaks.
     */ getFragment ( ) : Promise < SnapshotFragment > ; /**
     * Needs to be called when source mapper
     * is no longer needed / the class should be cleaned up
     * in order to prevent memory leaks.
     */ destroyFragment ( ) : void ; /**
     * Convenience function for getText(0, getLength())
     */ getFullText ( ) : string ; } /**
 * The mapper to get from original snapshot positions to generated and vice versa.
 */ export interface SnapshotFragment extends DocumentMapper { scriptInfo : TagInformation | null ; positionAt ( offset ) : Position ; offsetAt ( position ) : number ; } /**
 * Options that apply to svelte files.
 */ export interface SvelteSnapshotOptions { transformOnTemplateError : boolean ; } export namespace DocumentSnapshot { param document param options export function fromDocument ( document , options ) { const { tsxMap , text , exportedNames , parserError , nrPrependedLines , scriptKind } = preprocessSvelteFile ( document , options ) ; return new SvelteDocumentSnapshot ( document , parserError , scriptKind , text , nrPrependedLines , exportedNames , tsxMap ) ; } param filePath param createDocument param options export function fromFilePath ( filePath , createDocument , options ) { if ( isSvelteFilePath ( filePath ) ) { return DocumentSnapshot . fromSvelteFilePath ( filePath , createDocument , options ) ; } else { return DocumentSnapshot . fromNonSvelteFilePath ( filePath ) ; } } param filePath param options export function fromNonSvelteFilePath ( filePath ) { const originalText = ts . sys . readFile ( filePath ) ?? '' ; return new JSOrTSDocumentSnapshot ( INITIAL_VERSION , filePath , originalText ) ; } param filePath param createDocument param options export function fromSvelteFilePath ( filePath , createDocument , options ) { const originalText = ts . sys . readFile ( filePath ) ?? '' ; return fromDocument ( createDocument ( filePath , originalText ) , options ) ; } } /**
 * Tries to preprocess the svelte document and convert the contents into better analyzable js/ts(x) content.
 */ function preprocessSvelteFile ( document , options ) { let tsxMap ; let parserError = null ; let nrPrependedLines = 0 ; let text = document . getText ( ) ; let exportedNames = { has : ( ) => false } ; const scriptKind = [ getScriptKindFromAttributes ( document . scriptInfo ?. attributes ?? { } ) , getScriptKindFromAttributes ( document . moduleScriptInfo ?. attributes ?? { } ) ] . includes ( ts . ScriptKind . TSX ) ? ts . ScriptKind . TSX : ts . ScriptKind . JSX ; try { const tsx = svelte2tsx ( text , { filename : document . getFilePath ( ) ?? undefined , isTsFile : scriptKind === ts . ScriptKind . TSX , emitOnTemplateError : options . transformOnTemplateError , namespace : document . config ?. compilerOptions ?. namespace , accessors : document . config ?. compilerOptions ?. accessors ?? document . config ?. compilerOptions ?. customElement } ) ; text = tsx . code ; tsxMap = tsx . map ; exportedNames = tsx . exportedNames ; if ( tsxMap ) { tsxMap . sources = [ document . uri ] ; const scriptInfo = document . scriptInfo || document . moduleScriptInfo ; const tsCheck = getTsCheckComment ( scriptInfo ?. content ) ; if ( tsCheck ) { text = tsCheck + text ; nrPrependedLines = 1 ; } } } catch ( e ) { const start = { line : ( e . start ?. line ?? 1 ) - 1 , character : e . start ?. column ?? 0 } ; const end = e . end ? { line : e . end . line - 1 , character : e . end . column } : start ; parserError = { range : { start , end } , message : e . message , code : - 1 } ; const scriptInfo = document . scriptInfo || document . moduleScriptInfo ; text = scriptInfo ? scriptInfo . content : '' ; } return { tsxMap , text , exportedNames , parserError , nrPrependedLines , scriptKind } ; } /**
 * A svelte document snapshot suitable for the ts language service and the plugin.
 */ export class SvelteDocumentSnapshot implements DocumentSnapshot { private fragment ? : SvelteSnapshotFragment ; version = this . parent . version ; constructor ( private readonly parent , public readonly parserError , public readonly scriptKind , private readonly text , private readonly nrPrependedLines , private readonly exportedNames , private readonly tsxMap ? ) { } get filePath ( ) { return this . parent . getFilePath ( ) || '' ; } getText ( start , end ) { return this . text . substring ( start , end ) ; } getLength ( ) { return this . text . length ; } getFullText ( ) { return this . text ; } getChangeRange ( ) { return undefined ; } positionAt ( offset ) { return positionAt ( offset , this . text ) ; } getLineContainingOffset ( offset ) { const chunks = this . getText ( 0 , offset ) . split ( '\n' ) ; return chunks [ chunks . length - 1 ] ; } hasProp ( name ) { return this . exportedNames . has ( name ) ; } async getFragment ( ) { if ( ! this . fragment ) { const uri = pathToUrl ( this . filePath ) ; this . fragment = new SvelteSnapshotFragment ( await this . getMapper ( uri ) , this . text , this . parent , uri ) ; } return this . fragment ; } destroyFragment ( ) { if ( this . fragment ) { this . fragment . destroy ( ) ; this . fragment = undefined ; } } private async getMapper ( uri ) { const scriptInfo = this . parent . scriptInfo || this . parent . moduleScriptInfo ; if ( ! this . tsxMap ) { if ( ! scriptInfo ) { return new IdentityMapper ( uri ) ; } return new FragmentMapper ( this . parent . getText ( ) , scriptInfo , uri ) ; } return new ConsumerDocumentMapper ( await new SourceMapConsumer ( this . tsxMap ) , uri , this . nrPrependedLines ) ; } } /**
 * A js/ts document snapshot suitable for the ts language service and the plugin.
 * Since no mapping has to be done here, it also implements the mapper interface.
 */ export class JSOrTSDocumentSnapshot extends IdentityMapper implements DocumentSnapshot , SnapshotFragment { scriptKind = getScriptKindFromFileName ( this . filePath ) ; scriptInfo = null ; private lineOffsets ? : number [ ] ; constructor ( public version , public readonly filePath , private text ) { super ( pathToUrl ( filePath ) ) ; } getText ( start , end ) { return this . text . substring ( start , end ) ; } getLength ( ) { return this . text . length ; } getFullText ( ) { return this . text ; } getChangeRange ( ) { return undefined ; } positionAt ( offset ) { return positionAt ( offset , this . text , this . getLineOffsets ( ) ) ; } offsetAt ( position ) { return offsetAt ( position , this . text , this . getLineOffsets ( ) ) ; } async getFragment ( ) { return this ; } destroyFragment ( ) { } update ( changes ) { for ( const change of changes ) { let start = 0 ; let end = 0 ; if ( 'range' in change ) { start = this . offsetAt ( change . range . start ) ; end = this . offsetAt ( change . range . end ) ; } else { end = this . getLength ( ) ; } this . text = this . text . slice ( 0 , start ) + change . text + this . text . slice ( end ) ; } this . version ++ ; this . lineOffsets = undefined ; } private getLineOffsets ( ) { if ( ! this . lineOffsets ) { this . lineOffsets = getLineOffsets ( this . text ) ; } return this . lineOffsets ; } } /**
 * The mapper to get from original svelte document positions
 * to generated snapshot positions and vice versa.
 */ export class SvelteSnapshotFragment implements SnapshotFragment { private lineOffsets = getLineOffsets ( this . text ) ; constructor ( private readonly mapper , public readonly text , private readonly parent , private readonly url ) { } get scriptInfo ( ) { return this . parent . scriptInfo ; } get moduleScriptInfo ( ) { return this . parent . moduleScriptInfo ; } get originalText ( ) { return this . parent . getText ( ) ; } getOriginalPosition ( pos ) { return this . mapper . getOriginalPosition ( pos ) ; } getGeneratedPosition ( pos ) { return this . mapper . getGeneratedPosition ( pos ) ; } isInGenerated ( pos ) { return ! isInTag ( pos , this . parent . styleInfo ) ; } getURL ( ) { return this . url ; } positionAt ( offset ) { return positionAt ( offset , this . text , this . lineOffsets ) ; } offsetAt ( position ) { return offsetAt ( position , this . text , this . lineOffsets ) ; } /**
     * Needs to be called when source mapper is no longer needed in order to prevent memory leaks.
     */ destroy ( ) { if ( this . mapper . destroy ) { this . mapper . destroy ( ) ; } } }