]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
xgettext: Allow inhibiting the warnings about URLs and email addresses.
authorBruno Haible <bruno@clisp.org>
Sat, 10 Jan 2026 19:16:46 +0000 (20:16 +0100)
committerBruno Haible <bruno@clisp.org>
Sat, 10 Jan 2026 19:16:46 +0000 (20:16 +0100)
Reported at <https://savannah.gnu.org/bugs/?67672>.

* gettext-tools/src/message.h (enum syntax_check_type): Add sc_url, sc_email.
(NSYNTAXCHECKS): Increase by 2.
* gettext-tools/src/message.c (syntax_check_name): Update.
* gettext-tools/src/xgettext.h (default_syntax_check): Add comment.
* gettext-tools/src/xgettext.c (default_syntax_check): Add initializer.
(main): Recognize --no-check option.
(usage): Document --no-check option.
* gettext-tools/src/xg-message.c (decide_syntax_check): Assume that
default_syntax_check[i] != undecided.
* gettext-tools/src/xg-check.c (syntax_check_function): Remove the second
argument.
(string_has_ascii_ellipsis): New function, extracted from
syntax_check_ellipsis_unicode.
(message_has_ascii_ellipsis): New function.
(syntax_check_ellipsis_unicode): Remove the second argument. Simplify. Emit only
a single error for both msgid and msgid_plural.
(string_has_space_ellipsis): New function, extracted from
syntax_check_space_ellipsis.
(message_has_space_ellipsis): New function.
(syntax_check_space_ellipsis): Remove the second argument. Simplify. Emit only
a single error for both msgid and msgid_plural.
(syntax_check_quote_unicode): Remove the second argument.
(syntax_check_bullet_unicode_string): New function, extracted from
syntax_check_bullet_unicode.
(syntax_check_bullet_unicode): Remove the second argument. Simplify.
(string_has_url): Don't recognize 'mailto:' URLs.
(syntax_check_url, syntax_check_email): New functions, extracted from
url_check_message.
(url_check_message): Remove function.
(sc_funcs): Add syntax_check_url, syntax_check_email.
(syntax_check_message): Simplify.
(xgettext_check_message_list): Don't invoke url_check_message.
* gettext-tools/tests/xgettext-14: Update after xg-check.c changesd.
* gettext-tools/tests/xgettext-20: Add more testcases.
* gettext-tools/doc/xgettext.texi: Document the --no-check option.
* gettext-tools/doc/gettext.texi: Bump copyright year.
* NEWS: Mention the change.

NEWS
gettext-tools/doc/gettext.texi
gettext-tools/doc/xgettext.texi
gettext-tools/src/message.c
gettext-tools/src/message.h
gettext-tools/src/xg-check.c
gettext-tools/src/xg-message.c
gettext-tools/src/xgettext.c
gettext-tools/src/xgettext.h
gettext-tools/tests/xgettext-14
gettext-tools/tests/xgettext-20

diff --git a/NEWS b/NEWS
index 43543a287ba2d182217fb0ecac7e9b8ac54a078a..e08116b02db083b7786c57644b6c4a95b351c2c7 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -43,6 +43,16 @@ Version 1.0 - December 2025
       message.
     - The documentation has a new chapter "Pretranslation".
 
+# Improvements for maintainers:
+  * xgettext:
+    - The refactoring suggestion when a translatable string contains an URL
+      or email address can now be inhibited through a command-line option
+      '--no-check=url' or '--no-check=email', or through a comment in the
+      source code of the form
+          /* xgettext: no-url-check */
+      or
+          /* xgettext: no-email-check */
+
 # Programming languages support:
   * OCaml:
     - xgettext now supports OCaml.
index f2f09586180884ca966e1f3e4e841dc5cb62753d..2929ef5db7a44219a4879e284fc3fbe08edc5f5f 100644 (file)
@@ -94,7 +94,7 @@ This file provides documentation for GNU @code{gettext} utilities.
 It also serves as a reference for the free Translation Project.
 
 @copying
-Copyright (C) 1995-1998, 2001-2025 Free Software Foundation, Inc.
+Copyright (C) 1995-1998, 2001-2026 Free Software Foundation, Inc.
 
 This manual is free documentation.  It is dually licensed under the
 GNU FDL and the GNU GPL.  This means that you can redistribute this
@@ -129,7 +129,7 @@ A copy of the license is included in @ref{GNU GPL}.
 @page
 @vskip 0pt plus 1filll
 @c @insertcopying
-Copyright (C) 1995-1998, 2001-2025 Free Software Foundation, Inc.
+Copyright (C) 1995-1998, 2001-2026 Free Software Foundation, Inc.
 
 This manual is free documentation.  It is dually licensed under the
 GNU FDL and the GNU GPL.  This means that you can redistribute this
index b089aa629ebd8402dd50457a14ab12b4a7efe6ea..e2f32c5b488bc8c31b330835f121cd9637c3cb32 100644 (file)
@@ -1,5 +1,5 @@
 @c This file is part of the GNU gettext manual.
-@c Copyright (C) 1995-2025 Free Software Foundation, Inc.
+@c Copyright (C) 1995-2026 Free Software Foundation, Inc.
 @c See the file gettext.texi for copying conditions.
 
 @pindex xgettext
