UNPKG

@arkts/shared

Version:
376 lines (372 loc) 11.8 kB
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 };