@arkts/shared
Version:
ArkTS common utilities package.
376 lines (372 loc) • 11.8 kB
JavaScript
import { t as LanguageServerLogger } from "./lsp-logger-u6DsbrTb.mjs";
import { createRequire } from "node:module";
import * as fs from "node:fs";
import * as path from "node:path";
import { URI } from "vscode-uri";
//#region src/resource-resolver.ts
/**
* 资源类型枚举
*/
let ResourceType = /* @__PURE__ */ function(ResourceType$1) {
ResourceType$1["Color"] = "color";
ResourceType$1["String"] = "string";
ResourceType$1["Float"] = "float";
ResourceType$1["Boolean"] = "boolean";
ResourceType$1["Integer"] = "integer";
ResourceType$1["Media"] = "media";
ResourceType$1["Profile"] = "profile";
ResourceType$1["Symbol"] = "symbol";
ResourceType$1["Plural"] = "plural";
return ResourceType$1;
}({});
/**
* 解析 $r() 资源引用字符串
* @param resourceRef 资源引用字符串,如 'app.color.bg_color' 或 'sys.string.title'
* @returns 解析结果
*/
function parseResourceReference(resourceRef) {
const match = resourceRef.replace(/^['"`]|['"`]$/g, "").match(/^(app|sys)\.(\w+)\.(\w+)$/);
if (!match) return null;
const [, scope, type, name] = match;
if (!Object.values(ResourceType).includes(type)) return null;
return {
scope,
type,
name,
raw: resourceRef
};
}
/**
* 构建资源文件路径
* @param projectRoot 项目根路径
* @param moduleName 模块名(如 'entry', 'sampleLibrary')
* @param resourceType 资源类型
* @returns 资源文件路径
*/
function buildResourceFilePath(projectRoot, moduleName, resourceType) {
const basePath = path.join(projectRoot, moduleName, "src", "main", "resources", "base");
if (resourceType === ResourceType.Media) return path.join(basePath, "media");
else return path.join(basePath, "element", `${resourceType}.json`);
}
/**
* 资源解析器类
*/
var ResourceResolver = class {
projectRoot;
resourceIndex = /* @__PURE__ */ new Map();
sdkPath;
getSdkPath() {
return this.sdkPath;
}
getProjectRoot() {
return this.projectRoot;
}
constructor(logger, projectRoot, sdkPath) {
this.logger = logger;
this.projectRoot = projectRoot;
this.sdkPath = sdkPath;
}
/**
* 查找项目中的所有模块
*/
async findModules() {
const modules = [];
try {
await this.findModulesRecursive(this.projectRoot, "", modules, 3);
} catch (error) {
console.error("Failed to find modules:", error);
}
return modules;
}
/**
* 递归查找模块
*/
async findModulesRecursive(currentPath, relativePath, modules, maxDepth) {
if (maxDepth <= 0) return;
try {
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
for (const entry of entries) if (entry.isDirectory() && !this.shouldSkipDirectory(entry.name)) {
const fullPath = path.join(currentPath, entry.name);
const moduleRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name;
const moduleJsonPath = path.join(fullPath, "src", "main", "module.json5");
if (fs.existsSync(moduleJsonPath)) {
this.logger.getConsola().log(`Found module: ${moduleRelativePath} at ${fullPath}`);
modules.push(moduleRelativePath);
} else await this.findModulesRecursive(fullPath, moduleRelativePath, modules, maxDepth - 1);
}
} catch (error) {
this.logger.getConsola().error(`Cannot access directory ${currentPath}:`, error);
}
}
/**
* 判断是否应该跳过某个目录
*/
shouldSkipDirectory(dirName) {
return [
"node_modules",
".git",
".vscode",
".idea",
"dist",
"build",
"out",
".changeset",
".github",
".qoder",
"screenshots",
"hvigor",
"autosign"
].includes(dirName) || dirName.startsWith(".");
}
/**
* 构建资源索引
*/
async buildIndex() {
this.resourceIndex.clear();
this.logger.getConsola().log(`Building resource index for project: ${this.projectRoot}`);
const modules = await this.findModules();
this.logger.getConsola().log(`Found ${modules.length} modules:`, modules);
for (const moduleName of modules) {
this.logger.getConsola().log(`Indexing module: ${moduleName}`);
await this.indexModule(moduleName);
}
await this.indexSystemResources();
this.logger.getConsola().log(`Resource index built with ${this.resourceIndex.size} resources`);
}
/**
* 为特定模块构建索引
*/
async indexModule(moduleName) {
const basePath = path.join(this.projectRoot, moduleName, "src", "main", "resources", "base");
await this.indexElementResources(basePath, moduleName);
await this.indexMediaResources(basePath, moduleName);
}
/**
* 索引元素资源(JSON文件)
*/
async indexElementResources(basePath, moduleName) {
const elementPath = path.join(basePath, "element");
if (!fs.existsSync(elementPath)) return;
try {
const files = await fs.promises.readdir(elementPath);
for (const file of files) if (file.endsWith(".json")) {
const resourceType = path.basename(file, ".json");
if (Object.values(ResourceType).includes(resourceType)) await this.indexJsonResource(path.join(elementPath, file), resourceType);
}
} catch (error) {
this.logger.getConsola().error(`Failed to index element resources for ${moduleName}:`, error);
}
}
/**
* 索引JSON资源文件
*/
async indexJsonResource(filePath, resourceType) {
try {
const content = await fs.promises.readFile(filePath, "utf-8");
const json = JSON.parse(content);
const lines = content.split("\n");
if (json[resourceType] && Array.isArray(json[resourceType])) {
for (const item of json[resourceType]) if (item.name && item.value) {
const range = this.findJsonItemRange(lines, item.name);
const reference = {
scope: "app",
type: resourceType,
name: item.name,
raw: `app.${resourceType}.${item.name}`
};
const location = {
uri: URI.file(filePath).toString(),
range,
value: item.value
};
const key = `${reference.scope}.${reference.type}.${reference.name}`;
this.resourceIndex.set(key, {
reference,
location
});
}
}
} catch (error) {
this.logger.getConsola().error(`Failed to index JSON resource ${filePath}:`, error);
}
}
/**
* 索引系统资源(sys 资源)
*/
async indexSystemResources() {
if (!this.sdkPath) {
this.logger.getConsola().log("SDK path not provided, skipping system resource indexing");
return;
}
const sysResourcePath = path.join(this.sdkPath, "ets", "build-tools", "ets-loader", "sysResource.js");
if (!fs.existsSync(sysResourcePath)) {
this.logger.getConsola().log(`System resource file not found: ${sysResourcePath}`);
return;
}
try {
this.logger.getConsola().log(`Indexing system resources from: ${sysResourcePath}`);
const sysResources = createRequire("/")(sysResourcePath);
if (typeof sysResources !== "object" || !sysResources || !("sys" in sysResources) || typeof sysResources.sys !== "object" || !sysResources.sys) {
this.logger.getConsola().log(`System resource file only support export an 'sys' object. The file must start with 'module.exports.sys = {}'.`);
return;
}
this.indexSysResourceObject(sysResources.sys, sysResourcePath);
this.logger.getConsola().log("System resources indexed successfully");
} catch (error) {
this.logger.getConsola().error("Failed to index system resources:", error);
}
}
/**
* 索引系统资源对象
*/
indexSysResourceObject(sysResources, filePath) {
const lines = fs.readFileSync(filePath, "utf-8").split("\n");
for (const [resourceType, resources] of Object.entries(sysResources)) if (typeof resources === "object" && resources !== null) for (const [resourceName, resourceId] of Object.entries(resources)) {
const range = this.findSysResourceItemRange(lines, resourceName, resourceType);
const reference = {
scope: "sys",
type: resourceType,
name: resourceName,
raw: `sys.${resourceType}.${resourceName}`
};
const location = {
uri: URI.file(filePath).toString(),
range,
value: `System Resource ID: ${resourceId}`
};
const key = `${reference.scope}.${reference.type}.${reference.name}`;
this.resourceIndex.set(key, {
reference,
location
});
}
}
/**
* 在系统资源文件中查找指定资源的位置
*/
findSysResourceItemRange(lines, resourceName, resourceType) {
let inResourceTypeSection = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.includes(`${resourceType}:`)) {
inResourceTypeSection = true;
continue;
}
if (inResourceTypeSection) {
if (line.includes("}") && !line.includes(resourceName)) {
const nextLine = i + 1 < lines.length ? lines[i + 1].trim() : "";
if (nextLine === "" || nextLine.includes(":") || nextLine === "}") {
inResourceTypeSection = false;
continue;
}
}
if (line.includes(resourceName)) {
const start = lines[i].indexOf(resourceName);
if (start >= 0) return {
start: {
line: i,
character: start
},
end: {
line: i,
character: start + resourceName.length
}
};
}
}
}
}
/**
* 索引媒体资源
*/
async indexMediaResources(basePath, moduleName) {
const mediaPath = path.join(basePath, "media");
if (!fs.existsSync(mediaPath)) return;
try {
const files = await fs.promises.readdir(mediaPath);
for (const file of files) {
const filePath = path.join(mediaPath, file);
if ((await fs.promises.stat(filePath)).isFile()) {
const name = path.basename(file, path.extname(file));
const reference = {
scope: "app",
type: ResourceType.Media,
name,
raw: `app.media.${name}`
};
const location = {
uri: URI.file(filePath).toString(),
value: file
};
const key = `${reference.scope}.${reference.type}.${reference.name}`;
this.resourceIndex.set(key, {
reference,
location
});
}
}
} catch (error) {
console.error(`Failed to index media resources for ${moduleName}:`, error);
}
}
/**
* 在JSON文件中查找指定名称的项的位置
*/
findJsonItemRange(lines, itemName) {
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.match(/* @__PURE__ */ new RegExp(`"name"\\s*:\\s*"${itemName}"`))) {
const start = line.indexOf(`"${itemName}"`);
return {
start: {
line: i,
character: start
},
end: {
line: i,
character: start + itemName.length + 2
}
};
}
}
}
/**
* 解析资源引用并返回位置
*/
async resolveResourceReference(resourceRef) {
const parsed = parseResourceReference(resourceRef);
if (!parsed) return null;
const key = `${parsed.scope}.${parsed.type}.${parsed.name}`;
return this.resourceIndex.get(key)?.location || null;
}
/**
* 获取所有已索引的资源
*/
getAllResources() {
return Array.from(this.resourceIndex.values());
}
/**
* 清除索引
*/
clearIndex() {
this.resourceIndex.clear();
}
};
//#endregion
//#region src/sys-resource.ts
let SysResource;
(function(_SysResource) {
function is(value) {
return typeof value === "object" && value !== null && "sys" in value && typeof value.sys === "object" && value.sys !== null && Object.entries(value.sys).every(([key, value$1]) => typeof key === "string" && typeof value$1 === "object" && value$1 !== null);
}
_SysResource.is = is;
function toEtsFormat(sysResource) {
return [...new Set(Object.entries(sysResource.sys).flatMap(([key, value]) => Object.entries(value).map(([name]) => `sys.${key}.${name}`)))];
}
_SysResource.toEtsFormat = toEtsFormat;
})(SysResource || (SysResource = {}));
//#endregion
//#region src/index.ts
function typeAssert(_value) {}
//#endregion
export { LanguageServerLogger, ResourceResolver, ResourceType, SysResource, buildResourceFilePath, parseResourceReference, typeAssert };