@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
237 lines (223 loc) • 7.56 kB
JavaScript
/**
* Shared path utilities for CodeHighlighter components
*
* Back navigation counting functions:
* - resolveRelativePath().backSteps: Net back navigation after path resolution (recommended for most cases)
* - countConsecutiveBackNavigation(): Raw consecutive '../' at start (for trimming leading patterns)
* - countBackNavigationOccurrences(): Total raw '../' count anywhere (for metadata analysis)
*/
/**
* Minimal file representation for path utilities
*/
/**
* Resolves a relative path by handling .. and . segments properly
* This mimics path.resolve() behavior for relative paths
* Returns the net back navigation steps after path resolution
*/
export function resolveRelativePath(relativePath) {
// Split the path into segments
const segments = relativePath.split('/');
const resolved = [];
let backSteps = 0;
for (const segment of segments) {
if (segment === '' || segment === '.') {
// Skip empty and current directory segments
continue;
} else if (segment === '..') {
if (resolved.length > 0) {
// Remove the last segment (go back one directory)
resolved.pop();
} else {
// Count back steps that go beyond the current directory
backSteps += 1;
}
} else {
// Regular directory or file segment
resolved.push(segment);
}
}
return {
resolvedPath: resolved.join('/'),
backSteps
};
}
/**
* Split a path into components, filtering out empty strings
*/
export function splitPath(path) {
return path.split('/').filter(Boolean);
}
/**
* Extract URL path components, filtering out empty strings
*/
export function getUrlParts(url) {
return splitPath(new URL(url).pathname);
}
/**
* Remove trailing slash from a path string
*/
export function removeTrailingSlash(path) {
return path.endsWith('/') ? path.slice(0, -1) : path;
}
/**
* Remove a specific number of back navigation prefixes from a path
*/
export function removeBackNavigationPrefix(path, count) {
let result = path;
for (let i = 0; i < count; i += 1) {
if (result.startsWith('../')) {
result = result.slice(3);
} else {
break;
}
}
return result;
}
/**
* Calculate the maximum back navigation levels from a collection of file paths
*
* This function analyzes all file paths in the collection and determines:
* 1. The maximum back navigation steps needed to reach any file (including metadata)
* 2. The maximum back navigation steps needed to reach any non-metadata file
*
* @param files - Record of relative file paths to file content (string) or file objects with optional metadata flag
* @returns Object containing:
* - maxBackNavigation: Maximum '../' steps needed to reach any file in the collection
* - maxSourceBackNavigation: Maximum '../' steps needed to reach any non-metadata file
*
* @example
* ```typescript
* const files = {
* 'component.tsx': 'url',
* '../shared/utils.ts': 'url',
* '../../docs/readme.md': { metadata: true }
* };
*
* const result = calculateMaxBackNavigation(files);
* // result: { maxBackNavigation: 2, maxSourceBackNavigation: 1 }
* ```
*/
export function calculateMaxBackNavigation(files) {
let maxBackNavigation = 0;
let maxSourceBackNavigation = 0;
for (const [relativePath, fileContent] of Object.entries(files)) {
// Check if this is a metadata file
const isMetadata = typeof fileContent === 'object' && fileContent.metadata;
const {
backSteps
} = resolveRelativePath(relativePath);
if (!isMetadata) {
maxSourceBackNavigation = Math.max(maxSourceBackNavigation, backSteps);
}
maxBackNavigation = Math.max(maxBackNavigation, backSteps);
}
return {
maxBackNavigation,
maxSourceBackNavigation
};
}
/**
* Calculate the maximum back navigation level from a collection of file paths
*
* This function analyzes file paths and determines the maximum number of back navigation
* steps needed to reach any non-metadata file. It ignores metadata files completely,
* focusing only on source code and other content files.
*
* @param files - Record of relative file paths to file content (string) or file objects with optional metadata flag
* @returns The maximum number of `../` steps needed to reach any non-metadata file
*
* @example
* ```typescript
* const files = {
* 'component.tsx': 'url',
* '../shared/utils.ts': 'url',
* '../../docs/readme.md': { metadata: true }, // ignored
* '../../../deep/source.js': 'url'
* };
*
* const maxSteps = calculateMaxSourceBackNavigation(files);
* // maxSteps: 3 (from '../../../deep/source.js')
* ```
*/
export function calculateMaxSourceBackNavigation(files) {
let maxSourceBackNavigation = 0;
for (const [relativePath, fileContent] of Object.entries(files)) {
// Check if this is a metadata file
const isMetadata = typeof fileContent === 'object' && fileContent.metadata;
// Skip metadata files - only consider non-metadata files for maxSourceBackNavigation
if (isMetadata) {
continue;
}
// Use path resolution to get the net back steps (most accurate)
const {
backSteps
} = resolveRelativePath(relativePath);
maxSourceBackNavigation = Math.max(maxSourceBackNavigation, backSteps);
}
return maxSourceBackNavigation;
}
/**
* Build a path from multiple components, filtering out empty parts
*/
export function buildPath(...segments) {
const parts = [];
for (const segment of segments) {
if (segment === undefined) {
continue;
}
if (Array.isArray(segment)) {
parts.push(...segment);
} else {
parts.push(segment);
}
}
return parts.filter(Boolean).map(removeTrailingSlash).join('/');
}
/**
* Create synthetic directory names for path structure
* Generates alphabetic names: 'a', 'b', 'c', ..., 'z', 'aa', 'ab', 'ac', etc.
* @param count - Number of directory names to generate
* @returns Array of alphabetic directory names
*/
export function createSyntheticDirectories(count) {
return Array.from({
length: count
}, (_, i) => {
let result = '';
let num = i + 1; // 1-based for Excel-style naming
while (num > 0) {
num -= 1; // Adjust for 0-based indexing
result = String.fromCharCode(97 + num % 26) + result;
num = Math.floor(num / 26);
}
return result;
});
}
/**
* Calculate the required back navigation pattern for metadata files positioning.
* This combines maxSourceBackNavigation from files with additional levels from metadataPrefix.
*
* @param files - Record of extraFiles to analyze for source back navigation
* @param metadataPrefix - Optional prefix path (e.g., 'src/', 'src/app/') that adds additional back navigation levels
* @returns A string of '../' patterns representing the back navigation needed
*
* @example
* ```typescript
* const files = { '../utils.ts': 'url', '../../shared.ts': 'url' };
* const result = calculateMetadataBackNavigation(files, 'src/');
* // result: '../../../' (maxSourceBackNavigation=2 + metadataPrefix=1)
* ```
*/
export function calculateMetadataBackNavigation(files, metadataPrefix) {
// Get the maxSourceBackNavigation from the file structure
let backLevels = 0;
if (files) {
backLevels = calculateMaxSourceBackNavigation(files);
}
if (metadataPrefix) {
// When a prefix is provided, add additional back navigation based on prefix depth
const prefixSegments = metadataPrefix.split('/').filter(Boolean);
backLevels += prefixSegments.length;
}
return '../'.repeat(backLevels);
}