]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
xgettext: C: Improve heuristic for format strings.
authorBruno Haible <bruno@clisp.org>
Sun, 1 Jun 2025 22:11:27 +0000 (00:11 +0200)
committerBruno Haible <bruno@clisp.org>
Mon, 2 Jun 2025 00:21:06 +0000 (02:21 +0200)
* gettext-tools/src/format-c-parse.h (struct spec): Add field
'likely_intentional_directives'.
(format_parse_entrails): Set it to the number of directives that don't contain
a space.
* gettext-tools/src/format-c.c (format_is_unlikely_intentional): Also test the
'likely_intentional_directives' field.
* gettext-tools/tests/format-c-6: New file.
* gettext-tools/tests/Makefile.am (TESTS): Add it.

gettext-tools/src/format-c-parse.h
gettext-tools/src/format-c.c
gettext-tools/tests/Makefile.am
gettext-tools/tests/format-c-6 [new file with mode: 0755]

index 0b52d4081c1c1e1f8354d611fff8932d1ea793ea..c0ea33395db2b75b090fcad64d82a55072484f96 100644 (file)
@@ -142,6 +142,11 @@ struct unnumbered_arg
 struct spec
 {
   unsigned int directives;
+  /* We consider a directive as "likely intentional" if it does not contain a
+     space.  This prevents xgettext from flagging strings like "100% complete"
+     as 'c-format' if they don't occur in a context that requires a format
+     string.  */
+  unsigned int likely_intentional_directives;
   unsigned int unnumbered_arg_count;
   struct unnumbered_arg *unnumbered;
   bool unlikely_intentional;
@@ -182,6 +187,7 @@ format_parse_entrails (const char *format, bool translated,
   unsigned int allocated;
 
   spec.directives = 0;
+  spec.likely_intentional_directives = 0;
   spec.unnumbered_arg_count = 0;
   spec.unnumbered = NULL;
   spec.unlikely_intentional = false;
@@ -202,6 +208,7 @@ format_parse_entrails (const char *format, bool translated,
         format_arg_type_t integer_size;
         /* Relevant for the conversion characters a, A, e, E, f, F, g, G.  */
         format_arg_type_t floatingpoint_size;
+        bool likely_intentional = true;
 
         FDI_SET (format - 1, FMTDIR_START);
         spec.directives++;
@@ -236,7 +243,11 @@ format_parse_entrails (const char *format, bool translated,
           {
             if (*format == ' ' || *format == '+' || *format == '-'
                 || *format == '#' || *format == '0' || *format == '\'')
-              format++;
+              {
+                if (*format == ' ')
+                  likely_intentional = false;
+                format++;
+              }
 #if HANDLE_I_FLAG
             else if (translated && *format == 'I')
               {
@@ -858,6 +869,8 @@ format_parse_entrails (const char *format, bool translated,
               }
           }
 
+        if (likely_intentional)
+          spec.likely_intentional_directives++;
         FDI_SET (format, FMTDIR_END);
 
         format++;
index b30e1c6f0880710c07a1dfbccd801c58b36e8af5..7092b74bfa1a99f1ecd9f2ec4681494fb014ba8d 100644 (file)
@@ -1,5 +1,5 @@
 /* C format strings.
-   Copyright (C) 2001-2004, 2006-2007, 2009-2010, 2019, 2023 Free Software Foundation, Inc.
+   Copyright (C) 2001-2025 Free Software Foundation, Inc.
    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
 
    This program is free software: you can redistribute it and/or modify
@@ -105,20 +105,20 @@ format_free (void *descr)
   free (spec);
 }
 
-static bool
-format_is_unlikely_intentional (void *descr)
+static int
+format_get_number_of_directives (void *descr)
 {
   struct spec *spec = (struct spec *) descr;
 
-  return spec->unlikely_intentional;
+  return spec->directives;
 }
 
-static int
-format_get_number_of_directives (void *descr)
+static bool
+format_is_unlikely_intentional (void *descr)
 {
   struct spec *spec = (struct spec *) descr;
 
-  return spec->directives;
+  return spec->likely_intentional_directives == 0 || spec->unlikely_intentional;
 }
 
 static bool
index 7801c9631e3f01f8602a905d853759e7926ee889..11286f09420687b24827df15b835a005f3f875d0 100644 (file)
@@ -197,7 +197,7 @@ TESTS = gettext-1 gettext-2 \
        xgettext-ycp-stackovfl-3 xgettext-ycp-stackovfl-4 \
        format-awk-1 format-awk-2 format-awk-3 \
        format-boost-1 format-boost-2 \
-       format-c-1 format-c-2 format-c-3 format-c-4 format-c-5 \
+       format-c-1 format-c-2 format-c-3 format-c-4 format-c-5 format-c-6 \
        format-c++-brace-1 format-c++-brace-2 \
        format-csharp-1 format-csharp-2 \
        format-d-1 format-d-2 \
diff --git a/gettext-tools/tests/format-c-6 b/gettext-tools/tests/format-c-6
new file mode 100755 (executable)
index 0000000..ac4a399
--- /dev/null
@@ -0,0 +1,47 @@
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test heuristic recognition of C format strings like "100% complete".
+
+cat <<\EOF > f-c-6.c
+gettext("Test 1a is 100% complete");
+printf(gettext("Test 1b is 100% complete"), 120);
+gettext("Test 2a from 0% complete to 100% complete");
+printf(gettext("Test 2b from 0% complete to 100% complete"), 120, 121);
+gettext("Test 3a of %s is 100% complete");
+printf(gettext("Test 3b of %s is 100% complete"), "foo", 120);
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --omit-header --no-location -d f-c-6.tmp f-c-6.c || Exit 1
+LC_ALL=C tr -d '\r' < f-c-6.tmp.po > f-c-6.po || Exit 1
+
+cat <<\EOF > f-c-6.ok
+msgid "Test 1a is 100% complete"
+msgstr ""
+
+#, c-format
+msgid "Test 1b is 100% complete"
+msgstr ""
+
+msgid "Test 2a from 0% complete to 100% complete"
+msgstr ""
+
+#, c-format
+msgid "Test 2b from 0% complete to 100% complete"
+msgstr ""
+
+#, c-format
+msgid "Test 3a of %s is 100% complete"
+msgstr ""
+
+#, c-format
+msgid "Test 3b of %s is 100% complete"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} f-c-6.ok f-c-6.po
+result=$?
+
+exit $result