]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: refine element type
authorEvan You <yyx990803@gmail.com>
Thu, 16 Nov 2023 02:54:54 +0000 (10:54 +0800)
committerEvan You <yyx990803@gmail.com>
Sat, 25 Nov 2023 08:18:29 +0000 (16:18 +0800)
packages/compiler-core/src/parser/index.ts
packages/compiler-core/src/utils.ts

index de697e4ff0327f1e794c0d7afebfafb7c1a3937f..97937d434603ea66f0c9e24674c81e10d3a03aca 100644 (file)
@@ -18,6 +18,7 @@ import Tokenizer, { CharCodes, QuoteType, isWhitespace } from './Tokenizer'
 import { CompilerCompatOptions } from '../compat/compatConfig'
 import { NO, extend } from '@vue/shared'
 import { defaultOnError, defaultOnWarn } from '../errors'
+import { isCoreComponent } from '../utils'
 
 type OptionalOptions =
   | 'getTextMode' // TODO
@@ -113,8 +114,7 @@ const tokenizer = new Tokenizer({
       type: NodeTypes.ELEMENT,
       tag: name,
       ns: currentOptions.getNamespace(name, getParent()),
-      // TODO refine tag type
-      tagType: ElementTypes.ELEMENT,
+      tagType: ElementTypes.ELEMENT, // will be refined on tag close
       props: [],
       children: [],
       loc: {
@@ -188,8 +188,6 @@ const tokenizer = new Tokenizer({
       if (name === 'pre') {
         inVPre = true
         currentElementIsVPreBoundary = true
-        // force current element type
-        currentElement!.tagType = ElementTypes.ELEMENT
         // convert dirs before this one to attributes
         const props = currentElement!.props
         for (let i = 0; i < props.length; i++) {
@@ -362,9 +360,23 @@ function onCloseTag(el: ElementNode, end: number) {
     offset++
   }
   el.loc.end = tokenizer.getPos(end + offset + 1)
+
+  // refine element type
+  const tag = el.tag
+  if (!inVPre) {
+    if (tag === 'slot') {
+      el.tagType = ElementTypes.SLOT
+    } else if (isFragmentTemplate(el)) {
+      el.tagType = ElementTypes.TEMPLATE
+    } else if (isComponent(el)) {
+      el.tagType = ElementTypes.COMPONENT
+    }
+  }
+
   // whitepsace management
   el.children = condenseWhitespace(el.children)
-  if (currentOptions.isPreTag(el.tag)) {
+
+  if (currentOptions.isPreTag(tag)) {
     inPre--
   }
   if (currentElementIsVPreBoundary) {
@@ -373,8 +385,77 @@ function onCloseTag(el: ElementNode, end: number) {
   }
 }
 
-const windowsNewlineRE = /\r\n/g
+const specialTemplateDir = new Set(['if', 'else', 'else-if', 'for', 'slot'])
+function isFragmentTemplate({ tag, props }: ElementNode): boolean {
+  if (tag === 'template') {
+    for (let i = 0; i < props.length; i++) {
+      if (
+        props[i].type === NodeTypes.DIRECTIVE &&
+        specialTemplateDir.has(props[i].name)
+      ) {
+        return true
+      }
+    }
+  }
+  return false
+}
 
+function isComponent({ tag, props }: ElementNode): boolean {
+  if (currentOptions.isCustomElement(tag)) {
+    return false
+  }
+  if (
+    tag === 'component' ||
+    isUpperCase(tag.charCodeAt(0)) ||
+    isCoreComponent(tag) ||
+    currentOptions.isBuiltInComponent?.(tag) ||
+    !currentOptions.isNativeTag?.(tag)
+  ) {
+    return true
+  }
+  // at this point the tag should be a native tag, but check for potential "is"
+  // casting
+  for (let i = 0; i < props.length; i++) {
+    const p = props[i]
+    if (p.type === NodeTypes.ATTRIBUTE) {
+      if (p.name === 'is' && p.value) {
+        if (p.value.content.startsWith('vue:')) {
+          return true
+        }
+        // TODO else if (
+        //   __COMPAT__ &&
+        //   checkCompatEnabled(
+        //     CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
+        //     context,
+        //     p.loc
+        //   )
+        // ) {
+        //   return true
+        // }
+      }
+    }
+    // TODO else if (
+    //   __COMPAT__ &&
+    //   // :is on plain element - only treat as component in compat mode
+    //   p.name === 'bind' &&
+    //   isStaticArgOf(p.arg, 'is') &&
+    //   checkCompatEnabled(
+    //     CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
+    //     context,
+    //     p.loc
+    //   )
+    // ) {
+    //   return true
+    // }
+  }
+  return false
+}
+
+function isUpperCase(c: number) {
+  return c > 64 && c < 91
+}
+
+const windowsNewlineRE = /\r\n/g
 function condenseWhitespace(nodes: TemplateChildNode[]): TemplateChildNode[] {
   const shouldCondense = currentOptions.whitespace !== 'preserve'
   let removedWhitespace = false
index baffe5086f621f3dff376ebe14704bbc55e5f225..ee6beadbbfa6f556295eb0c98b59d0ccbfc25193 100644 (file)
@@ -49,14 +49,19 @@ export const isBuiltInType = (tag: string, expected: string): boolean =>
   tag === expected || tag === hyphenate(expected)
 
 export function isCoreComponent(tag: string): symbol | void {
-  if (isBuiltInType(tag, 'Teleport')) {
-    return TELEPORT
-  } else if (isBuiltInType(tag, 'Suspense')) {
-    return SUSPENSE
-  } else if (isBuiltInType(tag, 'KeepAlive')) {
-    return KEEP_ALIVE
-  } else if (isBuiltInType(tag, 'BaseTransition')) {
-    return BASE_TRANSITION
+  switch (tag) {
+    case 'Teleport':
+    case 'teleport':
+      return TELEPORT
+    case 'Suspense':
+    case 'suspense':
+      return SUSPENSE
+    case 'KeepAlive':
+    case 'keep-alive':
+      return KEEP_ALIVE
+    case 'BaseTransition':
+    case 'base-transition':
+      return BASE_TRANSITION
   }
 }