import { parse, SFCScriptCompileOptions, compileScript } from '../src' import { parse as babelParse } from '@babel/parser' import { babelParserDefaultPlugins } from '@vue/shared' function compile(src: string, options?: SFCScriptCompileOptions) { const { descriptor } = parse(src) return compileScript(descriptor, options) } function assertCode(code: string) { // parse the generated code to make sure it is valid try { babelParse(code, { sourceType: 'module', plugins: [...babelParserDefaultPlugins, 'typescript'] }) } catch (e) { console.log(code) throw e } expect(code).toMatchSnapshot() } describe('SFC compile `).content ) }) test('should extract comment for import or type declarations', () => { assertCode( compile(``).content ) }) test('explicit setup signature', () => { assertCode( compile(``).content ) }) test('import dedupe between `) assertCode(content) expect(content.indexOf(`import { x }`)).toEqual( content.lastIndexOf(`import { x }`) ) }) describe('exports', () => { test('export const x = ...', () => { const { content, bindings } = compile( `` ) assertCode(content) expect(bindings).toStrictEqual({ x: 'setup' }) }) test('export const { x } = ... (destructuring)', () => { const { content, bindings } = compile(``) assertCode(content) expect(bindings).toStrictEqual({ a: 'setup', b: 'setup', c: 'setup', d: 'setup', e: 'setup', f: 'setup' }) }) test('export function x() {}', () => { const { content, bindings } = compile( `` ) assertCode(content) expect(bindings).toStrictEqual({ x: 'setup' }) }) test('export class X() {}', () => { const { content, bindings } = compile( `` ) assertCode(content) expect(bindings).toStrictEqual({ X: 'setup' }) }) test('export { x }', () => { const { content, bindings } = compile( `` ) assertCode(content) expect(bindings).toStrictEqual({ x: 'setup', y: 'setup' }) }) test(`export { x } from './x'`, () => { const { content, bindings } = compile( `` ) assertCode(content) expect(bindings).toStrictEqual({ x: 'setup', y: 'setup' }) }) test(`export default from './x'`, () => { const { content, bindings } = compile( ``, { babelParserPlugins: ['exportDefaultFrom'] } ) assertCode(content) expect(bindings).toStrictEqual({}) }) test(`export { x as default }`, () => { const { content, bindings } = compile( `` ) assertCode(content) expect(bindings).toStrictEqual({ y: 'setup' }) }) test(`export { x as default } from './x'`, () => { const { content, bindings } = compile( `` ) assertCode(content) expect(bindings).toStrictEqual({ y: 'setup' }) }) test(`export * from './x'`, () => { const { content, bindings } = compile( `` ) assertCode(content) expect(bindings).toStrictEqual({ y: 'setup' // in this case we cannot extract bindings from ./x so it falls back // to runtime proxy dispatching }) }) test('export default in ` ) assertCode(content) expect(bindings).toStrictEqual({ foo: 'props', y: 'setup' }) }) }) describe('`) assertCode(content) expect(bindings).toStrictEqual({ a: 'setup' }) }) test('extract props', () => { const { content } = compile(` `) assertCode(content) expect(content).toMatch(`string: { type: String, required: true }`) expect(content).toMatch(`number: { type: Number, required: true }`) expect(content).toMatch(`boolean: { type: Boolean, required: true }`) expect(content).toMatch(`object: { type: Object, required: true }`) expect(content).toMatch(`objectLiteral: { type: Object, required: true }`) expect(content).toMatch(`fn: { type: Function, required: true }`) expect(content).toMatch(`functionRef: { type: Function, required: true }`) expect(content).toMatch(`objectRef: { type: Object, required: true }`) expect(content).toMatch(`array: { type: Array, required: true }`) expect(content).toMatch(`arrayRef: { type: Array, required: true }`) expect(content).toMatch(`tuple: { type: Array, required: true }`) expect(content).toMatch(`set: { type: Set, required: true }`) expect(content).toMatch(`literal: { type: String, required: true }`) expect(content).toMatch(`optional: { type: null, required: false }`) expect(content).toMatch(`recordRef: { type: Object, required: true }`) expect(content).toMatch(`interface: { type: Object, required: true }`) expect(content).toMatch(`alias: { type: Array, required: true }`) expect(content).toMatch( `union: { type: [String, Number], required: true }` ) expect(content).toMatch( `literalUnion: { type: [String, String], required: true }` ) expect(content).toMatch( `literalUnionMixed: { type: [String, Number, Boolean], required: true }` ) expect(content).toMatch(`intersection: { type: Object, required: true }`) }) test('extract emits', () => { const { content } = compile(` `) assertCode(content) expect(content).toMatch( `declare function __emit__(e: 'foo' | 'bar'): void` ) expect(content).toMatch( `declare function __emit__(e: 'baz', id: number): void` ) expect(content).toMatch( `emits: ["foo", "bar", "baz"] as unknown as undefined` ) }) }) describe('CSS vars injection', () => { test('\n` + `` ).content ) }) test('\n` + `` ).content ) }) test('\n` + `` ).content ) }) test('w/ \n` + `` ).content ) }) }) describe('async/await detection', () => { function assertAwaitDetection(code: string, shouldAsync = true) { const { content } = compile(``) expect(content).toMatch( `export ${shouldAsync ? `async ` : ``}function setup` ) } test('expression statement', () => { assertAwaitDetection(`await foo`) }) test('variable', () => { assertAwaitDetection(`const a = 1 + (await foo)`) }) test('export', () => { assertAwaitDetection(`export const a = 1 + (await foo)`) }) test('nested statements', () => { assertAwaitDetection(`if (ok) { await foo } else { await bar }`) }) test('should ignore await inside functions', () => { // function declaration assertAwaitDetection(`export 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(`Cannot export locally defined variable as default`) }) test('export default referencing local var', () => { expect(() => compile(``) ).toThrow(`cannot reference locally declared variables`) }) test('export default referencing exports', () => { expect(() => compile(``) ).toThrow(`cannot reference locally declared variables`) }) test('should allow export default referencing scope var', () => { assertCode( compile(``).content ) }) test('should allow export default referencing imported binding', () => { assertCode( compile(``).content ) }) test('should allow export default referencing re-exported binding', () => { assertCode( compile(``).content ) }) test('error on duplicated default export', () => { expect(() => compile(` `) ).toThrow(`Default export is already declared`) expect(() => compile(` `) ).toThrow(`Default export is already declared`) expect(() => compile(` `) ).toThrow(`Default export is already declared`) expect(() => compile(` `) ).toThrow(`Default export is already declared`) expect(() => compile(` `) ).toThrow(`Default export is already declared`) }) }) }) describe('SFC analyze `) expect(scriptAst).toBeDefined() }) it('recognizes props array declaration', () => { const { bindings } = compile(` `) expect(bindings).toStrictEqual({ foo: 'props', bar: 'props' }) }) it('recognizes props object declaration', () => { const { bindings } = compile(` `) expect(bindings).toStrictEqual({ foo: 'props', bar: 'props', baz: 'props', qux: 'props' }) }) it('recognizes setup return', () => { const { bindings } = compile(` `) expect(bindings).toStrictEqual({ foo: 'setup', bar: 'setup' }) }) it('recognizes async setup return', () => { const { bindings } = compile(` `) expect(bindings).toStrictEqual({ foo: 'setup', bar: 'setup' }) }) it('recognizes data return', () => { const { bindings } = compile(` `) expect(bindings).toStrictEqual({ foo: 'data', bar: 'data' }) }) it('recognizes methods', () => { const { bindings } = compile(` `) expect(bindings).toStrictEqual({ foo: 'options' }) }) it('recognizes computeds', () => { const { bindings } = compile(` `) expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' }) }) it('recognizes injections array declaration', () => { const { bindings } = compile(` `) expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' }) }) it('recognizes injections object declaration', () => { const { bindings } = compile(` `) expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' }) }) it('works for mixed bindings', () => { const { bindings } = compile(` `) expect(bindings).toStrictEqual({ foo: 'options', bar: 'props', baz: 'setup', qux: 'data', quux: 'options', quuz: 'options' }) }) it('works for script setup', () => { const { bindings } = compile(` `) expect(bindings).toStrictEqual({ foo: 'props' }) }) })