All files / packages/utils/src path.ts

0% Statements 0/52
0% Branches 0/1
0% Functions 0/1
0% Lines 0/52

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97                                                                                                                                                                                                 
import path from 'node:path'
import { parse as errorParse } from 'error-stack-parser-es/lite'
import picomatch from 'picomatch'
import { isWindows } from './common'
import { WindowsDiskRE, WindowsSlashRE } from './constant'
 
export function slash(p: string): string {
  return p.replace(WindowsSlashRE, '/')
}
 
export function normalizePath(id: string): string {
  return path.posix.normalize(isWindows ? slash(id) : id)
}
 
export function getCommonBase(globsResolved: string[]): null | string {
  const bases = globsResolved
    .filter(g => g[0] !== '!')
    .map((glob) => {
      let { base } = picomatch.scan(glob)
      // `scan('a/foo.js')` returns `base: 'a/foo.js'`
      if (path.posix.basename(base).includes('.'))
        base = path.posix.dirname(base)
 
      return base
    })
 
  if (!bases.length)
    return null
 
  let commonAncestor = ''
  const dirS = bases[0]!.split('/')
  for (let i = 0; i < dirS.length; i++) {
    const candidate = dirS.slice(0, i + 1).join('/')
    if (bases.every(base => base.startsWith(candidate)))
      commonAncestor = candidate
    else break
  }
  if (!commonAncestor)
    commonAncestor = '/'
 
  return commonAncestor
}
 
/**
 * 处理绝对路径
 */
export function parseAbsolute(pathStr?: string | null) {
  if (!pathStr)
    return pathStr
  pathStr = slash(pathStr)
  return isWindows && pathStr.startsWith('/') && pathStr.slice(1).match(WindowsDiskRE)
    ? pathStr.slice(1)
    : pathStr
}
 
/**
 * 获取调用方文件路径
 */
export function getCallerPath(depth = 2) {
  const error = new Error('get-caller-path')
  const stacks = errorParse(error)
  const filesList = Array.from(new Set(stacks.map(i => i.file || null)))
 
  /**
   * 匹配 https,http,file 协议的路径,提取文件名,允许带行、列号
   * @description `^((?:file|https?):\/\/)?` 匹配协议
   * @description `(.*?)` 非贪婪匹配任意字符,直到遇到可选的冒号和数字(行号、列号)
   * @description `(?<file>.*?)` 可通过 `match.groups.file` 获取该分组的值
   * @description `(?::\d+)?(?::\d+)?` 匹配可选的 `:行号和:列号` 部分,最多两个
   */
  const linePattern = /^((?:file|https?):\/\/)?(?<file>.*?)(?::\d+)?(?::\d+)?$/
 
  // 目标堆栈层级
  const targetIndex = depth
  const targetLine = filesList[targetIndex] || filesList.at(-1)
 
  const match = targetLine?.match(linePattern)?.groups?.file
 
  return parseAbsolute(match) || null
 
  // const stack = error.stack?.split('\n') || []
 
  // // 根据环境选择解析策略
  // const isNode = typeof process !== 'undefined' && process.versions?.node
  // const linePattern = isNode
  //   ? /\((?<file>.*?):\d+:\d+\)/
  //   // eslint-disable-next-line regexp/no-super-linear-backtracking
  //   : /(http|https|file):\/\/.*?\/(?<file>[^:]+):\d+:\d+/
 
  // // 动态计算目标堆栈层级(基础深度 + 调用层级)
  // const targetIndex = 2 + depth
  // const targetLine = stack[targetIndex] || ''
 
  // const match = targetLine.match(linePattern)
  // return match?.groups?.file || null
}