import path , { dirname } from 'path' ; import ts from 'typescript' ; import * as postcss from 'postcss' ; import postcssValueParser from 'postcss-value-parser' ; import cssSelectorTokenizer from 'css-selector-tokenizer' ; import type { IFileSystem , IFileSystemDescriptor } from '@file-services/types' ; import { ClassSymbol , CSSResolve , expandCustomSelectors , ImportSymbol , process as stylableProcess , safeParse , SRule , StateParsedValue , Stylable , StylableMeta , valueMapping , JSResolve , } from '@stylable/core' ; import type { Location , ParameterInformation , Position , SignatureHelp , SignatureInformation , } from 'vscode-languageserver' ; import { URI } from 'vscode-uri' ; import { CodeMixinCompletionProvider , CompletionProvider , createRange , CssMixinCompletionProvider , ExtendCompletionProvider , FormatterCompletionProvider , GlobalCompletionProvider , ImportInternalDirectivesProvider , NamedCompletionProvider , ProviderOptions , ProviderPosition , ProviderRange , PseudoElementCompletionProvider , RulesetInternalDirectivesProvider , SelectorCompletionProvider , StateEnumCompletionProvider , StateSelectorCompletionProvider , StateTypeCompletionProvider , TopLevelDirectiveProvider , ValueCompletionProvider , ValueDirectiveProvider , StImportNamedCompletionProvider , } from './completion-providers' ; import { topLevelDirectives } from './completion-types' ; import type { Completion } from './completion-types' ; import { createStateTypeSignature , createStateValidatorSignature , resolveStateParams , resolveStateTypeOrValidator , } from './feature/pseudo-class' ; import type { ExtendedTsLanguageService } from './types' ; import { isInNode , isRoot , isSelector , pathFromPosition } from './utils/postcss-ast-utils' ; import { parseSelector , SelectorChunk , SelectorInternalChunk , SelectorQuery , } from './utils/selector-analyzer' ; function findLast < T > ( arr , predicate ) { for ( let index = arr . length - 1 ; index >= 0 ; index -- ) { const item = arr [ index ] ; if ( predicate ( item , index , arr ) ) { return item ; } } return null ; } export class Provider { private providers : CompletionProvider [ ] = [ RulesetInternalDirectivesProvider , ImportInternalDirectivesProvider , TopLevelDirectiveProvider , ValueDirectiveProvider , GlobalCompletionProvider , SelectorCompletionProvider , ExtendCompletionProvider , CssMixinCompletionProvider , CodeMixinCompletionProvider , FormatterCompletionProvider , NamedCompletionProvider , StateTypeCompletionProvider , StateSelectorCompletionProvider , StateEnumCompletionProvider , PseudoElementCompletionProvider , ValueCompletionProvider , StImportNamedCompletionProvider , ] ; constructor ( private stylable , private tsLangService ) { } public provideCompletionItemsFromSrc ( src , pos , fileName , fs ) { const res = fixAndProcess ( src , pos , fileName ) ; const completions = [ ] ; if ( ! res . processed . meta ) { return [ ] ; } const options = this . createProviderOptions ( src , pos , res . processed . meta , res . processed . fakes , res . currentLine , res . cursorLineIndex , fs ) ; for ( const provider of this . providers ) { completions . push ( ... provider . provide ( options ) ) ; } return this . dedupeComps ( completions ) ; } public getDefinitionLocation ( src , position , filePath , fs ) { if ( ! filePath . endsWith ( '.st.css' ) ) { return [ ] ; } const callingMeta = this . stylable . process ( filePath ) ; const { word , meta } = getDefSymbol ( src , position , filePath , this . stylable ) ; if ( ! meta || ! word ) { return [ ] ; } const defs = [ ] ; let temp = null ; let stateMeta ; let maybeRequestPath ; try { maybeRequestPath = this . stylable . resolver . resolvePath ( fs . dirname ( meta . source ) , word ) ; } catch { } if ( maybeRequestPath ) { if ( fs . statSync ( maybeRequestPath ) ) { defs . push ( new ProviderLocation ( maybeRequestPath , { start : { line : 0 , character : 0 } , end : { line : 0 , character : 0 } , } ) ) ; } } else if ( Object . keys ( meta . getAllSymbols ( ) ) . find ( ( sym ) => sym === word . replace ( '.' , '' ) ) ) { const symbol = meta . getSymbol ( word . replace ( '.' , '' ) ) ! ; switch ( symbol . _kind ) { case 'class' : { defs . push ( new ProviderLocation ( meta . source , this . findWord ( word . replace ( '.' , '' ) , fs . readFileSync ( meta . source , 'utf8' ) , position ) ) ) ; break ; } case 'var' : { defs . push ( new ProviderLocation ( meta . source , this . findWord ( word , fs . readFileSync ( meta . source , 'utf8' ) , position ) ) ) ; break ; } case 'import' : { let resolved = null ; try { resolved = this . stylable . resolver . resolve ( symbol ) ; } catch { } let filePath ; if ( resolved && resolved . _kind !== 'js' ) { filePath = resolved . meta . source ; } else { try { filePath = this . stylable . resolvePath ( dirname ( meta . source ) , symbol . import . request ) ; } catch { } } if ( filePath ) { const doc = fs . readFileSync ( filePath , 'utf8' ) ; if ( doc !== '' ) { defs . push ( new ProviderLocation ( filePath , this . findWord ( word , doc , position ) ) ) ; } } break ; } } } else if ( Object . values ( meta . getAllSymbols ( ) ) . some ( ( k ) => { if ( k . _kind === 'class' ) { const symbolStates = k [ valueMapping . states ] ; if ( symbolStates && Object . keys ( symbolStates ) . some ( ( key ) => key === word ) ) { const postcsspos = new ProviderPosition ( position . line + 1 , position . character ) ; const pfp = pathFromPosition ( callingMeta . rawAst , postcsspos , [ ] , true ) ; const selec = ( pfp [ pfp . length - 1 ] as postcss . Rule ) . selector ; const char = isInNode ( postcsspos , pfp [ pfp . length - 1 ] ) ? 1 : position . character ; const parsel = parseSelector ( selec , char ) ; const t = parsel . target ; const arr = Array . isArray ( t . focusChunk ) ? ( t . focusChunk as SelectorQuery [ ] ) [ t . index ] . text : t . focusChunk . text ; let name = findLast ( arr , ( str ) => ! str . startsWith ( ':' ) || str . startsWith ( '::' ) ) ; name = name ! . replace ( '.' , '' ) . replace ( /:/g , '' ) ; const localSymbol = callingMeta . getSymbol ( name ) ; if ( name === k . name || ( ! name . startsWith ( name . charAt ( 0 ) . toLowerCase ( ) ) && k . name === 'root' ) ) { temp = k ; stateMeta = meta ; return true ; } else if ( localSymbol && ( localSymbol . _kind === 'class' || localSymbol . _kind === 'element' ) && localSymbol [ valueMapping . extends ] ) { const res = this . findMyState ( callingMeta , name , word ) ; if ( res ) { temp = k ; stateMeta = res . meta ; return true ; } } } } return false ; } ) ) { if ( temp ) { const name = ( temp as any ) . name ; defs . push ( new ProviderLocation ( meta . source , this . findWord ( name , fs . readFileSync ( stateMeta ! . source , 'utf8' ) , position ) ) ) ; } } else if ( Object . keys ( meta . customSelectors ) . find ( ( sym ) => sym === ':--' + word ) ) { defs . push ( new ProviderLocation ( meta . source , this . findWord ( ':--' + word , src , position ) ) ) ; } else if ( ! word . startsWith ( word . charAt ( 0 ) . toLowerCase ( ) ) ) { defs . push ( new ProviderLocation ( meta . source , createRange ( 0 , 0 , 0 , 0 ) ) ) ; } return defs ; } public findMyState ( origMeta , elementName , state ) { const importedSymbol = origMeta . getClass ( elementName ) ! [ valueMapping . extends ] ; let res = null ; if ( importedSymbol && importedSymbol . _kind === 'import' ) { res = this . stylable . resolver . resolveImport ( importedSymbol ) ; } const localSymbol = origMeta . getSymbol ( elementName ) ! ; if ( res && res . _kind === 'css' && Object . keys ( ( res . symbol as ClassSymbol ) [ valueMapping . states ] ! ) . includes ( state ) ) { return res ; } else if ( res && res . _kind === 'css' && ( localSymbol . _kind === 'class' || localSymbol . _kind === 'element' ) && localSymbol [ valueMapping . extends ] ) { return this . findMyState ( res . meta , res . symbol . name , state ) ; } else { return null ; } } public getSignatureHelp ( src , pos , filePath , fs , paramInfo ) { if ( ! filePath . endsWith ( '.st.css' ) ) { return null ; } const { processed : { meta } , } = fixAndProcess ( src , pos , filePath ) ; if ( ! meta ) { return null ; } const split = src . split ( '\n' ) ; const line = split [ pos . line ] ; let value = '' ; const stPath = pathFromPosition ( meta . rawAst , { line : pos . line + 1 , character : pos . character + 1 , } ) ; const lastStPath = stPath [ stPath . length - 1 ] ; if ( isRoot ( lastStPath ) ) { return this . getSignatureForStateWithParamSelector ( meta , pos , line ) ; } else if ( line . slice ( 0 , pos . character ) . trim ( ) . startsWith ( valueMapping . states ) ) { return this . getSignatureForStateWithParamDefinition ( pos , line ) ; } if ( line . slice ( 0 , pos . character ) . trim ( ) . startsWith ( valueMapping . mixin ) ) { value = line . slice ( 0 , pos . character ) . trim ( ) . slice ( valueMapping . mixin . length + 1 ) . trim ( ) ; } else if ( line . slice ( 0 , pos . character ) . trim ( ) . includes ( ':' ) ) { value = line . slice ( 0 , pos . character ) . trim ( ) . slice ( line . slice ( 0 , pos . character ) . trim ( ) . indexOf ( ':' ) + 1 ) . trim ( ) ; } if ( /value\(\s*[^)]*$/ . test ( value ) ) { return null ; } const parsed = postcssValueParser ( value ) ; let mixin = '' ; const rev = parsed . nodes [ parsed . nodes . length - 1 ] ; if ( rev . type === 'function' && ! ! rev . unclosed ) { mixin = rev . value ; } else { return null ; } const activeParam = rev . nodes . reduce ( ( acc , cur ) => { return cur . type === 'div' ? acc + 1 : acc ; } , 0 ) ; if ( mixin === 'value' ) { return null ; } const mappedSymbol = meta . getSymbol ( mixin ) ; if ( mappedSymbol && mappedSymbol . _kind === 'import' ) { if ( mappedSymbol . import . from . endsWith ( '.ts' ) ) { return this . getSignatureForTsModifier ( mixin , activeParam , mappedSymbol . import . from , ( meta . getSymbol ( mixin ) ! as ImportSymbol ) . type === 'default' , paramInfo ) ; } else if ( mappedSymbol . import . from . endsWith ( '.js' ) ) { if ( fs . fileExistsSync ( mappedSymbol . import . from . slice ( 0 , - 3 ) + '.d.ts' ) ) { return this . getSignatureForTsModifier ( mixin , activeParam , mappedSymbol . import . from . slice ( 0 , - 3 ) + '.d.ts' , mappedSymbol . type === 'default' , paramInfo ) ; } else { const resolvedPath = this . stylable . resolvePath ( dirname ( meta . source ) , mappedSymbol . import . request ) ; const fileExists = fs . fileExistsSync ( resolvedPath ) ; return fileExists ? this . getSignatureForJsModifier ( mixin , activeParam , fs . readFileSync ( resolvedPath , 'utf8' ) , paramInfo ) : null ; } } } return null ; } private findWord ( word , src , position ) { const split = src . split ( '\n' ) ; const regex = '\\b' + '\\.?' + this . escapeRegExp ( word . replace ( '.' , '' ) . replace ( ':--' , '' ) ) + '\\b' ; let lineIndex = split . findIndex ( ( l ) => { const reg = RegExp ( regex ) . exec ( l ) ; return ! ! reg && l . slice ( reg . index - 2 , reg . index ) !== '::' ; } ) ; if ( lineIndex === - 1 || lineIndex === position . line ) { lineIndex = position . line ; } if ( lineIndex === - 1 ) { return createRange ( 0 , 0 , 0 , 0 ) ; } const line = split [ lineIndex ] ; const match = line . match ( RegExp ( regex ) ) ; if ( match ) { return createRange ( lineIndex , line . lastIndexOf ( word ) , lineIndex , line . lastIndexOf ( word ) + word . length ) ; } else { return createRange ( 0 , 0 , 0 , 0 ) ; } } private escapeRegExp ( re ) { return re . replace ( /([.*+?^=!:${}()|[\]/\\])/g , '\\$1' ) ; } private getSignatureForTsModifier ( mixin , activeParam , filePath , isDefault , paramInfo ) { const sig = extractTsSignature ( filePath , mixin , isDefault , this . tsLangService ) ; if ( ! sig || ! sig . declaration ) { return null ; } const ptypes = sig . parameters . map ( ( p ) => { return ( p . name + ':' + ( ( p . valueDeclaration as ts . ParameterDeclaration ) . type as ts . TypeReferenceNode ) . getFullText ( ) ) ; } ) ; const rtype = sig . declaration . type ? ( sig . declaration . type as ts . TypeReferenceNode ) . getText ( ) : '' ; const parameters = sig . parameters . map ( ( pt ) => { const label = pt . name + ':' + ( ( pt . valueDeclaration as ts . ParameterDeclaration ) . type as ts . TypeReferenceNode ) . getFullText ( ) ; return paramInfo . create ( label ) ; } ) ; const sigInfo = { label : mixin + '(' + ptypes . join ( ', ' ) + '): ' + rtype , parameters , } ; return { activeParameter : activeParam , activeSignature : 0 , signatures : [ sigInfo ] , } as SignatureHelp ; } private getSignatureForJsModifier ( mixin , activeParam , fileSrc , paramInfo ) { const lines = fileSrc . split ( '\n' ) ; const mixinLine = lines . findIndex ( ( l ) => l . trim ( ) . startsWith ( 'exports.' + mixin ) ) ; const docStartLine = lines . slice ( 0 , mixinLine ) . lastIndexOf ( lines . slice ( 0 , mixinLine ) . reverse ( ) . find ( ( l ) => l . trim ( ) . startsWith ( '/**' ) ) ! ) ; const docLines = lines . slice ( docStartLine , mixinLine ) ; const formattedLines = [ ] ; docLines . forEach ( ( l ) => { if ( l . trim ( ) . startsWith ( '*/' ) ) { return ; } if ( l . trim ( ) . startsWith ( '/**' ) && ! ! l . trim ( ) . slice ( 3 ) . trim ( ) ) { formattedLines . push ( l . trim ( ) . slice ( 3 ) . trim ( ) ) ; } if ( l . trim ( ) . startsWith ( '*' ) ) { formattedLines . push ( l . trim ( ) . slice ( 1 ) . trim ( ) ) ; } } ) ; const returnStart = formattedLines . findIndex ( ( l ) => l . startsWith ( '@returns' ) ) ; const returnEnd = formattedLines . slice ( returnStart + 1 ) . findIndex ( ( l ) => l . startsWith ( '@' ) ) === - 1 ? formattedLines . length - 1 : formattedLines . slice ( returnStart + 1 ) . findIndex ( ( l ) => l . startsWith ( '@' ) ) + returnStart ; const returnLines = formattedLines . slice ( returnStart , returnEnd + 1 ) ; formattedLines . splice ( returnStart , returnLines . length ) ; const returnType = /@returns *{(\w+)}/ . exec ( returnLines [ 0 ] ) ? /@returns *{(\w+)}/ . exec ( returnLines [ 0 ] ) ! [ 1 ] : '' ; const summaryStart = formattedLines . findIndex ( ( l ) => l . startsWith ( '@summary' ) ) ; let summaryLines = [ ] ; if ( summaryStart !== - 1 ) { const summaryEnd = formattedLines . slice ( summaryStart + 1 ) . findIndex ( ( l ) => l . startsWith ( '@' ) ) === - 1 ? formattedLines . length - 1 : formattedLines . slice ( summaryStart + 1 ) . findIndex ( ( l ) => l . startsWith ( '@' ) ) + summaryStart ; summaryLines = formattedLines . slice ( summaryStart , summaryEnd + 1 ) ; formattedLines . splice ( summaryStart , summaryLines . length ) ; } const params = [ ] ; while ( formattedLines . find ( ( l ) => l . startsWith ( '@param' ) ) ) { const paramStart = formattedLines . findIndex ( ( l ) => l . startsWith ( '@param' ) ) ; const paramEnd = formattedLines . slice ( paramStart + 1 ) . findIndex ( ( l ) => l . startsWith ( '@' ) ) === - 1 ? formattedLines . length - 1 : formattedLines . slice ( paramStart + 1 ) . findIndex ( ( l ) => l . startsWith ( '@' ) ) + paramStart ; const paramLines = formattedLines . slice ( paramStart , paramEnd + 1 ) ; formattedLines . splice ( paramStart , paramLines . length ) ; if ( /@param *{([ \w<>,'"|]*)} *(\w*)(.*)/ . exec ( paramLines [ 0 ] ) ) { params . push ( [ /@param *{([ \w<>,'"|]*)} *(\w*)(.*)/ . exec ( paramLines [ 0 ] ) ! [ 1 ] , /@param *{([ \w<>,'"|]*)} *(\w*)(.*)/ . exec ( paramLines [ 0 ] ) ! [ 2 ] , /@param *{([ \w<>,'"|]*)} *(\w*)(.*)/ . exec ( paramLines [ 0 ] ) ! [ 3 ] , ] ) ; } } let descLines = [ ] ; if ( formattedLines . find ( ( l ) => l . startsWith ( '@description' ) ) ) { const descStart = formattedLines . findIndex ( ( l ) => l . startsWith ( '@description' ) ) ; const descEnd = formattedLines . slice ( descStart + 1 ) . findIndex ( ( l ) => l . startsWith ( '@' ) ) === - 1 ? formattedLines . length - 1 : formattedLines . slice ( descStart + 1 ) . findIndex ( ( l ) => l . startsWith ( '@' ) ) + descStart ; descLines = formattedLines . slice ( descStart , descEnd + 1 ) ; } else if ( formattedLines . findIndex ( ( l ) => l . startsWith ( '@' ) ) === - 1 ) { descLines = formattedLines ; } else { descLines = formattedLines . slice ( 0 , formattedLines . findIndex ( ( l ) => l . startsWith ( '@' ) ) + 1 ) ; } if ( descLines [ 0 ] && descLines [ 0 ] . startsWith ( '@description' ) ) { descLines [ 0 ] = descLines [ 0 ] . slice ( 12 ) . trim ( ) ; } const parameters = params . map ( ( p ) => paramInfo . create ( p [ 1 ] + ': ' + p [ 0 ] , p [ 2 ] . trim ( ) ) ) ; const sigInfo = { label : mixin + '(' + parameters . map ( ( p ) => p . label ) . join ( ', ' ) + '): ' + returnType , documentation : descLines . join ( '\n' ) , parameters , } ; return { activeParameter : activeParam , activeSignature : 0 , signatures : [ sigInfo ] , } as SignatureHelp ; } private getSignatureForStateWithParamSelector ( meta , pos , line ) { let word = '' ; const posChar = pos . character + 1 ; const parsed = cssSelectorTokenizer . parse ( line ) ; if ( parsed . nodes [ 0 ] . type === 'selector' ) { let length = 0 ; parsed . nodes [ 0 ] . nodes . forEach ( ( node ) => { if ( node . type === 'invalid' ) { return ; } else if ( node . type === 'spacing' ) { length += node . value . length ; } else if ( node . name !== undefined ) { length += node . name . length + 1 ; if ( node . type === 'pseudo-class' && posChar > length + 1 && node . content !== undefined && posChar <= length + 2 + node . content . length ) { word = node . name ; } } } ) ; } let stateDef = null as StateParsedValue | string | null ; if ( word ) { const transformer = this . stylable . createTransformer ( ) ; const resolvedElements = transformer . resolveSelectorElements ( meta , line ) ; resolvedElements [ 0 ] [ 0 ] . resolved . forEach ( ( el ) => { const symbolStates = ( el . symbol as ClassSymbol ) [ valueMapping . states ] ; if ( symbolStates && typeof symbolStates [ word ] === 'object' ) { stateDef = symbolStates [ word ] ; } } ) ; if ( stateDef && typeof stateDef === 'object' ) { const parameters = resolveStateParams ( stateDef ) ; const sigInfo = { label : `${ word }(${ parameters })` , parameters : [ { label : parameters } ] as ParameterInformation [ ] , } ; return { activeParameter : 0 , activeSignature : 0 , signatures : [ sigInfo ] , } as SignatureHelp ; } } return null ; } private getSignatureForStateWithParamDefinition ( pos , line ) { const res = resolveStateTypeOrValidator ( pos , line ) ; if ( typeof res === 'string' ) { return createStateValidatorSignature ( res ) ; } else if ( typeof res === 'boolean' ) { return createStateTypeSignature ( ) ; } else { return null ; } } private createProviderOptions ( src , position , meta , fakeRules , fullLineText , cursorPosInLine , fs ) { const transformer = this . stylable . createTransformer ( ) ; const path = pathFromPosition ( meta . rawAst , { line : position . line + 1 , character : position . character , } ) ; const astAtCursor = path [ path . length - 1 ] ; const parentAst = ( astAtCursor as postcss . Declaration ) . parent ? ( astAtCursor as postcss . Declaration ) . parent : undefined ; const parentSelector = parentAst && isSelector ( parentAst ) && fakeRules . findIndex ( ( f ) => { return f . selector === parentAst . selector ; } ) === - 1 ? ( parentAst as SRule ) : astAtCursor && isSelector ( astAtCursor ) && fakeRules . findIndex ( ( f ) => { return f . selector === astAtCursor . selector ; } ) === - 1 ? ( astAtCursor as SRule ) : null ; const { lineChunkAtCursor , fixedCharIndex } = getChunkAtCursor ( fullLineText , cursorPosInLine ) ; const ps = parseSelector ( lineChunkAtCursor , fixedCharIndex ) ; const chunkStrings = ps . selector . reduce ( ( acc , s ) => { return acc . concat ( s . text ) ; } , [ ] as string [ ] ) ; const currentSelector = ( ps . selector [ 0 ] as SelectorChunk ) . classes [ 0 ] || ( ps . selector [ 0 ] as SelectorChunk ) . customSelectors [ 0 ] || chunkStrings [ 0 ] ; const expandedLine = expandCustomSelectors ( postcss . rule ( { selector : lineChunkAtCursor } ) , meta . customSelectors ) . split ( ' ' ) . pop ( ) ! ; const resolvedElements = transformer . resolveSelectorElements ( meta , expandedLine ) ; const resolvedRoot = transformer . resolveSelectorElements ( meta , `.${ meta . root }` ) [ 0 ] [ 0 ] ; let resolved = [ ] ; if ( currentSelector && resolvedElements [ 0 ] . length ) { const clas = resolvedElements [ 0 ] . find ( ( e ) => e . type === 'class' || ( e . type === 'element' && e . resolved . length > 1 ) ) ; resolved = clas ? clas . resolved : [ ] ; } return { meta , fs , stylable : this . stylable , src , tsLangService : this . tsLangService , resolvedElements , resolvedRoot , parentSelector , astAtCursor , lineChunkAtCursor , lastSelectoid : ps . lastSelector , fullLineText , position , resolved , currentSelector , target : ps . target , isMediaQuery : isInMediaQuery ( path ) , fakes : fakeRules , } ; } private dedupeComps ( completions ) { const uniqs = new Map < string , Completion > ( ) ; completions . forEach ( ( comp ) => { if ( ! uniqs . has ( comp . label ) ) { uniqs . set ( comp . label , comp ) ; } } ) ; const res = [ ] ; uniqs . forEach ( ( v ) => res . push ( v ) ) ; return res ; } } function isIllegalLine ( line ) { return /^\s*[-.:]+\s*$/ . test ( line ) ; } const lineEndsRegexp = /({|}|;)/ ; function findRefs ( word , defMeta , scannedMeta , callingMeta , stylable , pos ? ) { if ( ! word ) { return [ ] ; } const refs = [ ] ; const trans = stylable . createTransformer ( ) ; if ( word . startsWith ( ':global(' ) ) { scannedMeta . rawAst . walkRules ( ( rule ) => { if ( rule . selector . includes ( word ) && rule . source && rule . source . start ) { refs . push ( { uri : URI . file ( scannedMeta . source ) . toString ( ) , range : { start : { line : rule . source . start . line - 1 , character : rule . selector . indexOf ( word ) + ':global(' . length , } , end : { line : rule . source . start . line - 1 , character : rule . selector . indexOf ( word ) + word . length - 1 , } , } , } ) ; } } ) ; return refs ; } const valueRegex = new RegExp ( '(\\.?' + word + ')(\\s|$|\\:|;|\\)|,)' , 'g' ) ; scannedMeta . rawAst . walkRules ( ( rule ) => { const filterRegex = new RegExp ( '(\\.?' + word + ')(\\s|$|\\:|;|\\))' , 'g' ) ; if ( filterRegex . test ( rule . selector ) && ! ! rule . source && ! ! rule . source . start ) { const resScanned = trans . resolveSelectorElements ( scannedMeta , rule . selector ) ; if ( resScanned [ 0 ] . some ( ( rl ) => { return ( rl . name === word && rl . resolved . some ( ( i ) => i . meta . source === defMeta . source ) ) ; } ) ) { let match = valueRegex . exec ( rule . selector ) ; while ( match !== null ) { const index = match [ 0 ] . startsWith ( '.' ) ? match . index : match . index - 1 ; refs . push ( { uri : URI . file ( scannedMeta . source ) . toString ( ) , range : { start : { line : rule . source . start . line - 1 , character : rule . source . start . column + index , } , end : { line : rule . source . start . line - 1 , character : rule . source . start . column + index + word . length , } , } , } ) ; match = valueRegex . exec ( rule . selector ) ; } } else if ( ! ! pos && resScanned [ 0 ] . some ( ( rs ) => { const postcsspos = new ProviderPosition ( pos . line + 1 , pos . character ) ; const pfp = pathFromPosition ( callingMeta . rawAst , postcsspos , [ ] , true ) ; let lastStPath = pfp [ pfp . length - 1 ] ; if ( lastStPath . type === 'decl' ) { lastStPath = pfp [ pfp . length - 2 ] as postcss . Rule ; } const char = isInNode ( postcsspos , pfp [ pfp . length - 1 ] ) ? 1 : pos . character ; const callPs = parseSelector ( ( lastStPath as postcss . Rule ) . selector , char ) ; const callingElement = findLast ( callPs . selector [ callPs . target . index ] . text . slice ( 0 , callPs . target . internalIndex + 1 ) , ( e ) => ! e . startsWith ( ':' ) || e . startsWith ( '::' ) ) ; if ( ! callingElement ) { return false ; } const selector = ( lastStPath as postcss . Rule ) . selector ; const selectorElement = trans . resolveSelectorElements ( callingMeta , selector . slice ( 0 , selector . indexOf ( word ) + word . length ) ) [ 0 ] ; const resolvedSelectorElement = selectorElement [ selectorElement . length - 1 ] ! . resolved ; const lastResolvedSelector = resolvedSelectorElement [ resolvedSelectorElement . length - 1 ] ; if ( rs . resolved . some ( ( inner ) => inner . meta . source === defMeta . source && Object . keys ( ( inner . symbol as ClassSymbol ) [ valueMapping . states ] ! ) . includes ( word ) ) && rs . resolved [ rs . resolved . length - 1 ] . symbol . name === lastResolvedSelector . symbol . name ) { return true ; } return false ; } ) ) { let match = valueRegex . exec ( rule . selector ) ; while ( match !== null ) { const index = match [ 0 ] . startsWith ( '.' ) ? match . index : match . index - 1 ; refs . push ( { uri : URI . file ( scannedMeta . source ) . toString ( ) , range : { start : { line : rule . source . start . line - 1 , character : rule . source . start . column + index , } , end : { line : rule . source . start . line - 1 , character : rule . source . start . column + index + word . length , } , } , } ) ; match = valueRegex . exec ( rule . selector ) ; } } } } ) ; scannedMeta . rawAst . walkDecls ( ( decl ) => { if ( ! decl . source || ! decl . source . start ) { return ; } const directiveRegex = new RegExp ( valueMapping . extends + '|' + valueMapping . named + '|' + valueMapping . default ) ; if ( directiveRegex . test ( decl . prop ) ) { const reg = new RegExp ( valueRegex . source ) ; const match = reg . exec ( decl . value ) ; if ( match ) { refs . push ( { uri : URI . file ( scannedMeta . source ) . toString ( ) , range : { start : { line : decl . source . start . line - 1 , character : match . index + decl . source . start . column + decl . prop . length + ( decl . raws . between ? decl . raws . between . length : 0 ) - 1 , } , end : { line : decl . source . start . line - 1 , character : match . index + decl . source . start . column + decl . prop . length + ( decl . raws . between ? decl . raws . between . length : 0 ) + word . length - 1 , } , } , } ) ; } } } ) ; scannedMeta . rawAst . walkDecls ( ( decl ) => { if ( ! decl . source || ! decl . source . start || ! pos ) { return ; } const directiveRegex = new RegExp ( valueMapping . states ) ; const postcsspos = new ProviderPosition ( pos . line + 1 , pos . character ) ; const pfp = pathFromPosition ( callingMeta . rawAst , postcsspos , [ ] , true ) ; const char = isInNode ( postcsspos , pfp [ pfp . length - 1 ] ) ? 1 : pos . character ; const callPs = parseSelector ( ( pfp [ pfp . length - 1 ] as postcss . Rule ) . selector , char ) ; const callingElement = findLast ( callPs . selector [ callPs . target . index ] . text . slice ( 0 , callPs . target . internalIndex + 1 ) , ( e ) => ! e . startsWith ( ':' ) || e . startsWith ( '::' ) ) ; const blargh = trans . resolveSelectorElements ( callingMeta , ( pfp [ pfp . length - 1 ] as postcss . Rule ) ! . selector ) ; if ( directiveRegex . test ( decl . prop ) && scannedMeta . source === defMeta . source && ! ! blargh . length && ! ! callingElement && blargh [ 0 ] . some ( ( inner ) => { return ( inner . name === callingElement . replace ( /:/g , '' ) . replace ( '.' , '' ) && inner . resolved . some ( ( s ) => s . symbol . name === ( decl . parent as postcss . Rule ) . selector . replace ( '.' , '' ) ) ) ; } ) ) { const reg = new RegExp ( valueRegex . source ) ; const match = reg . exec ( decl . value ) ; if ( match ) { refs . push ( { uri : URI . file ( scannedMeta . source ) . toString ( ) , range : { start : { line : decl . source . start . line - 1 , character : match . index + decl . source . start . column + decl . prop . length + ( decl . raws . between ? decl . raws . between . length : 0 ) - 1 , } , end : { line : decl . source . start . line - 1 , character : match . index + decl . source . start . column + decl . prop . length + ( decl . raws . between ? decl . raws . between . length : 0 ) + word . length - 1 , } , } , } ) ; } } } ) ; scannedMeta . rawAst . walkDecls ( valueMapping . mixin , ( decl ) => { if ( ! decl . source || ! decl . source . start ) { return ; } const lines = decl . value . split ( '\n' ) ; lines . forEach ( ( line , index ) => { let match = valueRegex . exec ( line ) ; while ( match !== null ) { refs . push ( { uri : URI . file ( scannedMeta . source ) . toString ( ) , range : { start : { line : decl . source ! . start ! . line - 1 + index , character : index ? match . index : decl . source ! . start ! . column + valueMapping . mixin . length + match . index + ( decl . raws . between ? decl . raws . between . length : 0 ) - 1 , } , end : { line : decl . source ! . start ! . line - 1 + index , character : word . length + ( index ? match . index : decl . source ! . start ! . column + valueMapping . mixin . length + match . index + ( decl . raws . between ? decl . raws . between . length : 0 ) - 1 ) , } , } , } ) ; match = valueRegex . exec ( line ) ; } } ) ; } ) ; scannedMeta . rawAst . walkDecls ( word , ( decl ) => { if ( decl . parent && decl . parent . type === 'rule' && ( decl . parent as postcss . Rule ) . selector === ':vars' && ! ! decl . source && ! ! decl . source . start ) { refs . push ( { uri : URI . file ( scannedMeta . source ) . toString ( ) , range : { start : { line : decl . source . start . line - 1 , character : decl . source . start . column - 1 , } , end : { line : decl . source . start . line - 1 , character : decl . source . start . column + word . length - 1 , } , } , } ) ; } } ) ; scannedMeta . rawAst . walkDecls ( ( decl ) => { if ( decl . value . includes ( 'value(' ) && ! ! decl . source && ! ! decl . source . start ) { const usageRegex = new RegExp ( 'value\\(\\s*' + word + '\\s*\\)' , 'g' ) ; const match = usageRegex . exec ( decl . value ) ; if ( match ) { refs . push ( { uri : URI . file ( scannedMeta . source ) . toString ( ) , range : { start : { line : decl . source . start . line - 1 , character : match . index + decl . source . start . column + decl . prop . length + ( decl . raws . between ? decl . raws . between . length : 0 ) + 'value(' . length - 1 , } , end : { line : decl . source . start . line - 1 , character : match . index + decl . source . start . column + decl . prop . length + ( decl . raws . between ? decl . raws . between . length : 0 ) + 'value(' . length + word . length - 1 , } , } , } ) ; } } } ) ; return refs ; } function newFindRefs ( word , defMeta , callingMeta , stylesheetsPath , stylable , pos ? ) { let refs = [ ] ; if ( word . startsWith ( ':global(' ) ) { stylesheetsPath . forEach ( ( stylesheetPath ) => { const scannedMeta = stylable . process ( stylesheetPath ) ; scannedMeta . rawAst . walkRules ( ( rule ) => { if ( rule . selector . includes ( word ) ) { refs = refs . concat ( findRefs ( word , defMeta , scannedMeta , callingMeta , stylable ) ) ; } } ) ; } ) ; return refs ; } else { word = word . replace ( '.' , '' ) ; } const defSymbols = defMeta . getAllSymbols ( ) ; if ( ! defSymbols [ word ] && ! word . startsWith ( word . charAt ( 0 ) . toLowerCase ( ) ) ) { stylesheetsPath . forEach ( ( stylesheetPath ) => { const scannedMeta = stylable . process ( stylesheetPath ) ; let tmp = '' ; const scannedSymbols = scannedMeta . getAllSymbols ( ) ; if ( Object . keys ( scannedSymbols ) . some ( ( k ) => { tmp = k ; const localSymbol = scannedSymbols [ k ] ; return ( ( localSymbol . _kind === 'element' && localSymbol . alias && localSymbol . alias . import . from === defMeta . source ) || ( localSymbol . _kind === 'import' && localSymbol . import . from === defMeta . source ) ) ; } ) ) { refs = refs . concat ( findRefs ( tmp , defMeta , scannedMeta , callingMeta , stylable ) ) ; } } ) ; } else if ( defSymbols [ word ] && defSymbols [ word ] . _kind === 'var' ) { stylesheetsPath . forEach ( ( stylesheetPath ) => { const scannedMeta = stylable . process ( stylesheetPath ) ; const scannedSymbols = scannedMeta . getAllSymbols ( ) ; if ( ! scannedSymbols [ word ] || ( scannedSymbols [ word ] . _kind !== 'var' && scannedSymbols [ word ] . _kind !== 'import' ) ) { return ; } if ( scannedMeta . source === defMeta . source ) { refs = refs . concat ( findRefs ( word . replace ( '.' , '' ) , defMeta , scannedMeta , callingMeta , stylable ) ) ; } else { const newSymb = stylable . resolver . deepResolve ( scannedSymbols [ word ] ) ; if ( ! newSymb || ! newSymb . meta ) { return ; } if ( newSymb . meta . source === defMeta . source ) { refs = refs . concat ( findRefs ( word . replace ( '.' , '' ) , defMeta , scannedMeta , callingMeta , stylable ) ) ; } } } ) ; } else if ( defSymbols [ word ] && ( defSymbols [ word ] . _kind === 'class' || defSymbols [ word ] . _kind === 'import' ) ) { const trans = stylable . createTransformer ( ) ; const valueRegex = new RegExp ( '(\\.?' + word + ')\\b' , 'g' ) ; stylesheetsPath . forEach ( ( stylesheetPath ) => { const scannedMeta = stylable . process ( stylesheetPath ) ; let done = false ; scannedMeta . rawAst . walkRules ( ( r ) => { if ( valueRegex . test ( r . selector ) && ! done ) { const resolved = trans . resolveSelectorElements ( scannedMeta , r . selector ) ; const resolvedInner = resolved [ 0 ] . find ( ( r ) => r . name === word ) ; if ( resolvedInner && resolvedInner . resolved . some ( ( r ) => r . meta . source === defMeta . source ) ) { refs = refs . concat ( findRefs ( word . replace ( '.' , '' ) , defMeta , scannedMeta , callingMeta , stylable ) ) ; done = true ; } } } ) ; scannedMeta . rawAst . walkDecls ( ( d ) => { if ( valueRegex . test ( d . value ) && ! done ) { if ( d . prop === valueMapping . named && d . parent && d . parent . nodes . find ( ( n ) => { return ( ( n as postcss . Declaration ) . prop === valueMapping . from && path . resolve ( path . dirname ( scannedMeta . source ) , ( n as postcss . Declaration ) . value . replace ( /"/g , '' ) ) === defMeta . source ) ; } ) ) { refs = refs . concat ( findRefs ( word . replace ( '.' , '' ) , defMeta , scannedMeta , callingMeta , stylable ) ) ; done = true ; } } } ) ; } ) ; } else if ( Object . values ( defSymbols ) . some ( ( sym ) => { const symbolStates = sym . _kind === 'class' && sym [ valueMapping . states ] ; return ( sym . _kind === 'class' && symbolStates && Object . keys ( symbolStates ) . some ( ( k ) => { if ( k === word && ! ! pos ) { const postcsspos = new ProviderPosition ( pos . line + 1 , pos . character ) ; const pfp = pathFromPosition ( callingMeta . rawAst , postcsspos , [ ] , true ) ; let lastStPath = pfp [ pfp . length - 1 ] ; if ( lastStPath . type === 'decl' ) { lastStPath = pfp [ pfp . length - 2 ] as postcss . Rule ; } const selec = ( lastStPath as postcss . Rule ) . selector ; const char = isInNode ( postcsspos , pfp [ pfp . length - 1 ] ) ? 1 : pos . character ; const parsel = parseSelector ( selec , char ) ; const t = parsel . target ; const arr = Array . isArray ( t . focusChunk ) ? ( t . focusChunk as SelectorQuery [ ] ) [ t . index ] . text : t . focusChunk . text ; let name = findLast ( arr , ( str ) => ! str . startsWith ( ':' ) || str . startsWith ( '::' ) ) ; const trans = stylable . createTransformer ( ) ; const pse = trans . resolveSelectorElements ( callingMeta , selec ) ; name = name ! . replace ( '.' , '' ) . replace ( /:/g , '' ) ; if ( ! ! pse && ! ! pse [ 0 ] . some ( ( psInner ) => psInner . name === name && psInner . resolved . some ( ( r ) => r . symbol . name === sym . name ) ) ) { return true ; } } return false ; } ) ) ; } ) ) { stylesheetsPath . forEach ( ( stylesheetPath ) => { const scannedMeta = stylable . process ( stylesheetPath ) ; let done = false ; if ( defMeta . source === scannedMeta . source ) { refs = refs . concat ( findRefs ( word . replace ( '.' , '' ) , defMeta , scannedMeta , callingMeta , stylable , pos ) ) ; return ; } if ( ! pos ) { return ; } const trans = stylable . createTransformer ( ) ; scannedMeta . rawAst . walkRules ( ( r ) => { if ( r . selector . includes ( ':' + word ) && ! done ) { const parsed = parseSelector ( r . selector , r . selector . indexOf ( word ) ) ; const elem = ( parsed . selector [ parsed . target . index ] as SelectorChunk ) . type === '*' || ! ( parsed . selector [ parsed . target . index ] as SelectorChunk ) . type . startsWith ( ( parsed . selector [ parsed . target . index ] as SelectorChunk ) . type . charAt ( 0 ) . toLowerCase ( ) ) ? findLast ( ( parsed . selector [ parsed . target . index ] as SelectorChunk ) . text , ( str ) => ! str . startsWith ( ':' ) || str . startsWith ( '::' ) ) ! . replace ( '.' , '' ) : ( parsed . selector [ parsed . target . index ] as SelectorInternalChunk ) . name ; const reso = trans . resolveSelectorElements ( scannedMeta , r . selector ) ; const symb = reso [ 0 ] . find ( ( o ) => o . name === elem ) ; if ( ! ! symb && symb . resolved . some ( ( inner ) => { if ( inner . symbol . _kind === 'class' ) { const symbolStates = inner . symbol [ valueMapping . states ] ; return ( symbolStates && inner . meta . source === defMeta . source && Object . keys ( symbolStates ) . includes ( word ) ) ; } return false ; } ) ) { refs = refs . concat ( findRefs ( word . replace ( '.' , '' ) , defMeta , scannedMeta , callingMeta , stylable , pos ) ) ; done = true ; } } } ) ; } ) ; } return refs ; } export function getRenameRefs ( filePath , pos , fs , stylable ) { const refs = getRefs ( filePath , pos , fs , stylable ) ; const newRefs = [ ] ; refs . forEach ( ( ref ) => { const FISH = URI . parse ( ref . uri ) . path . startsWith ( stylable . projectRoot ) ; const isRefInProject = ref . uri . startsWith ( stylable . projectRoot ) ; if ( ! ref . uri . includes ( 'node_modules' ) && ( FISH || isRefInProject ) ) { newRefs . push ( ref ) ; } } ) ; return newRefs ; } export function getRefs ( filePath , position , fs , stylable ) { const callingMeta = stylable . process ( filePath ) ; const symb = getDefSymbol ( fs . readFileSync ( filePath , 'utf8' ) , position , filePath , stylable ) ; if ( ! symb . meta ) { return [ ] ; } const stylesheets = fs . findFilesSync ( stylable . projectRoot , { filterFile : ( fileDesc ) => { return fileDesc . name . endsWith ( '.st.css' ) ; } , } ) . map ( ( stylesheetPath ) => URI . file ( stylesheetPath ) . fsPath ) ; return newFindRefs ( symb . word , symb . meta , callingMeta , stylesheets , stylable , position ) ; } export function createMeta ( src , path ) { let meta ; const fakes = [ ] ; try { const ast = safeParse ( src , { from : URI . file ( path ) . fsPath } ) ; if ( ast . nodes ) { for ( const node of ast . nodes ) { if ( node . type === 'decl' ) { const r = postcss . rule ( { selector : node . prop + ':' + node . value } ) ; r . source = node . source ; node . replaceWith ( r ) ; fakes . push ( r ) ; } } } if ( ast . raws . after && ast . raws . after . trim ( ) ) { const r = postcss . rule ( { selector : ast . raws . after . trim ( ) } ) ; ast . append ( r ) ; fakes . push ( r ) ; } meta = stylableProcess ( ast ) ; } catch ( error ) { return { meta : null , fakes } ; } return { meta , fakes , } ; } export function fixAndProcess ( src , position , filePath ) { let cursorLineIndex = position . character ; const lines = src . replace ( /\r\n/g , '\n' ) . split ( '\n' ) ; let currentLine = lines [ position . line ] ; let fixedSrc = src ; if ( currentLine . match ( lineEndsRegexp ) ) { let currentLocation = 0 ; const splitLine = currentLine . split ( lineEndsRegexp ) ; for ( let i = 0 ; i < splitLine . length ; i += 2 ) { currentLocation += splitLine [ i ] . length + 1 ; if ( currentLocation >= position . character ) { currentLine = splitLine [ i ] ; if ( isIllegalLine ( currentLine ) ) { splitLine [ i ] = '\n' ; lines . splice ( position . line , 1 , splitLine . join ( '' ) ) ; fixedSrc = lines . join ( '\n' ) ; } break ; } else { cursorLineIndex -= splitLine [ i ] . length + 1 ; } } } else if ( isIllegalLine ( currentLine ) ) { lines . splice ( position . line , 1 , '' ) ; fixedSrc = lines . join ( '\n' ) ; } const processed = createMeta ( fixedSrc , filePath ) ; return { processed , currentLine , cursorLineIndex , } ; } export class ProviderLocation { constructor ( public uri , public range ) { } } export function extractTsSignature ( filePath , mixin , isDefault , tsLangService ) { tsLangService . setOpenedFiles ( [ filePath ] ) ; const program = tsLangService . ts . getProgram ( ) ; if ( ! program ) { return ; } const tc = program . getTypeChecker ( ) ; const sf = program . getSourceFile ( filePath ) ; if ( ! sf ) { return ; } const mix = tc . getSymbolsInScope ( sf , ts . SymbolFlags . Function ) . find ( ( f ) => { if ( isDefault ) { return ( f as any ) . exportSymbol && ( f as any ) . exportSymbol . escapedName === 'default' ; } else { return ( f as any ) . exportSymbol && ( f as any ) . exportSymbol . escapedName === mixin ; } } ) ; if ( ! mix || ! mix . declarations ) { return ; } return tc . getSignatureFromDeclaration ( mix . declarations [ 0 ] as ts . SignatureDeclaration ) ; } export function extractJsModifierReturnType ( mixin , fileSrc ) { const lines = fileSrc . split ( '\n' ) ; const mixinLine = lines . findIndex ( ( l ) => l . trim ( ) . startsWith ( 'exports.' + mixin ) ) ; const docStartLine = lines . slice ( 0 , mixinLine ) . lastIndexOf ( lines . slice ( 0 , mixinLine ) . reverse ( ) . find ( ( l ) => l . trim ( ) . startsWith ( '/**' ) ) ! ) ; const docLines = lines . slice ( docStartLine , mixinLine ) ; const formattedLines = [ ] ; docLines . forEach ( ( l ) => { if ( l . trim ( ) . startsWith ( '*/' ) ) { return ; } if ( l . trim ( ) . startsWith ( '/**' ) && ! ! l . trim ( ) . slice ( 3 ) . trim ( ) ) { formattedLines . push ( l . trim ( ) . slice ( 3 ) . trim ( ) ) ; } if ( l . trim ( ) . startsWith ( '*' ) ) { formattedLines . push ( l . trim ( ) . slice ( 1 ) . trim ( ) ) ; } } ) ; const returnStart = formattedLines . findIndex ( ( l ) => l . startsWith ( '@returns' ) ) ; const returnEnd = formattedLines . slice ( returnStart + 1 ) . findIndex ( ( l ) => l . startsWith ( '@' ) ) === - 1 ? formattedLines . length - 1 : formattedLines . slice ( returnStart + 1 ) . findIndex ( ( l ) => l . startsWith ( '@' ) ) + returnStart ; const returnLines = formattedLines . slice ( returnStart , returnEnd + 1 ) ; formattedLines . splice ( returnStart , returnLines . length ) ; const returnType = /@returns *{(\w+)}/ . exec ( returnLines [ 0 ] ) ? /@returns *{(\w+)}/ . exec ( returnLines [ 0 ] ) ! [ 1 ] : '' ; return returnType ; } function isInMediaQuery ( path ) { return path . some ( ( n ) => n . type === 'atrule' && ( n as postcss . AtRule ) . name === 'media' ) ; } export function isDirective ( line ) { return Object . keys ( valueMapping ) . some ( ( k ) => line . trim ( ) . startsWith ( ( valueMapping as any ) [ k ] ) ) ; } function isNamedDirective ( line ) { return line . includes ( valueMapping . named ) ; } function isStImportNamed ( line ) { return line . trim ( ) . startsWith ( topLevelDirectives . stImport ) && line . includes ( '[' ) ; } export function isInValue ( lineText , position ) { let isInValue = false ; if ( lineText . includes ( 'value(' ) ) { const line = lineText . slice ( 0 , position . character ) ; let stack = 0 ; for ( let i = 0 ; i <= line . length ; i ++ ) { if ( line [ i ] === '(' ) { stack += 1 ; } else if ( line [ i ] === ')' ) { stack -= 1 ; } } if ( stack > 0 ) { isInValue = true ; } } return isInValue ; } function getChunkAtCursor ( fullLineText , cursorPosInLine ) { let fixedCharIndex = cursorPosInLine ; let lineChunkAtCursor = fullLineText ; while ( lineChunkAtCursor . lastIndexOf ( ' ' ) >= cursorPosInLine ) { lineChunkAtCursor = lineChunkAtCursor . slice ( 0 , lineChunkAtCursor . lastIndexOf ( ' ' ) ) ; } if ( ! isDirective ( lineChunkAtCursor ) && lineChunkAtCursor . lastIndexOf ( ' ' ) > - 1 && lineChunkAtCursor . lastIndexOf ( ' ' ) < cursorPosInLine ) { fixedCharIndex -= lineChunkAtCursor . lastIndexOf ( ' ' ) + 1 ; lineChunkAtCursor = lineChunkAtCursor . slice ( lineChunkAtCursor . lastIndexOf ( ' ' ) ) ; } return { lineChunkAtCursor : lineChunkAtCursor . trim ( ) , fixedCharIndex } ; } export function getNamedValues ( src , lineIndex ) { const lines = src . split ( '\n' ) ; let isNamedValueLine = false ; const namedValues = [ ] ; for ( let i = lineIndex ; i >= 0 ; i -- ) { if ( isDirective ( lines [ i ] ) && ! isNamedDirective ( lines [ i ] ) ) { break ; } else if ( isNamedDirective ( lines [ i ] ) ) { isNamedValueLine = true ; const valueStart = lines [ i ] . indexOf ( ':' ) + 1 ; const value = lines [ i ] . slice ( valueStart ) ; value . split ( ',' ) . map ( ( x ) => x . trim ( ) ) . filter ( ( x ) => x !== '' ) . forEach ( ( x ) => namedValues . push ( x ) ) ; break ; } else if ( isStImportNamed ( lines [ i ] ) ) { isNamedValueLine = true ; const valueStart = lines [ i ] . indexOf ( '[' ) + 1 ; const valueEnd = lines [ i ] . indexOf ( ']' ) ; const value = lines [ i ] . slice ( valueStart , valueEnd ) ; value . split ( ',' ) . map ( ( x ) => x . trim ( ) ) . filter ( ( x ) => x !== '' ) . forEach ( ( x ) => namedValues . push ( x ) ) ; break ; } else { const valueStart = lines [ i ] . indexOf ( ':' ) + 1 ; const value = lines [ i ] . slice ( valueStart ) ; value . split ( ',' ) . map ( ( x ) => x . trim ( ) ) . filter ( ( x ) => x !== '' ) . forEach ( ( x ) => namedValues . push ( x ) ) ; } } return { isNamedValueLine , namedValues } ; } export function getExistingNames ( lineText , position ) { const valueStart = lineText . includes ( topLevelDirectives . stImport ) ? lineText . indexOf ( '[' ) + 1 : lineText . indexOf ( ':' ) + 1 ; const value = lineText . slice ( valueStart , position . character ) ; const parsed = postcssValueParser ( value . trim ( ) ) ; const names = parsed . nodes . filter ( ( n ) => n . type === 'function' || n . type === 'word' ) . map ( ( n ) => n . value ) ; const rev = parsed . nodes . reverse ( ) ; const lastName = parsed . nodes . length && rev [ 0 ] . type === 'word' ? rev [ 0 ] . value : '' ; return { names , lastName } ; } function findNode ( nodes , index ) { return nodes . filter ( ( n ) => n . sourceIndex <= index ) . reduce ( ( m , n ) => { return m . sourceIndex > n . sourceIndex ? m : n ; } , { sourceIndex : - 1 } ) ; } export function getDefSymbol ( src , position , filePath , stylable ) { const res = fixAndProcess ( src , position , filePath ) ; let meta = res . processed . meta ; if ( ! meta ) { return { word : '' , meta : null } ; } const parsed = postcssValueParser ( res . currentLine ) . nodes ; let val = findNode ( parsed , position . character ) ; while ( val . nodes && val . nodes . length > 0 ) { if ( findNode ( val . nodes , position . character ) . sourceIndex >= 0 ) { val = findNode ( val . nodes , position . character ) ; } else { break ; } } let word = val . value ; if ( word . startsWith ( '[' ) ) { word = word . slice ( 1 , word . length ) ; } if ( word . endsWith ( ']' ) ) { word = word . slice ( 0 , word . length - 1 ) ; } const { lineChunkAtCursor } = getChunkAtCursor ( res . currentLine . slice ( 0 , val . sourceIndex + val . value . length ) , position . character ) ; const directiveRegex = new RegExp ( valueMapping . extends + '|' + valueMapping . named + '|' + valueMapping . default + '|' + valueMapping . mixin ) ; if ( lineChunkAtCursor . startsWith ( ':global' ) ) { return { word : ':global(' + word + ')' , meta } ; } const match = lineChunkAtCursor . match ( directiveRegex ) ; const localSymbol = meta . getSymbol ( word ) ; if ( match && localSymbol ) { let imp ; if ( localSymbol . _kind === 'import' && localSymbol . type !== 'default' ) { imp = stylable . resolver . resolveImport ( localSymbol ) ; } else if ( localSymbol . _kind === 'import' && localSymbol . type === 'default' ) { imp = stylable . resolver . resolveImport ( localSymbol ) ; return { word : imp ?. meta ?. root || '' , meta : imp ?. meta || null } ; } else if ( localSymbol . _kind === 'element' && localSymbol . alias ) { imp = stylable . resolver . resolveImport ( localSymbol . alias ) ; } else if ( localSymbol . _kind === 'class' ) { if ( localSymbol . alias ) { const res = stylable . resolver . resolveImport ( localSymbol . alias ) ; return { word , meta : res ? res . meta : null } ; } return { word , meta } ; } if ( imp ) { if ( imp . _kind === 'js' ) { return { word , meta } ; } else { return { word , meta : imp . meta } ; } } else { return { word : '' , meta : null } ; } } const varRegex = new RegExp ( 'value\\(\\s*' + word ) ; if ( varRegex . test ( lineChunkAtCursor ) ) { const symbol = meta . getSymbol ( word ) ; if ( ! symbol ) { return { word , meta : null } ; } else if ( symbol . _kind === 'var' ) { return { word , meta } ; } const resolvedVar = stylable . resolver . deepResolve ( symbol ) ; if ( resolvedVar ) { return { word , meta : resolvedVar . meta } ; } else { return { word , meta : null } ; } } const transformer = stylable . createTransformer ( ) ; const expandedLine = expandCustomSelectors ( postcss . rule ( { selector : lineChunkAtCursor } ) , meta . customSelectors ) . split ( ' ' ) . pop ( ) ! ; const resolvedElements = transformer . resolveSelectorElements ( meta , expandedLine ) ; let reso ; if ( ! word . startsWith ( word . charAt ( 0 ) . toLowerCase ( ) ) ) { reso = resolvedElements [ 0 ] [ resolvedElements [ 0 ] . length - 1 ] . resolved . find ( ( res ) => ! ! ( res . symbol as ClassSymbol ) [ '-st-root' ] ) ; } else if ( resolvedElements . length && resolvedElements [ 0 ] . length ) { reso = resolvedElements [ 0 ] [ resolvedElements [ 0 ] . length - 1 ] . resolved . find ( ( res ) => { let symbolStates ; if ( res . symbol . _kind === 'class' ) { symbolStates = res . symbol [ valueMapping . states ] ; } return ( ( res . symbol . name === word . replace ( '.' , '' ) && ! ( res . symbol as ClassSymbol ) . alias ) || ( symbolStates && Object . keys ( symbolStates ) . some ( ( k ) => k === word ) ) ) ; } ) ; } if ( reso ) { meta = reso . meta ; } return { word , meta } ; }