@@ -188,8 +188,8 @@ the line with the string there is merely a blank line.
 @item --check[=@var{CHECK}]
 @opindex --check@r{, @code{xgettext} option}
 @cindex supported syntax checks, @code{xgettext}
-Perform a syntax check on msgid and msgid_plural.  The supported checks
-are:
+Perform a syntax check on msgid and msgid_plural.
+The supported checks that are disabled by default and that can be enabled are:
 
 @table @samp
 @item ellipsis-unicode
@@ -223,11 +223,37 @@ where @var{name} is the name of a valid syntax check.  If a flag is
 prefixed by @code{no-}, the meaning is negated.
 
 Some tests apply the checks to each sentence within the msgid, rather
-than the whole string.  xgettext detects the end of sentence by
+than the whole string.  @code{xgettext} detects the end of sentence by
 performing a pattern match, which usually looks for a period followed by
 a certain number of spaces.  The number is specified with the
 @code{--sentence-end} option.
 
+@item --no-check[=@var{CHECK}]
+@opindex --no-check@r{, @code{xgettext} option}
+Don't perform a syntax check on msgid and msgid_plural
+that is enabled by default.
+The supported checks that are enabled by default are:
+
+@table @samp
+@item url
+Prohibit a URL inside a string.
+
+@item email
+Prohibit an email address inside a string.
+
+@end table
+
+The option has an effect on all input files.
+To disable a check for a certain string,
+you can mark it with an @code{xgettext:} special comment in the source file.
+For example, if you want to suppress the check on a particular string,
+add the following comment:
+
+@example
+/* xgettext: no-email-check */
+gettext ("Specify your@@email-address.com here.");
+@end example
+
 @item --sentence-end[=@var{TYPE}]
 @opindex --sentence-end@r{, @code{xgettext} option}
 @cindex sentence end markers, @code{xgettext}
index 48c4b6b9cf8e0349e0a7870728500cfd26654b09..f3127f47dc6f29a2731d556fb193d283b24d4d54 100644 (file)
@@ -1,5 +1,5 @@
 /* GNU gettext - internationalization aids
-   Copyright (C) 1995-2025 Free Software Foundation, Inc.
+   Copyright (C) 1995-2026 Free Software Foundation, Inc.
 
    This file was written by Peter Miller <millerp@canb.auug.org.au>
 
@@ -175,7 +175,9 @@ const char *const syntax_check_name[NSYNTAXCHECKS] =
   /* sc_ellipsis_unicode */     "ellipsis-unicode",
   /* sc_space_ellipsis */       "space-ellipsis",
   /* sc_quote_unicode */        "quote-unicode",
-  /* sc_bullet_unicode */       "bullet-unicode"
+  /* sc_bullet_unicode */       "bullet-unicode",
+  /* sc_url */                  "url",
+  /* sc_email */                "email"
 };
 
 
index b37e4890cdf6ce242794103e927a9334bede0eed..52a5ce4bebd300355ad35e7e401cef8573f0f24b 100644 (file)
@@ -1,5 +1,5 @@
 /* GNU gettext - internationalization aids
-   Copyright (C) 1995-2025 Free Software Foundation, Inc.
+   Copyright (C) 1995-2026 Free Software Foundation, Inc.
 
    This file was written by Peter Miller <millerp@canb.auug.org.au>
 
@@ -134,9 +134,11 @@ enum syntax_check_type
   sc_ellipsis_unicode,
   sc_space_ellipsis,
   sc_quote_unicode,
-  sc_bullet_unicode
+  sc_bullet_unicode,
+  sc_url,
+  sc_email
 };
-#define NSYNTAXCHECKS 4
+#define NSYNTAXCHECKS 6
 extern LIBGETTEXTSRC_DLL_VARIABLE const char *const syntax_check_name[NSYNTAXCHECKS];
 
 /* Is current msgid subject to a syntax check?  */
index b9e53b4a195a673e39d7e5fdfd56c86228e7b2ce..7c41f157600c1ed3e653a0d45c256f66842fad1b 100644 (file)
@@ -1,5 +1,5 @@
 /* Checking of messages in POT files: so-called "syntax checks".
-   Copyright (C) 2015-2025 Free Software Foundation, Inc.
+   Copyright (C) 2015-2026 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 
 /* Function that implements a single syntax check.
    MP is a message.
-   MSGID is either MP->msgid or MP->msgid_plural.
    Returns the number of errors that were seen and reported.  */
-typedef int (* syntax_check_function) (const message_ty *mp, const char *msgid);
+typedef int (* syntax_check_function) (const message_ty *mp);
 
 
-/* Implementation of the sc_ellipsis_unicode syntax check.  */
+/* ----- Implementation of the sc_ellipsis_unicode syntax check. ----- */
 
