Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | 22x 22x 22x 10x 10x 10x 21x 21x 10x 22x 22x 22x 22x 22x 10x 10x 10x 10x 1x 3x 10x 10x 2x 2x 8x 18x 2x 2x 16x 18x 18x 6x 3x 3x 6x 18x 6x 18x 18x 20x 19x 21x 3x 20x | /**
* Types Builder - Props/Types generation
*
* Generates TypeScript prop interfaces and component signatures from UIF definitions.
*/
import {
type PropMetadata,
type ComponentMetadata,
sanitizeIdentifier,
} from '@fds-uif/generator-base/browser';
import type { GenerationContext } from './types.js';
/**
* Build the props interface for a component
*/
export function buildPropsInterface(metadata: ComponentMetadata, context: GenerationContext): string {
const { name, props } = metadata;
const lines: string[] = [];
lines.push(`export interface ${name}Props {`);
for (const prop of props) {
const propDef = buildPropDefinition(prop, context);
lines.push(propDef);
}
// Add className prop for composition
if (!props.some((p) => p.name === 'className')) {
lines.push(' /** Additional CSS classes */');
lines.push(' className?: string;');
}
// Add style prop
if (!props.some((p) => p.name === 'style')) {
lines.push(' /** Inline styles */');
lines.push(' style?: React.CSSProperties;');
context.typeImports.add('React');
}
lines.push('}');
return lines.join('\n');
}
/**
* Build a single prop definition
*/
function buildPropDefinition(prop: PropMetadata, context: GenerationContext): string {
const lines: string[] = [];
const required = prop.required ? '' : '?';
const type = mapPropType(prop, context);
// Sanitize prop name to be a valid JS identifier
const propName = sanitizeIdentifier(prop.name);
// Add JSDoc comment if description exists
if (prop.description) {
lines.push(` /** ${prop.description} */`);
}
// Add default value annotation if present
if (prop.default !== undefined) {
lines.push(` /** @default ${JSON.stringify(prop.default)} */`);
}
lines.push(` ${propName}${required}: ${type};`);
return lines.join('\n');
}
/**
* Map UIF prop type to TypeScript type
*/
function mapPropType(prop: PropMetadata, context: GenerationContext): string {
// If explicit type is provided by analyzer, use it (with ReactNode handling)
if (prop.type) {
if (prop.type === 'ReactNode') {
context.typeImports.add('ReactNode');
return 'React.ReactNode';
}
return prop.type;
}
// Infer from source when no explicit type
const sourceTypeMap: Record<string, string> = {
state: 'boolean',
modifier: 'boolean',
variant: 'string',
attribute: 'string',
};
if (prop.source === 'slot') {
context.typeImports.add('ReactNode');
return 'React.ReactNode';
}
return sourceTypeMap[prop.source] ?? 'unknown';
}
/**
* Build component function signature
*/
export function buildFunctionSignature(
metadata: ComponentMetadata,
context: GenerationContext,
options: { forwardRef?: boolean } = {},
): string {
const { name } = metadata;
if (options.forwardRef) {
context.imports.add("import { forwardRef } from 'react';");
return `export const ${name} = forwardRef<HTMLElement, ${name}Props>(function ${name}(props, ref)`;
}
return `export function ${name}(props: ${name}Props)`;
}
/**
* Build props destructuring
*/
export function buildPropsDestructuring(metadata: ComponentMetadata, context: GenerationContext): string {
const { props } = metadata;
const destructured: string[] = [];
for (const prop of props) {
// Sanitize prop name to be a valid JS identifier
const propName = sanitizeIdentifier(prop.name);
if (prop.default !== undefined) {
destructured.push(`${propName} = ${JSON.stringify(prop.default)}`);
} else {
destructured.push(propName);
}
}
// Always include className and style
if (!props.some((p) => p.name === 'className')) {
destructured.push('className');
}
if (!props.some((p) => p.name === 'style')) {
destructured.push('style');
}
return `const { ${destructured.join(', ')} } = props;`;
}
/**
* Get all type imports needed
*/
export function getTypeImports(context: GenerationContext): string[] {
const imports: string[] = [];
if (context.typeImports.size > 0) {
// Filter out 'React' since we import * as React
const types = Array.from(context.typeImports)
.filter((t) => t !== 'React')
.sort();
if (types.length > 0) {
imports.push(`import type { ${types.join(', ')} } from 'react';`);
}
}
return imports;
}
|