refactor(compiler-sfc): remove parseOnly mode

This commit is contained in:
Evan You 2021-08-22 22:28:47 -04:00
parent db8dc753c0
commit 0c2ea1c134
3 changed files with 16 additions and 217 deletions

View File

@ -1,87 +0,0 @@
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()
})
})

View File

@ -3,7 +3,6 @@ import {
BindingMetadata, BindingMetadata,
BindingTypes, BindingTypes,
createRoot, createRoot,
locStub,
NodeTypes, NodeTypes,
transform, transform,
parserOptions, parserOptions,
@ -12,12 +11,7 @@ import {
isFunctionType, isFunctionType,
walkIdentifiers walkIdentifiers
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import { import { SFCDescriptor, SFCScriptBlock } from './parse'
ScriptSetupTextRanges,
SFCDescriptor,
SFCScriptBlock,
TextRange
} from './parse'
import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser' import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser'
import { import {
babelParserDefaultPlugins, babelParserDefaultPlugins,
@ -103,28 +97,16 @@ export interface SFCScriptCompileOptions {
* options passed to `compiler-dom`. * options passed to `compiler-dom`.
*/ */
templateOptions?: Partial<SFCTemplateCompileOptions> 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 { interface ImportBinding {
isType: boolean isType: boolean
imported: string imported: string
source: string source: string
rangeNode: Node
isFromSetup: boolean isFromSetup: boolean
isUsedInTemplate: boolean isUsedInTemplate: boolean
} }
interface VariableBinding {
type: BindingTypes
rangeNode: Node
}
/** /**
* Compile `<script setup>` * Compile `<script setup>`
* It requires the whole SFC descriptor because we need to handle and merge * It requires the whole SFC descriptor because we need to handle and merge
@ -138,18 +120,6 @@ export function compileScript(
// feature flags // feature flags
const enableRefSugar = !!options.refSugar const enableRefSugar = !!options.refSugar
let refBindings: string[] | undefined 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 // for backwards compat
if (!options) { if (!options) {
@ -190,8 +160,7 @@ export function compileScript(
try { try {
const scriptAst = _parse(script.content, { const scriptAst = _parse(script.content, {
plugins, plugins,
sourceType: 'module', sourceType: 'module'
errorRecovery: parseOnly
}).program.body }).program.body
const bindings = analyzeScriptBindings(scriptAst) const bindings = analyzeScriptBindings(scriptAst)
let content = script.content let content = script.content
@ -232,18 +201,11 @@ export function compileScript(
// metadata that needs to be returned // metadata that needs to be returned
const bindingMetadata: BindingMetadata = {} const bindingMetadata: BindingMetadata = {}
const ranges: ScriptSetupTextRanges | undefined = parseOnly
? {
scriptBindings: [],
scriptSetupBindings: []
}
: undefined
const defaultTempVar = `__default__` const defaultTempVar = `__default__`
const helperImports: Set<string> = new Set() const helperImports: Set<string> = new Set()
const userImports: Record<string, ImportBinding> = Object.create(null) const userImports: Record<string, ImportBinding> = Object.create(null)
const userImportAlias: Record<string, string> = 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 defaultExport: Node | undefined
let hasDefinePropsCall = false let hasDefinePropsCall = false
@ -288,7 +250,6 @@ export function compileScript(
offset: number offset: number
): Program { ): Program {
try { try {
options.errorRecovery = parseOnly
return _parse(input, options).program return _parse(input, options).program
} catch (e) { } catch (e) {
e.message = `[@vue/compiler-sfc] ${e.message}\n\n${ e.message = `[@vue/compiler-sfc] ${e.message}\n\n${
@ -317,8 +278,7 @@ export function compileScript(
local: string, local: string,
imported: string | false, imported: string | false,
isType: boolean, isType: boolean,
isFromSetup: boolean, isFromSetup: boolean
rangeNode: Node
) { ) {
if (source === 'vue' && imported) { if (source === 'vue' && imported) {
userImportAlias[imported] = local userImportAlias[imported] = local
@ -337,7 +297,6 @@ export function compileScript(
isType, isType,
imported: imported || 'default', imported: imported || 'default',
source, source,
rangeNode,
isFromSetup, isFromSetup,
isUsedInTemplate isUsedInTemplate
} }
@ -609,8 +568,7 @@ export function compileScript(
specifier.local.name, specifier.local.name,
imported, imported,
node.importKind === 'type', node.importKind === 'type',
false, false
specifier.local
) )
} }
} else if (node.type === 'ExportDefaultDeclaration') { } else if (node.type === 'ExportDefaultDeclaration') {
@ -705,8 +663,8 @@ export function compileScript(
) { ) {
error( error(
`ref sugar using the label syntax was an experimental proposal and ` + `ref sugar using the label syntax was an experimental proposal and ` +
`has been dropped based on community feedback.`, `has been dropped based on community feedback. Please check out ` +
// TODO + ` Please check out the adjusted proposal at ...`, `the new proposal at https://github.com/vuejs/rfcs/discussions/369`,
node node
) )
} }
@ -764,8 +722,7 @@ export function compileScript(
local, local,
imported, imported,
node.importKind === 'type', node.importKind === 'type',
true, true
specifier.local
) )
} }
} }
@ -897,44 +854,6 @@ export function compileScript(
} }
} }
// 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 // 3. Apply ref sugar transform
if (enableRefSugar) { if (enableRefSugar) {
warnExperimental( warnExperimental(
@ -1007,7 +926,7 @@ export function compileScript(
: BindingTypes.SETUP_MAYBE_REF : BindingTypes.SETUP_MAYBE_REF
} }
for (const key in setupBindings) { for (const key in setupBindings) {
bindingMetadata[key] = setupBindings[key].type bindingMetadata[key] = setupBindings[key]
} }
// known ref bindings // known ref bindings
if (refBindings) { if (refBindings) {
@ -1240,19 +1159,16 @@ export function compileScript(
} }
function registerBinding( function registerBinding(
bindings: Record<string, VariableBinding>, bindings: Record<string, BindingTypes>,
node: Identifier, node: Identifier,
type: BindingTypes type: BindingTypes
) { ) {
bindings[node.name] = { bindings[node.name] = type
type,
rangeNode: node
}
} }
function walkDeclaration( function walkDeclaration(
node: Declaration, node: Declaration,
bindings: Record<string, VariableBinding>, bindings: Record<string, BindingTypes>,
userImportAlias: Record<string, string> userImportAlias: Record<string, string>
) { ) {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
@ -1301,16 +1217,13 @@ function walkDeclaration(
) { ) {
// export function foo() {} / export class Foo {} // export function foo() {} / export class Foo {}
// export declarations must be named. // export declarations must be named.
bindings[node.id!.name] = { bindings[node.id!.name] = BindingTypes.SETUP_CONST
type: BindingTypes.SETUP_CONST,
rangeNode: node.id!
}
} }
} }
function walkObjectPattern( function walkObjectPattern(
node: ObjectPattern, node: ObjectPattern,
bindings: Record<string, VariableBinding>, bindings: Record<string, BindingTypes>,
isConst: boolean, isConst: boolean,
isDefineCall = false isDefineCall = false
) { ) {
@ -1341,7 +1254,7 @@ function walkObjectPattern(
function walkArrayPattern( function walkArrayPattern(
node: ArrayPattern, node: ArrayPattern,
bindings: Record<string, VariableBinding>, bindings: Record<string, BindingTypes>,
isConst: boolean, isConst: boolean,
isDefineCall = false isDefineCall = false
) { ) {
@ -1352,7 +1265,7 @@ function walkArrayPattern(
function walkPattern( function walkPattern(
node: Node, node: Node,
bindings: Record<string, VariableBinding>, bindings: Record<string, BindingTypes>,
isConst: boolean, isConst: boolean,
isDefineCall = false isDefineCall = false
) { ) {
@ -1745,13 +1658,6 @@ function getObjectOrArrayExpressionKeys(value: Node): string[] {
return [] return []
} }
function toTextRange(node: Node): TextRange {
return {
start: node.start!,
end: node.end!
}
}
const templateUsageCheckCache = createCache<string>() const templateUsageCheckCache = createCache<string>()
function resolveTemplateUsageCheckString(sfc: SFCDescriptor) { function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {

View File

@ -43,27 +43,7 @@ export interface SFCScriptBlock extends SFCBlock {
bindings?: BindingMetadata bindings?: BindingMetadata
scriptAst?: Statement[] scriptAst?: Statement[]
scriptSetupAst?: Statement[] scriptSetupAst?: Statement[]
ranges?: ScriptSetupTextRanges
} }
/**
* Text range data for IDE support
*/
export interface ScriptSetupTextRanges {
scriptBindings: TextRange[]
scriptSetupBindings: TextRange[]
propsTypeArg?: TextRange
propsRuntimeArg?: TextRange
emitsTypeArg?: TextRange
emitsRuntimeArg?: TextRange
withDefaultsArg?: TextRange
}
export interface TextRange {
start: number
end: number
}
export interface SFCStyleBlock extends SFCBlock { export interface SFCStyleBlock extends SFCBlock {
type: 'style' type: 'style'
scoped?: boolean scoped?: boolean