@@ -1268,6 +1268,82 @@ const name = "World";
12681268 }
12691269 }
12701270
1271+ #[ test]
1272+ fn parse_astro_attribute_shorthand_spans ( ) {
1273+ use oxc_ast:: ast:: JSXAttributeValue ;
1274+
1275+ let allocator = Allocator :: default ( ) ;
1276+ let source_type = SourceType :: astro ( ) ;
1277+ let source = r"<Component {prop} />" ;
1278+ let ret = Parser :: new ( & allocator, source, source_type) . parse_astro ( ) ;
1279+ assert ! ( !ret. panicked, "parser panicked: {:?}" , ret. errors) ;
1280+ assert ! ( ret. errors. is_empty( ) , "errors: {:?}" , ret. errors) ;
1281+
1282+ let JSXChild :: Element ( element) = & ret. root . body [ 0 ] else {
1283+ panic ! ( "Expected JSXChild::Element" ) ;
1284+ } ;
1285+ let JSXAttributeItem :: Attribute ( attr) = & element. opening_element . attributes [ 0 ] else {
1286+ panic ! ( "Expected Attribute, got SpreadAttribute" ) ;
1287+ } ;
1288+ let JSXAttributeName :: Identifier ( name) = & attr. name else {
1289+ panic ! ( "expected ident name" ) ;
1290+ } ;
1291+ let Some ( JSXAttributeValue :: ExpressionContainer ( container) ) = & attr. value else {
1292+ panic ! ( "expected expression container value" ) ;
1293+ } ;
1294+
1295+ assert_eq ! ( attr. span. source_text( source) , "{prop}" ) ;
1296+ assert_eq ! ( container. span. source_text( source) , "{prop}" ) ;
1297+ assert_eq ! ( name. span. source_text( source) , "prop" ) ;
1298+ }
1299+
1300+ /// Shorthand is desugared away, so this span relationship is all consumers have to detect it.
1301+ #[ test]
1302+ fn parse_astro_attribute_shorthand_is_distinguishable_by_span ( ) {
1303+ let shorthand =
1304+ [ "<C {prop} />" , "<C {prop} />" , "<C {{ a: 1 }} />" , "<C {obj?.prop} />" ] ;
1305+ let longhand = [
1306+ "<C class=\" a\" />" ,
1307+ "<C class=\" a\" />" ,
1308+ "<C\n class=\" a\" \n />" ,
1309+ "<C client:load />" ,
1310+ "<C a:b=\" c\" />" ,
1311+ "<C disabled />" ,
1312+ "<C x={y} />" ,
1313+ "<C x=`y` />" ,
1314+ ] ;
1315+
1316+ for ( source, expected) in
1317+ shorthand. iter ( ) . map ( |s| ( s, true ) ) . chain ( longhand. iter ( ) . map ( |s| ( s, false ) ) )
1318+ {
1319+ let allocator = Allocator :: default ( ) ;
1320+ let ret = Parser :: new ( & allocator, source, SourceType :: astro ( ) ) . parse_astro ( ) ;
1321+ assert ! ( !ret. panicked, "{source:?} panicked: {:?}" , ret. errors) ;
1322+ assert ! ( ret. errors. is_empty( ) , "{source:?} errors: {:?}" , ret. errors) ;
1323+
1324+ let JSXChild :: Element ( element) = & ret. root . body [ 0 ] else {
1325+ panic ! ( "{source:?}: expected JSXChild::Element" ) ;
1326+ } ;
1327+ let JSXAttributeItem :: Attribute ( attr) = & element. opening_element . attributes [ 0 ] else {
1328+ panic ! ( "{source:?}: expected Attribute" ) ;
1329+ } ;
1330+ let JSXAttributeName :: Identifier ( name) = & attr. name else {
1331+ panic ! ( "{source:?}: expected ident name" ) ;
1332+ } ;
1333+
1334+ assert_eq ! (
1335+ attr. span. start < name. span. start,
1336+ expected,
1337+ "{source:?}: attribute span {:?} vs name span {:?}" ,
1338+ attr. span,
1339+ name. span
1340+ ) ;
1341+ if expected {
1342+ assert_eq ! ( & source[ attr. span. start as usize ..] [ ..1 ] , "{" , "{source:?}" ) ;
1343+ }
1344+ }
1345+ }
1346+
12711347 // A non-identifier `{expr}` is a shorthand whose name is the expression source text.
12721348 #[ test]
12731349 fn parse_astro_attribute_shorthand_object_expression ( ) {
0 commit comments