Refactor and Format codes
This commit is contained in:
parent
ca80f4e273
commit
c54299e522
|
@ -13,6 +13,27 @@ import path from 'node:path'
|
|||
import { hash, toBase62 } from '../vite.config';
|
||||
import { createLogger } from 'vite';
|
||||
|
||||
interface VueAstNode {
|
||||
type: number;
|
||||
tag?: string;
|
||||
loc?: {
|
||||
start: { offset: number, line: number, column: number },
|
||||
end: { offset: number, line: number, column: number },
|
||||
source?: string
|
||||
};
|
||||
props?: Array<{
|
||||
name: string;
|
||||
type: number;
|
||||
value?: { content?: string };
|
||||
arg?: { content?: string };
|
||||
exp?: { content?: string; loc?: any };
|
||||
}>;
|
||||
children?: VueAstNode[];
|
||||
content?: any;
|
||||
__markerId?: string;
|
||||
__children?: string[];
|
||||
}
|
||||
|
||||
export type AnalysisResult = {
|
||||
filePath: string;
|
||||
usage: SearchIndexItem[];
|
||||
|
@ -27,6 +48,22 @@ export type SearchIndexItem = {
|
|||
children?: (SearchIndexItem[] | string);
|
||||
}
|
||||
|
||||
// 関連するノードタイプの定数化
|
||||
const NODE_TYPES = {
|
||||
ELEMENT: 1,
|
||||
EXPRESSION: 2,
|
||||
TEXT: 3,
|
||||
INTERPOLATION: 5, // Mustache
|
||||
};
|
||||
|
||||
// マーカー関係を表す型
|
||||
interface MarkerRelation {
|
||||
parentId?: string;
|
||||
markerId: string;
|
||||
node: VueAstNode;
|
||||
}
|
||||
|
||||
// ロガーの設定
|
||||
const logger = createLogger();
|
||||
const loggerInfo = logger.info;
|
||||
const loggerWarn = logger.warn;
|
||||
|
@ -47,6 +84,9 @@ logger.error = (msg, options) => {
|
|||
loggerError(msg, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析結果をTypeScriptファイルとして出力する
|
||||
*/
|
||||
function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisResult[]): void {
|
||||
logger.info(`Processing ${analysisResults.length} files for output`);
|
||||
|
||||
|
@ -56,43 +96,17 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR
|
|||
// 1. すべてのマーカーを一旦フラットに収集
|
||||
for (const file of analysisResults) {
|
||||
logger.info(`Processing file: ${file.filePath} with ${file.usage.length} markers`);
|
||||
|
||||
for (const marker of file.usage) {
|
||||
if (marker.id) {
|
||||
// キーワードの処理(文字列から配列へ変換)
|
||||
let keywords = marker.keywords;
|
||||
if (typeof keywords === 'string' && keywords.startsWith('[') && keywords.endsWith(']')) {
|
||||
try {
|
||||
// JSON5解析を試みる(ただしi18n参照などがある場合は例外発生)
|
||||
keywords = JSON5.parse(keywords.replace(/'/g, '"'));
|
||||
} catch (e) {
|
||||
// 解析に失敗した場合は文字列のままにする
|
||||
logger.warn(`Keeping keywords as string expression: ${keywords}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 子要素の処理(文字列から配列へ変換)
|
||||
let children = marker.children || [];
|
||||
if (typeof children === 'string' && children.startsWith('[') && children.endsWith(']')) {
|
||||
try {
|
||||
// JSON5解析を試みる
|
||||
children = JSON5.parse(children.replace(/'/g, '"'));
|
||||
} catch (e) {
|
||||
// 解析に失敗した場合は空配列に
|
||||
logger.warn(`Could not parse children: ${children}, using empty array`);
|
||||
children = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 子マーカーの内部構造を適切に更新
|
||||
if (Array.isArray(children) && children.length > 0) {
|
||||
logger.info(`Marker ${marker.id} has ${children.length} children: ${JSON.stringify(children)}`);
|
||||
}
|
||||
|
||||
allMarkers.set(marker.id, {
|
||||
// キーワードとchildren処理を共通化
|
||||
const processedMarker = {
|
||||
...marker,
|
||||
keywords,
|
||||
children: Array.isArray(children) ? children : []
|
||||
});
|
||||
keywords: processMarkerProperty(marker.keywords, 'keywords'),
|
||||
children: processMarkerProperty(marker.children || [], 'children')
|
||||
};
|
||||
|
||||
allMarkers.set(marker.id, processedMarker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,6 +114,47 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR
|
|||
logger.info(`Collected total ${allMarkers.size} unique markers`);
|
||||
|
||||
// 2. 子マーカーIDの収集
|
||||
const childIds = collectChildIds(allMarkers);
|
||||
logger.info(`Found ${childIds.size} child markers`);
|
||||
|
||||
// 3. ルートマーカーの特定(他の誰かの子でないマーカー)
|
||||
const rootMarkers = identifyRootMarkers(allMarkers, childIds);
|
||||
logger.info(`Found ${rootMarkers.length} root markers`);
|
||||
|
||||
// 4. 子マーカーの参照を解決
|
||||
const resolvedRootMarkers = resolveChildReferences(rootMarkers, allMarkers);
|
||||
|
||||
// 5. デバッグ情報を生成
|
||||
const { totalMarkers, totalChildren } = countMarkers(resolvedRootMarkers);
|
||||
logger.info(`Total markers in tree: ${totalMarkers} (${resolvedRootMarkers.length} roots + ${totalChildren} nested children)`);
|
||||
|
||||
// 6. 結果をTS形式で出力
|
||||
writeOutputFile(outputPath, resolvedRootMarkers);
|
||||
}
|
||||
|
||||
/**
|
||||
* マーカーのプロパティ(keywordsやchildren)を処理する
|
||||
*/
|
||||
function processMarkerProperty(propValue: any, propType: 'keywords' | 'children'): any {
|
||||
// 文字列の配列表現を解析
|
||||
if (typeof propValue === 'string' && propValue.startsWith('[') && propValue.endsWith(']')) {
|
||||
try {
|
||||
// JSON5解析を試みる
|
||||
return JSON5.parse(propValue.replace(/'/g, '"'));
|
||||
} catch (e) {
|
||||
// 解析に失敗した場合
|
||||
logger.warn(`Could not parse ${propType}: ${propValue}, using ${propType === 'children' ? 'empty array' : 'as is'}`);
|
||||
return propType === 'children' ? [] : propValue;
|
||||
}
|
||||
}
|
||||
|
||||
return propValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全マーカーから子IDを収集する
|
||||
*/
|
||||
function collectChildIds(allMarkers: Map<string, SearchIndexItem>): Set<string> {
|
||||
const childIds = new Set<string>();
|
||||
|
||||
allMarkers.forEach((marker, id) => {
|
||||
|
@ -117,23 +172,36 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR
|
|||
}
|
||||
});
|
||||
|
||||
logger.info(`Found ${childIds.size} child markers`);
|
||||
return childIds;
|
||||
}
|
||||
|
||||
// 3. ルートマーカーの特定(他の誰かの子でないマーカー)
|
||||
/**
|
||||
* ルートマーカー(他の子でないマーカー)を特定する
|
||||
*/
|
||||
function identifyRootMarkers(
|
||||
allMarkers: Map<string, SearchIndexItem>,
|
||||
childIds: Set<string>
|
||||
): SearchIndexItem[] {
|
||||
const rootMarkers: SearchIndexItem[] = [];
|
||||
|
||||
allMarkers.forEach((marker, id) => {
|
||||
if (!childIds.has(id)) {
|
||||
// このマーカーはルート(他の誰の子でもない)
|
||||
rootMarkers.push(marker);
|
||||
logger.info(`Added root marker to output: ${id} with label ${marker.label}`);
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`Found ${rootMarkers.length} root markers`);
|
||||
return rootMarkers;
|
||||
}
|
||||
|
||||
// 4. 子マーカーの参照を解決(IDから実際のオブジェクトに)
|
||||
function resolveChildrenReferences(marker: SearchIndexItem): SearchIndexItem {
|
||||
/**
|
||||
* 子マーカーの参照をIDから実際のオブジェクトに解決する
|
||||
*/
|
||||
function resolveChildReferences(
|
||||
rootMarkers: SearchIndexItem[],
|
||||
allMarkers: Map<string, SearchIndexItem>
|
||||
): SearchIndexItem[] {
|
||||
function resolveChildrenForMarker(marker: SearchIndexItem): SearchIndexItem {
|
||||
// マーカーのディープコピーを作成
|
||||
const resolvedMarker = { ...marker };
|
||||
|
||||
|
@ -147,7 +215,7 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR
|
|||
|
||||
if (childMarker) {
|
||||
// 子マーカーの子も再帰的に解決
|
||||
const resolvedChild = resolveChildrenReferences(childMarker);
|
||||
const resolvedChild = resolveChildrenForMarker(childMarker);
|
||||
children.push(resolvedChild);
|
||||
logger.info(`Resolved child ${childId} for parent ${marker.id}`);
|
||||
}
|
||||
|
@ -166,47 +234,78 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR
|
|||
return resolvedMarker;
|
||||
}
|
||||
|
||||
// すべてのルートマーカーに対して子の参照を解決
|
||||
const resolvedRootMarkers = rootMarkers.map(marker => {
|
||||
return resolveChildrenReferences(marker);
|
||||
});
|
||||
|
||||
// 特殊なプロパティ変換用の関数 - i18n参照処理を強化
|
||||
function formatSpecialProperty(key: string, value: any): string {
|
||||
// 値がundefinedの場合は空文字列を返す
|
||||
if (value === undefined) {
|
||||
return '""';
|
||||
// すべてのルートマーカーの子を解決
|
||||
return rootMarkers.map(marker => resolveChildrenForMarker(marker));
|
||||
}
|
||||
|
||||
// childrenが配列の場合は特別に処理
|
||||
if (key === 'children' && Array.isArray(value)) {
|
||||
return customStringify(value);
|
||||
}
|
||||
/**
|
||||
* マーカー数を数える(デバッグ用)
|
||||
*/
|
||||
function countMarkers(markers: SearchIndexItem[]): { totalMarkers: number, totalChildren: number } {
|
||||
let totalMarkers = markers.length;
|
||||
let totalChildren = 0;
|
||||
|
||||
// keywordsが配列の場合、特別に処理
|
||||
if (key === 'keywords' && Array.isArray(value)) {
|
||||
return `[${formatArrayForOutput(value)}]`;
|
||||
function countNested(items: SearchIndexItem[]): void {
|
||||
for (const marker of items) {
|
||||
if (marker.children && Array.isArray(marker.children)) {
|
||||
totalChildren += marker.children.length;
|
||||
totalMarkers += marker.children.length;
|
||||
countNested(marker.children as SearchIndexItem[]);
|
||||
}
|
||||
|
||||
// 文字列値の場合の特別処理
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 上記以外は通常の JSON5 文字列として返す
|
||||
return JSON5.stringify(value);
|
||||
countNested(markers);
|
||||
return { totalMarkers, totalChildren };
|
||||
}
|
||||
|
||||
// オブジェクトをカスタム形式に変換する関数 - i18n参照処理を強化
|
||||
/**
|
||||
* 最終的なTypeScriptファイルを出力
|
||||
*/
|
||||
function writeOutputFile(outputPath: string, resolvedRootMarkers: SearchIndexItem[]): void {
|
||||
try {
|
||||
const tsOutput = generateTypeScriptCode(resolvedRootMarkers);
|
||||
fs.writeFileSync(outputPath, tsOutput, 'utf-8');
|
||||
logger.info(`Successfully wrote search index to ${outputPath} with ${resolvedRootMarkers.length} root entries`);
|
||||
} catch (error) {
|
||||
logger.error('[create-search-index]: error writing output: ', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeScriptコード生成
|
||||
*/
|
||||
function generateTypeScriptCode(resolvedRootMarkers: SearchIndexItem[]): string {
|
||||
return `
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// This file was automatically generated by create-search-index.
|
||||
// Do not edit this file.
|
||||
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export type SearchIndexItem = {
|
||||
id: string;
|
||||
path?: string;
|
||||
label: string;
|
||||
keywords: string | string[];
|
||||
icon?: string;
|
||||
children?: (SearchIndexItem[] | string);
|
||||
};
|
||||
|
||||
export const searchIndexes:SearchIndexItem[] = ${customStringify(resolvedRootMarkers)} as const;
|
||||
|
||||
export type SearchIndex = typeof searchIndexes;
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* オブジェクトを特殊な形式の文字列に変換する
|
||||
* i18n参照を保持しつつ適切な形式に変換
|
||||
*/
|
||||
function customStringify(obj: any, depth = 0): string {
|
||||
const INDENT_STR = '\t';
|
||||
|
||||
|
@ -266,7 +365,46 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR
|
|||
return `{\n${entries.join(',\n')},\n${indent}}`;
|
||||
}
|
||||
|
||||
// 配列式の文字列表現を修正 - i18n参照を適切に処理
|
||||
/**
|
||||
* 特殊プロパティの書式設定
|
||||
*/
|
||||
function formatSpecialProperty(key: string, value: any): string {
|
||||
// 値がundefinedの場合は空文字列を返す
|
||||
if (value === undefined) {
|
||||
return '""';
|
||||
}
|
||||
|
||||
// childrenが配列の場合は特別に処理
|
||||
if (key === 'children' && Array.isArray(value)) {
|
||||
return customStringify(value);
|
||||
}
|
||||
|
||||
// keywordsが配列の場合、特別に処理
|
||||
if (key === 'keywords' && Array.isArray(value)) {
|
||||
return `[${formatArrayForOutput(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;
|
||||
}
|
||||
}
|
||||
|
||||
// 上記以外は通常の JSON5 文字列として返す
|
||||
return JSON5.stringify(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配列式の文字列表現を生成
|
||||
*/
|
||||
function formatArrayForOutput(items: any[]): string {
|
||||
return items.map(item => {
|
||||
// i18n.ts. 参照の文字列はそのままJavaScript式として出力
|
||||
|
@ -280,73 +418,58 @@ function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisR
|
|||
}).join(', ');
|
||||
}
|
||||
|
||||
// 最終出力用のデバッグ情報を生成
|
||||
let totalMarkers = resolvedRootMarkers.length;
|
||||
let totalChildren = 0;
|
||||
|
||||
function countNestedMarkers(markers: SearchIndexItem[]): void {
|
||||
for (const marker of markers) {
|
||||
if (marker.children && Array.isArray(marker.children)) {
|
||||
totalChildren += marker.children.length;
|
||||
totalMarkers += marker.children.length;
|
||||
countNestedMarkers(marker.children as SearchIndexItem[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
countNestedMarkers(resolvedRootMarkers);
|
||||
logger.info(`Total markers in tree: ${totalMarkers} (${resolvedRootMarkers.length} roots + ${totalChildren} nested children)`);
|
||||
|
||||
// 結果をTS形式で出力
|
||||
const tsOutput = `
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
/**
|
||||
* 要素ノードからテキスト内容を抽出する
|
||||
* 各抽出方法を分離して可読性を向上
|
||||
*/
|
||||
|
||||
// This file was automatically generated by create-search-index.
|
||||
// Do not edit this file.
|
||||
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export type SearchIndexItem = {
|
||||
id: string;
|
||||
path?: string;
|
||||
label: string;
|
||||
keywords: string | string[];
|
||||
icon?: string;
|
||||
children?: (SearchIndexItem[] | string);
|
||||
};
|
||||
|
||||
export const searchIndexes:SearchIndexItem[] = ${customStringify(resolvedRootMarkers)} as const;
|
||||
|
||||
export type SearchIndex = typeof searchIndexes;
|
||||
`;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(outputPath, tsOutput, 'utf-8');
|
||||
logger.info(`Successfully wrote search index to ${outputPath} with ${resolvedRootMarkers.length} root entries and ${totalChildren} nested children`);
|
||||
} catch (error) {
|
||||
logger.error('[create-search-index]: error: ', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 要素ノードからテキスト内容を抽出する関数 (Mustache構文のi18n参照にも対応)
|
||||
function extractElementText(node: any): string | null {
|
||||
function extractElementText(node: VueAstNode): 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*$/;
|
||||
// 1. 直接コンテンツの抽出を試行
|
||||
const directContent = extractDirectContent(node);
|
||||
if (directContent) return directContent;
|
||||
|
||||
// 子要素がない場合は終了
|
||||
if (!node.children || !Array.isArray(node.children)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. インターポレーションノードを検索
|
||||
const interpolationContent = extractInterpolationContent(node.children);
|
||||
if (interpolationContent) return interpolationContent;
|
||||
|
||||
// 3. 式ノードを検索
|
||||
const expressionContent = extractExpressionContent(node.children);
|
||||
if (expressionContent) return expressionContent;
|
||||
|
||||
// 4. テキストノードを検索
|
||||
const textContent = extractTextContent(node.children);
|
||||
if (textContent) return textContent;
|
||||
|
||||
// 5. 再帰的に子ノードを探索
|
||||
return extractNestedContent(node.children);
|
||||
}
|
||||
|
||||
/**
|
||||
* ノードから直接コンテンツを抽出
|
||||
*/
|
||||
function extractDirectContent(node: VueAstNode): string | null {
|
||||
if (!node.content) return null;
|
||||
|
||||
const content = typeof node.content === 'string'
|
||||
? node.content.trim()
|
||||
: (node.content.content ? node.content.content.trim() : null);
|
||||
|
||||
if (!content) return null;
|
||||
|
||||
// childrenが配列でない場合、content直接チェック (インラインテキスト対応)
|
||||
if (node.content) {
|
||||
const content = node.content.trim();
|
||||
console.log(`Direct node content found: ${content}`);
|
||||
|
||||
// Mustache構文のチェック
|
||||
const mustachePattern = /^\s*{{\s*(.*?)\s*}}\s*$/;
|
||||
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}`);
|
||||
|
@ -360,31 +483,31 @@ function extractElementText(node: any): string | null {
|
|||
}
|
||||
|
||||
// その他のコンテンツ
|
||||
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表現)
|
||||
/**
|
||||
* インターポレーションノード(Mustache)からコンテンツを抽出
|
||||
*/
|
||||
function extractInterpolationContent(children: VueAstNode[]): string | null {
|
||||
for (const child of children) {
|
||||
if (child.type === NODE_TYPES.INTERPOLATION) {
|
||||
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を再帰的に探索
|
||||
// オブジェクト形式の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;
|
||||
|
@ -394,35 +517,52 @@ function extractElementText(node: any): string | null {
|
|||
}
|
||||
}
|
||||
|
||||
// 最初のパスで i18n.ts. 参照パターンを持つものを探す (最優先)
|
||||
for (const child of node.children) {
|
||||
if (child.type === 2 && child.content) { // 式ノード
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 式ノードからコンテンツを抽出
|
||||
*/
|
||||
function extractExpressionContent(children: VueAstNode[]): string | null {
|
||||
// i18n.ts. 参照パターンを持つものを優先
|
||||
for (const child of children) {
|
||||
if (child.type === NODE_TYPES.EXPRESSION && child.content) {
|
||||
const expr = child.content.trim();
|
||||
|
||||
if (expr.includes('i18n.ts.')) {
|
||||
console.log(`Found i18n reference in expression node: ${expr}`);
|
||||
return expr; // i18n参照を見つけたら即座に返す
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2回目のパスで一般的な式を探す
|
||||
for (const child of node.children) {
|
||||
if (child.type === 2 && child.content) { // その他の式ノード
|
||||
// その他の式
|
||||
for (const child of children) {
|
||||
if (child.type === NODE_TYPES.EXPRESSION && 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) { // テキストノード
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* テキストノードからコンテンツを抽出
|
||||
*/
|
||||
function extractTextContent(children: VueAstNode[]): string | null {
|
||||
for (const child of children) {
|
||||
if (child.type === NODE_TYPES.TEXT && child.content) {
|
||||
const text = child.content.trim();
|
||||
|
||||
if (text) {
|
||||
console.log(`Found text node: ${text}`);
|
||||
|
||||
// Mustache構文のチェック
|
||||
const mustachePattern = /^\s*{{\s*(.*?)\s*}}\s*$/;
|
||||
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();
|
||||
|
@ -433,16 +573,25 @@ function extractElementText(node: any): string | null {
|
|||
}
|
||||
}
|
||||
|
||||
// 深さ優先で再帰的に探索 (子の子まで調べる)
|
||||
for (const child of node.children) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 子ノードを再帰的に探索してコンテンツを抽出
|
||||
*/
|
||||
function extractNestedContent(children: VueAstNode[]): string | null {
|
||||
for (const child of 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がなくても内部を調査
|
||||
} else if (child.type === NODE_TYPES.ELEMENT) {
|
||||
// childrenがなくても内部を調査
|
||||
const nestedContent = extractElementText(child);
|
||||
|
||||
if (nestedContent) {
|
||||
console.log(`Found content in childless element: ${nestedContent}`);
|
||||
return nestedContent;
|
||||
|
@ -453,17 +602,19 @@ function extractElementText(node: any): string | null {
|
|||
return null;
|
||||
}
|
||||
|
||||
// SearchLabelとSearchKeywordを探して抽出する関数 (スコープを適切に分離)
|
||||
function extractLabelsAndKeywords(nodes: any[]): { label: string | null, keywords: any[] } {
|
||||
/**
|
||||
* SearchLabelとSearchKeywordを探して抽出する関数
|
||||
*/
|
||||
function extractLabelsAndKeywords(nodes: VueAstNode[]): { 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[]) {
|
||||
function findComponents(nodes: VueAstNode[]) {
|
||||
for (const node of nodes) {
|
||||
if (node.type === 1) { // Element node
|
||||
if (node.type === NODE_TYPES.ELEMENT) {
|
||||
console.log(`Checking element: ${node.tag}`);
|
||||
|
||||
// SearchMarkerの場合は、その子要素は別スコープなのでスキップ
|
||||
|
@ -488,7 +639,7 @@ function extractLabelsAndKeywords(nodes: any[]): { label: string | null, keyword
|
|||
if (node.children && Array.isArray(node.children)) {
|
||||
for (const child of node.children) {
|
||||
// Mustacheインターポレーション
|
||||
if (child.type === 5 && child.content) {
|
||||
if (child.type === NODE_TYPES.INTERPOLATION && child.content) {
|
||||
// content内の式を取り出す
|
||||
const expression = child.content.content ||
|
||||
(child.content.type === 4 ? child.content.content : null) ||
|
||||
|
@ -502,13 +653,13 @@ function extractLabelsAndKeywords(nodes: any[]): { label: string | null, keyword
|
|||
}
|
||||
}
|
||||
// 式ノード
|
||||
else if (child.type === 2 && child.content && child.content.includes('i18n.ts.')) {
|
||||
else if (child.type === NODE_TYPES.EXPRESSION && 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) {
|
||||
else if (child.type === NODE_TYPES.TEXT && 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();
|
||||
|
@ -536,7 +687,7 @@ function extractLabelsAndKeywords(nodes: any[]): { label: string | null, keyword
|
|||
if (node.children && Array.isArray(node.children)) {
|
||||
for (const child of node.children) {
|
||||
// Mustacheインターポレーション
|
||||
if (child.type === 5 && child.content) {
|
||||
if (child.type === NODE_TYPES.INTERPOLATION && child.content) {
|
||||
// content内の式を取り出す
|
||||
const expression = child.content.content ||
|
||||
(child.content.type === 4 ? child.content.content : null) ||
|
||||
|
@ -551,14 +702,14 @@ function extractLabelsAndKeywords(nodes: any[]): { label: string | null, keyword
|
|||
}
|
||||
}
|
||||
// 式ノード
|
||||
else if (child.type === 2 && child.content && child.content.includes('i18n.ts.')) {
|
||||
else if (child.type === NODE_TYPES.EXPRESSION && 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) {
|
||||
else if (child.type === NODE_TYPES.TEXT && 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();
|
||||
|
@ -859,20 +1010,6 @@ function parseArrayExpression(expr: string): any[] {
|
|||
}
|
||||
}
|
||||
|
||||
// 配列式の文字列表現を修正 - 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,
|
||||
|
@ -941,7 +1078,7 @@ export async function analyzeVueProps(options: {
|
|||
interface MarkerRelation {
|
||||
parentId?: string;
|
||||
markerId: string;
|
||||
node: any;
|
||||
node: VueAstNode;
|
||||
}
|
||||
|
||||
async function processVueFile(
|
||||
|
@ -1059,9 +1196,9 @@ async function processVueFile(
|
|||
const childrenProp = parentNode.props?.find((prop: any) => prop.type === 7 && prop.name === 'bind' && prop.arg?.content === 'children');
|
||||
|
||||
// 親ノードの開始位置を特定
|
||||
const parentNodeStart = parentNode.loc.start.offset;
|
||||
const parentNodeStart = parentNode.loc!.start.offset;
|
||||
const endOfParentStartTag = parentNode.children && parentNode.children.length > 0
|
||||
? code.lastIndexOf('>', parentNode.children[0].loc.start.offset)
|
||||
? code.lastIndexOf('>', parentNode.children[0].loc!.start.offset)
|
||||
: code.indexOf('>', parentNodeStart);
|
||||
|
||||
if (endOfParentStartTag === -1) continue;
|
||||
|
@ -1072,8 +1209,8 @@ async function processVueFile(
|
|||
if (childrenProp) {
|
||||
// AST で :children 属性が検出された場合、それを更新
|
||||
try {
|
||||
const childrenStart = code.indexOf('[', childrenProp.exp.loc.start.offset);
|
||||
const childrenEnd = code.indexOf(']', childrenProp.exp.loc.start.offset);
|
||||
const childrenStart = code.indexOf('[', childrenProp.exp!.loc.start.offset);
|
||||
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, '"'));
|
||||
|
|
Loading…
Reference in New Issue