diff --git a/ets2panda/bindings/native/src/lsp.cpp b/ets2panda/bindings/native/src/lsp.cpp index 58fef1622a8c3fb58608c99debca3585d42e1b37..5a43b8f8a328f5f7540dadc696490b4f6dba617f 100644 --- a/ets2panda/bindings/native/src/lsp.cpp +++ b/ets2panda/bindings/native/src/lsp.cpp @@ -1807,4 +1807,28 @@ KInt impl_getOffsetByColAndLine(KNativePointer contextPtr, KInt line, KInt colum LSPAPI const *impl = GetImpl(); return impl->getOffsetByColAndLine(context, line, column); } -TS_INTEROP_3(getOffsetByColAndLine, KInt, KNativePointer, KInt, KInt) \ No newline at end of file +TS_INTEROP_3(getOffsetByColAndLine, KInt, KNativePointer, KInt, KInt) + +KNativePointer impl_getClassDefinition(KNativePointer astNodePtr, KStringPtr &nodeNamePtr) +{ + auto ast = reinterpret_cast(astNodePtr); + LSPAPI const *impl = GetImpl(); + return impl->getClassDefinition(ast, nodeNamePtr.data()); +} +TS_INTEROP_2(getClassDefinition, KNativePointer, KNativePointer, KStringPtr) + +KNativePointer impl_getProgramAst(KNativePointer contextPtr) +{ + auto context = reinterpret_cast(contextPtr); + LSPAPI const *impl = GetImpl(); + return impl->getProgramAst(context); +} +TS_INTEROP_1(getProgramAst, KNativePointer, KNativePointer) + +KNativePointer impl_getDefinitionDataFromNode(KNativePointer astNodePtr, KStringPtr &nodeNamePtr) +{ + auto ast = reinterpret_cast(astNodePtr); + LSPAPI const *impl = GetImpl(); + return new DefinitionInfo(impl->getDefinitionDataFromNode(ast, nodeNamePtr.data())); +} +TS_INTEROP_2(getDefinitionDataFromNode, KNativePointer, KNativePointer, KStringPtr) \ No newline at end of file diff --git a/ets2panda/bindings/src/common/Es2pandaNativeModule.ts b/ets2panda/bindings/src/common/Es2pandaNativeModule.ts index 823bb00b0834a55e9c947b91273eb7c3eb157993..ddda9dcd89b5cbe589ff9aee15ba8cf50b7ce8c6 100644 --- a/ets2panda/bindings/src/common/Es2pandaNativeModule.ts +++ b/ets2panda/bindings/src/common/Es2pandaNativeModule.ts @@ -984,6 +984,18 @@ export class Es2pandaNativeModule { _InvalidateFileCache(globalContextPtr: KPtr, filename: String): void { throw new Error('Not implemented'); } + + _getProgramAst(context: KPtr): KPtr { + throw new Error('Not implemented'); + } + + _getClassDefinition(astNode: KPtr, nodeName: String): KPtr { + throw new Error('Not implemented'); + } + + _getDefinitionDataFromNode(astNode: KPtr, nodeName: String): KPtr { + throw new Error('Not implemented'); + } } export function initEs2panda(): Es2pandaNativeModule { diff --git a/ets2panda/bindings/src/common/arkTSConfigGenerator.ts b/ets2panda/bindings/src/common/arkTSConfigGenerator.ts index 5b2a1ee5f9b999b9ebe4a6ba658caaf301ddb078..7e94bf25f9df09de7ab27140c70424d4acdd02c3 100644 --- a/ets2panda/bindings/src/common/arkTSConfigGenerator.ts +++ b/ets2panda/bindings/src/common/arkTSConfigGenerator.ts @@ -16,7 +16,7 @@ import * as path from 'path'; import * as fs from 'fs'; -import { changeFileExtension, ensurePathExists } from './utils'; +import { changeFileExtension, ensurePathExists, getFileLanguageVersion } from './utils'; import { BuildConfig, ModuleInfo } from './types'; import { LANGUAGE_VERSION, PANDA_SDK_PATH_FROM_SDK, SYSTEM_SDK_PATH_FROM_SDK } from './preDefine'; @@ -120,6 +120,20 @@ export class ArkTSConfigGenerator { }); } + private getAlias(fullPath: string, entryRoot: string): string { + const normalizedFull = path.normalize(fullPath); + const normalizedEntry = path.normalize(entryRoot); + const entryDir = normalizedEntry.endsWith(path.sep) ? normalizedEntry : normalizedEntry + path.sep; + if (!normalizedFull.startsWith(entryDir)) { + throw new Error(`Path ${fullPath} is not under entry root ${entryRoot}`); + } + const entryName = path.basename(normalizedEntry); + const relativePath = normalizedFull.substring(entryDir.length); + const formatPath = path.join(entryName, relativePath).replace(/\\/g, '/'); + const alias = formatPath; + return changeFileExtension(alias, ''); + } + private getPathSection(moduleInfo: ModuleInfo): Record { if (Object.keys(this.pathSection).length !== 0) { return this.pathSection; @@ -132,9 +146,27 @@ export class ArkTSConfigGenerator { Object.values(moduleInfo.staticDepModuleInfos).forEach((depModuleName: string) => { let depModuleInfo = this.moduleInfos[depModuleName]; - this.pathSection[depModuleInfo.packageName] = [path.resolve(depModuleInfo.moduleRootPath)]; + if (depModuleInfo.language === LANGUAGE_VERSION.ARKTS_1_2) { + this.pathSection[depModuleInfo.packageName] = [path.resolve(depModuleInfo.moduleRootPath)]; + } else if (depModuleInfo.language === LANGUAGE_VERSION.ARKTS_HYBRID) { + depModuleInfo.compileFiles.forEach((file) => { + const firstLine = fs.readFileSync(file, 'utf-8').split('\n')[0]; + if (firstLine.includes('use static')) { + this.pathSection[this.getAlias(file, depModuleInfo.moduleRootPath)] = [path.resolve(file)]; + } + }); + } }); + if (moduleInfo.language === LANGUAGE_VERSION.ARKTS_HYBRID) { + moduleInfo.compileFiles.forEach((file) => { + const firstLine = fs.readFileSync(file, 'utf-8').split('\n')[0]; + if (getFileLanguageVersion(firstLine) === LANGUAGE_VERSION.ARKTS_1_2) { + this.pathSection[this.getAlias(file, moduleInfo.moduleRootPath)] = [path.resolve(file)]; + } + }); + } + return this.pathSection; } @@ -144,31 +176,39 @@ export class ArkTSConfigGenerator { return changeFileExtension(ohmurl, ''); } + private parseDeclFile(moduleInfo: ModuleInfo, dependencySection: Record) { + if (!moduleInfo.declFilesPath || !fs.existsSync(moduleInfo.declFilesPath)) { + console.error(`Module ${moduleInfo.packageName} depends on dynamic module ${moduleInfo.packageName}, but + decl file not found on path ${moduleInfo.declFilesPath}`); + return; + } + let declFilesObject = JSON.parse(fs.readFileSync(moduleInfo.declFilesPath, 'utf-8')); + Object.keys(declFilesObject.files).forEach((file: string) => { + let ohmurl: string = this.getOhmurl(file, moduleInfo); + dependencySection[ohmurl] = { + language: 'js', + path: declFilesObject.files[file].declPath, + ohmUrl: declFilesObject.files[file].ohmUrl + }; + + let absFilePath: string = path.resolve(moduleInfo.moduleRootPath, file); + let entryFileWithoutExtension: string = changeFileExtension(moduleInfo.entryFile, ''); + if (absFilePath === entryFileWithoutExtension) { + dependencySection[moduleInfo.packageName] = dependencySection[ohmurl]; + } + }); + } + private getDependenciesSection(moduleInfo: ModuleInfo, dependencySection: Record): void { let depModules: string[] = moduleInfo.dynamicDepModuleInfos; depModules.forEach((depModuleName: string) => { let depModuleInfo = this.moduleInfos[depModuleName]; - if (!depModuleInfo.declFilesPath || !fs.existsSync(depModuleInfo.declFilesPath)) { - console.error(`Module ${moduleInfo.packageName} depends on dynamic module ${depModuleInfo.packageName}, but - decl file not found on path ${depModuleInfo.declFilesPath}`); - return; - } - let declFilesObject = JSON.parse(fs.readFileSync(depModuleInfo.declFilesPath, 'utf-8')); - Object.keys(declFilesObject.files).forEach((file: string) => { - let ohmurl: string = this.getOhmurl(file, depModuleInfo); - dependencySection[ohmurl] = { - language: 'js', - path: declFilesObject.files[file].declPath, - ohmUrl: declFilesObject.files[file].ohmUrl - }; - - let absFilePath: string = path.resolve(depModuleInfo.moduleRootPath, file); - let entryFileWithoutExtension: string = changeFileExtension(depModuleInfo.entryFile, ''); - if (absFilePath === entryFileWithoutExtension) { - dependencySection[depModuleInfo.packageName] = dependencySection[ohmurl]; - } - }); + this.parseDeclFile(depModuleInfo, dependencySection); }); + + if (moduleInfo.language === LANGUAGE_VERSION.ARKTS_HYBRID) { + this.parseDeclFile(moduleInfo, dependencySection); + } } public writeArkTSConfigFile(moduleInfo: ModuleInfo): void { diff --git a/ets2panda/bindings/src/common/types.ts b/ets2panda/bindings/src/common/types.ts index 29edf9d82a22794cef0e38dd051c4cb8c4417350..91d753a660e249e42aeed114e8afeb79c0705681 100644 --- a/ets2panda/bindings/src/common/types.ts +++ b/ets2panda/bindings/src/common/types.ts @@ -109,7 +109,11 @@ export class Context extends ArktsObject { throwError(`Config not initialized`); } return new Context( - global.es2panda._CreateContextFromStringWithHistory(global.config, passString(source), passString(global.filePath)) + global.es2panda._CreateContextFromStringWithHistory( + global.config, + passString(source), + passString(global.filePath) + ) ); } @@ -164,7 +168,6 @@ export interface PathConfig { } export interface DeclgenConfig { - enableDeclgenEts2Ts: boolean; declgenV1OutPath?: string; declgenBridgeCodePath?: string; } @@ -225,3 +228,11 @@ export interface TextDocumentChangeInfo { rangeEnd?: number; updateText?: string; } + +export enum AstNodeType { + CLASS_DEFINITION +} +export interface NodeInfo { + name: string; + kind: AstNodeType; +} diff --git a/ets2panda/bindings/src/common/utils.ts b/ets2panda/bindings/src/common/utils.ts index 800f92b064490f638bf0e71299ed0809ab6bcf76..8ad712c6560e5cc97444b409c55cc9fff9dfdd5a 100644 --- a/ets2panda/bindings/src/common/utils.ts +++ b/ets2panda/bindings/src/common/utils.ts @@ -16,7 +16,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; -import { DECL_ETS_SUFFIX } from './preDefine'; +import { DECL_ETS_SUFFIX, LANGUAGE_VERSION } from './preDefine'; export function throwError(error: string): never { throw new Error(error); @@ -67,3 +67,13 @@ export function getModuleNameAndPath(filePath: string, projectPath: string): [st } return [moduleName, moduleRootPath]; } + +export function getFileLanguageVersion(fileSource: string): string { + const firstLine = fileSource.split('\n')[0]; + const hasUseStatic = firstLine.includes('use static'); + if (hasUseStatic) { + return LANGUAGE_VERSION.ARKTS_1_2; + } else { + return LANGUAGE_VERSION.ARKTS_1_1; + } +} diff --git a/ets2panda/bindings/src/lsp/generateArkTSConfig.ts b/ets2panda/bindings/src/lsp/generateArkTSConfig.ts index fae5a16029d155265c52a0df422300ce6b748559..0e87e96088be935fc3662397c8e39d10a2a4b7b2 100644 --- a/ets2panda/bindings/src/lsp/generateArkTSConfig.ts +++ b/ets2panda/bindings/src/lsp/generateArkTSConfig.ts @@ -26,9 +26,14 @@ function collectDepModuleInfos(moduleInfo: ModuleInfo, allBuildConfig: Record { let depModule = allBuildConfig[moduleName]; - depModule.language === LANGUAGE_VERSION.ARKTS_1_2 - ? staticDepModules.push(depModule.packageName) - : dynamicDepModules.push(depModule.packageName); + if (depModule.language === LANGUAGE_VERSION.ARKTS_1_2) { + staticDepModules.push(depModule.packageName); + } else if (depModule.language === LANGUAGE_VERSION.ARKTS_1_1) { + dynamicDepModules.push(depModule.packageName); + } else { + staticDepModules.push(depModule.packageName); + dynamicDepModules.push(depModule.packageName); + } }); } moduleInfo.dynamicDepModuleInfos = dynamicDepModules; @@ -69,10 +74,10 @@ export function generateModuleInfo(allBuildConfig: Record, export function generateArkTsConfigs(allBuildConfig: Record): Record { let moduleInfos: Record = collectModuleInfos(allBuildConfig); - Object.keys(moduleInfos).forEach((filePath: string) => { - let packageName = moduleInfos[filePath].packageName; - let generator = ArkTSConfigGenerator.getGenerator(allBuildConfig[packageName], moduleInfos); - generator.writeArkTSConfigFile(moduleInfos[filePath]); + Object.keys(moduleInfos).forEach((packageName: string) => { + let buildConfig = allBuildConfig[packageName]; + let generator = ArkTSConfigGenerator.getGenerator(buildConfig, moduleInfos); + generator.writeArkTSConfigFile(moduleInfos[packageName]); }); let fileToModuleInfo: Record = {}; Object.values(moduleInfos).forEach((moduleInfo: ModuleInfo) => { diff --git a/ets2panda/bindings/src/lsp/generateBuildConfig.ts b/ets2panda/bindings/src/lsp/generateBuildConfig.ts index 7eace71cb11d0a6bd3f9445e2a25b66f473516e2..67d98d257850600be10082e44c2be99324b1b2cd 100644 --- a/ets2panda/bindings/src/lsp/generateBuildConfig.ts +++ b/ets2panda/bindings/src/lsp/generateBuildConfig.ts @@ -17,13 +17,14 @@ import * as fs from 'fs'; import * as path from 'path'; import * as JSON5 from 'json5'; import { BuildConfig, PathConfig } from '../common/types'; -import { DEFAULT_CACHE_DIR, EXTERNAL_API_PATH_FROM_SDK } from '../common/preDefine'; +import { DEFAULT_CACHE_DIR, EXTERNAL_API_PATH_FROM_SDK, LANGUAGE_VERSION } from '../common/preDefine'; +import { getFileLanguageVersion } from '../common/utils'; export interface ModuleDescriptor { - arktsversion: string; name: string; moduleType: string; srcPath: string; + arktsversion?: string; aceModuleJsonPath?: string; } @@ -172,6 +173,28 @@ function addPluginPathConfigs(buildConfig: BuildConfig, module: ModuleDescriptor buildConfig.aceModuleJsonPath = module.aceModuleJsonPath; } +function getModuleLanguageVersion(compileFiles: Set): string { + let found1_1 = false; + let found1_2 = false; + + for (const file of compileFiles) { + const sourceFile = fs.readFileSync(file, 'utf8'); + const languageVersion = getFileLanguageVersion(sourceFile); + + if (languageVersion === LANGUAGE_VERSION.ARKTS_1_2) { + found1_2 = true; + } else if (languageVersion === LANGUAGE_VERSION.ARKTS_1_1) { + found1_1 = true; + } + + if (found1_1 && found1_2) { + return LANGUAGE_VERSION.ARKTS_HYBRID; + } + } + + return found1_2 ? LANGUAGE_VERSION.ARKTS_1_2 : found1_1 ? LANGUAGE_VERSION.ARKTS_1_1 : ''; +} + export function generateBuildConfigs( pathConfig: PathConfig, modules?: ModuleDescriptor[] @@ -185,8 +208,6 @@ export function generateBuildConfigs( const definedModules = modules; - const enableDeclgen: Map = new Map(modules.map((module) => [module.name, false])); - for (const module of definedModules) { const modulePath = module.srcPath; const compileFiles = new Set(getEtsFiles(modulePath)); @@ -196,19 +217,15 @@ export function generateBuildConfigs( const dependencies = getModuleDependencies(modulePath); for (const depPath of dependencies) { getEtsFiles(depPath).forEach((file) => compileFiles.add(file)); - const depModule = definedModules.find((m) => m.srcPath === depPath); - if (module.arktsversion === '1.1' && depModule?.arktsversion === '1.2') { - enableDeclgen.set(depModule.name, true); - } } - + let languageVersion = getModuleLanguageVersion(compileFiles); allBuildConfigs[module.name] = { plugins: pluginMap, compileFiles: Array.from(compileFiles), packageName: module.name, moduleType: module.moduleType, moduleRootPath: modulePath, - language: module.arktsversion, + language: languageVersion, buildSdkPath: pathConfig.buildSdkPath, projectPath: pathConfig.projectPath, declgenOutDir: pathConfig.declgenOutDir, @@ -217,30 +234,24 @@ export function generateBuildConfigs( : path.resolve(pathConfig.buildSdkPath, EXTERNAL_API_PATH_FROM_SDK), cacheDir: pathConfig.cacheDir !== undefined ? pathConfig.cacheDir : path.join(pathConfig.projectPath, DEFAULT_CACHE_DIR), - enableDeclgenEts2Ts: false, declFilesPath: - module.arktsversion === '1.1' - ? path.join(pathConfig.declgenOutDir, 'static', module.name, 'decl-fileInfo.json') + languageVersion !== LANGUAGE_VERSION.ARKTS_1_2 + ? path.join(pathConfig.declgenOutDir, module.name, 'declgen', 'dynamic', 'decl-fileInfo.json') + : undefined, + declgenV1OutPath: + languageVersion !== LANGUAGE_VERSION.ARKTS_1_1 + ? path.join(pathConfig.declgenOutDir, module.name, 'declgen', 'static') + : undefined, + declgenBridgeCodePath: + languageVersion !== LANGUAGE_VERSION.ARKTS_1_1 + ? path.join(pathConfig.declgenOutDir, module.name, 'declgen', 'static', 'declgenBridgeCode') : undefined, dependencies: dependencies.map((dep) => { const depModule = definedModules.find((m) => m.srcPath === dep); - return depModule!.name; + return depModule ? depModule.name : ''; }) }; addPluginPathConfigs(allBuildConfigs[module.name], module); } - Object.entries(allBuildConfigs).forEach(([key, config]) => { - if (enableDeclgen.get(key) === true) { - config.enableDeclgenEts2Ts = true; - config.declgenV1OutPath = path.join(pathConfig.declgenOutDir, 'dynamic', key, 'declgenV1'); - config.declgenBridgeCodePath = path.join(pathConfig.declgenOutDir, 'dynamic', key, 'declgenBridgeCode'); - if (!fs.existsSync(config.declgenV1OutPath)) { - fs.mkdirSync(config.declgenV1OutPath, { recursive: true }); - } - if (!fs.existsSync(config.declgenBridgeCodePath)) { - fs.mkdirSync(config.declgenBridgeCodePath, { recursive: true }); - } - } - }); return allBuildConfigs; } diff --git a/ets2panda/bindings/src/lsp/index.ts b/ets2panda/bindings/src/lsp/index.ts index effe67f08670ee67f0bfb3e486fd3060c32c7470..8289832bbc58dfe1a42d29ba7ef08a987f0b59cb 100644 --- a/ets2panda/bindings/src/lsp/index.ts +++ b/ets2panda/bindings/src/lsp/index.ts @@ -26,4 +26,4 @@ export { LspSymbolDisplayPart } from './lspNode'; export type { ModuleDescriptor } from './generateBuildConfig'; -export type { PathConfig, TextDocumentChangeInfo } from '../common/types'; +export type { PathConfig, TextDocumentChangeInfo, AstNodeType, NodeInfo } from '../common/types'; diff --git a/ets2panda/bindings/src/lsp/lsp_helper.ts b/ets2panda/bindings/src/lsp/lsp_helper.ts index 3b8999ce90b036de8a8c84427e78ee3b66b876bf..f41f950a0ef63f03057f3cd595360b71feef8d00 100644 --- a/ets2panda/bindings/src/lsp/lsp_helper.ts +++ b/ets2panda/bindings/src/lsp/lsp_helper.ts @@ -60,7 +60,9 @@ import { WorkerInfo, ModuleInfo, PathConfig, - TextDocumentChangeInfo + TextDocumentChangeInfo, + NodeInfo, + AstNodeType } from '../common/types'; import { PluginDriver, PluginHook } from '../common/ui_plugins_driver'; import { ModuleDescriptor, generateBuildConfigs } from './generateBuildConfig'; @@ -72,12 +74,12 @@ import { KInt, KNativePointer, KPointer } from '../common/InteropTypes'; import { passPointerArray } from '../common/private'; import { NativePtrDecoder } from '../common/Platform'; import { Worker as ThreadWorker } from 'worker_threads'; -import { ensurePathExists } from '../common/utils'; +import { ensurePathExists, getFileLanguageVersion } from '../common/utils'; import * as child_process from 'child_process'; -import { DECL_ETS_SUFFIX, DEFAULT_CACHE_DIR, TS_SUFFIX } from '../common/preDefine'; +import { DECL_ETS_SUFFIX, DEFAULT_CACHE_DIR, LANGUAGE_VERSION, TS_SUFFIX } from '../common/preDefine'; import * as crypto from 'crypto'; import * as os from 'os'; -import { changeDeclgenFileExtension, getModuleNameAndPath } from '../common/utils'; +import { changeDeclgenFileExtension } from '../common/utils'; const ets2pandaCmdPrefix = ['-', '--extension', 'ets', '--arktsconfig']; @@ -104,6 +106,7 @@ export class Lsp { private moduleInfos: Record; // Map private pathConfig: PathConfig; private lspDriverHelper = new LspDriverHelper(); + private declFileMap: Record = {}; // Map constructor(pathConfig: PathConfig, getContentCallback?: (filePath: string) => string, modules?: ModuleDescriptor[]) { initBuildEnv(); @@ -123,7 +126,7 @@ export class Lsp { this.moduleInfos = generateArkTsConfigs(this.buildConfigs); this.pathConfig = pathConfig; PluginDriver.getInstance().initPlugins(Object.values(this.buildConfigs)[0]); - this.generateDeclFile(); + this.initDeclFile(); } // Partially update for new file @@ -197,110 +200,42 @@ export class Lsp { } } - generateDeclFile(): void { - for (const [moduleName, buildConfig] of Object.entries(this.buildConfigs)) { - if (!buildConfig.enableDeclgenEts2Ts) { - continue; - } - if (!buildConfig.declgenOutDir || buildConfig.declgenOutDir === '') { - return; + private generateDeclFile(filePath: string): void { + const fileSource = this.getFileSource(filePath); + if (getFileLanguageVersion(fileSource) === LANGUAGE_VERSION.ARKTS_1_2) { + const [cfg, ctx] = this.createContext(filePath); + try { + let moduleInfo = this.moduleInfos[filePath]; + let modulePath: string = path.relative(moduleInfo.moduleRootPath, filePath); + let declEtsOutputPath: string = changeDeclgenFileExtension( + path.join(moduleInfo.declgenV1OutPath!, modulePath), + DECL_ETS_SUFFIX + ); + let etsOutputPath: string = changeDeclgenFileExtension( + path.join(moduleInfo.declgenBridgeCodePath!, modulePath), + TS_SUFFIX + ); + this.declFileMap[declEtsOutputPath] = filePath; + ensurePathExists(declEtsOutputPath); + ensurePathExists(etsOutputPath); + global.es2pandaPublic._GenerateTsDeclarationsFromContext(ctx, declEtsOutputPath, etsOutputPath, 1, 0); + } finally { + this.destroyContext(cfg, ctx); } - buildConfig.compileFiles.forEach((compilefilePath: string) => { - if (!this.moduleInfos.hasOwnProperty(compilefilePath)) { - return; - } - const [cfg, ctx] = this.createContext(compilefilePath); - try { - // declgen file - let moduleInfo = this.moduleInfos[compilefilePath]; - let modulePath: string = path.relative(buildConfig.moduleRootPath, compilefilePath); - let declOut: string = ''; - let declBridgeOut: string = ''; - if (!moduleInfo.declgenV1OutPath) { - declOut = path.join(buildConfig.declgenOutDir, moduleName); - } - if (!moduleInfo.declgenBridgeCodePath) { - declBridgeOut = path.join(buildConfig.declgenOutDir, moduleName); - } - let declEtsOutputPath: string = changeDeclgenFileExtension( - path.join(moduleInfo.declgenV1OutPath ?? declOut, modulePath), - DECL_ETS_SUFFIX - ); - let etsOutputPath: string = changeDeclgenFileExtension( - path.join(moduleInfo.declgenBridgeCodePath ?? declBridgeOut, modulePath), - TS_SUFFIX - ); - ensurePathExists(declEtsOutputPath); - ensurePathExists(etsOutputPath); - global.es2pandaPublic._GenerateTsDeclarationsFromContext(ctx, declEtsOutputPath, etsOutputPath, 1, 0); - } finally { - this.destroyContext(cfg, ctx); - } - }); } } - modifyDeclFile(modifyFilePath: string, arktsConfigFile?: string): void { - // source file - let sourceFilePath = path.resolve(modifyFilePath.valueOf()); - let moduleInfo: ModuleInfo; - if (this.moduleInfos.hasOwnProperty(sourceFilePath)) { - moduleInfo = this.moduleInfos[sourceFilePath]; - } else { - const [newModuleName, newModuleRootPath] = getModuleNameAndPath(modifyFilePath, this.pathConfig.projectPath); - if (newModuleName && newModuleName !== '' && newModuleRootPath && newModuleRootPath !== '') { - moduleInfo = { - packageName: newModuleName, - moduleRootPath: newModuleRootPath, - moduleType: '', - entryFile: '', - arktsConfigFile: arktsConfigFile ?? '', - compileFiles: [], - declgenV1OutPath: '', - declgenBridgeCodePath: '', - staticDepModuleInfos: [], - dynamicDepModuleInfos: [], - language: '' - }; - } else { - return; - } + private initDeclFile(): void { + for (const filePath of Object.keys(this.moduleInfos)) { + this.generateDeclFile(filePath); } - const moduleName = moduleInfo.packageName; - const moduleRootPath = moduleInfo.moduleRootPath; - if (!this.buildConfigs.hasOwnProperty(moduleName)) { - return; - } - const buildConfig = this.buildConfigs[moduleName]; - if (!buildConfig.enableDeclgenEts2Ts) { + } + + updateDeclFile(filePath: string): void { + if (!this.moduleInfos.hasOwnProperty(filePath)) { return; } - const [cfg, ctx] = this.createContext(sourceFilePath); - try { - // declgen file - let declOut: string = ''; - let declBridgeOut: string = ''; - if (!moduleInfo.declgenV1OutPath) { - declOut = path.join(buildConfig.declgenOutDir, moduleName); - } - if (!moduleInfo.declgenBridgeCodePath) { - declBridgeOut = path.join(buildConfig.declgenOutDir, moduleName); - } - let filePathFromModuleRoot: string = path.relative(moduleRootPath, modifyFilePath); - let declEtsOutputPath: string = changeDeclgenFileExtension( - path.join(moduleInfo.declgenV1OutPath ?? declOut, filePathFromModuleRoot), - DECL_ETS_SUFFIX - ); - let etsOutputPath: string = changeDeclgenFileExtension( - path.join(moduleInfo.declgenBridgeCodePath ?? declBridgeOut, filePathFromModuleRoot), - TS_SUFFIX - ); - ensurePathExists(declEtsOutputPath); - ensurePathExists(etsOutputPath); - global.es2pandaPublic._GenerateTsDeclarationsFromContext(ctx, declEtsOutputPath, etsOutputPath, 1, 0); - } finally { - this.destroyContext(cfg, ctx); - } + this.generateDeclFile(filePath); } getOffsetByColAndLine(filename: String, line: number, column: number): number { @@ -314,7 +249,10 @@ export class Lsp { return ptr; } - getDefinitionAtPosition(filename: String, offset: number): LspDefinitionData { + getDefinitionAtPosition(filename: String, offset: number, nodeInfos?: NodeInfo[]): LspDefinitionData { + if (nodeInfos) { + return this.getDefinitionAtPositionByNodeInfos(filename, nodeInfos); + } let ptr: KPointer; const [cfg, ctx] = this.createContext(filename); try { @@ -325,6 +263,26 @@ export class Lsp { return new LspDefinitionData(ptr); } + private getDefinitionAtPositionByNodeInfos(declFilePath: String, nodeInfos: NodeInfo[]): LspDefinitionData { + let ptr: KPointer; + const sourceFilePath = this.declFileMap[declFilePath.valueOf()]; + const [cfg, ctx] = this.createContext(sourceFilePath); + let astNode = global.es2panda._getProgramAst(ctx); + let currentNodeName: string = ''; + try { + nodeInfos.forEach((nodeInfo) => { + if (nodeInfo.kind === AstNodeType.CLASS_DEFINITION) { + currentNodeName = nodeInfo.name; + astNode = global.es2panda._getClassDefinition(astNode, currentNodeName); + } + }); + } finally { + ptr = global.es2panda._getDefinitionDataFromNode(astNode, currentNodeName); + this.destroyContext(cfg, ctx); + } + return new LspDefinitionData(ptr); + } + getSemanticDiagnostics(filename: String): LspDiagsNode { let ptr: KPointer; const [cfg, ctx] = this.createContext(filename); @@ -902,7 +860,14 @@ export class Lsp { } private collectCompileJobs(jobs: Record, isValid: boolean = false): void { - let entryFileList: string[] = Object.keys(this.moduleInfos); + let entryFileList: string[] = Object.keys(this.moduleInfos).filter((file) => { + if (this.moduleInfos[file].language === LANGUAGE_VERSION.ARKTS_1_2) { + return true; + } else if (this.moduleInfos[file].language === LANGUAGE_VERSION.ARKTS_HYBRID) { + const fileSource = this.getFileSource(file); + return getFileLanguageVersion(fileSource) === LANGUAGE_VERSION.ARKTS_1_2; + } + }); this.getFileDependencies(entryFileList, this.fileDependencies); const data = fs.readFileSync(this.fileDependencies, 'utf-8'); let fileDepsInfo: FileDepsInfo = JSON.parse(data) as FileDepsInfo; @@ -1022,6 +987,10 @@ export class Lsp { } }); + if (files.length === 0) { + return; + } + let ets2pandaCmd: string[] = [ '_', '--extension', @@ -1189,7 +1158,9 @@ export class Lsp { this.collectCompileJobs(jobs); this.initGlobalContext(jobs); this.initCompileQueues(jobs, queues); - + if (Object.keys(jobs).length === 0 && queues.length === 0) { + return; + } const processingJobs = new Set(); const workers: ThreadWorker[] = []; await this.invokeWorkers(jobs, queues, processingJobs, workers, numWorkers); diff --git a/ets2panda/lsp/BUILD.gn b/ets2panda/lsp/BUILD.gn index 5dfcae14ba3c2da739c598ba4439349d37f46eed..adc8274c1f9d04ceedf766b5e4eacf3c2946c790 100644 --- a/ets2panda/lsp/BUILD.gn +++ b/ets2panda/lsp/BUILD.gn @@ -70,6 +70,7 @@ ohos_source_set("libes2panda_lsp_static") { "src/get_class_property_info.cpp", "src/get_definition_and_bound_span.cpp", "src/get_name_or_dotted_name_span.cpp", + "src/get_node.cpp", "src/get_safe_delete_info.cpp", "src/inlay_hints.cpp", "src/internal_api.cpp", diff --git a/ets2panda/lsp/CMakeLists.txt b/ets2panda/lsp/CMakeLists.txt index c152fd89c3e3e84c6c4c93d971020c985e06332e..be13c8be66564c391e74a50f6760e18c30409586 100644 --- a/ets2panda/lsp/CMakeLists.txt +++ b/ets2panda/lsp/CMakeLists.txt @@ -124,6 +124,7 @@ set(ES2PANDA_LSP_SRC ./src/register_code_fix/fix_add_function_return_statement.cpp ./src/register_code_fix/ui_plugin_suggest.cpp ./src/get_name_or_dotted_name_span.cpp + ./src/get_node.cpp ) panda_add_library(${LSP_LIB} SHARED ${ES2PANDA_LSP_SRC}) diff --git a/ets2panda/lsp/include/api.h b/ets2panda/lsp/include/api.h index ffe9c4741c60150aa7ff92c1530db8f7921e3c5e..1ba4720c47d2712d92f01edbef89414c573a6948 100644 --- a/ets2panda/lsp/include/api.h +++ b/ets2panda/lsp/include/api.h @@ -552,6 +552,9 @@ typedef struct LSPAPI { CombinedCodeActionsInfo (*getCombinedCodeFix)(const char *fileName, const std::string &fixId, CodeFixOptions &codeFixOptions); TextSpan *(*GetNameOrDottedNameSpan)(es2panda_Context *context, int startPos); + es2panda_AstNode *(*getProgramAst)(es2panda_Context *context); + es2panda_AstNode *(*getClassDefinition)(es2panda_AstNode *astNode, const std::string &nodeName); + DefinitionInfo (*getDefinitionDataFromNode)(es2panda_AstNode *astNode, const std::string &nodeName); } LSPAPI; CAPI_EXPORT LSPAPI const *GetImpl(); // NOLINTEND diff --git a/ets2panda/lsp/include/get_node.h b/ets2panda/lsp/include/get_node.h new file mode 100644 index 0000000000000000000000000000000000000000..022e69e2b70f508b47787ca79e32a63084d1cdc6 --- /dev/null +++ b/ets2panda/lsp/include/get_node.h @@ -0,0 +1,26 @@ +/* + * 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. + */ +#ifndef ES2PANDA_LSP_GET_NODE_H +#define ES2PANDA_LSP_GET_NODE_H + +#include +#include "public/es2panda_lib.h" + +namespace ark::es2panda::lsp { +es2panda_AstNode *GetProgramAstImpl(es2panda_Context *context); +es2panda_AstNode *GetClassDefinitionImpl(es2panda_AstNode *astNode, const std::string &nodeName); +} // 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 129747640c2e1bb602be2ebb85ce7a70e9e30f09..4ebea274a39a5af46c75cb14330209f2df484428 100644 --- a/ets2panda/lsp/src/api.cpp +++ b/ets2panda/lsp/src/api.cpp @@ -18,8 +18,8 @@ #include #include #include "class_hierarchy.h" +#include "get_node.h" #include "lsp/include/organize_imports.h" -#include "compiler/lowering/util.h" #include "get_safe_delete_info.h" #include "internal_api.h" #include "ir/astNode.h" @@ -461,6 +461,44 @@ TextSpan *GetNameOrDottedNameSpan(es2panda_Context *context, int startPos) return result; } +es2panda_AstNode *GetProgramAst(es2panda_Context *context) +{ + return GetProgramAstImpl(context); +} + +es2panda_AstNode *GetClassDefinition(es2panda_AstNode *astNode, const std::string &nodeName) +{ + return GetClassDefinitionImpl(astNode, nodeName); +} + +DefinitionInfo GetDefinitionDataFromNode(es2panda_AstNode *astNode, const std::string &nodeName) +{ + DefinitionInfo result; + if (astNode == nullptr) { + return result; + } + auto node = reinterpret_cast(astNode); + auto targetNode = node->FindChild([&nodeName](ir::AstNode *childNode) { + return childNode->IsIdentifier() && std::string(childNode->AsIdentifier()->Name()) == nodeName; + }); + std::string filePath; + while (node != nullptr) { + if (node->Range().start.Program() != nullptr) { + filePath = std::string(node->Range().start.Program()->SourceFile().GetAbsolutePath().Utf8()); + break; + } + if (node->IsETSModule()) { + filePath = std::string(node->AsETSModule()->Program()->SourceFilePath()); + break; + } + node = node->Parent(); + } + if (targetNode != nullptr) { + result = {filePath, targetNode->Start().index, targetNode->End().index - targetNode->Start().index}; + } + return result; +} + LSPAPI g_lspImpl = {GetDefinitionAtPosition, GetApplicableRefactors, GetImplementationAtPosition, @@ -501,7 +539,10 @@ LSPAPI g_lspImpl = {GetDefinitionAtPosition, GetOffsetByColAndLine, GetCodeFixesAtPosition, GetCombinedCodeFix, - GetNameOrDottedNameSpan}; + GetNameOrDottedNameSpan, + GetProgramAst, + GetClassDefinition, + GetDefinitionDataFromNode}; } // namespace ark::es2panda::lsp CAPI_EXPORT LSPAPI const *GetImpl() diff --git a/ets2panda/lsp/src/get_node.cpp b/ets2panda/lsp/src/get_node.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e5b2eb5d114cfb5f6e2c22dd12e2e53abac83730 --- /dev/null +++ b/ets2panda/lsp/src/get_node.cpp @@ -0,0 +1,43 @@ +/* + * 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 "get_node.h" +#include "public/es2panda_lib.h" +#include "public/public.h" + +namespace ark::es2panda::lsp { +es2panda_AstNode *GetProgramAstImpl(es2panda_Context *context) +{ + if (context == nullptr) { + return nullptr; + } + auto ctx = reinterpret_cast(context); + return reinterpret_cast(ctx->parserProgram->Ast()); +} + +es2panda_AstNode *GetClassDefinitionImpl(es2panda_AstNode *astNode, const std::string &nodeName) +{ + if (astNode == nullptr) { + return nullptr; + } + auto ast = reinterpret_cast(astNode); + auto targetNode = ast->FindChild([&nodeName](ir::AstNode *childNode) { + return childNode->IsClassDefinition() && + std::string(childNode->AsClassDefinition()->Ident()->Name()) == nodeName; + }); + return reinterpret_cast(targetNode); +} + +} // namespace ark::es2panda::lsp \ No newline at end of file diff --git a/ets2panda/test/unit/lsp/CMakeLists.txt b/ets2panda/test/unit/lsp/CMakeLists.txt index acbc0633771ee211723ee300f9d7d552576e06ed..88c5e0dbb6d98d231e332b65d0be7e3885594c57 100644 --- a/ets2panda/test/unit/lsp/CMakeLists.txt +++ b/ets2panda/test/unit/lsp/CMakeLists.txt @@ -214,7 +214,7 @@ ets2panda_add_gtest(lsp_api_test_find_rename_locations CPP_SOURCES ets2panda_add_gtest(lsp_api_test_fix_expected_comma CPP_SOURCES fix_expected_comma_test.cpp - ) +) ets2panda_add_gtest(lsp_api_test_change_tracker CPP_SOURCES change_tracker_test.cpp @@ -282,10 +282,14 @@ ets2panda_add_gtest(lsp_api_test_forgotten_this_property_access CPP_SOURCES forgotten_this_property_access_test.cpp ) - ets2panda_add_gtest(lsp_api_test_remove_accidental_call_parentheses CPP_SOURCES +ets2panda_add_gtest(lsp_api_test_remove_accidental_call_parentheses CPP_SOURCES remove_accidental_call_parentheses_test.cpp - ) +) ets2panda_add_gtest(lsp_api_add_missing_new_operator CPP_SOURCES add_missing_new_operator_test.cpp -) \ No newline at end of file +) + +ets2panda_add_gtest(lsp_api_get_node_test CPP_SOURCES + get_node_test.cpp +) diff --git a/ets2panda/test/unit/lsp/get_node_test.cpp b/ets2panda/test/unit/lsp/get_node_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3af59910047bbe59f043392d8e007817ae933b32 --- /dev/null +++ b/ets2panda/test/unit/lsp/get_node_test.cpp @@ -0,0 +1,88 @@ +/** + * 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 "ir/astNode.h" +#include "lsp/include/api.h" +#include "lsp_api_test.h" +#include "public/es2panda_lib.h" +#include "public/public.h" +#include + +namespace { +using ark::es2panda::lsp::Initializer; + +class LspGetNodeTests : public LSPAPITests { +protected: + static void SetUpTestSuite() + { + initializer_ = new Initializer(); + GenerateContexts(*initializer_); + } + + static void TearDownTestSuite() + { + initializer_->DestroyContext(contexts_); + delete initializer_; + initializer_ = nullptr; + } + static void GenerateContexts(Initializer &initializer) + { + contexts_ = initializer.CreateContext("GetNodeTest.ets", ES2PANDA_STATE_CHECKED, R"(class Foo { + Foo = 1; +})"); + } + // NOLINTBEGIN(fuchsia-statically-constructed-objects, cert-err58-cpp) + static inline es2panda_Context *contexts_ = nullptr; + static inline Initializer *initializer_ = nullptr; + // NOLINTEND(fuchsia-statically-constructed-objects, cert-err58-cpp) +}; + +TEST_F(LspGetNodeTests, GetProgramAst1) +{ + auto ctx = reinterpret_cast(contexts_); + auto expectedAst = ctx->parserProgram->Ast(); + LSPAPI const *lspApi = GetImpl(); + auto ast = lspApi->getProgramAst(contexts_); + ASSERT_EQ(reinterpret_cast(ast), expectedAst); +} + +TEST_F(LspGetNodeTests, GetProgramAst2) +{ + LSPAPI const *lspApi = GetImpl(); + auto ast = lspApi->getProgramAst(nullptr); + ASSERT_EQ(ast, nullptr); +} + +TEST_F(LspGetNodeTests, GetClassDefinition1) +{ + auto ctx = reinterpret_cast(contexts_); + auto ast = ctx->parserProgram->Ast(); + LSPAPI const *lspApi = GetImpl(); + const std::string nodeName = "Foo"; + auto res = lspApi->getClassDefinition(reinterpret_cast(ast), nodeName); + ASSERT_TRUE(reinterpret_cast(res)->IsClassDefinition()); + ASSERT_EQ(reinterpret_cast(res)->AsClassDefinition()->Ident()->Name(), + nodeName.data()); +} + +TEST_F(LspGetNodeTests, GetClassDefinition2) +{ + LSPAPI const *lspApi = GetImpl(); + const std::string nodeName = "Foo"; + auto res = lspApi->getClassDefinition(nullptr, nodeName); + ASSERT_EQ(res, nullptr); +} + +} // namespace \ No newline at end of file