Browse Source

feat(compiler): v-text transform + move dom-specific errros codes to compiler-dom

Evan You 6 years ago
parent
commit
f91d335e65

+ 18 - 12
packages/compiler-core/src/errors.ts

@@ -1,24 +1,29 @@
 import { SourceLocation } from './ast'
 
 export interface CompilerError extends SyntaxError {
-  code: ErrorCodes
+  code: number
   loc?: SourceLocation
 }
 
+export interface CoreCompilerError extends CompilerError {
+  code: ErrorCodes
+}
+
 export function defaultOnError(error: CompilerError) {
   throw error
 }
 
-export function createCompilerError(
-  code: ErrorCodes,
-  loc?: SourceLocation
-): CompilerError {
-  const msg = __DEV__ || !__BROWSER__ ? errorMessages[code] : code
+export function createCompilerError<T extends number>(
+  code: T,
+  loc?: SourceLocation,
+  messages?: { [code: number]: string }
+): T extends ErrorCodes ? CoreCompilerError : CompilerError {
+  const msg = __DEV__ || !__BROWSER__ ? (messages || errorMessages)[code] : code
   const locInfo = loc ? ` (${loc.start.line}:${loc.start.column})` : ``
   const error = new SyntaxError(msg + locInfo) as CompilerError
   error.code = code
   error.loc = loc
-  return error
+  return error as any
 }
 
 export const enum ErrorCodes {
@@ -68,8 +73,6 @@ export const enum ErrorCodes {
   X_V_FOR_MALFORMED_EXPRESSION,
   X_V_BIND_NO_EXPRESSION,
   X_V_ON_NO_EXPRESSION,
-  X_V_HTML_NO_EXPRESSION,
-  X_V_HTML_WITH_CHILDREN,
   X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
   X_V_SLOT_NAMED_SLOT_ON_COMPONENT,
   X_V_SLOT_MIXED_SLOT_USAGE,
@@ -79,7 +82,12 @@ export const enum ErrorCodes {
 
   // generic errors
   X_PREFIX_ID_NOT_SUPPORTED,
-  X_MODULE_MODE_NOT_SUPPORTED
+  X_MODULE_MODE_NOT_SUPPORTED,
+
+  // Sepcial value for higher-order compilers to pick up the last code
+  // to avoid collision of error codes. This should always be kept as the last
+  // item.
+  __EXTEND_POINT__
 }
 
 export const errorMessages: { [code: number]: string } = {
@@ -146,8 +154,6 @@ export const errorMessages: { [code: number]: string } = {
   [ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
   [ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
   [ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
-  [ErrorCodes.X_V_HTML_NO_EXPRESSION]: `v-html is missing epxression.`,
-  [ErrorCodes.X_V_HTML_WITH_CHILDREN]: `v-html will override element children.`,
   [ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
   [ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT]:
     `Named v-slot on component. ` +

+ 6 - 1
packages/compiler-core/src/index.ts

@@ -87,5 +87,10 @@ export {
   CodegenContext,
   CodegenResult
 } from './codegen'
-export { ErrorCodes, CompilerError, createCompilerError } from './errors'
+export {
+  ErrorCodes,
+  CoreCompilerError,
+  CompilerError,
+  createCompilerError
+} from './errors'
 export * from './ast'

+ 2 - 2
packages/compiler-core/src/parse.ts

@@ -1,6 +1,6 @@
 import {
   ErrorCodes,
-  CompilerError,
+  CoreCompilerError,
   createCompilerError,
   defaultOnError
 } from './errors'
@@ -38,7 +38,7 @@ export interface ParserOptions {
   // The full set is https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references
   namedCharacterReferences?: { [name: string]: string | undefined }
 
-  onError?: (error: CompilerError) => void
+  onError?: (error: CoreCompilerError) => void
 }
 
 export const defaultParserOptions: Required<ParserOptions> = {

+ 4 - 4
packages/compiler-dom/__tests__/transforms/vHtml.spec.ts

@@ -2,8 +2,7 @@ import {
   parse,
   transform,
   PlainElementNode,
-  CompilerOptions,
-  ErrorCodes
+  CompilerOptions
 } from '@vue/compiler-core'
 import { transformVHtml } from '../../src/transforms/vHtml'
 import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
@@ -12,6 +11,7 @@ import {
   genFlagText
 } from '../../../compiler-core/__tests__/testUtils'
 import { PatchFlags } from '@vue/shared'
+import { DOMErrorCodes } from '../../src/errors'
 
 function transformWithVHtml(template: string, options: CompilerOptions = {}) {
   const ast = parse(template)
@@ -47,7 +47,7 @@ describe('compiler: v-html transform', () => {
       onError
     })
     expect(onError.mock.calls).toMatchObject([
-      [{ code: ErrorCodes.X_V_HTML_WITH_CHILDREN }]
+      [{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }]
     ])
     expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
       arguments: [
@@ -68,7 +68,7 @@ describe('compiler: v-html transform', () => {
       onError
     })
     expect(onError.mock.calls).toMatchObject([
-      [{ code: ErrorCodes.X_V_HTML_NO_EXPRESSION }]
+      [{ code: DOMErrorCodes.X_V_HTML_NO_EXPRESSION }]
     ])
   })
 })

+ 74 - 0
packages/compiler-dom/__tests__/transforms/vText.spec.ts

@@ -0,0 +1,74 @@
+import {
+  parse,
+  transform,
+  PlainElementNode,
+  CompilerOptions
+} from '@vue/compiler-core'
+import { transformVText } from '../../src/transforms/vText'
+import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
+import {
+  createObjectMatcher,
+  genFlagText
+} from '../../../compiler-core/__tests__/testUtils'
+import { PatchFlags } from '@vue/shared'
+import { DOMErrorCodes } from '../../src/errors'
+
+function transformWithVText(template: string, options: CompilerOptions = {}) {
+  const ast = parse(template)
+  transform(ast, {
+    nodeTransforms: [transformElement],
+    directiveTransforms: {
+      text: transformVText
+    },
+    ...options
+  })
+  return ast
+}
+
+describe('compiler: v-text transform', () => {
+  it('should convert v-text to textContent', () => {
+    const ast = transformWithVText(`<div v-text="test"/>`)
+    expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
+      arguments: [
+        `"div"`,
+        createObjectMatcher({
+          textContent: `[test]`
+        }),
+        `null`,
+        genFlagText(PatchFlags.PROPS),
+        `["textContent"]`
+      ]
+    })
+  })
+
+  it('should raise error and ignore children when v-text is present', () => {
+    const onError = jest.fn()
+    const ast = transformWithVText(`<div v-text="test">hello</div>`, {
+      onError
+    })
+    expect(onError.mock.calls).toMatchObject([
+      [{ code: DOMErrorCodes.X_V_TEXT_WITH_CHILDREN }]
+    ])
+    expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
+      arguments: [
+        `"div"`,
+        createObjectMatcher({
+          textContent: `[test]`
+        }),
+        `null`, // <-- children should have been removed
+        genFlagText(PatchFlags.PROPS),
+        `["textContent"]`
+      ]
+    })
+  })
+
+  it('should raise error if has no expression', () => {
+    const onError = jest.fn()
+    transformWithVText(`<div v-text></div>`, {
+      onError
+    })
+    expect(onError.mock.calls).toMatchObject([
+      [{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }]
+    ])
+  })
+})

+ 35 - 0
packages/compiler-dom/src/errors.ts

@@ -0,0 +1,35 @@
+import {
+  SourceLocation,
+  CompilerError,
+  createCompilerError,
+  ErrorCodes
+} from '@vue/compiler-core'
+
+export interface DOMCompilerError extends CompilerError {
+  code: DOMErrorCodes
+}
+
+export function createDOMCompilerError(
+  code: DOMErrorCodes,
+  loc?: SourceLocation
+): DOMCompilerError {
+  return createCompilerError(
+    code,
+    loc,
+    __DEV__ || !__BROWSER__ ? DOMErrorMessages : undefined
+  )
+}
+
+export const enum DOMErrorCodes {
+  X_V_HTML_NO_EXPRESSION = ErrorCodes.__EXTEND_POINT__,
+  X_V_HTML_WITH_CHILDREN,
+  X_V_TEXT_NO_EXPRESSION,
+  X_V_TEXT_WITH_CHILDREN
+}
+
+export const DOMErrorMessages: { [code: number]: string } = {
+  [DOMErrorCodes.X_V_HTML_NO_EXPRESSION]: `v-html is missing expression.`,
+  [DOMErrorCodes.X_V_HTML_WITH_CHILDREN]: `v-html will override element children.`,
+  [DOMErrorCodes.X_V_TEXT_NO_EXPRESSION]: `v-text is missing expression.`,
+  [DOMErrorCodes.X_V_TEXT_WITH_CHILDREN]: `v-text will override element children.`
+}

+ 7 - 4
packages/compiler-dom/src/transforms/vHtml.ts

@@ -1,18 +1,21 @@
 import {
   DirectiveTransform,
-  createCompilerError,
-  ErrorCodes,
   createObjectProperty,
   createSimpleExpression
 } from '@vue/compiler-core'
+import { createDOMCompilerError, DOMErrorCodes } from '../errors'
 
 export const transformVHtml: DirectiveTransform = (dir, node, context) => {
   const { exp, loc } = dir
   if (!exp) {
-    context.onError(createCompilerError(ErrorCodes.X_V_HTML_NO_EXPRESSION, loc))
+    context.onError(
+      createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc)
+    )
   }
   if (node.children.length) {
-    context.onError(createCompilerError(ErrorCodes.X_V_HTML_WITH_CHILDREN, loc))
+    context.onError(
+      createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc)
+    )
     node.children.length = 0
   }
   return {

+ 28 - 1
packages/compiler-dom/src/transforms/vText.ts

@@ -1 +1,28 @@
-// TODO
+import {
+  DirectiveTransform,
+  createObjectProperty,
+  createSimpleExpression
+} from '@vue/compiler-core'
+import { createDOMCompilerError, DOMErrorCodes } from '../errors'
+
+export const transformVText: DirectiveTransform = (dir, node, context) => {
+  const { exp, loc } = dir
+  if (!exp) {
+    context.onError(
+      createDOMCompilerError(DOMErrorCodes.X_V_TEXT_NO_EXPRESSION, loc)
+    )
+  }
+  if (node.children.length) {
+    context.onError(
+      createDOMCompilerError(DOMErrorCodes.X_V_TEXT_WITH_CHILDREN, loc)
+    )
+    node.children.length = 0
+  }
+  return {
+    props: createObjectProperty(
+      createSimpleExpression(`textContent`, true, loc),
+      exp || createSimpleExpression('', true)
+    ),
+    needRuntime: false
+  }
+}