chore: remove a fallback path and simplify the entire code

This commit is contained in:
anatawa12 2025-04-06 23:59:31 +09:00
parent 7865474de5
commit 8cb334c91d
No known key found for this signature in database
GPG Key ID: 9CA909848B8E4EA6
1 changed files with 223 additions and 301 deletions

View File

@ -24,7 +24,6 @@ import { hash, toBase62 } from '../vite.config';
import { minimatch } from 'minimatch'; import { minimatch } from 'minimatch';
import { import {
type AttributeNode, type AttributeNode,
type CompoundExpressionNode,
type DirectiveNode, type DirectiveNode,
type ElementNode, type ElementNode,
ElementTypes, ElementTypes,
@ -89,6 +88,64 @@ function initLogger(options: Options) {
} }
} }
//region AST Utility
type WalkVueNode = RootNode | TemplateChildNode | SimpleExpressionNode;
/**
* Walks the Vue AST.
* @param nodes
* @param context The context value passed to callback. you can update context for children by returning value in callback
* @param callback Returns false if you don't want to walk inner tree
*/
function walkVueElements<C extends {} | null>(nodes: WalkVueNode[], context: C, callback: (node: ElementNode, context: C) => C | undefined | void | false): void {
for (const node of nodes) {
if (node.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error("Unexpected COMPOUND_EXPRESSION");
if (node.type === NodeTypes.ELEMENT) {
const result = callback(node, context);
if (result === false) return;
if (result !== undefined) context = result;
}
if ('children' in node) {
walkVueElements(node.children, context, callback);
}
}
}
function findAttribute(props: Array<AttributeNode | DirectiveNode>, name: string): AttributeNode | DirectiveNode | null {
for (const prop of props) {
switch (prop.type) {
case NodeTypes.ATTRIBUTE:
if (prop.name === name) {
return prop;
}
break;
case NodeTypes.DIRECTIVE:
if (prop.name === 'bind' && prop.arg && 'content' in prop.arg && prop.arg.content === name) {
return prop;
}
break;
}
}
return null;
}
function findEndOfStartTagAttributes(node: ElementNode): number {
if (node.children.length > 0) {
// 子要素がある場合、最初の子要素の開始位置を基準にする
const nodeStart = node.loc.start.offset;
const firstChildStart = node.children[0].loc.start.offset;
const endOfStartTag = node.loc.source.lastIndexOf('>', firstChildStart - nodeStart);
if (endOfStartTag === -1) throw new Error("Bug: Failed to find end of start tag");
return nodeStart + endOfStartTag;
} else {
// 子要素がない場合、自身の終了位置から逆算
return node.isSelfClosing ? node.loc.end.offset - 1 : node.loc.end.offset;
}
}
//endregion
/** /**
* TypeScriptコード生成 * TypeScriptコード生成
*/ */
@ -110,6 +167,8 @@ function customStringify(obj: unknown): string {
}); });
} }
// region extractElementText
/** /**
* *
*/ */
@ -162,6 +221,9 @@ function extractElementText2Inner(node: TemplateChildNode, processingNodeName: s
} }
} }
// endregion
// region extractUsageInfoFromTemplateAst
/** /**
* SearchLabelとSearchKeywordを探して抽出する関数 * SearchLabelとSearchKeywordを探して抽出する関数
@ -172,48 +234,72 @@ function extractLabelsAndKeywords(nodes: TemplateChildNode[]): { label: string |
logger.info(`Extracting labels and keywords from ${nodes.length} nodes`); logger.info(`Extracting labels and keywords from ${nodes.length} nodes`);
// 再帰的にSearchLabelとSearchKeywordを探索ネストされたSearchMarkerは処理しない walkVueElements(nodes, null, (node) => {
function findComponents(nodes: TemplateChildNode[]) { switch (node.tag) {
for (const node of nodes) { case 'SearchMarker':
if (node.type === NodeTypes.ELEMENT) { return false; // SearchMarkerはスキップ
logger.info(`Checking element: ${node.tag}`); case 'SearchLabel':
if (label !== undefined) {
// SearchMarkerの場合は、その子要素は別スコープなのでスキップ logger.warn(`Duplicate SearchLabel found, ignoring the second one at ${node.loc.start.line}`);
if (node.tag === 'SearchMarker') { break; // 2つ目のSearchLabelは無視
logger.info(`Found nested SearchMarker at ${node.loc.start.line} - skipping its content to maintain scope isolation`);
continue; // このSearchMarkerの中身は処理しない (スコープ分離)
} }
switch (node.tag) { label = extractElementText(node);
case 'SearchLabel': return;
if (label !== undefined) { case 'SearchKeyword':
logger.warn(`Duplicate SearchLabel found, ignoring the second one at ${node.loc.start.line}`); const content = extractElementText(node);
break; // 2つ目のSearchLabelは無視 if (content) {
} keywords.push(content);
label = extractElementText(node);
break;
case 'SearchKeyword':
const content = extractElementText(node);
if (content) {
keywords.push(content);
}
break;
} }
return;
// 子要素を再帰的に調査ただしSearchMarkerは除外
findComponents(node.children);
}
} }
}
findComponents(nodes); return;
});
// デバッグ情報 // デバッグ情報
logger.info(`Extraction completed: label=${label}, keywords=[${keywords.join(', ')}]`); logger.info(`Extraction completed: label=${label}, keywords=[${keywords.join(', ')}]`);
return { label: label ?? null, keywords }; return { label: label ?? null, keywords };
} }
function getStringProp(attr: AttributeNode | DirectiveNode | null): string | null {
switch (attr?.type) {
case null:
case undefined:
return null;
case NodeTypes.ATTRIBUTE:
return attr.value?.content ?? null;
case NodeTypes.DIRECTIVE:
if (attr.exp == null) return null;
if (attr.exp.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error('Unexpected COMPOUND_EXPRESSION');
const value = evalExpression(attr.exp.content ?? '');
if (typeof value !== 'string') {
logger.error(`Expected string value, got ${typeof value} at line ${attr.loc.start.line}`);
return null;
}
return value;
}
}
function getStringArrayProp(attr: AttributeNode | DirectiveNode | null): string[] | null {
switch (attr?.type) {
case null:
case undefined:
return null;
case NodeTypes.ATTRIBUTE:
logger.error(`Expected directive, got attribute at line ${attr.loc.start.line}`);
return null;
case NodeTypes.DIRECTIVE:
if (attr.exp == null) return null;
if (attr.exp.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error('Unexpected COMPOUND_EXPRESSION');
const value = evalExpression(attr.exp.content ?? '');
if (!Array.isArray(value) || !value.every(x => typeof x === 'string')) {
logger.error(`Expected string array value, got ${typeof value} at line ${attr.loc.start.line}`);
return null;
}
return value;
}
}
function extractUsageInfoFromTemplateAst( function extractUsageInfoFromTemplateAst(
templateAst: RootNode | undefined, templateAst: RootNode | undefined,
@ -222,124 +308,73 @@ function extractUsageInfoFromTemplateAst(
const allMarkers: SearchIndexItem[] = []; const allMarkers: SearchIndexItem[] = [];
const markerMap = new Map<string, SearchIndexItem>(); const markerMap = new Map<string, SearchIndexItem>();
if (!templateAst) return allMarkers; if (!templateAst) return allMarkers;
// マーカーの基本情報を収集 walkVueElements<string | null>([templateAst], null, (node, parentId) => {
function collectMarkers(node: TemplateChildNode | RootNode, parentId: string | null = null) { if (node.tag !== 'SearchMarker') {
if (node.type === NodeTypes.ELEMENT && node.tag === 'SearchMarker') { return;
// マーカーID取得
const markerIdProp = node.props?.find(p => p.name === 'markerId');
const markerId = markerIdProp?.type == NodeTypes.ATTRIBUTE ? markerIdProp.value?.content : null;
// SearchMarkerにマーカーIDがない場合はエラー
if (markerId == null) {
logger.error(`Marker ID not found for node: ${JSON.stringify(node)}`);
throw new Error(`Marker ID not found in file ${id}`);
}
// マーカー基本情報
const markerInfo: SearchIndexItem = {
id: markerId,
parentId: parentId ?? undefined,
label: '', // デフォルト値
keywords: [],
};
// 静的プロパティを取得
if (node.props && Array.isArray(node.props)) {
for (const prop of node.props) {
if (prop.type === NodeTypes.ATTRIBUTE && prop.name && prop.name !== 'markerId') {
if (prop.name === 'path') markerInfo.path = prop.value?.content || '';
else if (prop.name === 'icon') markerInfo.icon = prop.value?.content || '';
else if (prop.name === 'label') markerInfo.label = prop.value?.content || '';
}
}
}
// バインドプロパティを取得
const bindings = extractNodeBindings(node);
const assertString = (value: unknown, key: string): string => {
if (typeof value !== 'string') throw new Error(`Invalid type for ${key} in marker ${markerId}: expected string, got ${typeof value}`);
return value;
}
const assertStringArray = (value: unknown, key: string): string[] => {
if (!Array.isArray(value) || !value.every(x => typeof x === 'string')) throw new Error(`Invalid type for ${key} in marker ${markerId}: expected string array`);
return value;
}
if (bindings.path) markerInfo.path = assertString(bindings.path, 'path');
if (bindings.icon) markerInfo.icon = assertString(bindings.icon, 'icon');
if (bindings.label) markerInfo.label = assertString(bindings.label, 'label');
if (bindings.inlining) {
markerInfo.inlining = assertStringArray(bindings.inlining, 'inlining');
logger.info(`Added inlining ${JSON.stringify(bindings.inlining)} to marker ${markerId}`);
}
if (bindings.keywords) {
markerInfo.keywords = assertStringArray(bindings.keywords, 'keywords');
}
//pathがない場合はファイルパスを設定
if (markerInfo.path == null && parentId == null) {
markerInfo.path = id.match(/.*(\/(admin|settings)\/[^\/]+)\.vue$/)?.[1];
}
// SearchLabelとSearchKeywordを抽出 (AST全体を探索)
{
const extracted = extractLabelsAndKeywords(node.children);
if (extracted.label && markerInfo.label) logger.warn(`Duplicate label found for ${markerId} at ${id}:${node.loc.start.line}`);
markerInfo.label = extracted.label ?? markerInfo.label ?? '';
markerInfo.keywords = [...extracted.keywords, ...markerInfo.keywords];
}
if (!markerInfo.label) {
logger.warn(`No label found for ${markerId} at ${id}:${node.loc.start.line}`);
}
// マーカーを登録
markerMap.set(markerId, markerInfo);
allMarkers.push(markerInfo);
} }
// 再帰的に子ノードを処理 // マーカーID取得
if ('children' in node && Array.isArray(node.children)) { const markerIdProp = node.props?.find(p => p.name === 'markerId');
for (const child of node.children) { const markerId = markerIdProp?.type == NodeTypes.ATTRIBUTE ? markerIdProp.value?.content : null;
if (typeof child == 'object' && child.type !== NodeTypes.SIMPLE_EXPRESSION) {
collectMarkers(child, parentId); // SearchMarkerにマーカーIDがない場合はエラー
} if (markerId == null) {
} logger.error(`Marker ID not found for node: ${JSON.stringify(node)}`);
} throw new Error(`Marker ID not found in file ${id}`);
} }
// マーカー基本情報
const markerInfo: SearchIndexItem = {
id: markerId,
parentId: parentId ?? undefined,
label: '', // デフォルト値
keywords: [],
};
// バインドプロパティを取得
const path = getStringProp(findAttribute(node.props, 'path'))
const icon = getStringProp(findAttribute(node.props, 'icon'))
const label = getStringProp(findAttribute(node.props, 'label'))
const inlining = getStringArrayProp(findAttribute(node.props, 'inlining'))
const keywords = getStringArrayProp(findAttribute(node.props, 'keywords'))
if (path) markerInfo.path = path;
if (icon) markerInfo.icon = icon;
if (label) markerInfo.label = label;
if (inlining) markerInfo.inlining = inlining;
if (keywords) markerInfo.keywords = keywords;
//pathがない場合はファイルパスを設定
if (markerInfo.path == null && parentId == null) {
markerInfo.path = id.match(/.*(\/(admin|settings)\/[^\/]+)\.vue$/)?.[1];
}
// SearchLabelとSearchKeywordを抽出 (AST全体を探索)
{
const extracted = extractLabelsAndKeywords(node.children);
if (extracted.label && markerInfo.label) logger.warn(`Duplicate label found for ${markerId} at ${id}:${node.loc.start.line}`);
markerInfo.label = extracted.label ?? markerInfo.label ?? '';
markerInfo.keywords = [...extracted.keywords, ...markerInfo.keywords];
}
if (!markerInfo.label) {
logger.warn(`No label found for ${markerId} at ${id}:${node.loc.start.line}`);
}
// マーカーを登録
markerMap.set(markerId, markerInfo);
allMarkers.push(markerInfo);
return markerId;
});
// AST解析開始
collectMarkers(templateAst);
return allMarkers; return allMarkers;
} }
type Bindings = Partial<Record<keyof SearchIndexItem, unknown>>; //endregion
// バインドプロパティの処理を修正する関数
function extractNodeBindings(node: TemplateChildNode | RootNode): Bindings {
const bindings: Bindings = {};
if (node.type !== NodeTypes.ELEMENT) return bindings; //region evalExpression
// バインド式を収集
for (const prop of node.props) {
if (prop.type === NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.arg && 'content' in prop.arg) {
const propName = prop.arg.content;
if (prop.exp?.type === NodeTypes.COMPOUND_EXPRESSION) throw new Error('unexpected COMPOUND_EXPRESSION');
const propContent = prop.exp?.content || '';
logger.info(`Processing bind prop ${propName}: ${propContent}`);
bindings[propName] = evalExpression(propContent);
}
}
return bindings;
}
/** /**
* expr * expr
@ -411,14 +446,7 @@ export function collectFileMarkers(id: string, code: string): SearchIndexItem[]
return []; // エラーが発生したファイルはスキップ return []; // エラーが発生したファイルはスキップ
} }
const fileMarkers = extractUsageInfoFromTemplateAst(descriptor.template?.ast, id); return extractUsageInfoFromTemplateAst(descriptor.template?.ast, id);
if (fileMarkers && fileMarkers.length > 0) {
logger.info(`Successfully extracted ${fileMarkers.length} markers from ${id}`);
} else {
logger.info(`No markers found in ${id}`);
}
return fileMarkers;
} catch (error) { } catch (error) {
logger.error(`Error analyzing file ${id}:`, error); logger.error(`Error analyzing file ${id}:`, error);
} }
@ -426,6 +454,8 @@ export function collectFileMarkers(id: string, code: string): SearchIndexItem[]
return []; return [];
} }
// endregion
type TransformedCode = { type TransformedCode = {
code: string, code: string,
map: SourceMap, map: SourceMap,
@ -473,84 +503,37 @@ export class MarkerIdAssigner {
}; };
} }
type SearchMarkerElementNode = ElementNode & { walkVueElements<string | null>([ast], null, (node, parentId) => {
__markerId?: string, if (node.tag !== 'SearchMarker') return;
__children?: string[],
};
function traverse(node: RootNode | TemplateChildNode | SimpleExpressionNode | CompoundExpressionNode, currentParent?: SearchMarkerElementNode) { const markerIdProp = findAttribute(node.props, 'markerId');
if (node.type === NodeTypes.ELEMENT && node.tag === 'SearchMarker') {
// 行番号はコード先頭からの改行数で取得 let nodeMarkerId: string;
const lineNumber = code.slice(0, node.loc.start.offset).split('\n').length; if (markerIdProp != null) {
if (markerIdProp.type !== NodeTypes.ATTRIBUTE) return logger.error(`markerId must be a attribute at ${id}:${markerIdProp.loc.start.line}`);
if (markerIdProp.value == null) return logger.error(`markerId must have a value at ${id}:${markerIdProp.loc.start.line}`);
nodeMarkerId = markerIdProp.value.content;
} else {
// ファイルパスと行番号からハッシュ値を生成 // ファイルパスと行番号からハッシュ値を生成
// この際実行環境で差が出ないようにファイルパスを正規化 // この際実行環境で差が出ないようにファイルパスを正規化
const idKey = id.replace(/\\/g, '/').split('packages/frontend/')[1] const idKey = id.replace(/\\/g, '/').split('packages/frontend/')[1]
const generatedMarkerId = toBase62(hash(`${idKey}:${lineNumber}`)); const generatedMarkerId = toBase62(hash(`${idKey}:${node.loc.start.line}`));
const props = node.props || []; // markerId attribute を追加
const hasMarkerIdProp = props.some((prop) => prop.type === NodeTypes.ATTRIBUTE && prop.name === 'markerId'); const endOfStartTag = findEndOfStartTagAttributes(node);
const nodeMarkerId = hasMarkerIdProp s.appendRight(endOfStartTag, ` markerId="${generatedMarkerId}" data-in-app-search-marker-id="${generatedMarkerId}"`);
? props.find((prop): prop is AttributeNode => prop.type === NodeTypes.ATTRIBUTE && prop.name === 'markerId')?.value?.content as string
: generatedMarkerId;
(node as SearchMarkerElementNode).__markerId = nodeMarkerId;
// 子マーカーの場合、親ノードに __children を設定しておく nodeMarkerId = generatedMarkerId;
if (currentParent) {
currentParent.__children = currentParent.__children || [];
currentParent.__children.push(nodeMarkerId);
}
const parentMarkerId = currentParent && currentParent.__markerId;
markerRelations.push({
parentId: parentMarkerId,
markerId: nodeMarkerId,
node: node,
});
if (!hasMarkerIdProp) {
const nodeStart = node.loc.start.offset;
let endOfStartTag;
if (node.children && node.children.length > 0) {
// 子要素がある場合、最初の子要素の開始位置を基準にする
endOfStartTag = code.lastIndexOf('>', node.children[0].loc.start.offset);
} else if (node.loc.end.offset > nodeStart) {
// 子要素がない場合、自身の終了位置から逆算
const nodeSource = code.substring(nodeStart, node.loc.end.offset);
// 自己終了タグか通常の終了タグかを判断
if (nodeSource.includes('/>')) {
endOfStartTag = code.indexOf('/>', nodeStart) - 1;
} else {
endOfStartTag = code.indexOf('>', nodeStart);
}
}
if (endOfStartTag !== undefined && endOfStartTag !== -1) {
// markerId が既に存在しないことを確認
const tagText = code.substring(nodeStart, endOfStartTag + 1);
const markerIdRegex = /\s+markerId\s*=\s*["'][^"']*["']/;
if (!markerIdRegex.test(tagText)) {
s.appendRight(endOfStartTag, ` markerId="${generatedMarkerId}" data-in-app-search-marker-id="${generatedMarkerId}"`);
logger.info(`Adding markerId="${generatedMarkerId}" to ${id}:${lineNumber}`);
} else {
logger.info(`markerId already exists in ${id}:${lineNumber}`);
}
}
}
} }
const newParent: SearchMarkerElementNode | undefined = node.type === NodeTypes.ELEMENT && node.tag === 'SearchMarker' ? node : currentParent; markerRelations.push({
if ('children' in node) { parentId: parentId ?? undefined,
for (const child of node.children) { markerId: nodeMarkerId,
if (typeof child == 'object') { node: node,
traverse(child, newParent); });
}
}
}
}
traverse(ast); // AST を traverse (1段階目: ID 生成と親子関係記録) return nodeMarkerId;
})
// 2段階目: :children 属性の追加 // 2段階目: :children 属性の追加
// 最初に親マーカーごとに子マーカーIDを集約する処理を追加 // 最初に親マーカーごとに子マーカーIDを集約する処理を追加
@ -562,93 +545,42 @@ export class MarkerIdAssigner {
if (!parentChildrenMap.has(relation.parentId)) { if (!parentChildrenMap.has(relation.parentId)) {
parentChildrenMap.set(relation.parentId, []); parentChildrenMap.set(relation.parentId, []);
} }
parentChildrenMap.get(relation.parentId)?.push(relation.markerId); parentChildrenMap.get(relation.parentId)!.push(relation.markerId);
} }
}); });
// 2. 親ごとにまとめて :children 属性を処理 // 2. 親ごとにまとめて :children 属性を処理
for (const [parentId, childIds] of parentChildrenMap.entries()) { for (const [parentId, childIds] of parentChildrenMap.entries()) {
const parentRelation = markerRelations.find(r => r.markerId === parentId); const parentRelation = markerRelations.find(r => r.markerId === parentId);
if (!parentRelation || !parentRelation.node) continue; if (!parentRelation) continue;
const parentNode = parentRelation.node; const parentNode = parentRelation.node;
const childrenProp = parentNode.props?.find((prop): prop is DirectiveNode => const childrenProp = findAttribute(parentNode.props, 'children');
prop.type === NodeTypes.DIRECTIVE && if (childrenProp != null) {
prop.name === 'bind' && if (childrenProp.type !== NodeTypes.DIRECTIVE) {
prop.arg?.type === NodeTypes.SIMPLE_EXPRESSION && console.error(`children prop should be directive (:children) at ${id}:${childrenProp.loc.start.line}`);
prop.arg.content === 'children'); continue;
}
// 親ノードの開始位置を特定
const parentNodeStart = parentNode.loc!.start.offset;
const endOfParentStartTag = parentNode.children && parentNode.children.length > 0
? code.lastIndexOf('>', parentNode.children[0].loc!.start.offset)
: code.indexOf('>', parentNodeStart);
if (endOfParentStartTag === -1) continue;
// 親タグのテキストを取得
const parentTagText = code.substring(parentNodeStart, endOfParentStartTag + 1);
if (childrenProp) {
// AST で :children 属性が検出された場合、それを更新 // AST で :children 属性が検出された場合、それを更新
try { const childrenValue = getStringArrayProp(childrenProp);
const childrenStart = code.indexOf('[', childrenProp.exp!.loc.start.offset); if (childrenValue == null) continue;
const childrenEnd = code.indexOf(']', childrenProp.exp!.loc.start.offset);
if (childrenStart !== -1 && childrenEnd !== -1) {
const childrenArrayStr = code.slice(childrenStart, childrenEnd + 1);
let childrenArray = JSON5.parse(childrenArrayStr.replace(/'/g, '"'));
// 新しいIDを追加重複は除外 const newValue: string[] = [...childrenValue];
const newIds = childIds.filter(id => !childrenArray.includes(id)); for (const childId of [...childIds]) {
if (newIds.length > 0) { if (!newValue.includes(childId)) {
childrenArray = [...childrenArray, ...newIds]; newValue.push(childId);
const updatedChildrenArrayStr = JSON5.stringify(childrenArray).replace(/"/g, "'");
s.overwrite(childrenStart, childrenEnd + 1, updatedChildrenArrayStr);
logger.info(`Added ${newIds.length} child markerIds to existing :children in ${id}`);
}
} }
} catch (e) {
logger.error('Error updating :children attribute:', e);
} }
const expression = JSON.stringify(newValue).replaceAll(/"/g, "'");
s.overwrite(childrenProp.exp!.loc.start.offset, childrenProp.exp!.loc.end.offset, expression);
logger.info(`Added ${childIds.length} child markerIds to existing :children in ${id}`);
} else { } else {
// AST では検出されなかった場合、タグテキストを調べる // :children 属性がまだない場合、新規作成
const childrenRegex = /:children\s*=\s*["']\[(.*?)\]["']/; const endOfParentStartTag = findEndOfStartTagAttributes(parentNode);
const childrenMatch = parentTagText.match(childrenRegex); s.appendRight(endOfParentStartTag, ` :children="${JSON5.stringify(childIds).replace(/"/g, "'")}"`);
logger.info(`Created new :children attribute with ${childIds.length} markerIds in ${id}`);
if (childrenMatch) {
// テキストから :children 属性値を解析して更新
try {
const childrenContent = childrenMatch[1];
const childrenArrayStr = `[${childrenContent}]`;
const childrenArray = JSON5.parse(childrenArrayStr.replace(/'/g, '"'));
// 新しいIDを追加重複は除外
const newIds = childIds.filter(id => !childrenArray.includes(id));
if (newIds.length > 0) {
childrenArray.push(...newIds);
// :children="[...]" の位置を特定して上書き
const attrStart = parentTagText.indexOf(':children=');
if (attrStart > -1) {
const attrValueStart = parentTagText.indexOf('[', attrStart);
const attrValueEnd = parentTagText.indexOf(']', attrValueStart) + 1;
if (attrValueStart > -1 && attrValueEnd > -1) {
const absoluteStart = parentNodeStart + attrValueStart;
const absoluteEnd = parentNodeStart + attrValueEnd;
const updatedArrayStr = JSON5.stringify(childrenArray).replace(/"/g, "'");
s.overwrite(absoluteStart, absoluteEnd, updatedArrayStr);
logger.info(`Updated existing :children in tag text for ${id}`);
}
}
}
} catch (e) {
logger.error('Error updating :children in tag text:', e);
}
} else {
// :children 属性がまだない場合、新規作成
s.appendRight(endOfParentStartTag, ` :children="${JSON5.stringify(childIds).replace(/"/g, "'")}"`);
logger.info(`Created new :children attribute with ${childIds.length} markerIds in ${id}`);
}
} }
} }
@ -800,13 +732,3 @@ export function pluginCreateSearchIndexVirtualModule(options: Options, asigner:
} }
}; };
} }
// i18n参照を検出するためのヘルパー関数を追加
function isI18nReference(text: string | null | undefined): boolean {
if (!text) return false;
// ドット記法i18n.ts.something
const dotPattern = /i18n\.ts\.\w+/;
// ブラケット記法i18n.ts['something']
const bracketPattern = /i18n\.ts\[['"][^'"]+['"]\]/;
return dotPattern.test(text) || bracketPattern.test(text);
}