]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-sfc): throw an error if scope var name conflicts with component name
authordaiwei <daiwei521@126.com>
Thu, 29 Aug 2024 08:58:35 +0000 (16:58 +0800)
committerdaiwei <daiwei521@126.com>
Thu, 29 Aug 2024 08:58:35 +0000 (16:58 +0800)
packages/compiler-core/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/__tests__/transforms/vSlot.spec.ts
packages/compiler-core/src/errors.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-dom/src/errors.ts

index 3da778eb675538afd29b2a6b729af721caa16df3..1cf0ccae9fc28626442ddb134feafcdcdf046d76 100644 (file)
@@ -1,5 +1,265 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
+exports[`compiler: transform component slots > dynamically named slots 1`] = `
+"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    [_ctx.one]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
+    [_ctx.two]: _withCtx(({ bar }) => [_toDisplayString(_ctx.foo), _toDisplayString(bar)]),
+    _: 2 /* DYNAMIC */
+  }, 1024 /* DYNAMIC_SLOTS */))
+}"
+`;
+
+exports[`compiler: transform component slots > implicit default slot 1`] = `
+"const { createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    default: _withCtx(() => [
+      _createElementVNode("div")
+    ]),
+    _: 1 /* STABLE */
+  }))
+}"
+`;
+
+exports[`compiler: transform component slots > named slot with v-for w/ prefixIdentifiers: true 1`] = `
+"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, renderList: _renderList, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
+    _renderList(_ctx.list, (name) => {
+      return {
+        name: name,
+        fn: _withCtx(() => [_toDisplayString(name)])
+      }
+    })
+  ]), 1024 /* DYNAMIC_SLOTS */))
+}"
+`;
+
+exports[`compiler: transform component slots > named slot with v-if + prefixIdentifiers: true 1`] = `
+"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
+    (_ctx.ok)
+      ? {
+          name: "one",
+          fn: _withCtx((props) => [_toDisplayString(props)]),
+          key: "0"
+        }
+      : undefined
+  ]), 1024 /* DYNAMIC_SLOTS */))
+}"
+`;
+
+exports[`compiler: transform component slots > named slot with v-if + v-else-if + v-else 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+
+    const _component_Comp = _resolveComponent("Comp")
+
+    return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
+      ok
+        ? {
+            name: "one",
+            fn: _withCtx(() => ["foo"]),
+            key: "0"
+          }
+        : orNot
+          ? {
+              name: "two",
+              fn: _withCtx((props) => ["bar"]),
+              key: "1"
+            }
+          : {
+              name: "one",
+              fn: _withCtx(() => ["baz"]),
+              key: "2"
+            }
+    ]), 1024 /* DYNAMIC_SLOTS */))
+  }
+}"
+`;
+
+exports[`compiler: transform component slots > named slot with v-if 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+
+    const _component_Comp = _resolveComponent("Comp")
+
+    return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
+      ok
+        ? {
+            name: "one",
+            fn: _withCtx(() => ["hello"]),
+            key: "0"
+          }
+        : undefined
+    ]), 1024 /* DYNAMIC_SLOTS */))
+  }
+}"
+`;
+
+exports[`compiler: transform component slots > named slots w/ implicit default slot 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+  with (_ctx) {
+    const { createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+
+    const _component_Comp = _resolveComponent("Comp")
+
+    return (_openBlock(), _createBlock(_component_Comp, null, {
+      one: _withCtx(() => ["foo"]),
+      default: _withCtx(() => [
+        "bar",
+        _createElementVNode("span")
+      ]),
+      _: 1 /* STABLE */
+    }))
+  }
+}"
+`;
+
+exports[`compiler: transform component slots > nested slots scoping 1`] = `
+"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Inner = _resolveComponent("Inner")
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    default: _withCtx(({ foo }) => [
+      _createVNode(_component_Inner, null, {
+        default: _withCtx(({ bar }) => [_toDisplayString(foo), _toDisplayString(bar), _toDisplayString(_ctx.baz)]),
+        _: 2 /* DYNAMIC */
+      }, 1024 /* DYNAMIC_SLOTS */),
+      " ",
+      _toDisplayString(foo),
+      _toDisplayString(_ctx.bar),
+      _toDisplayString(_ctx.baz)
+    ]),
+    _: 1 /* STABLE */
+  }))
+}"
+`;
+
+exports[`compiler: transform component slots > on component dynamically named slot 1`] = `
+"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    [_ctx.named]: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
+    _: 2 /* DYNAMIC */
+  }, 1024 /* DYNAMIC_SLOTS */))
+}"
+`;
+
+exports[`compiler: transform component slots > on component named slot 1`] = `
+"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    named: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
+    _: 1 /* STABLE */
+  }))
+}"
+`;
+
+exports[`compiler: transform component slots > on-component default slot 1`] = `
+"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    default: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
+    _: 1 /* STABLE */
+  }))
+}"
+`;
+
+exports[`compiler: transform component slots > template named slots 1`] = `
+"const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    one: _withCtx(({ foo }) => [_toDisplayString(foo), _toDisplayString(_ctx.bar)]),
+    two: _withCtx(({ bar }) => [_toDisplayString(_ctx.foo), _toDisplayString(bar)]),
+    _: 1 /* STABLE */
+  }))
+}"
+`;
+
+exports[`compiler: transform component slots > with whitespace: 'preserve' > implicit default slot 1`] = `
+"const { createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    header: _withCtx(() => [" Header "]),
+    default: _withCtx(() => [
+      " ",
+      _createElementVNode("p")
+    ]),
+    _: 1 /* STABLE */
+  }))
+}"
+`;
+
+exports[`compiler: transform component slots > with whitespace: 'preserve' > named default slot + implicit whitespace content 1`] = `
+"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    header: _withCtx(() => [" Header "]),
+    default: _withCtx(() => [" Default "]),
+    _: 1 /* STABLE */
+  }))
+}"
+`;
+
+exports[`compiler: transform component slots > with whitespace: 'preserve' > should not generate whitespace only default slot 1`] = `
+"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
+
+return function render(_ctx, _cache) {
+  const _component_Comp = _resolveComponent("Comp")
+
+  return (_openBlock(), _createBlock(_component_Comp, null, {
+    header: _withCtx(() => [" Header "]),
+    footer: _withCtx(() => [" Footer "]),
+    _: 1 /* STABLE */
+  }))
+}"
+`;
+
 exports[`compiler: v-for > codegen > basic v-for 1`] = `
 "const _Vue = Vue
 
index bf3510a052d3b540466e188289c1261986f023df..c1f67ea4bf46e8bca5549fc66b686e3936369b17 100644 (file)
@@ -40,6 +40,7 @@ import { PatchFlags } from '@vue/shared'
 import { createObjectMatcher } from '../testUtils'
 import { transformText } from '../../src/transforms/transformText'
 import { parseWithForTransform } from './vFor.spec'
+import { parseWithSlots } from './vSlot.spec'
 
 function parseWithElementTransform(
   template: string,
@@ -1381,4 +1382,44 @@ describe('compiler: element transform', () => {
       ],
     })
   })
+
+  test('v-for scope var name conflict with component name', () => {
+    const onError = vi.fn()
+    parseWithForTransform(`<Comp v-for="Comp of list" />`, {
+      onError,
+      prefixIdentifiers: true,
+      bindingMetadata: {
+        Comp: BindingTypes.SETUP_CONST,
+      },
+    })
+    expect(onError.mock.calls[0]).toMatchObject([
+      {
+        code: ErrorCodes.X_VAR_NAME_CONFLICT_WITH_COMPONENT_NAME,
+      },
+    ])
+  })
+
+  test('slot scope var name conflict with component name', () => {
+    const onError = vi.fn()
+    parseWithSlots(
+      `<CompB>
+    <template #default="{ Comp }">
+      <Comp>{{Comp}}</Comp>
+    </template>
+  </CompB>`,
+      {
+        onError,
+        prefixIdentifiers: true,
+        bindingMetadata: {
+          Comp: BindingTypes.SETUP_CONST,
+          CompB: BindingTypes.SETUP_CONST,
+        },
+      },
+    )
+    expect(onError.mock.calls[0]).toMatchObject([
+      {
+        code: ErrorCodes.X_VAR_NAME_CONFLICT_WITH_COMPONENT_NAME,
+      },
+    ])
+  })
 })
index 4766c2ca9d863c3177701e9c45f2104958a51ebb..5a51656c177640b00451a389f63a1b28bb748676 100644 (file)
@@ -29,7 +29,10 @@ import { PatchFlags } from '@vue/shared'
 import { transformFor } from '../../src/transforms/vFor'
 import { transformIf } from '../../src/transforms/vIf'
 
-function parseWithSlots(template: string, options: CompilerOptions = {}) {
+export function parseWithSlots(
+  template: string,
+  options: CompilerOptions = {},
+) {
   const ast = parse(template, {
     whitespace: options.whitespace,
   })
index 58e113ab19eef2d27e3e3e950d5651592748c443..c70bc7544bf02f4510398ad6a56b708bd5aaf03e 100644 (file)
@@ -90,6 +90,7 @@ export enum ErrorCodes {
   X_V_MODEL_ON_PROPS,
   X_INVALID_EXPRESSION,
   X_KEEP_ALIVE_INVALID_CHILDREN,
+  X_VAR_NAME_CONFLICT_WITH_COMPONENT_NAME,
 
   // generic errors
   X_PREFIX_ID_NOT_SUPPORTED,
@@ -179,6 +180,7 @@ export const errorMessages: Record<ErrorCodes, string> = {
   [ErrorCodes.X_INVALID_EXPRESSION]: `Error parsing JavaScript expression: `,
   [ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN]: `<KeepAlive> expects exactly one child component.`,
   [ErrorCodes.X_VNODE_HOOKS]: `@vnode-* hooks in templates are no longer supported. Use the vue: prefix instead. For example, @vnode-mounted should be changed to @vue:mounted. @vnode-* hooks support has been removed in 3.4.`,
+  [ErrorCodes.X_VAR_NAME_CONFLICT_WITH_COMPONENT_NAME]: `the variable name conflicts with the component name.`,
 
   // generic errors
   [ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
index c917436ea91effff6b0808f431f45383573251f8..47b3c92c6945c844c1cee271ad285081ba92ea66 100644 (file)
@@ -319,6 +319,20 @@ export function resolveComponentType(
   return toValidAssetId(tag, `component`)
 }
 
+/**
+ * dev only
+ */
+const checkName = (name: string, context: TransformContext) => {
+  if (context.identifiers[name]) {
+    context.onError(
+      createCompilerError(
+        ErrorCodes.X_VAR_NAME_CONFLICT_WITH_COMPONENT_NAME,
+        context.currentNode!.loc,
+      ),
+    )
+  }
+}
+
 function resolveSetupReference(name: string, context: TransformContext) {
   const bindings = context.bindingMetadata
   if (!bindings || bindings.__isScriptSetup === false) {
@@ -344,6 +358,7 @@ function resolveSetupReference(name: string, context: TransformContext) {
     checkType(BindingTypes.SETUP_REACTIVE_CONST) ||
     checkType(BindingTypes.LITERAL_CONST)
   if (fromConst) {
+    __DEV__ && checkName(fromConst, context)
     return context.inline
       ? // in inline mode, const setup bindings (e.g. imports) can be used as-is
         fromConst
@@ -355,6 +370,7 @@ function resolveSetupReference(name: string, context: TransformContext) {
     checkType(BindingTypes.SETUP_REF) ||
     checkType(BindingTypes.SETUP_MAYBE_REF)
   if (fromMaybeRef) {
+    __DEV__ && checkName(fromMaybeRef, context)
     return context.inline
       ? // setup scope bindings that may be refs need to be unrefed
         `${context.helperString(UNREF)}(${fromMaybeRef})`
@@ -363,6 +379,7 @@ function resolveSetupReference(name: string, context: TransformContext) {
 
   const fromProps = checkType(BindingTypes.PROPS)
   if (fromProps) {
+    __DEV__ && checkName(fromProps, context)
     return `${context.helperString(UNREF)}(${
       context.inline ? '__props' : '$props'
     }[${JSON.stringify(fromProps)}])`
index b47624840abe1e34b0619ff1c04d661394ef7bd3..5bffa4e53a194fd42affb6195a1a13bc1446f258 100644 (file)
@@ -21,7 +21,7 @@ export function createDOMCompilerError(
 }
 
 export enum DOMErrorCodes {
-  X_V_HTML_NO_EXPRESSION = 53 /* ErrorCodes.__EXTEND_POINT__ */,
+  X_V_HTML_NO_EXPRESSION = 54 /* ErrorCodes.__EXTEND_POINT__ */,
   X_V_HTML_WITH_CHILDREN,
   X_V_TEXT_NO_EXPRESSION,
   X_V_TEXT_WITH_CHILDREN,