Skip to content

Commit dc5a00a

Browse files
authored
Parser: Improve attribute locations for Action View helpers (#1451)
This pull request improves the source locations on `HTMLAttributeNode` for Action View helper attributes. Previously, all sub-nodes (`name`, `equals`, `value`, `open_quote`, `close_quote`, `literal`) shared the same approximate location range. Now each component has precise locations pointing to the actual source positions. The location computation now correctly handles all Ruby hash key syntax variants: - Label syntax: `class: "classes"` - Symbol hashrocket: `:class => "classes"` - String hashrocket: `"class" => "classes"` - Quoted label syntax: `"class": "classes"` The `equals` token also now reflects the actual source syntax: `": "` for label syntax and `" => "` for hashrocket syntax.
1 parent 3bc2408 commit dc5a00a

File tree

111 files changed

+1615
-967
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

111 files changed

+1615
-967
lines changed

javascript/packages/rewriter/src/built-ins/action-view-tag-helper-to-html.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,12 @@ class ActionViewTagHelperToHTMLVisitor extends Visitor {
150150
})
151151

152152
mutableValue.children = newChildren
153+
}
153154

154-
if (!value.quoted) {
155-
mutableValue.quoted = true
156-
mutableValue.open_quote = createSyntheticToken('"')
157-
mutableValue.close_quote = createSyntheticToken('"')
158-
}
155+
if (!value.quoted) {
156+
mutableValue.quoted = true
157+
mutableValue.open_quote = createSyntheticToken('"')
158+
mutableValue.close_quote = createSyntheticToken('"')
159159
}
160160
}
161161
}

src/analyze/action_view/attribute_extraction_helpers.c

Lines changed: 190 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,171 @@ static char* extract_string_from_prism_node(pm_node_t* node, hb_allocator_T* all
2929
return hb_allocator_strndup(allocator, (const char*) source, length);
3030
}
3131

