とりあえず省略記法に対応
This commit is contained in:
@@ -171,7 +171,7 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR
|
||||
return resolveChildrenReferences(marker);
|
||||
});
|
||||
|
||||
// 特殊なプロパティ変換用の関数
|
||||
// 特殊なプロパティ変換用の関数 - i18n参照処理を強化
|
||||
function formatSpecialProperty(key: string, value: any): string {
|
||||
// 値がundefinedの場合は空文字列を返す
|
||||
if (value === undefined) {
|
||||
@@ -183,55 +183,82 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR
|
||||
return customStringify(value);
|
||||
}
|
||||
|
||||
// 文字列でない場合はJSON5で文字列化
|
||||
if (typeof value !== 'string') {
|
||||
return JSON5.stringify(value);
|
||||
// keywordsが配列の場合、特別に処理
|
||||
if (key === 'keywords' && Array.isArray(value)) {
|
||||
return `[${formatArrayForOutput(value)}]`;
|
||||
}
|
||||
|
||||
// i18n.ts 参照を含む場合
|
||||
if (value.includes('i18n.ts.')) {
|
||||
return value; // クォートなしで直接返す
|
||||
}
|
||||
// 文字列値の場合の特別処理
|
||||
if (typeof value === 'string') {
|
||||
// i18n.ts 参照を含む場合 - クォートなしでそのまま出力
|
||||
if (value.includes('i18n.ts.')) {
|
||||
logger.info(`Preserving i18n reference in output: ${value}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
// keywords が配列リテラルの形式の場合
|
||||
if (key === 'keywords' && value.startsWith('[') && value.endsWith(']')) {
|
||||
return value; // クォートなしで直接返す
|
||||
// keywords が配列リテラルの形式の場合
|
||||
if (key === 'keywords' && value.startsWith('[') && value.endsWith(']')) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// 上記以外は通常の JSON5 文字列として返す
|
||||
return JSON5.stringify(value);
|
||||
}
|
||||
|
||||
// オブジェクトをカスタム形式に変換する関数
|
||||
// オブジェクトをカスタム形式に変換する関数 - i18n参照処理を強化
|
||||
function customStringify(obj: any, depth = 0): string {
|
||||
const INDENT_STR = '\t';
|
||||
|
||||
// 配列の処理
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 0) return '[]';
|
||||
const indent = INDENT_STR.repeat(depth);
|
||||
const childIndent = INDENT_STR.repeat(depth + 1);
|
||||
const items = obj.map(item => `${childIndent}${customStringify(item, depth + 1)}`).join(',\n');
|
||||
|
||||
// 配列要素の処理
|
||||
const items = obj.map(item => {
|
||||
// オブジェクト要素
|
||||
if (typeof item === 'object' && item !== null) {
|
||||
return `${childIndent}${customStringify(item, depth + 1)}`;
|
||||
}
|
||||
|
||||
// i18n参照を含む文字列要素
|
||||
if (typeof item === 'string' && item.includes('i18n.ts.')) {
|
||||
return `${childIndent}${item}`; // クォートなしでそのまま出力
|
||||
}
|
||||
|
||||
// その他の要素
|
||||
return `${childIndent}${JSON5.stringify(item)}`;
|
||||
}).join(',\n');
|
||||
|
||||
return `[\n${items},\n${indent}]`;
|
||||
}
|
||||
|
||||
// null または非オブジェクト
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return JSON5.stringify(obj);
|
||||
}
|
||||
|
||||
// オブジェクトの処理
|
||||
const indent = INDENT_STR.repeat(depth);
|
||||
const childIndent = INDENT_STR.repeat(depth + 1);
|
||||
|
||||
const entries = Object.entries(obj)
|
||||
// 不要なプロパティを除去
|
||||
.filter(([key, value]) => {
|
||||
// valueがundefinedの場合は出力しない
|
||||
if (value === undefined) return false;
|
||||
// childrenが空配列の場合は出力しない
|
||||
if (key === 'children' && Array.isArray(value) && value.length === 0) return false;
|
||||
return true;
|
||||
})
|
||||
// 各プロパティを変換
|
||||
.map(([key, value]) => {
|
||||
// childrenが配列の場合で要素がある場合のみ特別処理
|
||||
// 子要素配列の特殊処理
|
||||
if (key === 'children' && Array.isArray(value) && value.length > 0) {
|
||||
return `${childIndent}${key}: ${customStringify(value, depth + 1)}`;
|
||||
}
|
||||
|
||||
// ラベルやその他プロパティを処理
|
||||
return `${childIndent}${key}: ${formatSpecialProperty(key, value)}`;
|
||||
});
|
||||
|
||||
@@ -239,6 +266,20 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR
|
||||
return `{\n${entries.join(',\n')},\n${indent}}`;
|
||||
}
|
||||
|
||||
// 配列式の文字列表現を修正 - i18n参照を適切に処理
|
||||
function formatArrayForOutput(items: any[]): string {
|
||||
return items.map(item => {
|
||||
// i18n.ts. 参照の文字列はそのままJavaScript式として出力
|
||||
if (typeof item === 'string' && item.includes('i18n.ts.')) {
|
||||
logger.info(`Preserving i18n reference in array: ${item}`);
|
||||
return item; // クォートなしでそのまま
|
||||
}
|
||||
|
||||
// その他の値はJSON5形式で文字列化
|
||||
return JSON5.stringify(item);
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
// 最終出力用のデバッグ情報を生成
|
||||
let totalMarkers = resolvedRootMarkers.length;
|
||||
let totalChildren = 0;
|
||||
@@ -266,8 +307,6 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR
|
||||
// This file was automatically generated by create-search-index.
|
||||
// Do not edit this file.
|
||||
|
||||
/* eslint-disable @stylistic/comma-spacing */
|
||||
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export type SearchIndexItem = {
|
||||
@@ -282,8 +321,6 @@ export type SearchIndexItem = {
|
||||
export const searchIndexes:SearchIndexItem[] = ${customStringify(resolvedRootMarkers)} as const;
|
||||
|
||||
export type SearchIndex = typeof searchIndexes;
|
||||
|
||||
/* eslint-enable @stylistic/comma-spacing */
|
||||
`;
|
||||
|
||||
try {
|
||||
@@ -294,95 +331,350 @@ export type SearchIndex = typeof searchIndexes;
|
||||
}
|
||||
}
|
||||
|
||||
// 要素ノードからテキスト内容を抽出する関数 (Mustache構文のi18n参照にも対応)
|
||||
function extractElementText(node: any): string | null {
|
||||
if (!node) return null;
|
||||
|
||||
console.log(`Extracting text from node type=${node.type}, tag=${node.tag || 'unknown'}`);
|
||||
|
||||
// Mustache構文を検出するための正規表現パターン
|
||||
const mustachePattern = /^\s*{{\s*(.*?)\s*}}\s*$/;
|
||||
|
||||
// childrenが配列でない場合、content直接チェック (インラインテキスト対応)
|
||||
if (node.content) {
|
||||
const content = node.content.trim();
|
||||
console.log(`Direct node content found: ${content}`);
|
||||
|
||||
// Mustache構文のチェック
|
||||
const mustacheMatch = content.match(mustachePattern);
|
||||
if (mustacheMatch && mustacheMatch[1] && mustacheMatch[1].includes('i18n.ts.')) {
|
||||
const extractedContent = mustacheMatch[1].trim();
|
||||
console.log(`Extracted i18n reference from mustache: ${extractedContent}`);
|
||||
return extractedContent;
|
||||
}
|
||||
|
||||
// 直接i18n参照を含む場合
|
||||
if (content.includes('i18n.ts.')) {
|
||||
console.log(`Direct i18n reference found: ${content}`);
|
||||
return content;
|
||||
}
|
||||
|
||||
// その他のコンテンツ
|
||||
if (content) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
// childrenがない場合は終了
|
||||
if (!node.children || !Array.isArray(node.children)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Mustacheテンプレート構文の特殊ノード検出 (type=5はインターポレーション)
|
||||
for (const child of node.children) {
|
||||
if (child.type === 5) { // インターポレーションノード (Mustache表現)
|
||||
console.log(`Found interpolation node (Mustache): `, child);
|
||||
if (child.content && child.content.type === 4 && child.content.content) {
|
||||
const content = child.content.content.trim();
|
||||
console.log(`Interpolation content: ${content}`);
|
||||
if (content.includes('i18n.ts.')) {
|
||||
return content;
|
||||
}
|
||||
} else if (child.content && typeof child.content === 'object') {
|
||||
// オブジェクト形式のcontentを再帰的に探索
|
||||
console.log(`Complex interpolation node:`, JSON.stringify(child.content).substring(0, 100));
|
||||
if (child.content.content) {
|
||||
const content = child.content.content.trim();
|
||||
if (content.includes('i18n.ts.')) {
|
||||
console.log(`Found i18n reference in complex interpolation: ${content}`);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最初のパスで i18n.ts. 参照パターンを持つものを探す (最優先)
|
||||
for (const child of node.children) {
|
||||
if (child.type === 2 && child.content) { // 式ノード
|
||||
const expr = child.content.trim();
|
||||
if (expr.includes('i18n.ts.')) {
|
||||
console.log(`Found i18n reference in expression node: ${expr}`);
|
||||
return expr; // i18n参照を見つけたら即座に返す
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2回目のパスで一般的な式を探す
|
||||
for (const child of node.children) {
|
||||
if (child.type === 2 && child.content) { // その他の式ノード
|
||||
const expr = child.content.trim();
|
||||
console.log(`Found expression: ${expr}`);
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
|
||||
// 3回目のパスでテキストノードを探す
|
||||
for (const child of node.children) {
|
||||
if (child.type === 3 && child.content) { // テキストノード
|
||||
const text = child.content.trim();
|
||||
if (text) {
|
||||
console.log(`Found text node: ${text}`);
|
||||
|
||||
// Mustache構文のチェック
|
||||
const mustacheMatch = text.match(mustachePattern);
|
||||
if (mustacheMatch && mustacheMatch[1] && mustacheMatch[1].includes('i18n.ts.')) {
|
||||
console.log(`Extracted i18n ref from text mustache: ${mustacheMatch[1]}`);
|
||||
return mustacheMatch[1].trim();
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 深さ優先で再帰的に探索 (子の子まで調べる)
|
||||
for (const child of node.children) {
|
||||
if (child.children && Array.isArray(child.children) && child.children.length > 0) {
|
||||
const nestedContent = extractElementText(child);
|
||||
if (nestedContent) {
|
||||
console.log(`Found nested content: ${nestedContent}`);
|
||||
return nestedContent;
|
||||
}
|
||||
} else if (child.type === 1) { // 子要素ノード - childrenがなくても内部を調査
|
||||
const nestedContent = extractElementText(child);
|
||||
if (nestedContent) {
|
||||
console.log(`Found content in childless element: ${nestedContent}`);
|
||||
return nestedContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// SearchLabelとSearchKeywordを探して抽出する関数 (スコープを適切に分離)
|
||||
function extractLabelsAndKeywords(nodes: any[]): { label: string | null, keywords: any[] } {
|
||||
let label: string | null = null;
|
||||
const keywords: any[] = [];
|
||||
|
||||
console.log(`Extracting labels and keywords from ${nodes.length} nodes`);
|
||||
|
||||
// 再帰的にSearchLabelとSearchKeywordを探索(ネストされたSearchMarkerは処理しない)
|
||||
function findComponents(nodes: any[]) {
|
||||
for (const node of nodes) {
|
||||
if (node.type === 1) { // Element node
|
||||
console.log(`Checking element: ${node.tag}`);
|
||||
|
||||
// SearchMarkerの場合は、その子要素は別スコープなのでスキップ
|
||||
if (node.tag === 'SearchMarker') {
|
||||
console.log(`Found nested SearchMarker - skipping its content to maintain scope isolation`);
|
||||
continue; // このSearchMarkerの中身は処理しない (スコープ分離)
|
||||
}
|
||||
|
||||
// SearchLabelの処理
|
||||
if (node.tag === 'SearchLabel') {
|
||||
console.log(`Found SearchLabel node, structure:`, JSON.stringify(node).substring(0, 200) + '...');
|
||||
|
||||
// まず完全なノード内容の抽出を試みる
|
||||
const content = extractElementText(node);
|
||||
if (content) {
|
||||
label = content;
|
||||
console.log(`SearchLabel content extracted: ${content}`);
|
||||
} else {
|
||||
console.log(`SearchLabel found but extraction failed, trying direct children inspection`);
|
||||
|
||||
// バックアップ: 子直接確認 - type=5のMustacheインターポレーションを重点的に確認
|
||||
if (node.children && Array.isArray(node.children)) {
|
||||
for (const child of node.children) {
|
||||
// Mustacheインターポレーション
|
||||
if (child.type === 5 && child.content) {
|
||||
// content内の式を取り出す
|
||||
const expression = child.content.content ||
|
||||
(child.content.type === 4 ? child.content.content : null) ||
|
||||
JSON.stringify(child.content);
|
||||
|
||||
console.log(`Interpolation expression: ${expression}`);
|
||||
if (typeof expression === 'string' && expression.includes('i18n.ts.')) {
|
||||
label = expression.trim();
|
||||
console.log(`Found i18n in interpolation: ${label}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 式ノード
|
||||
else if (child.type === 2 && child.content && child.content.includes('i18n.ts.')) {
|
||||
label = child.content.trim();
|
||||
console.log(`Found i18n in expression: ${label}`);
|
||||
break;
|
||||
}
|
||||
// テキストノードでもMustache構文を探す
|
||||
else if (child.type === 3 && child.content) {
|
||||
const mustacheMatch = child.content.trim().match(/^\s*{{\s*(.*?)\s*}}\s*$/);
|
||||
if (mustacheMatch && mustacheMatch[1] && mustacheMatch[1].includes('i18n.ts.')) {
|
||||
label = mustacheMatch[1].trim();
|
||||
console.log(`Found i18n in text mustache: ${label}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// SearchKeywordの処理
|
||||
else if (node.tag === 'SearchKeyword') {
|
||||
console.log(`Found SearchKeyword node`);
|
||||
|
||||
// まず完全なノード内容の抽出を試みる
|
||||
const content = extractElementText(node);
|
||||
if (content) {
|
||||
keywords.push(content);
|
||||
console.log(`SearchKeyword content extracted: ${content}`);
|
||||
} else {
|
||||
console.log(`SearchKeyword found but extraction failed, trying direct children inspection`);
|
||||
|
||||
// バックアップ: 子直接確認 - type=5のMustacheインターポレーションを重点的に確認
|
||||
if (node.children && Array.isArray(node.children)) {
|
||||
for (const child of node.children) {
|
||||
// Mustacheインターポレーション
|
||||
if (child.type === 5 && child.content) {
|
||||
// content内の式を取り出す
|
||||
const expression = child.content.content ||
|
||||
(child.content.type === 4 ? child.content.content : null) ||
|
||||
JSON.stringify(child.content);
|
||||
|
||||
console.log(`Keyword interpolation: ${expression}`);
|
||||
if (typeof expression === 'string' && expression.includes('i18n.ts.')) {
|
||||
const keyword = expression.trim();
|
||||
keywords.push(keyword);
|
||||
console.log(`Found i18n keyword in interpolation: ${keyword}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 式ノード
|
||||
else if (child.type === 2 && child.content && child.content.includes('i18n.ts.')) {
|
||||
const keyword = child.content.trim();
|
||||
keywords.push(keyword);
|
||||
console.log(`Found i18n keyword in expression: ${keyword}`);
|
||||
break;
|
||||
}
|
||||
// テキストノードでもMustache構文を探す
|
||||
else if (child.type === 3 && child.content) {
|
||||
const mustacheMatch = child.content.trim().match(/^\s*{{\s*(.*?)\s*}}\s*$/);
|
||||
if (mustacheMatch && mustacheMatch[1] && mustacheMatch[1].includes('i18n.ts.')) {
|
||||
const keyword = mustacheMatch[1].trim();
|
||||
keywords.push(keyword);
|
||||
console.log(`Found i18n keyword in text mustache: ${keyword}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 子要素を再帰的に調査(ただしSearchMarkerは除外)
|
||||
if (node.children && Array.isArray(node.children)) {
|
||||
findComponents(node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findComponents(nodes);
|
||||
|
||||
// デバッグ情報
|
||||
console.log(`Extraction completed: label=${label}, keywords=[${keywords.join(', ')}]`);
|
||||
return { label, keywords };
|
||||
}
|
||||
|
||||
function extractUsageInfoFromTemplateAst(
|
||||
templateAst: any,
|
||||
code: string,
|
||||
): SearchIndexItem[] {
|
||||
// すべてのマーカー情報を保持するために、結果をトップレベルマーカー+すべての子マーカーを含む配列に変更
|
||||
const allMarkers: SearchIndexItem[] = [];
|
||||
// マーカーIDからオブジェクトへのマップ
|
||||
const markerMap = new Map<string, SearchIndexItem>();
|
||||
// 子マーカーIDを集約するためのセット
|
||||
const childrenIds = new Set<string>();
|
||||
|
||||
if (!templateAst) {
|
||||
return allMarkers;
|
||||
}
|
||||
|
||||
// デバッグ情報
|
||||
logger.info('Started extracting markers from AST');
|
||||
if (!templateAst) return allMarkers;
|
||||
|
||||
// マーカーの基本情報を収集
|
||||
function collectMarkers(node: any, parentId: string | null = null) {
|
||||
// SearchMarkerコンポーネントの検出
|
||||
if (node.type === 1 && node.tag === 'SearchMarker') {
|
||||
// マーカーID生成 (markerId属性またはDOM内に記録されているものを使用)
|
||||
// マーカーID取得
|
||||
const markerIdProp = node.props?.find((p: any) => p.name === 'markerId');
|
||||
const markerId = markerIdProp?.value?.content ||
|
||||
node.__markerId ||
|
||||
`marker-${Math.random().toString(36).substring(2, 10)}`;
|
||||
|
||||
logger.info(`Found SearchMarker with ID: ${markerId}`);
|
||||
node.__markerId ||
|
||||
`marker-${Math.random().toString(36).substring(2, 10)}`;
|
||||
|
||||
// マーカー基本情報
|
||||
const markerInfo: SearchIndexItem = {
|
||||
id: markerId,
|
||||
children: [],
|
||||
label: '',
|
||||
label: '', // デフォルト値
|
||||
keywords: [],
|
||||
};
|
||||
|
||||
// 静的プロパティを抽出
|
||||
// 静的プロパティを取得
|
||||
if (node.props && Array.isArray(node.props)) {
|
||||
node.props.forEach((prop: any) => {
|
||||
for (const prop of node.props) {
|
||||
if (prop.type === 6 && 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 || '';
|
||||
|
||||
logger.info(`Static prop ${prop.name}:`, prop.value?.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// バインドプロパティを抽出
|
||||
if (node.props && Array.isArray(node.props)) {
|
||||
node.props.forEach((prop: any) => {
|
||||
if (prop.type === 7 && prop.name === 'bind' && prop.arg?.content) {
|
||||
const propName = prop.arg.content;
|
||||
const propValue = prop.exp?.content || '';
|
||||
// バインドプロパティを取得
|
||||
const bindings = extractNodeBindings(node);
|
||||
if (bindings.path) markerInfo.path = bindings.path;
|
||||
if (bindings.icon) markerInfo.icon = bindings.icon;
|
||||
if (bindings.label) markerInfo.label = bindings.label;
|
||||
if (bindings.children) markerInfo.children = bindings.children;
|
||||
if (bindings.keywords) {
|
||||
if (Array.isArray(bindings.keywords)) {
|
||||
markerInfo.keywords = bindings.keywords;
|
||||
} else {
|
||||
markerInfo.keywords = bindings.keywords || [];
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Bind prop ${propName}:`, propValue);
|
||||
// SearchLabelとSearchKeywordを抽出 (AST全体を探索)
|
||||
if (node.children && Array.isArray(node.children)) {
|
||||
console.log(`Processing marker ${markerId} for labels and keywords`);
|
||||
const extracted = extractLabelsAndKeywords(node.children);
|
||||
|
||||
if (propName === 'label') {
|
||||
markerInfo.label = propValue;
|
||||
} else if (propName === 'path') {
|
||||
markerInfo.path = propValue;
|
||||
} else if (propName === 'icon') {
|
||||
markerInfo.icon = propValue;
|
||||
} else if (propName === 'keywords') {
|
||||
markerInfo.keywords = propValue || '[]';
|
||||
} else if (propName === 'children') {
|
||||
markerInfo.children = propValue || '[]';
|
||||
}
|
||||
// SearchLabelからのラベル取得は最優先で適用
|
||||
if (extracted.label) {
|
||||
markerInfo.label = extracted.label;
|
||||
console.log(`Using extracted label for ${markerId}: ${extracted.label}`);
|
||||
} else if (markerInfo.label) {
|
||||
console.log(`Using existing label for ${markerId}: ${markerInfo.label}`);
|
||||
} else {
|
||||
markerInfo.label = 'Unnamed marker';
|
||||
console.log(`No label found for ${markerId}, using default`);
|
||||
}
|
||||
|
||||
// SearchKeywordからのキーワード取得を追加
|
||||
if (extracted.keywords.length > 0) {
|
||||
const existingKeywords = Array.isArray(markerInfo.keywords) ?
|
||||
[...markerInfo.keywords] :
|
||||
(markerInfo.keywords ? [markerInfo.keywords] : []);
|
||||
|
||||
// i18n参照のキーワードは最優先で追加
|
||||
const combinedKeywords = [...existingKeywords];
|
||||
for (const kw of extracted.keywords) {
|
||||
combinedKeywords.push(kw);
|
||||
console.log(`Added extracted keyword to ${markerId}: ${kw}`);
|
||||
}
|
||||
});
|
||||
|
||||
markerInfo.keywords = combinedKeywords;
|
||||
}
|
||||
}
|
||||
|
||||
// ラベルがない場合はデフォルト値を設定
|
||||
if (!markerInfo.label) {
|
||||
markerInfo.label = 'Unnamed marker';
|
||||
}
|
||||
|
||||
// キーワードがない場合はデフォルト値を設定
|
||||
if (!markerInfo.keywords || (Array.isArray(markerInfo.keywords) && markerInfo.keywords.length === 0)) {
|
||||
markerInfo.keywords = '[]';
|
||||
}
|
||||
|
||||
// マーカーをマップに保存し、すべてのマーカーリストにも追加
|
||||
// マーカーを登録
|
||||
markerMap.set(markerId, markerInfo);
|
||||
allMarkers.push(markerInfo); // すべてのマーカーを保持
|
||||
allMarkers.push(markerInfo);
|
||||
|
||||
// 親子関係を記録
|
||||
if (parentId) {
|
||||
@@ -395,11 +687,10 @@ function extractUsageInfoFromTemplateAst(
|
||||
parent.children = [markerId];
|
||||
}
|
||||
childrenIds.add(markerId);
|
||||
logger.info(`Added ${markerId} as child of ${parentId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 子ノードを処理(親は現在のノード)
|
||||
// 子ノードを処理
|
||||
if (node.children && Array.isArray(node.children)) {
|
||||
node.children.forEach((child: any) => {
|
||||
collectMarkers(child, markerId);
|
||||
@@ -409,7 +700,7 @@ function extractUsageInfoFromTemplateAst(
|
||||
return markerId;
|
||||
}
|
||||
|
||||
// SearchMarkerでない場合は、子ノードを同じ親コンテキストで処理
|
||||
// 子ノードを処理
|
||||
if (node.children && Array.isArray(node.children)) {
|
||||
node.children.forEach((child: any) => {
|
||||
collectMarkers(child, parentId);
|
||||
@@ -419,16 +710,169 @@ function extractUsageInfoFromTemplateAst(
|
||||
return null;
|
||||
}
|
||||
|
||||
// AST解析を開始
|
||||
// AST解析開始
|
||||
collectMarkers(templateAst);
|
||||
|
||||
// デバッグ情報
|
||||
logger.info(`Found ${markerMap.size} markers, ${childrenIds.size} children`);
|
||||
|
||||
// 重要: すべてのマーカー情報を返す
|
||||
return allMarkers;
|
||||
}
|
||||
|
||||
// バインドプロパティの処理を修正する関数
|
||||
function extractNodeBindings(node: any): Record<string, any> {
|
||||
const bindings: Record<string, any> = {};
|
||||
|
||||
if (!node.props || !Array.isArray(node.props)) return bindings;
|
||||
|
||||
// バインド式を収集
|
||||
for (const prop of node.props) {
|
||||
if (prop.type === 7 && prop.name === 'bind' && prop.arg?.content) {
|
||||
const propName = prop.arg.content;
|
||||
const propContent = prop.exp?.content || '';
|
||||
|
||||
logger.info(`Processing bind prop ${propName}: ${propContent}`);
|
||||
|
||||
// keywordsの特殊処理
|
||||
if (propName === 'keywords') {
|
||||
try {
|
||||
const content = propContent.trim();
|
||||
|
||||
// 配列式の場合
|
||||
if (content.startsWith('[') && content.endsWith(']')) {
|
||||
// i18n参照や特殊な式を保持するため、各要素を個別に解析
|
||||
const elements = parseArrayExpression(content);
|
||||
if (elements.length > 0) {
|
||||
bindings.keywords = elements;
|
||||
logger.info(`Parsed keywords array: ${JSON5.stringify(elements)}`);
|
||||
} else {
|
||||
bindings.keywords = [];
|
||||
logger.info('Empty keywords array');
|
||||
}
|
||||
}
|
||||
// その他の式(非配列)
|
||||
else if (content) {
|
||||
bindings.keywords = content; // 式をそのまま保持
|
||||
logger.info(`Keeping keywords as expression: ${content}`);
|
||||
} else {
|
||||
bindings.keywords = [];
|
||||
logger.info('No keywords provided');
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(`Failed to parse keywords binding: ${propContent}`, e);
|
||||
// エラーが起きても何らかの値を設定
|
||||
bindings.keywords = propContent || [];
|
||||
}
|
||||
}
|
||||
// その他のプロパティ
|
||||
else if (propName === 'label') {
|
||||
// ラベルの場合も式として保持
|
||||
bindings[propName] = propContent;
|
||||
logger.info(`Set label from bind expression: ${propContent}`);
|
||||
}
|
||||
else {
|
||||
bindings[propName] = propContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bindings;
|
||||
}
|
||||
|
||||
// 配列式をパースする補助関数(文字列リテラル処理を改善)
|
||||
function parseArrayExpression(expr: string): any[] {
|
||||
try {
|
||||
// 単純なケースはJSON5でパースを試みる
|
||||
return JSON5.parse(expr.replace(/'/g, '"'));
|
||||
} catch (e) {
|
||||
// 複雑なケース(i18n.ts.xxx などの式を含む場合)は手動パース
|
||||
logger.info(`Complex array expression, trying manual parsing: ${expr}`);
|
||||
|
||||
// "["と"]"を取り除く
|
||||
const content = expr.substring(1, expr.length - 1).trim();
|
||||
if (!content) return [];
|
||||
|
||||
const result: any[] = [];
|
||||
let currentItem = '';
|
||||
let depth = 0;
|
||||
let inString = false;
|
||||
let stringChar = '';
|
||||
|
||||
// カンマで区切る(ただし文字列内や入れ子の配列内のカンマは無視)
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
const char = content[i];
|
||||
|
||||
if (inString) {
|
||||
if (char === stringChar && content[i - 1] !== '\\') {
|
||||
inString = false;
|
||||
}
|
||||
currentItem += char;
|
||||
} else if (char === '"' || char === "'") {
|
||||
inString = true;
|
||||
stringChar = char;
|
||||
currentItem += char;
|
||||
} else if (char === '[') {
|
||||
depth++;
|
||||
currentItem += char;
|
||||
} else if (char === ']') {
|
||||
depth--;
|
||||
currentItem += char;
|
||||
} else if (char === ',' && depth === 0) {
|
||||
// 項目の区切りを検出
|
||||
const trimmed = currentItem.trim();
|
||||
|
||||
// 純粋な文字列リテラルの場合、実際の値に変換
|
||||
if ((trimmed.startsWith("'") && trimmed.endsWith("'")) ||
|
||||
(trimmed.startsWith('"') && trimmed.endsWith('"'))) {
|
||||
try {
|
||||
result.push(JSON5.parse(trimmed));
|
||||
} catch (err) {
|
||||
result.push(trimmed);
|
||||
}
|
||||
} else {
|
||||
// それ以外の式はそのまま(i18n.ts.xxx など)
|
||||
result.push(trimmed);
|
||||
}
|
||||
|
||||
currentItem = '';
|
||||
} else {
|
||||
currentItem += char;
|
||||
}
|
||||
}
|
||||
|
||||
// 最後の項目を処理
|
||||
if (currentItem.trim()) {
|
||||
const trimmed = currentItem.trim();
|
||||
|
||||
// 純粋な文字列リテラルの場合、実際の値に変換
|
||||
if ((trimmed.startsWith("'") && trimmed.endsWith("'")) ||
|
||||
(trimmed.startsWith('"') && trimmed.endsWith('"'))) {
|
||||
try {
|
||||
result.push(JSON5.parse(trimmed));
|
||||
} catch (err) {
|
||||
result.push(trimmed);
|
||||
}
|
||||
} else {
|
||||
// それ以外の式はそのまま(i18n.ts.xxx など)
|
||||
result.push(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Parsed complex array expression: ${expr} -> ${JSON.stringify(result)}`);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// 配列式の文字列表現を修正 - i18n参照を適切に処理
|
||||
function formatArrayForOutput(items: any[]): string {
|
||||
return items.map(item => {
|
||||
// i18n.ts. 参照の文字列はそのままJavaScript式として出力
|
||||
if (typeof item === 'string' && item.includes('i18n.ts.')) {
|
||||
logger.info(`Preserving i18n reference in array: ${item}`);
|
||||
return item; // クォートなしでそのまま
|
||||
}
|
||||
|
||||
// その他の値はJSON5形式で文字列化
|
||||
return JSON5.stringify(item);
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
export async function analyzeVueProps(options: {
|
||||
targetFilePaths: string[],
|
||||
exportFilePath: string,
|
||||
@@ -466,7 +910,7 @@ export async function analyzeVueProps(options: {
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
logger.error(`Compile Error: ${filePath}`, errors);
|
||||
logger.error(`Compile Error: ${filePath}, ${errors}`);
|
||||
continue; // エラーが発生したファイルはスキップ
|
||||
}
|
||||
|
||||
@@ -578,7 +1022,7 @@ async function processVueFile(
|
||||
s.appendRight(endOfStartTag, ` markerId="${generatedMarkerId}"`);
|
||||
logger.info(`Adding markerId="${generatedMarkerId}" to ${id}:${lineNumber}`);
|
||||
} else {
|
||||
logger.warn(`markerId already exists in ${id}:${lineNumber}`);
|
||||
logger.info(`markerId already exists in ${id}:${lineNumber}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user