From f91d335e6595029b713b55cb0cea77e7085c382d Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 9 Oct 2019 11:13:13 -0400 Subject: [PATCH] feat(compiler): v-text transform + move dom-specific errros codes to compiler-dom --- packages/compiler-core/src/errors.ts | 30 +++++--- packages/compiler-core/src/index.ts | 7 +- packages/compiler-core/src/parse.ts | 4 +- .../__tests__/transforms/vHtml.spec.ts | 8 +- .../__tests__/transforms/vText.spec.ts | 74 +++++++++++++++++++ packages/compiler-dom/src/errors.ts | 35 +++++++++ packages/compiler-dom/src/transforms/vHtml.ts | 11 ++- packages/compiler-dom/src/transforms/vText.ts | 29 +++++++- 8 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 packages/compiler-dom/__tests__/transforms/vText.spec.ts create mode 100644 packages/compiler-dom/src/errors.ts diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index a9b2cf5d..8bd0089f 100644 --- a/packages/compiler-core/src/errors.ts +++ b/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( + 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 outlet.`, [ErrorCodes.X_V_SLOT_NAMED_SLOT_ON_COMPONENT]: `Named v-slot on component. ` + diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 336659a6..e1fdec2b 100644 --- a/packages/compiler-core/src/index.ts +++ b/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' diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 54e97801..1dab15e5 100644 --- a/packages/compiler-core/src/parse.ts +++ b/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 = { diff --git a/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts b/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts index 1e3648ca..12d3d6b3 100644 --- a/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts +++ b/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 }] ]) }) }) diff --git a/packages/compiler-dom/__tests__/transforms/vText.spec.ts b/packages/compiler-dom/__tests__/transforms/vText.spec.ts new file mode 100644 index 00000000..ef27ec50 --- /dev/null +++ b/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(`
`) + 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(`
hello
`, { + 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(`
`, { + onError + }) + expect(onError.mock.calls).toMatchObject([ + [{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }] + ]) + }) +}) diff --git a/packages/compiler-dom/src/errors.ts b/packages/compiler-dom/src/errors.ts new file mode 100644 index 00000000..edab3fb7 --- /dev/null +++ b/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.` +} diff --git a/packages/compiler-dom/src/transforms/vHtml.ts b/packages/compiler-dom/src/transforms/vHtml.ts index abd2a23b..43b3b6c6 100644 --- a/packages/compiler-dom/src/transforms/vHtml.ts +++ b/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 { diff --git a/packages/compiler-dom/src/transforms/vText.ts b/packages/compiler-dom/src/transforms/vText.ts index 70b786d1..1b244c89 100644 --- a/packages/compiler-dom/src/transforms/vText.ts +++ b/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 + } +}