utils.spec.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { babelParse, walkIdentifiers } from '@vue/compiler-sfc'
  2. import {
  3. type ExpressionNode,
  4. type TransformContext,
  5. isReferencedIdentifier,
  6. } from '../src'
  7. import { type Position, createSimpleExpression } from '../src/ast'
  8. import {
  9. advancePositionWithClone,
  10. isMemberExpressionBrowser,
  11. isMemberExpressionNode,
  12. toValidAssetId,
  13. } from '../src/utils'
  14. function p(line: number, column: number, offset: number): Position {
  15. return { column, line, offset }
  16. }
  17. describe('advancePositionWithClone', () => {
  18. test('same line', () => {
  19. const pos = p(1, 1, 0)
  20. const newPos = advancePositionWithClone(pos, 'foo\nbar', 2)
  21. expect(newPos.column).toBe(3)
  22. expect(newPos.line).toBe(1)
  23. expect(newPos.offset).toBe(2)
  24. })
  25. test('same line', () => {
  26. const pos = p(1, 1, 0)
  27. const newPos = advancePositionWithClone(pos, 'foo\nbar', 4)
  28. expect(newPos.column).toBe(1)
  29. expect(newPos.line).toBe(2)
  30. expect(newPos.offset).toBe(4)
  31. })
  32. test('multiple lines', () => {
  33. const pos = p(1, 1, 0)
  34. const newPos = advancePositionWithClone(pos, 'foo\nbar\nbaz', 10)
  35. expect(newPos.column).toBe(3)
  36. expect(newPos.line).toBe(3)
  37. expect(newPos.offset).toBe(10)
  38. })
  39. })
  40. describe('isMemberExpression', () => {
  41. function commonAssertions(raw: (exp: ExpressionNode) => boolean) {
  42. const fn = (str: string) => raw(createSimpleExpression(str))
  43. // should work
  44. expect(fn('obj.foo')).toBe(true)
  45. expect(fn('obj[foo]')).toBe(true)
  46. expect(fn('obj[arr[0]]')).toBe(true)
  47. expect(fn('obj[arr[ret.bar]]')).toBe(true)
  48. expect(fn('obj[arr[ret[bar]]]')).toBe(true)
  49. expect(fn('obj[arr[ret[bar]]].baz')).toBe(true)
  50. expect(fn('obj[1 + 1]')).toBe(true)
  51. expect(fn(`obj[x[0]]`)).toBe(true)
  52. expect(fn('obj[1][2]')).toBe(true)
  53. expect(fn('obj[1][2].foo[3].bar.baz')).toBe(true)
  54. expect(fn(`a[b[c.d]][0]`)).toBe(true)
  55. expect(fn('obj?.foo')).toBe(true)
  56. expect(fn('foo().test')).toBe(true)
  57. // strings
  58. expect(fn(`a['foo' + bar[baz]["qux"]]`)).toBe(true)
  59. // multiline whitespaces
  60. expect(fn('obj \n .foo \n [bar \n + baz]')).toBe(true)
  61. expect(fn(`\n model\n.\nfoo \n`)).toBe(true)
  62. // should fail
  63. expect(fn('a \n b')).toBe(false)
  64. expect(fn('obj[foo')).toBe(false)
  65. expect(fn('objfoo]')).toBe(false)
  66. expect(fn('obj[arr[0]')).toBe(false)
  67. expect(fn('obj[arr0]]')).toBe(false)
  68. expect(fn('a + b')).toBe(false)
  69. expect(fn('foo()')).toBe(false)
  70. expect(fn('a?b:c')).toBe(false)
  71. expect(fn(`state['text'] = $event`)).toBe(false)
  72. }
  73. test('browser', () => {
  74. commonAssertions(isMemberExpressionBrowser)
  75. expect(isMemberExpressionBrowser(createSimpleExpression('123[a]'))).toBe(
  76. false,
  77. )
  78. })
  79. test('node', () => {
  80. const ctx = { expressionPlugins: ['typescript'] } as any as TransformContext
  81. const fn = (str: string) =>
  82. isMemberExpressionNode(createSimpleExpression(str), ctx)
  83. commonAssertions(exp => isMemberExpressionNode(exp, ctx))
  84. // TS-specific checks
  85. expect(fn('foo as string')).toBe(true)
  86. expect(fn(`foo.bar as string`)).toBe(true)
  87. expect(fn(`foo['bar'] as string`)).toBe(true)
  88. expect(fn(`foo[bar as string]`)).toBe(true)
  89. expect(fn(`(foo as string)`)).toBe(true)
  90. expect(fn(`123[a]`)).toBe(true)
  91. expect(fn(`foo() as string`)).toBe(false)
  92. expect(fn(`a + b as string`)).toBe(false)
  93. // #9865
  94. expect(fn('""')).toBe(false)
  95. expect(fn('undefined')).toBe(false)
  96. expect(fn('null')).toBe(false)
  97. })
  98. })
  99. test('toValidAssetId', () => {
  100. expect(toValidAssetId('foo', 'component')).toBe('_component_foo')
  101. expect(toValidAssetId('p', 'directive')).toBe('_directive_p')
  102. expect(toValidAssetId('div', 'filter')).toBe('_filter_div')
  103. expect(toValidAssetId('foo-bar', 'component')).toBe('_component_foo_bar')
  104. expect(toValidAssetId('test-测试-1', 'component')).toBe(
  105. '_component_test_2797935797_1',
  106. )
  107. })
  108. describe('isReferencedIdentifier', () => {
  109. test('identifiers in function parameters should not be inferred as references', () => {
  110. expect.assertions(4)
  111. const ast = babelParse(`(({ title }) => [])`)
  112. walkIdentifiers(
  113. ast.program.body[0],
  114. (node, parent, parentStack, isReference) => {
  115. expect(isReference).toBe(false)
  116. expect(isReferencedIdentifier(node, parent, parentStack)).toBe(false)
  117. },
  118. true,
  119. )
  120. })
  121. })