diff --git a/.vscode/launch.json b/.vscode/launch.json index 62b38f5..b6f7cdc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,12 +3,13 @@ "version": "0.2.0", "configurations": [ { - "name": "Launch Client", + "name": "VSCode Extension", "type": "extensionHost", "request": "launch", "autoAttachChildProcesses": true, "runtimeExecutable": "${execPath}", "args": [ + "--disable-extension=mpxjs.mpx-official", "--extensionDevelopmentPath=${workspaceRoot}/vscode", "--folder-uri=${workspaceRoot}/inspect-extension" ], diff --git a/inspect-extension/.vscode/settings.json b/inspect-extension/.vscode/settings.json index 89b2275..77a2c6d 100644 --- a/inspect-extension/.vscode/settings.json +++ b/inspect-extension/.vscode/settings.json @@ -10,5 +10,5 @@ "mpx.format.script.prettier": true, "mpx.format.template.prettier": true, // 方便调试 TM,设置默认现代主题 - "workbench.colorTheme": "Default Dark Modern" + "workbench.colorTheme": "Dark Modern" } diff --git a/inspect-extension/components/reference-type-props/component-js-setup.mpx b/inspect-extension/components/reference-type-props/component-js-setup.mpx new file mode 100644 index 0000000..4a77f41 --- /dev/null +++ b/inspect-extension/components/reference-type-props/component-js-setup.mpx @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/inspect-extension/components/reference-type-props/component-options.mpx b/inspect-extension/components/reference-type-props/component-options.mpx new file mode 100644 index 0000000..2cb787c --- /dev/null +++ b/inspect-extension/components/reference-type-props/component-options.mpx @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/inspect-extension/components/reference-type-props/index.mpx b/inspect-extension/components/reference-type-props/index.mpx new file mode 100644 index 0000000..8418071 --- /dev/null +++ b/inspect-extension/components/reference-type-props/index.mpx @@ -0,0 +1,23 @@ + + + + + diff --git a/inspect-extension/components/script-json/components/list.mpx b/inspect-extension/components/script-json/components/list.mpx index c40919b..5128e83 100644 --- a/inspect-extension/components/script-json/components/list.mpx +++ b/inspect-extension/components/script-json/components/list.mpx @@ -13,6 +13,12 @@ import { createComponent } from '@mpxjs/core' createComponent({ + properties:{ + parentText: { + type: String, + value: 'parent text', + } + }, data: { listData: ['手机', '电视', '电脑'], }, diff --git a/packages/language-core/src/codegen/globalTypes/defineComponentTypes.ts b/packages/language-core/src/codegen/globalTypes/defineComponentTypes.ts index e5405b1..f979de2 100644 --- a/packages/language-core/src/codegen/globalTypes/defineComponentTypes.ts +++ b/packages/language-core/src/codegen/globalTypes/defineComponentTypes.ts @@ -37,6 +37,27 @@ const globalTypes = () => ` : {} ` +const runtimePropTypes = () => ` +type __VLS_ResolveProp = T extends { type: StringConstructor } + ? string + : T extends { type: NumberConstructor } + ? number + : T extends { type: BooleanConstructor } + ? boolean + : T extends { type: ArrayConstructor } + ? any[] + : T extends { type: ObjectConstructor } + ? Record + : T extends { type: import('@mpxjs/core').PropType } + ? V + : T extends import('@mpxjs/core').PropType + ? V + : unknown +type __VLS_GetPropsType> = Partial<{ + readonly [K in keyof T]: __VLS_ResolveProp +}> +` + const localTypes = (lib: MpxCompilerOptions['lib']) => ` // #region DefineComponent - local types type Data = object | (() => object) @@ -190,4 +211,5 @@ interface ReplaceWxComponentIns { export const defineComponentTypesContents = { globalTypes, localTypes, + runtimePropTypes, } diff --git a/packages/language-core/src/codegen/globalTypes/index.ts b/packages/language-core/src/codegen/globalTypes/index.ts index c02a715..8918d21 100644 --- a/packages/language-core/src/codegen/globalTypes/index.ts +++ b/packages/language-core/src/codegen/globalTypes/index.ts @@ -167,6 +167,7 @@ export function generateGlobalTypes({ : T[K] } ${defineComponentTypesContents.globalTypes()} + ${defineComponentTypesContents.runtimePropTypes()} } ` + defineComponentTypesContents.localTypes(lib) diff --git a/packages/language-core/src/codegen/script/componentSelf.ts b/packages/language-core/src/codegen/script/componentSelf.ts index 170fc4f..1f4b763 100644 --- a/packages/language-core/src/codegen/script/componentSelf.ts +++ b/packages/language-core/src/codegen/script/componentSelf.ts @@ -1,18 +1,37 @@ import type { Code } from '../../types' import type { ScriptCodegenOptions } from './index' -import { endOfLine } from '../utils' +import { endOfLine, newLine } from '../utils' +import { getSlotsPropertyName } from '../../utils/shared' export function* generateComponentSelf( options: ScriptCodegenOptions, ): Generator { if (options.sfc.scriptSetup) { + yield `type __VLS_SelfBase = ` if (options.scriptSetupRanges?.defineExpose) { - yield `const __VLS_self = typeof __VLS_defineExpose${endOfLine}` + yield `typeof __VLS_defineExpose` } else { - yield `const __VLS_self = {}${endOfLine}` + yield `{}` } + yield endOfLine + yield `type __VLS_SelfComponent = __VLS_SelfBase & {${newLine}` + yield ` new (props: __VLS_PublicProps): __VLS_SelfBase & {${newLine}` + yield ` $props: __VLS_PublicProps${endOfLine}` + yield ` ${getSlotsPropertyName()}: __VLS_Slots${endOfLine}` + yield ` }${newLine}` + yield `}${endOfLine}` + yield `const __VLS_self = {} as __VLS_SelfComponent${endOfLine}` } else { - yield `const __VLS_self = typeof __VLS_defineComponent${endOfLine}` + yield `type __VLS_SelfProps = __VLS_GetPropsType<${newLine}` + yield ` NonNullable${newLine}` + yield `>${endOfLine}` + yield `type __VLS_SelfComponent = typeof __VLS_defineComponent & {${newLine}` + yield ` new (props: __VLS_SelfProps): typeof __VLS_defineComponent & {${newLine}` + yield ` $props: __VLS_SelfProps${endOfLine}` + yield ` ${getSlotsPropertyName()}: __VLS_Slots${endOfLine}` + yield ` }${newLine}` + yield `}${endOfLine}` + yield `const __VLS_self = {} as __VLS_SelfComponent${endOfLine}` } yield `export default __VLS_self${endOfLine}` } diff --git a/packages/language-core/src/codegen/script/index.ts b/packages/language-core/src/codegen/script/index.ts index 1b1a196..41ef9b9 100644 --- a/packages/language-core/src/codegen/script/index.ts +++ b/packages/language-core/src/codegen/script/index.ts @@ -9,7 +9,10 @@ import { generateSrc } from './src' import { generateTemplate } from './template' import { codeFeatures } from '../codeFeatures' import { generateComponentSelf } from './componentSelf' -import { generateJsonPathCompletionImports } from './jsonUsingComponents' +import { + generateJsonPathCompletionImports, + generateJsonUsingComponents, +} from './jsonUsingComponents' import { endOfLine, generateDefineComponent, @@ -19,7 +22,6 @@ import { import { generateGlobalTypes, getGlobalTypesFileName } from '../globalTypes' import { ScriptCodegenContext, createScriptCodegenContext } from './context' import { generateScriptSetup, generateScriptSetupImports } from './scriptSetup' -// import { generateJsonUsingComponents } from './jsonUsingComponents' export interface ScriptCodegenOptions { ts: typeof ts @@ -30,6 +32,7 @@ export interface ScriptCodegenOptions { lang: string scriptRanges: ScriptRanges | undefined scriptSetupRanges: ScriptSetupRanges | undefined + scriptSetupImportComponentNames: Set templateCodegen: (TemplateCodegenContext & { codes: Code[] }) | undefined destructuredPropNames: Set templateRefNames: Set @@ -79,6 +82,10 @@ export function* generateScript( options.scriptSetupRanges, ) } + // Keep JSON path completion imports near the real import section so the + // generated script layout stays close to vue-language-tools. + yield* generateJsonPathCompletionImports(options, ctx) + yield* generateJsonUsingComponents(options, ctx) if (options.sfc.script && options.scriptRanges) { const { createComponentObj } = options.scriptRanges const isCreateComponentRawObject = @@ -177,12 +184,8 @@ export function* generateScript( if (!ctx.generatedTemplate) { yield* generateTemplate(options, ctx) yield* generateComponentSelf(options) - // yield* generateJsonUsingComponents(options, ctx) } - // 添加虚拟路径补全导入代码 - yield* generateJsonPathCompletionImports(options, ctx) - yield* ctx.localTypes.generate([...ctx.localTypes.getUsedNames()]) if (options.appendGlobalTypes) { yield generateGlobalTypes(options.mpxCompilerOptions) diff --git a/packages/language-core/src/codegen/script/jsonUsingComponents.ts b/packages/language-core/src/codegen/script/jsonUsingComponents.ts index d8e2262..83aa732 100644 --- a/packages/language-core/src/codegen/script/jsonUsingComponents.ts +++ b/packages/language-core/src/codegen/script/jsonUsingComponents.ts @@ -1,8 +1,9 @@ import type { Code } from '../../types' import type { ScriptCodegenOptions } from './index' import type { ScriptCodegenContext } from './context' +import { camelize, capitalize } from '@mpxjs/language-shared' import { endOfLine, newLine } from '../utils' -import { codeFeatures } from '../codeFeatures' +import { identifierRegex } from '../utils' export function* generateJsonUsingComponents( options: ScriptCodegenOptions, @@ -11,34 +12,32 @@ export function* generateJsonUsingComponents( const usingComponents = options.sfc.json?.usingComponents if (!usingComponents?.size) { + yield `const __MPX_jsonComponents = {}${endOfLine}` return } - yield `type __MPX_jsonComponents = {${newLine}` + yield `const __MPX_jsonComponents = {${newLine}` for (const [componentName, componentPaths] of usingComponents) { - for (const { - text: componentPath, - offset: componentPathOffset, - // nameOffset: componentNameOffset, - } of componentPaths) { - yield `${componentName}: typeof import('` - - yield [ - componentPath, - 'scriptSetup', - componentPathOffset, - codeFeatures.all, - ] + const firstImportName = getUsingComponentImportName(componentName, 0) + yield `${firstImportName}` + if (componentPaths.length > 1) { + // Multiple resolved paths still need a single runtime value, so keep the + // first import as the value and widen its type to all candidate imports. + yield `: ${firstImportName} as ` + for (let i = 0; i < componentPaths.length; i++) { + if (i) { + yield ` | ` + } + yield `typeof ${getUsingComponentImportName(componentName, i)}` + } } - - yield `)'${newLine}` + yield `,${newLine}` } yield `}${endOfLine}` } -// 新增函数:生成用于路径补全的虚拟 import 代码 export function* generateJsonPathCompletionImports( options: ScriptCodegenOptions, _ctx: ScriptCodegenContext, @@ -52,24 +51,25 @@ export function* generateJsonPathCompletionImports( // 为 usingComponents 生成虚拟 import if (usingComponents?.size) { - let index = 0 - for (const [, componentPaths] of usingComponents) { - for (const { - text: componentPath, - offset: componentPathOffset, - } of componentPaths) { - // 生成虚拟 import 语句,带特殊标记以便识别 - yield `import __mpx_path_completion_${index++} from '` + for (const [componentName, componentPaths] of usingComponents) { + for (let i = 0; i < componentPaths.length; i++) { + const componentPathInfo = componentPaths[i]! + const importName = getUsingComponentImportName(componentName, i) + + // Generate a virtual import with a stable identifier so the generated + // component map reads like normal source imports instead of index-based + // placeholders. + yield `import ${importName} from '` // 传递原始路径及位置信息 yield [ - componentPath, + componentPathInfo.text, 'json_import', - jsonStart + componentPathOffset, + jsonStart + componentPathInfo.offset, { // 仅启用补全功能,不参与语义分析等 completion: true, - navigation: true, // 需要导航功能来支持跳转 + navigation: false, semantic: false, verification: false, structure: false, @@ -91,10 +91,10 @@ export function* generateJsonPathCompletionImports( yield [ page.text, 'json_import', - page.offset, + jsonStart + page.offset, { completion: true, - navigation: true, + navigation: false, semantic: false, verification: false, structure: false, @@ -108,3 +108,15 @@ export function* generateJsonPathCompletionImports( yield `${newLine}` } + +export function getUsingComponentImportName( + componentName: string, + index: number, +) { + const camelizedName = capitalize(camelize(componentName)) + const baseName = + camelizedName && identifierRegex.test(camelizedName) + ? camelizedName + : `component_${index}` + return index && baseName === camelizedName ? `${baseName}_${index}` : baseName +} diff --git a/packages/language-core/src/codegen/script/template.ts b/packages/language-core/src/codegen/script/template.ts index f6b1b6c..6eb9137 100644 --- a/packages/language-core/src/codegen/script/template.ts +++ b/packages/language-core/src/codegen/script/template.ts @@ -65,7 +65,7 @@ function* generateTemplateElements(): Generator { } function* generateTemplateComponents(): Generator { - const types: Code[] = [`typeof __MPX_ctx`] + const types: Code[] = [`typeof __MPX_ctx`, `typeof __MPX_jsonComponents`] yield `type __VLS_LocalComponents =` for (const type of types) { diff --git a/packages/language-core/src/codegen/template/element.ts b/packages/language-core/src/codegen/template/element.ts index babfdb1..78d62d4 100644 --- a/packages/language-core/src/codegen/template/element.ts +++ b/packages/language-core/src/codegen/template/element.ts @@ -4,7 +4,7 @@ import type { Code, MpxCodeInformation } from '../../types' import * as CompilerDOM from '@vue/compiler-dom' import { camelize, capitalize } from '@mpxjs/language-shared' -import { getSlotsPropertyName, hyphenateTag } from '../../utils/shared' +import { hyphenateTag } from '../../utils/shared' import { codeFeatures } from '../codeFeatures' import { endOfLine, @@ -47,15 +47,18 @@ export function* generateComponent( node.tag, true, ) - const matchImportName = possibleOriginalNames.find(name => - options.scriptSetupImportComponentNames.has(name), - ) - const componentOriginalVar = matchImportName ?? ctx.getInternalVariable() + const componentOriginalVar = ctx.getInternalVariable() const componentFunctionalVar = ctx.getInternalVariable() const componentVNodeVar = ctx.getInternalVariable() const componentCtxVar = ctx.getInternalVariable() const isComponentTag = node.tag.toLowerCase() === 'component' + ctx.templateNodeTags.push({ + name: node.tag, + startTagOffset: tagOffsets[0], + endTagOffset: tagOffsets[1], + }) + ctx.currentComponent?.childTypes.push(`typeof ${componentVNodeVar}`) ctx.currentComponent = { ctxVar: componentCtxVar, @@ -98,32 +101,7 @@ export function* generateComponent( } } - if (matchImportName) { - // navigation support - yield `/** @type {[` - for (const tagOffset of tagOffsets) { - yield `typeof ` - if (componentOriginalVar === node.tag) { - yield [ - componentOriginalVar, - 'template', - tagOffset, - ctx.codeFeatures.withoutHighlightAndCompletion, - ] - } else { - const shouldCapitalize = - matchImportName[0].toUpperCase() === matchImportName[0] - yield* generateCamelized( - shouldCapitalize ? capitalize(node.tag) : node.tag, - 'template', - tagOffset, - ctx.codeFeatures.withoutHighlightAndCompletion, - ) - } - yield `, ` - } - yield `]} */${endOfLine}` - } else if (dynamicTagInfo) { + if (dynamicTagInfo) { yield `const ${componentOriginalVar} = (` yield* generateInterpolation( options, @@ -157,9 +135,7 @@ export function* generateComponent( options.selfComponentName && possibleOriginalNames.includes(options.selfComponentName) ) { - yield `typeof __VLS_self & (new () => { ` + - getSlotsPropertyName() + - `: __VLS_Slots }), ` + yield `typeof __VLS_self, ` } else { yield `void, ` } diff --git a/packages/language-core/src/codegen/template/elementProps.ts b/packages/language-core/src/codegen/template/elementProps.ts index 1037c8d..6af0772 100644 --- a/packages/language-core/src/codegen/template/elementProps.ts +++ b/packages/language-core/src/codegen/template/elementProps.ts @@ -437,7 +437,7 @@ function getPropsCodeInfo( strictPropsCheck: boolean, ): MpxCodeInformation { return ctx.resolveCodeFeatures({ - ...codeFeatures.withoutHighlightAndCompletion, + ...codeFeatures.withoutHighlight, verification: strictPropsCheck || { shouldReport(_source, code) { // https://typescript.tv/errors/#ts2353 diff --git a/packages/language-core/src/codegen/template/templateChild.ts b/packages/language-core/src/codegen/template/templateChild.ts index 1645a1e..d378d85 100644 --- a/packages/language-core/src/codegen/template/templateChild.ts +++ b/packages/language-core/src/codegen/template/templateChild.ts @@ -75,7 +75,6 @@ export function* generateTemplateChild( yield* generateVSlot(options, ctx, node, slotDir) } else if ( node.tagType === CompilerDOM.ElementTypes.ELEMENT || - node.tagType === CompilerDOM.ElementTypes.COMPONENT || node.tagType === CompilerDOM.ElementTypes.TEMPLATE ) { yield* generateElement(options, ctx, node) diff --git a/packages/language-core/src/plugins/mpx-tsx.ts b/packages/language-core/src/plugins/mpx-tsx.ts index 67b324b..1046242 100644 --- a/packages/language-core/src/plugins/mpx-tsx.ts +++ b/packages/language-core/src/plugins/mpx-tsx.ts @@ -1,5 +1,6 @@ import type { Code, MpxLanguagePlugin, Sfc } from '../types' import * as path from 'path-browserify' +import { camelize, capitalize } from '@mpxjs/language-shared' import { computed } from 'alien-signals' import { generateScript } from '../codegen/script' import { generateTemplate } from '../codegen/template' @@ -186,6 +187,11 @@ function createTsx( () => getScriptSetupRanges()?.defineProps?.name, ) + const getSelfComponentName = computed(() => { + const baseName = path.basename(fileName) + return capitalize(camelize(baseName.slice(0, baseName.lastIndexOf('.')))) + }) + const getGeneratedTemplate = computed(() => { if (getResolvedOptions().skipTemplateCheck || !sfc.template) { return @@ -205,7 +211,7 @@ function createTsx( hasDefineSlots: setupHasDefineSlots(), slotsAssignName: getSetupSlotsAssignName(), propsAssignName: getSetupPropsAssignName(), - selfComponentName: undefined, + selfComponentName: getSelfComponentName(), }) let current = codegen.next() @@ -232,6 +238,7 @@ function createTsx( lang: getLang(), scriptRanges: getScriptRanges(), scriptSetupRanges: getScriptSetupRanges(), + scriptSetupImportComponentNames: getSetupImportComponentNames(), templateCodegen: getGeneratedTemplate(), destructuredPropNames: getSetupDestructuredPropNames(), templateRefNames: getSetupTemplateRefNames(),