diff --git a/packages/compiler-sfc/__tests__/compileScriptParseOnlyMode.spec.ts b/packages/compiler-sfc/__tests__/compileScriptParseOnlyMode.spec.ts new file mode 100644 index 00000000..50a8a112 --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScriptParseOnlyMode.spec.ts @@ -0,0 +1,77 @@ +import { TextRange } from '../src/parse' +import { compileSFCScript } from './utils' + +describe('compileScript parseOnly mode', () => { + function compile(src: string) { + return compileSFCScript(src, { parseOnly: true }) + } + + function getRange(src: string, range: TextRange) { + return src.slice(range.start, range.end) + } + + test('bindings', () => { + const scriptSrc = ` + import { foo } from './x' + ` + const scriptSetupSrc = ` + import { bar } from './x' + + const a = 123 + function b() {} + class c {} + ` + const src = ` + + + ` + const { ranges } = compile(src) + + expect(getRange(scriptSrc, ranges!.scriptBindings[0])).toBe('foo') + expect( + ranges!.scriptSetupBindings.map(r => getRange(scriptSetupSrc, r)) + ).toMatchObject(['bar', 'a', 'b', 'c']) + }) + + test('defineProps', () => { + const src = ` + defineProps({ foo: String }) + ` + const { ranges } = compile(``) + expect(getRange(src, ranges!.propsRuntimeArg!)).toBe(`{ foo: String }`) + }) + + test('defineProps (type)', () => { + const src = ` + interface Props { x?: number } + defineProps() + ` + const { ranges } = compile(``) + expect(getRange(src, ranges!.propsTypeArg!)).toBe(`Props`) + }) + + test('withDefaults', () => { + const src = ` + interface Props { x?: number } + withDefaults(defineProps(), { x: 1 }) + ` + const { ranges } = compile(``) + expect(getRange(src, ranges!.withDefaultsArg!)).toBe(`{ x: 1 }`) + }) + + test('defineEmits', () => { + const src = ` + defineEmits(['foo']) + ` + const { ranges } = compile(``) + expect(getRange(src, ranges!.emitsRuntimeArg!)).toBe(`['foo']`) + }) + + test('defineEmits (type)', () => { + const src = ` + defineEmits<{ (e: 'x'): void }>() + ` + const { ranges } = compile(``) + expect(getRange(src, ranges!.emitsTypeArg!)).toBe(`{ (e: 'x'): void }`) + }) +}) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index d8873610..ade6d2d2 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1,6 +1,11 @@ import MagicString from 'magic-string' import { BindingMetadata, BindingTypes, UNREF } from '@vue/compiler-core' -import { SFCDescriptor, SFCScriptBlock } from './parse' +import { + ScriptSetupTextRanges, + SFCDescriptor, + SFCScriptBlock, + TextRange +} from './parse' import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser' import { babelParserDefaultPlugins, generateCodeFrame } from '@vue/shared' import { @@ -71,7 +76,31 @@ export interface SFCScriptCompileOptions { * from being hot-reloaded separately from component state. */ inlineTemplate?: boolean + /** + * Options for template compilation when inlining. Note these are options that + * would normally be pased to `compiler-sfc`'s own `compileTemplate()`, not + * options passed to `compiler-dom`. + */ templateOptions?: Partial + /** + * Skip codegen and only return AST / binding / text range information. + * Also makes the call error-tolerant. + * Used for IDE support. + */ + parseOnly?: boolean +} + +interface ImportBinding { + isType: boolean + imported: string + source: string + rangeNode: Node + isFromSetup: boolean +} + +interface VariableBinding { + type: BindingTypes + rangeNode: Node } /** @@ -83,10 +112,22 @@ export function compileScript( sfc: SFCDescriptor, options: SFCScriptCompileOptions ): SFCScriptBlock { - const { script, scriptSetup, source, filename } = sfc + let { script, scriptSetup, source, filename } = sfc + // feature flags + const enableRefSugar = !!options.refSugar + const parseOnly = !!options.parseOnly if (scriptSetup) { - warnExperimental(`