github-advanced-security[bot] commented on code in PR #36498: URL: https://github.com/apache/superset/pull/36498#discussion_r2621241540
########## docs/scripts/generate-extension-components.mjs: ########## @@ -0,0 +1,674 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * This script scans for Storybook stories in superset-core/src and generates + * MDX documentation pages for the developer portal. All components in + * superset-core are considered extension-compatible by virtue of their location. + * + * Usage: node scripts/generate-extension-components.mjs + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const ROOT_DIR = path.resolve(__dirname, '../..'); +const DOCS_DIR = path.resolve(__dirname, '..'); +const OUTPUT_DIR = path.join( + DOCS_DIR, + 'developer_portal/extensions/components' +); +const TYPES_OUTPUT_DIR = path.join(DOCS_DIR, 'src/types/apache-superset-core'); +const TYPES_OUTPUT_PATH = path.join(TYPES_OUTPUT_DIR, 'index.d.ts'); +const SUPERSET_CORE_DIR = path.join( + ROOT_DIR, + 'superset-frontend/packages/superset-core' +); + +/** + * Find all story files in the superset-core package + */ +async function findStoryFiles() { + const files = []; + + // Use fs to recursively find files since glob might not be available + function walkDir(dir) { + if (!fs.existsSync(dir)) return; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + walkDir(fullPath); + } else if (entry.name.endsWith('.stories.tsx')) { + files.push(fullPath); + } + } + } + + walkDir(path.join(SUPERSET_CORE_DIR, 'src')); + return files; +} + +/** + * Parse a story file and extract metadata + * + * All stories in superset-core are considered extension-compatible + * by virtue of their location - no tag needed. + */ +function parseStoryFile(filePath) { + const content = fs.readFileSync(filePath, 'utf-8'); + + // Extract component name from title + const titleMatch = content.match(/title:\s*['"]([^'"]+)['"]/); + const title = titleMatch ? titleMatch[1] : null; + + // Extract component name (last part of title path) + const componentName = title ? title.split('/').pop() : null; + + // Extract description from parameters + // Handle concatenated strings like: 'part1 ' + 'part2' + let description = ''; + + // First try to find the description block + const descBlockMatch = content.match( + /description:\s*{\s*component:\s*([\s\S]*?)\s*},?\s*}/ + ); + + if (descBlockMatch) { + const descBlock = descBlockMatch[1]; + // Extract all string literals and concatenate them + const stringParts = []; + const stringMatches = descBlock.matchAll(/['"]([^'"]*)['"]/g); + for (const match of stringMatches) { + stringParts.push(match[1]); + } + description = stringParts.join('').trim(); + } + + // Extract package info + const packageMatch = content.match(/package:\s*['"]([^'"]+)['"]/); + const packageName = packageMatch ? packageMatch[1] : '@apache-superset/core/ui'; + + // Extract import path - handle double-quoted strings containing single quotes + // Match: importPath: "import { Alert } from '@apache-superset/core';" + const importMatchDouble = content.match(/importPath:\s*"([^"]+)"/); + const importMatchSingle = content.match(/importPath:\s*'([^']+)'/); + let importPath = `import { ${componentName} } from '${packageName}';`; + if (importMatchDouble) { + importPath = importMatchDouble[1]; + } else if (importMatchSingle) { + importPath = importMatchSingle[1]; + } + + // Get the directory containing the story to find the component + const storyDir = path.dirname(filePath); + const componentFile = path.join(storyDir, 'index.tsx'); + const hasComponentFile = fs.existsSync(componentFile); + + // Try to extract props interface from component file (for future use) + if (hasComponentFile) { + // Read component file - props extraction reserved for future enhancement + // const componentContent = fs.readFileSync(componentFile, 'utf-8'); + } + + // Extract story exports (named exports that aren't the default) + const storyExports = []; + const exportMatches = content.matchAll( + /export\s+(?:const|function)\s+(\w+)/g + ); + for (const match of exportMatches) { + if (match[1] !== 'default') { + storyExports.push(match[1]); + } + } + + return { + filePath, + title, + componentName, + description, + packageName, + importPath, + storyExports, + hasComponentFile, + relativePath: path.relative(ROOT_DIR, filePath), + }; +} + +/** + * Extract argTypes/args from story content for generating controls + */ +function extractArgsAndControls(content, componentName, storyContent) { + // Look for InteractiveX.args pattern - handle multi-line objects + const argsMatch = content.match( + new RegExp(`Interactive${componentName}\\.args\\s*=\\s*\\{([\\s\\S]*?)\\};`, 's') + ); + + // Look for argTypes + const argTypesMatch = content.match( + new RegExp(`Interactive${componentName}\\.argTypes\\s*=\\s*\\{([\\s\\S]*?)\\};`, 's') + ); + + const args = {}; + const controls = []; + const propDescriptions = {}; + + if (argsMatch) { + // Parse args - handle strings, booleans, numbers + const argsContent = argsMatch[1]; + const argLines = argsContent.matchAll(/(\w+):\s*(['"]([^'"]*(?:\\.[^'"]*)*)['""]|true|false|\d+)/g); Review Comment: ## Inefficient regular expression This part of the regular expression may cause exponential backtracking on strings starting with 'a:"\\a' and containing many repetitions of '\\!'. [Show more details](https://github.com/apache/superset/security/code-scanning/2081) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