-static int
-syntax_check_ellipsis_unicode (const message_ty *mp, const char *msgid)
+/* Determine whether a string (msgid or msgid_plural) contains an ASCII
+   ellipsis.  */
+static bool
+string_has_ascii_ellipsis (const char *string)
 {
-  int seen_errors = 0;
-  {
-    const char *str = msgid;
-    const char *str_limit = str + strlen (msgid);
-    while (str < str_limit)
-      {
-        ucs4_t ending_char;
-        const char *end = sentence_end (str, &ending_char);
+  const char *str = string;
+  const char *str_limit = str + strlen (string);
+  while (str < str_limit)
+    {
+      ucs4_t ending_char;
+      const char *end = sentence_end (str, &ending_char);
 
-        /* sentence_end doesn't treat '...' specially.  */
-        const char *cp = end - (ending_char == '.' ? 2 : 3);
+      /* sentence_end doesn't treat '...' specially.  */
+      const char *cp = end - (ending_char == '.' ? 2 : 3);
 
-        if (cp >= str && memcmp (cp, "...", 3) == 0)
-          {
-            po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
-                       _("ASCII ellipsis ('...') instead of Unicode"));
-            seen_errors++;
-          }
+      if (cp >= str && memcmp (cp, "...", 3) == 0)
+        return true;
 
-        str = end + 1;
-      }
-  }
+      str = end + 1;
+    }
+  return false;
+}
 
-  return seen_errors;
+/* Determine whether a message contains an ASCII ellipsis.  */
+static bool
+message_has_ascii_ellipsis (const message_ty *mp)
+{
+  return string_has_ascii_ellipsis (mp->msgid)
+         || (mp->msgid_plural != NULL
+             && string_has_ascii_ellipsis (mp->msgid_plural));
 }
 
+static int
+syntax_check_ellipsis_unicode (const message_ty *mp)
+{
+  if (message_has_ascii_ellipsis (mp))
+    {
+      po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
+                 _("ASCII ellipsis ('...') instead of Unicode"));
+      return 1;
+    }
 
-/* Implementation of the sc_space_ellipsis syntax check.  */
+  return 0;
+}
 
-static int
-syntax_check_space_ellipsis (const message_ty *mp, const char *msgid)
+
+/* ----- Implementation of the sc_space_ellipsis syntax check. ----- */
+
+/* Determine whether a string (msgid or msgid_plural) contains a space before
+   an ellipsis.  */
+static bool
+string_has_space_ellipsis (const char *string)
 {
-  int seen_errors = 0;
-  {
-    const char *str = msgid;
-    const char *str_limit = str + strlen (msgid);
-    while (str < str_limit)
-      {
-        ucs4_t ending_char;
-        const char *end = sentence_end (str, &ending_char);
+  const char *str = string;
+  const char *str_limit = str + strlen (string);
+  while (str < str_limit)
+    {
+      ucs4_t ending_char;
+      const char *end = sentence_end (str, &ending_char);
 
-        const char *ellipsis = NULL;
-        if (ending_char == 0x2026)
-          ellipsis = end;
-        else if (ending_char == '.')
-          {
-            /* sentence_end doesn't treat '...' specially.  */
-            const char *cp = end - 2;
-            if (cp >= str && memcmp (cp, "...", 3) == 0)
-              ellipsis = cp;
-          }
-        else
-          {
-            /* Look for a '...'.  */
-            const char *cp = end - 3;
-            if (cp >= str && memcmp (cp, "...", 3) == 0)
-              ellipsis = cp;
-            else
-              {
-                /* Look for a U+2026.  */
-                ucs4_t uc = 0xfffd;
-                for (cp = end - 1; cp >= str; cp--)
-                  {
-                    u8_mbtouc (&uc, (const unsigned char *) cp, end - cp);
-                    if (uc != 0xfffd)
-                      break;
-                  }
+      const char *ellipsis = NULL;
+      if (ending_char == 0x2026)
+        ellipsis = end;
+      else if (ending_char == '.')
+        {
+          /* sentence_end doesn't treat '...' specially.  */
+          const char *cp = end - 2;
+          if (cp >= str && memcmp (cp, "...", 3) == 0)
+            ellipsis = cp;
+        }
+      else
+        {
+          /* Look for a '...'.  */
+          const char *cp = end - 3;
+          if (cp >= str && memcmp (cp, "...", 3) == 0)
+            ellipsis = cp;
+          else
+            {
+              /* Look for a U+2026.  */
+              ucs4_t uc = 0xfffd;
+              for (cp = end - 1; cp >= str; cp--)
+                {
+                  u8_mbtouc (&uc, (const unsigned char *) cp, end - cp);
+                  if (uc != 0xfffd)
+                    break;
+                }
 
-                if (uc == 0x2026)
-                  ellipsis = cp;
-              }
-          }
+              if (uc == 0x2026)
+                ellipsis = cp;
+            }
+        }
 
-        if (ellipsis)
-          {
-            /* Look at the character before ellipsis.  */
-            ucs4_t uc = 0xfffd;
-            for (const char *cp = ellipsis - 1; cp >= str; cp--)
-              {
-                u8_mbtouc (&uc, (const unsigned char *) cp, ellipsis - cp);
-                if (uc != 0xfffd)
-                  break;
-              }
+      if (ellipsis)
+        {
+          /* Look at the character before ellipsis.  */
+          ucs4_t uc = 0xfffd;
+          for (const char *cp = ellipsis - 1; cp >= str; cp--)
+            {
+              u8_mbtouc (&uc, (const unsigned char *) cp, ellipsis - cp);
+              if (uc != 0xfffd)
+                break;
+            }
 
-            if (uc != 0xfffd && uc_is_space (uc))
-              {
-                po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
-                           _("space before ellipsis found in user visible strings"));
-                seen_errors++;
-              }
-          }
+          if (uc != 0xfffd && uc_is_space (uc))
+            return true;
+        }
 
-        str = end + 1;
-      }
-  }
+      str = end + 1;
+    }
+  return false;
+}
 
