diff --git a/packages/compiler-sfc/__tests__/compileScriptParseOnlyMode.spec.ts b/packages/compiler-sfc/__tests__/compileScriptParseOnlyMode.spec.ts
new file mode 100644
index 00000000000..50a8a112245
--- /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 d88736106d2..ade6d2d2e03 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(`