* Copyright (c) 2025-2026 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 fs from 'fs';
import JSON5 from 'json5';
import { Lsp, ModuleDescriptor, PathConfig } from '../src';
import path from 'path';
export const DEFAULT_PATH_CONFIG: PathConfig = {
buildSdkPath: path.resolve('test', 'ets', 'static'),
projectPath: path.resolve('test', 'testcases'),
declgenOutDir: ''
};
export const UI_PLUGIN_LIST: string[] = ['ui-syntax-plugins', 'ui-plugins', 'memo-plugins'];
const DEFAULT_PLUGIN_LIST: string[] = [];
const STDLIB_PATH_KEYS = ['std', 'escompat', 'arkruntime'];
interface ParsedArkTSConfigObject {
compilerOptions?: {
baseUrl?: string;
paths?: Record<string, unknown>;
};
}
function normalizePathEntries(entries: string[], baseUrl?: string): string[] {
return entries.map((entry) => {
if (path.isAbsolute(entry)) {
return path.resolve(entry);
}
if (baseUrl) {
return path.resolve(baseUrl, entry);
}
return path.resolve(entry);
});
}
function getGlobalArkTSPaths(): Record<string, string[]> {
const globalArkTSConfigPath = process.env.ARKTSCONFIG;
if (!globalArkTSConfigPath || !fs.existsSync(globalArkTSConfigPath)) {
return {};
}
try {
const rawContent = fs.readFileSync(globalArkTSConfigPath, 'utf-8');
const parsed = JSON5.parse(rawContent) as ParsedArkTSConfigObject;
const baseUrl = parsed.compilerOptions?.baseUrl;
const rawPaths = parsed.compilerOptions?.paths ?? {};
const normalizedPaths: Record<string, string[]> = {};
Object.entries(rawPaths).forEach(([key, value]) => {
if (!Array.isArray(value)) {
return;
}
const stringEntries = value.filter((item): item is string => typeof item === 'string');
if (stringEntries.length === 0) {
return;
}
normalizedPaths[key] = normalizePathEntries(stringEntries, baseUrl);
});
return normalizedPaths;
} catch {
return {};
}
}
function hasUsablePathEntries(entries?: string[]): boolean {
return Array.isArray(entries) && entries.length > 0 && entries.some((entry) => fs.existsSync(entry));
}
function patchGeneratedArkTsConfigsForTest(lsp: Lsp): void {
const globalPaths = getGlobalArkTSPaths();
if (Object.keys(globalPaths).length === 0) {
return;
}
const moduleInfos = (lsp as any).moduleInfos as Record<string, { arktsConfigFile?: string }> | undefined;
if (!moduleInfos) {
return;
}
const arktsConfigFiles = Array.from(
new Set(
Object.values(moduleInfos)
.map((moduleInfo) => moduleInfo?.arktsConfigFile)
.filter((filePath): filePath is string => typeof filePath === 'string' && fs.existsSync(filePath))
)
);
arktsConfigFiles.forEach((arktsConfigFile) => {
try {
const rawContent = fs.readFileSync(arktsConfigFile, 'utf-8');
const parsed = JSON5.parse(rawContent) as ParsedArkTSConfigObject;
const compilerOptions = parsed.compilerOptions ?? {};
const pathSection = (compilerOptions.paths ?? {}) as Record<string, string[]>;
Object.entries(globalPaths).forEach(([key, value]) => {
if (!hasUsablePathEntries(pathSection[key]) && hasUsablePathEntries(value)) {
pathSection[key] = value;
}
});
STDLIB_PATH_KEYS.forEach((key) => {
if (!hasUsablePathEntries(pathSection[key])) {
delete pathSection[key];
}
});
compilerOptions.paths = pathSection;
parsed.compilerOptions = compilerOptions;
fs.writeFileSync(arktsConfigFile, JSON.stringify(parsed, null, 2), 'utf-8');
} catch {
}
});
}
function initLspEnv(pathConfig: PathConfig): void {
const bindingsPath =
process.env.BINDINGS_PATH || path.join(pathConfig.buildSdkPath, 'build-tools', 'bindings');
const pandaLibPath =
process.env.PANDA_LIB_PATH || path.join(pathConfig.buildSdkPath, 'build-tools', 'ets2panda', 'lib');
const pandaBinPath =
process.env.PANDA_BIN_PATH || path.join(pathConfig.buildSdkPath, 'build-tools', 'ets2panda', 'bin');
process.env.BINDINGS_PATH = bindingsPath;
process.env.PANDA_LIB_PATH = pandaLibPath;
process.env.PANDA_BIN_PATH = pandaBinPath;
}
export function getLsp(moduleName: string, plugins: string[] = DEFAULT_PLUGIN_LIST): Lsp {
initLspEnv(DEFAULT_PATH_CONFIG);
return new Lsp(
DEFAULT_PATH_CONFIG,
undefined,
[{ name: moduleName, moduleType: 'har', srcPath: path.resolve('test/testcases/', moduleName) }],
plugins
);
}
export function getLspWithUi(moduleName: string): Lsp {
return getLsp(moduleName, UI_PLUGIN_LIST);
}
export function getMultiModuleLsp(
projectName: string,
modules: ModuleDescriptor[],
plugins: string[] = DEFAULT_PLUGIN_LIST,
pathConfigOverrides: Partial<PathConfig> = {}
): Lsp {
const projectPath = path.resolve('test/testcases/', projectName);
const pathConfig: PathConfig = {
...DEFAULT_PATH_CONFIG,
projectPath,
...pathConfigOverrides
};
initLspEnv(pathConfig);
const resolvedModules = modules.map((module) => ({
...module,
srcPath: path.resolve(projectPath, module.srcPath)
}));
const lsp = new Lsp(pathConfig, undefined, resolvedModules, plugins);
patchGeneratedArkTsConfigsForTest(lsp);
return lsp;
}
export function getRealPath(moduleName: string, fileName: string): string {
return path.resolve('test/testcases/', moduleName, fileName);
}
export function getSDKPath(fileName: string): string {
return path.resolve('test/ets/static/api/', fileName);
}