Skip to content

Commit

Permalink
fix: compiler doens't report vineProp mixed with formal param
Browse files Browse the repository at this point in the history
  • Loading branch information
ShenQingchuan committed Nov 1, 2024
1 parent f61af7f commit e6064e2
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 41 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"build:eslint-parser": "cross-env NODE_ENV=production pnpm --filter @vue-vine/eslint-parser run build",
"build:main": "cross-env NODE_ENV=production pnpm --filter vue-vine run build",
"build:ls": "pnpm --filter @vue-vine/compiler --filter @vue-vine/language-service --filter @vue-vine/language-server --filter vue-vine-tsc run build",
"build:ext": "pnpm run build:ls && pnpm --filter vue-vine-extension run build",
"build:ext": "pnpm run build:compiler && pnpm run build:ls && pnpm --filter vue-vine-extension run build",
"test": "esno scripts/run-test.js",
"test:compiler": "pnpm --filter @vue-vine/compiler run test",
"test:e2e": "pnpm --filter @vue-vine/e2e-test run test",
Expand Down
11 changes: 8 additions & 3 deletions packages/compiler/src/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,16 +375,21 @@ const analyzeVineProps: AnalyzeRunner = (
// its type is `identifier`, and it must have an object literal type annotation.
// Save this parameter's name as `propsAlias`
const propsFormalParam = formalParams[0] as Identifier
const propsTypeAnnotation = (propsFormalParam.typeAnnotation as TSTypeAnnotation).typeAnnotation as TSTypeLiteral
const propsTypeAnnotation = (propsFormalParam.typeAnnotation as TSTypeAnnotation)?.typeAnnotation as TSTypeLiteral | undefined
if (!propsTypeAnnotation) {
return
}

vineCompFnCtx.propsFormalParam = propsTypeAnnotation
vineCompFnCtx.propsAlias = propsFormalParam.name;
// Analyze the object literal type annotation
// and save the props info into `vineCompFnCtx.props`
(propsTypeAnnotation.members as TSPropertySignature[])?.forEach((member) => {
if (!isIdentifier(member.key)) {
if (!isIdentifier(member.key) || !member.typeAnnotation) {
return
}
const propName = member.key.name
const propType = vineFileCtx.getAstNodeContent(member.typeAnnotation!.typeAnnotation)
const propType = vineFileCtx.getAstNodeContent(member.typeAnnotation.typeAnnotation)
const propMeta: VinePropMeta = {
isFromMacroDefine: false,
isRequired: member.optional === undefined ? true : !member.optional,
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export interface VineCompFnCtx {
propsAlias: string
props: Record<string, VinePropMeta>
propsDefinitionBy: 'annotaion' | 'macro'
propsFormalParam?: TSTypeLiteral
emitsAlias: string
emits: string[]
emitsTypeParam?: TSTypeLiteral
Expand Down
28 changes: 21 additions & 7 deletions packages/compiler/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1181,13 +1181,27 @@ function validatePropsForSingelFC(
isCheckFormalParamsPropPass = false
}

if (!isCheckFormalParamsPropPass) {
// Still check if there're maybe some invalid `vineProp` macro call
// that should be reported
isCheckVinePropMacroCallPass()
return false
}
return isCheckVinePropMacroCallPass()
// Still check if there're maybe some invalid `vineProp` macro call
// that should be reported, we don't allow two defintion styles to be used together
_breakableTraverse(
vineCompFnDecl,
(node) => {
if (isVineProp(node)) {
vineCompilerHooks.onError(
vineErr(
{ vineFileCtx },
{
msg: '`vineProp` macro calls is not allowed when props with props formal parameter defined',
location: node.loc,
},
),
)
isCheckFormalParamsPropPass = false
}
},
)

return isCheckFormalParamsPropPass
}
vineCompilerHooks.onError(
vineErr(
Expand Down
6 changes: 3 additions & 3 deletions packages/compiler/tests/validate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,9 @@ function App() {
"If you're defining a Vine component function's props with formal parameter, it must be one and only identifier",
"Vine component function's props type annotation must be an object literal",
"Vine component function's props type annotation must be an object literal",
"\`vineProp\` macro call must have a type parameter to specify the prop's type",
"\`vineProp.withDefault\` macro call must have at least 1 argument",
"\`vineProp.optional\` macro call must have a type parameter to specify the prop's type",
"\`vineProp\` macro calls is not allowed when props with props formal parameter defined",
"\`vineProp\` macro calls is not allowed when props with props formal parameter defined",
"\`vineProp\` macro calls is not allowed when props with props formal parameter defined",
]
`)
})
Expand Down
78 changes: 53 additions & 25 deletions packages/language-service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ export function createVueVineLanguagePlugin(
},
createVirtualCode(uriOrFileName, langaugeId, snapshot) {
const moduleId = String(uriOrFileName)
if (moduleId.endsWith('.vine.ts') && langaugeId === 'typescript') {
if (
moduleId.endsWith('.vine.ts')
&& !moduleId.includes('volar_virtual_code')
&& langaugeId === 'typescript'
) {
if (vineActiveModuleId !== moduleId) {
vinePerfMonitorLogger.reset()
vineActiveModuleId = moduleId
Expand Down Expand Up @@ -208,33 +212,19 @@ function createVueVineCode(
tsCodeSegments.push(propsParam)

// Generate `context: { ... }` after `props: ...`
const contextProperties: string[] = []

const slotsParam = `slots: { ${vineCompFn.slotsNamesInTemplate.map((slot) => {
const slotPropTypeLiteralNode = vineCompFn.slots[slot]?.props
return `${slot}: ${
slotPropTypeLiteralNode
? `(props: ${vineFileCtx.getAstNodeContent(slotPropTypeLiteralNode)}) => any`
: 'unknown'
}`
}).join(', ')} },`
contextProperties.push(slotsParam)

const emitsParam = `emit: ${
vineCompFn.emitsTypeParam
? `VueDefineEmits<${
vineFileCtx.getAstNodeContent(vineCompFn.emitsTypeParam)
}>`
: `{ ${vineCompFn.emits.map(emit => `${emit}: (...args: any[]) => boolean`)} }`
},`
contextProperties.push(emitsParam)

const contextFormalParam = `\n context: {\n${' '.repeat(4)}${contextProperties.join(`\n${' '.repeat(4)}`)}\n }\n`
tsCodeSegments.push(contextFormalParam)
tsCodeSegments.push(generateContextFormalParam(vineCompFn))
}
else {
// User provide a `props` formal parameter in the component function,
// we should keep it in virtual code, and generate `context: ...` after it.
// we should keep it in virtual code, and generate `context: ...` after it,
const formalParamNode = vineCompFn.propsFormalParam!
generateScriptUntil(formalParamNode.end!)

// Generate `context: { ... }` after `props: ...`
tsCodeSegments.push(`, ${generateContextFormalParam(vineCompFn, {
tabNum: 2,
lineWrapAtStart: false,
})}`)
}

// Need to extract all complex expression in `vineProp.withDefault`
Expand Down Expand Up @@ -400,6 +390,44 @@ function createVueVineCode(
currentOffset = targetOffset
}

function generateContextFormalParam(
vineCompFn: ReturnType<typeof compileVineForVirtualCode>['vineFileCtx']['vineCompFns'][number],
{
tabNum = 4,
lineWrapAtStart = true,
}: {
tabNum?: number
lineWrapAtStart?: boolean
} = {},
) {
// Generate `context: { ... }` after `props: ...`
const contextProperties: string[] = []

const slotsParam = `slots: { ${vineCompFn.slotsNamesInTemplate.map((slot) => {
const slotPropTypeLiteralNode = vineCompFn.slots[slot]?.props
return `${slot}: ${
slotPropTypeLiteralNode
? `(props: ${vineFileCtx.getAstNodeContent(slotPropTypeLiteralNode)}) => any`
: 'unknown'
}`
}).join(', ')} },`
contextProperties.push(slotsParam)

const emitsParam = `emit: ${
vineCompFn.emitsTypeParam
? `VueDefineEmits<${
vineFileCtx.getAstNodeContent(vineCompFn.emitsTypeParam)
}>`
: `{ ${vineCompFn.emits.map(emit => `${emit}: (...args: any[]) => boolean`)} }`
},`
contextProperties.push(emitsParam)

const contextFormalParam = `${lineWrapAtStart ? `\n` : ''} context: {\n${
' '.repeat(tabNum)}${contextProperties.join(`\n${' '.repeat(tabNum)}`)
}\n }\n`
return contextFormalParam
}

function* createStyleEmbeddedCodes(): Generator<VirtualCode> {
for (const styleDefines of Object.values(
vineFileCtx.styleDefine,
Expand Down
6 changes: 4 additions & 2 deletions packages/playground/src/pages/about.vine.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { PageHeader } from '../components/page-header.vine'

function TestSlotContainer() {
const fizz = vineProp<string>()
function TestSlotContainer(props: {
fizz: string
}) {
// const fizz = vineProp<string>()
vineEmits<{ emitCamel: [bar: string] }>()
vineSlots<{ slotCamel(props: { foo: number }): any }>()

Expand Down

0 comments on commit e6064e2

Please sign in to comment.