-  return seen_errors;
+/* Determine whether a message contains a space before an ellipsis.  */
+static bool
+message_has_space_ellipsis (const message_ty *mp)
+{
+  return string_has_space_ellipsis (mp->msgid)
+         || (mp->msgid_plural != NULL
+             && string_has_space_ellipsis (mp->msgid_plural));
 }
 
+static int
+syntax_check_space_ellipsis (const message_ty *mp)
+{
+  if (message_has_space_ellipsis (mp))
+    {
+      po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
+                 _("space before ellipsis found in user visible string"));
+      return 1;
+    }
+
+  return 0;
+}
 
-/* Implementation of the sc_quote_unicode syntax check.  */
+
+/* ----- Implementation of the sc_quote_unicode syntax check. ----- */
 
 struct callback_arg
 {
@@ -189,20 +220,23 @@ syntax_check_quote_unicode_callback (char quote, const char *quoted,
 }
 
 static int
-syntax_check_quote_unicode (const message_ty *mp, const char *msgid)
+syntax_check_quote_unicode (const message_ty *mp)
 {
   struct callback_arg arg;
   arg.mp = mp;
   arg.seen_errors = 0;
 
-  scan_quoted (msgid, strlen (msgid),
+  scan_quoted (mp->msgid, strlen (mp->msgid),
                syntax_check_quote_unicode_callback, &arg);
+  if (mp->msgid_plural != NULL)
+    scan_quoted (mp->msgid_plural, strlen (mp->msgid_plural),
+                 syntax_check_quote_unicode_callback, &arg);
 
   return arg.seen_errors;
 }
 
 
-/* Implementation of the sc_bullet_unicode syntax check.  */
+/* ----- Implementation of the sc_bullet_unicode syntax check. ----- */
 
 struct bullet_ty
 {
@@ -220,7 +254,7 @@ struct bullet_stack_ty
 static struct bullet_stack_ty bullet_stack;
 
 static int
-syntax_check_bullet_unicode (const message_ty *mp, const char *msgid)
+syntax_check_bullet_unicode_string (const message_ty *mp, const char *msgid)
 {
   bool seen_error = false;
 
@@ -306,110 +340,18 @@ syntax_check_bullet_unicode (const message_ty *mp, const char *msgid)
   return 0;
 }
 
-
-/* List of all syntax checks.  */
-static const syntax_check_function sc_funcs[NSYNTAXCHECKS] =
-{
-  syntax_check_ellipsis_unicode,
-  syntax_check_space_ellipsis,
-  syntax_check_quote_unicode,
-  syntax_check_bullet_unicode
-};
-
-
-/* Perform all syntax checks on a non-obsolete message.
-   Return the number of errors that were seen.  */
 static int
-syntax_check_message (const message_ty *mp)
+syntax_check_bullet_unicode (const message_ty *mp)
 {
-  int seen_errors = 0;
-
-  for (int i = 0; i < NSYNTAXCHECKS; i++)
-    {
-      if (mp->do_syntax_check[i] == yes)
-        {
-          seen_errors += sc_funcs[i] (mp, mp->msgid);
-          if (mp->msgid_plural)
-            seen_errors += sc_funcs[i] (mp, mp->msgid_plural);
-        }
-    }
-
-  return seen_errors;
+  return syntax_check_bullet_unicode_string (mp, mp->msgid)
+         + (mp->msgid_plural != NULL
+            ? syntax_check_bullet_unicode_string (mp, mp->msgid_plural)
+            : 0);
 }
 
 
-/* Signal an error when checking format strings.  */
-struct formatstring_error_logger_locals
-{
-  const lex_pos_ty *pos;
-};
-static void
-formatstring_error_logger (void *data, const char *format, ...)
-#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ > 2)
-     __attribute__ ((__format__ (__printf__, 2, 3)))
-#endif
-;
-static void
-formatstring_error_logger (void *data, const char *format, ...)
-{
-  struct formatstring_error_logger_locals *l =
-    (struct formatstring_error_logger_locals *) data;
-
-  va_list args;
-  va_start (args, format);
-  if_verror (IF_SEVERITY_ERROR,
-             l->pos->file_name, l->pos->line_number, (size_t)(-1), false,
-             format, args);
-  va_end (args);
-}
-
-
-/* Perform all format checks on a non-obsolete message.
-   Return the number of errors that were seen.  */
-static int
-format_check_message (const message_ty *mp)
-{
-  int seen_errors = 0;
-
-  if (mp->msgid_plural != NULL)
-    {
-      /* Look for format string incompatibilities between msgid and
-         msgid_plural.  */
-      for (size_t i = 0; i < NFORMATS; i++)
-        if (possible_format_p (mp->is_format[i]))
-          {
-            struct formatstring_parser *parser = formatstring_parsers[i];
-            char *invalid_reason1 = NULL;
-            void *descr1 =
-              parser->parse (mp->msgid, false, NULL, &invalid_reason1);
-            char *invalid_reason2 = NULL;
-            void *descr2 =
-              parser->parse (mp->msgid_plural, false, NULL, &invalid_reason2);
-
-            if (descr1 != NULL && descr2 != NULL)
-              {
-                struct formatstring_error_logger_locals locals;
-                locals.pos = &mp->pos;
-                if (parser->check (descr2, descr1, false,
-                                   formatstring_error_logger, &locals,
-                                   "msgid_plural", "msgid"))
-                  seen_errors++;
-              }
-
-            if (descr2 != NULL)
-              parser->free (descr2);
-            else
-              free (invalid_reason2);
-            if (descr1 != NULL)
-              parser->free (descr1);
-            else
-              free (invalid_reason1);
-          }
-      }
-
-  return seen_errors;
-}
-
+/* ----- Implementation of the sc_url syntax check. ----- */
+/* This check is enabled by default.  It produces a warning, not an error.  */
 
 /* Determine whether a string (msgid or msgid_plural) contains a URL.  */
 static bool
@@ -419,7 +361,12 @@ string_has_url (const char *string)
      (not "file:").  */
   static const char *patterns[] =
   {
+    /* We can afford to be silent about 'mailto:' here, because it is
+       almost always followed by an email address, that we report though
+       the sc_email check.  */
+    #if 0
     "mailto:",
+    #endif
     "http://", "https://",
     "ftp://",
     "irc://", "ircs://"
@@ -473,6 +420,20 @@ message_has_url (const message_ty *mp)
          || (mp->msgid_plural != NULL && string_has_url (mp->msgid_plural));
 }
 
+static int
+syntax_check_url (const message_ty *mp)
+{
+  if (message_has_url (mp))
+    if_error (IF_SEVERITY_WARNING,
+              mp->pos.file_name, mp->pos.line_number, (size_t)(-1), false,
+              _("Message contains an embedded URL.  Better move it out of the translatable string, see %s"),
+              "https://www.gnu.org/software/gettext/manual/html_node/No-embedded-URLs.html");
+  return 0;
+}
+
+
+/* ----- Implementation of the sc_email syntax check. ----- */
+/* This check is enabled by default.  It produces a warning, not an error.  */
 
 /* Determine whether a string (msgid or msgid_plural) contains an
    email address.  */
@@ -551,21 +512,116 @@ message_has_email (const message_ty *mp)
          || (mp->msgid_plural != NULL && string_has_email (mp->msgid_plural));
 }
 
-
-/* Perform the URL check on a non-obsolete message.  */
-static void
-url_check_message (const message_ty *mp)
+static int
+syntax_check_email (const message_ty *mp)
 {
-  if (message_has_url (mp))
-    if_error (IF_SEVERITY_WARNING,
-              mp->pos.file_name, mp->pos.line_number, (size_t)(-1), false,
-              _("Message contains an embedded URL.  Better move it out of the translatable string, see %s"),
-              "https://www.gnu.org/software/gettext/manual/html_node/No-embedded-URLs.html");
-  else if (message_has_email (mp))
+  if (message_has_email (mp))
     if_error (IF_SEVERITY_WARNING,
               mp->pos.file_name, mp->pos.line_number, (size_t)(-1), false,
               _("Message contains an embedded email address.  Better move it out of the translatable string, see %s"),
               "https://www.gnu.org/software/gettext/manual/html_node/No-embedded-URLs.html");
+
+  return 0;
+}
+
+
+/* ---------------------- List of all syntax checks. ---------------------- */
+static const syntax_check_function sc_funcs[NSYNTAXCHECKS] =
+{
+  syntax_check_ellipsis_unicode,
+  syntax_check_space_ellipsis,
+  syntax_check_quote_unicode,
+  syntax_check_bullet_unicode,
+  syntax_check_url,
+  syntax_check_email
+};
+
+
+/* Perform all syntax checks on a non-obsolete message.
+   Return the number of errors that were seen.  */
+static int
+syntax_check_message (const message_ty *mp)
+{
+  int seen_errors = 0;
+
+  for (int i = 0; i < NSYNTAXCHECKS; i++)
+    if (mp->do_syntax_check[i] == yes)
+      seen_errors += sc_funcs[i] (mp);
+
+  return seen_errors;
+}
+
+
+/* Signal an error when checking format strings.  */
+struct formatstring_error_logger_locals
+{
+  const lex_pos_ty *pos;
+};
+static void
+formatstring_error_logger (void *data, const char *format, ...)
+#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ > 2)
+     __attribute__ ((__format__ (__printf__, 2, 3)))
+#endif
+;
+static void
+formatstring_error_logger (void *data, const char *format, ...)
+{
+  struct formatstring_error_logger_locals *l =
+    (struct formatstring_error_logger_locals *) data;
+
+  va_list args;
+  va_start (args, format);
+  if_verror (IF_SEVERITY_ERROR,
+             l->pos->file_name, l->pos->line_number, (size_t)(-1), false,
+             format, args);
+  va_end (args);
+}
+
+
+/* Perform all format checks on a non-obsolete message.
+   Return the number of errors that were seen.  */
+static int
+format_check_message (const message_ty *mp)
+{
+  int seen_errors = 0;
+
+  if (mp->msgid_plural != NULL)
+    {
+      /* Look for format string incompatibilities between msgid and
+         msgid_plural.  */
+      for (size_t i = 0; i < NFORMATS; i++)
+        if (possible_format_p (mp->is_format[i]))
+          {
+            struct formatstring_parser *parser = formatstring_parsers[i];
+            char *invalid_reason1 = NULL;
+            void *descr1 =
+              parser->parse (mp->msgid, false, NULL, &invalid_reason1);
+            char *invalid_reason2 = NULL;
+            void *descr2 =
+              parser->parse (mp->msgid_plural, false, NULL, &invalid_reason2);
+
+            if (descr1 != NULL && descr2 != NULL)
+              {
+                struct formatstring_error_logger_locals locals;
+                locals.pos = &mp->pos;
+                if (parser->check (descr2, descr1, false,
+                                   formatstring_error_logger, &locals,
+                                   "msgid_plural", "msgid"))
+                  seen_errors++;
+              }
+
+            if (descr2 != NULL)
+              parser->free (descr2);
+            else
+              free (invalid_reason2);
+            if (descr1 != NULL)
+              parser->free (descr1);
+            else
+              free (invalid_reason1);
+          }
+      }
+
+  return seen_errors;
 }
 
 
@@ -583,7 +639,6 @@ xgettext_check_message_list (message_list_ty *mlp)
       if (!is_header (mp))
         {
           seen_errors += syntax_check_message (mp) + format_check_message (mp);
-          url_check_message (mp);
         }
     }
 
index 685713adf6aebb0aa04d22ace3ed7daa2fdd3ee5..d649f20b17cb4cdb40fa1f43116d882eea87f66d 100644 (file)
@@ -1,5 +1,5 @@
 /* Extracting a message.  Accumulating the message list.
-   Copyright (C) 2001-2025 Free Software Foundation, Inc.
+   Copyright (C) 2001-2026 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -231,7 +231,7 @@ decide_syntax_check (message_ty *mp)
 {
   for (size_t i = 0; i < NSYNTAXCHECKS; i++)
     if (mp->do_syntax_check[i] == undecided)
-      mp->do_syntax_check[i] = default_syntax_check[i] == yes ? yes : no;
+      mp->do_syntax_check[i] = default_syntax_check[i];
 }
 
 
index b57519f612467b3404e88a9a4460d09bdc34620b..295932b1675a9e48f2a81511bc071dae0cab52a5 100644 (file)
@@ -1,5 +1,5 @@
 /* Extracts strings from C source file to Uniforum style .po file.
-   Copyright (C) 1995-2025 Free Software Foundation, Inc.
+   Copyright (C) 1995-2026 Free Software Foundation, Inc.
    Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
 
    This program is free software: you can redistribute it and/or modify
@@ -239,8 +239,16 @@ static bool recognize_format_kde;
 /* If true, recognize Boost format strings.  */
 static bool recognize_format_boost;
 
-/* Syntax checks enabled by default.  */
-enum is_syntax_check default_syntax_check[NSYNTAXCHECKS];
+/* Syntax checks enabled through a command-line option or by default.  */
+enum is_syntax_check default_syntax_check[NSYNTAXCHECKS] =
+{
+  /* sc_ellipsis_unicode */     no,
+  /* sc_space_ellipsis */       no,
+  /* sc_quote_unicode */        no,
+  /* sc_bullet_unicode */       no,
+  /* sc_url */                  yes,
+  /* sc_email */                yes
+};
 
 static locating_rule_list_ty *its_locating_rules;
 
@@ -391,11 +399,11 @@ main (int argc, char *argv[])
     { "force-po",           0,              no_argument,      &force_po, 1 },
     { "foreign-user",       CHAR_MAX + 2,   no_argument       },
     { "from-code",          CHAR_MAX + 3,   required_argument },
-    { "generated",          CHAR_MAX + 24,  required_argument },
+    { "generated",          CHAR_MAX + 25,  required_argument },
     { "help",               'h',            no_argument       },
     { "indent",             'i',            no_argument       },
-    { "its",                CHAR_MAX + 20,  required_argument },
-    { "itstool",            CHAR_MAX + 19,  no_argument       },
+    { "its",                CHAR_MAX + 21,  required_argument },
+    { "itstool",            CHAR_MAX + 20,  no_argument       },
     { "join-existing",      'j',            no_argument       },
     { "kde",                CHAR_MAX + 10,  no_argument       },
     { "keyword",            'k',            optional_argument },
@@ -403,8 +411,9 @@ main (int argc, char *argv[])
     { "msgid-bugs-address", CHAR_MAX + 5,   required_argument },
     { "msgstr-prefix",      'm',            optional_argument },
     { "msgstr-suffix",      'M',            optional_argument },
+    { "no-check",           CHAR_MAX + 18,  required_argument },
     { "no-escape",          'e',            no_argument       },
-    { "no-git",             CHAR_MAX + 23,  no_argument       },
+    { "no-git",             CHAR_MAX + 24,  no_argument       },
     { "no-location",        CHAR_MAX + 16,  no_argument       },
     { "no-wrap",            CHAR_MAX + 4,   no_argument       },
     { "omit-header",        0,              no_argument,      &xgettext_omit_header, 1 },
@@ -414,15 +423,15 @@ main (int argc, char *argv[])
     { "package-version",    CHAR_MAX + 13,  required_argument },
     { "properties-output",  CHAR_MAX + 6,   no_argument       },
     { "qt",                 CHAR_MAX + 9,   no_argument       },
-    { "reference",          CHAR_MAX + 22,  required_argument },
-    { "sentence-end",       CHAR_MAX + 18,  required_argument },
+    { "reference",          CHAR_MAX + 23,  required_argument },
+    { "sentence-end",       CHAR_MAX + 19,  required_argument },
     { "sort-by-file",       'F',            no_argument       },
     { "sort-output",        's',            no_argument       },
-    { "strict",             CHAR_MAX + 25,  no_argument       },
+    { "strict",             CHAR_MAX + 26,  no_argument       },
     { "string-limit",       'l',            required_argument },
     { "stringtable-output", CHAR_MAX + 7,   no_argument       },
     { "style",              CHAR_MAX + 15,  required_argument },
-    { "tag",                CHAR_MAX + 21,  required_argument },
+    { "tag",                CHAR_MAX + 22,  required_argument },
     { "trigraphs",          'T',            no_argument       },
     { "verbose",            'v',            no_argument       },
     { "version",            'V',            no_argument       },
@@ -604,7 +613,7 @@ main (int argc, char *argv[])
           sort_by_msgid = true;
           break;
 
-        case CHAR_MAX + 25: /* --strict */
+        case CHAR_MAX + 26: /* --strict */
           message_print_style_uniforum ();
           break;
 
@@ -722,7 +731,23 @@ main (int argc, char *argv[])
           }
           break;
 