32+
static void compute_position_pair(
33+
const pm_location_t* location,
34+
const uint8_t* source,
35+
const char* original_source,
36+
size_t erb_content_offset,
37+
position_T* out_start,
38+
position_T* out_end
39+
) {
40+
*out_start = prism_location_to_position_with_offset(location, original_source, erb_content_offset, source);
41+
pm_location_t end_location = { .start = location->end, .end = location->end };
42+
*out_end = prism_location_to_position_with_offset(&end_location, original_source, erb_content_offset, source);
43+
}
44+
45+
static void extract_key_name_location(pm_node_t* key, pm_location_t* out_name_loc) {
46+
if (key->type == PM_SYMBOL_NODE) {
47+
pm_symbol_node_t* symbol = (pm_symbol_node_t*) key;
48+
*out_name_loc = symbol->value_loc;
49+
} else if (key->type == PM_STRING_NODE) {
50+
pm_string_node_t* string_node = (pm_string_node_t*) key;
51+
*out_name_loc = string_node->content_loc;
52+
} else {
53+
*out_name_loc = key->location;
54+
}
55+
}
56+
57+
static void compute_separator_info(
58+
pm_assoc_node_t* assoc,
59+
const uint8_t* source,
60+
const char* original_source,
61+
size_t erb_content_offset,
62+
position_T key_end,
63+
const char** out_separator_string,
64+
token_type_T* out_separator_type,
65+
position_T* out_separator_start,
66+
position_T* out_separator_end
67+
) {
68+
*out_separator_end =
69+
prism_location_to_position_with_offset(&assoc->value->location, original_source, erb_content_offset, source);
70+
71+
if (assoc->operator_loc.start != NULL) {
72+
*out_separator_string = " => ";
73+
*out_separator_type = TOKEN_EQUALS;
74+
*out_separator_start = key_end;
75+
} else {
76+
*out_separator_string = ": ";
77+
*out_separator_type = TOKEN_COLON;
78+
79+
pm_location_t colon_loc = {
80+
.start = assoc->key->location.end - 1,
81+
.end = assoc->key->location.end - 1,
82+
};
83+
84+
*out_separator_start =
85+
prism_location_to_position_with_offset(&colon_loc, original_source, erb_content_offset, source);
86+
}
87+
}
88+
89+
static void extract_delimited_locations(
90+
pm_node_t* node,
91+
const pm_location_t** out_opening,
92+
const pm_location_t** out_closing,
93+
const pm_location_t** out_content
94+
) {
95+
if (node->type == PM_STRING_NODE) {
96+
pm_string_node_t* string_node = (pm_string_node_t*) node;
97+
*out_opening = &string_node->opening_loc;
98+
*out_closing = &string_node->closing_loc;
99+
*out_content = &string_node->content_loc;
100+
} else if (node->type == PM_SYMBOL_NODE) {
101+
pm_symbol_node_t* symbol_node = (pm_symbol_node_t*) node;
102+
*out_opening = &symbol_node->opening_loc;
103+
*out_closing = &symbol_node->closing_loc;
104+
*out_content = &symbol_node->value_loc;
105+
} else {
106+
*out_opening = NULL;
107+
*out_closing = NULL;
108+
*out_content = NULL;
109+
}
110+
}
111+
112+
static void compute_value_positions(
113+
pm_node_t* value_node,
114+
const uint8_t* source,
115+
const char* original_source,
116+
size_t erb_content_offset,
117+
position_T* out_value_start,
118+
position_T* out_value_end,
119+
position_T* out_content_start,
120+
position_T* out_content_end,
121+
bool* out_quoted
122+
) {
123+
const pm_location_t* opening_loc;
124+
const pm_location_t* closing_loc;
125+
const pm_location_t* content_loc;
126+
127+
extract_delimited_locations(value_node, &opening_loc, &closing_loc, &content_loc);
128+
129+
if (opening_loc && opening_loc->start != NULL && closing_loc && closing_loc->start != NULL) {
130+
compute_position_pair(
131+
&*opening_loc,
132+
source,
133+
original_source,
134+
erb_content_offset,
135+
out_value_start,
136+
out_content_start
137+
);
138+
139+
compute_position_pair(&*closing_loc, source, original_source, erb_content_offset, out_content_end, out_value_end);
140+
*out_quoted = true;
141+
} else {
142+
const pm_location_t* fallback_loc = content_loc ? content_loc : &value_node->location;
143+
compute_position_pair(fallback_loc, source, original_source, erb_content_offset, out_value_start, out_value_end);
144+
*out_content_start = *out_value_start;
145+
*out_content_end = *out_value_end;
146+
*out_quoted = false;
147+
}
148+
}
149+
150+
static void fill_attribute_positions(
151+
pm_assoc_node_t* assoc,
152+
const uint8_t* source,
153+
const char* original_source,
154+
size_t erb_content_offset,
155+
attribute_positions_T* positions
156+
) {
157+
pm_location_t name_loc;
158+
extract_key_name_location(assoc->key, &name_loc);
159+
compute_position_pair(
160+
&name_loc,
161+
source,
162+
original_source,
163+
erb_content_offset,
164+
&positions->name_start,
165+
&positions->name_end
166+
);
167+
168+
position_T key_end;
169+
pm_location_t key_end_loc = { .start = assoc->key->location.end, .end = assoc->key->location.end };
170+
key_end = prism_location_to_position_with_offset(&key_end_loc, original_source, erb_content_offset, source);
171+
172+
compute_separator_info(
173+
assoc,
174+
source,
175+
original_source,
176+
erb_content_offset,
177+
key_end,
178+
&positions->separator_string,
179+
&positions->separator_type,
180+
&positions->separator_start,
181+
&positions->separator_end
182+
);
183+
184+
compute_value_positions(
185+
assoc->value,
186+
source,
187+
original_source,
188+
erb_content_offset,
189+
&positions->value_start,
190+
&positions->value_end,
191+
&positions->content_start,
192+
&positions->content_end,
193+
&positions->quoted
194+
);
195+
}
196+
32197
static char* build_prefixed_key(const char* prefix, const char* raw_key, hb_allocator_T* allocator) {
33198
char* dashed_key = convert_underscores_to_dashes(raw_key);
34199
const char* key = dashed_key ? dashed_key : raw_key;
@@ -46,33 +211,32 @@ static char* build_prefixed_key(const char* prefix, const char* raw_key, hb_allo
46211
static AST_HTML_ATTRIBUTE_NODE_T* create_attribute_from_value(
47212
const char* name_string,
48213
pm_node_t* value_node,
49-
position_T start_position,
50-
position_T end_position,
214+
attribute_positions_T* positions,
51215
hb_allocator_T* allocator
52216
) {
53217
if (value_node->type == PM_SYMBOL_NODE || value_node->type == PM_STRING_NODE) {
54218
char* value_string = extract_string_from_prism_node(value_node, allocator);
55219
if (!value_string) { return NULL; }
56220

57221
AST_HTML_ATTRIBUTE_NODE_T* attribute =
58-
create_html_attribute_node(name_string, value_string, start_position, end_position, allocator);
222+
create_html_attribute_node_precise(name_string, value_string, positions, allocator);
59223
hb_allocator_dealloc(allocator, value_string);
60224

61225
return attribute;
62226
} else if (value_node->type == PM_TRUE_NODE) {
63227
if (is_boolean_attribute(hb_string((char*) name_string))) {
64-
return create_html_attribute_node(name_string, NULL, start_position, end_position, allocator);
228+
return create_html_attribute_node_precise(name_string, NULL, positions, allocator);
65229
}
66-
return create_html_attribute_node(name_string, "true", start_position, end_position, allocator);
230+
return create_html_attribute_node_precise(name_string, "true", positions, allocator);
67231
} else if (value_node->type == PM_FALSE_NODE) {
68232
if (is_boolean_attribute(hb_string((char*) name_string))) { return NULL; }
69-
return create_html_attribute_node(name_string, "false", start_position, end_position, allocator);
233+
return create_html_attribute_node_precise(name_string, "false", positions, allocator);
70234
} else if (value_node->type == PM_INTERPOLATED_STRING_NODE) {
71235
return create_html_attribute_with_interpolated_value(
72236
name_string,
73237
(pm_interpolated_string_node_t*) value_node,
74-
start_position,
75-
end_position,
238+
positions->name_start,
239+
positions->value_end,
76240
allocator
77241
);
78242
} else {
@@ -81,7 +245,7 @@ static AST_HTML_ATTRIBUTE_NODE_T* create_attribute_from_value(
81245

82246
if (ruby_content && value_node->location.start) {
83247
AST_HTML_ATTRIBUTE_NODE_T* attribute =
84-
create_html_attribute_with_ruby_literal(name_string, ruby_content, start_position, end_position, allocator);
248+
create_html_attribute_with_ruby_literal_precise(name_string, ruby_content, positions, allocator);
85249
hb_allocator_dealloc(allocator, ruby_content);
86250
return attribute;
87251
}
@@ -102,23 +266,24 @@ AST_HTML_ATTRIBUTE_NODE_T* extract_html_attribute_from_assoc(
102266
char* name_string = extract_string_from_prism_node(assoc->key, allocator);
103267
if (!name_string) { return NULL; }
104268

105-
position_T start_position =
106-
prism_location_to_position_with_offset(&assoc->key->location, original_source, erb_content_offset, source);
107-
108269
if (!assoc->value) {
109270
hb_allocator_dealloc(allocator, name_string);
110-
111271
return NULL;
112272
}
113273

114274
if (assoc->value->type == PM_IMPLICIT_NODE) {
275+
pm_location_t name_loc;
276+
extract_key_name_location(assoc->key, &name_loc);
277+
position_T name_start, name_end;
278+
compute_position_pair(&name_loc, source, original_source, erb_content_offset, &name_start, &name_end);
279+
115280
char* dashed_name = convert_underscores_to_dashes(name_string);
116281

117282
AST_HTML_ATTRIBUTE_NODE_T* attribute = create_html_attribute_with_ruby_literal(
118283
dashed_name ? dashed_name : name_string,
119284
name_string,
120-
start_position,
121-
start_position,
285+
name_start,
286+
name_start,
122287
allocator
123288
);
124289

@@ -128,9 +293,6 @@ AST_HTML_ATTRIBUTE_NODE_T* extract_html_attribute_from_assoc(
128293
return attribute;
129294
}
130295

131-
position_T end_position =
132-
prism_location_to_position_with_offset(&assoc->value->location, original_source, erb_content_offset, source);
133-
134296
// Rails converts `method:` and `remote:` to `data-*` attributes
135297
if (strcmp(name_string, "method") == 0 || strcmp(name_string, "remote") == 0) {
136298
size_t name_len = strlen(name_string);
@@ -146,14 +308,12 @@ AST_HTML_ATTRIBUTE_NODE_T* extract_html_attribute_from_assoc(
146308
return NULL;
147309
}
148310

311+
attribute_positions_T positions;
312+
fill_attribute_positions(assoc, source, original_source, erb_content_offset, &positions);
313+
149314
char* dashed_name = convert_underscores_to_dashes(name_string);
150-
AST_HTML_ATTRIBUTE_NODE_T* attribute_node = create_attribute_from_value(
151-
dashed_name ? dashed_name : name_string,
152-
assoc->value,
153-
start_position,
154-
end_position,
155-
allocator
156-
);
315+
AST_HTML_ATTRIBUTE_NODE_T* attribute_node =
316+
create_attribute_from_value(dashed_name ? dashed_name : name_string, assoc->value, &positions, allocator);
157317

158318
if (dashed_name) { free(dashed_name); }
159319
hb_allocator_dealloc(allocator, name_string);
@@ -251,26 +411,11 @@ hb_array_T* extract_html_attributes_from_keyword_hash(
251411
hb_allocator_dealloc(allocator, raw_key);
252412

253413
if (attribute_key_string) {
254-
position_T attribute_start = prism_location_to_position_with_offset(
255-
&hash_assoc->key->location,
256-
original_source,
257-
erb_content_offset,
258-
source
259-
);
260-
position_T attribute_end = prism_location_to_position_with_offset(
261-
&hash_assoc->value->location,
262-
original_source,
263-
erb_content_offset,
264-
source
265-
);
266-
267-
AST_HTML_ATTRIBUTE_NODE_T* attribute = create_attribute_from_value(
268-
attribute_key_string,
269-
hash_assoc->value,
270-
attribute_start,
271-
attribute_end,
272-
allocator
273-
);
414+
attribute_positions_T hash_positions;
415+
fill_attribute_positions(hash_assoc, source, original_source, erb_content_offset, &hash_positions);
416+
417+
AST_HTML_ATTRIBUTE_NODE_T* attribute =
418+
create_attribute_from_value(attribute_key_string, hash_assoc->value, &hash_positions, allocator);
274419

275420
if (attribute) { hb_array_append(attributes, attribute); }
276421
hb_allocator_dealloc(allocator, attribute_key_string);

0 commit comments

Comments
 (0)