]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
xgettext: JavaScript: Recognize template literals inside JSX.
authorBruno Haible <bruno@clisp.org>
Thu, 26 Sep 2024 23:11:15 +0000 (01:11 +0200)
committerBruno Haible <bruno@clisp.org>
Thu, 26 Sep 2024 23:11:15 +0000 (01:11 +0200)
Reported by whiteinge <gnu-savannah@eseth.com>
at <https://savannah.gnu.org/bugs/?58407>
and by tuberry and Gianni Lerro <glerro@pm.me>
at <https://savannah.gnu.org/bugs/?62203>.

* gettext-tools/src/x-javascript.c (enum level_ty): New enum.
(levels, levels_alloc, level): New variables.
(new_level): New function.
(level_type): New macro.
(template_literal_depth, brace_depths, brace_depths_alloc): Remove variables.
(new_brace_depth_level): Remove function.
(xml_element_depth, inside_embedded_js_in_xml): Remove variables.
(phase5_get): Use the levels stack instead of the separate stack for braces and
XML.
(extract_javascript): Update.
* gettext-tools/tests/xgettext-javascript-6: Add test cases that mix JSX with
template literals.

gettext-tools/src/x-javascript.c
gettext-tools/tests/xgettext-javascript-6

index b408e1b8a5f5fc19bf589bb3ecd5e502f2b73501..024b7b36460ad3932f3c3692b287fb0ed0508e82 100644 (file)
@@ -991,33 +991,51 @@ phase5_scan_regexp (void)
     phase2_ungetc (c);
 }
 
-/* Number of open template literals `...${  */
-static int template_literal_depth;
-
-/* Number of open '{' tokens, at each template literal level.
-   The "current" element is brace_depths[template_literal_depth].  */
-static int *brace_depths;
-/* Number of allocated elements in brace_depths.  */
-static size_t brace_depths_alloc;
-
-/* Adds a new brace_depths level after template_literal_depth was
-   incremented.  */
+/* Various syntactic constructs can be nested:
+     - braces in expressions       {
+     - template literals           `...${
+     - XML elements                <tag>
+     - embedded JavaScript in XML  {
+   For a well-formed program:
+     - expressions must have balanced braces;
+     - template literals must be closed before the embedded JavaScript is closed;
+     - the embedded JavaScript must be closed before the XML element is closed;
+     - and so on.
+   Therefore we can represent these nested syntactic constructs with a stack;
+   each element is a new level.  */
+enum level_ty
+{
+  level_brace              = 1,
+  level_template_literal   = 2,
+  level_xml_element        = 3,
+  level_embedded_js_in_xml = 4
+};
+/* The level stack.  */
+static unsigned char *levels /* = NULL */;
+/* Number of allocated elements in levels.  */
+static size_t levels_alloc /* = 0 */;
+/* Number of currently used elements in levels.  */
+static size_t level;
+
+/* Adds a new level.  */
 static void
-new_brace_depth_level (void)
+new_level (enum level_ty l)
 {
-  if (template_literal_depth == brace_depths_alloc)
+  if (level == levels_alloc)
     {
-      brace_depths_alloc = 2 * brace_depths_alloc + 1;
-      /* Now template_literal_depth < brace_depths_alloc.  */
-      brace_depths =
-        (int *) xrealloc (brace_depths, brace_depths_alloc * sizeof (int));
+      levels_alloc = 2 * levels_alloc + 1;
+      /* Now level < levels_alloc.  */
+      levels =
+        (unsigned char *)
+        xrealloc (levels, levels_alloc * sizeof (unsigned char));
     }
-  brace_depths[template_literal_depth] = 0;
+  levels[level++] = l;
 }
 
-/* Number of open XML elements.  */
-static int xml_element_depth;
-static bool inside_embedded_js_in_xml;
+/* Returns the current level's type,
+   as one of the level_* enum items, or 0 if the level stack is empty.  */
+#define level_type() \
+  (level > 0 ? levels[level - 1] : 0)
 
 /* Parses some XML markup.
    Returns 0 for an XML comment,
@@ -1292,8 +1310,7 @@ phase5_get (token_ty *tp)
                   {
                     mixed_string_buffer_destroy (&msb);
                     tp->type = last_token_type = token_type_ltemplate;
-                    template_literal_depth++;
-                    new_brace_depth_level ();
+                    new_level (level_template_literal);
                     break;
                   }
 
@@ -1334,8 +1351,8 @@ phase5_get (token_ty *tp)
                - XMLMarkup and XMLElement are not allowed after an expression,
                - embedded JavaScript expressions in XML do not recurse.
              */