-        case CHAR_MAX + 18: /* --sentence-end */
+        case CHAR_MAX + 18: /* --no-check */
+          {
+            size_t i;
+            for (i = 0; i < NSYNTAXCHECKS; i++)
+              {
+                if (strcmp (optarg, syntax_check_name[i]) == 0)
+                  {
+                    default_syntax_check[i] = no;
+                    break;
+                  }
+              }
+            if (i == NSYNTAXCHECKS)
+              error (EXIT_FAILURE, 0, _("syntax check '%s' unknown"), optarg);
+          }
+          break;
+
+        case CHAR_MAX + 19: /* --sentence-end */
           if (strcmp (optarg, "single-space") == 0)
             sentence_end_required_spaces = 1;
           else if (strcmp (optarg, "double-space") == 0)
@@ -731,27 +756,27 @@ main (int argc, char *argv[])
             error (EXIT_FAILURE, 0, _("sentence end type '%s' unknown"), optarg);
           break;
 
-        case CHAR_MAX + 19: /* --itstool */
+        case CHAR_MAX + 20: /* --itstool */
           add_itstool_comments = true;
           break;
 
-        case CHAR_MAX + 20: /* --its */
+        case CHAR_MAX + 21: /* --its */
           explicit_its_filename = optarg;
           break;
 
