From a60ad9180d01f2ac6468bbda8c8dbc06def1b55b Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 17 Nov 2023 14:17:30 +0800 Subject: [PATCH] refactor: swap to new template parser - get rid of SourceLocation.source for memory efficiency - move source location generation logic transform phase into the parser itself so that SourceLocation.source is no longer needed - move v-for expression parsing into the parser itself - added nameLoc on AttributeNode for use in transformElement Tests are not passing yet. --- .../compiler-core/__tests__/codegen.spec.ts | 1 + .../compiler-core/__tests__/parse.spec.ts | 40 +- .../compiler-core/__tests__/transform.spec.ts | 2 +- .../__tests__/transforms/vFor.spec.ts | 2 +- .../__tests__/transforms/vIf.spec.ts | 2 +- .../compiler-core/__tests__/utils.spec.ts | 27 - packages/compiler-core/src/ast.ts | 34 +- packages/compiler-core/src/codegen.ts | 2 +- .../compiler-core/src/compat/compatConfig.ts | 1 + packages/compiler-core/src/compile.ts | 2 +- packages/compiler-core/src/index.ts | 4 +- packages/compiler-core/src/options.ts | 8 - packages/compiler-core/src/parse.ts | 1190 ----------------- packages/compiler-core/src/parser/index.ts | 152 ++- .../src/transforms/transformElement.ts | 12 +- .../src/transforms/transformExpression.ts | 1 - packages/compiler-core/src/transforms/vFor.ts | 157 +-- .../compiler-core/src/transforms/vModel.ts | 4 +- .../compiler-core/src/transforms/vSlot.ts | 14 +- packages/compiler-core/src/utils.ts | 26 - packages/compiler-dom/src/parserOptions.ts | 21 +- .../compiler-dom/src/transforms/Transition.ts | 4 +- packages/compiler-sfc/src/parse.ts | 48 +- .../src/transforms/ssrTransformElement.ts | 7 +- packages/vue/src/index.ts | 2 - 25 files changed, 222 insertions(+), 1541 deletions(-) delete mode 100644 packages/compiler-core/src/parse.ts diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts index 802df84b3..d18d6cb38 100644 --- a/packages/compiler-core/__tests__/codegen.spec.ts +++ b/packages/compiler-core/__tests__/codegen.spec.ts @@ -40,6 +40,7 @@ import { PatchFlags } from '@vue/shared' function createRoot(options: Partial = {}): RootNode { return { type: NodeTypes.ROOT, + source: '', children: [], helpers: new Set(), components: [], diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index b43679614..3ce27a1f3 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -1,5 +1,4 @@ import { ParserOptions } from '../src/options' -import { TextModes } from '../src/parse' import { ErrorCodes } from '../src/errors' import { CommentNode, @@ -1913,35 +1912,38 @@ describe('compiler: parse', () => { }) test.skip('parse with correct location info', () => { + const fooSrc = `foo + is ` + const barSrc = `{{ bar }}` + const butSrc = ` but ` + const bazSrc = `{{ baz }}` const [foo, bar, but, baz] = baseParse( - ` -foo - is {{ bar }} but {{ baz }}`.trim() + fooSrc + barSrc + butSrc + bazSrc ).children let offset = 0 expect(foo.loc.start).toEqual({ line: 1, column: 1, offset }) - offset += foo.loc.source.length + offset += fooSrc.length expect(foo.loc.end).toEqual({ line: 2, column: 5, offset }) expect(bar.loc.start).toEqual({ line: 2, column: 5, offset }) const barInner = (bar as InterpolationNode).content offset += 3 expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset }) - offset += barInner.loc.source.length + offset += 3 expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset }) offset += 3 expect(bar.loc.end).toEqual({ line: 2, column: 14, offset }) expect(but.loc.start).toEqual({ line: 2, column: 14, offset }) - offset += but.loc.source.length + offset += butSrc.length expect(but.loc.end).toEqual({ line: 2, column: 19, offset }) expect(baz.loc.start).toEqual({ line: 2, column: 19, offset }) const bazInner = (baz as InterpolationNode).content offset += 3 expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset }) - offset += bazInner.loc.source.length + offset += 3 expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset }) offset += 3 expect(baz.loc.end).toEqual({ line: 2, column: 28, offset }) @@ -2073,8 +2075,7 @@ foo test.skip('should NOT condense whitespaces in RCDATA text mode', () => { const ast = baseParse(``, { - getTextMode: ({ tag }) => - tag === 'textarea' ? TextModes.RCDATA : TextModes.DATA + parseMode: 'html' }) const preElement = ast.children[0] as ElementNode expect(preElement.children).toHaveLength(1) @@ -3069,24 +3070,7 @@ foo () => { const spy = vi.fn() const ast = baseParse(code, { - getNamespace: (tag, parent) => { - const ns = parent ? parent.ns : Namespaces.HTML - if (ns === Namespaces.HTML) { - if (tag === 'svg') { - return (Namespaces.HTML + 1) as any - } - } - return ns - }, - getTextMode: ({ tag }) => { - if (tag === 'textarea') { - return TextModes.RCDATA - } - if (tag === 'script') { - return TextModes.RAWTEXT - } - return TextModes.DATA - }, + parseMode: 'html', ...options, onError: spy }) diff --git a/packages/compiler-core/__tests__/transform.spec.ts b/packages/compiler-core/__tests__/transform.spec.ts index 042865efb..f0e91108e 100644 --- a/packages/compiler-core/__tests__/transform.spec.ts +++ b/packages/compiler-core/__tests__/transform.spec.ts @@ -1,4 +1,4 @@ -import { baseParse } from '../src/parse' +import { baseParse } from '../src/parser' import { transform, NodeTransform } from '../src/transform' import { ElementNode, diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts index 60a9378eb..c5132b68d 100644 --- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts @@ -1,4 +1,4 @@ -import { baseParse as parse } from '../../src/parse' +import { baseParse as parse } from '../../src/parser' import { transform } from '../../src/transform' import { transformIf } from '../../src/transforms/vIf' import { transformFor } from '../../src/transforms/vFor' diff --git a/packages/compiler-core/__tests__/transforms/vIf.spec.ts b/packages/compiler-core/__tests__/transforms/vIf.spec.ts index 5bc9bedd5..39fa13f00 100644 --- a/packages/compiler-core/__tests__/transforms/vIf.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vIf.spec.ts @@ -1,4 +1,4 @@ -import { baseParse as parse } from '../../src/parse' +import { baseParse as parse } from '../../src/parser' import { transform } from '../../src/transform' import { transformIf } from '../../src/transforms/vIf' import { transformElement } from '../../src/transforms/transformElement' diff --git a/packages/compiler-core/__tests__/utils.spec.ts b/packages/compiler-core/__tests__/utils.spec.ts index 45fa46fea..7baa1ae89 100644 --- a/packages/compiler-core/__tests__/utils.spec.ts +++ b/packages/compiler-core/__tests__/utils.spec.ts @@ -1,7 +1,6 @@ import { TransformContext } from '../src' import { Position } from '../src/ast' import { - getInnerRange, advancePositionWithClone, isMemberExpressionNode, isMemberExpressionBrowser, @@ -41,32 +40,6 @@ describe('advancePositionWithClone', () => { }) }) -describe('getInnerRange', () => { - const loc1 = { - source: 'foo\nbar\nbaz', - start: p(1, 1, 0), - end: p(3, 3, 11) - } - - test('at start', () => { - const loc2 = getInnerRange(loc1, 0, 4) - expect(loc2.start).toEqual(loc1.start) - expect(loc2.end.column).toBe(1) - expect(loc2.end.line).toBe(2) - expect(loc2.end.offset).toBe(4) - }) - - test('in between', () => { - const loc2 = getInnerRange(loc1, 4, 3) - expect(loc2.start.column).toBe(1) - expect(loc2.start.line).toBe(2) - expect(loc2.start.offset).toBe(4) - expect(loc2.end.column).toBe(4) - expect(loc2.end.line).toBe(2) - expect(loc2.end.offset).toBe(7) - }) -}) - describe('isMemberExpression', () => { function commonAssertions(fn: (str: string) => boolean) { // should work diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 8c3a18ec4..cc581fff1 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -1,5 +1,4 @@ import { isString } from '@vue/shared' -import { ForParseResult } from './transforms/vFor' import { RENDER_SLOT, CREATE_SLOTS, @@ -76,7 +75,6 @@ export interface Node { export interface SourceLocation { start: Position end: Position - source: string } export interface Position { @@ -102,6 +100,7 @@ export type TemplateChildNode = export interface RootNode extends Node { type: NodeTypes.ROOT + source: string children: TemplateChildNode[] helpers: Set components: string[] @@ -182,20 +181,33 @@ export interface CommentNode extends Node { export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE name: string + nameLoc: SourceLocation value: TextNode | undefined } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE + /** + * the normalized name without prefix or shorthands, e.g. "bind", "on" + */ name: string + /** + * the raw attribute name, preserving shorthand, and including arg & modifiers + * this is only used during parse. + */ + rawName?: string exp: ExpressionNode | undefined + /** + * the raw expression as a string + * only required on directives parsed from templates + */ + rawExp?: string arg: ExpressionNode | undefined modifiers: string[] - raw?: string /** * optional property to cache the expression parse result for v-for */ - parseResult?: ForParseResult + forParseResult?: ForParseResult } /** @@ -277,6 +289,14 @@ export interface ForNode extends Node { codegenNode?: ForCodegenNode } +export interface ForParseResult { + source: ExpressionNode + value: ExpressionNode | undefined + key: ExpressionNode | undefined + index: ExpressionNode | undefined + finalized: boolean +} + export interface TextCallNode extends Node { type: NodeTypes.TEXT_CALL content: TextNode | InterpolationNode | CompoundExpressionNode @@ -548,17 +568,17 @@ export interface ForIteratorExpression extends FunctionExpression { // associated with template nodes, so their source locations are just a stub. // Container types like CompoundExpression also don't need a real location. export const locStub: SourceLocation = { - source: '', start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } export function createRoot( children: TemplateChildNode[], - loc = locStub + source = '' ): RootNode { return { type: NodeTypes.ROOT, + source, children, helpers: new Set(), components: [], @@ -568,7 +588,7 @@ export function createRoot( cached: 0, temps: 0, codegenNode: undefined, - loc + loc: locStub } } diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index ceae49982..fb3040dd9 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -116,7 +116,7 @@ function createCodegenContext( ssr, isTS, inSSR, - source: ast.loc.source, + source: ast.source, code: ``, column: 1, line: 1, diff --git a/packages/compiler-core/src/compat/compatConfig.ts b/packages/compiler-core/src/compat/compatConfig.ts index dcb304263..b643e8011 100644 --- a/packages/compiler-core/src/compat/compatConfig.ts +++ b/packages/compiler-core/src/compat/compatConfig.ts @@ -1,5 +1,6 @@ import { SourceLocation } from '../ast' import { CompilerError } from '../errors' +// @ts-expect-error TODO import { ParserContext } from '../parse' import { TransformContext } from '../transform' diff --git a/packages/compiler-core/src/compile.ts b/packages/compiler-core/src/compile.ts index 01cb560cc..c2aa07346 100644 --- a/packages/compiler-core/src/compile.ts +++ b/packages/compiler-core/src/compile.ts @@ -1,5 +1,5 @@ import { CompilerOptions } from './options' -import { baseParse } from './parse' +import { baseParse } from './parser/index' import { transform, NodeTransform, DirectiveTransform } from './transform' import { generate, CodegenResult } from './codegen' import { RootNode } from './ast' diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index c88844a5c..74ca59e69 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -10,7 +10,7 @@ export { type BindingMetadata, BindingTypes } from './options' -export { baseParse, TextModes } from './parse' +export { baseParse } from './parser' export { transform, type TransformContext, @@ -70,5 +70,3 @@ export { warnDeprecation, CompilerDeprecationTypes } from './compat/compatConfig' - -export { baseParse as newParse } from './parser/index' diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts index 3ab42625e..491d1eafa 100644 --- a/packages/compiler-core/src/options.ts +++ b/packages/compiler-core/src/options.ts @@ -1,5 +1,4 @@ import { ElementNode, Namespace, TemplateChildNode, ParentNode } from './ast' -import { TextModes } from './parse' import { CompilerError } from './errors' import { NodeTransform, @@ -42,13 +41,6 @@ export interface ParserOptions * Get tag namespace */ getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace - /** - * Get text parsing mode for this element - */ - getTextMode?: ( - node: ElementNode, - parent: ElementNode | undefined - ) => TextModes /** * @default ['{{', '}}'] */ diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts deleted file mode 100644 index d9cdb4e59..000000000 --- a/packages/compiler-core/src/parse.ts +++ /dev/null @@ -1,1190 +0,0 @@ -import { ErrorHandlingOptions, ParserOptions } from './options' -import { NO, isArray, makeMap, extend } from '@vue/shared' -import { - ErrorCodes, - createCompilerError, - defaultOnError, - defaultOnWarn -} from './errors' -import { - assert, - advancePositionWithMutation, - advancePositionWithClone, - isCoreComponent, - isStaticArgOf -} from './utils' -import { - Namespaces, - AttributeNode, - CommentNode, - DirectiveNode, - ElementNode, - ElementTypes, - ExpressionNode, - NodeTypes, - Position, - RootNode, - SourceLocation, - TextNode, - TemplateChildNode, - InterpolationNode, - createRoot, - ConstantTypes -} from './ast' -import { - checkCompatEnabled, - CompilerCompatOptions, - CompilerDeprecationTypes, - isCompatEnabled, - warnDeprecation -} from './compat/compatConfig' - -type OptionalOptions = - | 'parseMode' - | 'whitespace' - | 'isNativeTag' - | 'isBuiltInComponent' - | keyof CompilerCompatOptions -type MergedParserOptions = Omit, OptionalOptions> & - Pick -type AttributeValue = - | { - content: string - isQuoted: boolean - loc: SourceLocation - } - | undefined - -// The default decoder only provides escapes for characters reserved as part of -// the template syntax, and is only used if the custom renderer did not provide -// a platform-specific decoder. -const decodeRE = /&(gt|lt|amp|apos|quot);/g -const decodeMap: Record = { - gt: '>', - lt: '<', - amp: '&', - apos: "'", - quot: '"' -} - -export const defaultParserOptions: MergedParserOptions = { - delimiters: [`{{`, `}}`], - getNamespace: () => Namespaces.HTML, - getTextMode: () => TextModes.DATA, - isVoidTag: NO, - isPreTag: NO, - isCustomElement: NO, - decodeEntities: (rawText: string): string => - rawText.replace(decodeRE, (_, p1) => decodeMap[p1]), - onError: defaultOnError, - onWarn: defaultOnWarn, - comments: __DEV__ -} - -export const enum TextModes { - // | Elements | Entities | End sign | Inside of - DATA, // | ✔ | ✔ | End tags of ancestors | - RCDATA, // | ✘ | ✔ | End tag of the parent |