UNPKG

@storybook/angular

Version:

Storybook for Angular: Develop Angular components in isolation with hot reloading.

259 lines (258 loc) • 9.99 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractComponentDescription = exports.extractArgTypes = exports.extractArgTypesFromData = exports.extractType = exports.findComponentByName = exports.checkValidCompodocJson = exports.checkValidComponentOrDirective = exports.getCompodocJson = exports.setCompodocJson = exports.isMethod = void 0; /* eslint-disable no-underscore-dangle */ const client_logger_1 = require("storybook/internal/client-logger"); const global_1 = require("@storybook/global"); const isMethod = (methodOrProp) => { return methodOrProp.args !== undefined; }; exports.isMethod = isMethod; const setCompodocJson = (compodocJson) => { global_1.global.__STORYBOOK_COMPODOC_JSON__ = compodocJson; }; exports.setCompodocJson = setCompodocJson; const getCompodocJson = () => global_1.global.__STORYBOOK_COMPODOC_JSON__; exports.getCompodocJson = getCompodocJson; const checkValidComponentOrDirective = (component) => { if (!component.name) { throw new Error(`Invalid component ${JSON.stringify(component)}`); } }; exports.checkValidComponentOrDirective = checkValidComponentOrDirective; const checkValidCompodocJson = (compodocJson) => { if (!compodocJson || !compodocJson.components) { throw new Error('Invalid compodoc JSON'); } }; exports.checkValidCompodocJson = checkValidCompodocJson; const hasDecorator = (item, decoratorName) => item.decorators && item.decorators.find((x) => x.name === decoratorName); const mapPropertyToSection = (item) => { if (hasDecorator(item, 'ViewChild')) { return 'view child'; } if (hasDecorator(item, 'ViewChildren')) { return 'view children'; } if (hasDecorator(item, 'ContentChild')) { return 'content child'; } if (hasDecorator(item, 'ContentChildren')) { return 'content children'; } return 'properties'; }; const mapItemToSection = (key, item) => { switch (key) { case 'methods': case 'methodsClass': return 'methods'; case 'inputsClass': return 'inputs'; case 'outputsClass': return 'outputs'; case 'properties': case 'propertiesClass': if ((0, exports.isMethod)(item)) { throw new Error("Cannot be of type Method if key === 'propertiesClass'"); } return mapPropertyToSection(item); default: throw new Error(`Unknown key: ${key}`); } }; const findComponentByName = (name, compodocJson) => compodocJson.components.find((c) => c.name === name) || compodocJson.directives.find((c) => c.name === name) || compodocJson.pipes.find((c) => c.name === name) || compodocJson.injectables.find((c) => c.name === name) || compodocJson.classes.find((c) => c.name === name); exports.findComponentByName = findComponentByName; const getComponentData = (component) => { if (!component) { return null; } (0, exports.checkValidComponentOrDirective)(component); const compodocJson = (0, exports.getCompodocJson)(); if (!compodocJson) { return null; } (0, exports.checkValidCompodocJson)(compodocJson); const { name } = component; const metadata = (0, exports.findComponentByName)(name, compodocJson); if (!metadata) { client_logger_1.logger.warn(`Component not found in compodoc JSON: '${name}'`); } return metadata; }; const displaySignature = (item) => { const args = item.args.map((arg) => `${arg.name}${arg.optional ? '?' : ''}: ${arg.type}`); return `(${args.join(', ')}) => ${item.returnType}`; }; const extractTypeFromValue = (defaultValue) => { const valueType = typeof defaultValue; return defaultValue || valueType === 'number' || valueType === 'boolean' || valueType === 'string' ? valueType : null; }; const extractEnumValues = (compodocType) => { const compodocJson = (0, exports.getCompodocJson)(); const enumType = compodocJson?.miscellaneous?.enumerations?.find((x) => x.name === compodocType); if (enumType?.childs.every((x) => x.value)) { return enumType.childs.map((x) => x.value); } if (typeof compodocType !== 'string' || compodocType.indexOf('|') === -1) { return null; } try { return compodocType.split('|').map((value) => JSON.parse(value)); } catch (e) { return null; } }; const extractType = (property, defaultValue) => { const compodocType = property.type || extractTypeFromValue(defaultValue); switch (compodocType) { case 'string': case 'boolean': case 'number': return { name: compodocType }; case undefined: case null: return { name: 'other', value: 'void' }; default: { const resolvedType = resolveTypealias(compodocType); const enumValues = extractEnumValues(resolvedType); return enumValues ? { name: 'enum', value: enumValues } : { name: 'other', value: 'empty-enum' }; } } }; exports.extractType = extractType; const castDefaultValue = (property, defaultValue) => { const compodocType = property.type; // All these checks are necessary as compodoc does not always set the type ie. @HostBinding have empty types. // null and undefined also have 'any' type if (['boolean', 'number', 'string', 'EventEmitter'].includes(compodocType)) { switch (compodocType) { case 'boolean': return defaultValue === 'true'; case 'number': return Number(defaultValue); case 'EventEmitter': return undefined; default: return defaultValue; } } else { switch (defaultValue) { case 'true': return true; case 'false': return false; case 'null': return null; case 'undefined': return undefined; default: return defaultValue; } } }; const extractDefaultValueFromComments = (property, value) => { let commentValue = value; property.jsdoctags.forEach((tag) => { if (['default', 'defaultvalue'].includes(tag.tagName.escapedText)) { const dom = new global_1.global.DOMParser().parseFromString(tag.comment, 'text/html'); commentValue = dom.body.textContent; } }); return commentValue; }; const extractDefaultValue = (property) => { try { let value = property.defaultValue?.replace(/^'(.*)'$/, '$1'); value = castDefaultValue(property, value); if (value == null && property.jsdoctags?.length > 0) { value = extractDefaultValueFromComments(property, value); } return value; } catch (err) { client_logger_1.logger.debug(`Error extracting ${property.name}: ${property.defaultValue}`); return undefined; } }; const resolveTypealias = (compodocType) => { const compodocJson = (0, exports.getCompodocJson)(); const typeAlias = compodocJson?.miscellaneous?.typealiases?.find((x) => x.name === compodocType); return typeAlias ? resolveTypealias(typeAlias.rawtype) : compodocType; }; const extractArgTypesFromData = (componentData) => { const sectionToItems = {}; const compodocClasses = ['component', 'directive'].includes(componentData.type) ? ['propertiesClass', 'methodsClass', 'inputsClass', 'outputsClass'] : ['properties', 'methods']; compodocClasses.forEach((key) => { const data = componentData[key] || []; data.forEach((item) => { const section = mapItemToSection(key, item); const defaultValue = (0, exports.isMethod)(item) ? undefined : extractDefaultValue(item); const type = (0, exports.isMethod)(item) || (section !== 'inputs' && section !== 'properties') ? { name: 'other', value: 'void' } : (0, exports.extractType)(item, defaultValue); const action = section === 'outputs' ? { action: item.name } : {}; const argType = { name: item.name, description: item.rawdescription || item.description, type, ...action, table: { category: section, type: { summary: (0, exports.isMethod)(item) ? displaySignature(item) : item.type, required: (0, exports.isMethod)(item) ? false : !item.optional, }, defaultValue: { summary: defaultValue }, }, }; if (!sectionToItems[section]) { sectionToItems[section] = []; } sectionToItems[section].push(argType); }); }); const SECTIONS = [ 'properties', 'inputs', 'outputs', 'methods', 'view child', 'view children', 'content child', 'content children', ]; const argTypes = {}; SECTIONS.forEach((section) => { const items = sectionToItems[section]; if (items) { items.forEach((argType) => { argTypes[argType.name] = argType; }); } }); return argTypes; }; exports.extractArgTypesFromData = extractArgTypesFromData; const extractArgTypes = (component) => { const componentData = getComponentData(component); return componentData && (0, exports.extractArgTypesFromData)(componentData); }; exports.extractArgTypes = extractArgTypes; const extractComponentDescription = (component) => { const componentData = getComponentData(component); return componentData && (componentData.rawdescription || componentData.description); }; exports.extractComponentDescription = extractComponentDescription;