import { SourceMapConsumer } from 'source-map' ; import type { compile } from 'svelte/compiler' ; import { CompileOptions } from 'svelte/types/compiler/interfaces' ; import { PreprocessorGroup , Processed } from 'svelte/types/compiler/preprocess/types' ; import { Position } from 'vscode-languageserver' ; import { getPackageInfo , importSvelte } from '../../importPackage' ; import { Document , DocumentMapper , extractScriptTags , extractStyleTag , FragmentMapper , IdentityMapper , isInTag , offsetAt , positionAt , SourceMapDocumentMapper , TagInformation } from '../../lib/documents' ; import { SvelteConfig } from '../../lib/documents/configLoader' ; import { getLastPartOfPath , isNotNullOrUndefined } from '../../utils' ; export type SvelteCompileResult = ReturnType < typeof compile > ; export enum TranspileErrorSource { Script = 'Script' , Style = 'Style' } type PositionMapper = Pick < DocumentMapper , 'getGeneratedPosition' | 'getOriginalPosition' > ; /**
 * Represents a text document that contains a svelte component.
 */ export class SvelteDocument { private transpiledDoc : ITranspiledSvelteDocument | undefined ; private compileResult : SvelteCompileResult | undefined ; public script : TagInformation | null ; public moduleScript : TagInformation | null ; public style : TagInformation | null ; public languageId = 'svelte' ; public version = 0 ; public uri = this . parent . uri ; public get config ( ) { return this . parent . configPromise ; } constructor ( private parent ) { this . script = this . parent . scriptInfo ; this . moduleScript = this . parent . moduleScriptInfo ; this . style = this . parent . styleInfo ; this . version = this . parent . version ; } getText ( ) { return this . parent . getText ( ) ; } getFilePath ( ) { return this . parent . getFilePath ( ) || '' ; } offsetAt ( position ) { return this . parent . offsetAt ( position ) ; } async getTranspiled ( ) { if ( ! this . transpiledDoc ) { const { version : { major , minor } } = getPackageInfo ( 'svelte' , this . getFilePath ( ) ) ; if ( major > 3 || ( major === 3 && minor >= 32 ) ) { this . transpiledDoc = await TranspiledSvelteDocument . create ( this . parent , await this . config ) ; } else { this . transpiledDoc = await FallbackTranspiledSvelteDocument . create ( this . parent , ( await this . config ) ?. preprocess ) ; } } return this . transpiledDoc ; } async getCompiled ( ) { if ( ! this . compileResult ) { this . compileResult = await this . getCompiledWith ( ( await this . config ) ?. compilerOptions ) ; } return this . compileResult ; } async getCompiledWith ( options = { } ) { const svelte = importSvelte ( this . getFilePath ( ) ) ; return svelte . compile ( ( await this . getTranspiled ( ) ) . getText ( ) , options ) ; } /**
     * Needs to be called before cleanup to prevent source map memory leaks.
     */ destroyTranspiled ( ) { if ( this . transpiledDoc ) { this . transpiledDoc . destroy ( ) ; this . transpiledDoc = undefined ; } } } export interface ITranspiledSvelteDocument extends PositionMapper { getText ( ) : string ; destroy ( ) : void ; } export class TranspiledSvelteDocument implements ITranspiledSvelteDocument { static async create ( document , config ) { if ( ! config ?. preprocess ) { return new TranspiledSvelteDocument ( document . getText ( ) ) ; } const filename = document . getFilePath ( ) || '' ; const svelte = importSvelte ( filename ) ; const preprocessed = await svelte . preprocess ( document . getText ( ) , wrapPreprocessors ( config ?. preprocess ) , { filename } ) ; if ( preprocessed . code === document . getText ( ) ) { return new TranspiledSvelteDocument ( document . getText ( ) ) ; } return new TranspiledSvelteDocument ( preprocessed . code , preprocessed . map ? new SourceMapDocumentMapper ( await createSourceMapConsumer ( preprocessed . map ) , getLastPartOfPath ( filename ) ) : undefined ) ; } constructor ( private code , private mapper ? ) { } getOriginalPosition ( generatedPosition ) { return this . mapper ?. getOriginalPosition ( generatedPosition ) || generatedPosition ; } getText ( ) { return this . code ; } getGeneratedPosition ( originalPosition ) { return this . mapper ?. getGeneratedPosition ( originalPosition ) || originalPosition ; } destroy ( ) { this . mapper ?. destroy ( ) ; } } /**
 * Only used when the user has an old Svelte version installed where source map support
 * for preprocessors is not built in yet.
 * This fallback version does not map correctly when there's both a module and instance script.
 * It isn't worth fixing these cases though now that Svelte ships a preprocessor with source maps.
 */ export class FallbackTranspiledSvelteDocument implements ITranspiledSvelteDocument { static async create ( document , preprocessors = [ ] ) { const { transpiled , processedScripts , processedStyles } = await transpile ( document , preprocessors ) ; const scriptMapper = await SvelteFragmentMapper . createScript ( document , transpiled , processedScripts ) ; const styleMapper = await SvelteFragmentMapper . createStyle ( document , transpiled , processedStyles ) ; return new FallbackTranspiledSvelteDocument ( document , transpiled , scriptMapper , styleMapper ) ; } private fragmentInfos = [ this . scriptMapper ?. fragmentInfo , this . styleMapper ?. fragmentInfo ] . filter ( isNotNullOrUndefined ) . sort ( ( i1 , i2 ) => i1 . end - i2 . end ) ; private constructor ( private parent , private transpiled , public scriptMapper , public styleMapper ) { } getOriginalPosition ( generatedPosition ) { if ( this . scriptMapper ?. isInTranspiledFragment ( generatedPosition ) ) { return this . scriptMapper . getOriginalPosition ( generatedPosition ) ; } if ( this . styleMapper ?. isInTranspiledFragment ( generatedPosition ) ) { return this . styleMapper . getOriginalPosition ( generatedPosition ) ; } let offset = offsetAt ( generatedPosition , this . transpiled ) ; for ( const fragmentInfo of this . fragmentInfos ) { if ( offset > fragmentInfo . end ) { offset += fragmentInfo . diff ; } } return this . parent . positionAt ( offset ) ; } getURL ( ) { return this . parent . getURL ( ) ; } getText ( ) { return this . transpiled ; } getGeneratedPosition ( originalPosition ) { const { styleInfo , scriptInfo } = this . parent ; if ( isInTag ( originalPosition , scriptInfo ) && this . scriptMapper ) { return this . scriptMapper . getGeneratedPosition ( originalPosition ) ; } if ( isInTag ( originalPosition , styleInfo ) && this . styleMapper ) { return this . styleMapper . getGeneratedPosition ( originalPosition ) ; } let offset = offsetAt ( originalPosition , this . parent . getText ( ) ) ; for ( const fragmentInfo of this . fragmentInfos ) { if ( offset > fragmentInfo . end ) { offset -= fragmentInfo . diff ; } } return positionAt ( offset , this . getText ( ) ) ; } /**
     * Needs to be called before cleanup to prevent source map memory leaks.
     */ destroy ( ) { this . scriptMapper ?. destroy ( ) ; this . styleMapper ?. destroy ( ) ; } } export class SvelteFragmentMapper implements PositionMapper { static async createStyle ( originalDoc , transpiled , processed ) { return SvelteFragmentMapper . create ( originalDoc , transpiled , originalDoc . styleInfo , extractStyleTag ( transpiled ) , processed ) ; } static async createScript ( originalDoc , transpiled , processed ) { const scriptInfo = originalDoc . scriptInfo || originalDoc . moduleScriptInfo ; const maybeScriptTag = extractScriptTags ( transpiled ) ; const maybeScriptTagInfo = maybeScriptTag && ( maybeScriptTag . script || maybeScriptTag . moduleScript ) ; return SvelteFragmentMapper . create ( originalDoc , transpiled , scriptInfo , maybeScriptTagInfo || null , processed ) ; } private static async create ( originalDoc , transpiled , originalTagInfo , transpiledTagInfo , processed ) { const sourceMapper = processed . length > 0 ? await SvelteFragmentMapper . createSourceMapper ( processed , originalDoc ) : new IdentityMapper ( originalDoc . uri ) ; if ( originalTagInfo && transpiledTagInfo ) { const sourceLength = originalTagInfo . container . end - originalTagInfo . container . start ; const transpiledLength = transpiledTagInfo . container . end - transpiledTagInfo . container . start ; const diff = sourceLength - transpiledLength ; return new SvelteFragmentMapper ( { end : transpiledTagInfo . container . end , diff } , new FragmentMapper ( originalDoc . getText ( ) , originalTagInfo , originalDoc . uri ) , new FragmentMapper ( transpiled , transpiledTagInfo , originalDoc . uri ) , sourceMapper ) ; } return null ; } private static async createSourceMapper ( processed , originalDoc ) { return processed . reduce ( async ( parent , processedSingle ) => processedSingle ?. map ? new SourceMapDocumentMapper ( await createSourceMapConsumer ( processedSingle . map ) , originalDoc . uri , await parent ) : new IdentityMapper ( originalDoc . uri , await parent ) , Promise . resolve < DocumentMapper > ( < any > undefined ) ) ; } private constructor ( /**
     * End offset + length difference to original
     */ public fragmentInfo , /**
     * Maps between full original source and fragment within that original.
     */ private originalFragmentMapper , /**
     * Maps between full transpiled source and fragment within that transpiled.
     */ private transpiledFragmentMapper , /**
     * Maps between original and transpiled, within fragment.
     */ private sourceMapper ) { } isInTranspiledFragment ( generatedPosition ) { return this . transpiledFragmentMapper . isInGenerated ( generatedPosition ) ; } getOriginalPosition ( generatedPosition ) { const positionInTranspiledFragment = this . transpiledFragmentMapper . getGeneratedPosition ( generatedPosition ) ; const positionInOriginalFragment = this . sourceMapper . getOriginalPosition ( positionInTranspiledFragment ) ; return this . originalFragmentMapper . getOriginalPosition ( positionInOriginalFragment ) ; } /**
     * Reversing `getOriginalPosition`
     */ getGeneratedPosition ( originalPosition ) { const positionInOriginalFragment = this . originalFragmentMapper . getGeneratedPosition ( originalPosition ) ; const positionInTranspiledFragment = this . sourceMapper . getGeneratedPosition ( positionInOriginalFragment ) ; return this . transpiledFragmentMapper . getOriginalPosition ( positionInTranspiledFragment ) ; } /**
     * Needs to be called before cleanup to prevent source map memory leaks.
     */ destroy ( ) { if ( this . sourceMapper . destroy ) { this . sourceMapper . destroy ( ) ; } } } /**
 * Wrap preprocessors and rethrow on errors with more info on where the error came from.
 */ function wrapPreprocessors ( preprocessors = [ ] ) { preprocessors = Array . isArray ( preprocessors ) ? preprocessors : [ preprocessors ] ; return preprocessors . map ( ( preprocessor ) => { const wrappedPreprocessor = { markup : preprocessor . markup } ; if ( preprocessor . script ) { wrappedPreprocessor . script = async ( args ) => { try { return await preprocessor . script ! ( args ) ; } catch ( e ) { e . __source = TranspileErrorSource . Script ; throw e ; } } ; } if ( preprocessor . style ) { wrappedPreprocessor . style = async ( args ) => { try { return await preprocessor . style ! ( args ) ; } catch ( e ) { e . __source = TranspileErrorSource . Style ; throw e ; } } ; } return wrappedPreprocessor ; } ) ; } async function transpile ( document , preprocessors = [ ] ) { preprocessors = Array . isArray ( preprocessors ) ? preprocessors : [ preprocessors ] ; const processedScripts = [ ] ; const processedStyles = [ ] ; const wrappedPreprocessors = preprocessors . map ( ( preprocessor ) => { const wrappedPreprocessor = { markup : preprocessor . markup } ; if ( preprocessor . script ) { wrappedPreprocessor . script = async ( args ) => { try { const res = await preprocessor . script ! ( args ) ; if ( res && res . map ) { processedScripts . push ( res ) ; } return res ; } catch ( e ) { e . __source = TranspileErrorSource . Script ; throw e ; } } ; } if ( preprocessor . style ) { wrappedPreprocessor . style = async ( args ) => { try { const res = await preprocessor . style ! ( args ) ; if ( res && res . map ) { processedStyles . push ( res ) ; } return res ; } catch ( e ) { e . __source = TranspileErrorSource . Style ; throw e ; } } ; } return wrappedPreprocessor ; } ) ; const svelte = importSvelte ( document . getFilePath ( ) || '' ) ; const result = await svelte . preprocess ( document . getText ( ) , wrappedPreprocessors , { filename : document . getFilePath ( ) || '' } ) ; const transpiled = result . code || result . toString ?. ( ) || '' ; return { transpiled , processedScripts , processedStyles } ; } async function createSourceMapConsumer ( map ) { return new SourceMapConsumer ( normalizeMap ( map ) ) ; function normalizeMap ( map ) { if ( typeof map === 'string' || map . version ) { return map ; } return map . toString ( ) ; } }