-        case CHAR_MAX + 21: /* --tag */
+        case CHAR_MAX + 22: /* --tag */
           x_javascript_tag (optarg);
           break;
 
-        case CHAR_MAX + 22: /* --reference */
+        case CHAR_MAX + 23: /* --reference */
           string_list_append (&files_for_vc_mtime, optarg);
           break;
 
-        case CHAR_MAX + 23: /* --no-git */
+        case CHAR_MAX + 24: /* --no-git */
           xgettext_no_git = true;
           break;
 
-        case CHAR_MAX + 24: /* --generated */
+        case CHAR_MAX + 25: /* --generated */
           gl_set_add (generated_files, optarg);
           break;
 
@@ -1212,6 +1237,9 @@ Operation mode:\n"));
                                 (ellipsis-unicode, space-ellipsis,\n\
                                  quote-unicode, bullet-unicode)\n"));
       printf (_("\
+      --no-check=NAME         don't perform syntax check on messages\n\
+                                (url, email)\n"));
+      printf (_("\
       --sentence-end=TYPE     type describing the end of sentence\n\
                                 (single-space, which is the default, \n\
                                  or double-space)\n"));
index 8d0af3bcc62dbc56800200de0a26d599d2e227b8..6511d786506a62dc93e5b43d72573423e4c4e268 100644 (file)
@@ -1,5 +1,5 @@
 /* xgettext common functions.
-   Copyright (C) 2001-2003, 2005-2006, 2008-2009, 2011, 2013-2014, 2018, 2020, 2023 Free Software Foundation, Inc.
+   Copyright (C) 2001-2026 Free Software Foundation, Inc.
    Written by Peter Miller <millerp@canb.auug.org.au>
    and Bruno Haible <haible@clisp.cons.org>, 2001.
 
@@ -53,6 +53,7 @@ extern int xgettext_omit_header;
 /* Be more verbose.  */
 extern int verbose;
 
