import ts from 'typescript' ; import { Position } from 'vscode-languageserver' ; import { Document , getLineAtPosition , getNodeIfIsInComponentStartTag , isInTag } from '../../../lib/documents' ; import { ComponentInfoProvider , JsOrTsComponentInfoProvider } from '../ComponentInfoProvider' ; import { DocumentSnapshot , SnapshotFragment , SvelteDocumentSnapshot } from '../DocumentSnapshot' ; import { LSAndTSDocResolver } from '../LSAndTSDocResolver' ; import { or } from '../../../utils' ; type NodePredicate = ( node : ts . Node ) => boolean ; type NodeTypePredicate < T extends ts . Node > = ( node : ts . Node ) => node is T ; /**
 * If the given original position is within a Svelte starting tag,
 * return the snapshot of that component.
 */ export async function getComponentAtPosition ( lang , doc , tsDoc , originalPosition ) { if ( tsDoc . parserError ) { return null ; } if ( isInTag ( originalPosition , doc . scriptInfo ) || isInTag ( originalPosition , doc . moduleScriptInfo ) ) { return null ; } const node = getNodeIfIsInComponentStartTag ( doc . html , doc . offsetAt ( originalPosition ) ) ; if ( ! node ) { return null ; } const fragment = await tsDoc . getFragment ( ) ; const generatedPosition = fragment . getGeneratedPosition ( doc . positionAt ( node . start + 1 ) ) ; const def = lang . getDefinitionAtPosition ( tsDoc . filePath , fragment . offsetAt ( generatedPosition ) ) ?. [ 0 ] ; if ( ! def ) { return null ; } return JsOrTsComponentInfoProvider . create ( lang , def ) ; } export function isComponentAtPosition ( doc , tsDoc , originalPosition ) { if ( tsDoc . parserError ) { return false ; } if ( isInTag ( originalPosition , doc . scriptInfo ) || isInTag ( originalPosition , doc . moduleScriptInfo ) ) { return false ; } return ! ! getNodeIfIsInComponentStartTag ( doc . html , doc . offsetAt ( originalPosition ) ) ; } /**
 * Checks if this a section that should be completely ignored
 * because it's purely generated.
 */ export function isInGeneratedCode ( text , start , end ) { const lastStart = text . lastIndexOf ( '/*Ωignore_startΩ*/' , start ) ; const lastEnd = text . lastIndexOf ( '/*Ωignore_endΩ*/' , start ) ; const nextEnd = text . indexOf ( '/*Ωignore_endΩ*/' , end ) ; return lastStart > lastEnd && lastStart < nextEnd ; } /**
 * Checks that this isn't a text span that should be completely ignored
 * because it's purely generated.
 */ export function isNoTextSpanInGeneratedCode ( text , span ) { return ! isInGeneratedCode ( text , span . start , span . start + span . length ) ; } export function isPartOfImportStatement ( text , position ) { const line = getLineAtPosition ( position , text ) ; return /\s*from\s+["'][^"']*/ . test ( line . slice ( 0 , position . character ) ) ; } export class SnapshotFragmentMap { private map = new Map < string , { fragment : SnapshotFragment ; snapshot : DocumentSnapshot ; } > ( ) ; constructor ( private resolver ) { } set ( fileName , content ) { this . map . set ( fileName , content ) ; } get ( fileName ) { return this . map . get ( fileName ) ; } getFragment ( fileName ) { return this . map . get ( fileName ) ?. fragment ; } async retrieve ( fileName ) { let snapshotFragment = this . get ( fileName ) ; if ( ! snapshotFragment ) { const snapshot = await this . resolver . getSnapshot ( fileName ) ; const fragment = await snapshot . getFragment ( ) ; snapshotFragment = { fragment , snapshot } ; this . set ( fileName , snapshotFragment ) ; } return snapshotFragment ; } async retrieveFragment ( fileName ) { return ( await this . retrieve ( fileName ) ) . fragment ; } } export function isAfterSvelte2TsxPropsReturn ( text , end ) { const textBeforeProp = text . substring ( 0 , end ) ; if ( textBeforeProp . includes ( '\nreturn { props: {' ) ) { return true ; } } export function findContainingNode < T extends ts . Node > ( node , textSpan , predicate ) { const children = node . getChildren ( ) ; const end = textSpan . start + textSpan . length ; for ( const child of children ) { if ( ! ( child . getStart ( ) <= textSpan . start && child . getEnd ( ) >= end ) ) { continue ; } if ( predicate ( child ) ) { return child ; } const foundInChildren = findContainingNode ( child , textSpan , predicate ) ; if ( foundInChildren ) { return foundInChildren ; } } } /**
 * Finds node exactly matching span {start, length}.
 */ export function findNodeAtSpan < T extends ts . Node > ( node , span , predicate ? ) { const { start , length } = span ; const end = start + length ; for ( const child of node . getChildren ( ) ) { const childStart = child . getStart ( ) ; if ( end <= childStart ) { return ; } const childEnd = child . getEnd ( ) ; if ( start >= childEnd ) { continue ; } if ( start === childStart && end === childEnd ) { if ( ! predicate ) { return child as T ; } if ( predicate ( child ) ) { return child ; } } const foundInChildren = findNodeAtSpan ( child , span , predicate ) ; if ( foundInChildren ) { return foundInChildren ; } } } function isSomeAncestor ( node , predicate ) { for ( let parent = node . parent ; parent ; parent = parent . parent ) { if ( predicate ( parent ) ) { return true ; } } return false ; } /**
 * Tests a node then its parent and successive ancestors for some respective predicates.
 */ function nodeAndParentsSatisfyRespectivePredicates < T extends ts . Node > ( selfPredicate , ... predicates ) { return ( node ) : node is T => { let next = node ; return [ selfPredicate , ... predicates ] . every ( ( predicate ) => { if ( ! next ) { return false ; } const current = next ; next = next . parent ; return predicate ( current ) ; } ) ; } ; } const isRenderFunction = nodeAndParentsSatisfyRespectivePredicates < ts . FunctionDeclaration & { name : ts . Identifier ; } > ( ( node ) => ts . isFunctionDeclaration ( node ) && node ?. name ?. getText ( ) === 'render' , ts . isSourceFile ) ; const isRenderFunctionBody = nodeAndParentsSatisfyRespectivePredicates ( ts . isBlock , isRenderFunction ) ; export const isReactiveStatement = nodeAndParentsSatisfyRespectivePredicates < ts . LabeledStatement > ( ( node ) => ts . isLabeledStatement ( node ) && node . label . getText ( ) === '$' , or ( isRenderFunctionBody , nodeAndParentsSatisfyRespectivePredicates ( ts . isBlock , ts . isArrowFunction , ts . isExpressionStatement , isRenderFunctionBody ) ) ) ; export const isInReactiveStatement = ( node ) => isSomeAncestor ( node , isReactiveStatement ) ; function gatherDescendants < T extends ts . Node > ( node , predicate , dest = [ ] ) { if ( predicate ( node ) ) { dest . push ( node ) ; } else { for ( const child of node . getChildren ( ) ) { gatherDescendants ( child , predicate , dest ) ; } } return dest ; } export const gatherIdentifiers = ( node ) => gatherDescendants ( node , ts . isIdentifier ) ;