From f7def3004dbdc4ceea857f421feafca4812475cc Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Mon, 14 Oct 2019 20:00:07 +0800 Subject: [PATCH 1/4] feat(compiler-core): more hoisting optimizations --- .../__snapshots__/parse.spec.ts.snap | 12 ++ .../compiler-core/__tests__/parse.spec.ts | 20 ++ .../__snapshots__/hoistStatic.spec.ts.snap | 51 +++++ .../__tests__/transforms/hoistStatic.spec.ts | 177 +++++++++++++++++- packages/compiler-core/src/ast.ts | 7 +- packages/compiler-core/src/parse.ts | 9 + .../src/transforms/hoistStatic.ts | 26 ++- .../src/transforms/transformElement.ts | 7 +- .../src/transforms/transformExpression.ts | 21 ++- packages/compiler-dom/__tests__/parse.spec.ts | 1 + 10 files changed, 318 insertions(+), 13 deletions(-) diff --git a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap index 2b733c3f5a7..6c2f7b5e1a4 100644 --- a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap @@ -3986,6 +3986,7 @@ Object { Object { "content": Object { "content": "a < b", + "hasPrefixedIdentifier": true, "isStatic": false, "loc": Object { "end": Object { @@ -7151,6 +7152,7 @@ Object { Object { "content": Object { "content": "''", + "hasPrefixedIdentifier": true, "isStatic": false, "loc": Object { "end": Object { @@ -7320,6 +7322,7 @@ Object { Object { "arg": Object { "content": "se", + "hasPrefixedIdentifier": true, "isStatic": false, "loc": Object { "end": Object { @@ -7640,6 +7643,7 @@ Object { Object { "content": Object { "content": "", + "hasPrefixedIdentifier": true, "isStatic": false, "loc": Object { "end": Object { @@ -7798,6 +7802,7 @@ Object { Object { "arg": Object { "content": "class", + "hasPrefixedIdentifier": false, "isStatic": true, "loc": Object { "end": Object { @@ -7816,6 +7821,7 @@ Object { }, "exp": Object { "content": "{ some: condition }", + "hasPrefixedIdentifier": true, "isStatic": false, "loc": Object { "end": Object { @@ -7876,6 +7882,7 @@ Object { Object { "arg": Object { "content": "style", + "hasPrefixedIdentifier": false, "isStatic": true, "loc": Object { "end": Object { @@ -7894,6 +7901,7 @@ Object { }, "exp": Object { "content": "{ color: 'red' }", + "hasPrefixedIdentifier": true, "isStatic": false, "loc": Object { "end": Object { @@ -7983,6 +7991,7 @@ Object { Object { "arg": Object { "content": "style", + "hasPrefixedIdentifier": false, "isStatic": true, "loc": Object { "end": Object { @@ -8001,6 +8010,7 @@ Object { }, "exp": Object { "content": "{ color: 'red' }", + "hasPrefixedIdentifier": true, "isStatic": false, "loc": Object { "end": Object { @@ -8080,6 +8090,7 @@ Object { Object { "arg": Object { "content": "class", + "hasPrefixedIdentifier": false, "isStatic": true, "loc": Object { "end": Object { @@ -8098,6 +8109,7 @@ Object { }, "exp": Object { "content": "{ some: condition }", + "hasPrefixedIdentifier": true, "isStatic": false, "loc": Object { "end": Object { diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index e4dcf2b2b54..c9eea6bb6e6 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -298,6 +298,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: `message`, isStatic: false, + hasPrefixedIdentifier: true, loc: { start: { offset: 2, line: 1, column: 3 }, end: { offset: 9, line: 1, column: 10 }, @@ -322,6 +323,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: `a { type: NodeTypes.SIMPLE_EXPRESSION, content: `a { content: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: false, + hasPrefixedIdentifier: true, content: 'c>d', loc: { start: { offset: 12, line: 1, column: 13 }, @@ -390,6 +394,8 @@ describe('compiler: parse', () => { content: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: false, + // The `hasPrefixedIdentifier` is the default value and will be determined in `transformExpression`. + hasPrefixedIdentifier: true, content: '""', loc: { start: { offset: 8, line: 1, column: 9 }, @@ -974,6 +980,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: false, + hasPrefixedIdentifier: true, loc: { start: { offset: 11, line: 1, column: 12 }, end: { offset: 12, line: 1, column: 13 }, @@ -999,6 +1006,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'click', isStatic: true, + hasPrefixedIdentifier: false, loc: { source: 'click', @@ -1071,6 +1079,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'click', isStatic: true, + hasPrefixedIdentifier: false, loc: { source: 'click', @@ -1107,6 +1116,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, + hasPrefixedIdentifier: false, loc: { source: 'a', @@ -1127,6 +1137,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, + hasPrefixedIdentifier: true, loc: { start: { offset: 8, line: 1, column: 9 }, @@ -1153,6 +1164,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, + hasPrefixedIdentifier: false, loc: { source: 'a', @@ -1173,6 +1185,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, + hasPrefixedIdentifier: true, loc: { start: { offset: 13, line: 1, column: 14 }, @@ -1199,6 +1212,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, + hasPrefixedIdentifier: false, loc: { source: 'a', @@ -1219,6 +1233,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, + hasPrefixedIdentifier: true, loc: { start: { offset: 8, line: 1, column: 9 }, @@ -1245,6 +1260,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, + hasPrefixedIdentifier: false, loc: { source: 'a', @@ -1265,6 +1281,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, + hasPrefixedIdentifier: true, loc: { start: { offset: 14, line: 1, column: 15 }, @@ -1291,6 +1308,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, + hasPrefixedIdentifier: false, loc: { source: 'a', start: { @@ -1310,6 +1328,8 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: '{ b }', isStatic: false, + // The `hasPrefixedIdentifier` is the default value and will be determined in transformExpression + hasPrefixedIdentifier: true, loc: { start: { offset: 10, line: 1, column: 11 }, end: { offset: 15, line: 1, column: 16 }, diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap index bebf5aada1c..7fcbf1ad607 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap @@ -152,6 +152,57 @@ return function render() { }" `; +exports[`compiler: hoistStatic transform prefixIdentifiers hoist class with static object value 1`] = ` +"const _Vue = Vue +const _createVNode = Vue.createVNode + +const _hoisted_1 = { class: { foo: true }} + +return function render() { + with (this) { + const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue + + return (_openBlock(), _createBlock(\\"div\\", null, [ + _createVNode(\\"span\\", _hoisted_1, _toString(_ctx.bar), 1 /* TEXT */) + ])) + } +}" +`; + +exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static interpolation 1`] = ` +"const _Vue = Vue +const _createVNode = Vue.createVNode + +const _hoisted_1 = _createVNode(\\"span\\", null, [\\"foo \\", _toString(1), _toString(2)]) + +return function render() { + with (this) { + const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue + + return (_openBlock(), _createBlock(\\"div\\", null, [ + _hoisted_1 + ])) + } +}" +`; + +exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static tree with static prop value 1`] = ` +"const _Vue = Vue +const _createVNode = Vue.createVNode + +const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toString(1), 1 /* TEXT */) + +return function render() { + with (this) { + const { toString: _toString, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue + + return (_openBlock(), _createBlock(\\"div\\", null, [ + _hoisted_1 + ])) + } +}" +`; + exports[`compiler: hoistStatic transform should NOT hoist components 1`] = ` "const _Vue = Vue diff --git a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts index 60ae861bf58..46861d47f12 100644 --- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts +++ b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts @@ -1,4 +1,10 @@ -import { parse, transform, NodeTypes, generate } from '../../src' +import { + parse, + transform, + NodeTypes, + generate, + CompilerOptions +} from '../../src' import { OPEN_BLOCK, CREATE_BLOCK, @@ -8,17 +14,24 @@ import { RENDER_LIST } from '../../src/runtimeHelpers' import { transformElement } from '../../src/transforms/transformElement' +import { transformExpression } from '../../src/transforms/transformExpression' import { transformIf } from '../../src/transforms/vIf' import { transformFor } from '../../src/transforms/vFor' import { transformBind } from '../../src/transforms/vBind' import { createObjectMatcher, genFlagText } from '../testUtils' import { PatchFlags } from '@vue/shared' -function transformWithHoist(template: string) { +function transformWithHoist(template: string, options: CompilerOptions = {}) { const ast = parse(template) transform(ast, { hoistStatic: true, - nodeTransforms: [transformIf, transformFor, transformElement], + prefixIdentifiers: options.prefixIdentifiers, + nodeTransforms: [ + transformIf, + transformFor, + ...(options.prefixIdentifiers ? [transformExpression] : []), + transformElement + ], directiveTransforms: { bind: transformBind } @@ -429,4 +442,162 @@ describe('compiler: hoistStatic transform', () => { }) expect(generate(root).code).toMatchSnapshot() }) + + describe('prefixIdentifiers', () => { + test('hoist nested static tree with static interpolation', () => { + const { root, args } = transformWithHoist( + `
foo {{ 1 }} {{ 2 }}
`, + { + prefixIdentifiers: true + } + ) + expect(root.hoists).toMatchObject([ + { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: CREATE_VNODE, + arguments: [ + `"span"`, + `null`, + [ + { + type: NodeTypes.TEXT, + content: `foo ` + }, + { + type: NodeTypes.INTERPOLATION, + content: { + content: `1`, + isStatic: false, + hasPrefixedIdentifier: false + } + }, + { + type: NodeTypes.INTERPOLATION, + content: { + content: `2`, + isStatic: false, + hasPrefixedIdentifier: false + } + } + ] + ] + } + ]) + expect(args).toMatchObject([ + `"div"`, + `null`, + [ + { + type: NodeTypes.ELEMENT, + codegenNode: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_hoisted_1` + } + } + ] + ]) + expect(generate(root).code).toMatchSnapshot() + }) + + test('hoist nested static tree with static prop value', () => { + const { root, args } = transformWithHoist( + `
{{ 1 }}
`, + { + prefixIdentifiers: true + } + ) + + expect(root.hoists).toMatchObject([ + { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: CREATE_VNODE, + arguments: [ + `"span"`, + createObjectMatcher({ foo: `[0]` }), + { + type: NodeTypes.INTERPOLATION, + content: { + content: `1`, + isStatic: false, + hasPrefixedIdentifier: false + } + }, + '1 /* TEXT */' + ] + } + ]) + expect(args).toMatchObject([ + `"div"`, + `null`, + [ + { + type: NodeTypes.ELEMENT, + codegenNode: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_hoisted_1` + } + } + ] + ]) + expect(generate(root).code).toMatchSnapshot() + }) + + test('hoist class with static object value', () => { + const { root, args } = transformWithHoist( + `
{{ bar }}
`, + { + prefixIdentifiers: true + } + ) + + expect(root.hoists).toMatchObject([ + { + type: NodeTypes.JS_OBJECT_EXPRESSION, + properties: [ + { + key: { + content: `class`, + hasPrefixedIdentifier: false, + isStatic: true + }, + value: { + content: `{ foo: true }`, + hasPrefixedIdentifier: false, + isStatic: false + } + } + ] + } + ]) + expect(args).toMatchObject([ + `"div"`, + `null`, + [ + { + type: NodeTypes.ELEMENT, + codegenNode: { + callee: CREATE_VNODE, + arguments: [ + `"span"`, + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_hoisted_1` + }, + { + type: NodeTypes.INTERPOLATION, + content: { + content: `_ctx.bar`, + hasPrefixedIdentifier: true, + isStatic: false + } + }, + `1 /* TEXT */` + ] + } + } + ] + ]) + expect(generate(root).code).toMatchSnapshot() + }) + }) }) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index b6442202dfe..ffb490a4c5c 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -165,6 +165,9 @@ export interface SimpleExpressionNode extends Node { type: NodeTypes.SIMPLE_EXPRESSION content: string isStatic: boolean + // Is used to indicate whether there is an identifier + // in an expression that needs to be prefixed with `_ctx.`. + hasPrefixedIdentifier: boolean // an expression parsed as the params of a function will track // the identifiers declared inside the function body. identifiers?: string[] @@ -494,11 +497,13 @@ export function createObjectProperty( export function createSimpleExpression( content: SimpleExpressionNode['content'], isStatic: SimpleExpressionNode['isStatic'], - loc: SourceLocation = locStub + loc: SourceLocation = locStub, + hasPrefixedIdentifier: boolean = true ): SimpleExpressionNode { return { type: NodeTypes.SIMPLE_EXPRESSION, loc, + hasPrefixedIdentifier, content, isStatic } diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 1e8a6d2a81a..66e31d60ba3 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -563,9 +563,13 @@ function parseAttribute( ) let content = match[2] let isStatic = true + // Used to indicate whether the arg needs to be prefixed, + // only dynamic arg need to be prefixed + let hasPrefixedIdentifier = false if (content.startsWith('[')) { isStatic = false + hasPrefixedIdentifier = true if (!content.endsWith(']')) { emitError( @@ -581,6 +585,7 @@ function parseAttribute( type: NodeTypes.SIMPLE_EXPRESSION, content, isStatic, + hasPrefixedIdentifier, loc } } @@ -606,6 +611,8 @@ function parseAttribute( type: NodeTypes.SIMPLE_EXPRESSION, content: value.content, isStatic: false, + // Set `hasPrefixedIdentifier` to true by default and will decide in transformExpression + hasPrefixedIdentifier: true, loc: value.loc }, arg, @@ -712,6 +719,8 @@ function parseInterpolation( content: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: false, + // Set `hasPrefixedIdentifier` to true by default and will decide in transformExpression + hasPrefixedIdentifier: true, content, loc: getSelection(context, innerStart, innerEnd) }, diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts index e74a2e7de34..cb3b25f9c4c 100644 --- a/packages/compiler-core/src/transforms/hoistStatic.ts +++ b/packages/compiler-core/src/transforms/hoistStatic.ts @@ -119,7 +119,7 @@ function isStaticNode( return resultCache.get(node) as boolean } const flag = getPatchFlag(node) - if (!flag) { + if (!flag || flag === PatchFlags.TEXT) { // element self is static. check its children. for (let i = 0; i < node.children.length; i++) { if (!isStaticNode(node.children[i], resultCache)) { @@ -137,9 +137,29 @@ function isStaticNode( return true case NodeTypes.IF: case NodeTypes.FOR: - case NodeTypes.INTERPOLATION: - case NodeTypes.COMPOUND_EXPRESSION: return false + case NodeTypes.COMPOUND_EXPRESSION: + return node.children.every(n => { + return ( + typeof n === 'string' || + typeof n === 'symbol' || + // NodeTypes.TEXT is static. + n.type === NodeTypes.TEXT || + // Expressions without identifiers are static. + (n.type === NodeTypes.SIMPLE_EXPRESSION && + !n.hasPrefixedIdentifier) || + // Check NodeTypes.INTERPOLATION + (n.type === NodeTypes.INTERPOLATION && isStaticNode(n, resultCache)) + ) + }) + case NodeTypes.INTERPOLATION: + // Representing this interpolation is static + return ( + (node.content.type === NodeTypes.SIMPLE_EXPRESSION && + !node.content.hasPrefixedIdentifier) || + (node.content.type === NodeTypes.COMPOUND_EXPRESSION && + isStaticNode(node.content, resultCache)) + ) default: if (__DEV__) { const exhaustiveCheck: never = node diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 3df966a8f2c..132663cd3f3 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -183,7 +183,12 @@ export function buildProps( const analyzePatchFlag = ({ key, value }: Property) => { if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) { - if (value.type !== NodeTypes.SIMPLE_EXPRESSION || !value.isStatic) { + if ( + value.type !== NodeTypes.SIMPLE_EXPRESSION || + // E.g:

. + // Do not add prop `foo` to `dynamicPropNames`. + (!value.isStatic && value.hasPrefixedIdentifier) + ) { const name = key.content if (name === 'ref') { hasRef = true diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index e3c2c028b80..da3a362b0ba 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -61,6 +61,7 @@ export const transformExpression: NodeTransform = (node, context) => { interface PrefixMeta { prefix?: string + prefixedName: boolean start: number end: number scopeIds?: Set @@ -108,6 +109,7 @@ export function processExpression( const ids: (Identifier & PrefixMeta)[] = [] const knownIds = Object.create(context.identifiers) + let hasPrefixedIdentifier = false // walk the AST and look for identifiers that need to be prefixed with `_ctx.`. walkJS(ast, { enter(node: Node & PrefixMeta, parent) { @@ -120,10 +122,13 @@ export function processExpression( node.prefix = `${node.name}: ` } node.name = `_ctx.${node.name}` + hasPrefixedIdentifier = true + node.prefixedName = true ids.push(node) } else if (!isStaticPropertyKey(node, parent)) { // also generate sub-expressions for other identifiers for better // source map support. (except for property keys which are static) + node.prefixedName = false ids.push(node) } } @@ -190,11 +195,16 @@ export function processExpression( } const source = rawExp.slice(start, end) children.push( - createSimpleExpression(id.name, false, { - source, - start: advancePositionWithClone(node.loc.start, source, start), - end: advancePositionWithClone(node.loc.start, source, end) - }) + createSimpleExpression( + id.name, + false, + { + source, + start: advancePositionWithClone(node.loc.start, source, start), + end: advancePositionWithClone(node.loc.start, source, end) + }, + id.prefixedName /* hasPrefixedIdentifier */ + ) ) if (i === ids.length - 1 && end < rawExp.length) { children.push(rawExp.slice(end)) @@ -206,6 +216,7 @@ export function processExpression( ret = createCompoundExpression(children, node.loc) } else { ret = node + ret.hasPrefixedIdentifier = hasPrefixedIdentifier } ret.identifiers = Object.keys(knownIds) return ret diff --git a/packages/compiler-dom/__tests__/parse.spec.ts b/packages/compiler-dom/__tests__/parse.spec.ts index 752d08c4ff5..4a0dc067fe2 100644 --- a/packages/compiler-dom/__tests__/parse.spec.ts +++ b/packages/compiler-dom/__tests__/parse.spec.ts @@ -117,6 +117,7 @@ describe('DOM parser', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: `a < b`, isStatic: false, + hasPrefixedIdentifier: true, loc: { start: { offset: 8, line: 1, column: 9 }, end: { offset: 16, line: 1, column: 17 }, From 4e211edc84fb632986b904e9ba5a613b5072457d Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Mon, 14 Oct 2019 20:36:44 +0800 Subject: [PATCH 2/4] fix: typing --- packages/compiler-core/__tests__/utils.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/compiler-core/__tests__/utils.spec.ts b/packages/compiler-core/__tests__/utils.spec.ts index 417d0d18534..f5ab64e0bd6 100644 --- a/packages/compiler-core/__tests__/utils.spec.ts +++ b/packages/compiler-core/__tests__/utils.spec.ts @@ -79,6 +79,7 @@ describe('isEmptyExpression', () => { content: '', type: NodeTypes.SIMPLE_EXPRESSION, isStatic: true, + hasPrefixedIdentifier: true, loc: null as any }) ).toBe(true) @@ -90,6 +91,7 @@ describe('isEmptyExpression', () => { content: ' \t ', type: NodeTypes.SIMPLE_EXPRESSION, isStatic: true, + hasPrefixedIdentifier: true, loc: null as any }) ).toBe(true) @@ -101,6 +103,7 @@ describe('isEmptyExpression', () => { content: 'foo', type: NodeTypes.SIMPLE_EXPRESSION, isStatic: true, + hasPrefixedIdentifier: true, loc: null as any }) ).toBe(false) From 4f38f7851dfe6157bf2599da2b82d095733d0864 Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Mon, 14 Oct 2019 22:02:31 +0800 Subject: [PATCH 3/4] chore: some tweaks --- packages/compiler-core/__tests__/utils.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/compiler-core/__tests__/utils.spec.ts b/packages/compiler-core/__tests__/utils.spec.ts index f5ab64e0bd6..4a005d7c258 100644 --- a/packages/compiler-core/__tests__/utils.spec.ts +++ b/packages/compiler-core/__tests__/utils.spec.ts @@ -79,7 +79,7 @@ describe('isEmptyExpression', () => { content: '', type: NodeTypes.SIMPLE_EXPRESSION, isStatic: true, - hasPrefixedIdentifier: true, + hasPrefixedIdentifier: false, loc: null as any }) ).toBe(true) @@ -91,7 +91,7 @@ describe('isEmptyExpression', () => { content: ' \t ', type: NodeTypes.SIMPLE_EXPRESSION, isStatic: true, - hasPrefixedIdentifier: true, + hasPrefixedIdentifier: false, loc: null as any }) ).toBe(true) @@ -103,7 +103,7 @@ describe('isEmptyExpression', () => { content: 'foo', type: NodeTypes.SIMPLE_EXPRESSION, isStatic: true, - hasPrefixedIdentifier: true, + hasPrefixedIdentifier: false, loc: null as any }) ).toBe(false) From 3f77211bd7c801488d3888fff45159729caef3cd Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Tue, 15 Oct 2019 14:04:20 +0800 Subject: [PATCH 4/4] fix: NOT hoist expression that with scope variable --- .../__snapshots__/parse.spec.ts.snap | 24 +++++------ .../compiler-core/__tests__/parse.spec.ts | 40 +++++++++---------- .../__snapshots__/hoistStatic.spec.ts.snap | 36 +++++++++++++++++ .../__tests__/transforms/hoistStatic.spec.ts | 36 ++++++++++++++--- .../compiler-core/__tests__/utils.spec.ts | 6 +-- packages/compiler-core/src/ast.ts | 8 ++-- packages/compiler-core/src/parse.ts | 17 ++++---- .../src/transforms/hoistStatic.ts | 29 +++++--------- .../src/transforms/transformElement.ts | 2 +- .../src/transforms/transformExpression.ts | 18 +++++---- packages/compiler-dom/__tests__/parse.spec.ts | 2 +- 11 files changed, 134 insertions(+), 84 deletions(-) diff --git a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap index 6c2f7b5e1a4..62402884778 100644 --- a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap @@ -3986,7 +3986,7 @@ Object { Object { "content": Object { "content": "a < b", - "hasPrefixedIdentifier": true, + "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -7152,7 +7152,7 @@ Object { Object { "content": Object { "content": "''", - "hasPrefixedIdentifier": true, + "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -7322,7 +7322,7 @@ Object { Object { "arg": Object { "content": "se", - "hasPrefixedIdentifier": true, + "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -7643,7 +7643,7 @@ Object { Object { "content": Object { "content": "", - "hasPrefixedIdentifier": true, + "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -7802,7 +7802,7 @@ Object { Object { "arg": Object { "content": "class", - "hasPrefixedIdentifier": false, + "isConstant": true, "isStatic": true, "loc": Object { "end": Object { @@ -7821,7 +7821,7 @@ Object { }, "exp": Object { "content": "{ some: condition }", - "hasPrefixedIdentifier": true, + "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -7882,7 +7882,7 @@ Object { Object { "arg": Object { "content": "style", - "hasPrefixedIdentifier": false, + "isConstant": true, "isStatic": true, "loc": Object { "end": Object { @@ -7901,7 +7901,7 @@ Object { }, "exp": Object { "content": "{ color: 'red' }", - "hasPrefixedIdentifier": true, + "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -7991,7 +7991,7 @@ Object { Object { "arg": Object { "content": "style", - "hasPrefixedIdentifier": false, + "isConstant": true, "isStatic": true, "loc": Object { "end": Object { @@ -8010,7 +8010,7 @@ Object { }, "exp": Object { "content": "{ color: 'red' }", - "hasPrefixedIdentifier": true, + "isConstant": false, "isStatic": false, "loc": Object { "end": Object { @@ -8090,7 +8090,7 @@ Object { Object { "arg": Object { "content": "class", - "hasPrefixedIdentifier": false, + "isConstant": true, "isStatic": true, "loc": Object { "end": Object { @@ -8109,7 +8109,7 @@ Object { }, "exp": Object { "content": "{ some: condition }", - "hasPrefixedIdentifier": true, + "isConstant": false, "isStatic": false, "loc": Object { "end": Object { diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index c9eea6bb6e6..3e0ccef7986 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -298,7 +298,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: `message`, isStatic: false, - hasPrefixedIdentifier: true, + isConstant: false, loc: { start: { offset: 2, line: 1, column: 3 }, end: { offset: 9, line: 1, column: 10 }, @@ -323,7 +323,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: `a { type: NodeTypes.SIMPLE_EXPRESSION, content: `a { content: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: false, - hasPrefixedIdentifier: true, + isConstant: false, content: 'c>d', loc: { start: { offset: 12, line: 1, column: 13 }, @@ -394,8 +394,8 @@ describe('compiler: parse', () => { content: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: false, - // The `hasPrefixedIdentifier` is the default value and will be determined in `transformExpression`. - hasPrefixedIdentifier: true, + // The `isConstant` is the default value and will be determined in `transformExpression`. + isConstant: false, content: '""', loc: { start: { offset: 8, line: 1, column: 9 }, @@ -980,7 +980,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: false, - hasPrefixedIdentifier: true, + isConstant: false, loc: { start: { offset: 11, line: 1, column: 12 }, end: { offset: 12, line: 1, column: 13 }, @@ -1006,7 +1006,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'click', isStatic: true, - hasPrefixedIdentifier: false, + isConstant: true, loc: { source: 'click', @@ -1079,7 +1079,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'click', isStatic: true, - hasPrefixedIdentifier: false, + isConstant: true, loc: { source: 'click', @@ -1116,7 +1116,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - hasPrefixedIdentifier: false, + isConstant: true, loc: { source: 'a', @@ -1137,7 +1137,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - hasPrefixedIdentifier: true, + isConstant: false, loc: { start: { offset: 8, line: 1, column: 9 }, @@ -1164,7 +1164,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - hasPrefixedIdentifier: false, + isConstant: true, loc: { source: 'a', @@ -1185,7 +1185,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - hasPrefixedIdentifier: true, + isConstant: false, loc: { start: { offset: 13, line: 1, column: 14 }, @@ -1212,7 +1212,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - hasPrefixedIdentifier: false, + isConstant: true, loc: { source: 'a', @@ -1233,7 +1233,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - hasPrefixedIdentifier: true, + isConstant: false, loc: { start: { offset: 8, line: 1, column: 9 }, @@ -1260,7 +1260,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - hasPrefixedIdentifier: false, + isConstant: true, loc: { source: 'a', @@ -1281,7 +1281,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - hasPrefixedIdentifier: true, + isConstant: false, loc: { start: { offset: 14, line: 1, column: 15 }, @@ -1308,7 +1308,7 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - hasPrefixedIdentifier: false, + isConstant: true, loc: { source: 'a', start: { @@ -1328,8 +1328,8 @@ describe('compiler: parse', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: '{ b }', isStatic: false, - // The `hasPrefixedIdentifier` is the default value and will be determined in transformExpression - hasPrefixedIdentifier: true, + // The `isConstant` is the default value and will be determined in transformExpression + isConstant: false, loc: { start: { offset: 10, line: 1, column: 11 }, end: { offset: 15, line: 1, column: 16 }, diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap index 7fcbf1ad607..dba4bf1f095 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap @@ -203,6 +203,42 @@ return function render() { }" `; +exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that with scope variable (2) 1`] = ` +"const _Vue = Vue + +return function render() { + with (this) { + const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue + + return (_openBlock(), _createBlock(\\"div\\", null, [ + (_openBlock(), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => { + return (_openBlock(), _createBlock(\\"p\\", null, [ + _createVNode(\\"span\\", null, _toString(o + 'foo'), 1 /* TEXT */) + ])) + }), 128 /* UNKEYED_FRAGMENT */)) + ])) + } +}" +`; + +exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist expressions that with scope variable 1`] = ` +"const _Vue = Vue + +return function render() { + with (this) { + const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, toString: _toString, createVNode: _createVNode } = _Vue + + return (_openBlock(), _createBlock(\\"div\\", null, [ + (_openBlock(), _createBlock(_Fragment, null, _renderList(_ctx.list, (o) => { + return (_openBlock(), _createBlock(\\"p\\", null, [ + _createVNode(\\"span\\", null, _toString(o), 1 /* TEXT */) + ])) + }), 128 /* UNKEYED_FRAGMENT */)) + ])) + } +}" +`; + exports[`compiler: hoistStatic transform should NOT hoist components 1`] = ` "const _Vue = Vue diff --git a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts index 46861d47f12..d998aa26cf6 100644 --- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts +++ b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts @@ -468,7 +468,7 @@ describe('compiler: hoistStatic transform', () => { content: { content: `1`, isStatic: false, - hasPrefixedIdentifier: false + isConstant: true } }, { @@ -476,7 +476,7 @@ describe('compiler: hoistStatic transform', () => { content: { content: `2`, isStatic: false, - hasPrefixedIdentifier: false + isConstant: true } } ] @@ -519,7 +519,7 @@ describe('compiler: hoistStatic transform', () => { content: { content: `1`, isStatic: false, - hasPrefixedIdentifier: false + isConstant: true } }, '1 /* TEXT */' @@ -557,12 +557,12 @@ describe('compiler: hoistStatic transform', () => { { key: { content: `class`, - hasPrefixedIdentifier: false, + isConstant: true, isStatic: true }, value: { content: `{ foo: true }`, - hasPrefixedIdentifier: false, + isConstant: true, isStatic: false } } @@ -587,7 +587,7 @@ describe('compiler: hoistStatic transform', () => { type: NodeTypes.INTERPOLATION, content: { content: `_ctx.bar`, - hasPrefixedIdentifier: true, + isConstant: false, isStatic: false } }, @@ -599,5 +599,29 @@ describe('compiler: hoistStatic transform', () => { ]) expect(generate(root).code).toMatchSnapshot() }) + + test('should NOT hoist expressions that with scope variable', () => { + const { root } = transformWithHoist( + `

{{ o }}

`, + { + prefixIdentifiers: true + } + ) + + expect(root.hoists.length).toBe(0) + expect(generate(root).code).toMatchSnapshot() + }) + + test('should NOT hoist expressions that with scope variable (2)', () => { + const { root } = transformWithHoist( + `

{{ o + 'foo' }}

`, + { + prefixIdentifiers: true + } + ) + + expect(root.hoists.length).toBe(0) + expect(generate(root).code).toMatchSnapshot() + }) }) }) diff --git a/packages/compiler-core/__tests__/utils.spec.ts b/packages/compiler-core/__tests__/utils.spec.ts index 4a005d7c258..d93d321299b 100644 --- a/packages/compiler-core/__tests__/utils.spec.ts +++ b/packages/compiler-core/__tests__/utils.spec.ts @@ -79,7 +79,7 @@ describe('isEmptyExpression', () => { content: '', type: NodeTypes.SIMPLE_EXPRESSION, isStatic: true, - hasPrefixedIdentifier: false, + isConstant: true, loc: null as any }) ).toBe(true) @@ -91,7 +91,7 @@ describe('isEmptyExpression', () => { content: ' \t ', type: NodeTypes.SIMPLE_EXPRESSION, isStatic: true, - hasPrefixedIdentifier: false, + isConstant: true, loc: null as any }) ).toBe(true) @@ -103,7 +103,7 @@ describe('isEmptyExpression', () => { content: 'foo', type: NodeTypes.SIMPLE_EXPRESSION, isStatic: true, - hasPrefixedIdentifier: false, + isConstant: true, loc: null as any }) ).toBe(false) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index ffb490a4c5c..d866e950153 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -165,9 +165,7 @@ export interface SimpleExpressionNode extends Node { type: NodeTypes.SIMPLE_EXPRESSION content: string isStatic: boolean - // Is used to indicate whether there is an identifier - // in an expression that needs to be prefixed with `_ctx.`. - hasPrefixedIdentifier: boolean + isConstant: boolean // an expression parsed as the params of a function will track // the identifiers declared inside the function body. identifiers?: string[] @@ -498,12 +496,12 @@ export function createSimpleExpression( content: SimpleExpressionNode['content'], isStatic: SimpleExpressionNode['isStatic'], loc: SourceLocation = locStub, - hasPrefixedIdentifier: boolean = true + isConstant: boolean = false ): SimpleExpressionNode { return { type: NodeTypes.SIMPLE_EXPRESSION, loc, - hasPrefixedIdentifier, + isConstant, content, isStatic } diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 66e31d60ba3..916f4925605 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -563,13 +563,12 @@ function parseAttribute( ) let content = match[2] let isStatic = true - // Used to indicate whether the arg needs to be prefixed, - // only dynamic arg need to be prefixed - let hasPrefixedIdentifier = false + // Non-dynamic arg is a constant. + let isConstant = true if (content.startsWith('[')) { isStatic = false - hasPrefixedIdentifier = true + isConstant = false if (!content.endsWith(']')) { emitError( @@ -585,7 +584,7 @@ function parseAttribute( type: NodeTypes.SIMPLE_EXPRESSION, content, isStatic, - hasPrefixedIdentifier, + isConstant, loc } } @@ -611,8 +610,8 @@ function parseAttribute( type: NodeTypes.SIMPLE_EXPRESSION, content: value.content, isStatic: false, - // Set `hasPrefixedIdentifier` to true by default and will decide in transformExpression - hasPrefixedIdentifier: true, + // Set `isConstant` to false by default and will decide in transformExpression + isConstant: false, loc: value.loc }, arg, @@ -719,8 +718,8 @@ function parseInterpolation( content: { type: NodeTypes.SIMPLE_EXPRESSION, isStatic: false, - // Set `hasPrefixedIdentifier` to true by default and will decide in transformExpression - hasPrefixedIdentifier: true, + // Set `isConstant` to false by default and will decide in transformExpression + isConstant: false, content, loc: getSelection(context, innerStart, innerEnd) }, diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts index cb3b25f9c4c..5743d68136c 100644 --- a/packages/compiler-core/src/transforms/hoistStatic.ts +++ b/packages/compiler-core/src/transforms/hoistStatic.ts @@ -2,6 +2,7 @@ import { RootNode, NodeTypes, TemplateChildNode, + SimpleExpressionNode, ElementTypes, ElementCodegenNode, PlainElementNode, @@ -11,7 +12,7 @@ import { } from '../ast' import { TransformContext } from '../transform' import { APPLY_DIRECTIVES } from '../runtimeHelpers' -import { PatchFlags } from '@vue/shared' +import { PatchFlags, isString, isSymbol } from '@vue/shared' import { isSlotOutlet, findProp } from '../utils' function hasDynamicKey(node: ElementNode) { @@ -107,7 +108,7 @@ function getPatchFlag(node: PlainElementNode): number | undefined { } function isStaticNode( - node: TemplateChildNode, + node: TemplateChildNode | SimpleExpressionNode, resultCache: Map ): boolean { switch (node.type) { @@ -138,28 +139,16 @@ function isStaticNode( case NodeTypes.IF: case NodeTypes.FOR: return false + case NodeTypes.INTERPOLATION: + return isStaticNode(node.content, resultCache) + case NodeTypes.SIMPLE_EXPRESSION: + return node.isConstant case NodeTypes.COMPOUND_EXPRESSION: - return node.children.every(n => { + return node.children.every(child => { return ( - typeof n === 'string' || - typeof n === 'symbol' || - // NodeTypes.TEXT is static. - n.type === NodeTypes.TEXT || - // Expressions without identifiers are static. - (n.type === NodeTypes.SIMPLE_EXPRESSION && - !n.hasPrefixedIdentifier) || - // Check NodeTypes.INTERPOLATION - (n.type === NodeTypes.INTERPOLATION && isStaticNode(n, resultCache)) + isString(child) || isSymbol(child) || isStaticNode(child, resultCache) ) }) - case NodeTypes.INTERPOLATION: - // Representing this interpolation is static - return ( - (node.content.type === NodeTypes.SIMPLE_EXPRESSION && - !node.content.hasPrefixedIdentifier) || - (node.content.type === NodeTypes.COMPOUND_EXPRESSION && - isStaticNode(node.content, resultCache)) - ) default: if (__DEV__) { const exhaustiveCheck: never = node diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 132663cd3f3..66cbb1af966 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -187,7 +187,7 @@ export function buildProps( value.type !== NodeTypes.SIMPLE_EXPRESSION || // E.g:

. // Do not add prop `foo` to `dynamicPropNames`. - (!value.isStatic && value.hasPrefixedIdentifier) + (!value.isStatic && !value.isConstant) ) { const name = key.content if (name === 'ref') { diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index da3a362b0ba..083b3023cc7 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -61,7 +61,7 @@ export const transformExpression: NodeTransform = (node, context) => { interface PrefixMeta { prefix?: string - prefixedName: boolean + isConstant: boolean start: number end: number scopeIds?: Set @@ -109,7 +109,7 @@ export function processExpression( const ids: (Identifier & PrefixMeta)[] = [] const knownIds = Object.create(context.identifiers) - let hasPrefixedIdentifier = false + let isConstant = true // walk the AST and look for identifiers that need to be prefixed with `_ctx.`. walkJS(ast, { enter(node: Node & PrefixMeta, parent) { @@ -122,13 +122,17 @@ export function processExpression( node.prefix = `${node.name}: ` } node.name = `_ctx.${node.name}` - hasPrefixedIdentifier = true - node.prefixedName = true + node.isConstant = false + isConstant = false ids.push(node) } else if (!isStaticPropertyKey(node, parent)) { + // This means this identifier is pointing to a scope variable (a v-for alias, or a v-slot prop) + // which is also dynamic and cannot be hoisted. + node.isConstant = !( + knownIds[node.name] && shouldPrefix(node, parent) + ) // also generate sub-expressions for other identifiers for better // source map support. (except for property keys which are static) - node.prefixedName = false ids.push(node) } } @@ -203,7 +207,7 @@ export function processExpression( start: advancePositionWithClone(node.loc.start, source, start), end: advancePositionWithClone(node.loc.start, source, end) }, - id.prefixedName /* hasPrefixedIdentifier */ + id.isConstant /* isConstant */ ) ) if (i === ids.length - 1 && end < rawExp.length) { @@ -216,7 +220,7 @@ export function processExpression( ret = createCompoundExpression(children, node.loc) } else { ret = node - ret.hasPrefixedIdentifier = hasPrefixedIdentifier + ret.isConstant = isConstant } ret.identifiers = Object.keys(knownIds) return ret diff --git a/packages/compiler-dom/__tests__/parse.spec.ts b/packages/compiler-dom/__tests__/parse.spec.ts index 4a0dc067fe2..6a5b22b4acc 100644 --- a/packages/compiler-dom/__tests__/parse.spec.ts +++ b/packages/compiler-dom/__tests__/parse.spec.ts @@ -117,7 +117,7 @@ describe('DOM parser', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: `a < b`, isStatic: false, - hasPrefixedIdentifier: true, + isConstant: false, loc: { start: { offset: 8, line: 1, column: 9 }, end: { offset: 16, line: 1, column: 17 },