+/* Syntax checks enabled through a command-line option or by default.  */
 extern enum is_syntax_check default_syntax_check[NSYNTAXCHECKS];
 
 
index 8035820a9d4da9bb08566f138f269a320aeae243..3afeaa195150246809dc1b75c4aec3310437cba0 100755 (executable)
@@ -20,7 +20,7 @@ EOF
 : ${XGETTEXT=xgettext}
 LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments --check=ellipsis-unicode -d xg-ellipsis-u.tmp xg-ellipsis-u.c 2>xg-ellipsis-u.err
 
-test `grep -c 'ASCII ellipsis' xg-ellipsis-u.err` = 4 || Exit 1
+test `grep -c 'ASCII ellipsis' xg-ellipsis-u.err` = 3 || Exit 1
 
 LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments --check=ellipsis-unicode --sentence-end=double-space -d xg-ellipsis-ud.tmp xg-ellipsis-u.c 2>xg-ellipsis-ud.err
 
@@ -40,7 +40,7 @@ EOF
 
 LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments --check=space-ellipsis -d xg-space-e.tmp xg-space-e.c 2>xg-space-e.err
 
-test `grep -c 'space before ellipsis' xg-space-e.err` = 3 || Exit 1
+test `grep -c 'space before ellipsis' xg-space-e.err` = 2 || Exit 1
 
 # --check=quote-unicode
 cat <<\EOF > xg-quote-u.c
