11use codeql_extractor:: extractor:: simple;
2- use yeast:: { rule, tree, ConcreteDesugarer , DesugaringConfig , PhaseKind } ;
2+ use yeast:: { manual_rule , rule, tree, ConcreteDesugarer , DesugaringConfig , PhaseKind , Rule } ;
33
4- fn translation_rules ( ) -> Vec < yeast:: Rule > {
4+ /// User context propagated from outer `property_binding` rules down to the
5+ /// inner accessor-translation rules so that every `accessor_declaration`
6+ /// emitted by an inner rule is born with the property's `name` (and
7+ /// optionally its `type`) already set — no schema-invalid intermediate
8+ /// state requiring post-hoc mutation.
9+ #[ derive( Clone , Default ) ]
10+ struct PropertyContext {
11+ /// Identifier node for the property name, to be used as the
12+ /// `accessor_declaration.name`. Set by the outer property_binding rule
13+ /// before translating accessor children.
14+ property_name : Option < yeast:: Id > ,
15+ /// Translated type node for the property type, to be used as the
16+ /// `accessor_declaration.type`. Set by the outer property_binding rule
17+ /// when present.
18+ property_type : Option < yeast:: Id > ,
19+ }
20+
21+ fn translation_rules ( ) -> Vec < Rule < PropertyContext > > {
522 vec ! [
623 // ---- Top-level ----
724 // Capture all top-level statements, including unnamed tokens like `nil`.
@@ -88,27 +105,35 @@ fn translation_rules() -> Vec<yeast::Rule> {
88105 // nodes for individual declarators. The outer property_declaration rule splices these out
89106 // and attaches binding/modifiers from the parent.
90107
91- // Computed property with explicit accessors (get/set/modify) →
92- // a sequence of accessor_declaration nodes, each with the property name
93- // attached. Subsequent accessors will be tagged chained_declaration by
94- // the outer property_declaration rule.
95- rule!(
108+ // Computed property with explicit accessors (get/set/modify) → a
109+ // sequence of `accessor_declaration` nodes. The outer rule
110+ // publishes the property's name and type into `ctx` so that each
111+ // inner accessor rule
112+ // (`computed_getter`/`computed_setter`/`computed_modify`) builds
113+ // its `accessor_declaration` with `name` and `type` set from the
114+ // start — no schema-invalid intermediate state.
115+ manual_rule!(
96116 ( property_binding
97117 name: @pattern
98118 type : _? @ty
99119 computed_value: ( computed_property accessor: _+ @accessors) )
100- =>
101- { ..{
102- for & acc in & accessors {
103- let acc_id: usize = acc. into( ) ;
104- for & t in ty. iter( ) . rev( ) {
105- ctx. prepend_field( acc_id, "type" , t. into( ) ) ;
106- }
107- let name_id = tree!( ( identifier #{ pattern} ) ) ;
108- ctx. prepend_field( acc_id, "name" , name_id) ;
120+ {
121+ // Translate `ty` first so the context holds an
122+ // output-schema node id.
123+ let translated_ty = ctx. translate_opt( ty) ?;
124+ // Build the property-name identifier from the
125+ // (untranslated) pattern leaf.
126+ let name_id = tree!( ( identifier #{ pattern} ) ) ;
127+
128+ ctx. property_name = Some ( name_id) ;
129+ ctx. property_type = translated_ty;
130+
131+ let mut result = Vec :: new( ) ;
132+ for acc in & accessors {
133+ result. extend( ctx. translate( * acc) ?) ;
109134 }
110- accessors
111- } }
135+ Ok ( result )
136+ }
112137 ) ,
113138 // Computed property: shorthand getter (no explicit get/set, just statements) →
114139 // a single accessor_declaration with kind "get".
@@ -124,31 +149,41 @@ fn translation_rules() -> Vec<yeast::Rule> {
124149 accessor_kind: ( accessor_kind "get" )
125150 body: ( block stmt: { ..body} ) )
126151 ) ,
127- // Stored property with willSet/didSet observers (initializer optional) →
128- // variable_declaration followed by one accessor_declaration per observer,
129- // each carrying the property name. Subsequent items are tagged
130- // chained_declaration by the outer property_declaration rule.
131- rule!(
152+ // Stored property with willSet/didSet observers (initializer
153+ // optional) → a `variable_declaration` followed by one
154+ // `accessor_declaration` per observer, each born with the
155+ // property name set. Manual rule: we publish the property name
156+ // into `ctx` before translating the observer children so the
157+ // inner `willset_clause` / `didset_clause` rules construct
158+ // valid `accessor_declaration` nodes from the start.
159+ manual_rule!(
132160 ( property_binding
133161 name: ( pattern bound_identifier: @name)
134162 type : _? @ty
135163 value: _? @val
136164 observers: ( willset_didset_block willset: _? @ws didset: _? @ds) )
137- =>
138- ( variable_declaration
139- pattern: ( name_pattern identifier: ( identifier #{ name} ) )
140- type : { ..ty}
141- value: { ..val} )
142- { ..{
143- let mut obs_ids = Vec :: new( ) ;
144- for & obs in ws. iter( ) . chain( ds. iter( ) ) {
145- let obs_id: usize = obs. into( ) ;
146- let ident = tree!( ( identifier #{ name} ) ) ;
147- ctx. prepend_field( obs_id, "name" , ident) ;
148- obs_ids. push( obs_id) ;
165+ {
166+ // Translate ty and val so the variable_declaration
167+ // below contains output-schema nodes.
168+ let translated_ty = ctx. translate_opt( ty) ?;
169+ let translated_val = ctx. translate_opt( val) ?;
170+
171+ let var_decl = tree!(
172+ ( variable_declaration
173+ pattern: ( name_pattern identifier: ( identifier #{ name} ) )
174+ type : { ..translated_ty}
175+ value: { ..translated_val} )
176+ ) ;
177+
178+ // Publish the property name for the observer rules.
179+ ctx. property_name = Some ( tree!( ( identifier #{ name} ) ) ) ;
180+
181+ let mut result = vec![ var_decl] ;
182+ for obs in ws. into_iter( ) . chain( ds) {
183+ result. extend( ctx. translate( obs) ?) ;
149184 }
150- obs_ids
151- } }
185+ Ok ( result )
186+ }
152187 ) ,
153188 // property_binding with any pattern name (identifier or destructuring)
154189 rule!(
@@ -899,10 +934,14 @@ fn translation_rules() -> Vec<yeast::Rule> {
899934 // protocol_property_requirements wrapper — should be consumed by above; fallback
900935 rule!( ( protocol_property_requirements accessor: _* @accs) => { ..accs} ) ,
901936 // Computed getter → accessor_declaration (body optional).
937+ // Reads `ctx.property_name`/`ctx.property_type` set by the outer
938+ // property_binding manual rule.
902939 rule!(
903940 ( computed_getter body: ( block statement: _* @body) ?)
904941 =>
905942 ( accessor_declaration
943+ name: { ctx. property_name. ok_or( "computed_getter outside property_binding context" ) ?}
944+ type : { ..ctx. property_type}
906945 accessor_kind: ( accessor_kind "get" )
907946 body: ( block stmt: { ..body} ) )
908947 ) ,
@@ -911,6 +950,8 @@ fn translation_rules() -> Vec<yeast::Rule> {
911950 ( computed_setter parameter: @param body: ( block statement: _* @body) )
912951 =>
913952 ( accessor_declaration
953+ name: { ctx. property_name. ok_or( "computed_setter outside property_binding context" ) ?}
954+ type : { ..ctx. property_type}
914955 accessor_kind: ( accessor_kind "set" )
915956 parameter: ( parameter pattern: ( name_pattern identifier: ( identifier #{ param} ) ) )
916957 body: ( block stmt: { ..body} ) )
@@ -920,6 +961,8 @@ fn translation_rules() -> Vec<yeast::Rule> {
920961 ( computed_setter body: ( block statement: _* @body) ?)
921962 =>
922963 ( accessor_declaration
964+ name: { ctx. property_name. ok_or( "computed_setter outside property_binding context" ) ?}
965+ type : { ..ctx. property_type}
923966 accessor_kind: ( accessor_kind "set" )
924967 body: ( block stmt: { ..body} ) )
925968 ) ,
@@ -928,16 +971,22 @@ fn translation_rules() -> Vec<yeast::Rule> {
928971 ( computed_modify body: ( block statement: _* @body) )
929972 =>
930973 ( accessor_declaration
974+ name: { ctx. property_name. ok_or( "computed_modify outside property_binding context" ) ?}
975+ type : { ..ctx. property_type}
931976 accessor_kind: ( accessor_kind "modify" )
932977 body: ( block stmt: { ..body} ) )
933978 ) ,
934- // willset/didset block — spread to children
979+ // willset/didset block — spread to children (only reachable as a
980+ // fallback; the outer property_binding manual rule normally
981+ // captures the willset/didset clauses directly).
935982 rule!( ( willset_didset_block _* @clauses) => { ..clauses} ) ,
936- // willset clause → accessor_declaration (body optional).
983+ // willset clause → accessor_declaration (body optional). Reads
984+ // `ctx.property_name` set by the outer property_binding rule.
937985 rule!(
938986 ( willset_clause body: ( block statement: _* @body) ?)
939987 =>
940988 ( accessor_declaration
989+ name: { ctx. property_name. ok_or( "willset_clause outside property_binding context" ) ?}
941990 accessor_kind: ( accessor_kind "willSet" )
942991 body: ( block stmt: { ..body} ) )
943992 ) ,
@@ -946,6 +995,7 @@ fn translation_rules() -> Vec<yeast::Rule> {
946995 ( didset_clause body: ( block statement: _* @body) ?)
947996 =>
948997 ( accessor_declaration
998+ name: { ctx. property_name. ok_or( "didset_clause outside property_binding context" ) ?}
949999 accessor_kind: ( accessor_kind "didSet" )
9501000 body: ( block stmt: { ..body} ) )
9511001 ) ,
@@ -967,7 +1017,7 @@ fn translation_rules() -> Vec<yeast::Rule> {
9671017
9681018pub fn language_spec ( desugared_ast_schema : & ' static str ) -> simple:: LanguageSpec {
9691019 let ts_language: tree_sitter:: Language = tree_sitter_swift:: LANGUAGE . into ( ) ;
970- let config = DesugaringConfig :: new ( )
1020+ let config = DesugaringConfig :: < PropertyContext > :: new ( )
9711021 . add_phase ( "translate" , PhaseKind :: OneShot , translation_rules ( ) )
9721022 . with_output_node_types_yaml ( desugared_ast_schema) ;
9731023 let desugarer = ConcreteDesugarer :: new ( ts_language. clone ( ) , config)
0 commit comments