diff --git a/.vscode/settings.json b/.vscode/settings.json index 6b4719f5..2cf52472 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "Builtins", "Codegen", "deindent", + "depromisify", "endregion", "hygen", "lcfirst", diff --git a/packages/compiler-tsx/src/template/generate.ts b/packages/compiler-tsx/src/template/generate.ts index 9d347710..99679c44 100644 --- a/packages/compiler-tsx/src/template/generate.ts +++ b/packages/compiler-tsx/src/template/generate.ts @@ -407,15 +407,11 @@ function genComponentNode(node: ComponentNode): void { ctx.write('/>', node.endTagLoc).newLine() return // done } - writeLine('>') + ctx.write('$slots=') indent(() => { wrap('{', '}', () => { - ctx.write( - `${getRuntimeFn(ctx.typeIdentifier, 'checkSlots')}(${ - node.resolvedName ?? node.tag - }, {`, - ) + ctx.write(`{`) ctx.newLine() indent(() => { node.slots.forEach((slotNode) => { @@ -447,9 +443,10 @@ function genComponentNode(node: ComponentNode): void { ctx.write('},').newLine() }) }) - ctx.write('})') + ctx.write('}') }) }) + writeLine('>') ctx.newLine() ctx.write(' } @@ -22,6 +22,7 @@ export function transformScriptSetup( options: TransformOptionsResolved, ): ScriptSetupBlockTransformResult { const content = script?.content ?? '' + const generic = script?.attrs?.['generic'] const result = transform(content, { internalIdentifierPrefix: options.internalIdentifierPrefix, runtimeModuleName: options.runtimeModuleName, @@ -30,6 +31,9 @@ export function transformScriptSetup( fileName: options.fileName, lib: options.typescript, cache: options.cache, + attrsIdentifier: `${options.internalIdentifierPrefix}_attrs`, + slotsIdentifier: `${options.internalIdentifierPrefix}_slots`, + generic: typeof generic === 'string' ? generic : undefined, }) invariant(result.map != null) @@ -38,11 +42,9 @@ export function transformScriptSetup( code: result.code, map: result.map, identifiers: result.identifiers, - exportIdentifier: result.componentIdentifier, + exportIdentifier: result.privateComponentIdentifier, + componentIdentifier: result.publicComponentIdentifier, scopeIdentifier: result.scopeIdentifier, - propsIdentifier: result.propsIdentifier, - emitsIdentifier: result.emitsIdentifier, - exposeIdentifier: result.exposeIdentifier, exports: result.exports, } } diff --git a/packages/compiler-tsx/src/vue/compile.ts b/packages/compiler-tsx/src/vue/compile.ts index e3ac9ea6..638002d7 100644 --- a/packages/compiler-tsx/src/vue/compile.ts +++ b/packages/compiler-tsx/src/vue/compile.ts @@ -7,6 +7,8 @@ import { import { Cache, createCache, + first, + invariant, rebaseSourceMap, SourceTransformer, } from '@vuedx/shared' @@ -15,6 +17,7 @@ import type { TransformOptionsResolved, } from '../types/TransformOptions' import { transformCustomBlock } from './blocks/transformCustomBlock' +import { createProgram } from '@vuedx/transforms' import type { RootNode } from '@vue/compiler-core' import type { RawSourceMap } from 'source-map' @@ -219,64 +222,61 @@ export function compileWithDecodedSourceMap( }) const exported = [ - scriptSetup.exportIdentifier, - scriptSetup.propsIdentifier, - scriptSetup.emitsIdentifier, - scriptSetup.exposeIdentifier, - template.attrsIdentifier, - template.slotsIdentifier, - resolvedOptions.contextIdentifier, + ...(descriptor.scriptSetup == null + ? [template.attrsIdentifier, template.slotsIdentifier, contextIdentifier] + : [scriptSetup.componentIdentifier]), ...Object.values(scriptSetup.exports), ].join(', ') - builder.append(`return {${exported}};});`) + builder.append(`return {${exported}};};`) builder.nextLine() - builder.append(`const {${exported}} = ${scriptSetup.scopeIdentifier};\n`) + builder.append(`const {${exported}} = ${scriptSetup.scopeIdentifier}();\n`) Object.entries(scriptSetup.exports).forEach(([name, identifier]) => { builder.append(`export type ${name} = typeof ${identifier};\n`) }) region('public component definition', () => { - const props = `${resolvedOptions.contextIdentifier}.$props` - - const parentClassIfAny = ` extends ${name}Public` - const type = `new () => typeof ${scriptSetup.exposeIdentifier}` - if (resolvedOptions.isTypeScript) { - builder.append(`const ${name}Public = null as unknown as ${type};`) - builder.nextLine() + if (descriptor.scriptSetup == null) { + const props = `${resolvedOptions.contextIdentifier}.$props` + const inheritAttrs = + descriptor.template?.content.includes('@vue-attrs-target') === true || + script.inheritAttrs + const propsType = `typeof ${props}` + const attrsType = `typeof ${template.attrsIdentifier}` + const slotsType = `${resolvedOptions.typeIdentifier}.internal.Slots>` + builder.append( + [ + `export default class ${name} {`, + defineProperty( + '$props', + inheritAttrs + ? `${resolvedOptions.typeIdentifier}.internal.MergeAttrs<${propsType}, ${attrsType}> & {$slots: ${slotsType}}` + : `${propsType} & {$slots: ${slotsType}}`, + ), + `}`, + ].join('\n'), + ) } else { + const generic = + typeof descriptor.scriptSetup.attrs['generic'] === 'string' + ? descriptor.scriptSetup.attrs['generic'] + : '' + const typeArgs = parseGenericArgNames(generic) + + const component = + typeArgs.length > 0 + ? `(new (${scriptSetup.scopeIdentifier}<${typeArgs.join(', ')}>().${ + scriptSetup.componentIdentifier + }<${typeArgs.join(', ')}>))` + : `(new (${scriptSetup.scopeIdentifier}().${scriptSetup.componentIdentifier}))` + + const genericExp = typeArgs.length > 0 ? `<${generic}>` : '' + builder.append(`export default class ${name}${genericExp} {\n`) builder.append( - `const ${name}Public = /** @type {${type}} */ (/** @type {unknown} */ (null));`, + ` $props = {...${component}.$props, $slots: ${component}.$slots };\n`, ) - builder.nextLine() + builder.append(`}`) } - - const inheritAttrs = - descriptor.template?.content.includes('@vue-attrs-target') === true || - script.inheritAttrs - - const propsType = - descriptor.scriptSetup != null - ? `typeof ${props} & ${resolvedOptions.typeIdentifier}.internal.EmitsToProps` - : `typeof ${props}` - const attrsType = `typeof ${template.attrsIdentifier}` - - builder.append( - [ - `export default class ${name}${parentClassIfAny} {`, - defineProperty( - '$props', - inheritAttrs - ? `${resolvedOptions.typeIdentifier}.internal.MergeAttrs<${propsType}, ${attrsType}>` - : propsType, - ), - defineProperty( - '$slots', - `${resolvedOptions.typeIdentifier}.internal.Slots>`, - ), - `}`, - ].join('\n'), - ) builder.nextLine() }) @@ -303,6 +303,17 @@ export function compileWithDecodedSourceMap( ? ` ${name} = null as unknown as ${type};` : ` ${name} = /** @type {${type}} */ (/** @type {unknown} */ (null));` } + + function parseGenericArgNames(code: string): string[] { + const ts = options.typescript + const program = createProgram(ts, `function _<${code}>() {}`) + const sourceFile = program.getSourceFile('input.ts') + invariant(sourceFile != null, 'sourceFile should not be null') + const decl = first(sourceFile.statements) + invariant(ts.isFunctionDeclaration(decl)) + invariant(decl.typeParameters != null) + return decl.typeParameters.map((p) => p.name.getText()) + } } function runIfNeeded( diff --git a/packages/compiler-tsx/test/__snapshots__/baseline.js b/packages/compiler-tsx/test/__snapshots__/baseline.js index 00a0f4f6..9dd73b61 100644 --- a/packages/compiler-tsx/test/__snapshots__/baseline.js +++ b/packages/compiler-tsx/test/__snapshots__/baseline.js @@ -19,12 +19,15 @@ const __VueDX___Script_Component = __VueDX__defineComponent({}); //#endregion //#region \n\n\n"]} \ No newline at end of file +{"version":3,"file":"/Users/znck/Workspace/OpenSource/vuedx/languagetools/packages/compiler-tsx/test/fixtures/ts-script-template.tsx","mappings":";;;;AAAkB;AAClB;mCAEc;AACd;AACA,EAAE;;;AAJF;AAAA;;;;;;;;;;;;;;;;A,4B;A,E,2B;A,E,0B;A,E,kC;A,E,gC;A,E,4B;A,E,Q;A,I,E;A,MAQE,CAACA,G;A;A,QAEEC,K,C,CAAOC,Y,C;A,QACPC,S,C,CAAWH,G,C;A,Q,OACVA,G,C,EAAYI,I,G;A,Q,KACNJ,G,E;A,QACPK,O,C,C,mC;A,U,O;A,U,K,gC,c,mB;A,UAAOC,W;A,U,C,E;A,U,O;A,U,K,gC,c,mB;A,UACKA,W;A,U,C,E;A,U,O;A,U,K,gC,c,mB;A,UACCA,W;A,U,C,E;A,Q,E,C;A,Q,mC;A,M,C;A,QAEd,EAAGN,GAAI,E;A,MACT,E,G,C;A,I,G;A,E,C;A,A,C;A,A,kB;A,A,6B;A,A,4B;A,E,2B;A,E,0B;A,E,kC;A,E,gC;A,E,4B;A,E,2E;A,E,G;A,A,C;A,A,8B;A,A,gC;A,E,O,iC,C,gC,E;A,I,kF,C;A,E,G;A,A,K;;;;;;;;;","names":["<

>3","<

>5","<

>12","<

>9","<

>4","<>5|7","<

>11"],"sources":["/Users/znck/Workspace/OpenSource/vuedx/languagetools/packages/compiler-tsx/test/fixtures/ts-script-template.vue"],"sourcesContent":["\n\n\n"]} \ No newline at end of file diff --git a/packages/transforms/src/createProgram.ts b/packages/transforms/src/createProgram.ts new file mode 100644 index 00000000..c48a830a --- /dev/null +++ b/packages/transforms/src/createProgram.ts @@ -0,0 +1,62 @@ +import TypeScript from 'typescript/lib/tsserverlibrary' +import { TransformScriptOptions } from './TransformScriptOptions' + +export function createProgram( + ts: typeof TypeScript, + source: string, + fileName: string = 'input.ts', + lang: TransformScriptOptions['lang'] = 'ts', + previous?: TypeScript.Program, +): TypeScript.Program { + const compilerHost: TypeScript.CompilerHost = { + fileExists: () => true, + getCanonicalFileName: (filename) => filename, + getCurrentDirectory: () => '', + getDefaultLibFileName: () => 'lib.d.ts', + getNewLine: () => '\n', + getSourceFile: (id) => { + if (id !== fileName) return + + return ts.createSourceFile( + id, + source, + ts.ScriptTarget.Latest, + true, + getScriptKind(lang), + ) + }, + readFile: () => undefined, + useCaseSensitiveFileNames: () => true, + writeFile: () => undefined, + } + + const program = ts.createProgram( + [fileName], + { + noResolve: true, + target: ts.ScriptTarget.Latest, + jsx: lang.endsWith('x') ? ts.JsxEmit.Preserve : undefined, + }, + compilerHost, + previous, + ) + + return program + + function getScriptKind( + lang: TransformScriptOptions['lang'], + ): TypeScript.ScriptKind { + switch (lang) { + case 'js': + return ts.ScriptKind.JS + case 'ts': + return ts.ScriptKind.TS + case 'tsx': + return ts.ScriptKind.TSX + case 'jsx': + return ts.ScriptKind.JSX + default: + throw new Error(`Unknown lang`) + } + } +} diff --git a/packages/transforms/src/index.ts b/packages/transforms/src/index.ts index 1321be8a..c7e6f758 100644 --- a/packages/transforms/src/index.ts +++ b/packages/transforms/src/index.ts @@ -2,3 +2,4 @@ export * from './TransformScriptOptions' export * from './tsTransformScript' export * from './tsTransformScriptSetup' export * from './findIdentifiers' +export * from './createProgram' diff --git a/packages/transforms/src/tsTransformScript.ts b/packages/transforms/src/tsTransformScript.ts index 5370f4af..c9b88609 100644 --- a/packages/transforms/src/tsTransformScript.ts +++ b/packages/transforms/src/tsTransformScript.ts @@ -5,6 +5,7 @@ import { SourceTransformer, } from '@vuedx/shared' import type TypeScript from 'typescript/lib/tsserverlibrary' +import { createProgram } from './createProgram' import { findIdentifiers, KnownIdentifier } from './findIdentifiers' import { TransformScriptOptions } from './TransformScriptOptions' export interface TransformScriptResult { @@ -23,36 +24,11 @@ export function transformScript( const key = `${options.fileName}:script:program` const ts = options.lib const inputFile = `input.${options.lang}` - const compilerHost: TypeScript.CompilerHost = { - fileExists: () => true, - getCanonicalFileName: (filename) => filename, - getCurrentDirectory: () => '', - getDefaultLibFileName: () => 'lib.d.ts', - getNewLine: () => '\n', - getSourceFile: (filename) => { - if (filename !== inputFile) return - - return ts.createSourceFile( - filename, - source, - ts.ScriptTarget.Latest, - true, - getScriptKind(options.lang), - ) - }, - readFile: () => undefined, - useCaseSensitiveFileNames: () => true, - writeFile: () => undefined, - } - - const program = ts.createProgram( - [inputFile], - { - noResolve: true, - target: ts.ScriptTarget.Latest, - jsx: options.lang.endsWith('x') ? ts.JsxEmit.Preserve : undefined, - }, - compilerHost, + const program = createProgram( + ts, + source, + inputFile, + options.lang, options.cache?.get(key) as TypeScript.Program, ) options.cache?.set(key, program) @@ -114,23 +90,6 @@ export function transformScript( inheritAttrs, } - function getScriptKind( - lang: TransformScriptOptions['lang'], - ): TypeScript.ScriptKind { - switch (lang) { - case 'js': - return ts.ScriptKind.JS - case 'ts': - return ts.ScriptKind.TS - case 'tsx': - return ts.ScriptKind.TSX - case 'jsx': - return ts.ScriptKind.JSX - default: - throw new Error(`Unknown lang`) - } - } - function findNodes(sourceFile: TypeScript.SourceFile): void { sourceFile.statements.forEach((statement) => { if (ts.isExportAssignment(statement)) { diff --git a/packages/transforms/src/tsTransformScriptSetup.ts b/packages/transforms/src/tsTransformScriptSetup.ts index 9698b7da..b937cd3d 100644 --- a/packages/transforms/src/tsTransformScriptSetup.ts +++ b/packages/transforms/src/tsTransformScriptSetup.ts @@ -5,57 +5,37 @@ import { SourceTransformer, } from '@vuedx/shared' import type TypeScript from 'typescript/lib/tsserverlibrary' +import { createProgram } from './createProgram' import { findIdentifiers, KnownIdentifier } from './findIdentifiers' import { TransformScriptOptions } from './TransformScriptOptions' export interface TransformScriptSetupResult { code: string map: DecodedSourceMap identifiers: KnownIdentifier[] - propsIdentifier: string - emitsIdentifier: string - exposeIdentifier: string scopeIdentifier: string - componentIdentifier: string + privateComponentIdentifier: string + publicComponentIdentifier: string exports: Record } +export interface TransformScriptSetupOptions extends TransformScriptOptions { + generic?: string + attrsIdentifier: string + slotsIdentifier: string +} + export function transformScriptSetup( source: string, - options: TransformScriptOptions, + options: TransformScriptSetupOptions, ): TransformScriptSetupResult { const key = `${options.fileName}:scriptSetup:program` const ts = options.lib const inputFile = `input.${options.lang}` - const compilerHost: TypeScript.CompilerHost = { - fileExists: () => true, - getCanonicalFileName: (filename) => filename, - getCurrentDirectory: () => '', - getDefaultLibFileName: () => 'lib.d.ts', - getNewLine: () => '\n', - getSourceFile: (filename) => { - if (filename !== inputFile) return - - return ts.createSourceFile( - filename, - source, - ts.ScriptTarget.Latest, - true, - getScriptKind(options.lang), - ) - }, - readFile: () => undefined, - useCaseSensitiveFileNames: () => true, - writeFile: () => undefined, - } - - const program = ts.createProgram( - [inputFile], - { - noResolve: true, - target: ts.ScriptTarget.Latest, - jsx: options.lang.endsWith('x') ? ts.JsxEmit.Preserve : undefined, - }, - compilerHost, + const program = createProgram( + ts, + source, + inputFile, + options.lang, options.cache?.get(key) as TypeScript.Program, ) options.cache?.set(key, program) @@ -78,15 +58,17 @@ export function transformScriptSetup( | TypeScript.EnumDeclaration > = [] const exportedNames: Record = {} - + const _ = options.internalIdentifierPrefix const vars = { - internalProps: `${options.internalIdentifierPrefix}_ScriptSetup_internalProps`, - scope: `${options.internalIdentifierPrefix}_ScriptSetup_scope`, - Component: `${options.internalIdentifierPrefix}_ScriptSetup_Component`, - emits: `${options.internalIdentifierPrefix}_ScriptSetup_emits`, - props: `${options.internalIdentifierPrefix}_ScriptSetup_props`, - expose: `${options.internalIdentifierPrefix}_ScriptSetup_expose`, + internalProps: `${_}ScriptSetup_internalProps`, + internalComponent: `${_}ScriptSetup_ComponentPrivate`, + publicComponent: `${_}ScriptSetup_Component`, + scope: `${_}ScriptSetup_scope`, + emits: `${_}ScriptSetup_emits`, + props: `${_}ScriptSetup_props`, + expose: `${_}ScriptSetup_expose`, } + const generic = options.generic != null ? `<${options.generic}>` : '' const code = new SourceTransformer(inputFile, source) findNodes(sourceFile) @@ -103,12 +85,12 @@ export function transformScriptSetup( } const { line } = code.sourceLineColumnMapper.positionAt(offset) - // annotate range + // wrap setup code in a function code.append(`\n`, { mappings: [[[0, 0, line + 1, 0]]] }) - code.append( - `const ${vars.scope} = ${options.typeIdentifier}.internal.scope(async () => {`, - { mappings: [[[0, 0, line + 1, 0]]] }, - ) + + code.append(`function ${vars.scope}${generic}() {`, { + mappings: [[[0, 0, line + 1, 0]]], + }) if (exportedNodes.length > 0) { genExportedNodes(offset, source.length) @@ -143,6 +125,7 @@ export function transformScriptSetup( } } + // define private props (withDefaults) if (internalPropsIdentifier == null && internalPropsInitializer != null) { code.clone(offset, internalPropsInitializer.getStart()) code.append(`const ${vars.internalProps} = `) @@ -151,7 +134,30 @@ export function transformScriptSetup( internalPropsInitializer.getEnd(), ) code.append(';\n') + } else if ( + internalPropsIdentifier == null && + internalPropsInitializer == null + ) { + code.append(`const ${vars.internalProps} = {};\n`) + } + code.append( + `const ${vars.internalComponent} = ${_}defineComponent((_: typeof ${ + internalPropsIdentifier?.getText() ?? vars.internalProps + })=> {});\n`, + ) + + // define expose + let expose = '' + if (exposeOptions != null) { + code.append(`const ${vars.expose} = (`) + code.clone(exposeOptions.getStart(), exposeOptions.getEnd()) + code.append(`);\n`) + code.append( + `const ${vars.expose}_API = null as unknown as new () => typeof ${vars.expose};\n`, + ) + expose = ` extends ${vars.expose}_API` } + // define props if (propsIdentifier != null) { code.append(`const ${vars.props} = ${propsIdentifier.text};\n`) @@ -182,28 +188,34 @@ export function transformScriptSetup( code.append(`const ${vars.emits} = ({});\n`) } - // define expose - if (exposeOptions != null) { - code.append(`const ${vars.expose} = (`) - code.clone(exposeOptions.getStart(), exposeOptions.getEnd()) - code.append(`);\n`) + // define public component + code.append(`class ${vars.publicComponent}${generic}${expose} {\n`) + // define $props using mergeAttrs + code.append( + `$props = null as unknown as ${options.typeIdentifier}.internal.MergeAttrs<`, + ) + // + if (propsType != null) { + code.clone(propsType.getStart(), propsType.getEnd()) } else { - code.append(`const ${vars.expose} = {};\n`) + code.append(`typeof ${vars.props}`) } - - if (internalPropsIdentifier == null && internalPropsInitializer == null) { - code.append(`const ${vars.internalProps} = {};\n`) + // + code.append(` & ${options.typeIdentifier}.internal.EmitsToProps<`) + if (emitsType != null) { + code.append(`${options.typeIdentifier}.internal.EmitTypeToEmits<`) + code.clone(emitsType.getStart(), emitsType.getEnd()) + code.append(`>`) + } else { + code.append(`typeof ${vars.emits}`) } - + code.append(`>`) + // + code.append(`, typeof ${options.attrsIdentifier}>;\n`) code.append( - [ - `const ${vars.Component} = ${ - options.internalIdentifierPrefix - }defineComponent((_: typeof ${ - internalPropsIdentifier?.getText() ?? vars.internalProps - })=> {});\n`, - ].join('\n'), + `$slots = null as unknown as ${options.typeIdentifier}.internal.Slots>;\n`, ) + code.append('}\n') code.append(`\n`) const result = code.end() @@ -212,10 +224,8 @@ export function transformScriptSetup( code: result.code, map: result.map, identifiers, - componentIdentifier: vars.Component, - propsIdentifier: vars.props, - emitsIdentifier: vars.emits, - exposeIdentifier: vars.expose, + privateComponentIdentifier: vars.internalComponent, + publicComponentIdentifier: vars.publicComponent, scopeIdentifier: vars.scope, exports: exportedNames, } @@ -252,23 +262,6 @@ export function transformScriptSetup( return modifier as TypeScript.ExportKeyword } - function getScriptKind( - lang: TransformScriptOptions['lang'], - ): TypeScript.ScriptKind { - switch (lang) { - case 'js': - return ts.ScriptKind.JS - case 'ts': - return ts.ScriptKind.TS - case 'tsx': - return ts.ScriptKind.TSX - case 'jsx': - return ts.ScriptKind.JSX - default: - throw new Error(`Unknown lang`) - } - } - function findNodes(sourceFile: TypeScript.SourceFile): void { sourceFile.statements.forEach((statement) => { if (!ts.isImportDeclaration(statement)) { @@ -287,6 +280,7 @@ export function transformScriptSetup( } else { internalPropsInitializer = declaration.initializer } + processProps(declaration.initializer) } else if (isFnCall(declaration.initializer, 'withDefaults')) { if (ts.isIdentifier(declaration.name)) { internalPropsIdentifier = declaration.name diff --git a/packages/transforms/test/tsTransformScriptSetup.spec.ts b/packages/transforms/test/tsTransformScriptSetup.spec.ts index 37cf1ed1..04168bc9 100644 --- a/packages/transforms/test/tsTransformScriptSetup.spec.ts +++ b/packages/transforms/test/tsTransformScriptSetup.spec.ts @@ -30,13 +30,16 @@ describe(transformScriptSetup, () => { expect(code).toMatchInlineSnapshot(` import { ref } from 'vue' - const __ScriptSetup_scope = VueDX.internal.scope(async () => { + function _ScriptSetup_scope() { const foo = ref(0) - const __ScriptSetup_props = defineProps({}); - const __ScriptSetup_emits = ({}); - const __ScriptSetup_expose = {}; - const __ScriptSetup_internalProps = {}; - const __ScriptSetup_Component = _defineComponent((_: typeof __ScriptSetup_internalProps)=> {}); + const _ScriptSetup_internalProps = {}; + const _ScriptSetup_ComponentPrivate = _defineComponent((_: typeof _ScriptSetup_internalProps)=> {}); + const _ScriptSetup_props = defineProps({}); + const _ScriptSetup_emits = ({}); + class _ScriptSetup_Component { + $props = null as unknown as VueDX.internal.MergeAttrs, typeof undefined>; + $slots = null as unknown as VueDX.internal.Slots>; + } `) }) @@ -51,22 +54,25 @@ describe(transformScriptSetup, () => { expect(code).toMatchInlineSnapshot(` import { ref } from 'vue' - const __ScriptSetup_scope = VueDX.internal.scope(async () => { + function _ScriptSetup_scope() { const foo = ref(0) defineProps({ bar: String }) const foo = ref(0) - const __ScriptSetup_internalProps = defineProps({ + const _ScriptSetup_internalProps = defineProps({ bar: String }); - const __ScriptSetup_props = defineProps({ + const _ScriptSetup_ComponentPrivate = _defineComponent((_: typeof _ScriptSetup_internalProps)=> {}); + const _ScriptSetup_props = defineProps({ bar: String }); - const __ScriptSetup_emits = ({}); - const __ScriptSetup_expose = {}; - const __ScriptSetup_Component = _defineComponent((_: typeof __ScriptSetup_internalProps)=> {}); + const _ScriptSetup_emits = ({}); + class _ScriptSetup_Component { + $props = null as unknown as VueDX.internal.MergeAttrs, typeof undefined>; + $slots = null as unknown as VueDX.internal.Slots>; + } `) }) @@ -81,22 +87,27 @@ describe(transformScriptSetup, () => { expect(code).toMatchInlineSnapshot(` import { ref } from 'vue' - const __ScriptSetup_scope = VueDX.internal.scope(async () => { + function _ScriptSetup_scope() { const foo = ref(0) defineProps<{ bar: string }>() const foo = ref(0) - const __ScriptSetup_internalProps = defineProps<{ + const _ScriptSetup_internalProps = defineProps<{ bar: string }>(); - const __ScriptSetup_props = defineProps<{ + const _ScriptSetup_ComponentPrivate = _defineComponent((_: typeof _ScriptSetup_internalProps)=> {}); + const _ScriptSetup_props = defineProps<{ bar: string }>(); - const __ScriptSetup_emits = ({}); - const __ScriptSetup_expose = {}; - const __ScriptSetup_Component = _defineComponent((_: typeof __ScriptSetup_internalProps)=> {}); + const _ScriptSetup_emits = ({}); + class _ScriptSetup_Component { + $props = null as unknown as VueDX.internal.MergeAttrs<{ + bar: string + } & VueDX.internal.EmitsToProps, typeof undefined>; + $slots = null as unknown as VueDX.internal.Slots>; + } `) }) @@ -111,22 +122,25 @@ describe(transformScriptSetup, () => { expect(code).toMatchInlineSnapshot(` import { ref } from 'vue' - const __ScriptSetup_scope = VueDX.internal.scope(async () => { + function _ScriptSetup_scope() { const foo = ref(0) withDefaults(defineProps({ bar: String }), { bar: 'baz' }) const foo = ref(0) - const __ScriptSetup_internalProps = withDefaults(defineProps({ + const _ScriptSetup_internalProps = withDefaults(defineProps({ bar: String }), { bar: 'baz' }); - const __ScriptSetup_props = defineProps({ + const _ScriptSetup_ComponentPrivate = _defineComponent((_: typeof _ScriptSetup_internalProps)=> {}); + const _ScriptSetup_props = defineProps({ bar: String }); - const __ScriptSetup_emits = ({}); - const __ScriptSetup_expose = {}; - const __ScriptSetup_Component = _defineComponent((_: typeof __ScriptSetup_internalProps)=> {}); + const _ScriptSetup_emits = ({}); + class _ScriptSetup_Component { + $props = null as unknown as VueDX.internal.MergeAttrs, typeof undefined>; + $slots = null as unknown as VueDX.internal.Slots>; + } `) }) @@ -141,15 +155,18 @@ describe(transformScriptSetup, () => { expect(code).toMatchInlineSnapshot(` import { ref } from 'vue' - const __ScriptSetup_scope = VueDX.internal.scope(async () => { + function _ScriptSetup_scope() { const foo = ref(0) const props = defineProps({ bar: String }) - const __ScriptSetup_props = props; - const __ScriptSetup_emits = ({}); - const __ScriptSetup_expose = {}; - const __ScriptSetup_Component = _defineComponent((_: typeof props)=> {}); + const _ScriptSetup_ComponentPrivate = _defineComponent((_: typeof props)=> {}); + const _ScriptSetup_props = props; + const _ScriptSetup_emits = ({}); + class _ScriptSetup_Component { + $props = null as unknown as VueDX.internal.MergeAttrs, typeof undefined>; + $slots = null as unknown as VueDX.internal.Slots>; + } `) }) @@ -164,17 +181,20 @@ describe(transformScriptSetup, () => { expect(code).toMatchInlineSnapshot(` import { ref } from 'vue' - const __ScriptSetup_scope = VueDX.internal.scope(async () => { + function _ScriptSetup_scope() { const foo = ref(0) const props = withDefaults(defineProps({ bar: String }), { bar: 'baz' }) - const __ScriptSetup_props = defineProps({ + const _ScriptSetup_ComponentPrivate = _defineComponent((_: typeof props)=> {}); + const _ScriptSetup_props = defineProps({ bar: String }); - const __ScriptSetup_emits = ({}); - const __ScriptSetup_expose = {}; - const __ScriptSetup_Component = _defineComponent((_: typeof props)=> {}); + const _ScriptSetup_emits = ({}); + class _ScriptSetup_Component { + $props = null as unknown as VueDX.internal.MergeAttrs, typeof undefined>; + $slots = null as unknown as VueDX.internal.Slots>; + } `) }) @@ -187,14 +207,17 @@ describe(transformScriptSetup, () => { expect(code).toMatchInlineSnapshot(` import { ref } from 'vue' - const __ScriptSetup_scope = VueDX.internal.scope(async () => { + function _ScriptSetup_scope() { const foo = ref(0) defineEmits(['bar']) - const __ScriptSetup_props = defineProps({}); - const __ScriptSetup_emits = (['bar']); - const __ScriptSetup_expose = {}; - const __ScriptSetup_internalProps = {}; - const __ScriptSetup_Component = _defineComponent((_: typeof __ScriptSetup_internalProps)=> {}); + const _ScriptSetup_internalProps = {}; + const _ScriptSetup_ComponentPrivate = _defineComponent((_: typeof _ScriptSetup_internalProps)=> {}); + const _ScriptSetup_props = defineProps({}); + const _ScriptSetup_emits = (['bar']); + class _ScriptSetup_Component { + $props = null as unknown as VueDX.internal.MergeAttrs, typeof undefined>; + $slots = null as unknown as VueDX.internal.Slots>; + } `) }) @@ -207,14 +230,17 @@ describe(transformScriptSetup, () => { expect(code).toMatchInlineSnapshot(` import { ref } from 'vue' - const __ScriptSetup_scope = VueDX.internal.scope(async () => { + function _ScriptSetup_scope() { const foo = ref(0) const emits = defineEmits(['bar']) - const __ScriptSetup_props = defineProps({}); - const __ScriptSetup_emits = (['bar']); - const __ScriptSetup_expose = {}; - const __ScriptSetup_internalProps = {}; - const __ScriptSetup_Component = _defineComponent((_: typeof __ScriptSetup_internalProps)=> {}); + const _ScriptSetup_internalProps = {}; + const _ScriptSetup_ComponentPrivate = _defineComponent((_: typeof _ScriptSetup_internalProps)=> {}); + const _ScriptSetup_props = defineProps({}); + const _ScriptSetup_emits = (['bar']); + class _ScriptSetup_Component { + $props = null as unknown as VueDX.internal.MergeAttrs, typeof undefined>; + $slots = null as unknown as VueDX.internal.Slots>; + } `) }) @@ -230,19 +256,22 @@ describe(transformScriptSetup, () => { expect(code).toMatchInlineSnapshot(` import { ref } from 'vue' - const __ScriptSetup_scope = VueDX.internal.scope(async () => { + function _ScriptSetup_scope() { const foo = ref(0) const emits = defineEmits({ bar: (arg: string) => typeof arg === 'string' }) const options = {} - const __ScriptSetup_props = defineProps({}); - const __ScriptSetup_emits = ({ + const _ScriptSetup_internalProps = {}; + const _ScriptSetup_ComponentPrivate = _defineComponent((_: typeof _ScriptSetup_internalProps)=> {}); + const _ScriptSetup_props = defineProps({}); + const _ScriptSetup_emits = ({ bar: (arg: string) => typeof arg === 'string' }); - const __ScriptSetup_expose = {}; - const __ScriptSetup_internalProps = {}; - const __ScriptSetup_Component = _defineComponent((_: typeof __ScriptSetup_internalProps)=> {}); + class _ScriptSetup_Component { + $props = null as unknown as VueDX.internal.MergeAttrs, typeof undefined>; + $slots = null as unknown as VueDX.internal.Slots>; + } `) }) @@ -257,18 +286,23 @@ describe(transformScriptSetup, () => { expect(code).toMatchInlineSnapshot(` import { ref } from 'vue' - const __ScriptSetup_scope = VueDX.internal.scope(async () => { + function _ScriptSetup_scope() { const foo = ref(0) defineEmits<{ (event: 'bar', arg: string) => boolean }>() - const __ScriptSetup_props = defineProps({}); - const __ScriptSetup_emits = ({} as unknown as VueDX.internal.EmitTypeToEmits<{ + const _ScriptSetup_internalProps = {}; + const _ScriptSetup_ComponentPrivate = _defineComponent((_: typeof _ScriptSetup_internalProps)=> {}); + const _ScriptSetup_props = defineProps({}); + const _ScriptSetup_emits = ({} as unknown as VueDX.internal.EmitTypeToEmits<{ (event: 'bar', arg: string) => boolean }>); - const __ScriptSetup_expose = {}; - const __ScriptSetup_internalProps = {}; - const __ScriptSetup_Component = _defineComponent((_: typeof __ScriptSetup_internalProps)=> {}); + class _ScriptSetup_Component { + $props = null as unknown as VueDX.internal.MergeAttrs boolean + }>>, typeof undefined>; + $slots = null as unknown as VueDX.internal.Slots>; + } `) }) diff --git a/packages/typescript-plugin-vue/src/features/CompletionsService.ts b/packages/typescript-plugin-vue/src/features/CompletionsService.ts index 40a1458d..6b0c9395 100644 --- a/packages/typescript-plugin-vue/src/features/CompletionsService.ts +++ b/packages/typescript-plugin-vue/src/features/CompletionsService.ts @@ -84,8 +84,6 @@ export class CompletionsService position - templateRange.start, ) - console.log(`@@@ completion`, kind, node) - if (kind === 'attribute') { const index = file.generated .getText() @@ -281,6 +279,7 @@ export class CompletionsService kind === 'eventName' || kind === 'propName' ) { + if (entry.name === '$slots') return [] if ( entry.kind === this.ts.lib.ScriptElementKind.memberVariableElement // TODO: check others ) { diff --git a/packages/typescript-plugin-vue/types/3.x.d.ts b/packages/typescript-plugin-vue/types/3.x.d.ts index fdbd4325..b063263e 100644 --- a/packages/typescript-plugin-vue/types/3.x.d.ts +++ b/packages/typescript-plugin-vue/types/3.x.d.ts @@ -18,14 +18,14 @@ import { MergeAttrs, PropsOf } from './shared/Props' import { renderList } from './shared/renderList' import { renderSlot } from './shared/renderSlot' import { Slots, SlotsFrom, GetSlotProps, checkSlots } from './shared/Slots' -import { first, flat, union, merge, getNameOption, scope } from './shared/utils' +import { first, flat, union, merge, getNameOption } from './shared/utils' import { EmitsToProps, EmitTypeToEmits } from './shared/emits' import {} from './shared/jsx' export type version = '3.x' export namespace internal { - export { first, flat, union, merge, getNameOption, scope, unref } + export { first, flat, union, merge, getNameOption, unref } export { resolveComponent, resolveDirective, getElementType } export { renderList, renderSlot, Slots, GetSlotProps } export { defineComponent } diff --git a/packages/typescript-plugin-vue/types/shared/jsx.d.ts b/packages/typescript-plugin-vue/types/shared/jsx.d.ts index 650ec724..336ce12b 100644 --- a/packages/typescript-plugin-vue/types/shared/jsx.d.ts +++ b/packages/typescript-plugin-vue/types/shared/jsx.d.ts @@ -1,24 +1 @@ export {} - -// declare module '@vue/runtime-core' { -// export namespace JSX { -// interface Element extends VNode {} - -// interface ElementClass { -// $props: {} -// } - -// interface ElementAttributesProperty { -// $props: {} -// } - -// // interface ElementChildrenAttribute { -// // $slots?: {} -// // } - -// interface IntrinsicElements extends VueIntrinsicElements { -// [name: string]: any -// } -// interface IntrinsicAttributes extends VueIntrinsicAttributes {} -// } -// } diff --git a/packages/typescript-plugin-vue/types/shared/utils.d.ts b/packages/typescript-plugin-vue/types/shared/utils.d.ts index a7d28cf8..0ee7f7c5 100644 --- a/packages/typescript-plugin-vue/types/shared/utils.d.ts +++ b/packages/typescript-plugin-vue/types/shared/utils.d.ts @@ -174,7 +174,7 @@ export function flat( depth?: D, ): Array> -export function scope(fn: () => Promise): T +export function depromisify(fn: () => Promise): () => T export function first(items: T[]): T diff --git a/packages/vue-virtual-textdocument/test/__snapshots__/sourcemap.spec.ts.snap b/packages/vue-virtual-textdocument/test/__snapshots__/sourcemap.spec.ts.snap index ad03b045..d3cfd16c 100644 --- a/packages/vue-virtual-textdocument/test/__snapshots__/sourcemap.spec.ts.snap +++ b/packages/vue-virtual-textdocument/test/__snapshots__/sourcemap.spec.ts.snap @@ -10,12 +10,15 @@ const __VueDX__Script_Component = __VueDX_defineComponent({}); //#endregion //#region + + diff --git a/samples/vue3/typescript-diagnostics/src/test-generic-component.vue b/samples/vue3/typescript-diagnostics/src/test-generic-component.vue new file mode 100644 index 00000000..26080314 --- /dev/null +++ b/samples/vue3/typescript-diagnostics/src/test-generic-component.vue @@ -0,0 +1,15 @@ + + + diff --git a/test/specs/completions.spec.ts b/test/specs/completions.spec.ts index c1e65f1f..a3910e11 100644 --- a/test/specs/completions.spec.ts +++ b/test/specs/completions.spec.ts @@ -103,8 +103,6 @@ describe('completions', () => { ':b', 'c', ':c', - 'class', - ':class', 'key', ':key', '@a', @@ -112,8 +110,6 @@ describe('completions', () => { '@c', 'ref', ':ref', - 'style', - ':style', ]) }) diff --git a/test/specs/component-generics.spec.ts b/test/specs/component-generics.spec.ts new file mode 100644 index 00000000..a7c39fd4 --- /dev/null +++ b/test/specs/component-generics.spec.ts @@ -0,0 +1,42 @@ +import { Position } from 'vscode-languageserver-textdocument' +import { createEditorContext, getProjectPath } from '../support/helpers' +import { TestServer } from '../support/TestServer' + +describe('completions', () => { + const server = new TestServer() + const service = createEditorContext( + server, + getProjectPath('typescript-diagnostics'), + ) + + beforeAll( + async () => + await server.sendCommand('configure', { + preferences: { + providePrefixAndSuffixTextForRename: true, + allowRenameOfImportPath: true, + includePackageJsonAutoImports: 'auto', + }, + }), + ) + + afterAll(async () => await server.close()) + + test('can detect slot types in generic component', async () => { + const editor = await service.open('src/test-generic-component.vue') + const types: Record = { + string: { line: 5, character: 33 }, + number: { line: 8, character: 29 }, + Date: { line: 11, character: 38 }, + } + + for (const [type, position] of Object.entries(types)) { + await editor.setCursor(position) + const result = await server.sendCommand( + 'quickinfo', + editor.fileAndLocation, + ) + expect(result.body?.displayString).toBe(`(parameter) value: ${type}`) + } + }) +}) diff --git a/test/specs/diagnostics.spec.ts b/test/specs/diagnostics.spec.ts index bdf68eb5..c7aaea06 100644 --- a/test/specs/diagnostics.spec.ts +++ b/test/specs/diagnostics.spec.ts @@ -11,15 +11,16 @@ describe('project', () => { afterAll(async () => await editor.closeAll()) - test.skip('checks exports in