UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

433 lines (398 loc) 17.7 kB
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 }; }