Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(language-core): typed directives in template #4807

Merged
merged 8 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions packages/language-core/lib/codegen/globalTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import { getSlotsPropertyName } from '../utils/shared';

export function generateGlobalTypes(lib: string, target: number, strictTemplates: boolean) {
const fnPropsType = `(K extends { $props: infer Props } ? Props : any)${strictTemplates ? '' : ' & Record<string, unknown>'}`;
return `
let text = ``;
if (target < 3.5) {
text += `
; declare module '${lib}' {
export interface GlobalComponents { }
}
declare global {
export interface GlobalDirectives { }
}`;
}
text += `
; declare global {
const __VLS_intrinsicElements: __VLS_IntrinsicElements;
const __VLS_directiveBindingRestFields: { instance: null, oldValue: null, modifiers: any, dir: any };
const __VLS_unref: typeof import('${lib}').unref;
Expand All @@ -28,9 +33,10 @@ declare global {
)}
type __VLS_GlobalComponents = ${(
target >= 3.5
? `import('${lib}').GlobalComponents`
? `import('${lib}').GlobalComponents;`
: `import('${lib}').GlobalComponents & Pick<typeof import('${lib}'), 'Transition' | 'TransitionGroup' | 'KeepAlive' | 'Suspense' | 'Teleport'>;`
)}
type __VLS_GlobalDirectives = import('${lib}').GlobalDirectives;
type __VLS_IsAny<T> = 0 extends 1 & T ? true : false;
type __VLS_PickNotAny<A, B> = __VLS_IsAny<A> extends true ? B : A;
type __VLS_unknownDirective = (arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown) => void;
Expand Down Expand Up @@ -131,4 +137,5 @@ declare global {
function __VLS_tryAsConstant<const T>(t: T): T;
}
`;
return text;
};
30 changes: 29 additions & 1 deletion packages/language-core/lib/codegen/script/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,34 @@ function* generateTemplateComponents(options: ScriptCodegenOptions): Generator<C
}
yield `}${endOfLine}`;

yield `let __VLS_components: typeof __VLS_localComponents & __VLS_GlobalComponents${endOfLine}`;
yield `let __VLS_components!: typeof __VLS_localComponents & __VLS_GlobalComponents${endOfLine}`;
}

export function* generateTemplateDirectives(options: ScriptCodegenOptions): Generator<Code> {
const exps: Code[] = [];

if (options.sfc.script && options.scriptRanges?.exportDefault?.directivesOption) {
const { directivesOption } = options.scriptRanges.exportDefault;
exps.push([
options.sfc.script.content.substring(directivesOption.start, directivesOption.end),
'script',
directivesOption.start,
codeFeatures.navigation,
]);
}

exps.push(`{} as NonNullable<typeof __VLS_self extends { directives: infer D } ? D : {}>`);
exps.push(`__VLS_ctx`);

yield `const __VLS_localDirectives = {${newLine}`;
for (const type of exps) {
yield `...`;
yield type;
yield `,${newLine}`;
}
yield `}${endOfLine}`;

yield `let __VLS_directives!: typeof __VLS_localDirectives & __VLS_GlobalDirectives${endOfLine}`;
}

export function* generateTemplate(
Expand All @@ -100,6 +127,7 @@ export function* generateTemplate(
});
yield* generateTemplateCtx(options, isClassComponent);
yield* generateTemplateComponents(options);
yield* generateTemplateDirectives(options);
yield* generateTemplateBody(options, templateCodegenCtx);
return templateCodegenCtx;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function* generateElementDirectives(
prop.loc.start.offset,
prop.loc.end.offset,
ctx.codeFeatures.verification,
`__VLS_directiveAsFunction(__VLS_ctx.`,
`__VLS_directiveAsFunction(__VLS_directives.`,
...generateCamelized(
'v-' + prop.name,
prop.loc.start.offset,
Expand Down
10 changes: 8 additions & 2 deletions packages/language-core/lib/parsers/scriptRanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
argsNode: ts.ObjectLiteralExpression | undefined,
componentsOption: TextRange | undefined,
componentsOptionNode: ts.ObjectLiteralExpression | undefined,
directivesOption: TextRange | undefined,
nameOption: TextRange | undefined,
inheritAttrsOption: string | undefined,
}) | undefined;
Expand Down Expand Up @@ -40,6 +41,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
}
if (obj) {
let componentsOptionNode: ts.ObjectLiteralExpression | undefined;
let directivesOptionNode: ts.ObjectLiteralExpression | undefined;
let nameOptionNode: ts.Expression | undefined;
let inheritAttrsOption: string | undefined;
ts.forEachChild(obj, node => {
Expand All @@ -48,10 +50,13 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
if (name === 'components' && ts.isObjectLiteralExpression(node.initializer)) {
componentsOptionNode = node.initializer;
}
if (name === 'name') {
else if (name === 'directives' && ts.isObjectLiteralExpression(node.initializer)) {
directivesOptionNode = node.initializer;
}
else if (name === 'name') {
nameOptionNode = node.initializer;
}
if (name === 'inheritAttrs') {
else if (name === 'inheritAttrs') {
inheritAttrsOption = getNodeText(ts, node.initializer, ast);
}
}
Expand All @@ -63,6 +68,7 @@ export function parseScriptRanges(ts: typeof import('typescript'), ast: ts.Sourc
argsNode: withNode ? obj : undefined,
componentsOption: componentsOptionNode ? _getStartEnd(componentsOptionNode) : undefined,
componentsOptionNode: withNode ? componentsOptionNode : undefined,
directivesOption: directivesOptionNode ? _getStartEnd(directivesOptionNode) : undefined,
nameOption: nameOptionNode ? _getStartEnd(nameOptionNode) : undefined,
inheritAttrsOption,
};
Expand Down
1 change: 1 addition & 0 deletions test-workspace/tsc/passedFixtures/vue2/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"../vue3/defineEmits",
"../vue3/defineModel",
"../vue3/defineProp_B",
"../vue3/directives/option.vue",
"../vue3/events",
"../vue3/no-script-block",
"../vue3/slots",
Expand Down
13 changes: 10 additions & 3 deletions test-workspace/tsc/passedFixtures/vue3/directives/main.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@
import { FunctionDirective } from 'vue';
import { exactType } from '../../shared';

let Foo: (_: { foo?: string; }) => void;
declare module 'vue' {
interface GlobalDirectives {
vFoo: FunctionDirective<typeof Comp, (_: number) => void>;
}
}

let vFoo: FunctionDirective<typeof Foo, (_: number) => void>;
let Comp!: (_: { foo?: string; }) => void;

let vBar!: FunctionDirective<typeof Comp, (_: boolean) => void>;
</script>

<template>
<Foo v-foo="v => exactType(v, {} as number)"></Foo>
<Comp v-foo="v => exactType(v, {} as number)" />
<Comp v-bar="v => exactType(v, {} as boolean)" />
</template>
19 changes: 19 additions & 0 deletions test-workspace/tsc/passedFixtures/vue3/directives/option.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script lang="ts">
import { FunctionDirective } from 'vue';
import { exactType } from '../../shared';

let Comp!: (_: { foo?: string; }) => void;

export default {
directives: {
vBaz: {} as FunctionDirective<typeof Comp, (_: string) => void>
}
};
</script>

<script setup lang="ts">
</script>

<template>
<Comp v-baz="v => exactType(v, {} as string)" />
</template>