-            if (xml_element_depth > 0
-                || (!inside_embedded_js_in_xml
+            if (level_type () == level_xml_element
+                || (level_type () != level_embedded_js_in_xml
                     && ! is_after_expression ()))
               {
                 /* Recognize XML markup: XML comment, CDATA, Processing
@@ -1363,7 +1380,7 @@ phase5_get (token_ty *tp)
                     /* Opening element.  */
                     phase2_ungetc (c);
                     lexical_context = lc_xml_open_tag;
-                    xml_element_depth++;
+                    new_level (level_xml_element);
                   }
                 tp->type = last_token_type = token_type_xml_tag;
               }
@@ -1373,7 +1390,7 @@ phase5_get (token_ty *tp)
           return;
 
         case '>':
-          if (xml_element_depth > 0 && !inside_embedded_js_in_xml)
+          if (level_type () == level_xml_element)
             {
               switch (lexical_context)
                 {
@@ -1383,7 +1400,8 @@ phase5_get (token_ty *tp)
                   return;
 
                 case lc_xml_close_tag:
-                  if (--xml_element_depth > 0)
+                  level--;
+                  if (memchr (levels, level_xml_element, level) != NULL)
                     lexical_context = lc_xml_content;
                   else
                     lexical_context = lc_outside;
@@ -1398,7 +1416,7 @@ phase5_get (token_ty *tp)
           return;
 
         case '/':
-          if (xml_element_depth > 0 && !inside_embedded_js_in_xml)
+          if (level_type () == level_xml_element)
             {
               /* If it appears in an opening tag of an XML element, it's
                  part of '/>'.  */
@@ -1407,7 +1425,8 @@ phase5_get (token_ty *tp)
                   c = phase2_getc ();
                   if (c == '>')
                     {
-                      if (--xml_element_depth > 0)
+                      level--;
+                      if (memchr (levels, level_xml_element, level) != NULL)
                         lexical_context = lc_xml_content;
                       else
                         lexical_context = lc_outside;
@@ -1432,19 +1451,19 @@ phase5_get (token_ty *tp)
           return;
 
         case '{':
-          if (xml_element_depth > 0 && !inside_embedded_js_in_xml)
-            inside_embedded_js_in_xml = true;
+          if (level_type () == level_xml_element)
+            new_level (level_embedded_js_in_xml);
           else
-            brace_depths[template_literal_depth]++;
+            new_level (level_brace);
           tp->type = last_token_type = token_type_lbrace;
           return;
 
         case '}':
-          if (xml_element_depth > 0 && inside_embedded_js_in_xml)
-            inside_embedded_js_in_xml = false;
-          else if (brace_depths[template_literal_depth] > 0)
-            brace_depths[template_literal_depth]--;
-          else if (template_literal_depth > 0)
+          if (level_type () == level_embedded_js_in_xml)
+            level--;
+          else if (level_type () == level_brace)
+            level--;
+          else if (level_type () == level_template_literal)
             {
               /* Middle or right part of template literal.  */
               for (;;)
@@ -1454,7 +1473,7 @@ phase5_get (token_ty *tp)
                   if (uc == P7_EOF || uc == P7_STRING_END)
                     {
                       tp->type = last_token_type = token_type_rtemplate;
-                      template_literal_depth--;
+                      level--;
                       break;
                     }
 
@@ -1886,10 +1905,7 @@ extract_javascript (FILE *f,
   phase5_pushback_length = 0;
   last_token_type = token_type_start;
 
-  template_literal_depth = 0;
-  new_brace_depth_level ();
-  xml_element_depth = 0;
-  inside_embedded_js_in_xml = false;
+  level = 0;
 
   flag_context_list_table = flag_table;
   paren_nesting_depth = 0;
index da2fe60b3e6ffb108ec57c144a2d4a24f1ec2aaf..e4df485f9a16dbe180fa36dfd5455bd7d05bc1ed 100755 (executable)
@@ -48,6 +48,22 @@ function foo() {
   return <a>{ 'b' }</a>;
 }
 var s10 = _("Expected translation string #9");
+// Mixing JSX with template literals.
+var s11 = 0;
+var s12 = (
+  <div>
+    _("Expected translation string #10");
+    {`${ _("Expected translation string #11") }`}
+    _("Expected translation string #12");
+  </div>
+);
+var s13 = _("Expected translation string #13");
+var s14 = <div className={`${ _("Expected translation string #14") }`} /> ;
+var s15 = _("Expected translation string #15");
+var s16 = { a: 1, b: <div className={`${ _("Expected translation string #16") }`} /> };
+var s17 = _("Expected translation string #17");
+var s18 = `begin${ <div> _("Expected translation string #18") </div> }end`;
+var s19 = _("Expected translation string #19");
 EOF
 
 : ${XGETTEXT=xgettext}
@@ -103,6 +119,36 @@ msgstr ""
 
 msgid "Expected translation string #9"
 msgstr ""
+
+msgid "Expected translation string #10"
+msgstr ""
+
+msgid "Expected translation string #11"
+msgstr ""
+
+msgid "Expected translation string #12"
+msgstr ""
+
+msgid "Expected translation string #13"
+msgstr ""
+
+msgid "Expected translation string #14"
+msgstr ""
+
+msgid "Expected translation string #15"
+msgstr ""
+
+msgid "Expected translation string #16"
+msgstr ""
+
+msgid "Expected translation string #17"
+msgstr ""
+
+msgid "Expected translation string #18"
+msgstr ""
+
+msgid "Expected translation string #19"
+msgstr ""
 EOF
 
 : ${DIFF=diff}