Refactor and Format codes

This commit is contained in:
tai-cha 2025-03-02 10:11:22 +00:00
parent ca80f4e273
commit c54299e522
1 changed files with 841 additions and 704 deletions

View File

@ -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, '"'));