@@ -24,9 +24,7 @@ const uiSource = path.resolve(repoRoot, 'packages/ui/src');
2424const outFile = path . resolve ( documentsRoot , 'src/generated/ui-api.json' ) ;
2525const tdJson = path . resolve ( documentsRoot , 'node_modules/.cache/ui-typedoc.json' ) ;
2626
27- // Comment patterns that are engineering notes on the token pipeline, not
28- // public documentation - stripped so they never render as CSS variable
29- // descriptions in the docs (see e.g. status.module.css, icon-size.module.css).
27+ // Engineering notes in the CSS, never public documentation.
3028const INTERNAL_NOTE_RE = / m i s s i n g t o k e n / i;
3129
3230async function runTypedoc ( ) {
@@ -37,18 +35,10 @@ async function runTypedoc() {
3735 [
3836 '--json' ,
3937 tdJson ,
40- // `expand` over the whole components tree (rather than resolving just
41- // `index.ts`'s re-exports) so that per-variant prop types like
42- // LabelButtonProps/IconButtonProps/IconLabelButtonProps - not
43- // individually re-exported from the package barrel, only reachable
44- // through the Button component's overloaded signature - still get a
45- // full type reflection collectVariantProps can look up by name.
38+ // Whole tree, not the barrel: variant prop types are not re-exported.
4639 '--entryPoints' ,
4740 path . resolve ( uiSource , 'components' ) ,
48- // `shared` must be an entry too: helper prop types like WithIcon live
49- // there, and a type outside the entry tree gets no reflection - its
50- // intersection members (e.g. Accordion/Modal's `icon`) silently vanish
51- // from the generated tables.
41+ // A type outside the entry tree gets no reflection and vanishes from the tables.
5242 '--entryPoints' ,
5343 path . resolve ( uiSource , 'shared' ) ,
5444 '--entryPointStrategy' ,
@@ -78,7 +68,6 @@ function indexById(root) {
7868function findTypeByName ( root , name , warnings ) {
7969 const matches = [ ] ;
8070 ( function walk ( node ) {
81- // 2097152 = TypeAlias, 256 = Interface
8271 if ( node . name === name && ( node . kind === 2_097_152 || node . kind === 256 ) ) matches . push ( node ) ;
8372 for ( const child of node . children ?? [ ] ) walk ( child ) ;
8473 } ) ( root ) ;
@@ -169,10 +158,8 @@ function defaultTag(comment) {
169158 return value || null ;
170159}
171160
172- // A component whose props extend a native element's attributes accepts far
173- // more than the table lists (`placeholder`, `value`, `onChange`, aria-*, ...).
174- // Enumerating ~280 DOM attributes would drown the table, so record which
175- // element it forwards to and let the page say so in one line.
161+ // Which native element a component forwards its remaining props to; listing
162+ // ~280 DOM attributes in the table would drown the props that are ours.
176163const NATIVE_ATTRIBUTE_TYPES = new Map ( [
177164 [ 'InputHTMLAttributes' , 'input' ] ,
178165 [ 'ButtonHTMLAttributes' , 'button' ] ,
@@ -185,34 +172,27 @@ const NATIVE_ATTRIBUTE_TYPES = new Map([
185172function findNativeElement ( typeNode , byId , depth = 0 ) {
186173 if ( ! typeNode || depth > 8 ) return null ;
187174 if ( typeNode . type === 'reference' ) {
188- // TypeDoc keeps the qualifier when the import is namespaced (`React.HTMLAttributes`).
189175 const element = NATIVE_ATTRIBUTE_TYPES . get ( typeNode . name . replace ( / ^ R e a c t \. / , '' ) ) ;
190176 if ( element === 'element' ) {
191- // Plain HTMLAttributes<T> - name the element from its type argument.
192177 const tag = / ^ H T M L ( \w * ?) E l e m e n t $ / . exec ( typeNode . typeArguments ?. [ 0 ] ?. name ?? '' ) ?. [ 1 ] ;
193178 return tag ? tag . toLowerCase ( ) || 'element' : 'element' ;
194179 }
195180 if ( element ) return element ;
196- // A first-party alias can carry it indirectly (BaseButtonProps -> ButtonHTMLAttributes).
197181 if ( typeof typeNode . target === 'number' ) {
198182 const found = findNativeElement ( byId . get ( typeNode . target ) ?. type , byId , depth + 1 ) ;
199183 if ( found ) return found ;
200184 }
201185 }
202- // The reference is usually wrapped: `Omit<InputHTMLAttributes<…>, 'size'>`.
203186 for ( const nested of [ ...( typeNode . types ?? [ ] ) , ...( typeNode . typeArguments ?? [ ] ) ] ) {
204187 const found = findNativeElement ( nested , byId , depth + 1 ) ;
205188 if ( found ) return found ;
206189 }
207190 return null ;
208191}
209192
210- // Collect own properties from a prop type alias / interface, walking
211- // intersections and skipping referenced (extended / native HTML) members.
193+ // Own properties of a prop type, walking intersections and skipping native members.
212194function collectProps ( typeNode , byId , accumulator = new Map ( ) , context = null ) {
213195 if ( ! typeNode ) return accumulator ;
214- // TypeAlias / Interface: plain object members land directly on `.children`;
215- // computed types (intersections etc.) land on `.type`.
216196 if ( typeNode . kind === 2_097_152 || typeNode . kind === 256 ) {
217197 if ( typeNode . children ?. length ) {
218198 for ( const child of typeNode . children ) addProperty ( child , byId , accumulator ) ;
@@ -230,16 +210,13 @@ function collectProps(typeNode, byId, accumulator = new Map(), context = null) {
230210 }
231211 if ( typeNode . type === 'reference' && typeof typeNode . target === 'number' ) {
232212 const target = byId . get ( typeNode . target ) ;
233- // Only follow references into our own package's prop types, not native ones.
234- // Both declaration forms count - a prop type written as an interface is as
235- // valid a target as a type alias.
213+ // Follow first-party prop types only; both declaration forms count.
236214 if ( target && ( target . kind === 2_097_152 || target . kind === 256 ) ) {
237215 collectProps ( target , byId , accumulator , context ) ;
238216 }
239217 return accumulator ;
240218 }
241- // An unfollowed generic reference (Partial<X>, Omit<X, ...>) silently drops
242- // every prop of a first-party X - warn instead of shipping a slimmer table.
219+ // Partial<X> / Omit<X, …> would silently drop every prop of X.
243220 if ( typeNode . type === 'reference' && typeNode . typeArguments ?. length && context ) {
244221 const firstParty = typeNode . typeArguments . find (
245222 ( argument ) => argument . type === 'reference' && typeof argument . target === 'number' && byId . get ( argument . target ) ,
@@ -264,12 +241,8 @@ function addProperty(child, byId, accumulator) {
264241 } ) ;
265242}
266243
267- // Merge the prop sets of a discriminated-union component's variants (e.g.
268- // Button's Label/Icon/IconLabel components). Props shared by every variant
269- // with the same type are documented once, unqualified; props that are
270- // variant-specific (missing from, or typed differently in, some variants)
271- // get a note appended to their description so the table stays a single flat
272- // list without a separate "variants" column.
244+ // Merges the variants of a union/overload component into one flat table,
245+ // noting in the description where a prop applies to some variants only.
273246function collectVariantProps ( propsTypeNames , project , byId , warnings , slug , context ) {
274247 const perVariant = [ ] ;
275248 for ( const typeName of propsTypeNames ) {
@@ -290,15 +263,13 @@ function collectVariantProps(propsTypeNames, project, byId, warnings, slug, cont
290263 const occurrences = perVariant
291264 . filter ( ( variant ) => variant . props . has ( propertyName ) )
292265 . map ( ( variant ) => ( { typeName : variant . typeName , prop : variant . props . get ( propertyName ) } ) )
293- // `foo?: never` marks a prop as forbidden in that variant - treat it as absent .
266+ // `foo?: never` marks a prop forbidden in that variant.
294267 . filter ( ( occurrence ) => occurrence . prop . type !== 'never' ) ;
295268 if ( occurrences . length === 0 ) continue ;
296269 const distinctTypes = new Set ( occurrences . map ( ( o ) => o . prop . type ) ) ;
297270 const sharedByAll = occurrences . length === perVariant . length && distinctTypes . size === 1 ;
298271
299- // Required only when required in EVERY variant: `value` (controlled) and
300- // `defaultValue` (uncontrolled) marked required side by side would
301- // document a call that cannot exist. The variant note carries the detail.
272+ // Required in every variant, else the table documents an impossible call.
302273 const requiredEverywhere =
303274 occurrences . length === perVariant . length && occurrences . every ( ( o ) => o . prop . required ) ;
304275 const requiredInItsVariants = ! requiredEverywhere && occurrences . every ( ( o ) => o . prop . required ) ;
@@ -329,20 +300,16 @@ function collectVariantProps(propsTypeNames, project, byId, warnings, slug, cont
329300}
330301
331302function extractCssVariables ( directory , warnings , slug ) {
332- // An entry that documents an API rather than a styled component (a hook, say)
333- // carries no directory - its page renders the owning component's variables.
303+ // No directory - the entry documents an API, not a styled component.
334304 if ( ! directory ) return [ ] ;
335305
336306 const abs = path . resolve ( uiSource , 'components' , directory ) ;
337307 if ( ! existsSync ( abs ) ) {
338- // globSync on a missing directory returns [] - the page would then claim
339- // the component exposes no CSS variables.
340308 warnings . push ( `"${ slug } ": component directory ${ directory } does not exist` ) ;
341309 return [ ] ;
342310 }
343- // Subcomponents with their own COMPONENTS entry document their own
344- // variables - without this a parent page repeats them and offers overrides
345- // that do nothing there (Button listing NavButton's, Switch IconSwitch's).
311+ // Subcomponents with their own page document their own variables; an
312+ // override offered on the parent page would do nothing.
346313 const nestedPrefixes = COMPONENTS . map ( ( component ) => component . dir )
347314 . filter ( ( nested ) => nested ?. startsWith ( `${ directory } /` ) )
348315 . map ( ( nested ) => `${ nested . slice ( directory . length + 1 ) } /` ) ;
@@ -354,8 +321,6 @@ function extractCssVariables(directory, warnings, slug) {
354321 const variables = [ ] ;
355322 for ( const file of files ) {
356323 const css = readFileSync ( path . resolve ( abs , file ) , 'utf8' ) ;
357- // Match `--ax-public-xxx: value` declarations, capturing the value and an
358- // optional same-line comment.
359324 const re = / ( - - a x - p u b l i c - [ \w - ] + ) \s * : \s * ( [ ^ ; ] * ?) (?: \/ \* \s * ( .* ?) \s * \* \/ ) ? \s * ; / g;
360325 let m ;
361326 while ( ( m = re . exec ( css ) ) ) {
@@ -372,10 +337,8 @@ function extractCssVariables(directory, warnings, slug) {
372337 return variables ;
373338}
374339
375- // Groups the docs table by what the variable resolves to, not by what its name
376- // suggests: `edge-stroke-width` is a length and `snackbar-success-border` is a
377- // color, and neither reads that way from the name alone. Values usually point
378- // at a design token, so the token stylesheets are followed to the literal.
340+ // Groups by what the value resolves to - the name misleads (`edge-stroke-width`
341+ // is a length, `snackbar-success-border` is a color), so follow it to the literal.
379342const LITERAL_COLOR_RE = / ^ ( # | r g b | h s l | o k l c h | c o l o r - m i x | l i n e a r - g r a d i e n t | r a d i a l - g r a d i e n t | t r a n s p a r e n t \b | c u r r e n t C o l o r \b ) / i;
380343
381344const tokenValues = readTokenValues ( ) ;
@@ -442,8 +405,7 @@ async function main() {
442405 console . log ( '✔ ui-api.json generated\n ' + summary . join ( '\n ' ) ) ;
443406
444407 if ( warnings . length > 0 ) {
445- // An unresolved props type means a rename/typo silently shipped an empty
446- // "no configurable props" page - fail the build instead of warning.
408+ // An unresolved type would silently ship a "no configurable props" page.
447409 console . error ( '✗ ' + warnings . join ( '\n✗ ' ) ) ;
448410 process . exitCode = 1 ;
449411 }
0 commit comments