diff --git a/packages/uni-app-uts/__tests__/android/sfc/__snapshots__/compileScript.spec.ts.snap b/packages/uni-app-uts/__tests__/android/sfc/__snapshots__/compileScript.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..33085823b7bd7e958763f95d77469434e712a52c --- /dev/null +++ b/packages/uni-app-uts/__tests__/android/sfc/__snapshots__/compileScript.spec.ts.snap @@ -0,0 +1,779 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SFC analyze + `) + // expect(content).toMatch(`return { a, b }`) + assertCode(content) + }) + + test('should expose top level declarations', () => { + const { content, bindings } = compile(` + + + + `) + // expect(content).toMatch( + // `return { get aa() { return aa }, set aa(v) { aa = v }, ` + + // `bb, cc, dd, get a() { return a }, set a(v) { a = v }, b, c, d, ` + + // `get xx() { return xx }, get x() { return x } }` + // ) + expect(bindings).toStrictEqual({ + x: BindingTypes.SETUP_MAYBE_REF, + a: BindingTypes.SETUP_LET, + b: BindingTypes.SETUP_CONST, + c: BindingTypes.SETUP_CONST, + d: BindingTypes.SETUP_CONST, + xx: BindingTypes.SETUP_MAYBE_REF, + aa: BindingTypes.SETUP_LET, + bb: BindingTypes.LITERAL_CONST, + cc: BindingTypes.SETUP_CONST, + dd: BindingTypes.SETUP_CONST, + }) + assertCode(content) + }) + + test('binding analysis for destructure', () => { + const { content, bindings } = compile(` + + `) + // expect(content).toMatch('return { foo, bar, baz, y, z }') + expect(bindings).toStrictEqual({ + foo: BindingTypes.SETUP_MAYBE_REF, + bar: BindingTypes.SETUP_MAYBE_REF, + baz: BindingTypes.SETUP_MAYBE_REF, + y: BindingTypes.SETUP_MAYBE_REF, + z: BindingTypes.SETUP_MAYBE_REF, + }) + assertCode(content) + }) + + // describe(' + // + // `) + // assertCode(content) + // }) + + // test('script setup first', () => { + // const { content } = compile(` + // + // + // `) + // assertCode(content) + // }) + + // // #7805 + // test('keep original semi style', () => { + // const { content } = compile(` + // + // `) + // assertCode(content) + + // expect(content).toMatch(`console.log('test')`) + // expect(content).toMatch(`const props = __props;`) + // expect(content).toMatch(`const emit = __emit;`) + // // expect(content).toMatch(`(function () {})()`) + // }) + + // test('script setup first, named default export', () => { + // const { content } = compile(` + // + // + // `) + // assertCode(content) + // }) + + // // #4395 + // test('script setup first, lang="ts", script block content export default', () => { + // const { content } = compile(` + // + // + // `) + // // ensure __default__ is declared before used + // expect(content).toMatch(/const __default__[\S\s]*\.\.\.__default__/m) + // assertCode(content) + // }) + + // describe('spaces in ExportDefaultDeclaration node', () => { + // // #4371 + // test('with many spaces and newline', () => { + // // #4371 + // const { content } = compile(` + // + // + // `) + // assertCode(content) + // }) + + // test('with minimal spaces', () => { + // const { content } = compile(` + // + // + // `) + // assertCode(content) + // }) + // }) + + // test('export call expression as default', () => { + // const { content } = compile(` + // + + // + // `) + // assertCode(content) + // }) + // }) + + describe('imports', () => { + test('should hoist and expose imports', () => { + assertCode( + compile(``).content + ) + }) + + test('should extract comment for import or type declarations', () => { + assertCode( + compile(` + + `).content + ) + }) + + // #2740 + test('should allow defineProps/Emit at the start of imports', () => { + assertCode( + compile(``).content + ) + }) + + test('dedupe between user & helper', () => { + const { content } = compile( + ` + + `, + { reactivityTransform: true } + ) + assertCode(content) + expect(content).toMatch(`import { ref } from 'vue'`) + }) + + test('import dedupe between + + `) + assertCode(content) + expect(content.indexOf(`import { x }`)).toEqual( + content.lastIndexOf(`import { x }`) + ) + }) + + describe('import ref/reactive function from other place', () => { + test('import directly', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + ref: BindingTypes.SETUP_MAYBE_REF, + reactive: BindingTypes.SETUP_MAYBE_REF, + foo: BindingTypes.SETUP_MAYBE_REF, + bar: BindingTypes.SETUP_MAYBE_REF, + }) + }) + + test('import w/ alias', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + _reactive: BindingTypes.SETUP_MAYBE_REF, + _ref: BindingTypes.SETUP_MAYBE_REF, + foo: BindingTypes.SETUP_MAYBE_REF, + bar: BindingTypes.SETUP_MAYBE_REF, + }) + }) + + test('aliased usage before import site', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + bar: BindingTypes.SETUP_REACTIVE_CONST, + x: BindingTypes.SETUP_CONST, + }) + }) + }) + + test('should support module string names syntax', () => { + const { content, bindings } = compile(` + + + `) + assertCode(content) + expect(bindings).toStrictEqual({ + foo: BindingTypes.SETUP_MAYBE_REF, + }) + }) + }) + + // in dev mode, declared bindings are returned as an object from setup() + // when using TS, users may import types which should not be returned as + // values, so we need to check import usage in the template to determine + // what to be returned. + describe('dev mode import usage check', () => { + test('components', () => { + const { content } = compile(` + + + `) + // FooBar: should not be matched by plain text or incorrect case + // FooBaz: used as PascalCase component + // FooQux: used as kebab-case component + // foo: lowercase component + // expect(content).toMatch( + // `return { fooBar, get FooBaz() { return FooBaz }, ` + + // `get FooQux() { return FooQux }, get foo() { return foo } }` + // ) + assertCode(content) + }) + + test('directive', () => { + const { content } = compile(` + + + `) + // expect(content).toMatch(`return { get vMyDir() { return vMyDir } }`) + assertCode(content) + }) + + test('dynamic arguments', () => { + const { content } = compile(` + + + `) + // expect(content).toMatch( + // `return { get FooBar() { return FooBar }, get foo() { return foo }, ` + + // `get bar() { return bar }, get baz() { return baz } }` + // ) + assertCode(content) + }) + + // https://github.com/vuejs/core/issues/4599 + test('attribute expressions', () => { + const { content } = compile(` + + + `) + // expect(content).toMatch( + // `return { cond, get bar() { return bar }, get baz() { return baz } }` + // ) + assertCode(content) + }) + + test('vue interpolations', () => { + const { content } = compile(` + + + `) + // x: used in interpolation + // y: should not be matched by {{ yy }} or 'y' in binding exps + // x$y: #4274 should escape special chars when creating Regex + // expect(content).toMatch( + // `return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }` + // ) + assertCode(content) + }) + + // #4340 interpolations in template strings + test('js template string interpolations', () => { + const { content } = compile(` + + + `) + // VAR2 should not be matched + // expect(content).toMatch( + // `return { get VAR() { return VAR }, get VAR3() { return VAR3 } }` + // ) + assertCode(content) + }) + + // edge case: last tag in template + test('last tag', () => { + const { content } = compile(` + + + `) + // expect(content).toMatch( + // `return { get FooBaz() { return FooBaz }, get Last() { return Last } }` + // ) + assertCode(content) + }) + + test('TS annotations', () => { + const { content } = compile(` + + + `) + // expect(content).toMatch(`return { a, b, get Baz() { return Baz } }`) + assertCode(content) + }) + + // vuejs/vue#12591 + test('v-on inline statement', () => { + // should not error + compile(` + + + `) + }) + + test('template ref', () => { + const { content } = compile(` + + + `) + // expect(content).toMatch( + // 'return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }' + // ) + assertCode(content) + }) + }) + + describe('inlineTemplate mode', () => { + test('should work', () => { + const { content } = compile( + ` + + + `, + { inlineTemplate: true } + ) + // check snapshot and make sure helper imports and + // hoists are placed correctly. + assertCode(content) + // in inline mode, no need to call expose() since nothing is exposed + // anyway! + // expect(content).not.toMatch(`expose()`) + }) + + test('with defineExpose()', () => { + const { content } = compile( + ` + + `, + { inlineTemplate: true } + ) + assertCode(content) + // expect(content).toMatch(`setup(__props, { expose: __expose })`) + // expect(content).toMatch(`expose({ count })`) + }) + + test('referencing scope components and directives', () => { + const { content } = compile( + ` + + + `, + { inlineTemplate: true } + ) + // expect(content).toMatch('[_unref(vMyDir)]') + expect(content).toMatch('createVNode(ChildComp)') + // kebab-case component support + expect(content).toMatch('createVNode(SomeOtherComp)') + assertCode(content) + }) + + test('avoid unref() when necessary', () => { + // function, const, component import + const { content } = compile( + ` + + `, + { inlineTemplate: true } + ) + // no need to unref vue component import + expect(content).toMatch(`createVNode(Foo,`) + // #2699 should unref named imports from .vue + expect(content).toMatch(`unref(bar)`) + // should unref other imports + expect(content).toMatch(`unref(other)`) + // no need to unref constant literals + expect(content).not.toMatch(`unref(constant)`) + // should directly use .value for known refs + expect(content).toMatch(`count.value`) + // should unref() on const bindings that may be refs + expect(content).toMatch(`unref(maybe)`) + // should unref() on let bindings + expect(content).toMatch(`unref(lett)`) + // no need to unref namespace import (this also preserves tree-shaking) + expect(content).toMatch(`tree.foo()`) + // no need to unref function declarations + expect(content).toMatch(`{ onClick: fn }`) + // no need to mark constant fns in patch flag + expect(content).not.toMatch(`PROPS`) + assertCode(content) + }) + + test('v-model codegen', () => { + const { content } = compile( + ` + + `, + { inlineTemplate: true } + ) + // known const ref: set value + expect(content).toMatch(`(count).value = $event.detail.value`) + // const but maybe ref: assign if ref, otherwise do nothing + expect(content).toMatch( + `isRef(maybe) ? (maybe).value = $event.detail.value : null` + ) + // let: handle both cases + expect(content).toMatch( + `isRef(lett) ? (lett).value = $event.detail.value : lett = $event.detail.value` + ) + assertCode(content) + }) + + test('v-model should not generate ref assignment code for non-setup bindings', () => { + const { content } = compile( + ` + + + `, + { inlineTemplate: true } + ) + expect(content).not.toMatch(`isRef(foo)`) + }) + + test('template assignment expression codegen', () => { + const { content } = compile( + ` + + `, + { inlineTemplate: true } + ) + // known const ref: set value + expect(content).toMatch(`count.value = 1`) + // const but maybe ref: only assign after check + expect(content).toMatch(`maybe.value = count.value`) + // let: handle both cases + expect(content).toMatch( + `isRef(lett) ? lett.value = count.value : lett = count.value` + ) + expect(content).toMatch(`isRef(v) ? v.value += 1 : v += 1`) + expect(content).toMatch(`isRef(v) ? v.value -= 1 : v -= 1`) + expect(content).toMatch(`isRef(v) ? v.value = a : v = a`) + expect(content).toMatch(`isRef(v) ? v.value = _ctx.a : v = _ctx.a`) + assertCode(content) + }) + + test('template update expression codegen', () => { + const { content } = compile( + ` + + `, + { inlineTemplate: true } + ) + // known const ref: set value + expect(content).toMatch(`count.value++`) + expect(content).toMatch(`--count.value`) + // const but maybe ref (non-ref case ignored) + expect(content).toMatch(`maybe.value++`) + expect(content).toMatch(`--maybe.value`) + // let: handle both cases + expect(content).toMatch(`isRef(lett) ? lett.value++ : lett++`) + expect(content).toMatch(`isRef(lett) ? --lett.value : --lett`) + assertCode(content) + }) + + test('template destructure assignment codegen', () => { + const { content } = compile( + ` + + `, + { inlineTemplate: true } + ) + // known const ref: set value + expect(content).toMatch(`({ count: count.value } = val)`) + // const but maybe ref (non-ref case ignored) + expect(content).toMatch(`[maybe.value] = val`) + // let: assumes non-ref + expect(content).toMatch(`{ lett: lett } = val`) + assertCode(content) + }) + + // test('ssr codegen', () => { + // const { content } = compile( + // ` + // + // + // + // `, + // { + // inlineTemplate: true, + // templateOptions: { + // ssr: true, + // }, + // } + // ) + // expect(content).toMatch(`\n __ssrInlineRender: true,\n`) + // expect(content).toMatch(`return (_ctx, _push`) + // expect(content).toMatch(`ssrInterpolate`) + // expect(content).not.toMatch(`useCssVars`) + // expect(content).toMatch(`"--${mockId}-count": (count.value)`) + // expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`) + // assertCode(content) + // }) + + test('the v-for wrapped in parentheses can be correctly parsed & inline is false', () => { + expect(() => + compile( + ` + + + `, + { + inlineTemplate: false, + } + ) + ).not.toThrowError() + }) + }) + + describe('with TypeScript', () => { + test('hoist type declarations', () => { + const { content } = compile(` + `) + assertCode(content) + }) + + test('runtime Enum', () => { + const { content, bindings } = compile( + `` + ) + assertCode(content) + expect(bindings).toStrictEqual({ + Foo: BindingTypes.LITERAL_CONST, + }) + }) + + test('runtime Enum in normal script', () => { + const { content, bindings } = compile( + ` + ` + ) + assertCode(content) + expect(bindings).toStrictEqual({ + D: BindingTypes.LITERAL_CONST, + C: BindingTypes.LITERAL_CONST, + B: BindingTypes.LITERAL_CONST, + Foo: BindingTypes.LITERAL_CONST, + }) + }) + + test('const Enum', () => { + const { content, bindings } = compile( + ``, + { hoistStatic: true } + ) + assertCode(content) + expect(bindings).toStrictEqual({ + Foo: BindingTypes.LITERAL_CONST, + }) + }) + + // test('import type', () => { + // const { content } = compile( + // `` + // ) + // expect(content).toMatch(`return { get Baz() { return Baz } }`) + // assertCode(content) + // }) + }) + + // describe('async/await detection', () => { + // function assertAwaitDetection(code: string, shouldAsync = true) { + // const { content } = compile(``, { + // reactivityTransform: true, + // }) + // if (shouldAsync) { + // expect(content).toMatch(`let __temp, __restore`) + // } + // expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`) + // assertCode(content) + // return content + // } + + // test('expression statement', () => { + // assertAwaitDetection(`await foo`) + // }) + + // test('variable', () => { + // assertAwaitDetection(`const a = 1 + (await foo)`) + // }) + + // test('ref', () => { + // assertAwaitDetection(`let a = $ref(1 + (await foo))`) + // }) + + // // #4448 + // test('nested await', () => { + // assertAwaitDetection(`await (await foo)`) + // assertAwaitDetection(`await ((await foo))`) + // assertAwaitDetection(`await (await (await foo))`) + // }) + + // // should prepend semicolon + // test('nested leading await in expression statement', () => { + // const code = assertAwaitDetection(`foo()\nawait 1 + await 2`) + // expect(code).toMatch(`foo()\n;(`) + // }) + + // // #4596 should NOT prepend semicolon + // test('single line conditions', () => { + // const code = assertAwaitDetection(`if (false) await foo()`) + // expect(code).not.toMatch(`if (false) ;(`) + // }) + + // test('nested statements', () => { + // assertAwaitDetection(`if (ok) { await foo } else { await bar }`) + // }) + + // test('multiple `if` nested statements', () => { + // assertAwaitDetection(`if (ok) { + // let a = 'foo' + // await 0 + await 1 + // await 2 + // } else if (a) { + // await 10 + // if (b) { + // await 0 + await 1 + // } else { + // let a = 'foo' + // await 2 + // } + // if (b) { + // await 3 + // await 4 + // } + // } else { + // await 5 + // }`) + // }) + + // test('multiple `if while` nested statements', () => { + // assertAwaitDetection(`if (ok) { + // while (d) { + // await 5 + // } + // while (d) { + // await 5 + // await 6 + // if (c) { + // let f = 10 + // 10 + await 7 + // } else { + // await 8 + // await 9 + // } + // } + // }`) + // }) + + // test('multiple `if for` nested statements', () => { + // assertAwaitDetection(`if (ok) { + // for (let a of [1,2,3]) { + // await a + // } + // for (let a of [1,2,3]) { + // await a + // await a + // } + // }`) + // }) + + // test('should ignore await inside functions', () => { + // // function declaration + // assertAwaitDetection(`async function foo() { await bar }`, false) + // // function expression + // assertAwaitDetection(`const foo = async () => { await bar }`, false) + // // object method + // assertAwaitDetection(`const obj = { async method() { await bar }}`, false) + // // class method + // assertAwaitDetection( + // `const cls = class Foo { async method() { await bar }}`, + // false + // ) + // }) + // }) + + describe('errors', () => { + // test('`) + // ).toThrow(``) + ).toThrow(moduleErrorMsg) + + expect(() => + compile(``) + ).toThrow(moduleErrorMsg) + + expect(() => + compile(``) + ).toThrow(moduleErrorMsg) + }) + + test('defineProps/Emit() referencing local var', () => { + expect(() => + compile(``) + ).toThrow(`cannot reference locally declared variables`) + + expect(() => + compile(``) + ).toThrow(`cannot reference locally declared variables`) + + // #4644 + expect(() => + compile(` + + `) + ).not.toThrow(`cannot reference locally declared variables`) + }) + + test('should allow defineProps/Emit() referencing scope var', () => { + assertCode( + compile(``).content + ) + }) + + test('should allow defineProps/Emit() referencing imported binding', () => { + assertCode( + compile(``).content + ) + }) + }) +}) + +describe('SFC analyze + `) + + expect(scriptAst).toBeDefined() + }) + + it('recognizes props array declaration', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + foo: BindingTypes.PROPS, + bar: BindingTypes.PROPS, + }) + expect(bindings!.__isScriptSetup).toBe(false) + }) + + it('recognizes props object declaration', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + foo: BindingTypes.PROPS, + bar: BindingTypes.PROPS, + baz: BindingTypes.PROPS, + qux: BindingTypes.PROPS, + }) + expect(bindings!.__isScriptSetup).toBe(false) + }) + + it('recognizes setup return', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + foo: BindingTypes.SETUP_MAYBE_REF, + bar: BindingTypes.SETUP_MAYBE_REF, + }) + expect(bindings!.__isScriptSetup).toBe(false) + }) + + it('recognizes exported vars', () => { + const { bindings } = compile(` + + + `) + expect(bindings).toStrictEqual({ + foo: BindingTypes.LITERAL_CONST, + }) + }) + + it('recognizes async setup return', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + foo: BindingTypes.SETUP_MAYBE_REF, + bar: BindingTypes.SETUP_MAYBE_REF, + }) + expect(bindings!.__isScriptSetup).toBe(false) + }) + + it('recognizes data return', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + foo: BindingTypes.DATA, + bar: BindingTypes.DATA, + }) + }) + + it('recognizes methods', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS }) + }) + + it('recognizes computeds', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + foo: BindingTypes.OPTIONS, + bar: BindingTypes.OPTIONS, + }) + }) + + it('recognizes injections array declaration', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + foo: BindingTypes.OPTIONS, + bar: BindingTypes.OPTIONS, + }) + }) + + it('recognizes injections object declaration', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + foo: BindingTypes.OPTIONS, + bar: BindingTypes.OPTIONS, + }) + }) + + it('works for mixed bindings', () => { + const { bindings } = compile(` + + `) + expect(bindings).toStrictEqual({ + foo: BindingTypes.OPTIONS, + bar: BindingTypes.PROPS, + baz: BindingTypes.SETUP_MAYBE_REF, + qux: BindingTypes.DATA, + quux: BindingTypes.OPTIONS, + quuz: BindingTypes.OPTIONS, + }) + }) + + it('works for script setup', () => { + const { bindings } = compile(` + + `) + + expect(bindings).toStrictEqual({ + r: BindingTypes.SETUP_CONST, + a: BindingTypes.SETUP_REF, + b: BindingTypes.SETUP_LET, + c: BindingTypes.LITERAL_CONST, + d: BindingTypes.SETUP_MAYBE_REF, + e: BindingTypes.SETUP_LET, + foo: BindingTypes.PROPS, + }) + }) + + describe('auto name inference', () => { + test('basic', () => { + const { content } = compile( + ` + `, + undefined, + { + filename: 'FooBar.vue', + } + ) + expect(content).toMatch(`export default { + __name: 'FooBar'`) + assertCode(content) + }) + + test('do not overwrite manual name (object)', () => { + const { content } = compile( + ` + + `, + undefined, + { + filename: 'FooBar.vue', + } + ) + expect(content).not.toMatch(`name: 'FooBar'`) + expect(content).toMatch(`name: 'Baz'`) + assertCode(content) + }) + + test('do not overwrite manual name (call)', () => { + const { content } = compile( + ` + + `, + undefined, + { + filename: 'FooBar.vue', + } + ) + expect(content).not.toMatch(`name: 'FooBar'`) + expect(content).toMatch(`name: 'Baz'`) + assertCode(content) + }) + }) +}) + +// describe('SFC genDefaultAs', () => { +// test('normal `, +// { +// genDefaultAs: '_sfc_', +// } +// ) +// expect(content).not.toMatch('export default') +// expect(content).toMatch(`const _sfc_ = {}`) +// assertCode(content) +// }) + +// test('normal +// `, +// { +// genDefaultAs: '_sfc_', +// } +// ) +// expect(content).not.toMatch('export default') +// expect(content).not.toMatch('__default__') +// expect(content).toMatch(`const _sfc_ = {}`) +// assertCode(content) +// }) + +// test(' +// `, +// { +// genDefaultAs: '_sfc_', +// } +// ) +// expect(content).not.toMatch('export default') +// expect(content).toMatch( +// `const _sfc_ = /*#__PURE__*/Object.assign(__default__` +// ) +// assertCode(content) +// }) + +// test(' +// `, +// { +// genDefaultAs: '_sfc_', +// } +// ) +// expect(content).not.toMatch('export default') +// expect(content).toMatch( +// `const _sfc_ = /*#__PURE__*/Object.assign(__default__` +// ) +// assertCode(content) +// }) + +// test('`, +// { +// genDefaultAs: '_sfc_', +// } +// ) +// expect(content).not.toMatch('export default') +// expect(content).toMatch(`const _sfc_ = {\n setup`) +// assertCode(content) +// }) + +// test('`, +// { +// genDefaultAs: '_sfc_', +// } +// ) +// expect(content).not.toMatch('export default') +// expect(content).toMatch(`const _sfc_ = /*#__PURE__*/_defineComponent(`) +// assertCode(content) +// }) + +// test(' +// `, +// { +// genDefaultAs: '_sfc_', +// } +// ) +// expect(content).not.toMatch('export default') +// expect(content).toMatch( +// `const _sfc_ = /*#__PURE__*/_defineComponent({\n ...__default__` +// ) +// assertCode(content) +// }) + +// test('binding type for edge cases', () => { +// const { bindings } = compile( +// `` +// ) +// expect(bindings).toStrictEqual({ +// toRef: BindingTypes.SETUP_CONST, +// props: BindingTypes.SETUP_REACTIVE_CONST, +// foo: BindingTypes.SETUP_REF, +// }) +// }) +// }) diff --git a/packages/uni-app-uts/__tests__/android/sfc/utils.ts b/packages/uni-app-uts/__tests__/android/sfc/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a0be40c8224d4ef3475982d847b7334969ad2d5 --- /dev/null +++ b/packages/uni-app-uts/__tests__/android/sfc/utils.ts @@ -0,0 +1,58 @@ +import { parse, SFCParseOptions } from '@vue/compiler-sfc' +import { parse as babelParse } from '@babel/parser' + +import { + compileScript, + SFCScriptCompileOptions, +} from '../../../src/plugins/android/uvue/sfc/compiler/compileScript' +import { genTemplateCode } from '../../../src/plugins/android/uvue/code/template' +import { resolveGenTemplateCodeOptions } from '../../../src/plugins/android/uvue/sfc/template' +export const mockId = 'xxxxxxxx' + +export function compileSFCScript( + src: string, + options?: Partial, + parseOptions?: SFCParseOptions +) { + const { descriptor } = parse(src, parseOptions) + const result = compileScript(descriptor, { + ...options, + id: mockId, + className: '', + inlineTemplate: true, + scriptAndScriptSetup: true, + }) + if (options?.inlineTemplate) { + const isInline = !!descriptor.scriptSetup + const templateResult = genTemplateCode( + descriptor, + resolveGenTemplateCodeOptions(descriptor.filename, src, descriptor, { + mode: 'module', + inline: isInline, + rootDir: '', + sourceMap: false, + bindingMetadata: result.bindings, + }) + ) + result.content = result.content.replace( + `"INLINE_RENDER"`, + templateResult.code + ) + } + return result +} + +export function assertCode(code: string) { + // parse the generated code to make sure it is valid + try { + babelParse(code, { + sourceType: 'module', + plugins: ['typescript'], + }) + } catch (e: any) { + console.log(code, e) + throw e + } + // console.log(code) + expect(code).toMatchSnapshot() +} diff --git a/packages/uni-app-uts/__tests__/android/transforms/__snapshots__/transformExpressions.spec.ts.snap b/packages/uni-app-uts/__tests__/android/transforms/__snapshots__/transformExpressions.spec.ts.snap index 9309115b58b6ed19d7726ef77e9e285a1d013176..59726634ef7692ea447a2a1bc295474a4cc0ac70 100644 --- a/packages/uni-app-uts/__tests__/android/transforms/__snapshots__/transformExpressions.spec.ts.snap +++ b/packages/uni-app-uts/__tests__/android/transforms/__snapshots__/transformExpressions.spec.ts.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`compiler: expression transform bindingMetadata inline mode 1`] = `"createElementVNode("view", null, toDisplayString(__props.props) + " " + toDisplayString(unref(setup)) + " " + toDisplayString(setupConst) + " " + toDisplayString(_ctx.data) + " " + toDisplayString(_ctx.options) + " " + toDisplayString(isNaN), 1 /* TEXT */)"`; +exports[`compiler: expression transform bindingMetadata inline mode 1`] = `"createElementVNode("view", null, toDisplayString(__props.props) + " " + toDisplayString(unref(setup)) + " " + toDisplayString(setupConst) + " " + toDisplayString(_ctx.data) + " " + toDisplayString(_ctx.options) + " " + toDisplayString(isNaN.value), 1 /* TEXT */)"`; -exports[`compiler: expression transform bindingMetadata non-inline mode 1`] = `"createElementVNode("view", null, toDisplayString($props.props) + " " + toDisplayString($setup.setup) + " " + toDisplayString($data.data) + " " + toDisplayString($options.options) + " " + toDisplayString(isNaN), 1 /* TEXT */)"`; +exports[`compiler: expression transform bindingMetadata non-inline mode 1`] = `"createElementVNode("view", null, toDisplayString($props.props) + " " + toDisplayString($setup.setup) + " " + toDisplayString($data.data) + " " + toDisplayString($options.options) + " " + toDisplayString($setup.isNaN), 1 /* TEXT */)"`; exports[`compiler: expression transform bindingMetadata should not prefix temp variable of for loop 1`] = ` "createVNode(_component_div, utsMapOf({ diff --git a/packages/uni-app-uts/__tests__/android/transforms/transformExpressions.spec.ts b/packages/uni-app-uts/__tests__/android/transforms/transformExpressions.spec.ts index 1432dec1ea1ab6e7d02dd00a35f94acd91bc8a24..1ffd78aee1760c414f6efbefa063b2ec858a5712 100644 --- a/packages/uni-app-uts/__tests__/android/transforms/transformExpressions.spec.ts +++ b/packages/uni-app-uts/__tests__/android/transforms/transformExpressions.spec.ts @@ -532,7 +532,7 @@ describe('compiler: expression transform', () => { ) expect(code).toMatch(`$props.props`) expect(code).toMatch(`$setup.setup`) - // expect(code).toMatch(`$setup.isNaN`) + expect(code).toMatch(`$setup.isNaN`) expect(code).toMatch(`$data.data`) expect(code).toMatch(`$options.options`) // expect(code).toMatch(`_ctx, _cache, $props, $setup, $data, $options`) @@ -585,8 +585,7 @@ describe('compiler: expression transform', () => { expect(code).toMatch(`toDisplayString(setupConst)`) expect(code).toMatch(`_ctx.data`) expect(code).toMatch(`_ctx.options`) - // isNaN 设置了全局方法 - // expect(code).toMatch(`isNaN.value`) + expect(code).toMatch(`isNaN.value`) expect(code).toMatchSnapshot() }) diff --git a/packages/uni-app-uts/src/plugins/android/uvue/compiler/codegen.ts b/packages/uni-app-uts/src/plugins/android/uvue/compiler/codegen.ts index 4f9697e5550d67abd37ba400721334d0d67f88e5..712346bd42a385ba9bb7b2734ab29961b05ba7a9 100644 --- a/packages/uni-app-uts/src/plugins/android/uvue/compiler/codegen.ts +++ b/packages/uni-app-uts/src/plugins/android/uvue/compiler/codegen.ts @@ -99,7 +99,7 @@ function createCodegenContext( rootDir = '', targetLanguage = 'kotlin', mode = 'default', - prefixIdentifiers = false, + prefixIdentifiers = mode === 'module', bindingMetadata = {}, inline = false, sourceMap = false, diff --git a/packages/uni-app-uts/src/plugins/android/uvue/compiler/index.ts b/packages/uni-app-uts/src/plugins/android/uvue/compiler/index.ts index 7d13fb290a6cdd5edc3d2b0bccf690f2ab260ebd..8703618dcabec4131984f6d8f1f4b9eb57de3b91 100644 --- a/packages/uni-app-uts/src/plugins/android/uvue/compiler/index.ts +++ b/packages/uni-app-uts/src/plugins/android/uvue/compiler/index.ts @@ -107,7 +107,6 @@ export function compile( transform( ast, extend({}, options, { - prefixIdentifiers: options.prefixIdentifiers, nodeTransforms: [ ...nodeTransforms, ...getBaseNodeTransforms('/'), diff --git a/packages/uni-app-uts/src/plugins/android/uvue/compiler/options.ts b/packages/uni-app-uts/src/plugins/android/uvue/compiler/options.ts index 565e718270e6e15608ef4a97d1702f067ead512c..327f3d22cd98223eea1ed5060cfcc662369a23ac 100644 --- a/packages/uni-app-uts/src/plugins/android/uvue/compiler/options.ts +++ b/packages/uni-app-uts/src/plugins/android/uvue/compiler/options.ts @@ -3,6 +3,10 @@ import type { RawSourceMap } from 'source-map-js' import { DirectiveTransform, NodeTransform } from './transform' interface SharedTransformCodegenOptions { + /** + * @default 'default' + */ + mode?: 'default' | 'module' rootDir?: string targetLanguage?: 'kotlin' | 'swift' /** @@ -32,11 +36,6 @@ interface SharedTransformCodegenOptions { className?: string } export interface CodegenOptions extends SharedTransformCodegenOptions { - /** - * @default 'default' - */ - mode?: 'default' | 'module' - inMap?: RawSourceMap /** * Generate source map? diff --git a/packages/uni-app-uts/src/plugins/android/uvue/compiler/transform.ts b/packages/uni-app-uts/src/plugins/android/uvue/compiler/transform.ts index f0e443c1beecd23215e98b53fff4b7a260cded6e..30b7801bd4de1cd5c8d9589c11e7b6d0d4ea96a0 100644 --- a/packages/uni-app-uts/src/plugins/android/uvue/compiler/transform.ts +++ b/packages/uni-app-uts/src/plugins/android/uvue/compiler/transform.ts @@ -116,11 +116,12 @@ export interface TransformContext export function createTransformContext( root: RootNode, { + mode = 'default', rootDir = '', targetLanguage = 'kotlin', filename = '', cacheHandlers = false, - prefixIdentifiers = false, + prefixIdentifiers = mode === 'module', nodeTransforms = [], directiveTransforms = {}, scopeId = null, @@ -136,6 +137,7 @@ export function createTransformContext( const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/) const context: TransformContext = { // options + mode, rootDir, targetLanguage, selfName: nameMatch && capitalize(camelize(nameMatch[1])), diff --git a/packages/uni-app-uts/src/plugins/android/uvue/compiler/transforms/transformExpression.ts b/packages/uni-app-uts/src/plugins/android/uvue/compiler/transforms/transformExpression.ts index 414655de73b1742a39798e8f387048132a98606a..a6f130919f8efcacba75d76cc7c47229c2d5f975 100644 --- a/packages/uni-app-uts/src/plugins/android/uvue/compiler/transforms/transformExpression.ts +++ b/packages/uni-app-uts/src/plugins/android/uvue/compiler/transforms/transformExpression.ts @@ -221,7 +221,12 @@ export function processExpression( const isScopeVarReference = context.identifiers[rawExp] const isAllowedGlobal = isGloballyWhitelisted(rawExp) const isLiteral = isLiteralWhitelisted(rawExp) - if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) { + if ( + !asParams && + !isScopeVarReference && + !isLiteral && + (!isAllowedGlobal || bindingMetadata[rawExp]) + ) { // const bindings exposed from setup can be skipped for patching but // cannot be hoisted to module scope if (bindingMetadata[node.content] === BindingTypes.SETUP_CONST) { diff --git a/packages/uni-app-uts/src/plugins/android/uvue/index.ts b/packages/uni-app-uts/src/plugins/android/uvue/index.ts index f9dd685f0908c0d42afa4aac69ef82c5582d4db4..e4c402cc02d86d3b10ef3ea9013d0fc34bdbaa23 100644 --- a/packages/uni-app-uts/src/plugins/android/uvue/index.ts +++ b/packages/uni-app-uts/src/plugins/android/uvue/index.ts @@ -258,7 +258,6 @@ export async function transformVue( mode: 'module', filename: relativeFileName, className: className, - prefixIdentifiers: true, // 方便测试,build模式也提供sourceMap // sourceMap: false, sourceMap: needSourceMap, diff --git a/packages/uni-app-uts/src/plugins/android/uvue/sfc/compiler/compileScript.ts b/packages/uni-app-uts/src/plugins/android/uvue/sfc/compiler/compileScript.ts index 4fe7f36d48f7b8db859f64451bb7551a84db6b47..5b29c7025d220ca9ba748aa7ddebad1cb9adebff 100644 --- a/packages/uni-app-uts/src/plugins/android/uvue/sfc/compiler/compileScript.ts +++ b/packages/uni-app-uts/src/plugins/android/uvue/sfc/compiler/compileScript.ts @@ -13,6 +13,7 @@ import { Identifier, Statement, CallExpression, + ExportSpecifier, } from '@babel/types' import { walk } from 'estree-walker' import type { RawSourceMap } from 'source-map-js' @@ -43,12 +44,22 @@ import { } from './script/utils' import { analyzeScriptBindings } from './script/analyzeScriptBindings' import { parseUTSRelativeFilename } from '../../../utils' -import { rewriteConsole } from './script/rewriteConsole' +import { hasConsole, rewriteConsole } from './script/rewriteConsole' import { hasDebugError, rewriteDebugError } from './script/rewriteDebugError' +import { TypeScope } from './script/resolveType' + +export const normalScriptDefaultVar = `__default__` export const DEFAULT_FILENAME = 'anonymous.vue' export interface SFCScriptCompileOptions { + /** + * 是否同时支持使用