diff --git a/ets2panda/bindings/native/src/lsp.cpp b/ets2panda/bindings/native/src/lsp.cpp index 21152cd337a0025f35a20e875b96a23cea8e0077..0a114d63e74634c61441ed2c2d2e4306d573dcd0 100644 --- a/ets2panda/bindings/native/src/lsp.cpp +++ b/ets2panda/bindings/native/src/lsp.cpp @@ -1728,4 +1728,52 @@ KNativePointer impl_getSignatureHelpItems(KNativePointer context, KInt position) new SignatureHelpItems(ctx->getSignatureHelpItems(reinterpret_cast(context), position)); return textSpan; } -TS_INTEROP_2(getSignatureHelpItems, KNativePointer, KNativePointer, KInt) \ No newline at end of file +TS_INTEROP_2(getSignatureHelpItems, KNativePointer, KNativePointer, KInt) + +KNativePointer impl_getSymbolRefMapForDeclAndSrc(KNativePointer declarationContext, KNativePointer sourceContext) +{ + LSPAPI const *ctx = GetImpl(); + auto *ref = new SymbolReferenceMaps(ctx->getSymbolRefMapForDeclAndSrc( + reinterpret_cast(declarationContext), reinterpret_cast(sourceContext))); + return ref; +} +TS_INTEROP_2(getSymbolRefMapForDeclAndSrc, KNativePointer, KNativePointer, KNativePointer) + +KNativePointer impl_getReferenceMaps(KNativePointer refs) +{ + auto *refsPtr = reinterpret_cast(refs); + std::vector ptrs; + for (auto &el : refsPtr->referenceMaps) { + ptrs.push_back(new SymbolReferenceMap(el)); + } + return new std::vector(ptrs); +} +TS_INTEROP_1(getReferenceMaps, KNativePointer, KNativePointer) + +KNativePointer impl_getReferenceMapDeclaration(KNativePointer referenceMap) +{ + auto *ref = reinterpret_cast(referenceMap); + return &ref->declaration; +} +TS_INTEROP_1(getReferenceMapDeclaration, KNativePointer, KNativePointer) + +KNativePointer impl_getReferenceMapSource(KNativePointer referenceMap) +{ + auto *ref = reinterpret_cast(referenceMap); + return &ref->source; +} +TS_INTEROP_1(getReferenceMapSource, KNativePointer, KNativePointer) + +KInt impl_getSymbolReferenceStart(KNativePointer ref) +{ + auto *refPtr = reinterpret_cast(ref); + return refPtr->start; +} +TS_INTEROP_1(getSymbolReferenceStart, KInt, KNativePointer) + +KInt impl_getSymbolReferenceLength(KNativePointer ref) +{ + auto *refPtr = reinterpret_cast(ref); + return refPtr->length; +} +TS_INTEROP_1(getSymbolReferenceLength, KInt, KNativePointer) diff --git a/ets2panda/bindings/src/Es2pandaNativeModule.ts b/ets2panda/bindings/src/Es2pandaNativeModule.ts index b998458325f31f4829c3734dedd57b3c397f48d1..27501ae3cb1f0752b5d5c07c4c091a6a924270ba 100644 --- a/ets2panda/bindings/src/Es2pandaNativeModule.ts +++ b/ets2panda/bindings/src/Es2pandaNativeModule.ts @@ -79,6 +79,37 @@ export class Es2pandaNativeModule { ): KPtr { throw new Error('Not implemented'); } + _getSymbolRefMapForDeclAndSrc( + declCtx: KNativePointer, + srcCtx: KNativePointer, + ): KNativePointer { + throw new Error('Not implemented'); + } + _getReferenceMaps( + ptrPoint: KNativePointer, + ): KNativePointer { + throw new Error('Not implemented'); + } + _getReferenceMapDeclaration( + ptrPoint: KNativePointer, + ): KNativePointer { + throw new Error('Not implemented'); + } + _getReferenceMapSource( + ptrPoint: KNativePointer, + ): KNativePointer { + throw new Error('Not implemented'); + } + _getSymbolReferenceStart( + ptrPoint: KNativePointer, + ): KInt { + throw new Error('Not implemented'); + } + _getSymbolReferenceLength( + ptrPoint: KNativePointer, + ): KInt { + throw new Error('Not implemented'); + } _CreateContextFromFile(config: KPtr, filename: String): KPtr { throw new Error('Not implemented'); } diff --git a/ets2panda/bindings/src/build/pre_define.ts b/ets2panda/bindings/src/build/pre_define.ts new file mode 100644 index 0000000000000000000000000000000000000000..00ebe504f9a5f0ab038836570b389d868957f770 --- /dev/null +++ b/ets2panda/bindings/src/build/pre_define.ts @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const DECL_ETS_SUFFIX: string = '.d.ets'; +export const ETS_SUFFIX: string = '.ets'; +export const TS_SUFFIX: string = '.ts'; diff --git a/ets2panda/bindings/src/build/utils.ts b/ets2panda/bindings/src/build/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..9d91cf60e5a9508bf51afabf9f6a75bca7f31765 --- /dev/null +++ b/ets2panda/bindings/src/build/utils.ts @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { DECL_ETS_SUFFIX } from './pre_define'; +import { KNativePointer } from '../InteropTypes'; +import { AstInfo } from '../types'; +import { global } from '../global'; +import { + LspSymbolReferenceMaps, + LspDefinitionData, + LspReferenceData, +} from '../lspNode'; + +const WINDOWS: string = 'Windows_NT'; +const LINUX: string = 'Linux'; +const MAC: string = 'Darwin'; + +export function isWindows(): boolean { + return os.type() === WINDOWS; +} + +export function isLinux(): boolean { + return os.type() === LINUX; +} + +export function isMac(): boolean { + return os.type() === MAC; +} + +export function changeFileExtension(file: string, targetExt: string, originExt = ''): string { + let currentExt = originExt.length === 0 ? path.extname(file) : originExt; + let fileWithoutExt = file.substring(0, file.lastIndexOf(currentExt)); + return fileWithoutExt + targetExt; +} + +export function changeDeclgenFileExtension(file: string, targetExt: string): string { + if (file.endsWith(DECL_ETS_SUFFIX)) { + return changeFileExtension(file, targetExt, DECL_ETS_SUFFIX); + } + return changeFileExtension(file, targetExt); +} + +export function ensurePathExists(filePath: string): void { + try { + const dirPath: string = path.dirname(filePath); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + } catch (error) { + if (error instanceof Error) { + console.error(`Error: ${error.message}`); + } + } +} + +export function getModuleNameAndPath(filePath: string, projectPath: string): [string, string] { + let moduleName: string = ''; + let moduleRootPath: string = ''; + if (filePath.indexOf(projectPath) >= 0) { + const relativePath = path.relative(projectPath, filePath); + moduleName = relativePath.split(path.sep)[0]; + moduleRootPath = path.join(projectPath, moduleName); + } + return [moduleName, moduleRootPath]; +} + +export function setSymbolRelationMaps( + declLocalCtx: KNativePointer, + srclocalCtx: KNativePointer, + srcFilePath: string, + declFilePath: string, + relationInfoMap: Map> +): Map> { + if (declFilePath === null || declFilePath === undefined || declFilePath === '') { + return relationInfoMap; + } + let ptrPoint = global.es2panda._getSymbolRefMapForDeclAndSrc(declLocalCtx, srclocalCtx); + let refs = new LspSymbolReferenceMaps(ptrPoint); + refs.referenceMapList.forEach((itemInfo) => { + let sourceStart: number = itemInfo.source.start; + let sourcelength: number = itemInfo.source.length; + let declarationStart: number = itemInfo.declaration.start; + if (relationInfoMap.has(declFilePath)) { + let relationMap = relationInfoMap.get(declFilePath); + if (relationMap) { + let astInfo: AstInfo = { + offset: sourceStart, + length: sourcelength, + filePath: srcFilePath, + }; + relationMap.set(declarationStart, astInfo); + } + } else { + let relationMap: Map = new Map(); + let astInfo: AstInfo = { + offset: sourceStart, + length: sourcelength, + filePath: srcFilePath, + }; + relationMap.set(declarationStart, astInfo); + relationInfoMap.set(declFilePath, relationMap); + } + }); + return relationInfoMap; +} + +export function getRelationAstInfo( + lspPositionData: LspDefinitionData | LspReferenceData, + relationInfoMap: Map> +): AstInfo { + let astInfo: AstInfo | undefined; + const filePath = lspPositionData.fileName.toString(); + const fileOffset = lspPositionData.start; + if (relationInfoMap.has(filePath)) { + const relationMap = relationInfoMap.get(filePath); + if (relationMap && relationMap.has(fileOffset)) { + astInfo = relationMap.get(fileOffset); + } + } + if (astInfo && astInfo.filePath) { + return astInfo; + } + astInfo = { + offset: lspPositionData.start, + length: lspPositionData.length, + filePath: String(lspPositionData.fileName), + }; + return astInfo; +} diff --git a/ets2panda/bindings/src/lspNode.ts b/ets2panda/bindings/src/lspNode.ts index f553bf46c6eddbb20caaa347bbd6132d21852652..a2f69a175519095a270dabbd4e806558d2aa50ab 100644 --- a/ets2panda/bindings/src/lspNode.ts +++ b/ets2panda/bindings/src/lspNode.ts @@ -174,11 +174,17 @@ export class LspDiagsNode extends LspNode { } export class LspDefinitionData extends LspNode { - constructor(peer: KNativePointer) { + constructor(peer: KNativePointer, fileName?: String, start?: KInt, length?: KInt) { super(peer); - this.fileName = unpackString(global.es2panda._getFileNameFromDef(peer)); - this.start = global.es2panda._getStartFromDef(peer); - this.length = global.es2panda._getLengthFromDef(peer); + if (fileName && fileName !== '' && start && length) { + this.fileName = fileName; + this.start = start; + this.length = length; + } else { + this.fileName = unpackString(global.es2panda._getFileNameFromDef(peer)); + this.start = global.es2panda._getStartFromDef(peer); + this.length = global.es2panda._getLengthFromDef(peer); + } } readonly fileName: String; readonly start: KInt; @@ -186,11 +192,17 @@ export class LspDefinitionData extends LspNode { } export class LspReferenceData extends LspNode { - constructor(peer: KNativePointer) { + constructor(peer: KNativePointer, fileName?: String, start?: KInt, length?: KInt) { super(peer); - this.fileName = unpackString(global.es2panda._getReferenceFileName(peer)); - this.start = global.es2panda._getReferenceStart(peer); - this.length = global.es2panda._getReferenceLength(peer); + if (fileName && fileName !== '' && start && length) { + this.fileName = fileName; + this.start = start; + this.length = length; + } else { + this.fileName = unpackString(global.es2panda._getReferenceFileName(peer)); + this.start = global.es2panda._getReferenceStart(peer); + this.length = global.es2panda._getReferenceLength(peer); + } } readonly fileName: String; readonly start: KInt; @@ -219,6 +231,42 @@ export class LspReferences extends LspNode { readonly referenceInfos: LspReferenceData[]; } +export class LspSymbolReferenceData extends LspNode { + constructor(peer: KNativePointer) { + super(peer); + this.start = global.es2panda._getSymbolReferenceStart(peer); + this.length = global.es2panda._getSymbolReferenceLength(peer); + } + readonly start: KInt; + readonly length: KInt; +} + +export class LspSymbolReferenceMapData extends LspNode { + constructor(peer: KNativePointer) { + super(peer); + this.declaration = new LspSymbolReferenceData( + global.es2panda._getReferenceMapDeclaration(peer) + ); + this.source = new LspSymbolReferenceData( + global.es2panda._getReferenceMapSource(peer) + ); + } + readonly declaration: LspSymbolReferenceData; + readonly source: LspSymbolReferenceData; +} + +export class LspSymbolReferenceMaps extends LspNode { + constructor(peer: KNativePointer) { + super(peer); + this.referenceMapList = new NativePtrDecoder() + .decode(global.es2panda._getReferenceMaps(this.peer)) + .map((elPeer: KNativePointer) => { + return new LspSymbolReferenceMapData(elPeer); + }); + } + readonly referenceMapList: LspSymbolReferenceMapData[]; +} + export class LspTextSpan extends LspNode { constructor(peer: KNativePointer) { super(peer); diff --git a/ets2panda/bindings/src/lsp_helper.ts b/ets2panda/bindings/src/lsp_helper.ts index bdf10c82e921aac45e71d7346430cc95e94039ee..32beee22785a2fec0835041568d3b85a343dc5d8 100644 --- a/ets2panda/bindings/src/lsp_helper.ts +++ b/ets2panda/bindings/src/lsp_helper.ts @@ -50,7 +50,7 @@ import { } from './lspNode'; import { passStringArray, unpackString } from './private'; import { Es2pandaContextState } from './generated/Es2pandaEnums'; -import { BuildConfig, Config, FileDepsInfo, Job, JobInfo, WorkerInfo } from './types'; +import { BuildConfig, Config, FileDepsInfo, Job, JobInfo, WorkerInfo, AstInfo, ModuleInfo } from './types'; import { PluginDriver, PluginHook } from './ui_plugins_driver'; import { ModuleDescriptor } from './buildConfigGenerate'; import { generateArkTsConfigByModules } from './arktsConfigGenerate'; @@ -66,6 +66,18 @@ import * as child_process from 'child_process'; import { DECL_ETS_SUFFIX } from './preDefine'; import * as crypto from 'crypto'; import * as os from 'os'; +import { + changeDeclgenFileExtension, + setSymbolRelationMaps, + getModuleNameAndPath, + getRelationAstInfo, +} from './build/utils'; +import { + TS_SUFFIX, +} from './build/pre_define'; +import { + BuildMode +} from './build/buildMode'; const ets2pandaCmdPrefix = ['-', '--extension', 'ets', '--arktsconfig']; @@ -99,12 +111,17 @@ export class Lsp { private entryArkTsConfig: string; private fileDependencies: string; - constructor(projectPath: string, getContentCallback?: (filePath: string) => string) { + private relationInfoMap: Map>; // Map> + private declOutPath: string = ''; + + constructor(projectPath: string, getContentCallback?: (filePath: string) => string, outPath?: string) { initBuildEnv(); this.cacheDir = path.join(projectPath, '.idea', '.deveco'); let compileFileInfoPath = path.join(this.cacheDir, 'lsp_compileFileInfos.json'); this.fileDependencies = path.join(this.cacheDir, 'file_dependencies.json'); this.entryArkTsConfig = path.join(this.cacheDir, 'entry', 'arktsconfig.json'); + this.relationInfoMap = new Map>(); + this.pandaLibPath = path.resolve(__dirname, '../../ets2panda/lib'); this.projectPath = projectPath; this.pandaLibPath = process.env.PANDA_LIB_PATH ? process.env.PANDA_LIB_PATH @@ -117,6 +134,10 @@ export class Lsp { this.moduleToBuildConfig = JSON.parse(fs.readFileSync(buildConfigPath, 'utf-8')); this.filesMap = new Map(); this.getFileContent = getContentCallback || ((path: string): string => fs.readFileSync(path, 'utf8')); + if (outPath !== null && outPath !== undefined && outPath !== '') { + this.declOutPath = outPath; + this.generateDeclFileAndRelationMap(outPath); + } } modifyFilesMap(fileName: string, fileContent: TextDocumentChangeInfo): void { @@ -127,6 +148,126 @@ export class Lsp { this.filesMap.delete(fileName); } + generateDeclFileAndRelationMap(outPath: string): void { + let lspDriverHelper = new LspDriverHelper(); + for (let key in this.moduleToBuildConfig) { + const moduleConfig = this.moduleToBuildConfig[key]; + let buildMode = new BuildMode(moduleConfig); + buildMode.generateModuleInfos(); + let moduleInfos: [string, ModuleInfo][] = Array.from(buildMode.moduleInfos.entries()); + for (let moduleIndex = 0; moduleIndex < moduleInfos.length; moduleIndex++) { + const [key, moduleInfo] = moduleInfos[moduleIndex]; + for (let index = 0; index < moduleInfo.compileFileInfos.length; index++) { + // source file + const fileInfo = moduleInfo.compileFileInfos[index]; + let sourceFilePath = path.resolve(fileInfo.filePath); + let arktsconfig = this.fileNameToArktsconfig[fileInfo.filePath]; + let ets2pandaCmd = ets2pandaCmdPrefix.concat(arktsconfig); + let localCfg = lspDriverHelper.createCfg(ets2pandaCmd, sourceFilePath, this.pandaLibPath); + const source = this.getFileSource(fileInfo.filePath); + let localCtx = lspDriverHelper.createCtx(source, fileInfo.filePath, localCfg); + PluginDriver.getInstance().getPluginContext().setContextPtr(localCtx); + lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); + PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); + lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_CHECKED); + // declgen file + let filePathFromModuleRoot: string = path.relative(moduleInfo.moduleRootPath, fileInfo.filePath); + let declEtsOutputPath: string = changeDeclgenFileExtension( + path.join(outPath, moduleInfo.declgenV1OutPath ?? '', moduleInfo.packageName, filePathFromModuleRoot), + DECL_ETS_SUFFIX + ); + let etsOutputPath: string = changeDeclgenFileExtension( + path.join(outPath, moduleInfo.declgenBridgeCodePath ?? '', moduleInfo.packageName, filePathFromModuleRoot), + TS_SUFFIX + ); + ensurePathExists(declEtsOutputPath); + ensurePathExists(etsOutputPath); + global.es2pandaPublic._GenerateTsDeclarationsFromContext( + localCtx, + declEtsOutputPath, + etsOutputPath, + 1, + 1 + ); + let filePath = path.resolve(declEtsOutputPath); + let declgEts2pandaCmd = ets2pandaCmdPrefix.concat(arktsconfig); + let declgLocalCfg = lspDriverHelper.createCfg(declgEts2pandaCmd, filePath, this.pandaLibPath); + const declgSource = this.getFileSource(filePath); + let declgLocalCtx = lspDriverHelper.createCtx(declgSource, filePath, declgLocalCfg); + PluginDriver.getInstance().getPluginContext().setContextPtr(declgLocalCtx); + lspDriverHelper.proceedToState(declgLocalCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); + PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); + lspDriverHelper.proceedToState(declgLocalCtx, Es2pandaContextState.ES2PANDA_STATE_CHECKED); + // Get the Map mapping according to the Ctx of the declaration file and the source file + this.relationInfoMap = setSymbolRelationMaps(declgLocalCtx, localCtx, + fileInfo.filePath, filePath, this.relationInfoMap); + + // clean and destroy + PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); + lspDriverHelper.destroyContext(localCtx); + lspDriverHelper.destroyContext(declgLocalCtx); + } + } + } + } + + modifyDeclFileAndRelationMap(modifyFilePath: string): void { + if (!this.declOutPath) { + return; + } + // source file + let lspDriverHelper = new LspDriverHelper(); + let sourceFilePath = path.resolve(modifyFilePath.valueOf()); + let arktsconfig = this.fileNameToArktsconfig[sourceFilePath]; + let ets2pandaCmd = ets2pandaCmdPrefix.concat(arktsconfig); + let localCfg = lspDriverHelper.createCfg(ets2pandaCmd, sourceFilePath, this.pandaLibPath); + const source = this.getFileSource(sourceFilePath); + let localCtx = lspDriverHelper.createCtx(source, sourceFilePath, localCfg); + + const [moduleName, moduleRootPath] = getModuleNameAndPath(modifyFilePath, this.projectPath); + PluginDriver.getInstance().getPluginContext().setContextPtr(localCtx); + lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); + PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); + lspDriverHelper.proceedToState(localCtx, Es2pandaContextState.ES2PANDA_STATE_CHECKED); + // declgen file + let filePathFromModuleRoot: string = path.relative(moduleRootPath, modifyFilePath); + let declEtsOutputPath: string = changeDeclgenFileExtension( + path.join(this.declOutPath, moduleName, filePathFromModuleRoot), + DECL_ETS_SUFFIX + ); + let etsOutputPath: string = changeDeclgenFileExtension( + path.join(this.declOutPath, moduleName, filePathFromModuleRoot), + TS_SUFFIX + ); + ensurePathExists(declEtsOutputPath); + ensurePathExists(etsOutputPath); + global.es2pandaPublic._GenerateTsDeclarationsFromContext( + localCtx, + declEtsOutputPath, + etsOutputPath, + 1, + 1 + ); + let filePath = path.resolve(declEtsOutputPath); + let declgLocalCfg = lspDriverHelper.createCfg(ets2pandaCmd, filePath, this.pandaLibPath); + const declgSource = this.getFileSource(filePath); + let declgLocalCtx = lspDriverHelper.createCtx(declgSource, filePath, declgLocalCfg); + PluginDriver.getInstance().getPluginContext().setContextPtr(declgLocalCtx); + lspDriverHelper.proceedToState(declgLocalCtx, Es2pandaContextState.ES2PANDA_STATE_PARSED); + PluginDriver.getInstance().runPluginHook(PluginHook.PARSED); + lspDriverHelper.proceedToState(declgLocalCtx, Es2pandaContextState.ES2PANDA_STATE_CHECKED); + // Before the update, clear all the mapping relationships corresponding to the modified files in the Map. + if (filePath !== null && filePath !== undefined && filePath !== '' && this.relationInfoMap.has(filePath)) { + this.relationInfoMap.set(filePath, new Map()); + } + // Get the astMap mapping according to the Ctx of the declaration file and the source file + this.relationInfoMap = setSymbolRelationMaps(declgLocalCtx, localCtx, + modifyFilePath, filePath, this.relationInfoMap); + lspDriverHelper.destroyContext(declgLocalCtx); + PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); + lspDriverHelper.destroyContext(localCtx); + } + updateConfig(buildSdkPath: string, modules?: ModuleDescriptor[]): void { generateArkTsConfigByModules(buildSdkPath, this.projectPath, modules); let compileFileInfoPath = path.join(this.projectPath, '.idea', '.deveco', 'lsp_compileFileInfos.json'); @@ -153,7 +294,12 @@ export class Lsp { return parts[0] || ''; } - getDefinitionAtPosition(filename: String, offset: number): LspDefinitionData { + getDefinitionAtPosition(filename: String, offset: number, lspPositionData?: LspDefinitionData): LspDefinitionData { + // Mixed scenarios + if (lspPositionData) { + const astInfo: AstInfo = getRelationAstInfo(lspPositionData, this.relationInfoMap); + return new LspDefinitionData(1, String(astInfo.filePath), astInfo.offset, astInfo.length); + } let lspDriverHelper = new LspDriverHelper(); let filePath = path.resolve(filename.valueOf()); let arktsconfig = this.fileNameToArktsconfig[filePath]; @@ -210,7 +356,12 @@ export class Lsp { return unpackString(ptr); } - getImplementationAtPosition(filename: String, offset: number): LspDefinitionData { + getImplementationAtPosition(filename: String, offset: number, lspPositionData?: LspDefinitionData): LspDefinitionData { + // Mixed scenarios + if (lspPositionData) { + const astInfo: AstInfo = getRelationAstInfo(lspPositionData, this.relationInfoMap); + return new LspDefinitionData(1, String(astInfo.filePath), astInfo.offset, astInfo.length); + } let lspDriverHelper = new LspDriverHelper(); let filePath = path.resolve(filename.valueOf()); let arktsconfig = this.fileNameToArktsconfig[filePath]; @@ -273,7 +424,15 @@ export class Lsp { return result; } - getReferencesAtPosition(filename: String, offset: number): LspReferenceData[] { + getReferencesAtPosition(filename: String, offset: number, lspReferenceData?: LspReferenceData[]): LspReferenceData[] { + let result: LspReferenceData[] = []; + if (lspReferenceData && lspReferenceData.length > 0) { + lspReferenceData.forEach((referenceData: LspReferenceData) => { + const astInfo = getRelationAstInfo(referenceData, this.relationInfoMap); + result.push(new LspReferenceData(1, String(astInfo.filePath), astInfo.offset, astInfo.length)); + }); + return result; + } let lspDriverHelper = new LspDriverHelper(); let searchFilePath = path.resolve(filename.valueOf()); let arktsconfig = this.fileNameToArktsconfig[searchFilePath]; @@ -289,7 +448,6 @@ export class Lsp { PluginDriver.getInstance().runPluginHook(PluginHook.CLEAN); lspDriverHelper.destroyContext(localCtx); lspDriverHelper.destroyConfig(localCfg); - let result: LspReferenceData[] = []; let moduleName = path.basename(path.dirname(arktsconfig)); let buildConfig: BuildConfig = this.moduleToBuildConfig[moduleName]; for (let i = 0; i < buildConfig.compileFiles.length; i++) { diff --git a/ets2panda/bindings/src/types.ts b/ets2panda/bindings/src/types.ts index ef956f40bf59c6ff70f8c7e172ac6a6574d098b1..83bd709da2caaa190cf6b25c8e7eac70ce229a3d 100644 --- a/ets2panda/bindings/src/types.ts +++ b/ets2panda/bindings/src/types.ts @@ -245,3 +245,18 @@ export interface WorkerInfo { worker: ThreadWorker; isIdle: boolean; } + +export interface FileInfo { + filePath: string; + dependentFiles: string[]; + abcFilePath: string; + arktsConfigFile: string; + packageName: string; + moduleRootPath?: string; +} + +export interface AstInfo { + offset: number; + length: number; + filePath?: string; +} diff --git a/ets2panda/lsp/include/api.h b/ets2panda/lsp/include/api.h index 75ded0e385c46553dc6deba2a2e794c6ffb7ff10..7d8d9c57de4b2a028d99c4737ec01486537b88d6 100644 --- a/ets2panda/lsp/include/api.h +++ b/ets2panda/lsp/include/api.h @@ -72,6 +72,24 @@ typedef struct References { std::vector referenceInfos; } References; +typedef struct SymbolReference { + SymbolReference() = default; + SymbolReference(size_t s, size_t l) : start(s), length(l) {} + size_t start; + size_t length; +} SymbolReference; + +typedef struct SymbolReferenceMap { + SymbolReferenceMap() = default; + SymbolReferenceMap(SymbolReference dec, SymbolReference sour) : declaration(dec), source(sour) {} + SymbolReference declaration; + SymbolReference source; +} SymbolReferenceMap; + +typedef struct SymbolReferenceMaps { + std::vector referenceMaps; +} SymbolReferenceMaps; + typedef struct Position { size_t line_; // Line number size_t character_; // Character position in the line @@ -551,6 +569,8 @@ typedef struct LSPAPI { CombinedCodeActionsInfo (*getCombinedCodeFix)(const char *fileName, const std::string &fixId, CodeFixOptions &codeFixOptions); TextSpan *(*GetNameOrDottedNameSpan)(es2panda_Context *context, int startPos); + SymbolReferenceMaps (*getSymbolRefMapForDeclAndSrc)(es2panda_Context *declarationContext, + es2panda_Context *sourceContext); } LSPAPI; CAPI_EXPORT LSPAPI const *GetImpl(); // NOLINTEND diff --git a/ets2panda/lsp/include/internal_api.h b/ets2panda/lsp/include/internal_api.h index 03fa2abb1b594d23599fa915c5072cf652f5267c..69b159fd2a07a3f0e255522274e8a3b47e08c5b1 100644 --- a/ets2panda/lsp/include/internal_api.h +++ b/ets2panda/lsp/include/internal_api.h @@ -135,6 +135,8 @@ std::vector GetCodeFixesAtPositionImpl(es2panda_Context *cont CombinedCodeActionsInfo GetCombinedCodeFixImpl(es2panda_Context *context, const std::string &fixId, CodeFixOptions &codeFixOptions); ir::Identifier *GetIdentFromNewClassExprPart(const ir::Expression *value); +SymbolReferenceMaps GetSymbolRefMapForDeclAndSrcImpl(es2panda_Context *declarationContext, + es2panda_Context *sourceContext); } // namespace ark::es2panda::lsp #endif \ No newline at end of file diff --git a/ets2panda/lsp/src/api.cpp b/ets2panda/lsp/src/api.cpp index f13d00069e04238369006b46f0347811f81f132f..96e19b60ad4bcebc60d54f426f1969dfde0e2fa3 100644 --- a/ets2panda/lsp/src/api.cpp +++ b/ets2panda/lsp/src/api.cpp @@ -437,6 +437,11 @@ TextSpan *GetNameOrDottedNameSpan(es2panda_Context *context, int startPos) return result; } +SymbolReferenceMaps GetSymbolRefMapForDeclAndSrc(es2panda_Context *declarationContext, es2panda_Context *sourceContext) +{ + return GetSymbolRefMapForDeclAndSrcImpl(declarationContext, sourceContext); +} + LSPAPI g_lspImpl = {GetDefinitionAtPosition, GetApplicableRefactors, GetImplementationAtPosition, @@ -476,7 +481,8 @@ LSPAPI g_lspImpl = {GetDefinitionAtPosition, GetSignatureHelpItems, GetCodeFixesAtPosition, GetCombinedCodeFix, - GetNameOrDottedNameSpan}; + GetNameOrDottedNameSpan, + GetSymbolRefMapForDeclAndSrc}; } // namespace ark::es2panda::lsp CAPI_EXPORT LSPAPI const *GetImpl() diff --git a/ets2panda/lsp/src/internal_api.cpp b/ets2panda/lsp/src/internal_api.cpp index 795777e89ecca6795cded4270364c821d5184655..e9fa68e568b82eab93809ad491b57371c09a63a5 100644 --- a/ets2panda/lsp/src/internal_api.cpp +++ b/ets2panda/lsp/src/internal_api.cpp @@ -170,6 +170,67 @@ References GetFileReferencesImpl(es2panda_Context *referenceFileContext, char co return result; } +using IdentifierVector = std::vector; +std::unique_ptr GetIndentifierNode(const ir::AstNode *astNode) +{ + if (astNode == nullptr) { + return nullptr; + } + auto result = std::make_unique(); + astNode->FindChild([&result](ark::es2panda::ir::AstNode *child) { + if (!child->IsIdentifier()) { + return false; + } + auto identifier = child->AsIdentifier(); + auto isDuplicate = + std::any_of(result->begin(), result->end(), [&identifier](ark::es2panda::ir::Identifier *existing) { + return identifier->Range().start.index == existing->Range().start.index && + identifier->Range().end.index == existing->Range().end.index; + }); + if (!isDuplicate) { + result->emplace_back(identifier); + } + return false; + }); + return result; +} + +SymbolReferenceMaps GetSymbolRefMapForDeclAndSrcImpl(es2panda_Context *declarationContext, + es2panda_Context *sourceContext) +{ + SymbolReferenceMaps referenceMaps; + auto sourceLibCtx = reinterpret_cast(sourceContext); + if (sourceLibCtx->parserProgram == nullptr || sourceLibCtx->parserProgram->Ast() == nullptr) { + return referenceMaps; + } + auto decLibCtx = reinterpret_cast(declarationContext); + if (decLibCtx->parserProgram == nullptr || decLibCtx->parserProgram->Ast() == nullptr) { + return referenceMaps; + } + auto sourceNodes = GetIndentifierNode(reinterpret_cast(sourceLibCtx->parserProgram->Ast())); + if (!sourceNodes) { + return referenceMaps; + } + auto declarationNodes = GetIndentifierNode(reinterpret_cast(decLibCtx->parserProgram->Ast())); + if (!declarationNodes) { + return referenceMaps; + } + + for (auto declarationNode : *declarationNodes) { + auto it = std::find_if(sourceNodes->begin(), sourceNodes->end(), + [&declarationNode](ark::es2panda::ir::Identifier *source) { + return source->Name() == declarationNode->Name(); + }); + if (it != sourceNodes->end()) { + SymbolReference decRef(declarationNode->Start().index, + declarationNode->End().index - declarationNode->Start().index); + SymbolReference sourceRef((*it)->Start().index, (*it)->End().index - (*it)->Start().index); + referenceMaps.referenceMaps.emplace_back(decRef, sourceRef); + } + } + return referenceMaps; +} + bool IsToken(const ir::AstNode *node) { /** diff --git a/ets2panda/test/unit/lsp/CMakeLists.txt b/ets2panda/test/unit/lsp/CMakeLists.txt index 8106544cc6d29acf7836ba05545244e79cb850ed..4e8ddf805bd290bfc33efef2018502c7737dbcbd 100644 --- a/ets2panda/test/unit/lsp/CMakeLists.txt +++ b/ets2panda/test/unit/lsp/CMakeLists.txt @@ -222,3 +222,7 @@ ets2panda_add_gtest(lsp_api_test_fix_abstract_member CPP_SOURCES ets2panda_add_gtest(lsp_api_test_get_name_or_dotted_name_span CPP_SOURCES get_name_or_dotted_name_span_test.cpp ) + +ets2panda_add_gtest(lsp_api_get_symbol_ref_map_test CPP_SOURCES + get_symbol_ref_map_test.cpp +) \ No newline at end of file diff --git a/ets2panda/test/unit/lsp/get_symbol_ref_map_test.cpp b/ets2panda/test/unit/lsp/get_symbol_ref_map_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..867feb3bc46e060a8493407faf0dd99b9820c83b --- /dev/null +++ b/ets2panda/test/unit/lsp/get_symbol_ref_map_test.cpp @@ -0,0 +1,143 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at* + * + * http://www.apache.org/licenses/LICENSE-2.0* + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "lsp/include/create_type_help_items.h" +#include "lsp_api_test.h" +#include +#include +#include +#include +#include +#include "ir/astNode.h" +#include "lsp/include/internal_api.h" +#include "public/es2panda_lib.h" +#include "utils/arena_containers.h" + +namespace { + +using std::string; +using std::vector; + +class LSPCreateTypeHelpItems : public LSPAPITests { +public: + void TestGetSymbolRefMapForDeclAndSrcImpl(const string sourceText, const string declText) + { + const auto sourceTest = "sourecTest.ets"; + const auto declTest = "declTest.ets"; + std::vector sourceFiles = {sourceTest}; + std::vector sourceTexts = {sourceText}; + auto sourceFilePaths = CreateTempFile(sourceFiles, sourceTexts); + ark::es2panda::lsp::Initializer initializer = ark::es2panda::lsp::Initializer(); + es2panda_Context *sourceCtx = + initializer.CreateContext(sourceFiles.front().c_str(), ES2PANDA_STATE_CHECKED, sourceTexts.front().c_str()); + + std::vector declFiles = {declTest}; + std::vector declTexts = {declText}; + auto declFilePaths = CreateTempFile(declFiles, declTexts); + es2panda_Context *declCtx = + initializer.CreateContext(declFiles.front().c_str(), ES2PANDA_STATE_CHECKED, declTexts.front().c_str()); + auto symbolMap = ark::es2panda::lsp::GetSymbolRefMapForDeclAndSrcImpl(declCtx, sourceCtx); + for (const auto &referenceMap : symbolMap.referenceMaps) { + ASSERT_EQ(referenceMap.source.length, referenceMap.declaration.length); + } + initializer.DestroyContext(sourceCtx); + initializer.DestroyContext(declCtx); + } +}; + +TEST_F(LSPCreateTypeHelpItems, GetTypeHelpItemInterfaceTest) +{ + const auto sourceText = R"( +class A { + public fooA() { + return 1; + } +} +function foo() { +} +interface I { + fooI(): void; +} +let varA = 1; +type UT = number | string; +function main() {} +)"; + const auto declText = R"( +export declare function foo(): void; +export declare let varA: number; +export declare function main(): void; +export declare class A { + public fooA(): number; + constructor(); +} +export declare interface I { + fooI(): void; +} +export type UT = number | string; +)"; + TestGetSymbolRefMapForDeclAndSrcImpl(sourceText, declText); +} + +TEST_F(LSPCreateTypeHelpItems, GetSymbolRefMapForDeclAndSrcImplTest) +{ + const auto sourceText = R"( +class BaseClass { + public name: string; + public GetName(): string{ + return this.name; + } +} +)"; + const auto declText = R"( +export declare function main(): void; +export declare class BaseClass { + public get name(): string; + public set name(value: string); + public GetName(): string; + constructor(); +} +)"; + TestGetSymbolRefMapForDeclAndSrcImpl(sourceText, declText); +} + +TEST_F(LSPCreateTypeHelpItems, GetSymbolRefMapForDeclAndSrcImplSizeTest) +{ + const auto sourceText = R"( +class Base { + public name: string; + public GetName(): string{ + return this.name; + } +} +class Derived extends Base { + method() {} +} +)"; + const auto declText = R"( +export declare function main(): void; +export declare class Base { + public get name(): string; + public set name(value: string); + public GetName(): string; + constructor(); +} +export declare class Derived extends Base { + public method(): void; + constructor(); +} +)"; + TestGetSymbolRefMapForDeclAndSrcImpl(sourceText, declText); +} +} // namespace \ No newline at end of file