mh-two-thousand-and-two
2024-04-12 7fc6dbf547b8899d949b67cdec36b96a7d1701c7
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
const t = require('@babel/types')
const traverse = require('@babel/traverse').default
 
const {
  VAR_ROOT,
  IDENTIFIER_METHOD,
  IDENTIFIER_FILTER,
  IDENTIFIER_GLOBAL,
  METHOD_RENDER_LIST
} = require('../../constants')
 
function isMatch (name, forItem, forIndex) {
  return name === forItem || name === forIndex
}
 
function findScoped (path, test, state) {
  if (!path) {
    return state
  }
  const scoped = state.scoped.find(scoped => {
    const {
      forItem,
      forIndex,
      path: listPath
    } = scoped
    const funPath = path.findParent(path =>
      path.isFunctionExpression() &&
      t.isCallExpression(path.parentPath) &&
      path.parentPath.node.callee.name === METHOD_RENDER_LIST
    )
    if (funPath && funPath.parentPath === listPath) {
      // TODO 为兼容历史结构仅在当前 list 父级存在 v-if 返回
      const parent = listPath.findParent(path => path.isFunctionExpression() || path.isConditionalExpression())
      if (parent && parent.isConditionalExpression()) {
        return true
      }
    } else {
      return false
    }
    let match = false
    path.traverse({
      noScope: true,
      Identifier (path) {
        if (!match && path.key !== 'key' && (path.key !== 'property' || path.parent.computed)) {
          match = isMatch(path.node.name, forItem, forIndex)
          if (match) {
            path.stop()
          }
        }
      }
    })
    if (!match && test) {
      traverse(t.arrayExpression([test]), {
        noScope: true,
        Identifier (path) {
          if (!match && path.key !== 'key' && (path.key !== 'property' || path.parent.computed)) {
            const node = path.node
            match = isMatch(node.name, forItem, forIndex) || scoped.declarationArray.find(({ declarations }) => declarations.find(({ id }) => id === node))
            if (match) {
              path.stop()
            }
          }
        }
      })
    }
    return match
  })
  if (!scoped && state.scoped.length > 1) {
    return state.scoped[1] // 取父
  }
  return scoped || state
}
 
function findTest (path, state) {
  let tests
  if (path) {
    while (path.parentPath && path.key !== 'body') {
      if (path.key === 'consequent' || path.key === 'alternate') {
        const testOrig = path.container.test
        let test = t.arrayExpression([t.cloneDeep(testOrig)])
        traverse(test, {
          noScope: true,
          MemberExpression (memberExpressionPath) {
            const names = state.scoped.map(scoped => scoped.forItem)
            const node = memberExpressionPath.node
            const objectName = node.object.name
            const property = node.property
            const propertyName = property.name
            if (objectName === VAR_ROOT || (names.includes(objectName) && (propertyName === IDENTIFIER_METHOD || propertyName === IDENTIFIER_FILTER || propertyName === IDENTIFIER_GLOBAL))) {
              const array = []
              let tempPath = memberExpressionPath
              while (tempPath.parentPath) {
                const key = tempPath.key
                array.unshift(typeof key === 'number' ? `[${key}]` : `.${key}`)
                tempPath = tempPath.parentPath
              }
              memberExpressionPath.replaceWith(path.parentPath.get('test' + array.join('')).node.property)
            }
          }
        })
        test = test.elements[0]
        if (path.key === 'alternate') {
          test = t.unaryExpression('!', test)
        }
        tests = tests ? t.logicalExpression('&&', test, tests) : test
      }
      path = path.parentPath
    }
  }
  return tests
}
 
module.exports = function getMemberExpr (path, name, init, state, variableDeclaration = true) {
  const test = findTest(path, state)
  const scoped = findScoped(path, test, state)
 
  if (!variableDeclaration) {
    scoped.declarationArray.push(t.expressionStatement(init))
    return
  }
 
  const identifier = t.identifier(name)
 
  scoped.propertyArray.push(t.objectProperty(identifier, identifier))
  scoped.declarationArray.push(
    t.variableDeclaration('var', [t.variableDeclarator(identifier, test ? t.conditionalExpression(test, init, t.nullLiteral()) : init)])
  )
 
  state.identifierArray.push(identifier)
 
  const contextIdentifier = t.identifier(scoped.context)
  contextIdentifier.$mpProcessed = true
  return t.memberExpression(contextIdentifier, identifier)
}