index e96e2de748c08624e1c690d21134764e60e4d89e..2177cda8e5c36213865e265df07a68115f1e9cfc 100755 (executable)
@@ -9,16 +9,67 @@ cat <<\EOF > xg-test20.c
   gettext ("Report bugs to <mailto:foobar@example.com>");
   gettext ("Report bugs to: bug-foobar@gnu.org");
   gettext ("Report bugs in the bug tracker at <https://savannah.gnu.org/projects/foobar>");
+  /* xgettext: no-email-check */
+  gettext ("M2: Report bugs to <mailto:foobar@example.com>");
+  /* xgettext: no-email-check */
+  gettext ("M2: Report bugs to: bug-foobar@gnu.org");
+  /* xgettext: no-url-check */
+  gettext ("M2: Report bugs in the bug tracker at <https://savannah.gnu.org/projects/foobar>");
+  /* xgettext: no-url-check */
+  gettext ("M3: Report bugs to: bug-foobar@gnu.org");
 EOF
 
 : ${XGETTEXT=xgettext}
 LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments -d xg-test20.tmp xg-test20.c 2>xg-test20.err \
   || Exit 1
+cat xg-test20.err; echo
 
-if grep "xg-test20.c:1:.*No-embedded-URLs.html" xg-test20.err; then
+if grep "xg-test20.c:1:.*No-embedded-URLs.html" xg-test20.err >/dev/null; then
   Exit 1
 fi
 
-grep "xg-test20.c:2:.*No-embedded-URLs.html" xg-test20.err || Exit 1
-grep "xg-test20.c:3:.*No-embedded-URLs.html" xg-test20.err || Exit 1
-grep "xg-test20.c:4:.*No-embedded-URLs.html" xg-test20.err || Exit 1
+grep "xg-test20.c:2:.*No-embedded-URLs.html" xg-test20.err >/dev/null || Exit 1
+grep "xg-test20.c:3:.*No-embedded-URLs.html" xg-test20.err >/dev/null || Exit 1
+grep "xg-test20.c:4:.*No-embedded-URLs.html" xg-test20.err >/dev/null || Exit 1
+
+if grep "xg-test20.c:6:.*No-embedded-URLs.html" xg-test20.err >/dev/null; then
+  Exit 1
+fi
+if grep "xg-test20.c:8:.*No-embedded-URLs.html" xg-test20.err >/dev/null; then
+  Exit 1
+fi
+if grep "xg-test20.c:10:.*No-embedded-URLs.html" xg-test20.err >/dev/null; then
+  Exit 1
+fi
+
+grep "xg-test20.c:12:.*No-embedded-URLs.html" xg-test20.err >/dev/null || Exit 1
+
+# Likewise, with --no-check=url option:
+
+LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments --no-check=url -d xg-test20.tmp xg-test20.c 2>xg-test20a.err \
+  || Exit 1
+cat xg-test20a.err; echo
+
+grep "xg-test20.c:2:.*No-embedded-URLs.html" xg-test20a.err >/dev/null || Exit 1
+grep "xg-test20.c:3:.*No-embedded-URLs.html" xg-test20a.err >/dev/null || Exit 1
+if grep "xg-test20.c:4:.*No-embedded-URLs.html" xg-test20a.err >/dev/null; then
+  Exit 1
+fi
+grep "xg-test20.c:12:.*No-embedded-URLs.html" xg-test20a.err >/dev/null || Exit 1
+
+# Likewise, with --no-check=email option:
+
+LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments --no-check=email -d xg-test20.tmp xg-test20.c 2>xg-test20b.err \
+  || Exit 1
+cat xg-test20b.err; echo
+
+if grep "xg-test20.c:2:.*No-embedded-URLs.html" xg-test20b.err >/dev/null; then
+  Exit 1
+fi
+if grep "xg-test20.c:3:.*No-embedded-URLs.html" xg-test20b.err >/dev/null; then
+  Exit 1
+fi
+grep "xg-test20.c:4:.*No-embedded-URLs.html" xg-test20b.err >/dev/null || Exit 1
+if grep "xg-test20.c:12:.*No-embedded-URLs.html" xg-test20b.err >/dev/null; then
+  Exit 1
+fi