Skip to content

Commit

Permalink
feat: Support generic typed template slots for RFC 436 (#1987)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Oct 11, 2022
1 parent 4836fa5 commit ec5946d
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 61 deletions.
89 changes: 44 additions & 45 deletions vue-language-tools/vue-language-core/src/generators/script.ts
Expand Up @@ -173,7 +173,7 @@ export function generate(
addVirtualCode('script', scriptRanges.exportDefault.end, sfc.script.content.length);
}
}
function addVirtualCode(vueTag: 'script' | 'scriptSetup', start: number, end: number) {
function addVirtualCode(vueTag: 'script' | 'scriptSetup', start: number, end?: number) {
codeGen.push([
sfc[vueTag]!.content.substring(start, end),
vueTag,
Expand Down Expand Up @@ -234,40 +234,34 @@ export function generate(

if (sfc.scriptSetup && scriptSetupRanges) {

if (scriptRanges?.exportDefault) {
codeGen.push('(() => {\n');
}
else {
if (!scriptRanges?.exportDefault) {
// fix https://github.com/johnsoncodehk/volar/issues/1127
codeGen.push([
'',
'scriptSetup',
0,
{ diagnostic: true },
]);
codeGen.push('export default (');
if (vueCompilerOptions.experimentalRfc436 && sfc.scriptSetup.generic) {
codeGen.push(`<${sfc.scriptSetup.generic}>`);
}
codeGen.push('() => {\n');
codeGen.push('export default ');
}

if (vueCompilerOptions.experimentalRfc436 && sfc.scriptSetup.generic) {
codeGen.push(`<${sfc.scriptSetup.generic}>`);
}
codeGen.push('((');
if (scriptSetupRanges.propsTypeArg) {
codeGen.push('__VLS_props: ');
addVirtualCode('scriptSetup', scriptSetupRanges.propsTypeArg.start, scriptSetupRanges.propsTypeArg.end);
}
codeGen.push(') => {\n');
codeGen.push('const __VLS_setup = async () => {\n');

codeGen.push([
sfc.scriptSetup.content.substring(scriptSetupRanges.importSectionEndOffset),
'scriptSetup',
scriptSetupRanges.importSectionEndOffset,
{
hover: true,
references: true,
definition: true,
diagnostic: true,
rename: true,
completion: true,
semanticTokens: true,
},
]);
if (scriptSetupRanges.propsTypeArg) {
addVirtualCode('scriptSetup', scriptSetupRanges.importSectionEndOffset, scriptSetupRanges.propsTypeArg.start);
codeGen.push('typeof __VLS_props');
addVirtualCode('scriptSetup', scriptSetupRanges.propsTypeArg.end);
}
else {
addVirtualCode('scriptSetup', scriptSetupRanges.importSectionEndOffset);
}

if (scriptSetupRanges.propsTypeArg && scriptSetupRanges.withDefaultsArg) {
// fix https://github.com/johnsoncodehk/volar/issues/1187
Expand Down Expand Up @@ -299,9 +293,7 @@ export function generate(
codeGen.push(`__VLS_WithDefaults<`);
}

codeGen.push(`__VLS_TypePropsToRuntimeProps<`);
addExtraReferenceVirtualCode('scriptSetup', scriptSetupRanges.propsTypeArg.start, scriptSetupRanges.propsTypeArg.end);
codeGen.push(`>`);
codeGen.push(`__VLS_TypePropsToRuntimeProps<typeof __VLS_props>`);

if (scriptSetupRanges.withDefaultsArg) {
codeGen.push(`, typeof __VLS_withDefaultsArg`);
Expand Down Expand Up @@ -381,34 +373,41 @@ export function generate(

writeTemplate();

codeGen.push(`return {} as typeof __VLS_Component`);
codeGen.push(` & (new `);
if (vueCompilerOptions.experimentalRfc436 && sfc.scriptSetup.generic) {
if (vueCompilerOptions.experimentalRfc436) {
codeGen.push(`return {} as Omit<JSX.Element, 'props' | 'children'> & Omit<InstanceType<typeof __VLS_Component>, '$slots' | '$emit'>`);
codeGen.push(` & {\n`);
if (scriptSetupRanges.propsTypeArg) {
codeGen.push(`<${sfc.scriptSetup.generic}>(__VLS_props: `);
addVirtualCode('scriptSetup', scriptSetupRanges.propsTypeArg.start, scriptSetupRanges.propsTypeArg.end);
codeGen.push(`) => {\n`);
codeGen.push(`$props: typeof __VLS_props,\n`);
codeGen.push(`props: typeof __VLS_props,\n`);
}
else {
codeGen.push(`<${sfc.scriptSetup.generic}>() => {\n`);
codeGen.push(`props: InstanceType<typeof __VLS_Component>['$props'],\n`);
}
codeGen.push(`$emit: `);
if (scriptSetupRanges.emitsTypeArg) {
codeGen.push(`$emit: `);
addVirtualCode('scriptSetup', scriptSetupRanges.emitsTypeArg.start, scriptSetupRanges.emitsTypeArg.end);
codeGen.push(`,\n`);
}
else {
codeGen.push(`InstanceType<typeof __VLS_Component>['$emit']`);
}
codeGen.push(`,\n`);
if (htmlGen?.slotsNum) {
codeGen.push(`children: ReturnType<typeof __VLS_template>,\n`);
}
codeGen.push(`};\n`);
}
else {
codeGen.push(`() => {\n`);
}
if (htmlGen?.slotsNum) {
codeGen.push(`${getSlotsPropertyName(vueVersion)}: ReturnType<typeof __VLS_template>,\n`);
codeGen.push(`return {} as typeof __VLS_Component`);
if (htmlGen?.slotsNum) {
codeGen.push(` & { new (): { $slots: ReturnType<typeof __VLS_template> } }`);
}
codeGen.push(`;\n`);
}
codeGen.push(`});\n`);
codeGen.push(`};\n`);
codeGen.push(`return {} as unknown as Awaited<ReturnType<typeof __VLS_setup>>;\n`);
codeGen.push(`})()`);
codeGen.push(`})`);
if (!vueCompilerOptions.experimentalRfc436) {
codeGen.push(`({} as any)`);
}
if (scriptRanges?.exportDefault && scriptRanges.exportDefault.expression.end !== scriptRanges.exportDefault.end) {
addVirtualCode('script', scriptRanges.exportDefault.expression.end, scriptRanges.exportDefault.end);
}
Expand Down
36 changes: 21 additions & 15 deletions vue-language-tools/vue-language-core/src/generators/template.ts
Expand Up @@ -660,7 +660,6 @@ export function generate(
function writeEvents(node: CompilerDOM.ElementNode) {

let _varComponentInstance: string | undefined;
let writedInstance = false;

for (const prop of node.props) {
if (
Expand Down Expand Up @@ -802,20 +801,23 @@ export function generate(

function tryWriteInstance() {

if (writedInstance) {
return _varComponentInstance;
}

const componentVar = componentVars[node.tag];
if (!_varComponentInstance) {
const componentVar = componentVars[node.tag];

if (componentVar) {
_varComponentInstance = `__VLS_${elementIndex++}`;
codeGen.push(`const ${_varComponentInstance} = new ${componentVar}({ `);
writeProps(node, 'class', 'slots');
codeGen.push(`});\n`);
if (componentVar) {
const _varComponentInstanceA = `__VLS_${elementIndex++}`;
const _varComponentInstanceB = `__VLS_${elementIndex++}`;
_varComponentInstance = `__VLS_${elementIndex++}`;
codeGen.push(`const ${_varComponentInstanceA} = new ${componentVar}({ `);
writeProps(node, 'class', 'slots');
codeGen.push(`});\n`);
codeGen.push(`const ${_varComponentInstanceB} = ${componentVar}({ `);
writeProps(node, 'class', 'slots');
codeGen.push(`});\n`);
codeGen.push(`let ${_varComponentInstance}!: import('./__VLS_types.js').PickNotAny<typeof ${_varComponentInstanceA}, typeof ${_varComponentInstanceB}>;\n`);
}
}

writedInstance = true;
return _varComponentInstance;
}
}
Expand Down Expand Up @@ -1233,15 +1235,19 @@ export function generate(
&& prop.name === 'slot'
) {

const varComponentInstance = `__VLS_${elementIndex++}`;
const varComponentInstanceA = `__VLS_${elementIndex++}`;
const varComponentInstanceB = `__VLS_${elementIndex++}`;
const varSlots = `__VLS_${elementIndex++}`;

if (componentVar && parentEl) {
codeGen.push(`const ${varComponentInstance} = new ${componentVar}({ `);
codeGen.push(`const ${varComponentInstanceA} = new ${componentVar}({ `);
writeProps(parentEl, 'class', 'slots');
codeGen.push(`});\n`);
codeGen.push(`const ${varComponentInstanceB} = ${componentVar}({ `);
writeProps(parentEl, 'class', 'slots');
codeGen.push(`});\n`);
writeInterpolationVarsExtraCompletion();
codeGen.push(`let ${varSlots}!: import('./__VLS_types.js').ExtractComponentSlots<typeof ${varComponentInstance}>;\n`);
codeGen.push(`let ${varSlots}!: import('./__VLS_types.js').ExtractComponentSlots<import('./__VLS_types.js').PickNotAny<typeof ${varComponentInstanceA}, typeof ${varComponentInstanceB}>>;\n`);
}

if (prop.exp?.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION) {
Expand Down
4 changes: 3 additions & 1 deletion vue-language-tools/vue-language-core/src/utils/localTypes.ts
Expand Up @@ -53,16 +53,18 @@ export declare function directiveFunction<T>(dir: T):
export declare function withScope<T, K>(ctx: T, scope: K): ctx is T & K;
export declare function makeOptional<T>(t: T): { [K in keyof T]?: T[K] };
// TODO: make it stricter between class component type and functional component type
export type ExtractComponentSlots<T> =
IsAny<T> extends true ? Record<string, any>
: T extends { ${slots}?: infer S } ? { [K in keyof S]-?: S[K] extends ((obj: infer O) => any) | undefined ? O : any }
: T extends { children?: infer S } ? { [K in keyof S]-?: S[K] extends ((obj: infer O) => any) | undefined ? O : any }
: Record<string, any>;
export type FillingEventArg_ParametersLength<E extends (...args: any) => any> = IsAny<Parameters<E>> extends true ? -1 : Parameters<E>['length'];
export type FillingEventArg<E> = E extends (...args: any) => any ? FillingEventArg_ParametersLength<E> extends 0 ? ($event?: undefined) => ReturnType<E> : E : E;
export type ExtractProps<T> =
T extends FunctionalComponent<infer P> ? P
T extends (...args: any) => { props: infer Props } ? Props
: T extends new (...args: any) => { $props: infer Props } ? Props
: T; // IntrinsicElement
export type ReturnVoid<T> = T extends (...payload: infer P) => any ? (...payload: P) => void : (...args: any) => void;
Expand Down

0 comments on commit ec5946d

Please sign in to comment.