@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
433 lines (398 loc) • 17.7 kB
JavaScript
import _regeneratorRuntime from "@babel/runtime/helpers/esm/regeneratorRuntime";
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import { parseImports } from "../loaderUtils/index.js";
import { parseFunctionParameters, extractBalancedBraces } from "./parseFunctionParameters.js";
/**
* Helper function to convert the new parseImports format to a Map
* that maps import names to their resolved paths
*/
function buildImportMap(importResult) {
var importMap = new Map();
Object.values(importResult.relative).forEach(function (_ref) {
var path = _ref.path,
names = _ref.names;
names.forEach(function (_ref2) {
var name = _ref2.name,
alias = _ref2.alias;
// Use alias if available, otherwise use the original name
var nameToUse = alias || name;
importMap.set(nameToUse, path);
});
});
return importMap;
}
/**
* Helper function to build a mapping from import aliases to their original named exports
*/
function buildNamedExportsMap(importResult) {
var namedExportsMap = new Map();
Object.values(importResult.relative).forEach(function (_ref3) {
var names = _ref3.names;
names.forEach(function (_ref4) {
var name = _ref4.name,
alias = _ref4.alias,
type = _ref4.type;
// Use alias if available, otherwise use the original name as key
var nameToUse = alias || name;
// Only map to the original export name for named imports
// Default imports should map to undefined since they don't have a specific named export
if (type === 'named') {
namedExportsMap.set(nameToUse, name);
} else {
namedExportsMap.set(nameToUse, undefined); // undefined for default/namespace imports
}
});
});
return namedExportsMap;
}
/**
* Parses a variants object string and maps variant names to their import paths
*/
function parseVariantsObject(variantsObjectStr, importMap, namedExportsMap, functionName, filePath) {
var demoImports = {};
var namedExports = {};
// Parse the demo object to extract key-value pairs
// Handle both { Default: BasicCode } and { Default } syntax
var objectContentRegex = /(\w+)(?:\s*:\s*(\w+))?/g;
var objectMatch = objectContentRegex.exec(variantsObjectStr);
while (objectMatch !== null) {
var _objectMatch = objectMatch,
_objectMatch2 = _slicedToArray(_objectMatch, 3),
key = _objectMatch2[1],
value = _objectMatch2[2];
var importName = value || key; // Use value if provided, otherwise use key (shorthand syntax)
if (importMap.has(importName)) {
demoImports[key] = importMap.get(importName);
namedExports[key] = namedExportsMap.get(importName);
} else {
// Throw error if any variant component is not imported
throw new Error("Invalid variants parameter in ".concat(functionName, " call in ").concat(filePath, ". ") + "Component '".concat(importName, "' is not imported. Make sure to import it first."));
}
objectMatch = objectContentRegex.exec(variantsObjectStr);
}
return {
variants: demoImports,
namedExports: namedExports
};
}
/**
* Parses variants parameter which can be either an object literal or a single component identifier
*/
function parseVariantsParameter(variantsParam, importMap, namedExportsMap, functionName, filePath) {
var trimmed = variantsParam.trim();
// If it's an object literal, use existing logic
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
return parseVariantsObject(trimmed, importMap, namedExportsMap, functionName, filePath);
}
// If it's a single identifier, map it to "Default"
if (importMap.has(trimmed)) {
return {
variants: {
Default: importMap.get(trimmed)
},
namedExports: {
Default: namedExportsMap.get(trimmed)
}
};
}
// Throw error if the identifier is not found in imports
throw new Error("Invalid variants parameter in ".concat(functionName, " call in ").concat(filePath, ". ") + "Component '".concat(trimmed, "' is not imported. Make sure to import it first."));
}
/**
* Validates that a URL parameter follows the expected convention
*/
function validateUrlParameter(url, functionName, filePath) {
var trimmedUrl = url.trim();
// Check for import.meta.url
if (trimmedUrl === 'import.meta.url') {
return;
}
// Check for CJS equivalent: require('url').pathToFileURL(__filename).toString()
// https://github.com/javiertury/babel-plugin-transform-import-meta#importmetaurl
var cjsPattern = /require\s*\(\s*['"`]url['"`]\s*\)\s*\.\s*pathToFileURL\s*\(\s*__filename\s*\)\s*\.\s*toString\s*\(\s*\)/;
if (cjsPattern.test(trimmedUrl)) {
return;
}
throw new Error("Invalid URL parameter in ".concat(functionName, " call in ").concat(filePath, ". ") + "Expected 'import.meta.url' or 'require('url').pathToFileURL(__filename).toString()' but got: ".concat(trimmedUrl));
}
/**
* Validates that a variants parameter is either an object mapping to imports or a single identifier
*/
function validateVariantsParameter(variantsParam, functionName, filePath) {
if (!variantsParam || variantsParam.trim() === '') {
throw new Error("Invalid variants parameter in ".concat(functionName, " call in ").concat(filePath, ". ") + "Expected an object mapping variant names to imports or a single component identifier.");
}
var trimmed = variantsParam.trim();
// Check if it's an object literal
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
return; // Valid object literal
}
// Check if it's a valid identifier (single component)
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(trimmed)) {
return; // Valid identifier
}
throw new Error("Invalid variants parameter in ".concat(functionName, " call in ").concat(filePath, ". ") + "Expected an object mapping variant names to imports or a single component identifier, but got: ".concat(trimmed));
}
/**
* Parses a file to extract a single create* factory call and its variants and options
* Only supports one create* call per file - will throw an error if multiple are found
* Returns null if no create* call is found
*/
export function parseCreateFactoryCall(_x, _x2) {
return _parseCreateFactoryCall.apply(this, arguments);
}
/**
* Finds create* factory calls in code, handling multiline cases
*/
function _parseCreateFactoryCall() {
_parseCreateFactoryCall = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(code, filePath) {
var _yield$parseImports, importResult, externals, importMap, namedExportsMap, createFactoryMatches, match, functionName, fullMatch, urlParam, variantsParam, optionsObjectStr, hasOptions, url, _parseVariantsParamet, variants, namedExports, options, hasPrecompute, precomputeValue, precomputeKeyStart, precomputeValueStart, precomputeValueEnd, nameMatch, slugMatch, skipPrecomputeMatch, precomputeInfo, transformedExternals, _i, _Object$entries, _Object$entries$_i, modulePath, externalImport, live;
return _regeneratorRuntime().wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return parseImports(code, filePath);
case 2:
_yield$parseImports = _context.sent;
importResult = _yield$parseImports.relative;
externals = _yield$parseImports.externals;
importMap = buildImportMap({
relative: importResult,
externals: externals
});
namedExportsMap = buildNamedExportsMap({
relative: importResult,
externals: externals
}); // Find all create* calls in the code
createFactoryMatches = findCreateFactoryCalls(code, filePath); // Enforce single create* call per file
if (!(createFactoryMatches.length > 1)) {
_context.next = 10;
break;
}
throw new Error("Multiple create* factory calls found in ".concat(filePath, ". Only one create* call per file is supported. Found ").concat(createFactoryMatches.length, " calls."));
case 10:
if (!(createFactoryMatches.length === 0)) {
_context.next = 12;
break;
}
return _context.abrupt("return", null);
case 12:
match = createFactoryMatches[0];
functionName = match.functionName, fullMatch = match.fullMatch, urlParam = match.urlParam, variantsParam = match.variantsParam, optionsObjectStr = match.optionsObjectStr, hasOptions = match.hasOptions; // Validate URL parameter
validateUrlParameter(urlParam, functionName, filePath);
// Validate variants parameter
validateVariantsParameter(variantsParam, functionName, filePath);
// Extract URL (typically import.meta.url)
url = urlParam.trim(); // Resolve variants for this specific create* call
_parseVariantsParamet = parseVariantsParameter(variantsParam, importMap, namedExportsMap, functionName, filePath), variants = _parseVariantsParamet.variants, namedExports = _parseVariantsParamet.namedExports; // Parse options object
options = {};
hasPrecompute = false;
// Extract name
nameMatch = optionsObjectStr.match(/name\s*:\s*['"`]([^'"`]+)['"`]/);
if (nameMatch) {
options.name = nameMatch[1];
}
// Extract slug
slugMatch = optionsObjectStr.match(/slug\s*:\s*['"`]([^'"`]+)['"`]/);
if (slugMatch) {
options.slug = slugMatch[1];
}
// Extract skipPrecompute
skipPrecomputeMatch = optionsObjectStr.match(/skipPrecompute\s*:\s*(true|false)/);
if (skipPrecomputeMatch) {
options.skipPrecompute = skipPrecomputeMatch[1] === 'true';
}
// Extract precompute value using robust parsing
precomputeInfo = extractPrecomputeFromOptions(optionsObjectStr);
if (precomputeInfo) {
hasPrecompute = true;
precomputeKeyStart = precomputeInfo.keyStart;
precomputeValueStart = precomputeInfo.valueStart;
precomputeValueEnd = precomputeInfo.valueEnd;
precomputeValue = precomputeInfo.value;
options.precompute = precomputeValue;
}
// Transform externals from parseImports format to simplified format
// Only include side-effect imports (where names array is empty)
transformedExternals = {};
for (_i = 0, _Object$entries = Object.entries(externals); _i < _Object$entries.length; _i++) {
_Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), modulePath = _Object$entries$_i[0], externalImport = _Object$entries$_i[1];
// Only include side-effect imports (empty names array)
if (externalImport.names.length === 0) {
transformedExternals[modulePath] = []; // Empty array for side-effect imports
}
}
// Detect if this is a live demo based on function name containing "Live" as a distinct component
// This catches: createLive, createLiveDemo, createDemoLive, etc.
// But avoids false positives like: createDelivery, delivery, etc.
live = /Live/.test(functionName);
return _context.abrupt("return", {
functionName: functionName,
url: url,
variants: variants,
namedExports: namedExports,
options: options,
fullMatch: fullMatch,
variantsObjectStr: variantsParam,
optionsObjectStr: optionsObjectStr,
hasOptions: hasOptions,
hasPrecompute: hasPrecompute,
precomputeValue: hasPrecompute ? precomputeValue : undefined,
externals: transformedExternals,
live: live,
precomputeKeyStart: hasPrecompute ? precomputeKeyStart : undefined,
precomputeValueStart: hasPrecompute ? precomputeValueStart : undefined,
precomputeValueEnd: hasPrecompute ? precomputeValueEnd : undefined
});
case 32:
case "end":
return _context.stop();
}
}, _callee);
}));
return _parseCreateFactoryCall.apply(this, arguments);
}
function findCreateFactoryCalls(code, filePath) {
var results = [];
// Find all create* function calls
var createFactoryRegex = /\b(create\w*)\s*\(/g;
var match = createFactoryRegex.exec(code);
while (match !== null) {
var functionName = match[1];
var startIndex = match.index;
var parenIndex = match.index + match[0].length - 1; // Position of opening parenthesis
// Find the matching closing parenthesis
var parenCount = 0;
var endIndex = -1;
for (var i = parenIndex; i < code.length; i += 1) {
if (code[i] === '(') {
parenCount += 1;
} else if (code[i] === ')') {
parenCount -= 1;
if (parenCount === 0) {
endIndex = i;
break;
}
}
}
if (endIndex === -1) {
match = createFactoryRegex.exec(code);
continue;
}
var fullMatch = code.substring(startIndex, endIndex + 1);
var content = code.substring(parenIndex + 1, endIndex);
// Split by commas at the top level, handling nested structures and comments
var _parseFunctionParamet = parseFunctionParameters(content),
parts = _parseFunctionParamet.parts,
objects = _parseFunctionParamet.objects;
// Validate the function follows the convention
if (parts.length < 2 || parts.length > 3) {
throw new Error("Invalid ".concat(functionName, " call in ").concat(filePath, ". ") + "Expected 2-3 parameters (url, variants, options?) but got ".concat(parts.length, " parameters. ") + "Functions starting with 'create' must follow the convention: create*(url, variants, options?)");
}
if (parts.length === 2) {
var _parts = _slicedToArray(parts, 1),
urlParam = _parts[0];
// The variants parameter can be either an object literal or a single identifier
var variantsParam = objects[1] || parts[1].trim();
results.push({
functionName: functionName,
fullMatch: fullMatch,
urlParam: urlParam.trim(),
variantsParam: variantsParam,
optionsObjectStr: '{}',
// Default empty options
hasOptions: false // No options parameter was provided
});
} else if (parts.length === 3) {
var _parts2 = _slicedToArray(parts, 1),
_urlParam = _parts2[0];
// The variants parameter can be either an object literal or a single identifier
var _variantsParam = objects[1] || parts[1].trim();
var optionsObjectStr = objects[2];
if (!optionsObjectStr) {
throw new Error("Invalid options parameter in ".concat(functionName, " call in ").concat(filePath, ". ") + "Expected an object but could not parse: ".concat(parts[2].trim()));
}
results.push({
functionName: functionName,
fullMatch: fullMatch,
urlParam: _urlParam.trim(),
variantsParam: _variantsParam,
optionsObjectStr: optionsObjectStr,
hasOptions: true // Options parameter was provided
});
}
match = createFactoryRegex.exec(code);
}
return results;
}
/**
* Extracts precompute property from options object using robust parsing
*/
function extractPrecomputeFromOptions(optionsObjectStr) {
// Find the precompute property using regex
var precomputeMatch = optionsObjectStr.match(/precompute\s*:\s*/);
if (!precomputeMatch) {
return null;
}
var keyStart = precomputeMatch.index;
var valueStartIndex = keyStart + precomputeMatch[0].length;
// Extract the remaining part after "precompute:"
var remainingStr = optionsObjectStr.substring(valueStartIndex);
// Try to extract a balanced object first
var objectValue = extractBalancedBraces(remainingStr);
if (objectValue) {
// It's an object value
var _actualValueStart = valueStartIndex;
while (_actualValueStart < optionsObjectStr.length && /\s/.test(optionsObjectStr[_actualValueStart])) {
_actualValueStart += 1;
}
var _valueEnd = _actualValueStart + objectValue.length;
return {
keyStart: keyStart,
valueStart: _actualValueStart,
valueEnd: _valueEnd,
value: objectValue // Keep object as string
};
}
// It's a simple value (true, false, etc.)
// Parse until comma, newline, or closing brace
var i = 0;
var inString = false;
var stringChar = '';
while (i < remainingStr.length) {
var _char = remainingStr[i];
if (!inString && (_char === '"' || _char === "'" || _char === '`')) {
inString = true;
stringChar = _char;
} else if (inString && _char === stringChar && remainingStr[i - 1] !== '\\') {
inString = false;
stringChar = '';
} else if (!inString && (_char === ',' || _char === '}' || _char === '\n')) {
break;
}
i += 1;
}
var valueStr = remainingStr.substring(0, i).trim();
// Calculate precise boundaries
var actualValueStart = valueStartIndex;
while (actualValueStart < optionsObjectStr.length && /\s/.test(optionsObjectStr[actualValueStart])) {
actualValueStart += 1;
}
var valueEnd = actualValueStart + valueStr.length;
// Parse the value
var parsedValue;
if (valueStr === 'true') {
parsedValue = true;
} else if (valueStr === 'false') {
parsedValue = false;
} else {
parsedValue = valueStr;
}
return {
keyStart: keyStart,
valueStart: actualValueStart,
valueEnd: valueEnd,
value: parsedValue
};
}