import { TextDocument , Position , CancellationToken , ProviderResult , DefinitionProvider , Definition , Location , TextLine , Uri , workspace } from 'vscode' ; const path = require ( 'path' ) ; const fs = require ( 'fs' ) ; const paramCamse = require ( 'param-case' ) ; const glob = require ( 'glob' ) ; async function readDir ( dir , selectText , frame ) { return await new Promise ( ( resolve , reject ) => { fs . readdir ( dir , 'utf8' , ( err , files ) => { if ( err ) reject ( err ) ; if ( files . indexOf ( selectText . toLowerCase ( ) ) !== - 1 ) { if ( frame === 'iview' ) { let prePath = dir + path . sep + selectText . toLowerCase ( ) + path . sep ; let vuePath = prePath + selectText . toLowerCase ( ) + '.vue' ; let indexPath = prePath + 'index.js' ; if ( fs . existsSync ( vuePath ) ) { resolve ( vuePath ) ; } else if ( fs . existsSync ( indexPath ) ) { resolve ( indexPath ) ; } else { resolve ( '' ) ; } } else if ( frame === 'element-ui' ) { let prePath = dir + path . sep + selectText . replace ( /^el-/gi , '' ) + path . sep ; let mainPath = prePath + 'src' + path . sep + 'main.vue' ; let vuePath = prePath + 'src' + path . sep + selectText + '.vue' ; let indexPath = prePath + 'index.js' ; if ( fs . existsSync ( mainPath ) ) { resolve ( mainPath ) ; } else if ( fs . existsSync ( vuePath ) ) { resolve ( vuePath ) ; } else if ( fs . existsSync ( indexPath ) ) { resolve ( indexPath ) ; } else { resolve ( '' ) ; } } } else { resolve ( '' ) ; } } ) ; } ) ; } param plugin async function getPlugin ( plugin ) { return await new Promise ( ( resolve , reject ) => { fs . readFile ( workspace . rootPath + path . sep + 'package.json' , 'utf8' , ( err , data ) => { if ( err ) reject ( err ) ; let ret = '' ; let p = { } ; try { p = JSON . parse ( data ) ; } catch ( e ) { console . log ( 'e:' , e ) ; } if ( Array . isArray ( plugin ) ) { let framework = plugin ; for ( let i = 0 ; i < framework . length ; i ++ ) { const frame = framework [ i ] ; if ( ( p . dependencies && p . dependencies [ frame ] ) || ( p . devDependencies && p . devDependencies [ frame ] ) ) { ret = frame ; } } } else { let pluginArr = plugin . split ( '/' ) ; if ( pluginArr . length === 1 && ( p . dependencies && p . dependencies [ plugin ] ) || ( p . devDependencies && p . devDependencies [ plugin ] ) ) { ret = plugin ; } else if ( pluginArr . length > 1 && ( p . dependencies && p . dependencies [ pluginArr [ 0 ] ] ) || ( p . devDependencies && p . devDependencies [ pluginArr [ 0 ] ] ) ) { ret = plugin ; } } if ( ret ) { resolve ( ret ) ; } else { resolve ( '' ) ; } } ) ; } ) ; } param path async function getMain ( rootPath ) { return await new Promise ( ( resolve , reject ) => { fs . readFile ( rootPath + 'package.json' , 'utf8' , ( err , data ) => { if ( err ) reject ( err ) ; let p = { } ; try { p = JSON . parse ( data ) ; } catch ( e ) { console . log ( 'e:' , e ) ; } if ( p . main ) { resolve ( p . main ) ; } else { resolve ( '' ) ; } } ) ; } ) ; } param selectText async function definitionPlugin ( selectText ) { let frame = await getPlugin ( [ 'iview' , 'element-ui' ] ) ; if ( frame === 'iview' ) { return await readDir ( workspace . rootPath + path . sep + 'node_modules' + path . sep + 'iview' + path . sep + 'src' + path . sep + 'components' , paramCamse ( selectText ) , frame ) ; } else if ( frame === 'element-ui' ) { return await readDir ( workspace . rootPath + path . sep + 'node_modules' + path . sep + 'element-ui' + path . sep + 'packages' , paramCamse ( selectText ) . replace ( /^el-/gi , '' ) , frame ) ; } else { return '' ; } } export class vueHelperDefinitionProvider implements DefinitionProvider { public VUE_ATTR = { props : 1 , computed : 2 , methods : 3 , watch : 4 , beforeCreate : 5 , created : 6 , beforeMount : 7 , mounted : 8 , beforeUpdate : 9 , updated : 10 , activated : 11 , deactivated : 12 , beforeDestroy : 13 , destroyed : 14 , directives : 15 , filters : 16 , components : 17 , data : 18 } ; /**
     * 判断是文件内跳转还是文件外跳转
     */ getDefinitionPosition ( lineText ) { const pathRegs = [ /import\s+.*\s+from\s+['"](.*)['"]/ , /import\s*[^'"]*\(['"](.*)['"]\)[^'"]*/ , /.*require\s*\([^'"]*['"](.*)['"][^'"]*\)/ , /import\s+['"](.*)['"]/ , /import\s*\([^'"]*(?:\/\*.*\*\/)\s*['"](.*)['"][^'"]\)*/ ] ; let execResult ; for ( const pathReg of pathRegs ) { execResult = pathReg . exec ( lineText ) ; if ( execResult && execResult [ 1 ] ) { const filePath = execResult [ 1 ] ; return { path : filePath } ; } } } /**
     * 文件内跳转
     */ async definitionInFile ( document , position , line ) { const textSplite = [ ' ' , '<' , '>' , '"' , '\'' , '.' , '\\' , "=" , ":" , "@" , "(" , ")" , "[" , "]" , "{" , "}" , "," , "!" ] ; let posIndex = position . character ; let textMeta = line . text . substr ( posIndex , 1 ) ; let selectText = '' ; while ( textSplite . indexOf ( textMeta ) === - 1 && posIndex <= line . text . length ) { selectText += textMeta ; textMeta = line . text . substr ( ++ posIndex , 1 ) ; } posIndex = position . character - 1 ; textMeta = line . text . substr ( posIndex , 1 ) ; while ( textSplite . indexOf ( textMeta ) === - 1 && posIndex > 0 ) { selectText = textMeta + selectText ; textMeta = line . text . substr ( -- posIndex , 1 ) ; } let pos = 0 ; let begin = false ; let lineText = '' ; let braceLeftCount = 0 ; let attr = '' ; let searchType = '' ; if ( textMeta === '<' ) { searchType = 'components' ; } while ( pos < document . lineCount && ! /^\s*<\/script>\s*$/g . test ( lineText ) ) { lineText = document . lineAt ( ++ pos ) . text ; if ( ! begin ) { if ( /^\s*<script.*>\s*$/g . test ( lineText ) ) { begin = true ; } continue ; } let keyWord = lineText . replace ( /\s*(\w*)\s*(\(\s*\)|:|(:\s*function\s*\(\s*\)))\s*{\s*/gi , '$1' ) ; if ( this . VUE_ATTR [ keyWord ] !== undefined && braceLeftCount === 0 ) { attr = keyWord ; braceLeftCount = 0 ; } if ( searchType === 'components' ) { /**
                 * component组件跳转处理方式
                 * 1. 文件内import，require引入判断
                 * 2. iview, element组件判断
                 */ if ( attr ) { let retPath = await definitionPlugin ( selectText ) ; if ( retPath ) { return Promise . resolve ( new Location ( Uri . file ( retPath ) , new Position ( 0 , 0 ) ) ) ; } break ; } else { let tag = selectText . toLowerCase ( ) . replace ( /-/gi , '' ) ; if ( lineText . toLowerCase ( ) . includes ( tag ) && ( lineText . trim ( ) . indexOf ( 'import' ) === 0 || lineText . trim ( ) . indexOf ( 'require' ) === 0 ) ) { return this . definitionOutFile ( document , this . getDefinitionPosition ( lineText ) ) ; } } } else { if ( attr === 'data' && braceLeftCount >= 2 ) { let matchName = lineText . replace ( /\s*(\w+):.+/gi , '$1' ) ; if ( selectText === matchName && braceLeftCount === 2 ) { return Promise . resolve ( new Location ( document . uri , new Position ( pos , lineText . indexOf ( matchName ) + matchName . length ) ) ) ; } let braceLeft = lineText . match ( /{/gi ) ? lineText . match ( /{/gi ) . length : 0 ; let braceRight = lineText . match ( /}/gi ) ? lineText . match ( /}/gi ) . length : 0 ; braceLeftCount += braceLeft - braceRight ; } else if ( attr ) { let matchName = lineText . replace ( /\s*(async\s*)?(\w*)\s*(:|\().*/gi , '$2' ) ; if ( selectText === matchName && braceLeftCount === 1 ) { return Promise . resolve ( new Location ( document . uri , new Position ( pos , lineText . indexOf ( matchName ) + matchName . length ) ) ) ; } let braceLeft = lineText . match ( /{/gi ) ? lineText . match ( /{/gi ) . length : 0 ; let braceRight = lineText . match ( /}/gi ) ? lineText . match ( /}/gi ) . length : 0 ; braceLeftCount += braceLeft - braceRight ; } if ( attr === 'data' ) { if ( /\s*return\s*{\s*/gi . test ( lineText ) ) { braceLeftCount = 2 ; } } } } let files = glob . sync ( workspace . rootPath + '/!(node_modules)/**/*.vue' ) ; for ( let i = 0 ; i < files . length ; i ++ ) { const vueFile = files [ i ] ; let vueChangeFile = vueFile . replace ( /-/gi , '' ) . toLowerCase ( ) . replace ( /\.vue$/ , '' ) ; if ( vueChangeFile . endsWith ( '/' + selectText . toLowerCase ( ) . replace ( /-/gi , '' ) ) ) { return Promise . resolve ( new Location ( Uri . file ( vueFile ) , new Position ( 0 , 0 ) ) ) ; } } return Promise . resolve ( null ) ; } param document param position param line async definitionOutFile ( document , file ) { let filePath = file . path ; let isRelative = false ; if ( filePath . indexOf ( './' ) === 0 ) { isRelative = true ; } let config = workspace . getConfiguration ( 'vue-helper' ) ; if ( config || config . alias ) { for ( const key in config . alias ) { if ( config . alias . hasOwnProperty ( key ) && filePath . indexOf ( key ) === 0 ) { filePath = filePath . replace ( key , config . alias [ key ] ) ; break ; } } } if ( /(.*\/.*|[^.]+)\..*$/gi . test ( filePath ) ) { let tempFile = path . resolve ( workspace . rootPath , filePath ) ; if ( isRelative ) { tempFile = document . fileName . replace ( /(.*)\/[^\/]*$/i , '$1' ) + path . sep + filePath . replace ( /.\//i , '' ) ; } if ( fs . existsSync ( tempFile ) ) { return Promise . resolve ( new Location ( Uri . file ( tempFile ) , new Position ( 0 , 0 ) ) ) ; } } else { const postfix = [ 'vue' , 'js' , 'css' , 'scss' , 'less' ] ; for ( let i = 0 ; i < postfix . length ; i ++ ) { const post = postfix [ i ] ; let tempFile = path . resolve ( workspace . rootPath , filePath ) ; if ( isRelative ) { tempFile = document . fileName . replace ( /(.*)\/[^\/]*$/i , '$1' ) + path . sep + filePath . replace ( /.\//i , '' ) ; } if ( tempFile . endsWith ( '/' ) ) { tempFile = tempFile + 'index.' + post ; if ( fs . existsSync ( tempFile ) ) { return Promise . resolve ( new Location ( Uri . file ( tempFile ) , new Position ( 0 , 0 ) ) ) ; } } else { let indexFile = tempFile + path . sep + 'index.' + post ; tempFile += '.' + post ; if ( fs . existsSync ( tempFile ) ) { return Promise . resolve ( new Location ( Uri . file ( tempFile ) , new Position ( 0 , 0 ) ) ) ; } if ( fs . existsSync ( indexFile ) ) { return Promise . resolve ( new Location ( Uri . file ( indexFile ) , new Position ( 0 , 0 ) ) ) ; } } } } let plugin = await getPlugin ( filePath ) ; let pluginRootPath = workspace . rootPath + path . sep + 'node_modules' + path . sep + plugin + path . sep ; let pluginOwn = workspace . rootPath + path . sep + 'node_modules' + path . sep + plugin + '.js' ; let pluginPath = pluginRootPath + 'index.js' ; if ( fs . existsSync ( pluginOwn ) ) { return Promise . resolve ( new Location ( Uri . file ( pluginOwn ) , new Position ( 0 , 0 ) ) ) ; } else if ( fs . existsSync ( pluginPath ) ) { return Promise . resolve ( new Location ( Uri . file ( pluginPath ) , new Position ( 0 , 0 ) ) ) ; } let main = await getMain ( pluginRootPath ) ; if ( main ) { return Promise . resolve ( new Location ( Uri . file ( pluginRootPath + main ) , new Position ( 0 , 0 ) ) ) ; } return Promise . resolve ( null ) ; } provideDefinition ( document , position , token ) { const line = document . lineAt ( position . line ) ; let file = this . getDefinitionPosition ( line . text ) ; if ( file ) { return this . definitionOutFile ( document , file ) ; } else { return this . definitionInFile ( document , position , line ) ; } } }