@@ -754,20 +754,88 @@ export namespace J {
754754 readonly text : string ;
755755 }
756756
757- export interface LeftPadded < T extends J | Space | number | string | boolean > {
758- readonly kind : typeof Kind . LeftPadded ;
757+ /**
758+ * Represents padding information that appears before an element.
759+ * Used in LeftPadded types to hold the prefix space and markers.
760+ */
761+ export interface Prefix {
759762 readonly before : Space ;
760- readonly element : T ;
761763 readonly markers : Markers ;
762764 }
763765
764- export interface RightPadded < T extends J | boolean > {
765- readonly kind : typeof Kind . RightPadded ;
766- readonly element : T ;
766+ /**
767+ * Represents padding information that appears after an element.
768+ * Used in RightPadded types to hold the suffix space and markers.
769+ */
770+ export interface Suffix {
767771 readonly after : Space ;
768772 readonly markers : Markers ;
769773 }
770774
775+ /**
776+ * Padding mixin for left-padded elements.
777+ * Nests padding info under `padding` to avoid conflicts with element's own `markers`.
778+ */
779+ export interface LeftPaddingMixin {
780+ readonly padding : Prefix ;
781+ }
782+
783+ /**
784+ * Padding mixin for right-padded elements.
785+ * Nests padding info under `padding` to avoid conflicts with element's own `markers`.
786+ */
787+ export interface RightPaddingMixin {
788+ readonly padding : Suffix ;
789+ }
790+
791+ /**
792+ * Wrapper for primitive values in padding (boolean, number, string).
793+ * Primitives cannot use intersection types, so they use this wrapper.
794+ * Space is an object type, so it uses intersection instead.
795+ */
796+ export interface PaddedPrimitive < T extends number | string | boolean > {
797+ readonly element : T ;
798+ }
799+
800+ /**
801+ * LeftPadded represents an element with whitespace/comments before it.
802+ *
803+ * For tree nodes (J) and Space: Uses intersection type - access properties directly.
804+ * Example: `fieldAccess.name.simpleName` (no `.element` needed)
805+ * Example: `arrayType.dimension.whitespace` (Space properties directly)
806+ * Padding accessed via: `node.padding.before`, `node.padding.markers`
807+ *
808+ * For primitives (boolean, number, string): Uses wrapper - access via `.element` property.
809+ * Example: `import.static.element` (boolean value)
810+ * Padding accessed via: `node.padding.before`, `node.padding.markers`
811+ */
812+ export type LeftPadded < T extends J | Space | number | string | boolean > =
813+ T extends J
814+ ? T & LeftPaddingMixin
815+ : T extends Space
816+ ? Space & LeftPaddingMixin
817+ : PaddedPrimitive < Extract < T , number | string | boolean > > & LeftPaddingMixin ;
818+
819+ /**
820+ * RightPadded represents an element with whitespace/comments after it.
821+ *
822+ * For tree nodes (J): Uses intersection type - access properties directly.
823+ * Example: `stmt.kind` (no `.element` needed)
824+ * Padding accessed via: `stmt.padding.after`, `stmt.padding.markers`
825+ *
826+ * For booleans: Uses wrapper - access via `.element` property.
827+ * Padding accessed via: `node.padding.after`, `node.padding.markers`
828+ */
829+ export type RightPadded < T extends J | boolean > =
830+ T extends J
831+ ? T & RightPaddingMixin
832+ : PaddedPrimitive < T & boolean > & RightPaddingMixin ;
833+
834+ /**
835+ * Container represents a bracketed group of elements (e.g., method arguments,
836+ * type parameters). Has space before the opening bracket and a list of
837+ * right-padded elements.
838+ */
771839 export interface Container < T extends J > {
772840 readonly kind : typeof Kind . Container ;
773841 readonly before : Space ;
@@ -851,13 +919,98 @@ export const isNewClass = (n: any): n is J.NewClass => n.kind === J.Kind.NewClas
851919export const isReturn = ( n : any ) : n is J . Return => n . kind === J . Kind . Return ;
852920export const isVariableDeclarations = ( n : any ) : n is J . VariableDeclarations => n . kind === J . Kind . VariableDeclarations ;
853921
854- export function rightPadded < T extends J | boolean > ( t : T , trailing : J . Space , markers ?: Markers ) : J . RightPadded < T > {
855- return {
856- kind : J . Kind . RightPadded ,
857- element : t ,
858- after : trailing ,
859- markers : markers ?? emptyMarkers
860- } ;
922+ /**
923+ * Creates a RightPadded value.
924+ *
925+ * For tree nodes (J): Returns intersection type with nested padding.
926+ * For booleans: Returns wrapper with `element` property and nested padding.
927+ */
928+ export function rightPadded < T extends J > ( t : T , trailing : J . Space , paddingMarkers ?: Markers ) : J . RightPadded < T > ;
929+ export function rightPadded ( t : boolean , trailing : J . Space , paddingMarkers ?: Markers ) : J . RightPadded < boolean > ;
930+ export function rightPadded < T extends J | boolean > ( t : T , trailing : J . Space , paddingMarkers ?: Markers ) : J . RightPadded < T > {
931+ const padding : J . Suffix = { after : trailing , markers : paddingMarkers ?? emptyMarkers } ;
932+ if ( typeof t === 'boolean' ) {
933+ // Primitive: create wrapper with nested padding
934+ return { element : t , padding } as J . RightPadded < T > ;
935+ } else {
936+ // Tree node: merge with nested padding
937+ return { ...t as object , padding } as J . RightPadded < T > ;
938+ }
939+ }
940+
941+ /**
942+ * Creates a LeftPadded value.
943+ *
944+ * For tree nodes (J) and Space: Returns intersection type with nested padding.
945+ * For primitives (boolean, number, string): Returns wrapper with `element` property and nested padding.
946+ */
947+ export function leftPadded < T extends J > ( t : T , leading : J . Space , paddingMarkers ?: Markers ) : J . LeftPadded < T > ;
948+ export function leftPadded ( t : J . Space , leading : J . Space , paddingMarkers ?: Markers ) : J . LeftPadded < J . Space > ;
949+ export function leftPadded < T extends number | string | boolean > ( t : T , leading : J . Space , paddingMarkers ?: Markers ) : J . LeftPadded < T > ;
950+ export function leftPadded < T extends J | J . Space | number | string | boolean > ( t : T , leading : J . Space , paddingMarkers ?: Markers ) : J . LeftPadded < T > {
951+ const padding : J . Prefix = { before : leading , markers : paddingMarkers ?? emptyMarkers } ;
952+ if ( typeof t === 'boolean' || typeof t === 'number' || typeof t === 'string' ) {
953+ // Primitive: create wrapper with nested padding
954+ return { element : t , padding } as J . LeftPadded < T > ;
955+ } else {
956+ // Tree node or Space: merge with nested padding (intersection)
957+ return { ...t as object , padding } as J . LeftPadded < T > ;
958+ }
959+ }
960+
961+ /**
962+ * Type guard to check if a padded value uses intersection type (tree nodes or Space)
963+ * vs a primitive wrapper (has `element` property for boolean, number, string).
964+ */
965+ export function isIntersectionPadded ( padded : any ) : boolean {
966+ // All padded values have a `padding` property
967+ // Intersection types (tree nodes, Space) don't have `element`
968+ // Primitive wrappers have both `padding` and `element`
969+ return padded && typeof padded === 'object' && 'padding' in padded && ! ( 'element' in padded ) ;
970+ }
971+
972+ /**
973+ * @deprecated Use isIntersectionPadded instead
974+ */
975+ export const isTreePadded = isIntersectionPadded ;
976+
977+ /**
978+ * Extracts the element from a padded value.
979+ * For tree nodes and Space: returns the padded value itself (it IS the element with padding mixed in).
980+ * For primitives (boolean, number, string): returns the `.element` property.
981+ */
982+ export function getPaddedElement < T extends J > ( padded : J . LeftPadded < T > | J . RightPadded < T > ) : T ;
983+ export function getPaddedElement ( padded : J . LeftPadded < J . Space > ) : J . Space ;
984+ export function getPaddedElement < T extends number | string | boolean > ( padded : J . LeftPadded < T > ) : T ;
985+ export function getPaddedElement < T extends boolean > ( padded : J . RightPadded < T > ) : T ;
986+ // Catch-all overloads for union types with J and primitives
987+ export function getPaddedElement < T extends J | boolean > ( padded : J . RightPadded < T > ) : T ;
988+ export function getPaddedElement < T extends J | J . Space | number | string | boolean > ( padded : J . LeftPadded < T > ) : T ;
989+ export function getPaddedElement < T > ( padded : any ) : T {
990+ if ( 'element' in padded ) {
991+ return padded . element ;
992+ }
993+ // For tree nodes and Space, the padded value IS the element
994+ return padded as T ;
995+ }
996+
997+ /**
998+ * Sets the element in a padded value, returning a new padded value.
999+ * For tree nodes and Space: merges the new element with existing padding.
1000+ * For primitives (boolean, number, string): creates a new wrapper with the new element.
1001+ */
1002+ export function withPaddedElement < T extends J > ( padded : J . LeftPadded < T > , newElement : T ) : J . LeftPadded < T > ;
1003+ export function withPaddedElement < T extends J > ( padded : J . RightPadded < T > , newElement : T ) : J . RightPadded < T > ;
1004+ export function withPaddedElement ( padded : J . LeftPadded < J . Space > , newElement : J . Space ) : J . LeftPadded < J . Space > ;
1005+ export function withPaddedElement < T extends number | string | boolean > ( padded : J . LeftPadded < T > , newElement : T ) : J . LeftPadded < T > ;
1006+ export function withPaddedElement < T extends boolean > ( padded : J . RightPadded < T > , newElement : T ) : J . RightPadded < T > ;
1007+ export function withPaddedElement < T > ( padded : any , newElement : T ) : any {
1008+ if ( 'element' in padded ) {
1009+ // Primitive wrapper - update element, preserve padding
1010+ return { ...padded , element : newElement } ;
1011+ }
1012+ // Tree node or Space - merge new element with existing padding
1013+ return { ...newElement as object , padding : padded . padding } ;
8611014}
8621015
8631016export namespace TypedTree {
@@ -883,18 +1036,18 @@ export namespace TypedTree {
8831036
8841037 registerTypeGetter ( J . Kind . MethodDeclaration , ( tree : J . MethodDeclaration ) => tree . methodType ?. returnType ) ;
8851038 registerTypeGetter ( J . Kind . MethodInvocation , ( tree : J . MethodInvocation ) => tree . methodType ?. returnType ) ;
886- registerTypeGetter ( J . Kind . Parentheses , ( tree : J . Parentheses < TypedTree > ) => getType ( tree . tree . element ) ) ;
1039+ registerTypeGetter ( J . Kind . Parentheses , ( tree : J . Parentheses < TypedTree > ) => getType ( tree . tree ) ) ;
8871040 registerTypeGetter ( J . Kind . NewClass , ( tree : J . NewClass ) => tree . constructorType ?. returnType ) ;
8881041
8891042 // TODO ControlParentheses here isn't a TypedTree so why does this compile?
8901043 registerTypeGetter ( J . Kind . TypeCast , ( tree : J . TypeCast ) => getType ( tree . class ) ) ;
8911044
8921045 registerTypeGetter ( J . Kind . Empty , ( ) => Type . unknownType ) ;
8931046 registerTypeGetter ( J . Kind . MultiCatch , ( tree : J . MultiCatch ) => {
894- const bounds = tree . alternatives . map ( a => getType ( a . element ) ) ;
1047+ const bounds = tree . alternatives . map ( a => getType ( a ) ) ;
8951048 return { kind : Type . Kind . Union , bounds : bounds } ;
8961049 } ) ;
897- registerTypeGetter ( J . Kind . NullableType , ( tree : J . NullableType ) => getType ( tree . typeTree . element ) ) ;
1050+ registerTypeGetter ( J . Kind . NullableType , ( tree : J . NullableType ) => getType ( tree . typeTree ) ) ;
8981051 registerTypeGetter ( J . Kind . Wildcard , ( ) => Type . unknownType ) ;
8991052 registerTypeGetter ( J . Kind . Unknown , ( ) => Type . unknownType ) ;
9001053}
0 commit comments