+++ /dev/null
-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 = `
- <script>${scriptSrc}</script>
- <script setup>${scriptSetupSrc}</script>
- `
- 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(`<script setup>${src}</script>`)
- expect(getRange(src, ranges!.propsRuntimeArg!)).toBe(`{ foo: String }`)
- })
-
- test('defineProps (type)', () => {
- const src = `
- interface Props { x?: number }
- defineProps<Props>()
- `
- const { ranges } = compile(`<script setup lang="ts">${src}</script>`)
- expect(getRange(src, ranges!.propsTypeArg!)).toBe(`Props`)
- })
-
- test('withDefaults', () => {
- const src = `
- interface Props { x?: number }
- withDefaults(defineProps<Props>(), { x: 1 })
- `
- const { ranges } = compile(`<script setup lang="ts">${src}</script>`)
- expect(getRange(src, ranges!.withDefaultsArg!)).toBe(`{ x: 1 }`)
- })
-
- test('defineEmits', () => {
- const src = `
- defineEmits(['foo'])
- `
- const { ranges } = compile(`<script setup>${src}</script>`)
- expect(getRange(src, ranges!.emitsRuntimeArg!)).toBe(`['foo']`)
- })
-
- test('defineEmits (type)', () => {
- const src = `
- defineEmits<{ (e: 'x'): void }>()
- `
- const { ranges } = compile(`<script setup lang="ts">${src}</script>`)
- expect(getRange(src, ranges!.emitsTypeArg!)).toBe(`{ (e: 'x'): void }`)
- })
-
- test('no script setup block', () => {
- const src = `import { x } from './x'`
- const { ranges } = compile(`<script>${src}</script>`)
- expect(getRange(src, ranges!.scriptBindings[0])).toBe(`x`)
- })
-
- test('no script block', () => {
- expect(() => compile(`<style>hello</style>`)).not.toThrow()
- })
-})
BindingMetadata,
BindingTypes,
createRoot,
- locStub,
NodeTypes,
transform,
parserOptions,
isFunctionType,
walkIdentifiers
} from '@vue/compiler-dom'
-import {
- ScriptSetupTextRanges,
- SFCDescriptor,
- SFCScriptBlock,
- TextRange
-} from './parse'
+import { SFCDescriptor, SFCScriptBlock } from './parse'
import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser'
import {
babelParserDefaultPlugins,
* options passed to `compiler-dom`.
*/
templateOptions?: Partial<SFCTemplateCompileOptions>
- /**
- * 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
isUsedInTemplate: boolean
}
-interface VariableBinding {
- type: BindingTypes
- rangeNode: Node
-}
-
/**
* Compile `<script setup>`
* It requires the whole SFC descriptor because we need to handle and merge
// feature flags
const enableRefSugar = !!options.refSugar
let refBindings: string[] | undefined
- const parseOnly = !!options.parseOnly
-
- if (parseOnly && !scriptSetup) {
- // in parse-only mode, construct a fake script setup so we still perform
- // the full parse logic.
- scriptSetup = {
- type: 'script',
- content: '',
- attrs: {},
- loc: locStub
- }
- }
// for backwards compat
if (!options) {
try {
const scriptAst = _parse(script.content, {
plugins,
- sourceType: 'module',
- errorRecovery: parseOnly
+ sourceType: 'module'
}).program.body
const bindings = analyzeScriptBindings(scriptAst)
let content = script.content
// metadata that needs to be returned
const bindingMetadata: BindingMetadata = {}
- const ranges: ScriptSetupTextRanges | undefined = parseOnly
- ? {
- scriptBindings: [],
- scriptSetupBindings: []
- }
- : undefined
-
const defaultTempVar = `__default__`
const helperImports: Set<string> = new Set()
const userImports: Record<string, ImportBinding> = Object.create(null)
const userImportAlias: Record<string, string> = Object.create(null)
- const setupBindings: Record<string, VariableBinding> = Object.create(null)
+ const setupBindings: Record<string, BindingTypes> = Object.create(null)
let defaultExport: Node | undefined
let hasDefinePropsCall = false
offset: number
): Program {
try {
- options.errorRecovery = parseOnly
return _parse(input, options).program
} catch (e) {
e.message = `[@vue/compiler-sfc] ${e.message}\n\n${
local: string,
imported: string | false,
isType: boolean,
- isFromSetup: boolean,
- rangeNode: Node
+ isFromSetup: boolean
) {
if (source === 'vue' && imported) {
userImportAlias[imported] = local
isType,
imported: imported || 'default',
source,
- rangeNode,
isFromSetup,
isUsedInTemplate
}
specifier.local.name,
imported,
node.importKind === 'type',
- false,
- specifier.local
+ false
)
}
} else if (node.type === 'ExportDefaultDeclaration') {
) {
error(
`ref sugar using the label syntax was an experimental proposal and ` +
- `has been dropped based on community feedback.`,
- // TODO + ` Please check out the adjusted proposal at ...`,
+ `has been dropped based on community feedback. Please check out ` +
+ `the new proposal at https://github.com/vuejs/rfcs/discussions/369`,
node
)
}
local,
imported,
node.importKind === 'type',
- true,
- specifier.local
+ true
)
}
}
}
}
- // in parse only mode, we should have collected all the information we need,
- // return early.
- if (parseOnly) {
- for (const key in userImports) {
- const { rangeNode, isFromSetup } = userImports[key]
- const bindings = isFromSetup
- ? ranges!.scriptSetupBindings
- : ranges!.scriptBindings
- bindings.push(toTextRange(rangeNode))
- }
- for (const key in setupBindings) {
- ranges!.scriptSetupBindings.push(
- toTextRange(setupBindings[key].rangeNode)
- )
- }
- if (propsRuntimeDecl) {
- ranges!.propsRuntimeArg = toTextRange(propsRuntimeDecl)
- }
- if (propsTypeDeclRaw) {
- ranges!.propsTypeArg = toTextRange(propsTypeDeclRaw)
- }
- if (emitsRuntimeDecl) {
- ranges!.emitsRuntimeArg = toTextRange(emitsRuntimeDecl)
- }
- if (emitsTypeDeclRaw) {
- ranges!.emitsTypeArg = toTextRange(emitsTypeDeclRaw)
- }
- if (propsRuntimeDefaults) {
- ranges!.withDefaultsArg = toTextRange(propsRuntimeDefaults)
- }
- return {
- ...scriptSetup,
- ranges,
- scriptAst: scriptAst?.body,
- scriptSetupAst: scriptSetupAst?.body
- }
- }
-
// 3. Apply ref sugar transform
if (enableRefSugar) {
warnExperimental(
: BindingTypes.SETUP_MAYBE_REF
}
for (const key in setupBindings) {
- bindingMetadata[key] = setupBindings[key].type
+ bindingMetadata[key] = setupBindings[key]
}
// known ref bindings
if (refBindings) {
}
function registerBinding(
- bindings: Record<string, VariableBinding>,
+ bindings: Record<string, BindingTypes>,
node: Identifier,
type: BindingTypes
) {
- bindings[node.name] = {
- type,
- rangeNode: node
- }
+ bindings[node.name] = type
}
function walkDeclaration(
node: Declaration,
- bindings: Record<string, VariableBinding>,
+ bindings: Record<string, BindingTypes>,
userImportAlias: Record<string, string>
) {
if (node.type === 'VariableDeclaration') {
) {
// export function foo() {} / export class Foo {}
// export declarations must be named.
- bindings[node.id!.name] = {
- type: BindingTypes.SETUP_CONST,
- rangeNode: node.id!
- }
+ bindings[node.id!.name] = BindingTypes.SETUP_CONST
}
}
function walkObjectPattern(
node: ObjectPattern,
- bindings: Record<string, VariableBinding>,
+ bindings: Record<string, BindingTypes>,
isConst: boolean,
isDefineCall = false
) {
function walkArrayPattern(
node: ArrayPattern,
- bindings: Record<string, VariableBinding>,
+ bindings: Record<string, BindingTypes>,
isConst: boolean,
isDefineCall = false
) {
function walkPattern(
node: Node,
- bindings: Record<string, VariableBinding>,
+ bindings: Record<string, BindingTypes>,
isConst: boolean,
isDefineCall = false
) {
return []
}
-function toTextRange(node: Node): TextRange {
- return {
- start: node.start!,
- end: node.end!
- }
-}
-
const templateUsageCheckCache = createCache<string>()
function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {