aboutsummaryrefslogtreecommitdiff
path: root/venv/lib/python3.8/site-packages/dash/extract-meta.js
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.8/site-packages/dash/extract-meta.js')
-rwxr-xr-xvenv/lib/python3.8/site-packages/dash/extract-meta.js795
1 files changed, 795 insertions, 0 deletions
diff --git a/venv/lib/python3.8/site-packages/dash/extract-meta.js b/venv/lib/python3.8/site-packages/dash/extract-meta.js
new file mode 100755
index 0000000..3427ef8
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/dash/extract-meta.js
@@ -0,0 +1,795 @@
+#!/usr/bin/env node
+if (process.env.MODULES_PATH) {
+ module.paths.push(process.env.MODULES_PATH);
+}
+let ts,
+ tsEnabled = true;
+try {
+ ts = require('typescript');
+} catch (e) {
+ ts = {};
+ tsEnabled = false;
+}
+const fs = require('fs');
+const path = require('path');
+const reactDocs = require('react-docgen');
+
+const args = process.argv.slice(2);
+const src = args.slice(2);
+const ignorePattern = args[0] ? new RegExp(args[0]) : null;
+const reservedPatterns = args[1]
+ ? args[1].split('|').map(part => new RegExp(part))
+ : [];
+
+function help() {
+ console.error('usage: ');
+ console.error(
+ 'extract-meta ^fileIgnorePattern ^forbidden$|^props$|^patterns$' +
+ ' path/to/component(s) [path/to/more/component(s) ...] > metadata.json'
+ );
+}
+
+if (!src.length) {
+ help();
+ process.exit(1);
+}
+
+function getTsConfigCompilerOptions() {
+ // Since extract-meta can be run on JavaScript sources, if trying to get the
+ // config doesn't work, we can fall back gracefully.
+ try {
+ const tsconfig = ts.getParsedCommandLineOfConfigFile('tsconfig.json', { esModuleInterop: true }, ts.sys);
+ return tsconfig?.options ?? {};
+ } catch {
+ return {};
+ }
+}
+
+let failedBuild = false;
+const excludedDocProps = ['setProps', 'id', 'className', 'style'];
+
+const isOptional = prop => (prop.getFlags() & ts.SymbolFlags.Optional) !== 0;
+
+const PRIMITIVES = [
+ 'string',
+ 'number',
+ 'bool',
+ 'any',
+ 'array',
+ 'object',
+ 'node'
+];
+
+// These types take too long to parse because of heavy nesting.
+const BANNED_TYPES = [
+ 'Document',
+ 'ShadowRoot',
+ 'ChildNode',
+ 'ParentNode',
+];
+const unionSupport = PRIMITIVES.concat('boolean', 'Element', 'enum');
+
+const reArray = new RegExp(`(${unionSupport.join('|')})\\[\\]`);
+
+const isArray = rawType => reArray.test(rawType);
+
+const isUnionLiteral = typeObj =>
+ typeObj.types.every(
+ t =>
+ t.getFlags() &
+ (ts.TypeFlags.StringLiteral |
+ ts.TypeFlags.NumberLiteral |
+ ts.TypeFlags.EnumLiteral |
+ ts.TypeFlags.Undefined)
+ );
+
+function logError(error, filePath) {
+ if (filePath) {
+ process.stderr.write(`Error with path ${filePath}\n`);
+ }
+ process.stderr.write(error + '\n');
+ if (error instanceof Error) {
+ process.stderr.write(error.stack + '\n');
+ }
+}
+
+function isReservedPropName(propName) {
+ reservedPatterns.forEach(reservedPattern => {
+ if (reservedPattern.test(propName)) {
+ process.stderr.write(
+ `\nERROR: "${propName}" matches reserved word ` +
+ `pattern: ${reservedPattern.toString()}\n`
+ );
+ failedBuild = true;
+ }
+ });
+ return failedBuild;
+}
+
+function checkDocstring(name, value) {
+ if (
+ !value ||
+ (value.length < 1 && !excludedDocProps.includes(name.split('.').pop()))
+ ) {
+ logError(`\nDescription for ${name} is missing!`);
+ }
+}
+
+function docstringWarning(doc) {
+ checkDocstring(doc.displayName, doc.description);
+
+ Object.entries(doc.props || {}).forEach(([name, p]) =>
+ checkDocstring(`${doc.displayName}.${name}`, p.description)
+ );
+}
+
+function zipArrays(...arrays) {
+ const arr = [];
+ for (let i = 0; i <= arrays[0].length - 1; i++) {
+ arr.push(arrays.map(a => a[i]));
+ }
+ return arr;
+}
+
+function cleanPath(filepath) {
+ return filepath.split(path.sep).join('/');
+}
+
+function parseJSX(filepath) {
+ try {
+ const src = fs.readFileSync(filepath);
+ const doc = reactDocs.parse(src);
+ Object.keys(doc.props).forEach(propName =>
+ isReservedPropName(propName)
+ );
+ docstringWarning(doc);
+ return doc;
+ } catch (error) {
+ logError(error, filepath);
+ }
+}
+
+function gatherComponents(sources, components = {}) {
+ const names = [];
+ const filepaths = [];
+
+ const gather = filepath => {
+ if (ignorePattern && ignorePattern.test(filepath)) {
+ return;
+ }
+ const extension = path.extname(filepath);
+ if (['.jsx', '.js'].includes(extension)) {
+ components[cleanPath(filepath)] = parseJSX(filepath);
+ } else if (filepath.endsWith('.tsx')) {
+ try {
+ const name = /(.*)\.tsx/.exec(path.basename(filepath))[1];
+ filepaths.push(filepath);
+ names.push(name);
+ } catch (err) {
+ process.stderr.write(
+ `ERROR: Invalid component file ${filepath}: ${err}`
+ );
+ }
+ }
+ };
+
+ sources.forEach(sourcePath => {
+ if (fs.lstatSync(sourcePath).isDirectory()) {
+ fs.readdirSync(sourcePath).forEach(f => {
+ const filepath = path.join(sourcePath, f);
+ if (fs.lstatSync(filepath).isDirectory()) {
+ gatherComponents([filepath], components);
+ } else {
+ gather(filepath);
+ }
+ });
+ } else {
+ gather(sourcePath);
+ }
+ });
+
+ if (!tsEnabled) {
+ return components;
+ }
+
+ const program = ts.createProgram(filepaths, getTsConfigCompilerOptions());
+ const checker = program.getTypeChecker();
+
+ const coerceValue = t => {
+ // May need to improve for shaped/list literals.
+ if (t.isStringLiteral()) return `'${t.value}'`;
+ return t.value;
+ };
+
+ const getComponentFromExport = exp => {
+ const decl = exp.valueDeclaration || exp.declarations[0];
+ const type = checker.getTypeOfSymbolAtLocation(exp, decl);
+ const typeSymbol = type.symbol || type.aliasSymbol;
+
+ if (!typeSymbol) {
+ return exp;
+ }
+
+ const symbolName = typeSymbol.getName();
+
+ if (
+ (symbolName === 'MemoExoticComponent' ||
+ symbolName === 'ForwardRefExoticComponent') &&
+ exp.valueDeclaration &&
+ ts.isExportAssignment(exp.valueDeclaration) &&
+ ts.isCallExpression(exp.valueDeclaration.expression)
+ ) {
+ const component = checker.getSymbolAtLocation(
+ exp.valueDeclaration.expression.arguments[0]
+ );
+
+ if (component) return component;
+ }
+ return exp;
+ };
+
+ const getParent = node => {
+ let parent = node;
+ while (parent.parent) {
+ if (parent.parent.kind === ts.SyntaxKind.SourceFile) {
+ // We want the parent before the source file.
+ break;
+ }
+ parent = parent.parent;
+ }
+ return parent;
+ };
+
+ const getEnum = typeObj => ({
+ name: 'enum',
+ value: typeObj.types.map(t => ({
+ value: coerceValue(t),
+ computed: false
+ }))
+ });
+
+ const getUnion = (typeObj, propObj, parentType) => {
+ let name = 'union',
+ value;
+
+ // Union only do base types
+ value = typeObj.types
+ .filter(t => {
+ let typeName = t.intrinsicName;
+ if (!typeName) {
+ if (t.members) {
+ typeName = 'object';
+ }
+ }
+ if (t.value) {
+ // A literal value
+ return true;
+ }
+ return (
+ unionSupport.includes(typeName) ||
+ isArray(checker.typeToString(t))
+ );
+ })
+ .map(t => t.value ? {name: 'literal', value: t.value} : getPropType(t, propObj, parentType));
+
+ if (!value.length) {
+ name = 'any';
+ value = undefined;
+ }
+ return {
+ name,
+ value
+ };
+ };
+
+ const getPropTypeName = propName => {
+ if (propName.includes('=>') || propName === 'Function') {
+ return 'func';
+ } else if (propName === 'boolean') {
+ return 'bool';
+ } else if (propName === '[]') {
+ return 'array';
+ } else if (
+ propName === 'Element' ||
+ propName === 'ReactNode' ||
+ propName === 'ReactElement'
+ ) {
+ return 'node';
+ }
+ return propName;
+ };
+
+ const getPropType = (propType, propObj, parentType = null) => {
+ // Types can get namespace prefixes or not.
+ let name = checker.typeToString(propType).replace(/^React\./, '');
+ let value, elements;
+ const raw = name;
+
+ const newParentType = (parentType || []).concat(raw)
+
+ if (propType.isUnion()) {
+ if (isUnionLiteral(propType)) {
+ return {...getEnum(propType), raw};
+ } else if (raw.includes('|')) {
+ return {...getUnion(propType, propObj, newParentType), raw};
+ }
+ }
+
+ name = getPropTypeName(name);
+
+ // Shapes & array support.
+ if (!PRIMITIVES.concat('enum', 'func', 'union').includes(name)) {
+ if (
+ // Excluding object with arrays in the raw.
+ (name.includes('[]') && name.endsWith("]")) ||
+ name.includes('Array')
+ ) {
+ name = 'arrayOf';
+ const replaced = raw.replace('[]', '');
+ if (unionSupport.includes(replaced)) {
+ // Simple types are easier.
+ value = {
+ name: getPropTypeName(replaced),
+ raw: replaced
+ };
+ } else {
+ // Complex types get the type parameter (Array<type>)
+ const [nodeType] = checker.getTypeArguments(propType);
+
+ if (nodeType) {
+ value = getPropType(
+ nodeType, propObj, newParentType,
+ );
+ } else {
+ // Not sure, might be unsupported here.
+ name = 'array';
+ }
+ }
+ } else if (
+ name === 'tuple' ||
+ (name.startsWith('[') && name.endsWith(']'))
+ ) {
+ name = 'tuple';
+ elements = propType.resolvedTypeArguments.map(
+ t => getPropType(t, propObj, newParentType)
+ );
+ } else if (
+ BANNED_TYPES.includes(name) ||
+ (parentType && parentType.includes(name))
+ ) {
+ console.error(`Warning nested type: ${name}`);
+ name = 'any';
+ } else {
+ name = 'shape';
+ // If the type is declared as union it will have a types attribute.
+ if (propType.types && propType.types.length) {
+ if (isUnionLiteral(propType)) {
+ return {...getEnum(propType), raw};
+ }
+ return {
+ ...getUnion(propType, propObj, newParentType),
+ raw
+ };
+ } else if (propType.indexInfos && propType.indexInfos.length) {
+ const {type} = propType.indexInfos[0];
+ name = 'objectOf';
+ value = getPropType(type, propObj, newParentType);
+ } else {
+ value = getProps(
+ checker.getPropertiesOfType(propType),
+ propObj,
+ [],
+ {},
+ true,
+ newParentType,
+ );
+ }
+ }
+ }
+
+ return {
+ name,
+ value,
+ elements,
+ raw
+ };
+ };
+
+ const getDefaultProps = (symbol, source) => {
+ const statements = source.statements.filter(
+ stmt =>
+ (!!stmt.name &&
+ checker.getSymbolAtLocation(stmt.name) === symbol) ||
+ ts.isExpressionStatement(stmt) ||
+ ts.isVariableStatement(stmt)
+ );
+ return statements.reduce((acc, statement) => {
+ let propMap = {};
+
+ statement.getChildren().forEach(child => {
+ let {right} = child;
+ if (right && ts.isIdentifier(right)) {
+ const value = source.locals.get(right.escapedText);
+ if (
+ value &&
+ value.valueDeclaration &&
+ ts.isVariableDeclaration(value.valueDeclaration) &&
+ value.valueDeclaration.initializer
+ ) {
+ right = value.valueDeclaration.initializer;
+ }
+ }
+ if (right) {
+ const {properties} = right;
+ if (properties) {
+ propMap = getDefaultPropsValues(properties);
+ }
+ }
+ });
+
+ return {
+ ...acc,
+ ...propMap
+ };
+ }, {});
+ };
+
+ const getPropComment = symbol => {
+ // Doesn't work too good with the JsDocTags losing indentation.
+ // But used only in props should be fine.
+ const comment = symbol.getDocumentationComment();
+ const tags = symbol.getJsDocTags();
+ if (comment && comment.length) {
+ return comment
+ .map(c => c.text)
+ .concat(
+ tags.map(t =>
+ ['@', t.name].concat((t.text || []).map(e => e.text))
+ )
+ )
+ .join('\n');
+ }
+ return '';
+ };
+
+ const getPropsForFunctionalComponent = type => {
+ const callSignatures = type.getCallSignatures();
+
+ for (const sig of callSignatures) {
+ const params = sig.getParameters();
+ if (params.length === 0) {
+ continue;
+ }
+
+ // There is only one parameter for functional components: props
+ const p = params[0];
+ if (p.name === 'props' || params.length === 1) {
+ return p;
+ }
+ }
+ return null;
+ };
+
+ const getPropsForClassComponent = (typeSymbol, source, defaultProps) => {
+ const childs = source.getChildAt(0);
+ let stop;
+
+ for (let i = 0, n = childs.getChildCount(); i < n && !stop; i++) {
+ const c = childs.getChildAt(i);
+ if (!ts.isClassDeclaration(c)) continue;
+
+ if (!c.heritageClauses) continue;
+
+ for (const clause of c.heritageClauses) {
+ if (clause.token !== ts.SyntaxKind.ExtendsKeyword) continue;
+ const t = clause.types[0];
+ const propType = t.typeArguments[0];
+
+ const type = checker.getTypeFromTypeNode(propType);
+
+ return getProps(
+ type.getProperties(),
+ typeSymbol,
+ [],
+ defaultProps
+ );
+ }
+ }
+ };
+
+ const getDefaultPropsValues = properties =>
+ properties.reduce((acc, p) => {
+ if (!p.name || !p.initializer) {
+ return acc;
+ }
+ let propName, value;
+
+ switch (p.name.kind) {
+ case ts.SyntaxKind.NumericLiteral:
+ case ts.SyntaxKind.StringLiteral:
+ case ts.SyntaxKind.Identifier:
+ propName = p.name.text;
+ break;
+ case ts.SyntaxKind.ComputedPropertyName:
+ propName = p.name.getText();
+ break;
+ }
+
+ const {initializer} = p;
+
+ switch (initializer.kind) {
+ case ts.SyntaxKind.StringLiteral:
+ value = `'${initializer.text}'`;
+ break;
+ case ts.SyntaxKind.NumericLiteral:
+ value = initializer.text;
+ break;
+ case ts.SyntaxKind.NullKeyword:
+ value = 'null';
+ break;
+ case ts.SyntaxKind.FalseKeyword:
+ value = 'false';
+ break;
+ case ts.SyntaxKind.TrueKeyword:
+ value = 'true';
+ break;
+ default:
+ try {
+ value = initializer.getText();
+ } catch (e) {
+ value = undefined;
+ }
+ }
+
+ acc[propName] = {value, computed: false};
+
+ return acc;
+ }, {});
+
+ const getDefaultPropsForClassComponent = (type, source) => {
+ // For class component, the type has its own property, then get the
+ // first declaration and one of them will be either
+ // an ObjectLiteralExpression or an Identifier which get in the
+ // newChild with the proper props.
+ const defaultProps = type.getProperty('defaultProps');
+ if (!defaultProps) {
+ return {};
+ }
+ const decl = defaultProps.getDeclarations()[0];
+ let propValues = {};
+
+ decl.getChildren().forEach(child => {
+ let newChild = child;
+
+ if (ts.isIdentifier(child)) {
+ // There should be two identifier, the first is ignored.
+ const value = source.locals.get(child.escapedText);
+ if (
+ value &&
+ value.valueDeclaration &&
+ ts.isVariableDeclaration(value.valueDeclaration) &&
+ value.valueDeclaration.initializer
+ ) {
+ newChild = value.valueDeclaration.initializer;
+ }
+ }
+
+ const {properties} = newChild;
+ if (properties) {
+ propValues = getDefaultPropsValues(properties);
+ }
+ });
+ return propValues;
+ };
+
+ const getProps = (
+ properties,
+ propsObj,
+ baseProps = [],
+ defaultProps = {},
+ flat = false,
+ parentType = null,
+ ) => {
+ const results = {};
+
+ properties.forEach(prop => {
+ const name = prop.getName();
+ if (isReservedPropName(name)) {
+ return;
+ }
+ const propType = checker.getTypeOfSymbolAtLocation(
+ prop,
+ propsObj.valueDeclaration
+ );
+ const baseProp = baseProps.find(p => p.getName() === name);
+ const defaultValue = defaultProps[name];
+
+ const required =
+ !isOptional(prop) &&
+ (!baseProp || !isOptional(baseProp)) &&
+ defaultValue === undefined;
+
+ const description = getPropComment(prop);
+
+ let result = {
+ description,
+ required,
+ defaultValue
+ };
+ const type = getPropType(propType, propsObj, parentType);
+ // root object is inserted as type,
+ // otherwise it's flat in the value prop.
+ if (!flat) {
+ result.type = type;
+ } else {
+ result = {...result, ...type};
+ }
+
+ results[name] = result;
+ });
+
+ return results;
+ };
+
+ const getPropInfo = (propsObj, defaultProps) => {
+ const propsType = checker.getTypeOfSymbolAtLocation(
+ propsObj,
+ propsObj.valueDeclaration
+ );
+ const baseProps = propsType.getApparentProperties();
+ let propertiesOfProps = baseProps;
+
+ if (propsType.isUnionOrIntersection()) {
+ propertiesOfProps = [
+ ...checker.getAllPossiblePropertiesOfTypes(propsType.types),
+ ...baseProps
+ ];
+
+ if (!propertiesOfProps.length) {
+ const subTypes = checker.getAllPossiblePropertiesOfTypes(
+ propsType.types.reduce(
+ (all, t) => [...all, ...(t.types || [])],
+ []
+ )
+ );
+ propertiesOfProps = [...subTypes, ...baseProps];
+ }
+ }
+
+ return getProps(propertiesOfProps, propsObj, baseProps, defaultProps);
+ };
+
+ zipArrays(filepaths, names).forEach(([filepath, name]) => {
+ const source = program.getSourceFile(filepath);
+ const moduleSymbol = checker.getSymbolAtLocation(source);
+ const exports = checker.getExportsOfModule(moduleSymbol);
+
+ exports.forEach(exp => {
+ let rootExp = getComponentFromExport(exp);
+ const declaration =
+ rootExp.valueDeclaration || rootExp.declarations[0];
+ const type = checker.getTypeOfSymbolAtLocation(
+ rootExp,
+ declaration
+ );
+
+ let commentSource = rootExp;
+ const typeSymbol = type.symbol || type.aliasSymbol;
+ const originalName = rootExp.getName();
+
+ if (!rootExp.valueDeclaration) {
+ if (
+ originalName === 'default' &&
+ !typeSymbol &&
+ (rootExp.flags & ts.SymbolFlags.Alias) !== 0
+ ) {
+ // Some type of Exotic?
+ commentSource =
+ checker.getAliasedSymbol(
+ commentSource
+ ).valueDeclaration;
+ } else if (!typeSymbol) {
+ // Invalid component
+ return null;
+ } else {
+ // Function components.
+ rootExp = typeSymbol;
+ commentSource = rootExp.valueDeclaration || rootExp.declarations[0];
+ if (
+ commentSource &&
+ commentSource.parent
+ ) {
+ // Function with export later like `const MyComponent = (props) => <></>;`
+ commentSource = getParent(
+ commentSource.parent
+ );
+ }
+ }
+ } else if (
+ type.symbol &&
+ (ts.isPropertyAccessExpression(declaration) ||
+ ts.isPropertyDeclaration(declaration))
+ ) {
+ commentSource = type.symbol.declarations[0];
+ }
+
+ if (commentSource.valueDeclaration) {
+ commentSource = commentSource.valueDeclaration; // class components
+ if (
+ commentSource.parent &&
+ commentSource.parent.kind !== ts.SyntaxKind.SourceFile
+ ) {
+ // Memo components
+ commentSource = commentSource.parent;
+ }
+ }
+
+ let defaultProps = getDefaultProps(typeSymbol, source);
+ const propsType = getPropsForFunctionalComponent(type);
+ const isContext = !!type.getProperty('isContext');
+
+ let props;
+
+ if (propsType) {
+ if (
+ propsType.valueDeclaration &&
+ propsType.valueDeclaration.name &&
+ propsType.valueDeclaration.name.elements &&
+ propsType.valueDeclaration.name.elements.length
+ ) {
+ defaultProps = getDefaultPropsValues(propsType.valueDeclaration.name.elements);
+ }
+ props = getPropInfo(propsType, defaultProps);
+ } else {
+ defaultProps = getDefaultPropsForClassComponent(type, source);
+ props = getPropsForClassComponent(
+ typeSymbol,
+ source,
+ defaultProps
+ );
+ }
+
+ if (!props) {
+ // Ensure empty components has props.
+ props = {};
+ }
+
+ const fullText = source.getFullText();
+ let description = '';
+ const commentRanges = ts.getLeadingCommentRanges(
+ fullText,
+ commentSource.getFullStart()
+ );
+ if (commentRanges && commentRanges.length) {
+ description = commentRanges
+ .map(r =>
+ fullText
+ .slice(r.pos + 4, r.end - 3)
+ .split('\n')
+ .map(s => s.replace(/^(\s*\*?\s)/, ''))
+ .filter(e => e)
+ .join('\n')
+ )
+ .join('');
+ }
+ const doc = {
+ displayName: name,
+ description,
+ props,
+ isContext
+ };
+ docstringWarning(doc);
+ components[cleanPath(filepath)] = doc;
+ });
+ });
+
+ return components;
+}
+
+const metadata = gatherComponents(Array.isArray(src) ? src : [src]);
+if (!failedBuild) {
+ process.stdout.write(JSON.stringify(metadata, null, 2));
+} else {
+ logError('extract-meta failed');
+ process.exit(1);
+}